]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/gpu/drm/drm_crtc.c
Merge branch 'x86-efi-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[karo-tx-linux.git] / drivers / gpu / drm / drm_crtc.c
index 3eddfabeba9622ae608a86ff534de049c5b638ec..3bdf2a650d9c4c4ec8f65d1c8f0e1ebd3bcc507e 100644 (file)
@@ -68,9 +68,23 @@ void drm_modeset_unlock_all(struct drm_device *dev)
 
        mutex_unlock(&dev->mode_config.mutex);
 }
-
 EXPORT_SYMBOL(drm_modeset_unlock_all);
 
+/**
+ * drm_warn_on_modeset_not_all_locked - check that all modeset locks are locked
+ * @dev: device
+ */
+void drm_warn_on_modeset_not_all_locked(struct drm_device *dev)
+{
+       struct drm_crtc *crtc;
+
+       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
+               WARN_ON(!mutex_is_locked(&crtc->mutex));
+
+       WARN_ON(!mutex_is_locked(&dev->mode_config.mutex));
+}
+EXPORT_SYMBOL(drm_warn_on_modeset_not_all_locked);
+
 /* Avoid boilerplate.  I'm tired of typing. */
 #define DRM_ENUM_NAME_FN(fnname, list)                         \
        char *fnname(int val)                                   \
@@ -356,6 +370,9 @@ int drm_framebuffer_init(struct drm_device *dev, struct drm_framebuffer *fb,
        if (ret)
                goto out;
 
+       /* Grab the idr reference. */
+       drm_framebuffer_reference(fb);
+
        dev->mode_config.num_fb++;
        list_add(&fb->head, &dev->mode_config.fb_list);
 out:
@@ -372,6 +389,23 @@ static void drm_framebuffer_free(struct kref *kref)
        fb->funcs->destroy(fb);
 }
 
+static struct drm_framebuffer *__drm_framebuffer_lookup(struct drm_device *dev,
+                                                       uint32_t id)
+{
+       struct drm_mode_object *obj = NULL;
+       struct drm_framebuffer *fb;
+
+       mutex_lock(&dev->mode_config.idr_mutex);
+       obj = idr_find(&dev->mode_config.crtc_idr, id);
+       if (!obj || (obj->type != DRM_MODE_OBJECT_FB) || (obj->id != id))
+               fb = NULL;
+       else
+               fb = obj_to_fb(obj);
+       mutex_unlock(&dev->mode_config.idr_mutex);
+
+       return fb;
+}
+
 /**
  * drm_framebuffer_lookup - look up a drm framebuffer and grab a reference
  * @dev: drm device
@@ -384,22 +418,12 @@ static void drm_framebuffer_free(struct kref *kref)
 struct drm_framebuffer *drm_framebuffer_lookup(struct drm_device *dev,
                                               uint32_t id)
 {
-       struct drm_mode_object *obj = NULL;
        struct drm_framebuffer *fb;
 
        mutex_lock(&dev->mode_config.fb_lock);
-
-       mutex_lock(&dev->mode_config.idr_mutex);
-       obj = idr_find(&dev->mode_config.crtc_idr, id);
-       if (!obj || (obj->type != DRM_MODE_OBJECT_FB) || (obj->id != id))
-               fb = NULL;
-       else
-               fb = obj_to_fb(obj);
-       mutex_unlock(&dev->mode_config.idr_mutex);
-
+       fb = __drm_framebuffer_lookup(dev, id);
        if (fb)
                kref_get(&fb->refcount);
-
        mutex_unlock(&dev->mode_config.fb_lock);
 
        return fb;
@@ -430,6 +454,30 @@ void drm_framebuffer_reference(struct drm_framebuffer *fb)
 }
 EXPORT_SYMBOL(drm_framebuffer_reference);
 
+static void drm_framebuffer_free_bug(struct kref *kref)
+{
+       BUG();
+}
+
+static void __drm_framebuffer_unreference(struct drm_framebuffer *fb)
+{
+       DRM_DEBUG("FB ID: %d\n", fb->base.id);
+       kref_put(&fb->refcount, drm_framebuffer_free_bug);
+}
+
+/* dev->mode_config.fb_lock must be held! */
+static void __drm_framebuffer_unregister(struct drm_device *dev,
+                                        struct drm_framebuffer *fb)
+{
+       mutex_lock(&dev->mode_config.idr_mutex);
+       idr_remove(&dev->mode_config.crtc_idr, fb->base.id);
+       mutex_unlock(&dev->mode_config.idr_mutex);
+
+       fb->base.id = 0;
+
+       __drm_framebuffer_unreference(fb);
+}
+
 /**
  * drm_framebuffer_unregister_private - unregister a private fb from the lookup idr
  * @fb: fb to unregister
@@ -441,6 +489,12 @@ EXPORT_SYMBOL(drm_framebuffer_reference);
  */
 void drm_framebuffer_unregister_private(struct drm_framebuffer *fb)
 {
+       struct drm_device *dev = fb->dev;
+
+       mutex_lock(&dev->mode_config.fb_lock);
+       /* Mark fb as reaped and drop idr ref. */
+       __drm_framebuffer_unregister(dev, fb);
+       mutex_unlock(&dev->mode_config.fb_lock);
 }
 EXPORT_SYMBOL(drm_framebuffer_unregister_private);
 
