]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/gpu/drm/drm_fb_helper.c
Merge branch 'locking-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[karo-tx-linux.git] / drivers / gpu / drm / drm_fb_helper.c
index eaf652b389d93b6936f0539c2280922acc8ec15e..418d299f3b129b307f86a970fdf49d3a826af4c8 100644 (file)
@@ -56,8 +56,8 @@ static LIST_HEAD(kernel_fb_helper_list);
  * Teardown is done with drm_fb_helper_fini().
  *
  * At runtime drivers should restore the fbdev console by calling
- * drm_fb_helper_restore_fbdev_mode() from their ->lastclose callback. They
- * should also notify the fb helper code from updates to the output
+ * drm_fb_helper_restore_fbdev_mode_unlocked() from their ->lastclose callback.
+ * They should also notify the fb helper code from updates to the output
  * configuration by calling drm_fb_helper_hotplug_event(). For easier
  * integration with the output polling code in drm_crtc_helper.c the modeset
  * code provides a ->output_poll_changed callback.
@@ -89,8 +89,9 @@ static LIST_HEAD(kernel_fb_helper_list);
  * connectors to the fbdev, e.g. if some are reserved for special purposes or
  * not adequate to be used for the fbcon.
  *
- * Since this is part of the initial setup before the fbdev is published, no
- * locking is required.
+ * This function is protected against concurrent connector hotadds/removals
+ * using drm_fb_helper_add_one_connector() and
+ * drm_fb_helper_remove_one_connector().
  */
 int drm_fb_helper_single_add_all_connectors(struct drm_fb_helper *fb_helper)
 {
@@ -98,7 +99,8 @@ int drm_fb_helper_single_add_all_connectors(struct drm_fb_helper *fb_helper)
        struct drm_connector *connector;
        int i;
 
-       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+       mutex_lock(&dev->mode_config.mutex);
+       drm_for_each_connector(connector, dev) {
                struct drm_fb_helper_connector *fb_helper_connector;
 
                fb_helper_connector = kzalloc(sizeof(struct drm_fb_helper_connector), GFP_KERNEL);
@@ -108,6 +110,7 @@ int drm_fb_helper_single_add_all_connectors(struct drm_fb_helper *fb_helper)
                fb_helper_connector->connector = connector;
                fb_helper->connector_info[fb_helper->connector_count++] = fb_helper_connector;
        }
+       mutex_unlock(&dev->mode_config.mutex);
        return 0;
 fail:
        for (i = 0; i < fb_helper->connector_count; i++) {
@@ -115,6 +118,8 @@ fail:
                fb_helper->connector_info[i] = NULL;
        }
        fb_helper->connector_count = 0;
+       mutex_unlock(&dev->mode_config.mutex);
+
        return -ENOMEM;
 }
 EXPORT_SYMBOL(drm_fb_helper_single_add_all_connectors);
@@ -163,11 +168,14 @@ static void remove_from_modeset(struct drm_mode_set *set,
        }
        set->num_connectors--;
 
-       /* because i915 is pissy about this..
+       /*
         * TODO maybe need to makes sure we set it back to !=NULL somewhere?
         */
