These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / gpu / drm / drm_atomic.c
index 6e3b78e..aeee083 100644 (file)
 #include <drm/drm_atomic.h>
 #include <drm/drm_plane_helper.h>
 
-static void kfree_state(struct drm_atomic_state *state)
+/**
+ * drm_atomic_state_default_release -
+ * release memory initialized by drm_atomic_state_init
+ * @state: atomic state
+ *
+ * Free all the memory allocated by drm_atomic_state_init.
+ * This is useful for drivers that subclass the atomic state.
+ */
+void drm_atomic_state_default_release(struct drm_atomic_state *state)
 {
        kfree(state->connectors);
        kfree(state->connector_states);
@@ -38,24 +46,20 @@ static void kfree_state(struct drm_atomic_state *state)
        kfree(state->crtc_states);
        kfree(state->planes);
        kfree(state->plane_states);
-       kfree(state);
 }
+EXPORT_SYMBOL(drm_atomic_state_default_release);
 
 /**
- * drm_atomic_state_alloc - allocate atomic state
+ * drm_atomic_state_init - init new atomic state
  * @dev: DRM device
+ * @state: atomic state
  *
- * This allocates an empty atomic state to track updates.
+ * Default implementation for filling in a new atomic state.
+ * This is useful for drivers that subclass the atomic state.
  */
-struct drm_atomic_state *
-drm_atomic_state_alloc(struct drm_device *dev)
+int
+drm_atomic_state_init(struct drm_device *dev, struct drm_atomic_state *state)
 {
-       struct drm_atomic_state *state;
-
-       state = kzalloc(sizeof(*state), GFP_KERNEL);
-       if (!state)
-               return NULL;
-
        /* TODO legacy paths should maybe do a better job about
         * setting this appropriately?
         */
@@ -92,31 +96,50 @@ drm_atomic_state_alloc(struct drm_device *dev)
 
        state->dev = dev;
 
-       DRM_DEBUG_ATOMIC("Allocate atomic state %p\n", state);
+       DRM_DEBUG_ATOMIC("Allocated atomic state %p\n", state);
 
-       return state;
+       return 0;
 fail:
-       kfree_state(state);
+       drm_atomic_state_default_release(state);
+       return -ENOMEM;
+}
+EXPORT_SYMBOL(drm_atomic_state_init);
+
+/**
+ * drm_atomic_state_alloc - allocate atomic state
+ * @dev: DRM device
+ *
+ * This allocates an empty atomic state to track updates.
+ */
+struct drm_atomic_state *
+drm_atomic_state_alloc(struct drm_device *dev)
+{
+       struct drm_mode_config *config = &dev->mode_config;
+       struct drm_atomic_state *state;
+
+       if (!config->funcs->atomic_state_alloc) {
+               state = kzalloc(sizeof(*state), GFP_KERNEL);
+               if (!state)
+                       return NULL;
+               if (drm_atomic_state_init(dev, state) < 0) {
+                       kfree(state);
+                       return NULL;
+               }
+               return state;
+       }
 
-       return NULL;
+       return config->funcs->atomic_state_alloc(dev);
 }
 EXPORT_SYMBOL(drm_atomic_state_alloc);
 
 /**
- * drm_atomic_state_clear - clear state object
+ * drm_atomic_state_default_clear - clear base atomic state
  * @state: atomic state
  *
- * When the w/w mutex algorithm detects a deadlock we need to back off and drop
- * all locks. So someone else could sneak in and change the current modeset
- * configuration. Which means that all the state assembled in @state is no
- * longer an atomic update to the current state, but to some arbitrary earlier
- * state. Which could break assumptions the driver's ->atomic_check likely
- * relies on.
- *
- * Hence we must clear all cached state and completely start over, using this
- * function.
+ * Default implementation for clearing atomic state.
+ * This is useful for drivers that subclass the atomic state.
  */