@@ -464,14 +518,6 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb)
 {
        struct drm_device *dev = fb->dev;
 
-       /*
-        * This could be moved to drm_framebuffer_remove(), but for
-        * debugging is nice to keep around the list of fb's that are
-        * no longer associated w/ a drm_file but are not unreferenced
-        * yet.  (i915 and omapdrm have debugfs files which will show
-        * this.)
-        */
-       drm_mode_object_put(dev, &fb->base);
        mutex_lock(&dev->mode_config.fb_lock);
        list_del(&fb->head);
        dev->mode_config.num_fb--;
@@ -485,7 +531,11 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup);
  *
  * Scans all the CRTCs and planes in @dev's mode_config.  If they're
  * using @fb, removes it, setting it to NULL. Then drops the reference to the
- * passed-in framebuffer.
+ * passed-in framebuffer. Might take the modeset locks.
+ *
+ * Note that this function optimizes the cleanup away if the caller holds the
+ * last reference to the framebuffer. It is also guaranteed to not take the
+ * modeset locks in this case.
  */
 void drm_framebuffer_remove(struct drm_framebuffer *fb)
 {
@@ -495,32 +545,51 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb)
        struct drm_mode_set set;
        int ret;
 
-       WARN_ON(!drm_modeset_is_locked(dev));
        WARN_ON(!list_empty(&fb->filp_head));
 
-       /* remove from any CRTC */
-       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
-               if (crtc->fb == fb) {
-                       /* should turn off the crtc */
-                       memset(&set, 0, sizeof(struct drm_mode_set));
-                       set.crtc = crtc;
-                       set.fb = NULL;
-                       ret = drm_mode_set_config_internal(&set);
-                       if (ret)
-                               DRM_ERROR("failed to reset crtc %p when fb was deleted\n", crtc);
+       /*
+        * drm ABI mandates that we remove any deleted framebuffers from active
+        * useage. But since most sane clients only remove framebuffers they no
+        * longer need, try to optimize this away.
+        *
+        * Since we're holding a reference ourselves, observing a refcount of 1
+        * means that we're the last holder and can skip it. Also, the refcount
+        * can never increase from 1 again, so we don't need any barriers or
+        * locks.
+        *
+        * Note that userspace could try to race with use and instate a new
+        * usage _after_ we've cleared all current ones. End result will be an
+        * in-use fb with fb-id == 0. Userspace is allowed to shoot its own foot
+        * in this manner.
+        */
+       if (atomic_read(&fb->refcount.refcount) > 1) {
+               drm_modeset_lock_all(dev);
+               /* remove from any CRTC */
+               list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+                       if (crtc->fb == fb) {
+                               /* should turn off the crtc */
+                               memset(&set, 0, sizeof(struct drm_mode_set));
+                               set.crtc = crtc;
+                               set.fb = NULL;
+                               ret = drm_mode_set_config_internal(&set);
+                               if (ret)
+                                       DRM_ERROR("failed to reset crtc %p when fb was deleted\n", crtc);
+                       }
                }
-       }
 