-       if (set->num_connectors == 0)
+       if (set->num_connectors == 0) {
                set->fb = NULL;
+               drm_mode_destroy(connector->dev, set->mode);
+               set->mode = NULL;
+       }
 }
 
 int drm_fb_helper_remove_one_connector(struct drm_fb_helper *fb_helper,
@@ -269,7 +277,7 @@ static struct drm_framebuffer *drm_mode_config_fb(struct drm_crtc *crtc)
        struct drm_device *dev = crtc->dev;
        struct drm_crtc *c;
 
-       list_for_each_entry(c, &dev->mode_config.crtc_list, head) {
+       drm_for_each_crtc(c, dev) {
                if (crtc->base.id == c->base.id)
                        return c->primary->fb;
        }
@@ -321,7 +329,7 @@ static bool restore_fbdev_mode(struct drm_fb_helper *fb_helper)
 
        drm_warn_on_modeset_not_all_locked(dev);
 
-       list_for_each_entry(plane, &dev->mode_config.plane_list, head) {
+       drm_for_each_plane(plane, dev) {
                if (plane->type != DRM_PLANE_TYPE_PRIMARY)
                        drm_plane_force_disable(plane);
 
@@ -349,21 +357,6 @@ static bool restore_fbdev_mode(struct drm_fb_helper *fb_helper)
        }
        return error;
 }
-/**
- * drm_fb_helper_restore_fbdev_mode - restore fbdev configuration
- * @fb_helper: fbcon to restore
- *
- * This should be called from driver's drm ->lastclose callback
- * when implementing an fbcon on top of kms using this helper. This ensures that
- * the user isn't greeted with a black screen when e.g. X dies.
- *
- * Use this variant if you need to bypass locking (panic), or already
- * hold all modeset locks.  Otherwise use drm_fb_helper_restore_fbdev_mode_unlocked()
- */
-static bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper)
-{
-       return restore_fbdev_mode(fb_helper);
-}
 
 /**
  * drm_fb_helper_restore_fbdev_mode_unlocked - restore fbdev configuration
@@ -393,6 +386,31 @@ bool drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper)
 }
 EXPORT_SYMBOL(drm_fb_helper_restore_fbdev_mode_unlocked);
 
+static bool drm_fb_helper_is_bound(struct drm_fb_helper *fb_helper)
+{
+       struct drm_device *dev = fb_helper->dev;
+       struct drm_crtc *crtc;
+       int bound = 0, crtcs_bound = 0;
+
+       /* Sometimes user space wants everything disabled, so don't steal the
+        * display if there's a master. */
+       if (dev->primary->master)
+               return false;
+
+       drm_for_each_crtc(crtc, dev) {
+               if (crtc->primary->fb)
+                       crtcs_bound++;
+               if (crtc->primary->fb == fb_helper->fb)
+                       bound++;
+       }
+
+       if (bound < crtcs_bound)
+               return false;
+
+       return true;
+}
+
+#ifdef CONFIG_MAGIC_SYSRQ
 /*
  * restore fbcon display for all kms driver's using this helper, used for sysrq
  * and panic handling.
@@ -411,49 +429,15 @@ static bool drm_fb_helper_force_kernel_mode(void)
                if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
                        continue;
 
-               /*
-                * NOTE: Use trylock mode to avoid deadlocks and sleeping in
-                * panic context.
-                */
-               if (__drm_modeset_lock_all(dev, true) != 0) {
-                       error = true;
-                       continue;
-               }
-
-               ret = drm_fb_helper_restore_fbdev_mode(helper);
+               drm_modeset_lock_all(dev);
+               ret = restore_fbdev_mode(helper);
                if (ret)
                        error = true;
-
                drm_modeset_unlock_all(dev);
        }
        return error;
 }
 