-void drm_atomic_state_clear(struct drm_atomic_state *state)
+void drm_atomic_state_default_clear(struct drm_atomic_state *state)
 {
        struct drm_device *dev = state->dev;
        struct drm_mode_config *config = &dev->mode_config;
@@ -130,9 +153,15 @@ void drm_atomic_state_clear(struct drm_atomic_state *state)
                if (!connector)
                        continue;
 
-               WARN_ON(!drm_modeset_is_locked(&config->connection_mutex));
-
-               connector->funcs->atomic_destroy_state(connector,
+               /*
+                * FIXME: Async commits can race with connector unplugging and
+                * there's currently nothing that prevents cleanup up state for
+                * deleted connectors. As long as the callback doesn't look at
+                * the connector we'll be fine though, so make sure that's the
+                * case by setting all connector pointers to NULL.
+                */
+               state->connector_states[i]->connector = NULL;
+               connector->funcs->atomic_destroy_state(NULL,
                                                       state->connector_states[i]);
                state->connectors[i] = NULL;
                state->connector_states[i] = NULL;
@@ -162,6 +191,32 @@ void drm_atomic_state_clear(struct drm_atomic_state *state)
                state->plane_states[i] = NULL;
        }
 }
+EXPORT_SYMBOL(drm_atomic_state_default_clear);
+
+/**
+ * drm_atomic_state_clear - clear state object
+ * @state: atomic state
+ *
+ * When the w/w mutex algorithm detects a deadlock we need to back off and drop
+ * all locks. So someone else could sneak in and change the current modeset
+ * configuration. Which means that all the state assembled in @state is no
+ * longer an atomic update to the current state, but to some arbitrary earlier
+ * state. Which could break assumptions the driver's ->atomic_check likely
+ * relies on.
+ *
+ * Hence we must clear all cached state and completely start over, using this
+ * function.
+ */
+void drm_atomic_state_clear(struct drm_atomic_state *state)
+{
+       struct drm_device *dev = state->dev;
+       struct drm_mode_config *config = &dev->mode_config;
+
+       if (config->funcs->atomic_state_clear)
+               config->funcs->atomic_state_clear(state);
+       else
+               drm_atomic_state_default_clear(state);
+}
 EXPORT_SYMBOL(drm_atomic_state_clear);
 
 /**
@@ -173,14 +228,25 @@ EXPORT_SYMBOL(drm_atomic_state_clear);
  */
 void drm_atomic_state_free(struct drm_atomic_state *state)
 {
+       struct drm_device *dev;
+       struct drm_mode_config *config;
+
        if (!state)
                return;
 
+       dev = state->dev;
+       config = &dev->mode_config;
+
        drm_atomic_state_clear(state);
 
        DRM_DEBUG_ATOMIC("Freeing atomic state %p\n", state);
 
-       kfree_state(state);
+       if (config->funcs->atomic_state_free) {
+               config->funcs->atomic_state_free(state);
+       } else {
+               drm_atomic_state_default_release(state);
+               kfree(state);
+       }
 }
 EXPORT_SYMBOL(drm_atomic_state_free);
 
