From patchwork Tue Aug 29 14:02:03 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maarten Lankhorst X-Patchwork-Id: 9927455 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 5B9CC6022E for ; Tue, 29 Aug 2017 14:02:19 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 536262892D for ; Tue, 29 Aug 2017 14:02:19 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 4836128930; Tue, 29 Aug 2017 14:02:19 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_MED autolearn=unavailable version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 8709A2892D for ; Tue, 29 Aug 2017 14:02:18 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 0F8AC6E3D4; Tue, 29 Aug 2017 14:02:13 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from mblankhorst.nl (mblankhorst.nl [141.105.120.124]) by gabe.freedesktop.org (Postfix) with ESMTPS id 084316E3CD; Tue, 29 Aug 2017 14:02:10 +0000 (UTC) From: Maarten Lankhorst To: dri-devel@lists.freedesktop.org Subject: [PATCH 2/2] drm/atomic: Fix freeing connector/plane state too early by tracking commits Date: Tue, 29 Aug 2017 16:02:03 +0200 Message-Id: <20170829140203.10574-2-maarten.lankhorst@linux.intel.com> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20170829140203.10574-1-maarten.lankhorst@linux.intel.com> References: <20170829140203.10574-1-maarten.lankhorst@linux.intel.com> Cc: intel-gfx@lists.freedesktop.org, Laurent Pinchart X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP Currently we neatly track the crtc state, but forget to look at plane/connector state. When doing a nonblocking modeset, immediately followed by a setprop before the modeset completes, the setprop will see the modesets new state as the old state and free it. This has to be solved by waiting for hw_done on the connector, even if it's not assigned to a crtc. When a connector is unbound we take the last crtc commit, and when it stays unbound we create a new crtc commit for the connector that gets signaled on hw_done. We wait for it the same way as we do for crtc's, which will make sure we never run into a use-after-free situation. Signed-off-by: Maarten Lankhorst Testcase: kms_atomic_transition.plane-use-after-nonblocking-unbind* Cc: Laurent Pinchart --- drivers/gpu/drm/drm_atomic_helper.c | 171 ++++++++++++++++++++++++++++++++++-- include/drm/drm_connector.h | 7 ++ include/drm/drm_plane.h | 7 ++ 3 files changed, 179 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 9c2888cb4081..a4fd500d6200 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1644,6 +1644,39 @@ static void release_crtc_commit(struct completion *completion) drm_crtc_commit_put(commit); } +static void init_commit(struct drm_crtc_commit *commit, struct drm_crtc *crtc) +{ + init_completion(&commit->flip_done); + init_completion(&commit->hw_done); + init_completion(&commit->cleanup_done); + INIT_LIST_HEAD(&commit->commit_entry); + kref_init(&commit->ref); + commit->crtc = crtc; +} + +static struct drm_crtc_commit * +init_or_ref_crtc_commit(struct drm_atomic_state *state, struct drm_crtc *crtc) +{ + struct drm_crtc_commit *commit; + + if (crtc) { + struct drm_crtc_state *new_crtc_state; + + new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + commit = new_crtc_state->commit; + drm_crtc_commit_get(commit); + } else { + commit = kzalloc(sizeof(*commit), GFP_KERNEL); + if (!commit) + return NULL; + + init_commit(commit, NULL); + } + + return commit; +} + /** * drm_atomic_helper_setup_commit - setup possibly nonblocking commit * @state: new modeset state to be committed @@ -1692,6 +1725,10 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, { struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; + struct drm_connector *conn; + struct drm_connector_state *old_conn_state, *new_conn_state; + struct drm_plane *plane; + struct drm_plane_state *old_plane_state, *new_plane_state; struct drm_crtc_commit *commit; int i, ret; @@ -1700,12 +1737,7 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, if (!commit) return -ENOMEM; - init_completion(&commit->flip_done); - init_completion(&commit->hw_done); - init_completion(&commit->cleanup_done); - INIT_LIST_HEAD(&commit->commit_entry); - kref_init(&commit->ref); - commit->crtc = crtc; + init_commit(commit, crtc); new_crtc_state->commit = commit; @@ -1741,6 +1773,36 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, drm_crtc_commit_get(commit); } + for_each_oldnew_connector_in_state(state, conn, old_conn_state, new_conn_state, i) { + if (new_conn_state->crtc) + continue; + + if (nonblock && old_conn_state->commit && + !try_wait_for_completion(&old_conn_state->commit->flip_done)) + return -EBUSY; + + commit = init_or_ref_crtc_commit(state, old_conn_state->crtc); + if (!commit) + return -ENOMEM; + + new_conn_state->commit = commit; + } + + for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) { + if (new_plane_state->crtc) + continue; + + if (nonblock && old_plane_state->commit && + !try_wait_for_completion(&old_plane_state->commit->flip_done)) + return -EBUSY; + + commit = init_or_ref_crtc_commit(state, old_plane_state->crtc); + if (!commit) + return -ENOMEM; + + new_plane_state->commit = commit; + } + return 0; } EXPORT_SYMBOL(drm_atomic_helper_setup_commit); @@ -1761,6 +1823,10 @@ void drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *old_state) { struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state; + struct drm_plane *plane; + struct drm_plane_state *old_plane_state; + struct drm_connector *conn; + struct drm_connector_state *old_conn_state; struct drm_crtc_commit *commit; int i; long ret; @@ -1785,6 +1851,48 @@ void drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *old_state) DRM_ERROR("[CRTC:%d:%s] flip_done timed out\n", crtc->base.id, crtc->name); } + + for_each_old_connector_in_state(old_state, conn, old_conn_state, i) { + commit = old_conn_state->commit; + + if (!commit) + continue; + + ret = wait_for_completion_timeout(&commit->hw_done, + 10*HZ); + if (ret == 0) + DRM_ERROR("[CONNECTOR:%d:%s] hw_done timed out\n", + conn->base.id, conn->name); + + /* Currently no support for overwriting flips, hence + * stall for previous one to execute completely. */ + ret = wait_for_completion_timeout(&commit->flip_done, + 10*HZ); + if (ret == 0) + DRM_ERROR("[CONNECTOR:%d:%s] flip_done timed out\n", + conn->base.id, conn->name); + } + + for_each_old_plane_in_state(old_state, plane, old_plane_state, i) { + commit = old_plane_state->commit; + + if (!commit) + continue; + + ret = wait_for_completion_timeout(&commit->hw_done, + 10*HZ); + if (ret == 0) + DRM_ERROR("[PLANE:%d:%s] hw_done timed out\n", + plane->base.id, plane->name); + + /* Currently no support for overwriting flips, hence + * stall for previous one to execute completely. */ + ret = wait_for_completion_timeout(&commit->flip_done, + 10*HZ); + if (ret == 0) + DRM_ERROR("[PLANE:%d:%s] flip_done timed out\n", + plane->base.id, plane->name); + } } EXPORT_SYMBOL(drm_atomic_helper_wait_for_dependencies); @@ -1807,6 +1915,10 @@ void drm_atomic_helper_commit_hw_done(struct drm_atomic_state *old_state) { struct drm_crtc *crtc; struct drm_crtc_state *new_crtc_state; + struct drm_connector *conn; + struct drm_connector_state *new_conn_state; + struct drm_plane *plane; + struct drm_plane_state *new_plane_state; struct drm_crtc_commit *commit; int i; @@ -1819,6 +1931,23 @@ void drm_atomic_helper_commit_hw_done(struct drm_atomic_state *old_state) WARN_ON(new_crtc_state->event); complete_all(&commit->hw_done); } + + for_each_new_connector_in_state(old_state, conn, new_conn_state, i) { + commit = new_conn_state->commit; + if (commit && !commit->crtc) { + complete_all(&commit->hw_done); + complete_all(&commit->flip_done); + } + } + + for_each_new_plane_in_state(old_state, plane, new_plane_state, i) { + commit = new_plane_state->commit; + if (commit && !commit->crtc) { + complete_all(&commit->hw_done); + complete_all(&commit->flip_done); + } + } + } EXPORT_SYMBOL(drm_atomic_helper_commit_hw_done); @@ -2258,6 +2387,28 @@ int drm_atomic_helper_swap_state(struct drm_atomic_state *state, if (ret) return ret; } + + for_each_old_connector_in_state(state, connector, old_conn_state, i) { + commit = old_conn_state->commit; + + if (!commit) + continue; + + ret = wait_for_completion_interruptible(&commit->hw_done); + if (ret) + return ret; + } + + for_each_old_plane_in_state(state, plane, old_plane_state, i) { + commit = old_plane_state->commit; + + if (!commit) + continue; + + ret = wait_for_completion_interruptible(&commit->hw_done); + if (ret) + return ret; + } } for_each_oldnew_connector_in_state(state, connector, old_conn_state, new_conn_state, i) { @@ -3240,6 +3391,7 @@ void __drm_atomic_helper_plane_duplicate_state(struct drm_plane *plane, drm_framebuffer_get(state->fb); state->fence = NULL; + state->commit = NULL; } EXPORT_SYMBOL(__drm_atomic_helper_plane_duplicate_state); @@ -3281,6 +3433,9 @@ void __drm_atomic_helper_plane_destroy_state(struct drm_plane_state *state) if (state->fence) dma_fence_put(state->fence); + + if (state->commit) + drm_crtc_commit_put(state->commit); } EXPORT_SYMBOL(__drm_atomic_helper_plane_destroy_state); @@ -3359,6 +3514,7 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector, memcpy(state, connector->state, sizeof(*state)); if (state->crtc) drm_connector_get(connector); + state->commit = NULL; } EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state); @@ -3485,6 +3641,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state) { if (state->crtc) drm_connector_put(state->connector); + + if (state->commit) + drm_crtc_commit_put(state->commit); } EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state); diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index ea8da401c93c..8837649d16e8 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -347,6 +347,13 @@ struct drm_connector_state { struct drm_atomic_state *state; + /** + * @commit: Tracks the pending commit to prevent use-after-free conditions. + * + * Is only set when @crtc is NULL. + */ + struct drm_crtc_commit *commit; + struct drm_tv_connector_state tv; /** diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h index 73f90f9d057f..7d96116fd4c4 100644 --- a/include/drm/drm_plane.h +++ b/include/drm/drm_plane.h @@ -123,6 +123,13 @@ struct drm_plane_state { */ bool visible; + /** + * @commit: Tracks the pending commit to prevent use-after-free conditions. + * + * Is only set when @crtc is NULL. + */ + struct drm_crtc_commit *commit; + struct drm_atomic_state *state; };