-       list_for_each_entry(plane, &dev->mode_config.plane_list, head) {
-               if (plane->fb == fb) {
-                       /* should turn off the crtc */
-                       ret = plane->funcs->disable_plane(plane);
-                       if (ret)
-                               DRM_ERROR("failed to disable plane with busy fb\n");
-                       /* disconnect the plane from the fb and crtc: */
-                       plane->fb = NULL;
-                       plane->crtc = NULL;
+               list_for_each_entry(plane, &dev->mode_config.plane_list, head) {
+                       if (plane->fb == fb) {
+                               /* should turn off the crtc */
+                               ret = plane->funcs->disable_plane(plane);
+                               if (ret)
+                                       DRM_ERROR("failed to disable plane with busy fb\n");
+                               /* disconnect the plane from the fb and crtc: */
+                               __drm_framebuffer_unreference(plane->fb);
+                               plane->fb = NULL;
+                               plane->crtc = NULL;
+                       }
                }
+               drm_modeset_unlock_all(dev);
        }
 
        drm_framebuffer_unreference(fb);
@@ -1181,9 +1250,15 @@ void drm_mode_config_cleanup(struct drm_device *dev)
                drm_property_destroy(dev, property);
        }
 
-       /* Single-threaded teardown context, so it's not requied to grab the
+       /*
+        * Single-threaded teardown context, so it's not required to grab the
         * fb_lock to protect against concurrent fb_list access. Contrary, it
-        * would actually deadlock with the drm_framebuffer_cleanup function. */
+        * would actually deadlock with the drm_framebuffer_cleanup function.
+        *
+        * Also, if there are any framebuffers left, that's a driver leak now,
+        * so politely WARN about this.
+        */
+       WARN_ON(!list_empty(&dev->mode_config.fb_list));
        list_for_each_entry_safe(fb, fbt, &dev->mode_config.fb_list, head) {
                drm_framebuffer_remove(fb);
        }
@@ -1556,7 +1631,7 @@ int drm_mode_getconnector(struct drm_device *dev, void *data,
 
        DRM_DEBUG_KMS("[CONNECTOR:%d:?]\n", out_resp->connector_id);
 
-       drm_modeset_lock_all(dev);
+       mutex_lock(&dev->mode_config.mutex);
 
        obj = drm_mode_object_find(dev, out_resp->connector_id,
                                   DRM_MODE_OBJECT_CONNECTOR);
@@ -1653,7 +1728,8 @@ int drm_mode_getconnector(struct drm_device *dev, void *data,
        out_resp->count_encoders = encoders_count;
 
 out:
-       drm_modeset_unlock_all(dev);
+       mutex_unlock(&dev->mode_config.mutex);
+
        return ret;
 }
 
@@ -1818,7 +1894,7 @@ int drm_mode_setplane(struct drm_device *dev, void *data,
        struct drm_mode_object *obj;
        struct drm_plane *plane;
        struct drm_crtc *crtc;
-       struct drm_framebuffer *fb;
+       struct drm_framebuffer *fb = NULL, *old_fb = NULL;
        int ret = 0;
        unsigned int fb_width, fb_height;
        int i;
@@ -1826,8 +1902,6 @@ int drm_mode_setplane(struct drm_device *dev, void *data,
        if (!drm_core_check_feature(dev, DRIVER_MODESET))
                return -EINVAL;
 
-       drm_modeset_lock_all(dev);
-
        /*
         * First, find the plane, crtc, and fb objects.  If not available,
         * we don't bother to call the driver.
@@ -1837,16 +1911,18 @@ int drm_mode_setplane(struct drm_device *dev, void *data,
        if (!obj) {
                DRM_DEBUG_KMS("Unknown plane ID %d\n",
                              plane_req->plane_id);
-               ret = -ENOENT;
-               goto out;
+               return -ENOENT;
        }
        plane = obj_to_plane(obj);
 
        /* No fb means shut it down */
        if (!plane_req->fb_id) {
+               drm_modeset_lock_all(dev);
+               old_fb = plane->fb;
                plane->funcs->disable_plane(plane);
                plane->crtc = NULL;
                plane->fb = NULL;
+               drm_modeset_unlock_all(dev);
                goto out;
        }
 
@@ -1867,8 +1943,6 @@ int drm_mode_setplane(struct drm_device *dev, void *data,
                ret = -ENOENT;
                goto out;
        }
-       /* fb is protect by the mode_config lock, so drop the ref immediately */
-       drm_framebuffer_unreference(fb);
 
        /* Check whether this plane supports the fb pixel format. */
        for (i = 0; i < plane->format_count; i++)
@@ -1914,18 +1988,25 @@ int drm_mode_setplane(struct drm_device *dev, void *data,
                goto out;
        }
 
+       drm_modeset_lock_all(dev);
        ret = plane->funcs->update_plane(plane, crtc, fb,
                                         plane_req->crtc_x, plane_req->crtc_y,
                                         plane_req->crtc_w, plane_req->crtc_h,
                                         plane_req->src_x, plane_req->src_y,
                                         plane_req->src_w, plane_req->src_h);
        if (!ret) {
+               old_fb = plane->fb;
                plane->crtc = crtc;
                plane->fb = fb;
+               fb = NULL;
        }
+       drm_modeset_unlock_all(dev);
 
 out:
-       drm_modeset_unlock_all(dev);
+       if (fb)
+               drm_framebuffer_unreference(fb);
+       if (old_fb)
+               drm_framebuffer_unreference(old_fb);
 
        return ret;
 }
@@ -1940,8 +2021,21 @@ out:
 int drm_mode_set_config_internal(struct drm_mode_set *set)
 {
        struct drm_crtc *crtc = set->crtc;
+       struct drm_framebuffer *fb, *old_fb;
+       int ret;
 
-       return crtc->funcs->set_config(set);
+       old_fb = crtc->fb;
+       fb = set->fb;
+
+       ret = crtc->funcs->set_config(set);
+       if (ret == 0) {
+               if (old_fb)
+                       drm_framebuffer_unreference(old_fb);
+               if (fb)
+                       drm_framebuffer_reference(fb);
+       }
+
+       return ret;
 }
 EXPORT_SYMBOL(drm_mode_set_config_internal);
 
@@ -2002,6 +2096,8 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
                                goto out;
                        }
                        fb = crtc->fb;
+                       /* Make refcounting symmetric with the lookup path. */
+                       drm_framebuffer_reference(fb);
                } else {
                        fb = drm_framebuffer_lookup(dev, crtc_req->fb_id);
                        if (!fb) {
@@ -2010,9 +2106,6 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
                                ret = -EINVAL;
                                goto out;
                        }
-                       /* fb is protect by the mode_config lock, so drop the
-                        * ref immediately */
-                       drm_framebuffer_unreference(fb);
                }
 
                mode = drm_mode_create(dev);
