diff mbox

[v2,1/2] drm/i915: Fix oops caused by fbdev initialization failure

Message ID f07beea8143322d54dfeb929b91e0361ced273d5.1446987413.git.lukas@wunner.de (mailing list archive)
State New, archived
Headers show

Commit Message

Lukas Wunner Nov. 5, 2015, 9:42 a.m. UTC
intelfb_create() is called once on driver initialization. If it fails,
ifbdev->helper.fbdev, ifbdev->fb or ifbdev->fb->obj may be NULL.

intel_fbdev_destroy() is called on driver unload and dereferences
ifbdev->fb.

intel_fbdev_set_suspend() is called on suspend/resume and dereferences
ifbdev->helper.fbdev and ifbdev->fb->obj.

intel_fbdev_output_poll_changed() is called on hotplug events and calls
drm_fb_helper_hotplug_event(). If ifbdev->helper.fb is NULL it will
signal a delayed_hotplug ad infinitum; if it is not NULL it will call
drm_fb_helper_set_par() which dereferences ifbdev->helper.fbdev.

intel_fbdev_restore_mode() is called on lastclose and dereferences
ifbdev->fb->obj. It also calls __drm_atomic_helper_set_config() which
WARNs if ifbdev->fb is NULL, and drm_fb_helper_set_par() which
dereferences ifbdev->helper.fbdev.

i915_gem_framebuffer_info() is called on access to debugfs file
"i915_gem_framebuffer" and dereferences ifbdev, ifbdev->helper.fb
and ifbdev->helper.fb->obj.

intel_connector_add_to_fbdev() / intel_connector_remove_from_fbdev()
are called when registering / unregistering an mst connector and
dereference ifbdev.

Basically if fbdev creation fails, we end up with an ifbdev that's
only partially initialized and the driver will oops on unload,
the next suspend/resume cycle, hotplug events, when X11 is stopped
or when accessing debugfs file "i915_gem_framebuffer".

After the fbdev was destroyed with intel_fbdev_fini(), the driver
will oops on mst hotplug events.

Fix it.

Signed-off-by: Lukas Wunner <lukas@wunner.de>
---
 drivers/gpu/drm/i915/i915_debugfs.c | 24 +++++++++++++-----------
 drivers/gpu/drm/i915/intel_dp_mst.c | 10 ++++++++--
 drivers/gpu/drm/i915/intel_fbdev.c  | 16 ++++++++++------
 3 files changed, 31 insertions(+), 19 deletions(-)
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c
index 5659d4c..17821cf 100644
--- a/drivers/gpu/drm/i915/i915_debugfs.c
+++ b/drivers/gpu/drm/i915/i915_debugfs.c
@@ -1877,17 +1877,19 @@  static int i915_gem_framebuffer_info(struct seq_file *m, void *data)
 	struct drm_i915_private *dev_priv = dev->dev_private;
 
 	ifbdev = dev_priv->fbdev;
-	fb = to_intel_framebuffer(ifbdev->helper.fb);
-
-	seq_printf(m, "fbcon size: %d x %d, depth %d, %d bpp, modifier 0x%llx, refcount %d, obj ",
-		   fb->base.width,
-		   fb->base.height,
-		   fb->base.depth,
-		   fb->base.bits_per_pixel,
-		   fb->base.modifier[0],
-		   atomic_read(&fb->base.refcount.refcount));
-	describe_obj(m, fb->obj);
-	seq_putc(m, '\n');
+	if (ifbdev && ifbdev->helper.fb && ifbdev->fb->obj) {
+		fb = to_intel_framebuffer(ifbdev->helper.fb);
+
+		seq_printf(m, "fbcon size: %d x %d, depth %d, %d bpp, modifier 0x%llx, refcount %d, obj ",
+			   fb->base.width,
+			   fb->base.height,
+			   fb->base.depth,
+			   fb->base.bits_per_pixel,
+			   fb->base.modifier[0],
+			   atomic_read(&fb->base.refcount.refcount));
+		describe_obj(m, fb->obj);
+		seq_putc(m, '\n');
+	}
 #endif
 
 	mutex_lock(&dev->mode_config.fb_lock);
