Merge tag 'topic/drm-misc-2016-03-22' of git://anongit.freedesktop.org/drm-intel...
[linux-2.6-block.git] / drivers / gpu / drm / drm_atomic.c
index 3f74193885f1ff1df95db09fa76e01174f6353b4..8ee1db866e8004c95d9e7bbb038aff5f7daf2f84 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <drm/drmP.h>
 #include <drm/drm_atomic.h>
+#include <drm/drm_mode.h>
 #include <drm/drm_plane_helper.h>
 
 /**
@@ -65,8 +66,6 @@ drm_atomic_state_init(struct drm_device *dev, struct drm_atomic_state *state)
         */
        state->allow_modeset = true;
 
-       state->num_connector = ACCESS_ONCE(dev->mode_config.num_connector);
-
        state->crtcs = kcalloc(dev->mode_config.num_crtc,
                               sizeof(*state->crtcs), GFP_KERNEL);
        if (!state->crtcs)
@@ -83,16 +82,6 @@ drm_atomic_state_init(struct drm_device *dev, struct drm_atomic_state *state)
                                      sizeof(*state->plane_states), GFP_KERNEL);
        if (!state->plane_states)
                goto fail;
-       state->connectors = kcalloc(state->num_connector,
-                                   sizeof(*state->connectors),
-                                   GFP_KERNEL);
-       if (!state->connectors)
-               goto fail;
-       state->connector_states = kcalloc(state->num_connector,
-                                         sizeof(*state->connector_states),
-                                         GFP_KERNEL);
-       if (!state->connector_states)
-               goto fail;
 
        state->dev = dev;
 
@@ -387,6 +376,58 @@ int drm_atomic_set_mode_prop_for_crtc(struct drm_crtc_state *state,
 }
 EXPORT_SYMBOL(drm_atomic_set_mode_prop_for_crtc);
 
+/**
+ * drm_atomic_replace_property_blob - replace a blob property
+ * @blob: a pointer to the member blob to be replaced
+ * @new_blob: the new blob to replace with
+ * @replaced: whether the blob has been replaced
+ *
+ * RETURNS:
+ * Zero on success, error code on failure
+ */
+static void
+drm_atomic_replace_property_blob(struct drm_property_blob **blob,
+                                struct drm_property_blob *new_blob,
+                                bool *replaced)
+{
+       struct drm_property_blob *old_blob = *blob;
+
+       if (old_blob == new_blob)
+               return;
+
+       if (old_blob)
+               drm_property_unreference_blob(old_blob);
+       if (new_blob)
+               drm_property_reference_blob(new_blob);
+       *blob = new_blob;
+       *replaced = true;
+
+       return;
+}
+
+static int
+drm_atomic_replace_property_blob_from_id(struct drm_crtc *crtc,
+                                        struct drm_property_blob **blob,
+                                        uint64_t blob_id,
+                                        ssize_t expected_size,
+                                        bool *replaced)
+{
+       struct drm_device *dev = crtc->dev;
+       struct drm_property_blob *new_blob = NULL;
+
+       if (blob_id != 0) {
+               new_blob = drm_property_lookup_blob(dev, blob_id);
+               if (new_blob == NULL)
+                       return -EINVAL;
+               if (expected_size > 0 && expected_size != new_blob->length)
+                       return -EINVAL;
+       }
+
+       drm_atomic_replace_property_blob(blob, new_blob, replaced);
+
+       return 0;
+}
+
 /**
  * drm_atomic_crtc_set_property - set property on CRTC
  * @crtc: the drm CRTC to set a property on
@@ -409,6 +450,7 @@ int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
 {
        struct drm_device *dev = crtc->dev;
        struct drm_mode_config *config = &dev->mode_config;
+       bool replaced = false;
        int ret;
 
        if (property == config->prop_active)
@@ -419,8 +461,31 @@ int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
                ret = drm_atomic_set_mode_prop_for_crtc(state, mode);
                drm_property_unreference_blob(mode);
                return ret;
-       }
-       else if (crtc->funcs->atomic_set_property)
+       } else if (property == config->degamma_lut_property) {
+               ret = drm_atomic_replace_property_blob_from_id(crtc,
+                                       &state->degamma_lut,
+                                       val,
+                                       -1,
+                                       &replaced);
+               state->color_mgmt_changed = replaced;
+               return ret;
+       } else if (property == config->ctm_property) {
+               ret = drm_atomic_replace_property_blob_from_id(crtc,
+                                       &state->ctm,
+                                       val,
+                                       sizeof(struct drm_color_ctm),
+                                       &replaced);
+               state->color_mgmt_changed = replaced;
+               return ret;
+       } else if (property == config->gamma_lut_property) {
+               ret = drm_atomic_replace_property_blob_from_id(crtc,
+                                       &state->gamma_lut,
+                                       val,
+                                       -1,
+                                       &replaced);
+               state->color_mgmt_changed = replaced;
+               return ret;
+       } else if (crtc->funcs->atomic_set_property)
                return crtc->funcs->atomic_set_property(crtc, state, property, val);
        else
                return -EINVAL;
@@ -456,6 +521,12 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc,
                *val = state->active;
        else if (property == config->prop_mode_id)
                *val = (state->mode_blob) ? state->mode_blob->base.id : 0;
+       else if (property == config->degamma_lut_property)
+               *val = (state->degamma_lut) ? state->degamma_lut->base.id : 0;
+       else if (property == config->ctm_property)
+               *val = (state->ctm) ? state->ctm->base.id : 0;
+       else if (property == config->gamma_lut_property)
+               *val = (state->gamma_lut) ? state->gamma_lut->base.id : 0;
        else if (crtc->funcs->atomic_get_property)
                return crtc->funcs->atomic_get_property(crtc, state, property, val);
        else
@@ -823,19 +894,27 @@ drm_atomic_get_connector_state(struct drm_atomic_state *state,
 
        index = drm_connector_index(connector);
 
-       /*
-        * Construction of atomic state updates can race with a connector
-        * hot-add which might overflow. In this case flip the table and just
-        * restart the entire ioctl - no one is fast enough to livelock a cpu
-        * with physical hotplug events anyway.
-        *
-        * Note that we only grab the indexes once we have the right lock to
-        * prevent hotplug/unplugging of connectors. So removal is no problem,
-        * at most the array is a bit too large.
-        */
        if (index >= state->num_connector) {
-               DRM_DEBUG_ATOMIC("Hot-added connector would overflow state array, restarting\n");
-               return ERR_PTR(-EAGAIN);
+               struct drm_connector **c;
+               struct drm_connector_state **cs;
+               int alloc = max(index + 1, config->num_connector);
+
+               c = krealloc(state->connectors, alloc * sizeof(*state->connectors), GFP_KERNEL);
+               if (!c)
+                       return ERR_PTR(-ENOMEM);
+
+               state->connectors = c;
+               memset(&state->connectors[state->num_connector], 0,
+                      sizeof(*state->connectors) * (alloc - state->num_connector));
+
+               cs = krealloc(state->connector_states, alloc * sizeof(*state->connector_states), GFP_KERNEL);
+               if (!cs)
+                       return ERR_PTR(-ENOMEM);
+
+               state->connector_states = cs;
+               memset(&state->connector_states[state->num_connector], 0,
+                      sizeof(*state->connector_states) * (alloc - state->num_connector));
+               state->num_connector = alloc;
        }
 
        if (state->connector_states[index])