@@ -203,13 +269,12 @@ struct drm_crtc_state *
 drm_atomic_get_crtc_state(struct drm_atomic_state *state,
                          struct drm_crtc *crtc)
 {
-       int ret, index;
+       int ret, index = drm_crtc_index(crtc);
        struct drm_crtc_state *crtc_state;
 
-       index = drm_crtc_index(crtc);
-
-       if (state->crtc_states[index])
-               return state->crtc_states[index];
+       crtc_state = drm_atomic_get_existing_crtc_state(state, crtc);
+       if (crtc_state)
+               return crtc_state;
 
        ret = drm_modeset_lock(&crtc->mutex, state->acquire_ctx);
        if (ret)
@@ -230,6 +295,100 @@ drm_atomic_get_crtc_state(struct drm_atomic_state *state,
 }
 EXPORT_SYMBOL(drm_atomic_get_crtc_state);
 
+/**
+ * drm_atomic_set_mode_for_crtc - set mode for CRTC
+ * @state: the CRTC whose incoming state to update
+ * @mode: kernel-internal mode to use for the CRTC, or NULL to disable
+ *
+ * Set a mode (originating from the kernel) on the desired CRTC state. Does
+ * not change any other state properties, including enable, active, or
+ * mode_changed.
+ *
+ * RETURNS:
+ * Zero on success, error code on failure. Cannot return -EDEADLK.
+ */
+int drm_atomic_set_mode_for_crtc(struct drm_crtc_state *state,
+                                struct drm_display_mode *mode)
+{
+       struct drm_mode_modeinfo umode;
+
+       /* Early return for no change. */
+       if (mode && memcmp(&state->mode, mode, sizeof(*mode)) == 0)
+               return 0;
+
+       if (state->mode_blob)
+               drm_property_unreference_blob(state->mode_blob);
+       state->mode_blob = NULL;
+
+       if (mode) {
+               drm_mode_convert_to_umode(&umode, mode);
+               state->mode_blob =
+                       drm_property_create_blob(state->crtc->dev,
+                                                sizeof(umode),
+                                                &umode);
+               if (IS_ERR(state->mode_blob))
+                       return PTR_ERR(state->mode_blob);
+
+               drm_mode_copy(&state->mode, mode);
+               state->enable = true;
+               DRM_DEBUG_ATOMIC("Set [MODE:%s] for CRTC state %p\n",
+                                mode->name, state);
+       } else {
+               memset(&state->mode, 0, sizeof(state->mode));
+               state->enable = false;
+               DRM_DEBUG_ATOMIC("Set [NOMODE] for CRTC state %p\n",
+                                state);
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_atomic_set_mode_for_crtc);
+
+/**
+ * drm_atomic_set_mode_prop_for_crtc - set mode for CRTC
+ * @state: the CRTC whose incoming state to update
+ * @blob: pointer to blob property to use for mode
+ *
+ * Set a mode (originating from a blob property) on the desired CRTC state.
+ * This function will take a reference on the blob property for the CRTC state,
+ * and release the reference held on the state's existing mode property, if any
+ * was set.
+ *
+ * RETURNS:
+ * Zero on success, error code on failure. Cannot return -EDEADLK.
+ */
+int drm_atomic_set_mode_prop_for_crtc(struct drm_crtc_state *state,
+                                      struct drm_property_blob *blob)
+{
+       if (blob == state->mode_blob)
+               return 0;
+
+       if (state->mode_blob)
+               drm_property_unreference_blob(state->mode_blob);
+       state->mode_blob = NULL;
+
+       if (blob) {
+               if (blob->length != sizeof(struct drm_mode_modeinfo) ||
+                   drm_mode_convert_umode(&state->mode,
+                                          (const struct drm_mode_modeinfo *)
+                                           blob->data))
+                       return -EINVAL;
+
+               state->mode_blob = drm_property_reference_blob(blob);
+               state->enable = true;
+               DRM_DEBUG_ATOMIC("Set [MODE:%s] for CRTC state %p\n",
+                                state->mode.name, state);
+       } else {
+               memset(&state->mode, 0, sizeof(state->mode));
+               state->enable = false;
+               DRM_DEBUG_ATOMIC("Set [NOMODE] for CRTC state %p\n",
+                                state);
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_atomic_set_mode_prop_for_crtc);
+
 /**
  * drm_atomic_crtc_set_property - set property on CRTC
  * @crtc: the drm CRTC to set a property on
@@ -252,10 +411,18 @@ int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
 {
        struct drm_device *dev = crtc->dev;
        struct drm_mode_config *config = &dev->mode_config;
+       int ret;
 
-       /* FIXME: Mode prop is missing, which also controls ->enable. */
        if (property == config->prop_active)
                state->active = val;
+       else if (property == config->prop_mode_id) {
+               struct drm_property_blob *mode =
+                       drm_property_lookup_blob(dev, val);
+               ret = drm_atomic_set_mode_prop_for_crtc(state, mode);
+               if (mode)
+                       drm_property_unreference_blob(mode);
+               return ret;
+       }
        else if (crtc->funcs->atomic_set_property)
                return crtc->funcs->atomic_set_property(crtc, state, property, val);
        else
@@ -271,7 +438,8 @@ EXPORT_SYMBOL(drm_atomic_crtc_set_property);
  * consistent behavior you must call this function rather than the
  * driver hook directly.
  */
-int drm_atomic_crtc_get_property(struct drm_crtc *crtc,
+static int
+drm_atomic_crtc_get_property(struct drm_crtc *crtc,
                const struct drm_crtc_state *state,
                struct drm_property *property, uint64_t *val)
 {
@@ -280,6 +448,8 @@ int drm_atomic_crtc_get_property(struct drm_crtc *crtc,
 
        if (property == config->prop_active)
                *val = state->active;
+       else if (property == config->prop_mode_id)
+               *val = (state->mode_blob) ? state->mode_blob->base.id : 0;
        else if (crtc->funcs->atomic_get_property)
                return crtc->funcs->atomic_get_property(crtc, state, property, val);
        else
@@ -315,6 +485,23 @@ static int drm_atomic_crtc_check(struct drm_crtc *crtc,
                return -EINVAL;
        }
 
+       /* The state->enable vs. state->mode_blob checks can be WARN_ON,
+        * as this is a kernel-internal detail that userspace should never
+        * be able to trigger. */
+       if (drm_core_check_feature(crtc->dev, DRIVER_ATOMIC) &&
+           WARN_ON(state->enable && !state->mode_blob)) {
+               DRM_DEBUG_ATOMIC("[CRTC:%d] enabled without mode blob\n",
+                                crtc->base.id);
+               return -EINVAL;
+       }
+
+       if (drm_core_check_feature(crtc->dev, DRIVER_ATOMIC) &&
+           WARN_ON(!state->enable && state->mode_blob)) {
+               DRM_DEBUG_ATOMIC("[CRTC:%d] disabled with mode blob\n",
+                                crtc->base.id);
+               return -EINVAL;
+       }
+
        return 0;
 }
 
@@ -337,13 +524,12 @@ struct drm_plane_state *
 drm_atomic_get_plane_state(struct drm_atomic_state *state,
                          struct drm_plane *plane)
 {
-       int ret, index;
+       int ret, index = drm_plane_index(plane);
        struct drm_plane_state *plane_state;
 
-       index = drm_plane_index(plane);
-
-       if (state->plane_states[index])
-               return state->plane_states[index];
+       plane_state = drm_atomic_get_existing_plane_state(state, plane);
+       if (plane_state)
+               return plane_state;
 
        ret = drm_modeset_lock(&plane->mutex, state->acquire_ctx);
        if (ret)
@@ -478,6 +664,25 @@ drm_atomic_plane_get_property(struct drm_plane *plane,
        return 0;
 }
 
+static bool
+plane_switching_crtc(struct drm_atomic_state *state,
+                    struct drm_plane *plane,
+                    struct drm_plane_state *plane_state)
+{
+       if (!plane->state->crtc || !plane_state->crtc)
+               return false;
+
+       if (plane->state->crtc == plane_state->crtc)
+               return false;
+
+       /* This could be refined, but currently there's no helper or driver code
+        * to implement direct switching of active planes nor userspace to take
+        * advantage of more direct plane switching without the intermediate
+        * full OFF state.
+        */
+       return true;
+}
+
 /**
  * drm_atomic_plane_check - check plane state
  * @plane: plane to check
@@ -549,6 +754,12 @@ static int drm_atomic_plane_check(struct drm_plane *plane,
                return -ENOSPC;
        }
 
+       if (plane_switching_crtc(state->state, plane, state)) {
+               DRM_DEBUG_ATOMIC("[PLANE:%d] switching CRTC directly\n",
+                                plane->base.id);
+               return -EINVAL;
+       }
+
        return 0;
 }
 
@@ -884,7 +1095,7 @@ drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
         * Changed connectors are already in @state, so only need to look at the
         * current configuration.
         */
-       list_for_each_entry(connector, &config->connector_list, head) {
+       drm_for_each_connector(connector, state->dev) {
                if (connector->state->crtc != crtc)
                        continue;
 
@@ -897,6 +1108,45 @@ drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
 }
 EXPORT_SYMBOL(drm_atomic_add_affected_connectors);
 
+/**
+ * drm_atomic_add_affected_planes - add planes for crtc
+ * @state: atomic state
+ * @crtc: DRM crtc
+ *
+ * This function walks the current configuration and adds all planes
+ * currently used by @crtc to the atomic configuration @state. This is useful
+ * when an atomic commit also needs to check all currently enabled plane on
+ * @crtc, e.g. when changing the mode. It's also useful when re-enabling a CRTC
+ * to avoid special code to force-enable all planes.
+ *
+ * Since acquiring a plane state will always also acquire the w/w mutex of the
+ * current CRTC for that plane (if there is any) adding all the plane states for
+ * a CRTC will not reduce parallism of atomic updates.
+ *
+ * Returns:
+ * 0 on success or can fail with -EDEADLK or -ENOMEM. When the error is EDEADLK
+ * then the w/w mutex code has detected a deadlock and the entire atomic
+ * sequence must be restarted. All other errors are fatal.
+ */
+int
+drm_atomic_add_affected_planes(struct drm_atomic_state *state,
+                              struct drm_crtc *crtc)
+{
+       struct drm_plane *plane;
+
+       WARN_ON(!drm_atomic_get_existing_crtc_state(state, crtc));
+
+       drm_for_each_plane_mask(plane, state->dev, crtc->state->plane_mask) {
+               struct drm_plane_state *plane_state =
+                       drm_atomic_get_plane_state(state, plane);
+
+               if (IS_ERR(plane_state))
+                       return PTR_ERR(plane_state);
+       }
+       return 0;
+}
+EXPORT_SYMBOL(drm_atomic_add_affected_planes);
+
 /**
  * drm_atomic_connectors_for_crtc - count number of connected outputs
  * @state: atomic state
@@ -998,8 +1248,7 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
 
        if (!state->allow_modeset) {
                for_each_crtc_in_state(state, crtc, crtc_state, i) {
-                       if (crtc_state->mode_changed ||
-                           crtc_state->active_changed) {
+                       if (drm_atomic_crtc_needs_modeset(crtc_state)) {
                                DRM_DEBUG_ATOMIC("[CRTC:%d] requires full modeset\n",
                                                 crtc->base.id);
                                return -EINVAL;
@@ -1183,6 +1432,45 @@ static int atomic_set_prop(struct drm_atomic_state *state,
        return ret;
 }
 
+/**
+ * drm_atomic_update_old_fb -- Unset old_fb pointers and set plane->fb pointers.
+ *
+ * @dev: drm device to check.
+ * @plane_mask: plane mask for planes that were updated.
+ * @ret: return value, can be -EDEADLK for a retry.
+ *
+ * Before doing an update plane->old_fb is set to plane->fb,
+ * but before dropping the locks old_fb needs to be set to NULL
+ * and plane->fb updated. This is a common operation for each
+ * atomic update, so this call is split off as a helper.
+ */
+void drm_atomic_clean_old_fb(struct drm_device *dev,
+                            unsigned plane_mask,
+                            int ret)
+{
+       struct drm_plane *plane;
+
+       /* if succeeded, fixup legacy plane crtc/fb ptrs before dropping
+        * locks (ie. while it is still safe to deref plane->state).  We
+        * need to do this here because the driver entry points cannot
+        * distinguish between legacy and atomic ioctls.
+        */
+       drm_for_each_plane_mask(plane, dev, plane_mask) {
+               if (ret == 0) {
+                       struct drm_framebuffer *new_fb = plane->state->fb;
+                       if (new_fb)
+                               drm_framebuffer_reference(new_fb);
+                       plane->fb = new_fb;
+                       plane->crtc = plane->state->crtc;
+
+                       if (plane->old_fb)
+                               drm_framebuffer_unreference(plane->old_fb);
+               }
+               plane->old_fb = NULL;
+       }
+}
+EXPORT_SYMBOL(drm_atomic_clean_old_fb);
+
 int drm_mode_atomic_ioctl(struct drm_device *dev,
                          void *data, struct drm_file *file_priv)
 {
@@ -1197,7 +1485,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
        struct drm_plane *plane;
        struct drm_crtc *crtc;
        struct drm_crtc_state *crtc_state;
-       unsigned plane_mask = 0;
+       unsigned plane_mask;
        int ret = 0;
        unsigned int i, j;
 
@@ -1237,6 +1525,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
        state->allow_modeset = !!(arg->flags & DRM_MODE_ATOMIC_ALLOW_MODESET);
 
 retry:
+       plane_mask = 0;
        copied_objs = 0;
        copied_props = 0;
 
@@ -1246,24 +1535,18 @@ retry:
 
                if (get_user(obj_id, objs_ptr + copied_objs)) {
                        ret = -EFAULT;
-                       goto fail;
+                       goto out;
                }
 
                obj = drm_mode_object_find(dev, obj_id, DRM_MODE_OBJECT_ANY);
                if (!obj || !obj->properties) {
                        ret = -ENOENT;
-                       goto fail;
-               }
-
-               if (obj->type == DRM_MODE_OBJECT_PLANE) {
-                       plane = obj_to_plane(obj);
-                       plane_mask |= (1 << drm_plane_index(plane));
-                       plane->old_fb = plane->fb;
+                       goto out;
                }
 
                if (get_user(count_props, count_props_ptr + copied_objs)) {
                        ret = -EFAULT;
-                       goto fail;
+                       goto out;
                }
 
                copied_objs++;
@@ -1275,28 +1558,35 @@ retry:
 
                        if (get_user(prop_id, props_ptr + copied_props)) {
                                ret = -EFAULT;
-                               goto fail;
+                               goto out;
                        }
 
                        prop = drm_property_find(dev, prop_id);
                        if (!prop) {
                                ret = -ENOENT;
-                               goto fail;
+                               goto out;
                        }
 
                        if (copy_from_user(&prop_value,
                                           prop_values_ptr + copied_props,
                                           sizeof(prop_value))) {
                                ret = -EFAULT;
-                               goto fail;
+                               goto out;
                        }
 
                        ret = atomic_set_prop(state, obj, prop, prop_value);
                        if (ret)
-                               goto fail;
+                               goto out;
 
                        copied_props++;
                }
+
+               if (obj->type == DRM_MODE_OBJECT_PLANE && count_props &&
+                   !(arg->flags & DRM_MODE_ATOMIC_TEST_ONLY)) {
+                       plane = obj_to_plane(obj);
+                       plane_mask |= (1 << drm_plane_index(plane));
+                       plane->old_fb = plane->fb;
+               }
        }
 
        if (arg->flags & DRM_MODE_PAGE_FLIP_EVENT) {
@@ -1306,7 +1596,7 @@ retry:
                        e = create_vblank_event(dev, file_priv, arg->user_data);
                        if (!e) {
                                ret = -ENOMEM;
-                               goto fail;
+                               goto out;
                        }
 
                        crtc_state->event = e;
@@ -1314,62 +1604,47 @@ retry:
        }
 
        if (arg->flags & DRM_MODE_ATOMIC_TEST_ONLY) {
+               /*
+                * Unlike commit, check_only does not clean up state.
+                * Below we call drm_atomic_state_free for it.
+                */
                ret = drm_atomic_check_only(state);
-               /* _check_only() does not free state, unlike _commit() */
-               drm_atomic_state_free(state);
        } else if (arg->flags & DRM_MODE_ATOMIC_NONBLOCK) {
                ret = drm_atomic_async_commit(state);
        } else {
                ret = drm_atomic_commit(state);
        }
 
-       /* if succeeded, fixup legacy plane crtc/fb ptrs before dropping
-        * locks (ie. while it is still safe to deref plane->state).  We
-        * need to do this here because the driver entry points cannot
-        * distinguish between legacy and atomic ioctls.
-        */
-       drm_for_each_plane_mask(plane, dev, plane_mask) {
-               if (ret == 0) {
-                       struct drm_framebuffer *new_fb = plane->state->fb;
-                       if (new_fb)
-                               drm_framebuffer_reference(new_fb);
-                       plane->fb = new_fb;
-                       plane->crtc = plane->state->crtc;
-               } else {
-                       plane->old_fb = NULL;
-               }
-               if (plane->old_fb) {
-                       drm_framebuffer_unreference(plane->old_fb);
-                       plane->old_fb = NULL;
-               }
-       }
-
-       drm_modeset_drop_locks(&ctx);
-       drm_modeset_acquire_fini(&ctx);
-
-       return ret;
+out:
+       drm_atomic_clean_old_fb(dev, plane_mask, ret);
 
-fail:
-       if (ret == -EDEADLK)
-               goto backoff;
+       if (ret && arg->flags & DRM_MODE_PAGE_FLIP_EVENT) {
+               /*
+                * TEST_ONLY and PAGE_FLIP_EVENT are mutually exclusive,
+                * if they weren't, this code should be called on success
+                * for TEST_ONLY too.
+                */
 
-       if (arg->flags & DRM_MODE_PAGE_FLIP_EVENT) {
                for_each_crtc_in_state(state, crtc, crtc_state, i) {
-                       destroy_vblank_event(dev, file_priv, crtc_state->event);
-                       crtc_state->event = NULL;
+                       if (!crtc_state->event)
+                               continue;
+
+                       destroy_vblank_event(dev, file_priv,
+                                            crtc_state->event);
                }
        }
 
-       drm_atomic_state_free(state);
+       if (ret == -EDEADLK) {
+               drm_atomic_state_clear(state);
+               drm_modeset_backoff(&ctx);
+               goto retry;
+       }
+
+       if (ret || arg->flags & DRM_MODE_ATOMIC_TEST_ONLY)
+               drm_atomic_state_free(state);
 
        drm_modeset_drop_locks(&ctx);
        drm_modeset_acquire_fini(&ctx);
 
        return ret;
-
-backoff:
-       drm_atomic_state_clear(state);
-       drm_modeset_backoff(&ctx);
-
-       goto retry;
 }