-static bool drm_fb_helper_is_bound(struct drm_fb_helper *fb_helper)
-{
-       struct drm_device *dev = fb_helper->dev;
-       struct drm_crtc *crtc;
-       int bound = 0, crtcs_bound = 0;
-
-       /* Sometimes user space wants everything disabled, so don't steal the
-        * display if there's a master. */
-       if (dev->primary->master)
-               return false;
-
-       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
-               if (crtc->primary->fb)
-                       crtcs_bound++;
-               if (crtc->primary->fb == fb_helper->fb)
-                       bound++;
-       }
-
-       if (bound < crtcs_bound)
-               return false;
-
-       return true;
-}
-
-#ifdef CONFIG_MAGIC_SYSRQ
 static void drm_fb_helper_restore_work_fn(struct work_struct *ignored)
 {
        bool ret;
@@ -485,14 +469,6 @@ static void drm_fb_helper_dpms(struct fb_info *info, int dpms_mode)
        struct drm_connector *connector;
        int i, j;
 
-       /*
-        * fbdev->blank can be called from irq context in case of a panic.
-        * Since we already have our own special panic handler which will
-        * restore the fbdev console mode completely, just bail out early.
-        */
-       if (oops_in_progress)
-               return;
-
        /*
         * For each CRTC in this fb, turn the connectors on/off.
         */
@@ -526,6 +502,9 @@ static void drm_fb_helper_dpms(struct fb_info *info, int dpms_mode)
  */
 int drm_fb_helper_blank(int blank, struct fb_info *info)
 {
+       if (oops_in_progress)
+               return -EBUSY;
+
        switch (blank) {
        /* Display: On; HSync: On, VSync: On */
        case FB_BLANK_UNBLANK:
@@ -637,7 +616,7 @@ int drm_fb_helper_init(struct drm_device *dev,
        }
 
        i = 0;
-       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+       drm_for_each_crtc(crtc, dev) {
                fb_helper->crtc_info[i].mode_set.crtc = crtc;
                i++;
        }
@@ -649,6 +628,86 @@ out_free:
 }
 EXPORT_SYMBOL(drm_fb_helper_init);
 
+/**
+ * drm_fb_helper_alloc_fbi - allocate fb_info and some of its members
+ * @fb_helper: driver-allocated fbdev helper
+ *
+ * A helper to alloc fb_info and the members cmap and apertures. Called
+ * by the driver within the fb_probe fb_helper callback function.
+ *
+ * RETURNS:
+ * fb_info pointer if things went okay, pointer containing error code
+ * otherwise
+ */
+struct fb_info *drm_fb_helper_alloc_fbi(struct drm_fb_helper *fb_helper)
+{
+       struct device *dev = fb_helper->dev->dev;
+       struct fb_info *info;
+       int ret;
+
+       info = framebuffer_alloc(0, dev);
+       if (!info)
+               return ERR_PTR(-ENOMEM);
+
+       ret = fb_alloc_cmap(&info->cmap, 256, 0);
+       if (ret)
+               goto err_release;
+
+       info->apertures = alloc_apertures(1);
+       if (!info->apertures) {
+               ret = -ENOMEM;
+               goto err_free_cmap;
+       }
+
+       fb_helper->fbdev = info;
+
+       return info;
+
+err_free_cmap:
+       fb_dealloc_cmap(&info->cmap);
+err_release:
+       framebuffer_release(info);
+       return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(drm_fb_helper_alloc_fbi);
+
+/**
+ * drm_fb_helper_unregister_fbi - unregister fb_info framebuffer device
+ * @fb_helper: driver-allocated fbdev helper
+ *
+ * A wrapper around unregister_framebuffer, to release the fb_info
+ * framebuffer device
+ */
+void drm_fb_helper_unregister_fbi(struct drm_fb_helper *fb_helper)
+{
+       if (fb_helper && fb_helper->fbdev)
+               unregister_framebuffer(fb_helper->fbdev);
+}
+EXPORT_SYMBOL(drm_fb_helper_unregister_fbi);
+
+/**
+ * drm_fb_helper_release_fbi - dealloc fb_info and its members
+ * @fb_helper: driver-allocated fbdev helper
+ *
+ * A helper to free memory taken by fb_info and the members cmap and
+ * apertures
+ */
+void drm_fb_helper_release_fbi(struct drm_fb_helper *fb_helper)
+{
+       if (fb_helper) {
+               struct fb_info *info = fb_helper->fbdev;
+
+               if (info) {
+                       if (info->cmap.len)
+                               fb_dealloc_cmap(&info->cmap);
+                       framebuffer_release(info);
+               }
+
+               fb_helper->fbdev = NULL;
+       }
+}
+EXPORT_SYMBOL(drm_fb_helper_release_fbi);
+
 void drm_fb_helper_fini(struct drm_fb_helper *fb_helper)
 {
        if (!list_empty(&fb_helper->kernel_fb_list)) {
@@ -663,6 +722,149 @@ void drm_fb_helper_fini(struct drm_fb_helper *fb_helper)
 }
 EXPORT_SYMBOL(drm_fb_helper_fini);
 
+/**
+ * drm_fb_helper_unlink_fbi - wrapper around unlink_framebuffer
+ * @fb_helper: driver-allocated fbdev helper
+ *
+ * A wrapper around unlink_framebuffer implemented by fbdev core
+ */
+void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
+{
+       if (fb_helper && fb_helper->fbdev)
+               unlink_framebuffer(fb_helper->fbdev);
+}
+EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
+
+/**
+ * drm_fb_helper_sys_read - wrapper around fb_sys_read
+ * @info: fb_info struct pointer
+ * @buf: userspace buffer to read from framebuffer memory
+ * @count: number of bytes to read from framebuffer memory
+ * @ppos: read offset within framebuffer memory
+ *
+ * A wrapper around fb_sys_read implemented by fbdev core
+ */
+ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf,
+                              size_t count, loff_t *ppos)
+{
+       return fb_sys_read(info, buf, count, ppos);
+}
+EXPORT_SYMBOL(drm_fb_helper_sys_read);
+
+/**
+ * drm_fb_helper_sys_write - wrapper around fb_sys_write
+ * @info: fb_info struct pointer
+ * @buf: userspace buffer to write to framebuffer memory
+ * @count: number of bytes to write to framebuffer memory
+ * @ppos: write offset within framebuffer memory
+ *
+ * A wrapper around fb_sys_write implemented by fbdev core
+ */
+ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
+                               size_t count, loff_t *ppos)
+{
+       return fb_sys_write(info, buf, count, ppos);
+}
+EXPORT_SYMBOL(drm_fb_helper_sys_write);
+
+/**
+ * drm_fb_helper_sys_fillrect - wrapper around sys_fillrect
+ * @info: fbdev registered by the helper
+ * @rect: info about rectangle to fill
+ *
+ * A wrapper around sys_fillrect implemented by fbdev core
+ */
+void drm_fb_helper_sys_fillrect(struct fb_info *info,
+                               const struct fb_fillrect *rect)
+{
+       sys_fillrect(info, rect);
+}
+EXPORT_SYMBOL(drm_fb_helper_sys_fillrect);
+
+/**
+ * drm_fb_helper_sys_copyarea - wrapper around sys_copyarea
+ * @info: fbdev registered by the helper
+ * @area: info about area to copy
+ *
+ * A wrapper around sys_copyarea implemented by fbdev core
+ */
+void drm_fb_helper_sys_copyarea(struct fb_info *info,
+                               const struct fb_copyarea *area)
+{
+       sys_copyarea(info, area);
+}
+EXPORT_SYMBOL(drm_fb_helper_sys_copyarea);
+
+/**
+ * drm_fb_helper_sys_imageblit - wrapper around sys_imageblit
+ * @info: fbdev registered by the helper
+ * @image: info about image to blit
+ *
+ * A wrapper around sys_imageblit implemented by fbdev core
+ */
+void drm_fb_helper_sys_imageblit(struct fb_info *info,
+                                const struct fb_image *image)
+{
+       sys_imageblit(info, image);
+}
+EXPORT_SYMBOL(drm_fb_helper_sys_imageblit);
+
+/**
+ * drm_fb_helper_cfb_fillrect - wrapper around cfb_fillrect
+ * @info: fbdev registered by the helper
+ * @rect: info about rectangle to fill
+ *
+ * A wrapper around cfb_imageblit implemented by fbdev core
+ */
+void drm_fb_helper_cfb_fillrect(struct fb_info *info,
+                               const struct fb_fillrect *rect)
+{
+       cfb_fillrect(info, rect);
+}
+EXPORT_SYMBOL(drm_fb_helper_cfb_fillrect);
+
+/**
+ * drm_fb_helper_cfb_copyarea - wrapper around cfb_copyarea
+ * @info: fbdev registered by the helper
+ * @area: info about area to copy
+ *
+ * A wrapper around cfb_copyarea implemented by fbdev core
+ */
+void drm_fb_helper_cfb_copyarea(struct fb_info *info,
+                               const struct fb_copyarea *area)
+{
+       cfb_copyarea(info, area);
+}
+EXPORT_SYMBOL(drm_fb_helper_cfb_copyarea);
+
+/**
+ * drm_fb_helper_cfb_imageblit - wrapper around cfb_imageblit
+ * @info: fbdev registered by the helper
+ * @image: info about image to blit
+ *
+ * A wrapper around cfb_imageblit implemented by fbdev core
+ */
+void drm_fb_helper_cfb_imageblit(struct fb_info *info,
+                                const struct fb_image *image)
+{
+       cfb_imageblit(info, image);
+}
+EXPORT_SYMBOL(drm_fb_helper_cfb_imageblit);
+
+/**
+ * drm_fb_helper_set_suspend - wrapper around fb_set_suspend
+ * @fb_helper: driver-allocated fbdev helper
+ * @state: desired state, zero to resume, non-zero to suspend
+ *
+ * A wrapper around fb_set_suspend implemented by fbdev core
+ */
+void drm_fb_helper_set_suspend(struct drm_fb_helper *fb_helper, int state)
+{
+       if (fb_helper && fb_helper->fbdev)
+               fb_set_suspend(fb_helper->fbdev, state);
+}
+EXPORT_SYMBOL(drm_fb_helper_set_suspend);
+
 static int setcolreg(struct drm_crtc *crtc, u16 red, u16 green,
                     u16 blue, u16 regno, struct fb_info *info)
 {
@@ -750,9 +952,10 @@ int drm_fb_helper_setcmap(struct fb_cmap *cmap, struct fb_info *info)
        int i, j, rc = 0;
        int start;
 
-       if (__drm_modeset_lock_all(dev, !!oops_in_progress)) {
+       if (oops_in_progress)
                return -EBUSY;
-       }
+
+       drm_modeset_lock_all(dev);
        if (!drm_fb_helper_is_bound(fb_helper)) {
                drm_modeset_unlock_all(dev);
                return -EBUSY;
@@ -901,6 +1104,9 @@ int drm_fb_helper_set_par(struct fb_info *info)
        struct drm_fb_helper *fb_helper = info->par;
        struct fb_var_screeninfo *var = &info->var;
 
+       if (oops_in_progress)
+               return -EBUSY;
+
        if (var->pixclock != 0) {
                DRM_ERROR("PIXEL CLOCK SET\n");
                return -EINVAL;
@@ -926,9 +1132,10 @@ int drm_fb_helper_pan_display(struct fb_var_screeninfo *var,
        int ret = 0;
        int i;
 
-       if (__drm_modeset_lock_all(dev, !!oops_in_progress)) {
+       if (oops_in_progress)
                return -EBUSY;
-       }
+
+       drm_modeset_lock_all(dev);
        if (!drm_fb_helper_is_bound(fb_helper)) {
                drm_modeset_unlock_all(dev);
                return -EBUSY;