@@ -1347,44 +1426,23 @@ static struct drm_pending_vblank_event *create_vblank_event(
                struct drm_device *dev, struct drm_file *file_priv, uint64_t user_data)
 {
        struct drm_pending_vblank_event *e = NULL;
-       unsigned long flags;
-
-       spin_lock_irqsave(&dev->event_lock, flags);
-       if (file_priv->event_space < sizeof e->event) {
-               spin_unlock_irqrestore(&dev->event_lock, flags);
-               goto out;
-       }
-       file_priv->event_space -= sizeof e->event;
-       spin_unlock_irqrestore(&dev->event_lock, flags);
+       int ret;
 
        e = kzalloc(sizeof *e, GFP_KERNEL);
-       if (e == NULL) {
-               spin_lock_irqsave(&dev->event_lock, flags);
-               file_priv->event_space += sizeof e->event;
-               spin_unlock_irqrestore(&dev->event_lock, flags);
-               goto out;
-       }
+       if (!e)
+               return NULL;
 
        e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
-       e->event.base.length = sizeof e->event;
+       e->event.base.length = sizeof(e->event);
        e->event.user_data = user_data;
-       e->base.event = &e->event.base;
-       e->base.file_priv = file_priv;
-       e->base.destroy = (void (*) (struct drm_pending_event *)) kfree;
 
-out:
-       return e;
-}
-
-static void destroy_vblank_event(struct drm_device *dev,
-               struct drm_file *file_priv, struct drm_pending_vblank_event *e)
-{
-       unsigned long flags;
+       ret = drm_event_reserve_init(dev, file_priv, &e->base, &e->event.base);
+       if (ret) {
+               kfree(e);
+               return NULL;
+       }
 
-       spin_lock_irqsave(&dev->event_lock, flags);
-       file_priv->event_space += sizeof e->event;
-       spin_unlock_irqrestore(&dev->event_lock, flags);
-       kfree(e);
+       return e;
 }
 
 static int atomic_set_prop(struct drm_atomic_state *state,
@@ -1646,8 +1704,7 @@ out:
                        if (!crtc_state->event)
                                continue;
 
-                       destroy_vblank_event(dev, file_priv,
-                                            crtc_state->event);
+                       drm_event_cancel_free(dev, &crtc_state->event->base);
                }
        }