@@ -2112,6 +2205,9 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
        ret = drm_mode_set_config_internal(&set);
 
 out:
+       if (fb)
+               drm_framebuffer_unreference(fb);
+
        kfree(connector_set);
        drm_mode_destroy(dev, mode);
        drm_modeset_unlock_all(dev);
@@ -2171,7 +2267,7 @@ uint32_t drm_mode_legacy_fb_format(uint32_t bpp, uint32_t depth)
 
        switch (bpp) {
        case 8:
-               fmt = DRM_FORMAT_RGB332;
+               fmt = DRM_FORMAT_C8;
                break;
        case 16:
                if (depth == 15)
@@ -2239,11 +2335,6 @@ int drm_mode_addfb(struct drm_device *dev,
        if ((config->min_height > r.height) || (r.height > config->max_height))
                return -EINVAL;
 
-       drm_modeset_lock_all(dev);
-
-       /* TODO check buffer is sufficiently large */
-       /* TODO setup destructor callback */
-
        fb = dev->mode_config.funcs->fb_create(dev, file_priv, &r);
        if (IS_ERR(fb)) {
                DRM_DEBUG_KMS("could not create framebuffer\n");
@@ -2256,7 +2347,6 @@ int drm_mode_addfb(struct drm_device *dev,
        list_add(&fb->filp_head, &file_priv->fbs);
        DRM_DEBUG_KMS("[FB:%d]\n", fb->base.id);
        mutex_unlock(&file_priv->fbs_lock);
-       drm_modeset_unlock_all(dev);
 
        return ret;
 }
@@ -2425,8 +2515,6 @@ int drm_mode_addfb2(struct drm_device *dev,
        if (ret)
                return ret;
 
-       drm_modeset_lock_all(dev);
-
        fb = dev->mode_config.funcs->fb_create(dev, file_priv, r);
        if (IS_ERR(fb)) {
                DRM_DEBUG_KMS("could not create framebuffer\n");
@@ -2440,7 +2528,6 @@ int drm_mode_addfb2(struct drm_device *dev,
        DRM_DEBUG_KMS("[FB:%d]\n", fb->base.id);
        mutex_unlock(&file_priv->fbs_lock);
 
-       drm_modeset_unlock_all(dev);
 
        return ret;
 }
@@ -2464,39 +2551,39 @@ int drm_mode_rmfb(struct drm_device *dev,
        struct drm_framebuffer *fb = NULL;
        struct drm_framebuffer *fbl = NULL;
        uint32_t *id = data;
-       int ret = 0;
        int found = 0;
 
        if (!drm_core_check_feature(dev, DRIVER_MODESET))
                return -EINVAL;
 
-       drm_modeset_lock_all(dev);
-       fb = drm_framebuffer_lookup(dev, *id);
-       if (!fb) {
-               ret = -EINVAL;
-               goto out;
-       }
-       /* fb is protect by the mode_config lock, so drop the ref immediately */
-       drm_framebuffer_unreference(fb);
-
        mutex_lock(&file_priv->fbs_lock);
+       mutex_lock(&dev->mode_config.fb_lock);
+       fb = __drm_framebuffer_lookup(dev, *id);
+       if (!fb)
+               goto fail_lookup;
+
        list_for_each_entry(fbl, &file_priv->fbs, filp_head)
                if (fb == fbl)
                        found = 1;
-       if (!found) {
-               ret = -EINVAL;
-               mutex_unlock(&file_priv->fbs_lock);
-               goto out;
-       }
+       if (!found)
+               goto fail_lookup;
+
+       /* Mark fb as reaped, we still have a ref from fpriv->fbs. */
+       __drm_framebuffer_unregister(dev, fb);
 
        list_del_init(&fb->filp_head);
+       mutex_unlock(&dev->mode_config.fb_lock);
        mutex_unlock(&file_priv->fbs_lock);
 
        drm_framebuffer_remove(fb);
-out:
-       drm_modeset_unlock_all(dev);
 
-       return ret;
+       return 0;
+
+fail_lookup:
+       mutex_unlock(&dev->mode_config.fb_lock);
+       mutex_unlock(&file_priv->fbs_lock);
+
+       return -EINVAL;
 }
 
 /**
@@ -2517,19 +2604,14 @@ int drm_mode_getfb(struct drm_device *dev,
 {
        struct drm_mode_fb_cmd *r = data;
        struct drm_framebuffer *fb;
-       int ret = 0;
+       int ret;
 
        if (!drm_core_check_feature(dev, DRIVER_MODESET))
                return -EINVAL;
 
-       drm_modeset_lock_all(dev);
        fb = drm_framebuffer_lookup(dev, r->fb_id);
-       if (!fb) {
-               ret = -EINVAL;
-               goto out;
-       }
-       /* fb is protect by the mode_config lock, so drop the ref immediately */
-       drm_framebuffer_unreference(fb);
+       if (!fb)
+               return -EINVAL;
 
        r->height = fb->height;
        r->width = fb->width;
@@ -2541,8 +2623,8 @@ int drm_mode_getfb(struct drm_device *dev,
        else
                ret = -ENODEV;
 
-out:
-       drm_modeset_unlock_all(dev);
+       drm_framebuffer_unreference(fb);
+
        return ret;
 }
 
@@ -2560,14 +2642,9 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
        if (!drm_core_check_feature(dev, DRIVER_MODESET))
                return -EINVAL;
 
-       drm_modeset_lock_all(dev);
        fb = drm_framebuffer_lookup(dev, r->fb_id);
-       if (!fb) {
-               ret = -EINVAL;
-               goto out_err1;
-       }
-       /* fb is protect by the mode_config lock, so drop the ref immediately */
-       drm_framebuffer_unreference(fb);
+       if (!fb)
+               return -EINVAL;
 
        num_clips = r->num_clips;
        clips_ptr = (struct drm_clip_rect __user *)(unsigned long)r->clips_ptr;
@@ -2605,17 +2682,19 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
        }
 
        if (fb->funcs->dirty) {
+               drm_modeset_lock_all(dev);
                ret = fb->funcs->dirty(fb, file_priv, flags, r->color,
                                       clips, num_clips);
+               drm_modeset_unlock_all(dev);
        } else {
                ret = -ENOSYS;
-               goto out_err2;
        }
 
 out_err2:
        kfree(clips);
 out_err1:
-       drm_modeset_unlock_all(dev);
+       drm_framebuffer_unreference(fb);
+
        return ret;
 }
 
@@ -2636,14 +2715,20 @@ void drm_fb_release(struct drm_file *priv)
        struct drm_device *dev = priv->minor->dev;
        struct drm_framebuffer *fb, *tfb;
 
-       drm_modeset_lock_all(dev);
        mutex_lock(&priv->fbs_lock);
        list_for_each_entry_safe(fb, tfb, &priv->fbs, filp_head) {
+
+               mutex_lock(&dev->mode_config.fb_lock);
+               /* Mark fb as reaped, we still have a ref from fpriv->fbs. */
+               __drm_framebuffer_unregister(dev, fb);
+               mutex_unlock(&dev->mode_config.fb_lock);
+
                list_del_init(&fb->filp_head);
+
+               /* This will also drop the fpriv->fbs reference. */
                drm_framebuffer_remove(fb);
        }
        mutex_unlock(&priv->fbs_lock);
-       drm_modeset_unlock_all(dev);
 }
 
 /**
@@ -3619,7 +3704,7 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
        struct drm_mode_crtc_page_flip *page_flip = data;
        struct drm_mode_object *obj;
        struct drm_crtc *crtc;
-       struct drm_framebuffer *fb;
+       struct drm_framebuffer *fb = NULL, *old_fb = NULL;
        struct drm_pending_vblank_event *e = NULL;
        unsigned long flags;
        int hdisplay, vdisplay;
@@ -3629,12 +3714,12 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
            page_flip->reserved != 0)
                return -EINVAL;
 
-       drm_modeset_lock_all(dev);
        obj = drm_mode_object_find(dev, page_flip->crtc_id, DRM_MODE_OBJECT_CRTC);
        if (!obj)
-               goto out;
+               return -EINVAL;
        crtc = obj_to_crtc(obj);
 
+       mutex_lock(&crtc->mutex);
        if (crtc->fb == NULL) {
                /* The framebuffer is currently unbound, presumably
                 * due to a hotplug event, that userspace has not
@@ -3650,8 +3735,6 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
        fb = drm_framebuffer_lookup(dev, page_flip->fb_id);
        if (!fb)
                goto out;
-       /* fb is protect by the mode_config lock, so drop the ref immediately */
-       drm_framebuffer_unreference(fb);
 
        hdisplay = crtc->mode.hdisplay;
        vdisplay = crtc->mode.vdisplay;
@@ -3697,6 +3780,7 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
                        (void (*) (struct drm_pending_event *)) kfree;
        }
 
+       old_fb = crtc->fb;
        ret = crtc->funcs->page_flip(crtc, fb, e);
        if (ret) {
                if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
@@ -3705,10 +3789,27 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
                        spin_unlock_irqrestore(&dev->event_lock, flags);
                        kfree(e);
                }
+               /* Keep the old fb, don't unref it. */
+               old_fb = NULL;
+       } else {
+               /*
+                * Warn if the driver hasn't properly updated the crtc->fb
+                * field to reflect that the new framebuffer is now used.
+                * Failing to do so will screw with the reference counting
+                * on framebuffers.
+                */
+               WARN_ON(crtc->fb != fb);
+               /* Unref only the old framebuffer. */
+               fb = NULL;
        }
 
 out:
-       drm_modeset_unlock_all(dev);
+       if (fb)
+               drm_framebuffer_unreference(fb);
+       if (old_fb)
+               drm_framebuffer_unreference(old_fb);
+       mutex_unlock(&crtc->mutex);
+
        return ret;
 }
 
@@ -3776,6 +3877,7 @@ void drm_fb_get_bpp_depth(uint32_t format, unsigned int *depth,
                          int *bpp)
 {
        switch (format) {
+       case DRM_FORMAT_C8:
        case DRM_FORMAT_RGB332:
        case DRM_FORMAT_BGR233:
                *depth = 8;