diff --git a/drivers/gpu/drm/i915/intel_dp_mst.c b/drivers/gpu/drm/i915/intel_dp_mst.c
index 0639275..735368e 100644
--- a/drivers/gpu/drm/i915/intel_dp_mst.c
+++ b/drivers/gpu/drm/i915/intel_dp_mst.c
@@ -414,7 +414,10 @@  static void intel_connector_add_to_fbdev(struct intel_connector *connector)
 {
 #ifdef CONFIG_DRM_FBDEV_EMULATION
 	struct drm_i915_private *dev_priv = to_i915(connector->base.dev);
-	drm_fb_helper_add_one_connector(&dev_priv->fbdev->helper, &connector->base);
+
+	if (dev_priv->fbdev)
+		drm_fb_helper_add_one_connector(&dev_priv->fbdev->helper,
+						&connector->base);
 #endif
 }
 
@@ -422,7 +425,10 @@  static void intel_connector_remove_from_fbdev(struct intel_connector *connector)
 {
 #ifdef CONFIG_DRM_FBDEV_EMULATION
 	struct drm_i915_private *dev_priv = to_i915(connector->base.dev);
-	drm_fb_helper_remove_one_connector(&dev_priv->fbdev->helper, &connector->base);
+
+	if (dev_priv->fbdev)
+		drm_fb_helper_remove_one_connector(&dev_priv->fbdev->helper,
+						   &connector->base);
 #endif
 }
 
diff --git a/drivers/gpu/drm/i915/intel_fbdev.c b/drivers/gpu/drm/i915/intel_fbdev.c
index db82ad4..05484ba 100644
--- a/drivers/gpu/drm/i915/intel_fbdev.c
+++ b/drivers/gpu/drm/i915/intel_fbdev.c
@@ -529,8 +529,10 @@  static void intel_fbdev_destroy(struct drm_device *dev,
 
 	drm_fb_helper_fini(&ifbdev->helper);
 
-	drm_framebuffer_unregister_private(&ifbdev->fb->base);
-	drm_framebuffer_remove(&ifbdev->fb->base);
+	if (ifbdev->fb) {
+		drm_framebuffer_unregister_private(&ifbdev->fb->base);
+		drm_framebuffer_remove(&ifbdev->fb->base);
+	}
 }
 
 /*
@@ -734,7 +736,7 @@  void intel_fbdev_set_suspend(struct drm_device *dev, int state, bool synchronous
 	struct intel_fbdev *ifbdev = dev_priv->fbdev;
 	struct fb_info *info;
 
-	if (!ifbdev)
+	if (!ifbdev || !ifbdev->fb || !ifbdev->fb->obj || !ifbdev->helper.fbdev)
 		return;
 
 	info = ifbdev->helper.fbdev;
@@ -780,8 +782,10 @@  void intel_fbdev_set_suspend(struct drm_device *dev, int state, bool synchronous
 void intel_fbdev_output_poll_changed(struct drm_device *dev)
 {
 	struct drm_i915_private *dev_priv = dev->dev_private;
-	if (dev_priv->fbdev)
-		drm_fb_helper_hotplug_event(&dev_priv->fbdev->helper);
+	struct intel_fbdev *ifbdev = dev_priv->fbdev;
+
+	if (ifbdev && ifbdev->fb && ifbdev->fb->obj && ifbdev->helper.fbdev)
+		drm_fb_helper_hotplug_event(&ifbdev->helper);
 }
 
 void intel_fbdev_restore_mode(struct drm_device *dev)
@@ -791,7 +795,7 @@  void intel_fbdev_restore_mode(struct drm_device *dev)
 	struct intel_fbdev *ifbdev = dev_priv->fbdev;
 	struct drm_fb_helper *fb_helper;
 
-	if (!ifbdev)
+	if (!ifbdev || !ifbdev->fb || !ifbdev->fb->obj || !ifbdev->helper.fbdev)
 		return;
 
 	fb_helper = &ifbdev->helper;