From mboxrd@z Thu Jan 1 00:00:00 1970 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 06B3AFD2D63 for ; Tue, 10 Mar 2026 12:13:49 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 4A8B510E6FD; Tue, 10 Mar 2026 12:13:48 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="p7kjSbtk"; dkim-atps=neutral Received: from smtpout-04.galae.net (smtpout-04.galae.net [185.171.202.116]) by gabe.freedesktop.org (Postfix) with ESMTPS id 88E2010E6BC for ; Tue, 10 Mar 2026 12:13:45 +0000 (UTC) Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-04.galae.net (Postfix) with ESMTPS id 8EF9BC40683; Tue, 10 Mar 2026 12:14:04 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id C1A3A60002; Tue, 10 Mar 2026 12:13:43 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id A3BA910369C2C; Tue, 10 Mar 2026 13:13:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1773144822; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=jFa3HhMOQ05RoZTHCb1XkHixgVs2EPgA338waxzgtnY=; b=p7kjSbtkLm3dFXw8FUtfw0IsSQaVc5ykunUggP8/bV34x3fOkIeCoRdJbtI0iZQnRpLDc1 tojoFS619oftGVxxqu7wuIU5/pRzMDA6PunnxVTRigWVuxjpqy4DgUHd6z315pYhx5lx4V Ct1GuCJhx7R+61y3G2UFf44gbxHb+g1aRWyFWqgcI1Y4DCbJMy6zPI7qdtaO0JL3dNzJO3 nyo8Xx1GX8LS6JzZYTtmz5/XfUd3V9roiRU4ACrLw+IEwnqaB+feVPAEBIkOjK4ZNsGN2S THOvpVwlB2lFRPOBVHhV/eIYF3Cxbx4H/P4czC6+syi5Zwc6Bth2F/9CPLtjdA== From: Luca Ceresoli Date: Tue, 10 Mar 2026 13:13:23 +0100 Subject: [PATCH v2 1/2] drm/bridge: add drm_bridge_clear_and_put() MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260310-drm-bridge-atomic-vs-remove-clear_and_put-v2-1-51fe222f3cf0@bootlin.com> References: <20260310-drm-bridge-atomic-vs-remove-clear_and_put-v2-0-51fe222f3cf0@bootlin.com> In-Reply-To: <20260310-drm-bridge-atomic-vs-remove-clear_and_put-v2-0-51fe222f3cf0@bootlin.com> To: Andrzej Hajda , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , Inki Dae , Jagan Teki , Marek Szyprowski Cc: Osama Abdelkader , Hui Pu , Ian Ray , Thomas Petazzoni , dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, Luca Ceresoli X-Mailer: b4 0.14.3 X-Last-TLS-Session-Version: TLSv1.3 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" Drivers having a struct drm_bridge pointer pointing to a bridge in many cases hold that reference until the owning device is removed. In those cases the reference to the bridge can be put in the .remove callback (possibly using devm actions) or in the .destroy func (possibly with the help of struct drm_bridge::next_bridge). At those moments the driver should not be operating anymore and won't dereference the bridge pointer after it is put. However there are cases when drivers need to stop holding a reference to a bridge even when their device is not being removed. This is the case for bridge hot-unplug, when a bridge is removed but the previous entity (bridge or encoder) is staying. In such case the "previous entity" needs to put it but cannot do it via devm or .destroy, because it is not being removed. The easy way to dispose of such pointer is: drm_bridge_put(my_priv->some_bridge); my_priv->some_bridge = NULL; However this is risky because there is a time window between the two lines where the reference is put, and thus the bridge could be deallocated, but the pointer is still assigned. If other functions of the same driver were invoked concurrently they might dereference my_priv->some_bridge during that window, resulting in use-after-free. A correct solution is to clear the pointer before putting the reference, but that needs a temporary variable: struct drm_bridge *temp = my_priv->some_bridge; my_priv->some_bridge = NULL; drm_bridge_put(temp); This solution is however annoying to write, so the incorrect version might still sneak in. Add a simple, easy to use function to put a bridge after setting its pointer to NULL in the correct way. Signed-off-by: Luca Ceresoli --- This is a renamed version of drm_bridge_put_and_clear() which was sent as part of a larger patch [0] but with largely rewritten documentation and a detailed commit message. [0] https://lore.kernel.org/lkml/20250206-hotplug-drm-bridge-v6-14-9d6f2c9c3058@bootlin.com/ --- drivers/gpu/drm/drm_bridge.c | 34 ++++++++++++++++++++++++++++++++++ include/drm/drm_bridge.h | 1 + 2 files changed, 35 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index f8b0333a0a3b..0260b16cb47d 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -304,6 +304,9 @@ EXPORT_SYMBOL(drm_bridge_get); * * This function decrements the bridge's reference count and frees the * object if the reference count drops to zero. + * + * See also drm_bridge_clear_and_put() if you also need to set the pointer + * to NULL */ void drm_bridge_put(struct drm_bridge *bridge) { @@ -312,6 +315,37 @@ void drm_bridge_put(struct drm_bridge *bridge) } EXPORT_SYMBOL(drm_bridge_put); +/** + * drm_bridge_clear_and_put - Given a bridge pointer, clear the pointer + * then put the bridge + * @bridge_pp: pointer to pointer to a struct drm_bridge; ``bridge_pp`` + * must be non-NULL; if ``*bridge_pp`` is NULL this function + * does nothing + * + * Helper to put a DRM bridge, but only after setting its pointer to + * NULL. Useful when a struct drm_bridge reference must be dropped without + * leaving a use-after-free window where the pointed bridge might have been + * freed while still holding a pointer to it. + * + * For struct ``drm_bridge *some_bridge``, this code:: + * + * drm_bridge_clear_and_put(&some_bridge); + * + * is equivalent to the more complex:: + * + * struct drm_bridge *temp = some_bridge; + * some_bridge = NULL; + * drm_bridge_put(temp); + */ +void drm_bridge_clear_and_put(struct drm_bridge **bridge_pp) +{ + struct drm_bridge *bridge = *bridge_pp; + + *bridge_pp = NULL; + drm_bridge_put(bridge); +} +EXPORT_SYMBOL(drm_bridge_clear_and_put); + /** * drm_bridge_put_void - wrapper to drm_bridge_put() taking a void pointer * diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 4f19f7064ee3..66ab89cf48aa 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1290,6 +1290,7 @@ void drm_bridge_unplug(struct drm_bridge *bridge); struct drm_bridge *drm_bridge_get(struct drm_bridge *bridge); void drm_bridge_put(struct drm_bridge *bridge); +void drm_bridge_clear_and_put(struct drm_bridge **bridge_pp); /* Cleanup action for use with __free() */ DEFINE_FREE(drm_bridge_put, struct drm_bridge *, if (_T) drm_bridge_put(_T)) -- 2.53.0