summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/gpu/drm/drm_atomic_helper.c347
-rw-r--r--include/drm/drm_atomic_helper.h7
2 files changed, 354 insertions, 0 deletions
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index aa2cad922791..6dee09cfbf93 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -1157,6 +1157,10 @@ int drm_atomic_helper_commit(struct drm_device *dev,
if (nonblock)
return -EBUSY;
+ ret = drm_atomic_helper_setup_commit(state, nonblock);
+ if (ret)
+ return ret;
+
ret = drm_atomic_helper_prepare_planes(dev, state);
if (ret)
return ret;
@@ -1187,16 +1191,22 @@ int drm_atomic_helper_commit(struct drm_device *dev,
drm_atomic_helper_wait_for_fences(dev, state);
+ drm_atomic_helper_wait_for_dependencies(state);
+
drm_atomic_helper_commit_modeset_disables(dev, state);
drm_atomic_helper_commit_planes(dev, state, false);
drm_atomic_helper_commit_modeset_enables(dev, state);
+ drm_atomic_helper_commit_hw_done(state);
+
drm_atomic_helper_wait_for_vblanks(dev, state);
drm_atomic_helper_cleanup_planes(dev, state);
+ drm_atomic_helper_commit_cleanup_done(state);
+
drm_atomic_state_free(state);
return 0;
@@ -1241,6 +1251,306 @@ EXPORT_SYMBOL(drm_atomic_helper_commit);
* being displayed.
*/
+static int stall_checks(struct drm_crtc *crtc, bool nonblock)
+{
+ struct drm_crtc_commit *commit, *stall_commit = NULL;
+ bool completed = true;
+ int i;
+ long ret = 0;
+
+ spin_lock(&crtc->commit_lock);
+ i = 0;
+ list_for_each_entry(commit, &crtc->commit_list, commit_entry) {
+ if (i == 0) {
+ completed = try_wait_for_completion(&commit->flip_done);
+ /* Userspace is not allowed to get ahead of the previous
+ * commit with nonblocking ones. */
+ if (!completed && nonblock) {
+ spin_unlock(&crtc->commit_lock);
+ return -EBUSY;
+ }
+ } else if (i == 1) {
+ stall_commit = commit;
+ drm_crtc_commit_get(stall_commit);
+ } else
+ break;
+
+ i++;
+ }
+ spin_unlock(&crtc->commit_lock);
+
+ if (!stall_commit)
+ return 0;
+
+ /* We don't want to let commits get ahead of cleanup work too much,
+ * stalling on 2nd previous commit means triple-buffer won't ever stall.
+ */
+ ret = wait_for_completion_interruptible_timeout(&commit->cleanup_done,
+ 10*HZ);
+ if (ret == 0)
+ DRM_ERROR("[CRTC:%d:%s] cleanup_done timed out\n",
+ crtc->base.id, crtc->name);
+
+ drm_crtc_commit_put(stall_commit);
+
+ return ret < 0 ? ret : 0;
+}
+
+/**
+ * drm_atomic_helper_setup_commit - setup possibly nonblocking commit
+ * @state: new modeset state to be committed
+ * @nonblock: whether nonblocking behavior is requested.
+ *
+ * This function prepares @state to be used by the atomic helper's support for
+ * nonblocking commits. Drivers using the nonblocking commit infrastructure
+ * should always call this function from their ->atomic_commit hook.
+ *
+ * To be able to use this support drivers need to use a few more helper
+ * functions. drm_atomic_helper_wait_for_dependencies() must be called before
+ * actually committing the hardware state, and for nonblocking commits this call
+ * must be placed in the async worker. See also drm_atomic_helper_swap_state()
+ * and it's stall parameter, for when a driver's commit hooks look at the
+ * ->state pointers of struct &drm_crtc, &drm_plane or &drm_connector directly.
+ *
+ * Completion of the hardware commit step must be signalled using
+ * drm_atomic_helper_commit_hw_done(). After this step the driver is not allowed
+ * to read or change any permanent software or hardware modeset state. The only
+ * exception is state protected by other means than &drm_modeset_lock locks.
+ * Only the free standing @state with pointers to the old state structures can
+ * be inspected, e.g. to clean up old buffers using
+ * drm_atomic_helper_cleanup_planes().
+ *
+ * At the very end, before cleaning up @state drivers must call
+ * drm_atomic_helper_commit_cleanup_done().
+ *
+ * This is all implemented by in drm_atomic_helper_commit(), giving drivers a
+ * complete and esay-to-use default implementation of the atomic_commit() hook.
+ *
+ * The tracking of asynchronously executed and still pending commits is done
+ * using the core structure &drm_crtc_commit.
+ *
+ * By default there's no need to clean up resources allocated by this function
+ * explicitly: drm_atomic_state_default_clear() will take care of that
+ * automatically.
+ *
+ * Returns:
+ *
+ * 0 on success. -EBUSY when userspace schedules nonblocking commits too fast,
+ * -ENOMEM on allocation failures and -EINTR when a signal is pending.
+ */
+int drm_atomic_helper_setup_commit(struct drm_atomic_state *state,
+ bool nonblock)
+{
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *crtc_state;
+ struct drm_crtc_commit *commit;
+ int i, ret;
+
+ for_each_crtc_in_state(state, crtc, crtc_state, i) {
+ commit = kzalloc(sizeof(*commit), GFP_KERNEL);
+ 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;
+
+ state->crtcs[i].commit = commit;
+
+ ret = stall_checks(crtc, nonblock);
+ if (ret)
+ return ret;
+
+ /* Drivers only send out events when at least either current or
+ * new CRTC state is active. Complete right away if everything
+ * stays off. */
+ if (!crtc->state->active && !crtc_state->active) {
+ complete_all(&commit->flip_done);
+ continue;
+ }
+
+ /* Legacy cursor updates are fully unsynced. */
+ if (state->legacy_cursor_update) {
+ complete_all(&commit->flip_done);
+ continue;
+ }
+
+ if (!crtc_state->event) {
+ commit->event = kzalloc(sizeof(*commit->event),
+ GFP_KERNEL);
+ if (!commit->event)
+ return -ENOMEM;
+
+ crtc_state->event = commit->event;
+ }
+
+ crtc_state->event->base.completion = &commit->flip_done;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_atomic_helper_setup_commit);
+
+
+static struct drm_crtc_commit *preceeding_commit(struct drm_crtc *crtc)
+{
+ struct drm_crtc_commit *commit;
+ int i = 0;
+
+ list_for_each_entry(commit, &crtc->commit_list, commit_entry) {
+ /* skip the first entry, that's the current commit */
+ if (i == 1)
+ return commit;
+ i++;
+ }
+
+ return NULL;
+}
+
+/**
+ * drm_atomic_helper_wait_for_dependencies - wait for required preceeding commits
+ * @state: new modeset state to be committed
+ *
+ * This function waits for all preceeding commits that touch the same CRTC as
+ * @state to both be committed to the hardware (as signalled by
+ * drm_atomic_helper_commit_hw_done) and executed by the hardware (as signalled
+ * by calling drm_crtc_vblank_send_event on the event member of
+ * &drm_crtc_state).
+ *
+ * This is part of the atomic helper support for nonblocking commits, see
+ * drm_atomic_helper_setup_commit() for an overview.
+ */
+void drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *state)
+{
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *crtc_state;
+ struct drm_crtc_commit *commit;
+ int i;
+ long ret;
+
+ for_each_crtc_in_state(state, crtc, crtc_state, i) {
+ spin_lock(&crtc->commit_lock);
+ commit = preceeding_commit(crtc);
+ if (commit)
+ drm_crtc_commit_get(commit);
+ spin_unlock(&crtc->commit_lock);
+
+ if (!commit)
+ continue;
+
+ ret = wait_for_completion_timeout(&commit->hw_done,
+ 10*HZ);
+ if (ret == 0)
+ DRM_ERROR("[CRTC:%d:%s] hw_done timed out\n",
+ crtc->base.id, crtc->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("[CRTC:%d:%s] flip_done timed out\n",
+ crtc->base.id, crtc->name);
+
+ drm_crtc_commit_put(commit);
+ }
+}
+EXPORT_SYMBOL(drm_atomic_helper_wait_for_dependencies);
+
+/**
+ * drm_atomic_helper_commit_hw_done - setup possible nonblocking commit
+ * @state: new modeset state to be committed
+ *
+ * This function is used to signal completion of the hardware commit step. After
+ * this step the driver is not allowed to read or change any permanent software
+ * or hardware modeset state. The only exception is state protected by other
+ * means than &drm_modeset_lock locks.
+ *
+ * Drivers should try to postpone any expensive or delayed cleanup work after
+ * this function is called.
+ *
+ * This is part of the atomic helper support for nonblocking commits, see
+ * drm_atomic_helper_setup_commit() for an overview.
+ */
+void drm_atomic_helper_commit_hw_done(struct drm_atomic_state *state)
+{
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *crtc_state;
+ struct drm_crtc_commit *commit;
+ int i;
+
+ for_each_crtc_in_state(state, crtc, crtc_state, i) {
+ commit = state->crtcs[i].commit;
+ if (!commit)
+ continue;
+
+ /* backend must have consumed any event by now */
+ WARN_ON(crtc->state->event);
+ spin_lock(&crtc->commit_lock);
+ complete_all(&commit->hw_done);
+ spin_unlock(&crtc->commit_lock);
+ }
+}
+EXPORT_SYMBOL(drm_atomic_helper_commit_hw_done);
+
+/**
+ * drm_atomic_helper_commit_cleanup_done - signal completion of commit
+ * @state: new modeset state to be committed
+ *
+ * This signals completion of the atomic update @state, including any cleanup
+ * work. If used, it must be called right before calling
+ * drm_atomic_state_free().
+ *
+ * This is part of the atomic helper support for nonblocking commits, see
+ * drm_atomic_helper_setup_commit() for an overview.
+ */
+void drm_atomic_helper_commit_cleanup_done(struct drm_atomic_state *state)
+{
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *crtc_state;
+ struct drm_crtc_commit *commit;
+ int i;
+ long ret;
+
+ for_each_crtc_in_state(state, crtc, crtc_state, i) {
+ commit = state->crtcs[i].commit;
+ if (WARN_ON(!commit))
+ continue;
+
+ spin_lock(&crtc->commit_lock);
+ complete_all(&commit->cleanup_done);
+ WARN_ON(!try_wait_for_completion(&commit->hw_done));
+
+ /* commit_list borrows our reference, need to remove before we
+ * clean up our drm_atomic_state. But only after it actually
+ * completed, otherwise subsequent commits won't stall properly. */
+ if (try_wait_for_completion(&commit->flip_done)) {
+ list_del(&commit->commit_entry);
+ spin_unlock(&crtc->commit_lock);
+ continue;
+ }
+
+ spin_unlock(&crtc->commit_lock);
+
+ /* We must wait for the vblank event to signal our completion
+ * before releasing our reference, since the vblank work does
+ * not hold a reference of its own. */
+ ret = wait_for_completion_timeout(&commit->flip_done,
+ 10*HZ);
+ if (ret == 0)
+ DRM_ERROR("[CRTC:%d:%s] flip_done timed out\n",
+ crtc->base.id, crtc->name);
+
+ spin_lock(&crtc->commit_lock);
+ list_del(&commit->commit_entry);
+ spin_unlock(&crtc->commit_lock);
+ }
+}
+EXPORT_SYMBOL(drm_atomic_helper_commit_cleanup_done);
+
/**
* drm_atomic_helper_prepare_planes - prepare plane resources before commit
* @dev: DRM device
@@ -1560,17 +1870,45 @@ EXPORT_SYMBOL(drm_atomic_helper_cleanup_planes);
*
* 5. Call drm_atomic_helper_cleanup_planes() with @state, which since step 3
* contains the old state. Also do any other cleanup required with that state.
+ *
+ * @stall must be set when nonblocking commits for this driver directly access
+ * the ->state pointer of &drm_plane, &drm_crtc or &drm_connector. With the
+ * current atomic helpers this is almost always the case, since the helpers
+ * don't pass the right state structures to the callbacks.
*/
void drm_atomic_helper_swap_state(struct drm_atomic_state *state,
bool stall)
{
int i;
+ long ret;
struct drm_connector *connector;
struct drm_connector_state *conn_state;
struct drm_crtc *crtc;
struct drm_crtc_state *crtc_state;
struct drm_plane *plane;
struct drm_plane_state *plane_state;
+ struct drm_crtc_commit *commit;
+
+ if (stall) {
+ for_each_crtc_in_state(state, crtc, crtc_state, i) {
+ spin_lock(&crtc->commit_lock);
+ commit = list_first_entry_or_null(&crtc->commit_list,
+ struct drm_crtc_commit, commit_entry);
+ if (commit)
+ drm_crtc_commit_get(commit);
+ spin_unlock(&crtc->commit_lock);
+
+ if (!commit)
+ continue;
+
+ ret = wait_for_completion_timeout(&commit->hw_done,
+ 10*HZ);
+ if (ret == 0)
+ DRM_ERROR("[CRTC:%d:%s] hw_done timed out\n",
+ crtc->base.id, crtc->name);
+ drm_crtc_commit_put(commit);
+ }
+ }
for_each_connector_in_state(state, connector, conn_state, i) {
connector->state->state = state;
@@ -1582,6 +1920,15 @@ void drm_atomic_helper_swap_state(struct drm_atomic_state *state,
crtc->state->state = state;
swap(state->crtcs[i].state, crtc->state);
crtc->state->state = NULL;
+
+ if (state->crtcs[i].commit) {
+ spin_lock(&crtc->commit_lock);
+ list_add(&state->crtcs[i].commit->commit_entry,
+ &crtc->commit_list);
+ spin_unlock(&crtc->commit_lock);
+
+ state->crtcs[i].commit->event = NULL;
+ }
}
for_each_plane_in_state(state, plane, plane_state, i) {
diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h
index 51c57eaa2c8f..d90b3a60db45 100644
--- a/include/drm/drm_atomic_helper.h
+++ b/include/drm/drm_atomic_helper.h
@@ -74,6 +74,13 @@ void drm_atomic_helper_disable_planes_on_crtc(struct drm_crtc *crtc,
void drm_atomic_helper_swap_state(struct drm_atomic_state *state,
bool stall);
+/* nonblocking commit helpers */
+int drm_atomic_helper_setup_commit(struct drm_atomic_state *state,
+ bool nonblock);
+void drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *state);
+void drm_atomic_helper_commit_hw_done(struct drm_atomic_state *state);
+void drm_atomic_helper_commit_cleanup_done(struct drm_atomic_state *state);
+
/* implementations for legacy interfaces */
int drm_atomic_helper_update_plane(struct drm_plane *plane,
struct drm_crtc *crtc,