From patchwork Tue Dec 31 10:39:55 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 13923762 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id E754BE77188 for ; Tue, 31 Dec 2024 10:40:25 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 625CA10E647; Tue, 31 Dec 2024 10:40:25 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="cE5Bp8X6"; dkim-atps=neutral Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by gabe.freedesktop.org (Postfix) with ESMTPS id 3068F10E644 for ; Tue, 31 Dec 2024 10:40:20 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id 9E58AFF807; Tue, 31 Dec 2024 10:40:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1735641618; 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=PxBzmPPEWgBw3uis0MRm89ScUFpWvhfSJUjkNyWb73w=; b=cE5Bp8X6m2D6qBdaYiQf1+fGQemuF8ckbTmPXPSLApfMmE0IOeS5X2FfriwrvpmbDQYmFb w5r4tYOJ/zBgDwVQEnE8XZedVNcQ4E3JL8+77aIVP3QPSFmc+f6UyNtwqpHe8TPuT5Ubmc HG+7hKT7JZuF0fFz6aNb27DwU949TkHCJ5kvAMRTdhzrH2L1zCmSY3CTt9hmp6vO67ZKzZ nPqE9+PusaXhSu5nlJiG+Qnjpg9eDHZSkmtTOAYwADbLBF6THMB569W0IWolR5IKER4e+K YrNFLzEvCXCaw4SctP721FQWg0/ScUQJhkyXW6IVUdBmCzrdxj111YeYSjMZbw== From: Luca Ceresoli Date: Tue, 31 Dec 2024 11:39:55 +0100 Subject: [PATCH v5 01/10] drm/bridge: allow bridges to be informed about added and removed bridges MIME-Version: 1.0 Message-Id: <20241231-hotplug-drm-bridge-v5-1-173065a1ece1@bootlin.com> References: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> In-Reply-To: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> To: Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski , Catalin Marinas , Will Deacon , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , Daniel Thompson , Andrzej Hajda , Jonathan Corbet Cc: Paul Kocialkowski , Maxime Ripard , Dmitry Baryshkov , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Thomas Zimmermann , David Airlie , =?utf-8?q?Herv=C3=A9_Codina?= , Thomas Petazzoni , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org, Paul Kocialkowski , Luca Ceresoli X-Mailer: b4 0.14.2 X-GND-Sasl: luca.ceresoli@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" In preparation for allowing bridges to be added to and removed from a DRM card without destroying the whole card, add a new DRM bridge function called on addition and removal of bridges. Signed-off-by: Luca Ceresoli --- Changed in v5: - fixed kerneldoc errors This patch was added in v4. --- drivers/gpu/drm/drm_bridge.c | 12 ++++++++++++ include/drm/drm_bridge.h | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index c6af46dd02bfa9e15b59e4c460debdd7fd84be44..b1f0d25d55e23000521ac2ac37ee410348978ed4 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -205,8 +205,14 @@ static LIST_HEAD(bridge_list); */ void drm_bridge_add(struct drm_bridge *bridge) { + struct drm_bridge *br, *tmp; + mutex_init(&bridge->hpd_mutex); + list_for_each_entry_safe(br, tmp, &bridge_list, list) + if (br->funcs->bridge_event_notify) + br->funcs->bridge_event_notify(br, DRM_EVENT_BRIDGE_ADD, bridge); + mutex_lock(&bridge_lock); list_add_tail(&bridge->list, &bridge_list); mutex_unlock(&bridge_lock); @@ -243,10 +249,16 @@ EXPORT_SYMBOL(devm_drm_bridge_add); */ void drm_bridge_remove(struct drm_bridge *bridge) { + struct drm_bridge *br, *tmp; + mutex_lock(&bridge_lock); list_del_init(&bridge->list); mutex_unlock(&bridge_lock); + list_for_each_entry_safe(br, tmp, &bridge_list, list) + if (br->funcs->bridge_event_notify) + br->funcs->bridge_event_notify(br, DRM_EVENT_BRIDGE_REMOVE, bridge); + mutex_destroy(&bridge->hpd_mutex); } EXPORT_SYMBOL(drm_bridge_remove); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index e8d735b7f6a480468c88287e2517b387ceec0f22..6976bd842cedf9ce06abfb7306e7a3b4915f0378 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -54,6 +54,11 @@ enum drm_bridge_attach_flags { DRM_BRIDGE_ATTACH_NO_CONNECTOR = BIT(0), }; +enum drm_bridge_event_type { + DRM_EVENT_BRIDGE_ADD, + DRM_EVENT_BRIDGE_REMOVE, +}; + /** * struct drm_bridge_funcs - drm_bridge control functions */ @@ -676,6 +681,24 @@ struct drm_bridge_funcs { enum hdmi_infoframe_type type, const u8 *buffer, size_t len); + /** + * @bridge_event_notify: + * + * Notify that another bridge is being added or removed. + * + * This callback is optional. Bridges implementing it must always + * check whether the event refers to a bridge they actually need to + * interact with. + * + * @bridge: bridge being notified + * @event: event happened (add/remove bridge) + * @event_bridge: the bridge mentioned by the event (i.e. the + * bridge being added or removed) + */ + void (*bridge_event_notify)(struct drm_bridge *bridge, + enum drm_bridge_event_type event, + struct drm_bridge *event_bridge); + /** * @debugfs_init: * From patchwork Tue Dec 31 10:39:56 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 13923769 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 230CCE77194 for ; Tue, 31 Dec 2024 10:40:46 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 8EDB110E64B; Tue, 31 Dec 2024 10:40:44 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="kRlUawNt"; dkim-atps=neutral Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by gabe.freedesktop.org (Postfix) with ESMTPS id C486610E649 for ; Tue, 31 Dec 2024 10:40:42 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id 2283DFF805; Tue, 31 Dec 2024 10:40:19 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1735641621; 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=wdJRFPYJn2qqkOdPun4N5SXzAsUkIySnz0tR0H9uV8o=; b=kRlUawNtU0Xjy9Zt9zORzIia6GHO6oAv8REIkF+pDHVszF9rOGwMdqNOwfqHBhP4mqECv7 Ga/POKxglQcPJ2Pj2A4yeanVcDy9aXMPHSEgg6DDWAMsfikzffOYlFSCOISfuDSxQPKQtB 2oljVjOEA9en7kD019nnb2N83WwYO7xu5tZtuBZA/i3wEdfRA7fQuXqYwfejDDQodeHGz9 Z57MzJkSGHGvKvdGVuKblP1xBu3txUngtThoQAROl8lawXKBzg8YJyFeiBakRb/od+f8bI eO6RCnfmIQJfqorXoB5PqJci1tKGg3Rr/gP84B1f8cTO1qrN45Hlo5SfhW6iaw== From: Luca Ceresoli Date: Tue, 31 Dec 2024 11:39:56 +0100 Subject: [PATCH v5 02/10] drm/encoder: add drm_encoder_cleanup_from() MIME-Version: 1.0 Message-Id: <20241231-hotplug-drm-bridge-v5-2-173065a1ece1@bootlin.com> References: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> In-Reply-To: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> To: Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski , Catalin Marinas , Will Deacon , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , Daniel Thompson , Andrzej Hajda , Jonathan Corbet Cc: Paul Kocialkowski , Maxime Ripard , Dmitry Baryshkov , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Thomas Zimmermann , David Airlie , =?utf-8?q?Herv=C3=A9_Codina?= , Thomas Petazzoni , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org, Paul Kocialkowski , Luca Ceresoli X-Mailer: b4 0.14.2 X-GND-Sasl: luca.ceresoli@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Supporting hardware whose final part of the DRM pipeline can be physically removed requires the ability to detach all bridges from a given point to the end of the pipeline. Introduce a variant of drm_encoder_cleanup() for this. Signed-off-by: Luca Ceresoli --- Changes in v5: none Changes in v4: none Changes in v3: none Changed in v2: - fix a typo in a comment --- drivers/gpu/drm/drm_encoder.c | 21 +++++++++++++++++++++ include/drm/drm_encoder.h | 1 + 2 files changed, 22 insertions(+) diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c index 8f2bc6a28482229fd0b030a1958f87753ad7885f..472dfbefe2960924a4e83bec425af8c7ef5f5265 100644 --- a/drivers/gpu/drm/drm_encoder.c +++ b/drivers/gpu/drm/drm_encoder.c @@ -207,6 +207,27 @@ void drm_encoder_cleanup(struct drm_encoder *encoder) } EXPORT_SYMBOL(drm_encoder_cleanup); +/** + * drm_encoder_cleanup_from - remove a given bridge and all the following + * @encoder: encoder whole list of bridges shall be pruned + * @bridge: first bridge to remove + * + * Removes from an encoder all the bridges starting with a given bridge + * and until the end of the chain. + * + * This should not be used in "normal" DRM pipelines. It is only useful for + * devices whose final part of the DRM chain can be physically removed and + * later reconnected (possibly with different hardware). + */ +void drm_encoder_cleanup_from(struct drm_encoder *encoder, struct drm_bridge *bridge) +{ + struct drm_bridge *next; + + list_for_each_entry_safe_from(bridge, next, &encoder->bridge_chain, chain_node) + drm_bridge_detach(bridge); +} +EXPORT_SYMBOL(drm_encoder_cleanup_from); + static void drmm_encoder_alloc_release(struct drm_device *dev, void *ptr) { struct drm_encoder *encoder = ptr; diff --git a/include/drm/drm_encoder.h b/include/drm/drm_encoder.h index 977a9381c8ba943b4d3e021635ea14856df8a17d..bafcabb242674880a97dfb62a50d93cc4d80c1d4 100644 --- a/include/drm/drm_encoder.h +++ b/include/drm/drm_encoder.h @@ -320,6 +320,7 @@ static inline struct drm_encoder *drm_encoder_find(struct drm_device *dev, } void drm_encoder_cleanup(struct drm_encoder *encoder); +void drm_encoder_cleanup_from(struct drm_encoder *encoder, struct drm_bridge *bridge); /** * drm_for_each_encoder_mask - iterate over encoders specified by bitmask From patchwork Tue Dec 31 10:39:57 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 13923764 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id AEAD0E77188 for ; Tue, 31 Dec 2024 10:40:33 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 1CE9910E646; Tue, 31 Dec 2024 10:40:33 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="lU+Ue6/y"; dkim-atps=neutral Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by gabe.freedesktop.org (Postfix) with ESMTPS id 406C310E646 for ; Tue, 31 Dec 2024 10:40:25 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id B13DEFF806; Tue, 31 Dec 2024 10:40:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1735641624; 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=sBaFgoralwzp1wpqY92CwQTbHSw1iTTubcEfL9q+An4=; b=lU+Ue6/yFu7uv/Csb+hKDD+u7rk0MvYA6YOL5uBS4cQm1HR3+i8/vLztSqZswtpVzp4TZI EIOZrVddonK8doiTdlq1jLHfjn4322ul8Uu6qBGZczD0H5+n5ouefPyOQgheTATERevjW7 AVx9hItNbLoe8tpfLhlmgm57VqO52LVecBshGh9awzLbvmqZzJ0Gv2zPJ1ueU6LJUI7rOB Lm9NpGXB4FkTzQTmhJ+PTkk8p2zr+7MsPNcT4jUBoux+Fswcp1D1oGoFxQWELIeT5f/i5c gQB8dXMvNBsRxktAujZRDHezmgVGmKfrtsby6tUWNtC/tCFJ51V/168VxhsLHQ== From: Luca Ceresoli Date: Tue, 31 Dec 2024 11:39:57 +0100 Subject: [PATCH v5 03/10] drm/bridge: add support for refcounted DRM bridges MIME-Version: 1.0 Message-Id: <20241231-hotplug-drm-bridge-v5-3-173065a1ece1@bootlin.com> References: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> In-Reply-To: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> To: Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski , Catalin Marinas , Will Deacon , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , Daniel Thompson , Andrzej Hajda , Jonathan Corbet Cc: Paul Kocialkowski , Maxime Ripard , Dmitry Baryshkov , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Thomas Zimmermann , David Airlie , =?utf-8?q?Herv=C3=A9_Codina?= , Thomas Petazzoni , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org, Paul Kocialkowski , Luca Ceresoli X-Mailer: b4 0.14.2 X-GND-Sasl: luca.ceresoli@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" DRM bridges are currently considered as a fixed element of a DRM card, and thus their lifetime is assumed to extend for as long as the card exists. New use cases, such as hot-pluggable hardware with video bridges, require DRM bridges to be added and removed to a DRM card without tearing the card down. This is possible for connectors already (used by DP MST), so add this possibility to DRM bridges as well. Implementation is based on drm_connector_init() as far as it makes sense, and differs when it doesn't. A difference is that bridges are not exposed to userspace,hence struct drm_bridge does not embed a struct drm_mode_object which would provide the refcount and the free_cb. So here we add to struct drm_bridge just the refcount and free_cb fields (we don't need other struct drm_mode_object fields here) and instead of using the drm_mode_object_*() functions we reimplement from those functions the few lines that drm_bridge needs for refcounting. The function to enroll a private bridge driver data structure into refcounting is based on drm_connector_init() and so called drm_bridge_init() for symmetry, even though it does not initialize anything except the refcounting and the funcs pointer which is needed to access funcs->destroy. Signed-off-by: Luca Ceresoli --- This patch was added in v5. --- drivers/gpu/drm/drm_bridge.c | 87 ++++++++++++++++++++++++++++++++++++ include/drm/drm_bridge.h | 102 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index b1f0d25d55e23000521ac2ac37ee410348978ed4..6255ef59f73d8041a8cb7f2c6e23e5a67d1ae926 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -198,6 +198,85 @@ static DEFINE_MUTEX(bridge_lock); static LIST_HEAD(bridge_list); +static void drm_bridge_put_void(void *data) +{ + struct drm_bridge *bridge = (struct drm_bridge *)data; + + drm_bridge_put(bridge); +} + +static void drm_bridge_free(struct kref *kref) +{ + struct drm_bridge *bridge = container_of(kref, struct drm_bridge, refcount); + + DRM_DEBUG("bridge=%p\n", bridge); + + WARN_ON(!bridge->funcs->destroy); + + if (bridge->funcs->destroy) + bridge->funcs->destroy(bridge); +} + +/** + * drm_bridge_init - Initialize bridge and move private driver data + * lifetime management to the DRM bridge core + * + * @dev: struct device of the device whose removal shall trigger deallocation + * @bridge: the bridge to initialize + * @funcs: funcs structure for @bridge, which must have a valid .destroy func + * + * Takes over lifetime of a private bridge driver struct which embeds a + * struct drm_bridge. To be called by bridge drivers just after having + * allocated such a private structure. Initializes refcount to 1 and + * installs a callback which will call funcs->destroy when refcount drops + * to zero. + * + * After calling this function a bridge becomes a bridge with dynamic + * lifetime (aka a refcounted bridge). + * + * Drivers calling this function: + * - must not allocate the private structure using devm_*() functions + * - must not deallocate the private structure on device removal + * - must deallocate the private structure in funcs->destroy + * + * Drivers not calling this function: + * - must take care of freeing their private structure either by allocating + * it using devm_*() functions or free it explicitly on device removal + * using kfree() + * - must set funcs->destroy to NULL + * + * On failure, calls funcs->destroy, thus the caller does not need to free + * the driver private struct in case of error. + * + * Returns: + * Zero on success, error code on failure. + */ +int drm_bridge_init(struct device *dev, + struct drm_bridge *bridge, + const struct drm_bridge_funcs *funcs) +{ + int err; + + DRM_DEBUG("bridge=%p, funcs=%ps\n", bridge, funcs); + + if (!(funcs && funcs->destroy)) { + dev_warn(dev, "Missing funcs or destroy func pointer\n"); + return -EINVAL; + } + + bridge->free_cb = drm_bridge_free; + kref_init(&bridge->refcount); + + err = devm_add_action_or_reset(dev, drm_bridge_put_void, bridge); + if (err) + return err; + + bridge->funcs = funcs; + + return 0; +} +EXPORT_SYMBOL(drm_bridge_init); + /** * drm_bridge_add - add the given bridge to the global bridge list * @@ -207,6 +286,10 @@ void drm_bridge_add(struct drm_bridge *bridge) { struct drm_bridge *br, *tmp; + DRM_DEBUG("bridge=%p\n", bridge); + + drm_bridge_get(bridge); + mutex_init(&bridge->hpd_mutex); list_for_each_entry_safe(br, tmp, &bridge_list, list) @@ -251,6 +334,8 @@ void drm_bridge_remove(struct drm_bridge *bridge) { struct drm_bridge *br, *tmp; + DRM_DEBUG("bridge=%p\n", bridge); + mutex_lock(&bridge_lock); list_del_init(&bridge->list); mutex_unlock(&bridge_lock); @@ -260,6 +345,8 @@ void drm_bridge_remove(struct drm_bridge *bridge) br->funcs->bridge_event_notify(br, DRM_EVENT_BRIDGE_REMOVE, bridge); mutex_destroy(&bridge->hpd_mutex); + + drm_bridge_put(bridge); } EXPORT_SYMBOL(drm_bridge_remove); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 6976bd842cedf9ce06abfb7306e7a3b4915f0378..a548a6acb02e3d70c8e34de965f648320420a7d5 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -31,6 +31,7 @@ #include #include #include +#include struct device_node; @@ -89,6 +90,18 @@ struct drm_bridge_funcs { */ void (*detach)(struct drm_bridge *bridge); + /** + * @destroy: + * + * Clean up bridge resources for bridges with dynamic + * lifetime. This is called when the bridge refcount drops to + * zero. It is never called for bridges without dynamic lifetime. + * + * See drm_bridge_init() for info about bridges with dynamic + * lifetime. + */ + void (*destroy)(struct drm_bridge *bridge); + /** * @mode_valid: * @@ -810,6 +823,18 @@ struct drm_bridge { const struct drm_bridge_timings *timings; /** @funcs: control functions */ const struct drm_bridge_funcs *funcs; + + /** + * @refcount: reference count for bridges with dynamic lifetime + * (see drm_bridge_init) + */ + struct kref refcount; + /** + * @free_cb: free function callback, only set for bridges with + * dynamic lifetime + */ + void (*free_cb)(struct kref *kref); + /** @driver_private: pointer to the bridge driver's internal context */ void *driver_private; /** @ops: bitmask of operations supported by the bridge */ @@ -890,6 +915,83 @@ drm_priv_to_bridge(struct drm_private_obj *priv) return container_of(priv, struct drm_bridge, base); } +static inline bool drm_bridge_is_refcounted(struct drm_bridge *bridge) +{ + return bridge->free_cb; +} + +/** + * drm_bridge_get - Acquire a bridge reference + * @bridge: DRM bridge + * + * This function increments the bridge's refcount. + * + * It does nothing on non-refcounted bridges. See drm_bridge_init(). + */ +static inline void drm_bridge_get(struct drm_bridge *bridge) +{ + if (!drm_bridge_is_refcounted(bridge)) + return; + + DRM_DEBUG("bridge=%p GET\n", bridge); + + kref_get(&bridge->refcount); +} + +/** + * drm_bridge_put - Release a bridge reference + * @bridge: DRM bridge + * + * This function decrements the bridge's reference count and frees the + * object if the reference count drops to zero. + * + * It does nothing on non-refcounted bridges. See drm_bridge_init(). + * + * See also drm_bridge_put_and_clear() which is more handy in many cases. + */ +static inline void drm_bridge_put(struct drm_bridge *bridge) +{ + if (!drm_bridge_is_refcounted(bridge)) + return; + + DRM_DEBUG("bridge=%p PUT\n", bridge); + + kref_put(&bridge->refcount, bridge->free_cb); +} + +/** + * drm_bridge_put_and_clear - Given a bridge pointer, clear the pointer + * then put the bridge + * + * @br_ptr: pointer to a struct drm_bridge (must be != NULL) + * + * Helper to put a DRM bridge (whose pointer is passed), but only after + * setting its pointer to NULL. Useful for drivers having struct drm_bridge + * pointers they need to dispose of, without leaving a use-after-free + * window where the pointed bridge might have been freed while still + * holding a pointer to it. + * + * For example a driver having this private struct:: + * + * struct my_bridge { + * struct drm_bridge *remote_bridge; + * ... + * }; + * + * can dispose of remote_bridge using:: + * + * drm_bridge_put_and_clear(my_bridge->remote_bridge); + */ +#define drm_bridge_put_and_clear(br_ptr) do { \ + struct drm_bridge **brpp = &(br_ptr); \ + struct drm_bridge *brp = *brpp; \ + *brpp = NULL; \ + drm_bridge_put(brp); \ +} while (0) + +int drm_bridge_init(struct device *dev, + struct drm_bridge *bridge, + const struct drm_bridge_funcs *funcs); void drm_bridge_add(struct drm_bridge *bridge); int devm_drm_bridge_add(struct device *dev, struct drm_bridge *bridge); void drm_bridge_remove(struct drm_bridge *bridge); From patchwork Tue Dec 31 10:39:58 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 13923763 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 41E03E77188 for ; Tue, 31 Dec 2024 10:40:30 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id BEE8B10E644; Tue, 31 Dec 2024 10:40:29 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="YVHsRKu1"; dkim-atps=neutral Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by gabe.freedesktop.org (Postfix) with ESMTPS id E7F5B10E648 for ; Tue, 31 Dec 2024 10:40:27 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id 2C459FF809; Tue, 31 Dec 2024 10:40:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1735641626; 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=5spdtsuPp+u1VNzPSbXTjsRuHOgdZOi+G4vXrmIfO38=; b=YVHsRKu1ZmAMCJF6LBakIfnJ0Zun+wi3LyZY2btFOmM7UcqeVYJxBNuTZn7UQb2mDIOpaP VJWlUxzMrSk+b7swGgczM6rbm0aAzhaKaanocbIUpgMbjEzBADifFNrvg2LPh7RbsxX6p5 ZJ2MlWslDXYZUUROvaI1BjtgPfk95A2z0OCLni7rq9WF+aRr/uXwgiFVpg6+tUn0dPMw4l U2O0ddK1gUoLSNUq43u1emYf/cCG/SVHhg2jjabcahPdDqT3JzyvEh1itFeY7Pd7qN6Beq 8sgFFw0MrWiSluebylysYjkHfYOXhIkeBk0KZ1/MVfUCMnwU0sCVVo18EnOt4A== From: Luca Ceresoli Date: Tue, 31 Dec 2024 11:39:58 +0100 Subject: [PATCH v5 04/10] drm/bridge: add documentation of refcounted bridges MIME-Version: 1.0 Message-Id: <20241231-hotplug-drm-bridge-v5-4-173065a1ece1@bootlin.com> References: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> In-Reply-To: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> To: Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski , Catalin Marinas , Will Deacon , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , Daniel Thompson , Andrzej Hajda , Jonathan Corbet Cc: Paul Kocialkowski , Maxime Ripard , Dmitry Baryshkov , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Thomas Zimmermann , David Airlie , =?utf-8?q?Herv=C3=A9_Codina?= , Thomas Petazzoni , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org, Paul Kocialkowski , Luca Ceresoli X-Mailer: b4 0.14.2 X-GND-Sasl: luca.ceresoli@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Document in detail the new refcounted bridges as well as the "legacy" way. Signed-off-by: Luca Ceresoli --- This patch was added in v5. --- Documentation/gpu/drm-kms-helpers.rst | 6 ++ drivers/gpu/drm/drm_bridge.c | 122 ++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst index 8cf2f041af4704875910ce8228ae04615d0f21bd..ca2cfef2101988933e1464fe146997c1a661a117 100644 --- a/Documentation/gpu/drm-kms-helpers.rst +++ b/Documentation/gpu/drm-kms-helpers.rst @@ -151,6 +151,12 @@ Overview .. kernel-doc:: drivers/gpu/drm/drm_bridge.c :doc: overview +Bridge lifecycle +---------------- + +.. kernel-doc:: drivers/gpu/drm/drm_bridge.c + :doc: bridge lifecycle + Display Driver Integration -------------------------- diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 6255ef59f73d8041a8cb7f2c6e23e5a67d1ae926..e9f138aa5b3270b4e3a1a56dc8d4b7e5f993c929 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -60,6 +60,128 @@ * encoder chain. */ +/** + * DOC: bridge lifecycle + * + * Allocation, initializion and teardown of a bridge can be implemented in + * one of two ways: *refcounted* mode and *legacy* mode. + * + * In **refcounted** mode: + * + * - each &struct drm_bridge is reference counted since its instantiation + * - any code taking a pointer to a bridge has get and put APIs to refcount + * it and so ensure the bridge won't be deallocated while using it + * - deallocation is done when the last put happens and the refcount drops + * to zero + * - the driver instantiating the bridge also holds a reference, but the + * allocated struct can survive it + * + * A bridge using refcounted mode is called a *refcounted bridge*. + * + * In **legacy** mode the &struct drm_bridge lifetime is tied to the device + * instantiating it: it is allocated on probe and freed on removal. Any + * other kernel entities holding a pointer to the bridge could incur in + * use-after-free in case the bridge is deallocated at runtime. + * + * Legacy mode used to be the only one until refcounted bridges were + * introduced, hance the name. It is still fine in case the bridges are a + * fixed part of the pipeline, i.e. if the bridges are removed only when + * tearing down the entire card. Refcounted bridges support both that case + * and the case of more dynamic hardware with bridges that can be removed + * at runtime without tearing down the entire card. + * + * Usage of refcounted bridges happens in two sides: the driver + * implementing the bridge and the code using the bridge. + * + * For *drivers implemeting the bridge*, in both refcounted and legacy + * modes the common and expected pattern is that the driver declares a + * driver-specific struct embedding a &struct drm_bridge. E.g.:: + * + * struct my_bridge { + * ... + * struct drm_bridge bridge; + * ... + * }; + * + * When using refcounted mode, the driver should allocate ``struct + * my_bridge`` using regular allocation (as opposed to ``devm_`` or + * ``drmm_`` allocation), call drm_bridge_init() immediately afterwards to + * transfer lifecycle management to the DRM bridge core, and implement a + * ``.destroy`` function to deallocate the ``struct my_bridge``, as in this + * example:: + * + * static void my_bridge_destroy(struct drm_bridge *bridge) + * { + * kfree(container_of(bridge, struct my_bridge, bridge)); + * } + * + * static const struct drm_bridge_funcs my_bridge_funcs = { + * .destroy = my_bridge_destroy, + * ... + * }; + * + * static int my_bridge_probe(...) + * { + * struct my_bridge *mybr; + * int err; + * + * mybr = kzalloc(sizeof(*mybr), GFP_KERNEL); + * if (!mybr) + * return -ENOMEM; + * + * err = drm_bridge_init(dev, &mybr->bridge, &my_bridge_funcs); + * if (err) + * return err; + * + * ... + * drm_bridge_add(); + * ... + * } + * + * static void my_bridge_remove() + * { + * struct my_bridge *mybr = ...; + * drm_bridge_remove(&mybr->bridge); + * // ... NO kfree here! + * } + * + * In legacy mode, the driver can either use ``devm_`` allocation or + * equivalently free ``struct my_bridge`` in their remove function:: + * + * static int my_bridge_probe(...) + * { + * struct my_bridge *mybr; + * + * mybr = devm_kzalloc(dev, sizeof(*mybr), GFP_KERNEL); + * if (!mybr) + * return -ENOMEM; + * + * ... + * drm_bridge_add(); + * ... + * } + * + * static void my_bridge_remove() + * { + * struct my_bridge *mybr = ...; + * drm_bridge_remove(&mybr->bridge); + * // kfree(mybr) if not using devm_*() for allocation + * } + * + * The *code using the bridge* is all the code taking a &struct drm_bridge + * pointer, including other bridges, encoders and the DRM core. As the + * bridge could be removed at any time, such code can incur in + * use-after-free. To void that, it has to call drm_bridge_get() when + * taking a pointer and drm_bridge_put() after it has done using it. This + * will extend the allocation lifetime of the bridge struct until the last + * reference has been put, potentially after the bridge device has been + * removed from the kernel. + * + * Calling drm_bridge_get() and drm_bridge_put() on a bridge that is not + * refcounted does nothing, so code using these two APIs will work both on + * refcounted bridges and non-refcounted ones. + */ + /** * DOC: display driver integration * From patchwork Tue Dec 31 10:39:59 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 13923765 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id A4E99E77194 for ; Tue, 31 Dec 2024 10:40:34 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 028B410E64A; Tue, 31 Dec 2024 10:40:34 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="YZ34XH8G"; dkim-atps=neutral Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by gabe.freedesktop.org (Postfix) with ESMTPS id 4F1DB10E646 for ; Tue, 31 Dec 2024 10:40:30 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id D7D81FF808; Tue, 31 Dec 2024 10:40:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1735641629; 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=e8pur7/mdaBDK3Ok0R7BqviMkiAqadB8vn4pZnI/S7c=; b=YZ34XH8GutkTLZ+aFI/dpqCvBNef9HrvqXC72uGgH6hRP5vrnLsv/1mDSiKRvrfy0rhYu0 9+vjk9S0nQGJeaefEjZ9vlA/9mmIMRtkcQiiH/r9JiE/GQpyxH5JMTdE7AOVQnvAfTx1ha tdM/n8EEuUGqUF8QIBpvEEm1RRpFPRfQwpHCtKuu0SfARLAdEpIoDSXa2qyzm8/+v6P3df vPQrMna3ZU6EGDpi20o/3rwLl+KP6uys3EINDDT6mJChWhHAOZXFXUTjAlDbmHJSYImPN7 WL9sBUfcYQ9frYMoURtu318JpIwpwPH2okiZTVrVO9Ae/ZWjnSh6/VApVmpopw== From: Luca Ceresoli Date: Tue, 31 Dec 2024 11:39:59 +0100 Subject: [PATCH v5 05/10] drm/tests: bridge: add KUnit tests for DRM bridges (init and destroy) MIME-Version: 1.0 Message-Id: <20241231-hotplug-drm-bridge-v5-5-173065a1ece1@bootlin.com> References: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> In-Reply-To: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> To: Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski , Catalin Marinas , Will Deacon , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , Daniel Thompson , Andrzej Hajda , Jonathan Corbet Cc: Paul Kocialkowski , Maxime Ripard , Dmitry Baryshkov , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Thomas Zimmermann , David Airlie , =?utf-8?q?Herv=C3=A9_Codina?= , Thomas Petazzoni , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org, Paul Kocialkowski , Luca Ceresoli X-Mailer: b4 0.14.2 X-GND-Sasl: luca.ceresoli@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Add two simple KUnit tests for the newly introduced drm_bridge_init() and the corresponding .destroy deallocation function. Signed-off-by: Luca Ceresoli --- This patch was added in v5. --- drivers/gpu/drm/tests/Makefile | 1 + drivers/gpu/drm/tests/drm_bridge_test.c | 128 ++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/drivers/gpu/drm/tests/Makefile b/drivers/gpu/drm/tests/Makefile index 56dab563abd7a7ee7c147bd6b4927e2436b82e1d..909f98a132bb1d057b2666e8b891683ffb11cca4 100644 --- a/drivers/gpu/drm/tests/Makefile +++ b/drivers/gpu/drm/tests/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_KUNIT_TEST_HELPERS) += \ drm_kunit_helpers.o obj-$(CONFIG_DRM_KUNIT_TEST) += \ + drm_bridge_test.o \ drm_buddy_test.o \ drm_cmdline_parser_test.o \ drm_connector_test.o \ diff --git a/drivers/gpu/drm/tests/drm_bridge_test.c b/drivers/gpu/drm/tests/drm_bridge_test.c new file mode 100644 index 0000000000000000000000000000000000000000..b42e6b81a904abccfe1b3e88999b64cc6b2d4946 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_bridge_test.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Kunit test for DRM bridges + */ + +#include +#include + +#include + +struct drm_bridge_init_priv { + struct drm_device drm; + bool destroyed; +}; + +/* + * Mimick the typical struct defined by a bridge driver, which embeds a + * bridge plus other fields. + */ +struct dummy_drm_bridge { + struct drm_bridge_init_priv *init_priv; + struct drm_bridge bridge; +}; + +static struct dummy_drm_bridge *bridge_to_dummy(struct drm_bridge *bridge) +{ + return container_of(bridge, struct dummy_drm_bridge, bridge); +} + +static void dummy_drm_bridge_destroy(struct drm_bridge *bridge) +{ + struct dummy_drm_bridge *dummy = bridge_to_dummy(bridge); + + dummy->init_priv->destroyed = true; + kfree(dummy); +} + +static const struct drm_bridge_funcs drm_bridge_dummy_funcs = { + .destroy = dummy_drm_bridge_destroy, +}; + +static int drm_test_bridge_init(struct kunit *test) +{ + struct drm_bridge_init_priv *priv; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + priv = drm_kunit_helper_alloc_drm_device(test, dev, + struct drm_bridge_init_priv, drm, + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + + test->priv = priv; + return 0; +} + +/* + * Test that the allocation and initialization of a bridge works as + * expected and doesn't report any error. + */ +static void drm_test_drm_bridge_init(struct kunit *test) +{ + struct drm_bridge_init_priv *priv = test->priv; + struct dummy_drm_bridge *dummy; + int ret; + + dummy = kzalloc(sizeof(*dummy), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dummy); + + dummy->init_priv = priv; + + ret = drm_bridge_init(priv->drm.dev, + &dummy->bridge, + &drm_bridge_dummy_funcs); + KUNIT_EXPECT_EQ(test, ret, 0); + + KUNIT_EXPECT_FALSE(test, priv->destroyed); +} + +/* + * Test that the allocation and initialization of a bridge works as expected + * and doesn't report any error, and that the destroy func is called at the + * last drm_bridge_put(). + */ +static void drm_test_drm_bridge_put(struct kunit *test) +{ + struct drm_bridge_init_priv *priv = test->priv; + struct dummy_drm_bridge *dummy; + int ret; + + dummy = kzalloc(sizeof(*dummy), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dummy); + + dummy->init_priv = priv; + + ret = drm_bridge_init(priv->drm.dev, + &dummy->bridge, + &drm_bridge_dummy_funcs); + KUNIT_EXPECT_EQ(test, ret, 0); + + drm_bridge_get(&dummy->bridge); + drm_bridge_put(&dummy->bridge); + KUNIT_EXPECT_FALSE(test, priv->destroyed); + drm_bridge_put(&dummy->bridge); + KUNIT_EXPECT_TRUE(test, priv->destroyed); +} + +static struct kunit_case drm_bridge_init_tests[] = { + KUNIT_CASE(drm_test_drm_bridge_init), + KUNIT_CASE(drm_test_drm_bridge_put), + { } +}; + +static struct kunit_suite drm_bridge_init_test_suite = { + .name = "drm_bridge_init", + .init = drm_test_bridge_init, + .test_cases = drm_bridge_init_tests, +}; + +kunit_test_suites( + &drm_bridge_init_test_suite, +); + +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_DESCRIPTION("Kunit test for drm_bridge functions"); +MODULE_LICENSE("GPL"); From patchwork Tue Dec 31 10:40:00 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 13923770 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 6D25CE77188 for ; Tue, 31 Dec 2024 10:40:55 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id E30E610E64C; Tue, 31 Dec 2024 10:40:54 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="HqcIjpfG"; dkim-atps=neutral Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by gabe.freedesktop.org (Postfix) with ESMTPS id AABC510E64C for ; Tue, 31 Dec 2024 10:40:52 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id 3C58EFF804; Tue, 31 Dec 2024 10:40:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1735641631; 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=tgG7ptM2LlMfthlPU8YWespBaBvmS478hMiXjlaUP0E=; b=HqcIjpfGPJ7HiTpnjXCWHYSxYs4m2PoWbzHFTFNZknw5Q+CIQwoqQiqr5x+QuPMyj4MgvT U/SQZzalPCu3wL0rKIqkrcpRYhznEsL+rOLoMeGEvprMWAhoYWG3WWrb72Zzj066tA0TlV Y8ozkuWoCIY56F4h/ChouQKrW5By1Tk47CfN49fQttpJG5MK7VoDCShyfN4T15lnZoKF7l 6i7Bb5cNnsKKq2MaVhUooZOe/0Eg1SeSEdHLTclJzB6thXs3bIz6+o4Ypm+ud+TJsjotXz vDCGE97iwxLEfy+oSgEK1LD6u7hOmcBId4zwVIk05sxD6RNE8LvUBA4IE9Lu7g== From: Luca Ceresoli Date: Tue, 31 Dec 2024 11:40:00 +0100 Subject: [PATCH v5 06/10] drm/bridge: ti-sn65dsi83: use dynamic lifetime management MIME-Version: 1.0 Message-Id: <20241231-hotplug-drm-bridge-v5-6-173065a1ece1@bootlin.com> References: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> In-Reply-To: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> To: Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski , Catalin Marinas , Will Deacon , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , Daniel Thompson , Andrzej Hajda , Jonathan Corbet Cc: Paul Kocialkowski , Maxime Ripard , Dmitry Baryshkov , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Thomas Zimmermann , David Airlie , =?utf-8?q?Herv=C3=A9_Codina?= , Thomas Petazzoni , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org, Paul Kocialkowski , Luca Ceresoli X-Mailer: b4 0.14.2 X-GND-Sasl: luca.ceresoli@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" With proper use of drm_bridge_get() and _put(), this allows the bridge to be removable without dangling pointers and use-after-free. Signed-off-by: Luca Ceresoli --- This patch was added in v5. --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index 246fece303bc0ae9fb2eaa9c6218ce2ecd550f3b..df3ec39817f32879f9aab65812b6e7f4723f72fb 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -267,6 +267,11 @@ static void sn65dsi83_detach(struct drm_bridge *bridge) ctx->dsi = NULL; } +static void sn65dsi83_destroy(struct drm_bridge *bridge) +{ + kfree(bridge_to_sn65dsi83(bridge)); +} + static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx, const struct drm_display_mode *mode) { @@ -695,6 +700,7 @@ sn65dsi83_atomic_get_input_bus_fmts(struct drm_bridge *bridge, static const struct drm_bridge_funcs sn65dsi83_funcs = { .attach = sn65dsi83_attach, .detach = sn65dsi83_detach, + .destroy = sn65dsi83_destroy, .atomic_enable = sn65dsi83_atomic_enable, .atomic_pre_enable = sn65dsi83_atomic_pre_enable, .atomic_disable = sn65dsi83_atomic_disable, @@ -813,10 +819,14 @@ static int sn65dsi83_probe(struct i2c_client *client) struct sn65dsi83 *ctx; int ret; - ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; + ret = drm_bridge_init(dev, &ctx->bridge, &sn65dsi83_funcs); + if (ret) + return ret; + ctx->dev = dev; INIT_WORK(&ctx->reset_work, sn65dsi83_reset_work); INIT_DELAYED_WORK(&ctx->monitor_work, sn65dsi83_monitor_work); @@ -855,7 +865,6 @@ static int sn65dsi83_probe(struct i2c_client *client) dev_set_drvdata(dev, ctx); i2c_set_clientdata(client, ctx); - ctx->bridge.funcs = &sn65dsi83_funcs; ctx->bridge.of_node = dev->of_node; ctx->bridge.pre_enable_prev_first = true; drm_bridge_add(&ctx->bridge); From patchwork Tue Dec 31 10:40:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 13923766 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id A8419E77188 for ; Tue, 31 Dec 2024 10:40:40 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 214A710E653; Tue, 31 Dec 2024 10:40:40 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="KK+ZBFK4"; dkim-atps=neutral Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by gabe.freedesktop.org (Postfix) with ESMTPS id 15C0210E648 for ; Tue, 31 Dec 2024 10:40:34 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id 925E2FF806; Tue, 31 Dec 2024 10:40:31 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1735641633; 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=32FGOgMqkFd24eRdy6LzV+vdiYs/LXpL2vGROONBsso=; b=KK+ZBFK4Fduf5wjOvpZjiAOMyEjY0gITQhDROVMyIzrcY3DlPAiTXgfd1qvp0sc522kKxd fg2NtpdplM31yNw9djttbDyHRAMEtkOT8SenYYpGnrkXNYCmLAGMhg3WGNdJLXI1I2zNnH 1Z5mX02RbBOhBYb3rkGHr6+kXvxL0kxlmXheqB3xWZK+pqsult4zXLgT0VYnymf950K8kS vuPY5R5ggmNq7kKIdilwStMKf+BUlDrR5JJ15Trfy3zN5zaF8LJXPVYk2H+fNQOHV6VZ5i eQj8ooU2mAsKJ6p7iJkhUm51apwG8vUeywrr1eP/BCJwCqqQUMWibdhC5LCfPA== From: Luca Ceresoli Date: Tue, 31 Dec 2024 11:40:01 +0100 Subject: [PATCH v5 07/10] drm/bridge: panel: use dynamic lifetime management MIME-Version: 1.0 Message-Id: <20241231-hotplug-drm-bridge-v5-7-173065a1ece1@bootlin.com> References: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> In-Reply-To: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> To: Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski , Catalin Marinas , Will Deacon , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , Daniel Thompson , Andrzej Hajda , Jonathan Corbet Cc: Paul Kocialkowski , Maxime Ripard , Dmitry Baryshkov , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Thomas Zimmermann , David Airlie , =?utf-8?q?Herv=C3=A9_Codina?= , Thomas Petazzoni , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org, Paul Kocialkowski , Luca Ceresoli X-Mailer: b4 0.14.2 X-GND-Sasl: luca.ceresoli@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Enable lifetime management of panel-bridge, so that other modules taking a pointer to a panel bridge can refcount it and avoid use-after-free in case the panel bridge is hot-unplugged. Signed-off-by: Luca Ceresoli --- This patch was added in v5. --- drivers/gpu/drm/bridge/panel.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index 6e88339dec0f5faee690b7c53e8dcd0f1ee2281c..805809778f79f4519d9e31214cc5407357264da3 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -108,6 +108,11 @@ static void panel_bridge_detach(struct drm_bridge *bridge) drm_connector_cleanup(connector); } +static void panel_bridge_destroy(struct drm_bridge *bridge) +{ + kfree(drm_bridge_to_panel_bridge(bridge)); +} + static void panel_bridge_atomic_pre_enable(struct drm_bridge *bridge, struct drm_bridge_state *old_bridge_state) { @@ -210,6 +215,7 @@ static void panel_bridge_debugfs_init(struct drm_bridge *bridge, static const struct drm_bridge_funcs panel_bridge_bridge_funcs = { .attach = panel_bridge_attach, .detach = panel_bridge_detach, + .destroy = panel_bridge_destroy, .atomic_pre_enable = panel_bridge_atomic_pre_enable, .atomic_enable = panel_bridge_atomic_enable, .atomic_disable = panel_bridge_atomic_disable, @@ -286,19 +292,22 @@ struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel, u32 connector_type) { struct panel_bridge *panel_bridge; + int err; if (!panel) return ERR_PTR(-EINVAL); - panel_bridge = devm_kzalloc(panel->dev, sizeof(*panel_bridge), - GFP_KERNEL); + panel_bridge = kzalloc(sizeof(*panel_bridge), GFP_KERNEL); if (!panel_bridge) return ERR_PTR(-ENOMEM); + err = drm_bridge_init(panel->dev, &panel_bridge->bridge, &panel_bridge_bridge_funcs); + if (err) + return ERR_PTR(err); + panel_bridge->connector_type = connector_type; panel_bridge->panel = panel; - panel_bridge->bridge.funcs = &panel_bridge_bridge_funcs; panel_bridge->bridge.of_node = panel->dev->of_node; panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES; panel_bridge->bridge.type = connector_type; @@ -317,18 +326,13 @@ EXPORT_SYMBOL(drm_panel_bridge_add_typed); */ void drm_panel_bridge_remove(struct drm_bridge *bridge) { - struct panel_bridge *panel_bridge; - if (!bridge) return; if (bridge->funcs != &panel_bridge_bridge_funcs) return; - panel_bridge = drm_bridge_to_panel_bridge(bridge); - drm_bridge_remove(bridge); - devm_kfree(panel_bridge->panel->dev, bridge); } EXPORT_SYMBOL(drm_panel_bridge_remove); From patchwork Tue Dec 31 10:40:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 13923771 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id AA371E77188 for ; Tue, 31 Dec 2024 10:40:59 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 30E5A10E64D; Tue, 31 Dec 2024 10:40:59 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="Ic7Dg541"; dkim-atps=neutral Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by gabe.freedesktop.org (Postfix) with ESMTPS id 7301910E64D for ; Tue, 31 Dec 2024 10:40:57 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id 05672FF809; Tue, 31 Dec 2024 10:40:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1735641636; 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=iTffFfD4VzEYR9vlW/p4V75r3lQ1DwU0EdWChMuyjI8=; b=Ic7Dg541QDABb3D67WnsoIwb/53TK87QIFofk4LJvHP7u0eswpPhbtyTAdLVya4AWYhIRT 3s3SmSHe8eUaKHXxSP1TmIOWFJUm6T344mJNc1HJJGt7IG3bKAQiX37zRHxJpwv4ZhdQeT L4UXcApiAdkyRbZ0ic8WWZnQNVRYc1a7PyewO/SLRRBSi00Ld6ENhuDNZpjuJXTKfjbBTp S7O3DsTqoiOk/HbzqkuzYrnoZDQnL39cdVCL46Lj6SqngI/BhQm9Qu3FOnL4XtRtm0+YD2 a+7XV7/Kv28nMA0Z9WxdahVam2ODNOK8jufop+IKcfJG5FJ1Mf9dS/pS/hRCoQ== From: Luca Ceresoli Date: Tue, 31 Dec 2024 11:40:02 +0100 Subject: [PATCH v5 08/10] drm/bridge: samsung-dsim: use supporting variable for out_bridge MIME-Version: 1.0 Message-Id: <20241231-hotplug-drm-bridge-v5-8-173065a1ece1@bootlin.com> References: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> In-Reply-To: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> To: Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski , Catalin Marinas , Will Deacon , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , Daniel Thompson , Andrzej Hajda , Jonathan Corbet Cc: Paul Kocialkowski , Maxime Ripard , Dmitry Baryshkov , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Thomas Zimmermann , David Airlie , =?utf-8?q?Herv=C3=A9_Codina?= , Thomas Petazzoni , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org, Paul Kocialkowski , Luca Ceresoli X-Mailer: b4 0.14.2 X-GND-Sasl: luca.ceresoli@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Instead of using dsi->out_bridge during the bridge search process, use a temporary variable and assign dsi->out_bridge only on successful completion. The main goal is to be able to drm_bridge_get() the out_bridge before setting it in dsi->out_bridge, which is done in a later commit. Setting dsi->out_bridge as in current code would leave a use-after-free window in case the bridge is deallocated by some other thread between 'dsi->out_bridge = devm_drm_panel_bridge_add()' and drm_bridge_get(). This change additionally avoids leaving an ERR_PTR value in dsi->out_bridge on failure. This is not necessarily a problem but it is not clean. Signed-off-by: Luca Ceresoli --- This patch was added in v5. --- drivers/gpu/drm/bridge/samsung-dsim.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index f8b4fb8357659018ec0db65374ee5d05330639ae..c4d1563fd32019efde523dfc0863be044c05a826 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -1705,6 +1705,7 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host, struct device *dev = dsi->dev; struct device_node *np = dev->of_node; struct device_node *remote; + struct drm_bridge *out_bridge; struct drm_panel *panel; int ret; @@ -1740,21 +1741,23 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host, panel = of_drm_find_panel(remote); if (!IS_ERR(panel)) { - dsi->out_bridge = devm_drm_panel_bridge_add(dev, panel); + out_bridge = devm_drm_panel_bridge_add(dev, panel); } else { - dsi->out_bridge = of_drm_find_bridge(remote); - if (!dsi->out_bridge) - dsi->out_bridge = ERR_PTR(-EINVAL); + out_bridge = of_drm_find_bridge(remote); + if (!out_bridge) + out_bridge = ERR_PTR(-EINVAL); } of_node_put(remote); - if (IS_ERR(dsi->out_bridge)) { - ret = PTR_ERR(dsi->out_bridge); + if (IS_ERR(out_bridge)) { + ret = PTR_ERR(out_bridge); DRM_DEV_ERROR(dev, "failed to find the bridge: %d\n", ret); return ret; } + dsi->out_bridge = out_bridge; + DRM_DEV_INFO(dev, "Attached %s device (lanes:%d bpp:%d mode-flags:0x%lx)\n", device->name, device->lanes, mipi_dsi_pixel_format_to_bpp(device->format), From patchwork Tue Dec 31 10:40:03 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 13923767 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id E46A0E77188 for ; Tue, 31 Dec 2024 10:40:44 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 5549510E649; Tue, 31 Dec 2024 10:40:44 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="gij91b7P"; dkim-atps=neutral Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by gabe.freedesktop.org (Postfix) with ESMTPS id E0A4A10E658 for ; Tue, 31 Dec 2024 10:40:39 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id 5E8CDFF807; Tue, 31 Dec 2024 10:40:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1735641638; 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=TTcQh33h4sP1K7kYFcPVMoxBbZqDI+SUcTKhpuWTOmM=; b=gij91b7PznJGGhxAZOF/51HJTo0cqHVpkVu8A1p2EmoMQEJuGK+e9SlA4FF8hbAeYFhlVE eiq59Scy6JKy3SEZvXRnK41iy8x2Vx6bVl26nGdrTHxQ7MxaSvVy99VzQkgWVQHRgBSYx3 AgSRHlrCypT/xZnlz1TD9jrxDEoBFNOzwTCoG54VKTcUwHBV2iHDrVfocM9fo7e6ROpt+F VaNdSfOZwK3TxNtZhpTMmKuu3rnIhxRMpELqI7/Gv1kWJm7aJyFRrKmQ4lKMh6FAeC8Tx3 1JMpPyltnseCSFrbqQlvtRtF7jlZsVUaRS4Dvh5xtmF5BR8eANc3w2T89VNoOg== From: Luca Ceresoli Date: Tue, 31 Dec 2024 11:40:03 +0100 Subject: [PATCH v5 09/10] drm/bridge: samsung-dsim: refcount the out_bridge MIME-Version: 1.0 Message-Id: <20241231-hotplug-drm-bridge-v5-9-173065a1ece1@bootlin.com> References: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> In-Reply-To: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> To: Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski , Catalin Marinas , Will Deacon , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , Daniel Thompson , Andrzej Hajda , Jonathan Corbet Cc: Paul Kocialkowski , Maxime Ripard , Dmitry Baryshkov , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Thomas Zimmermann , David Airlie , =?utf-8?q?Herv=C3=A9_Codina?= , Thomas Petazzoni , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org, Paul Kocialkowski , Luca Ceresoli X-Mailer: b4 0.14.2 X-GND-Sasl: luca.ceresoli@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Refcount the out_bridge to avoid a use-after-free in case it is hot-unplugged. Signed-off-by: Luca Ceresoli --- This patch was added in v5. --- drivers/gpu/drm/bridge/samsung-dsim.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index c4d1563fd32019efde523dfc0863be044c05a826..4d32c453265931b5aecdc125623368fecacf4be3 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -1756,6 +1756,7 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host, return ret; } + drm_bridge_get(out_bridge); dsi->out_bridge = out_bridge; DRM_DEV_INFO(dev, "Attached %s device (lanes:%d bpp:%d mode-flags:0x%lx)\n", @@ -1774,13 +1775,13 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host, if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO)) { ret = samsung_dsim_register_te_irq(dsi, &device->dev); if (ret) - return ret; + goto err_put_bridge; } if (pdata->host_ops && pdata->host_ops->attach) { ret = pdata->host_ops->attach(dsi, device); if (ret) - return ret; + goto err_put_bridge; } dsi->lanes = device->lanes; @@ -1788,6 +1789,10 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host, dsi->mode_flags = device->mode_flags; return 0; + +err_put_bridge: + drm_bridge_put_and_clear(dsi->out_bridge); + return ret; } static void samsung_dsim_unregister_te_irq(struct samsung_dsim *dsi) @@ -1804,7 +1809,7 @@ static int samsung_dsim_host_detach(struct mipi_dsi_host *host, struct samsung_dsim *dsi = host_to_dsi(host); const struct samsung_dsim_plat_data *pdata = dsi->plat_data; - dsi->out_bridge = NULL; + drm_bridge_put_and_clear(dsi->out_bridge); if (pdata->host_ops && pdata->host_ops->detach) pdata->host_ops->detach(dsi, device); From patchwork Tue Dec 31 10:40:04 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Ceresoli X-Patchwork-Id: 13923768 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 56691E7718B for ; Tue, 31 Dec 2024 10:40:44 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id CFC6C10E648; Tue, 31 Dec 2024 10:40:43 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="CHqMtPTP"; dkim-atps=neutral Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by gabe.freedesktop.org (Postfix) with ESMTPS id 6D9EA10E648 for ; Tue, 31 Dec 2024 10:40:42 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id CA6E6FF80B; Tue, 31 Dec 2024 10:40:38 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1735641641; 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=cdmvpyBT92wx87GRYixM5z7yNZYghILMVA5IgLEfP6w=; b=CHqMtPTPBrufcLiD95Hlx+2XtXh4X6pugKcPKPYqpI4HZJlmjOmEXouMOQnET7lilxZ+EP Z6YG9QZ7Tx3xJnOrdPyGta8WLiUMxmNxXKqRf9P24nfL+7PBmsEatt1Mws/Q7yLE8kQ8y4 dfGGG8d0YVJ/kr3l2rpoc9TSiopL8qe0ds8zMF46IoN8pmjWXVOn6nfRDZuH9hqoNXURpx TPh8N0De0MBk9ax9QlDPv/mmOk7/GQ82Pf9XGZFvr9zJISbssxC3rQbeWIewDishJa0fBB IMAdsOOBAufS2AUFIqcp1v2kN/cEiyL3rSdx9jMaTkkM5xXiaBaPYhNh3/M2Vw== From: Luca Ceresoli Date: Tue, 31 Dec 2024 11:40:04 +0100 Subject: [PATCH v5 10/10] drm/bridge: hotplug-bridge: add driver to support hot-pluggable DSI bridges MIME-Version: 1.0 Message-Id: <20241231-hotplug-drm-bridge-v5-10-173065a1ece1@bootlin.com> References: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> In-Reply-To: <20241231-hotplug-drm-bridge-v5-0-173065a1ece1@bootlin.com> To: Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski , Catalin Marinas , Will Deacon , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , Daniel Thompson , Andrzej Hajda , Jonathan Corbet Cc: Paul Kocialkowski , Maxime Ripard , Dmitry Baryshkov , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Thomas Zimmermann , David Airlie , =?utf-8?q?Herv=C3=A9_Codina?= , Thomas Petazzoni , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-doc@vger.kernel.org, Paul Kocialkowski , Luca Ceresoli X-Mailer: b4 0.14.2 X-GND-Sasl: luca.ceresoli@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" This driver implements the point of a DRM pipeline where a connector allows removal of all the following bridges up to the panel. The DRM subsystem currently allows hotplug of the monitor but not preceding components. However there are embedded devices where the "tail" of the DRM pipeline, including one or more bridges, can be physically removed: .------------------------. | DISPLAY CONTROLLER | | .---------. .------. | | | ENCODER |<--| CRTC | | | '---------' '------' | '------|-----------------' | | HOTPLUG V CONNECTOR .---------. .--. .-. .---------. .-------. | 0 to N | | _| _| | | 1 to N | | | | BRIDGES |--DSI-->||_ |_ |--DSI-->| BRIDGES |--LVDS-->| PANEL | | | | | | | | | | | '---------' '--' '-' '---------' '-------' [--- fixed components --] [----------- removable add-on -----------] This driver supports such a device, where the final segment of a MIPI DSI bus, including one or more bridges, can be physically disconnected and reconnected at runtime, possibly with a different model. The add-on supported by this driver has a MIPI DSI bus traversing the hotplug connector and a DSI to LVDS bridge and an LVDS panel on the add-on. Hovever this driver is designed to be as far as possible generic and extendable to other busses that have no native hotplug and model ID discovery. This driver does not itself add and remove the bridges or panel on the add-on: this needs to be done by other means, e.g. device tree overlay runtime insertion and removal. The hotplug-bridge gets notified by the DRM bridge core after a removable bridge gets added or before it is removed. The hotplug-bridge role is to implement the "hot-pluggable connector" in the bridge chain. In this position, what the hotplug-bridge should ideally do is: * communicate with the previous component (bridge or encoder) so that it believes it always has a connected bridge following it and the DRM card is always present * be notified of the addition and removal of the following bridge and attach/detach to/from it * communicate with the following bridge so that it will attach and detach using the normal procedure (as if the entire pipeline were being created or destroyed, not only the tail) * instantiate two DRM connectors (similarly to what the DisplayPort MST code does): - a DSI connector representing the video lines of the hotplug connector; the status is always "disconnected" (no panel is ever attached directly to it) - an LSVD connector representing the classic connection to the panel; this gets added/removed whenever the add-on gets connected/disconnected; the status is always "connected" as the panel is always connected to the preceding bridge However some aspects make it a bit more complex than that. Most notably: * the next bridge can be probed and removed at any moment and all probing sequences need to be handled * the DSI host/device registration process, which adds to the DRM bridge attach process, makes the initial card registration tricky * the need to register and deregister the following bridges at runtime without tearing down the whole DRM card prevents using some of the functions that are normally recommended * the automatic mechanism to call the appropriate .get_modes operation (typically provided by the panel bridge) cannot work as the panel can disappear and reappear as a different model, so an ad-hoc lookup is needed The code handling these and other tricky aspects is accurately documented by comments in the code. The userspace representation when the add-on is not connected is: # modetest -c | grep -i '^[a-z0-9]' Connectors: id encoder status name size (mm) modes encoders 38 0 disconnected DSI-1 0x0 0 37 And when it is connected a new connector appears: # modetest -c | grep -i '^[a-z0-9]' Connectors: id encoder status name size (mm) modes encoders 38 0 disconnected DSI-1 0x0 0 37 39 0 connected LVDS-1 344x194 1 37 Co-developed-by: Paul Kocialkowski Signed-off-by: Paul Kocialkowski Signed-off-by: Luca Ceresoli --- Changed in v5: - use drm_bridge dynamic lifetime management - refcount bridge_modes and next_bridge via drm_bridge_get()/drm_bridge_put() - fix dynconn removal by using drm_connector_put() and making the .destroy callback work - add hotplug_bridge_grab() in hotplug_bridge_attach() to handle the case where the encoder driver is probed last - migrate platform driver from .remove_new to .remove Changed in v4: - convert from generic notifiers to the new bridge_event_notify bridge func - improved and updated commit message, adding 'modetest' output with/without add-on - select DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR, required after commit 9da7ec9b19d8 ("drm/bridge-connector: move to DRM_DISPLAY_HELPER module") Changed in v3: - dynamically add/remove the LVDS connector on hot(un)plug - take the firmware node normally via dev->of_node instead of using device_set_node(); this makes code more self-contained and generic - minor rewordings and cleanups Changed in v2: - change to be a platform device instantiated from the connector driver instead of a self-standing OF driver - add missing error handling for devm_drm_bridge_add() - various cleanups and style improvements - fix typo in comment --- MAINTAINERS | 5 + drivers/gpu/drm/bridge/Kconfig | 17 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/hotplug-bridge.c | 695 ++++++++++++++++++++++++++++++++ 4 files changed, 718 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 1675e44799e55d72711a6251f692a4a14bc3a84a..ab255f5696880258deee55d99ff0a7cde85efb7c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7182,6 +7182,11 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git F: Documentation/devicetree/bindings/display/panel/himax,hx8394.yaml F: drivers/gpu/drm/panel/panel-himax-hx8394.c +DRM DRIVER FOR HOTPLUG VIDEO CONNECTOR BRIDGE +M: Luca Ceresoli +S: Maintained +F: drivers/gpu/drm/bridge/hotplug-bridge.c + DRM DRIVER FOR HX8357D PANELS S: Orphan T: git https://gitlab.freedesktop.org/drm/misc/kernel.git diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 6b4664d91faa80f096ac6a0548ed342e802ae68b..f01971638d6818e33b32217922e165a8c18d51ee 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -90,6 +90,23 @@ config DRM_FSL_LDB help Support for i.MX8MP DPI-to-LVDS on-SoC encoder. +config DRM_HOTPLUG_BRIDGE + tristate "Hotplug DRM bridge support" + depends on OF + select DRM_PANEL_BRIDGE + select DRM_MIPI_DSI + select DRM_KMS_HELPER + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + help + Driver for a DRM bridge representing a physical connector that + splits a DRM pipeline into a fixed part and a physically + removable part. The fixed part includes up to the encoder and + zero or more bridges. The removable part includes any following + bridges up to the connector and panel and can be physically + removed and connected at runtime, possibly with different + components. + config DRM_ITE_IT6263 tristate "ITE IT6263 LVDS/HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 97304b429a530c108dcbff906965cda091b0a7a2..2f6ae1a97d15045316ee191c04dbc65650162bab 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o obj-$(CONFIG_DRM_FSL_LDB) += fsl-ldb.o +obj-$(CONFIG_DRM_HOTPLUG_BRIDGE) += hotplug-bridge.o obj-$(CONFIG_DRM_ITE_IT6263) += ite-it6263.o obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o diff --git a/drivers/gpu/drm/bridge/hotplug-bridge.c b/drivers/gpu/drm/bridge/hotplug-bridge.c new file mode 100644 index 0000000000000000000000000000000000000000..f717206287fc598cf7a3c5ac5bf9e1be4c8540d9 --- /dev/null +++ b/drivers/gpu/drm/bridge/hotplug-bridge.c @@ -0,0 +1,695 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A DRM bridge representing the split point between a fixed part of the + * DRM pipeline and a physically removable part. The fixed part includes up + * to the encoder and zero or more bridges. Insertion and removal of the + * "downstream" components happens via device driver probe/removal. + * + * Copyright (C) 2024, GE HealthCare + * + * Authors: + * Luca Ceresoli + * Paul Kocialkowski + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* + * Internal hotplug-bridge data. + * + * We have two 'struct drm_connector' here: + * - fixconn represents the DSI video lines on the hotplug connector where + * the removable part attaches. It is thus always instantiated. + * - dynconn represents the LVDS video lines where the panel is attached. + * It is part of the removable part of the video pipeline and as such is + * added and removed dynamically based on when the downstream devices + * appear and disappear. + */ +struct hotplug_bridge { + struct device *dev; + + /* Local bridge */ + struct drm_bridge bridge; + + /* Always-present connector (where the removal part will connect to) */ + struct drm_connector *fixconn; + + /* Downstream bridge (next in the chain) */ + struct drm_bridge *next_bridge; + /* Protect next_bridge */ + struct mutex next_bridge_mutex; + + /* Pointer to the last bridge exposing OP_MODES */ + struct drm_bridge *bridge_modes; + + /* The "tail" connector that gets added/removed at runtime */ + struct drm_connector dynconn; + + /* Local DSI host, for the downstream DSI device to attach to */ + struct mipi_dsi_host dsi_host; + /* Local DSI device, attached to the upstream DSI host */ + struct mipi_dsi_device *dsi_dev; + /* Upstream DSI host (the actual DSI controller) */ + struct mipi_dsi_host *prev_dsi_host; + + struct work_struct hpd_work; +}; + +static struct hotplug_bridge *hotplug_bridge_from_drm_bridge(struct drm_bridge *bridge) +{ + return container_of(bridge, struct hotplug_bridge, bridge); +} + +/* -------------------------------------------------------------------------- + * dynconn implementation + */ +static struct hotplug_bridge *hotplug_bridge_from_dynconn(struct drm_connector *conn) +{ + return container_of(conn, struct hotplug_bridge, dynconn); +} + +static int hotplug_bridge_dynconn_get_modes(struct drm_connector *connector) +{ + struct hotplug_bridge *hpb = hotplug_bridge_from_dynconn(connector); + + if (hpb->bridge_modes) + return hpb->bridge_modes->funcs->get_modes(hpb->bridge_modes, connector); + + return 0; +} + +static const struct drm_connector_helper_funcs hotplug_bridge_dynconn_connector_helper_funcs = { + .get_modes = hotplug_bridge_dynconn_get_modes, +}; + +static void hotplug_bridge_dynconn_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs dynconn_funcs = { + .destroy = hotplug_bridge_dynconn_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .fill_modes = drm_helper_probe_single_connector_modes, +}; + +/* + * In non-removable pipelines using a "bridge connector", + * drm_bridge_connector_init() stores in the bridge_connector a pointer to + * the last bridge having OP_MODES (typically the panel bridge), so the + * .get_modes op will automatically be called on that bridge (it also takes + * pointers to other bridges which we don't care about). The "bridge + * connector" is too restrictive for our use case so we cannot use it. But + * we need a pointer to the modes-providing bridge, so we need to replicate + * that bit of its logic. + * + * If no modes bridge is found, nothing is done. This is allowed. + * + * Also get the modes bridge to tie its lifetime to ours. + */ +static void hotplug_bridge_dynconn_bridge_modes_get(struct hotplug_bridge *hpb) +{ + struct drm_bridge *bridge; + + if (WARN_ON(!hpb->next_bridge || !hpb->bridge.encoder)) + return; + + drm_for_each_bridge_in_chain(hpb->bridge.encoder, bridge) + if (bridge->ops & DRM_BRIDGE_OP_MODES) { + drm_bridge_get(bridge); + hpb->bridge_modes = bridge; + } +} + +static void hotplug_bridge_dynconn_bridge_modes_put(struct hotplug_bridge *hpb) +{ + if (hpb->bridge_modes) + drm_bridge_put_and_clear(hpb->bridge_modes); +} + +static int hotplug_bridge_dynconn_add(struct hotplug_bridge *hpb) +{ + int err; + + err = drm_connector_init(hpb->bridge.dev, &hpb->dynconn, &dynconn_funcs, + DRM_MODE_CONNECTOR_LVDS); + if (err) + return err; + + drm_atomic_helper_connector_reset(&hpb->dynconn); + + drm_connector_helper_add(&hpb->dynconn, + &hotplug_bridge_dynconn_connector_helper_funcs); + + drm_connector_attach_encoder(&hpb->dynconn, hpb->bridge.encoder); + if (err) + goto err_cleanup; + + hotplug_bridge_dynconn_bridge_modes_get(hpb); + + err = drm_connector_register(&hpb->dynconn); + if (err) + goto err_cleanup; + + return 0; + +err_cleanup: + drm_connector_cleanup(&hpb->dynconn); + hotplug_bridge_dynconn_bridge_modes_put(hpb); + return err; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Attach the remote bridge to the encoder and to the next bridge in the + * chain, if possible. For this to succeed, we need to know: + * + * - the encoder, which is set at the first drm_bridge_attach() time + * - the next bridge, which is obtained via a notifier whenever the next + * bridge is (re)probed, or at probe time in case it was probed before us + * + * In order to handle different execution sequences, this function can be + * called from multiple places and needs to check all the prerequisites + * every time, and it will act only if both are met. + * + * Must be called with hpb->next_bridge_mutex held. + * + * Returns 0 if the encoder was attached successfully, -ENODEV if any of + * the two prerequisites above is not met (no encoder or no next bridge), + * the error returned by drm_bridge_attach() otherwise. + */ +static int hotplug_bridge_attach_to_encoder_chain(struct hotplug_bridge *hpb) +{ + int ret; + + if (!hpb->next_bridge || !hpb->bridge.encoder) + return -ENODEV; + + ret = drm_bridge_attach(hpb->bridge.encoder, hpb->next_bridge, &hpb->bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return dev_err_probe(hpb->dev, ret, "drm_bridge_attach failed\n"); + + dev_dbg(hpb->dev, "attached to encoder chain\n"); + + return 0; +} + +/* + * Stop the video pipeline and detach next_bridge. + * + * Must be called with hpb->next_bridge_mutex held. + */ +static void hotplug_bridge_detach_from_encoder_chain(struct hotplug_bridge *hpb) +{ + WARN_ON_ONCE(!hpb->next_bridge); + + dev_dbg(hpb->dev, "detaching from encoder chain\n"); + + drm_atomic_helper_shutdown(hpb->bridge.dev); + + drm_encoder_cleanup_from(hpb->bridge.encoder, hpb->next_bridge); +} + +static void hotplug_bridge_grab(struct hotplug_bridge *hpb) +{ + struct device *dev = hpb->dev; + struct drm_bridge *bridge; + struct drm_panel *panel; + int err; + + mutex_lock(&hpb->next_bridge_mutex); + + if (hpb->next_bridge) + goto out_unlock; + + /* + * This is supposed to be replaced by devm_drm_of_get_bridge(), but + * that is a devm_, and we need to remove the panel bridge also on + * next_bridge disconnect. + */ + err = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, &bridge); + if (err) + goto out_unlock; + + /* Convert the remote panel to a bridge */ + if (panel) + bridge = drm_panel_bridge_add(panel); + if (IS_ERR(bridge)) + goto out_unlock; + + drm_bridge_get(bridge); + hpb->next_bridge = bridge; + + dev_dbg(dev, "grabbed next bridge (%pOFn)\n", hpb->next_bridge->of_node); + + hpb->bridge.pre_enable_prev_first = hpb->next_bridge->pre_enable_prev_first; + + err = hotplug_bridge_attach_to_encoder_chain(hpb); + if (err) + goto err_panel_bridge_remove; + + err = hotplug_bridge_dynconn_add(hpb); + if (err) + goto err_detach_from_encoder_chain; + + queue_work(system_wq, &hpb->hpd_work); + goto out_unlock; + +err_detach_from_encoder_chain: + hotplug_bridge_detach_from_encoder_chain(hpb); +err_panel_bridge_remove: + drm_panel_bridge_remove(hpb->next_bridge); + drm_bridge_put_and_clear(hpb->next_bridge); +out_unlock: + mutex_unlock(&hpb->next_bridge_mutex); +} + +/* + * Detach from the next bridge and remove the panel bridge, either on + * release or when the downstream bridge is being removed. + * + * Can be called in these ways: + * + * - bridge_being_removed is NULL: detach unconditionally + * (this is useful on .remove() to teardown everything) + * - bridge_being_removed == hpb->next_bridge: detach + * (the downstream bridge is being removed) + * - bridge_being_removed != hpb->next_bridge: do nothing + * (the bridge being removed is not the downstream bridge) + * + * In all cases, does nothing when there is no downstream bridge. + */ +static void hotplug_bridge_release(struct hotplug_bridge *hpb, + struct drm_bridge *bridge_being_removed) +{ + mutex_lock(&hpb->next_bridge_mutex); + + if (!hpb->next_bridge) + goto out; + + if (bridge_being_removed && bridge_being_removed != hpb->next_bridge) + goto out; + + if (hpb->bridge_modes) + hotplug_bridge_dynconn_bridge_modes_put(hpb); + + dev_dbg(hpb->dev, "releasing next bridge (%pOFn)\n", hpb->next_bridge->of_node); + hotplug_bridge_detach_from_encoder_chain(hpb); + + dev_dbg(hpb->dev, "removing %s connector\n", hpb->dynconn.name); + drm_connector_put(&hpb->dynconn); + + /* + * This will check that the bridge actually belongs to panel-bridge + * before doing anything with it, so we can safely always call it. + */ + drm_panel_bridge_remove(hpb->next_bridge); + drm_bridge_put_and_clear(hpb->next_bridge); + + queue_work(system_wq, &hpb->hpd_work); + +out: + mutex_unlock(&hpb->next_bridge_mutex); +} + +static void hotplug_bridge_bridge_event_notify(struct drm_bridge *bridge, + enum drm_bridge_event_type event, + struct drm_bridge *event_bridge) +{ + struct hotplug_bridge *hpb = container_of(bridge, struct hotplug_bridge, bridge); + + switch (event) { + case DRM_EVENT_BRIDGE_ADD: + hotplug_bridge_grab(hpb); + break; + case DRM_EVENT_BRIDGE_REMOVE: + hotplug_bridge_release(hpb, event_bridge); + break; + } +} + +static int hotplug_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct hotplug_bridge *hpb = hotplug_bridge_from_drm_bridge(bridge); + struct device *dev = hpb->dev; + struct drm_connector *connector; + struct drm_encoder *encoder = hpb->bridge.encoder; + int err; + + /* Encoder was not yet provided to our bridge */ + if (!encoder) + return -ENODEV; + + /* Connector was already created */ + if (hpb->fixconn) + return dev_err_probe(dev, -EBUSY, "connector already created\n"); + + connector = drm_bridge_connector_init(bridge->dev, encoder); + if (IS_ERR(connector)) + return dev_err_probe(dev, PTR_ERR(connector), "failed to initialize connector\n"); + + drm_connector_attach_encoder(connector, encoder); + + hpb->fixconn = connector; + + drm_connector_register(connector); + + mutex_lock(&hpb->next_bridge_mutex); + err = hotplug_bridge_attach_to_encoder_chain(hpb); + mutex_unlock(&hpb->next_bridge_mutex); + + /* -ENODEV is acceptable, in case next_bridge is not yet known */ + if (err == -ENODEV) + err = 0; + + /* + * If the encoder driver is probed last, the + * hotplug_bridge_attach_to_encoder_chain() call in + * hotplug_bridge_grab() fails because hpb->bridge.encoder is still + * NULL, and hotplug_bridge_grab() will not have another chance to + * execute. So call it now, at the end of the encoder attach + * process. + */ + hotplug_bridge_grab(hpb); + + return err; +} + +static void hotplug_bridge_detach(struct drm_bridge *bridge) +{ + struct hotplug_bridge *hpb = hotplug_bridge_from_drm_bridge(bridge); + + mutex_lock(&hpb->next_bridge_mutex); + hotplug_bridge_detach_from_encoder_chain(hpb); + mutex_unlock(&hpb->next_bridge_mutex); + + if (hpb->fixconn) { + drm_connector_unregister(hpb->fixconn); + drm_connector_cleanup(hpb->fixconn); + hpb->fixconn = NULL; + } +} + +static void hotplug_bridge_destroy(struct drm_bridge *bridge) +{ + struct hotplug_bridge *hpb = hotplug_bridge_from_drm_bridge(bridge); + + kfree(hpb); +} + +/* + * The fixed connector is never attached to a panel, so it should always be + * reported as disconnected. + */ +static enum drm_connector_status hotplug_bridge_detect(struct drm_bridge *bridge) +{ + return connector_status_disconnected; +} + +static void hotplug_bridge_hpd_work_func(struct work_struct *work) +{ + struct hotplug_bridge *hpb = container_of(work, struct hotplug_bridge, hpd_work); + + if (hpb->bridge.dev) + drm_helper_hpd_irq_event(hpb->bridge.dev); +} + +static const struct drm_bridge_funcs hotplug_bridge_funcs = { + .attach = hotplug_bridge_attach, + .detach = hotplug_bridge_detach, + .destroy = hotplug_bridge_destroy, + .detect = hotplug_bridge_detect, + .bridge_event_notify = hotplug_bridge_bridge_event_notify, +}; + +static int hotplug_bridge_dsi_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device_remote) +{ + struct hotplug_bridge *hpb = dev_get_drvdata(host->dev); + + if (!hpb->dsi_dev) + return -ENODEV; + + mipi_dsi_detach(hpb->dsi_dev); + mipi_dsi_device_unregister(hpb->dsi_dev); + hpb->dsi_dev = NULL; + + return 0; +} + +/* + * Attach the local DSI device to the upstream DSI host, possibly with a + * "null" format. + * + * In "normal" bridges this function should be _only_ used as the .attach + * callback of hotplug_bridge_dsi_ops. But "normal" bridges have their + * downstream DSI device always connected, which we don't. When booting + * without anything connected downstream, our upstream bridge could be not + * even calling drm_bridge_add() until we do attach ourselves as a DSI + * device, preventing the whole DRM card from being instantiated. + * + * In order to always have a DRM card after boot, we do call this same + * function while probing in order to attach as a DSI device to the DSI + * master. However during probe we don't know the bus format yet. It would + * be nice to be able to update the format afterwards when a downstream DSI + * device is attaching to our local host, but there is no callback for + * that. To overcome this limitation, this function can be called in two + * ways: + * + * - during probe, to make the upstream bridge happy, when there is no + * next_dsi_dev yet and thus the lanes/format/etc are unknown + * - as the mipi_dsi_host_ops.attach callback proper, as soon as the + * next_dsi_dev is known + * + * The resulting call sequence is: + * + * 1. hotplug_bridge_dsi_attach() called by hotplug_bridge_probe() with + * next_dsi_dev == NULL: we attach to the host but with a fake format + * so the DRM card can be populated. hpb->dsi_dev becomes non-NULL. + * 2. hotplug_bridge_dsi_attach() called as .attach callback from a + * downstream device when it becomes available: we need to detach in + * order to re-attach with the format of the device. hpb->dsi_dev + * is found non-NULL, then reused so it will be non-NULL again. + * 3. hotplug_bridge_dsi_detach() called as the .detach callback by a + * downstream device: cleans up everything normally. hpb->dsi_dev goes + * from non-NULL to NULL. + * 4. hotplug_bridge_dsi_attach() called by a downstream device: attaches + * normally to the upstream DSI host. hpb->dsi_dev goes from NULL to + * non-NULL. + * + * Steps 3 and 4 are the "normal" attach/detach steps as on "normal" + * bridges. + * + * Steps 1 and 2 happen only the first time, steps 3 and 4 will happen + * every time the downstream bridge disconnects and reconnects. + */ +static int hotplug_bridge_dsi_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *next_dsi_dev) +{ + struct device *dev = host->dev; + struct hotplug_bridge *hpb = dev_get_drvdata(dev); + struct mipi_dsi_device *dsi_dev; + const struct mipi_dsi_device_info dsi_info = { + .type = "hotplug-bridge", + .channel = 0, + .node = NULL, + }; + int err; + + /* + * Step 2 only (first time we are called for an actual device + * attaching): clean up the fake attach done at step 1 + */ + if (hpb->dsi_dev) + hotplug_bridge_dsi_detach(&hpb->dsi_host, NULL); + + /* Register a local DSI device with the remote DSI host */ + dsi_dev = mipi_dsi_device_register_full(hpb->prev_dsi_host, + &dsi_info); + if (IS_ERR(dsi_dev)) + return PTR_ERR(dsi_dev); + + /* At step 1 we have no downstream device to get the format from */ + if (next_dsi_dev) { + dsi_dev->channel = next_dsi_dev->channel; + dsi_dev->lanes = next_dsi_dev->lanes; + dsi_dev->format = next_dsi_dev->format; + dsi_dev->mode_flags = next_dsi_dev->mode_flags; + } + + /* Attach our local DSI device to the remote DSI host */ + err = mipi_dsi_attach(dsi_dev); + if (err) { + mipi_dsi_device_unregister(dsi_dev); + return dev_err_probe(dev, err, "failed to attach hotplug dsi device to host\n"); + } + + hpb->dsi_dev = dsi_dev; + + return 0; +} + +/* + * Propagate mipi_dsi_device_transfer() to the upstream DSI host. + * + * Reimplements identically the minimal needed part of + * mipi_dsi_device_transfer(), including the -ENOSYS return value. + */ +static ssize_t hotplug_bridge_dsi_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct hotplug_bridge *hpb = dev_get_drvdata(host->dev); + const struct mipi_dsi_host_ops *ops; + + if (!hpb->dsi_dev) + return -ENODEV; + + ops = hpb->dsi_dev->host->ops; + + if (!ops || !ops->transfer) + return -ENOSYS; + + return ops->transfer(hpb->dsi_dev->host, msg); +} + +static const struct mipi_dsi_host_ops hotplug_bridge_dsi_ops = { + .attach = hotplug_bridge_dsi_attach, + .detach = hotplug_bridge_dsi_detach, + .transfer = hotplug_bridge_dsi_transfer, +}; + +/* + * Find the upstream DSI host and register our downstream-facing DSI host. + */ +static int hotplug_bridge_dsi_setup(struct hotplug_bridge *hpb) +{ + struct device *dev = hpb->dev; + struct device_node *endpoint; + struct device_node *node; + + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); + node = of_graph_get_remote_port_parent(endpoint); + + hpb->prev_dsi_host = of_find_mipi_dsi_host_by_node(node); + + of_node_put(node); + of_node_put(endpoint); + + if (!hpb->prev_dsi_host) + return -EPROBE_DEFER; + + hpb->dsi_host.dev = dev; + hpb->dsi_host.ops = &hotplug_bridge_dsi_ops; + + return mipi_dsi_host_register(&hpb->dsi_host); +} + +static void hotplug_bridge_dsi_cleanup(struct hotplug_bridge *hpb) +{ + mipi_dsi_host_unregister(&hpb->dsi_host); +} + +static int hotplug_bridge_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hotplug_bridge *hpb; + struct drm_bridge *bridge; + int err; + + hpb = kzalloc(sizeof(*hpb), GFP_KERNEL); + if (!hpb) + return -ENOMEM; + + err = drm_bridge_init(dev, &hpb->bridge, &hotplug_bridge_funcs); + if (err) + return err; + + hpb->dev = dev; + + mutex_init(&hpb->next_bridge_mutex); + INIT_WORK(&hpb->hpd_work, hotplug_bridge_hpd_work_func); + + err = hotplug_bridge_dsi_setup(hpb); + if (err) + return dev_err_probe(dev, err, "failed to setup DSI\n"); + + bridge = &hpb->bridge; + bridge->of_node = dev->of_node; + bridge->type = DRM_MODE_CONNECTOR_DSI; + bridge->ops |= DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_HPD; + + platform_set_drvdata(pdev, hpb); + + err = devm_drm_bridge_add(dev, bridge); + if (err) { + dev_err_probe(dev, err, "failed adding bridge\n"); + goto err_dsi_cleanup; + } + + err = hotplug_bridge_dsi_attach(&hpb->dsi_host, NULL); + if (err) { + dev_err_probe(dev, err, "failed first attach to upstream DSI host\n"); + goto err_dsi_cleanup; + } + + /* + * Since devm_drm_bridge_add() we can be notified of any bridges + * appearing, but also check now, in case the next bridge was + * probed earlier + */ + hotplug_bridge_grab(hpb); + + return 0; + +err_dsi_cleanup: + hotplug_bridge_dsi_cleanup(hpb); + return err; +} + +static void hotplug_bridge_remove(struct platform_device *pdev) +{ + struct hotplug_bridge *hpb = platform_get_drvdata(pdev); + + cancel_work_sync(&hpb->hpd_work); + + hotplug_bridge_release(hpb, NULL); + + hotplug_bridge_dsi_cleanup(hpb); +} + +static const struct platform_device_id hotplug_bridge_platform_ids[] = { + { .name = "hotplug-dsi-bridge" }, + {}, +}; +MODULE_DEVICE_TABLE(platform, hotplug_bridge_platform_ids); + +static struct platform_driver hotplug_bridge_driver = { + .probe = hotplug_bridge_probe, + .remove = hotplug_bridge_remove, + .id_table = hotplug_bridge_platform_ids, + .driver = { + .name = "hotplug-drm-bridge", + }, +}; + +module_platform_driver(hotplug_bridge_driver); + +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Paul Kocialkowski "); +MODULE_DESCRIPTION("Hotplug DRM Bridge"); +MODULE_LICENSE("GPL");