Skip to contentSkip to conversation

drm/fb-helper: Don't call dirty callback for untouched clips

PATCH Superseded Takashi Iwai 4 participants 10 messages Oct 20, 2016 – Oct 22, 2016

Subsystems (2)

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 drm/fb-helper: Don't call dirty callback for untouched clips

Since 4.7 kernel, we've seen the error messages like

kernel: [TTM] Buffer eviction failed
kernel: qxl 0000:00:02.0: object_init failed for (4026540032, 0x00000001)
kernel: [drm:qxl_alloc_bo_reserved [qxl]] *ERROR* failed to allocate VRAM BO

on QXL when switching and accessing on VT. The culprit was the generic deferred_io code (qxl driver switched to it since 4.7). There is a race between the dirty clip update and the call of callback.

In drm_fb_helper_dirty(), the dirty clip is updated in the spinlock, while it kicks off the update worker outside the spinlock. Meanwhile the update worker clears the dirty clip in the spinlock, too. Thus, when drm_fb_helper_dirty() is called concurrently, schedule_work() is called after the clip is cleared in the first worker call.

This patch addresses it by validating the clip before calling the dirty fb callback.

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98322
Bugzilla: https://bugzilla.suse.com/show_bug.cgi?id=1003298

Fixes eaa434defaca ('drm/fb-helper: Add fb_deferred_io support')
---
 drivers/gpu/drm/drm_fb_helper.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
drivers/gpu/drm/drm_fb_helper.c +9 -4
index 03414bde1f15..d790d205129e 100644
@@ -636,15 +636,20 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
 						    dirty_work);
 	struct drm_clip_rect *clip = &helper->dirty_clip;
 	struct drm_clip_rect clip_copy;
+	bool dirty;
 	unsigned long flags;
 
 	spin_lock_irqsave(&helper->dirty_lock, flags);
-	clip_copy = *clip;
-	clip->x1 = clip->y1 = ~0;
-	clip->x2 = clip->y2 = 0;
+	dirty = (clip->x1 < clip->x2 && clip->y1 < clip->y2);
+	if (dirty) {
+		clip_copy = *clip;
+		clip->x1 = clip->y1 = ~0;
+		clip->x2 = clip->y2 = 0;
+	}
 	spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
-	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+	if (dirty)
+		helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
 }
 
 /**
Signature · 1 line
2.10.1
#1 lore raw
Quoted 44 lines · Takashi Iwai

Since 4.7 kernel, we've seen the error messages like

kernel: [TTM] Buffer eviction failed
kernel: qxl 0000:00:02.0: object_init failed for (4026540032, 0x00000001)
kernel: [drm:qxl_alloc_bo_reserved [qxl]] *ERROR* failed to allocate VRAM BO

on QXL when switching and accessing on VT. The culprit was the generic deferred_io code (qxl driver switched to it since 4.7). There is a race between the dirty clip update and the call of callback.

In drm_fb_helper_dirty(), the dirty clip is updated in the spinlock, while it kicks off the update worker outside the spinlock. Meanwhile the update worker clears the dirty clip in the spinlock, too. Thus, when drm_fb_helper_dirty() is called concurrently, schedule_work() is called after the clip is cleared in the first worker call.

This patch addresses it by validating the clip before calling the dirty fb callback.

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98322
Bugzilla: https://bugzilla.suse.com/show_bug.cgi?id=1003298

Fixes eaa434defaca ('drm/fb-helper: Add fb_deferred_io support')
---
 drivers/gpu/drm/drm_fb_helper.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
drivers/gpu/drm/drm_fb_helper.c +1 -3
index 03414bde1f15..d790d205129e 100644
@@ -636,15 +636,20 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
 						    dirty_work);
 	struct drm_clip_rect *clip = &helper->dirty_clip;
 	struct drm_clip_rect clip_copy;
+	bool dirty;
 	unsigned long flags;
 
 	spin_lock_irqsave(&helper->dirty_lock, flags);
-	clip_copy = *clip;
-	clip->x1 = clip->y1 = ~0;
-	clip->x2 = clip->y2 = 0;

drivers/gpu/drm/drm_fb_helper.c static void drm_fb_helper_dirty_work(struct work_struct *work) · line 651

Quoted 10 lines
drivers/gpu/drm/drm_fb_helper.c +7 -1
+	dirty = (clip->x1 < clip->x2 && clip->y1 < clip->y2);
+	if (dirty) {
+		clip_copy = *clip;
+		clip->x1 = clip->y1 = ~0;
+		clip->x2 = clip->y2 = 0;
+	}
 	spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
-	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+	if (dirty)

Could do it the other way too, ie. just make the copy, and then check the copy (can be done after dropping the lock even). Would avoid having to add the 'dirty' variable.

Quoted 6 lines
drivers/gpu/drm/drm_fb_helper.c +1 -0
+		helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
 }
 
 /**
Signature · 1 line
2.10.1
Signature · 2 lines
Ville Syrjälä
Intel OTC
#2 lore raw
Quoted 54 lines · Ville Syrjälä
Quoted 52 lines · Takashi Iwai

Since 4.7 kernel, we've seen the error messages like

kernel: [TTM] Buffer eviction failed
kernel: qxl 0000:00:02.0: object_init failed for (4026540032, 0x00000001)
kernel: [drm:qxl_alloc_bo_reserved [qxl]] *ERROR* failed to allocate VRAM BO

on QXL when switching and accessing on VT. The culprit was the generic deferred_io code (qxl driver switched to it since 4.7). There is a race between the dirty clip update and the call of callback.

In drm_fb_helper_dirty(), the dirty clip is updated in the spinlock, while it kicks off the update worker outside the spinlock. Meanwhile the update worker clears the dirty clip in the spinlock, too. Thus, when drm_fb_helper_dirty() is called concurrently, schedule_work() is called after the clip is cleared in the first worker call.

This patch addresses it by validating the clip before calling the dirty fb callback.

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98322
Bugzilla: https://bugzilla.suse.com/show_bug.cgi?id=1003298

Fixes eaa434defaca ('drm/fb-helper: Add fb_deferred_io support')
---
 drivers/gpu/drm/drm_fb_helper.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
drivers/gpu/drm/drm_fb_helper.c +7 -3
index 03414bde1f15..d790d205129e 100644
@@ -636,15 +636,20 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
 						    dirty_work);
 	struct drm_clip_rect *clip = &helper->dirty_clip;
 	struct drm_clip_rect clip_copy;
+	bool dirty;
 	unsigned long flags;
 
 	spin_lock_irqsave(&helper->dirty_lock, flags);
-	clip_copy = *clip;
-	clip->x1 = clip->y1 = ~0;
-	clip->x2 = clip->y2 = 0;
+	dirty = (clip->x1 < clip->x2 && clip->y1 < clip->y2);
+	if (dirty) {
+		clip_copy = *clip;
+		clip->x1 = clip->y1 = ~0;
+		clip->x2 = clip->y2 = 0;
+	}
 	spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
Quoted 6 lines

drivers/gpu/drm/drm_fb_helper.c static void drm_fb_helper_dirty_work(struct work_struct *work) · line 651

Quoted 2 lines
drivers/gpu/drm/drm_fb_helper.c +1 -1
-	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+	if (dirty)

Could do it the other way too, ie. just make the copy, and then check the copy (can be done after dropping the lock even). Would avoid having to add the 'dirty' variable.

Sounds good. Let me try...

Takashi

#3 lore raw
Quoted 56 lines · Takashi Iwai
Quoted 54 lines · Ville Syrjälä
Quoted 52 lines · Takashi Iwai

Since 4.7 kernel, we've seen the error messages like

kernel: [TTM] Buffer eviction failed
kernel: qxl 0000:00:02.0: object_init failed for (4026540032, 0x00000001)
kernel: [drm:qxl_alloc_bo_reserved [qxl]] *ERROR* failed to allocate VRAM BO

on QXL when switching and accessing on VT. The culprit was the generic deferred_io code (qxl driver switched to it since 4.7). There is a race between the dirty clip update and the call of callback.

In drm_fb_helper_dirty(), the dirty clip is updated in the spinlock, while it kicks off the update worker outside the spinlock. Meanwhile the update worker clears the dirty clip in the spinlock, too. Thus, when drm_fb_helper_dirty() is called concurrently, schedule_work() is called after the clip is cleared in the first worker call.

This patch addresses it by validating the clip before calling the dirty fb callback.

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98322
Bugzilla: https://bugzilla.suse.com/show_bug.cgi?id=1003298

Fixes eaa434defaca ('drm/fb-helper: Add fb_deferred_io support')
---
 drivers/gpu/drm/drm_fb_helper.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
drivers/gpu/drm/drm_fb_helper.c +7 -3
index 03414bde1f15..d790d205129e 100644
@@ -636,15 +636,20 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
 						    dirty_work);
 	struct drm_clip_rect *clip = &helper->dirty_clip;
 	struct drm_clip_rect clip_copy;
+	bool dirty;
 	unsigned long flags;
 
 	spin_lock_irqsave(&helper->dirty_lock, flags);
-	clip_copy = *clip;
-	clip->x1 = clip->y1 = ~0;
-	clip->x2 = clip->y2 = 0;
+	dirty = (clip->x1 < clip->x2 && clip->y1 < clip->y2);
+	if (dirty) {
+		clip_copy = *clip;
+		clip->x1 = clip->y1 = ~0;
+		clip->x2 = clip->y2 = 0;
+	}
 	spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
Quoted 8 lines
Quoted 6 lines

drivers/gpu/drm/drm_fb_helper.c static void drm_fb_helper_dirty_work(struct work_struct *work) · line 651

Quoted 2 lines
drivers/gpu/drm/drm_fb_helper.c +1 -1
-	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+	if (dirty)

Could do it the other way too, ie. just make the copy, and then check the copy (can be done after dropping the lock even). Would avoid having to add the 'dirty' variable.

Sounds good. Let me try...

Another thing: How do we prevent userspace from doing the same, i.e. submitting an empty rectangle? Do we need to patch up drm_mode_dirtyfb_ioctl too? Not much point if we fix this bug in the fb helpers and leave the barn door wide open for userspace to oops drivers ;-)
-Daniel

Quoted 3 lines

Takashi

Signature · 3 lines
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
#4 lore raw
Quoted 65 lines · Daniel Vetter
Quoted 63 lines · Takashi Iwai
Quoted 60 lines · Ville Syrjälä
Quoted 44 lines · Takashi Iwai

Since 4.7 kernel, we've seen the error messages like

kernel: [TTM] Buffer eviction failed
kernel: qxl 0000:00:02.0: object_init failed for (4026540032, 0x00000001)
kernel: [drm:qxl_alloc_bo_reserved [qxl]] *ERROR* failed to allocate VRAM BO

on QXL when switching and accessing on VT. The culprit was the generic deferred_io code (qxl driver switched to it since 4.7). There is a race between the dirty clip update and the call of callback.

In drm_fb_helper_dirty(), the dirty clip is updated in the spinlock, while it kicks off the update worker outside the spinlock. Meanwhile the update worker clears the dirty clip in the spinlock, too. Thus, when drm_fb_helper_dirty() is called concurrently, schedule_work() is called after the clip is cleared in the first worker call.

This patch addresses it by validating the clip before calling the dirty fb callback.

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98322
Bugzilla: https://bugzilla.suse.com/show_bug.cgi?id=1003298

Fixes eaa434defaca ('drm/fb-helper: Add fb_deferred_io support')
---
 drivers/gpu/drm/drm_fb_helper.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
drivers/gpu/drm/drm_fb_helper.c +1 -3
index 03414bde1f15..d790d205129e 100644
@@ -636,15 +636,20 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
 						    dirty_work);
 	struct drm_clip_rect *clip = &helper->dirty_clip;
 	struct drm_clip_rect clip_copy;
+	bool dirty;
 	unsigned long flags;
 
 	spin_lock_irqsave(&helper->dirty_lock, flags);
-	clip_copy = *clip;
-	clip->x1 = clip->y1 = ~0;
-	clip->x2 = clip->y2 = 0;

drivers/gpu/drm/drm_fb_helper.c static void drm_fb_helper_dirty_work(struct work_struct *work) · line 651

Quoted 10 lines
drivers/gpu/drm/drm_fb_helper.c +7 -1
+	dirty = (clip->x1 < clip->x2 && clip->y1 < clip->y2);
+	if (dirty) {
+		clip_copy = *clip;
+		clip->x1 = clip->y1 = ~0;
+		clip->x2 = clip->y2 = 0;
+	}
 	spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
-	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+	if (dirty)

Could do it the other way too, ie. just make the copy, and then check the copy (can be done after dropping the lock even). Would avoid having to add the 'dirty' variable.

Quoted 7 lines
Quoted 1 line

Sounds good. Let me try...

Another thing: How do we prevent userspace from doing the same, i.e. submitting an empty rectangle? Do we need to patch up drm_mode_dirtyfb_ioctl too? Not much point if we fix this bug in the fb helpers and leave the barn door wide open for userspace to oops drivers ;-)

OK, then how about adding a helper to call the dirty callback with a sanity check like below?

Takashi

---

drivers/gpu/drm/drm_fb_helper.c +1 -1
index 03414bde1f15..7bef4d642511 100644
@@ -644,7 +644,7 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
 	clip->x2 = clip->y2 = 0;
 	spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
-	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+	drm_framebuffer_update_dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
 }
 
 /**
drivers/gpu/drm/drm_framebuffer.c +21 -6
index 398efd67cb93..0817a0b607c5 100644
@@ -529,6 +529,25 @@ int drm_mode_getfb(struct drm_device *dev,
 }
 
 /**
+ * blah blah
+ */
+int drm_framebuffer_update_dirty(struct drm_framebuffer *fb,
+				 struct drm_file *file_priv,
+				 unsigned flags, unsigned color,
+				 struct drm_clip_rect *clips,
+				 unsigned num_clips)
+{
+	if (!fb->funcs->dirty)
+		return -ENOSYS;
+	if (!num_clips)
+		return 0;
+	if (clips->x1 >= clips->x2 || clips->y1 >= clips->y2)
+		return 0;
+	return fb->funcs->dirty(fb, file_priv, flags, color, clips, num_clips);
+}
+EXPORT_SYMBOL(drm_framebuffer_update_dirty);
+
+/**
  * drm_mode_dirtyfb_ioctl - flush frontbuffer rendering on an FB
  * @dev: drm device for the ioctl
  * @data: data pointer for the ioctl
@@ -600,12 +619,8 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
 		}
 	}
 
-	if (fb->funcs->dirty) {
-		ret = fb->funcs->dirty(fb, file_priv, flags, r->color,
-				       clips, num_clips);
-	} else {
-		ret = -ENOSYS;
-	}
+	ret = drm_framebuffer_update_dirty(fb, file_priv, flags, r->color,
+					   clips, num_clips);
 
 out_err2:
 	kfree(clips);
include/drm/drm_framebuffer.h +6 -0
index f5ae1f436a4b..feabd9139fdb 100644
@@ -217,6 +217,12 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb);
 void drm_framebuffer_cleanup(struct drm_framebuffer *fb);
 void drm_framebuffer_unregister_private(struct drm_framebuffer *fb);
 
+int drm_framebuffer_update_dirty(struct drm_framebuffer *fb,
+				 struct drm_file *file_priv,
+				 unsigned flags, unsigned color,
+				 struct drm_clip_rect *clips,
+				 unsigned num_clips);
+
 /**
  * drm_framebuffer_reference - incr the fb refcnt
  * @fb: framebuffer
#5 lore raw
Quoted 107 lines · Takashi Iwai
Quoted 65 lines · Daniel Vetter
Quoted 63 lines · Takashi Iwai
Quoted 60 lines · Ville Syrjälä
Quoted 44 lines · Takashi Iwai

Since 4.7 kernel, we've seen the error messages like

kernel: [TTM] Buffer eviction failed
kernel: qxl 0000:00:02.0: object_init failed for (4026540032, 0x00000001)
kernel: [drm:qxl_alloc_bo_reserved [qxl]] *ERROR* failed to allocate VRAM BO

on QXL when switching and accessing on VT. The culprit was the generic deferred_io code (qxl driver switched to it since 4.7). There is a race between the dirty clip update and the call of callback.

In drm_fb_helper_dirty(), the dirty clip is updated in the spinlock, while it kicks off the update worker outside the spinlock. Meanwhile the update worker clears the dirty clip in the spinlock, too. Thus, when drm_fb_helper_dirty() is called concurrently, schedule_work() is called after the clip is cleared in the first worker call.

This patch addresses it by validating the clip before calling the dirty fb callback.

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98322
Bugzilla: https://bugzilla.suse.com/show_bug.cgi?id=1003298

Fixes eaa434defaca ('drm/fb-helper: Add fb_deferred_io support')
---
 drivers/gpu/drm/drm_fb_helper.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
drivers/gpu/drm/drm_fb_helper.c +1 -3
index 03414bde1f15..d790d205129e 100644
@@ -636,15 +636,20 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
 						    dirty_work);
 	struct drm_clip_rect *clip = &helper->dirty_clip;
 	struct drm_clip_rect clip_copy;
+	bool dirty;
 	unsigned long flags;
 
 	spin_lock_irqsave(&helper->dirty_lock, flags);
-	clip_copy = *clip;
-	clip->x1 = clip->y1 = ~0;
-	clip->x2 = clip->y2 = 0;

drivers/gpu/drm/drm_fb_helper.c static void drm_fb_helper_dirty_work(struct work_struct *work) · line 651

Quoted 10 lines
drivers/gpu/drm/drm_fb_helper.c +7 -1
+	dirty = (clip->x1 < clip->x2 && clip->y1 < clip->y2);
+	if (dirty) {
+		clip_copy = *clip;
+		clip->x1 = clip->y1 = ~0;
+		clip->x2 = clip->y2 = 0;
+	}
 	spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
-	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+	if (dirty)

Could do it the other way too, ie. just make the copy, and then check the copy (can be done after dropping the lock even). Would avoid having to add the 'dirty' variable.

Quoted 7 lines
Quoted 1 line

Sounds good. Let me try...

Another thing: How do we prevent userspace from doing the same, i.e. submitting an empty rectangle? Do we need to patch up drm_mode_dirtyfb_ioctl too? Not much point if we fix this bug in the fb helpers and leave the barn door wide open for userspace to oops drivers ;-)

OK, then how about adding a helper to call the dirty callback with a sanity check like below?

Takashi

---

drivers/gpu/drm/drm_fb_helper.c +1 -1
index 03414bde1f15..7bef4d642511 100644
@@ -644,7 +644,7 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
 	clip->x2 = clip->y2 = 0;
 	spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
-	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+	drm_framebuffer_update_dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
 }
 
 /**
drivers/gpu/drm/drm_framebuffer.c +4 -0
index 398efd67cb93..0817a0b607c5 100644
@@ -529,6 +529,25 @@ int drm_mode_getfb(struct drm_device *dev,
 }
 
 /**
+ * blah blah
+ */
+int drm_framebuffer_update_dirty(struct drm_framebuffer *fb,
+				 struct drm_file *file_priv,

drivers/gpu/drm/drm_framebuffer.c int drm_mode_getfb(struct drm_device *dev, · line 545

Quoted 10 lines
drivers/gpu/drm/drm_framebuffer.c +10 -0
+				 unsigned flags, unsigned color,
+				 struct drm_clip_rect *clips,
+				 unsigned num_clips)
+{
+	if (!fb->funcs->dirty)
+		return -ENOSYS;
+	if (!num_clips)
+		return 0;
+	if (clips->x1 >= clips->x2 || clips->y1 >= clips->y2)
+		return 0;

Would need to check every rect here.

Also for the ANNOTATE_COPY thing, should we check that each pair of rects has the same size?

Quoted 40 lines
drivers/gpu/drm/drm_framebuffer.c +7 -6
+	return fb->funcs->dirty(fb, file_priv, flags, color, clips, num_clips);
+}
+EXPORT_SYMBOL(drm_framebuffer_update_dirty);
+
+/**
  * drm_mode_dirtyfb_ioctl - flush frontbuffer rendering on an FB
  * @dev: drm device for the ioctl
  * @data: data pointer for the ioctl
@@ -600,12 +619,8 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
 		}
 	}
 
-	if (fb->funcs->dirty) {
-		ret = fb->funcs->dirty(fb, file_priv, flags, r->color,
-				       clips, num_clips);
-	} else {
-		ret = -ENOSYS;
-	}
+	ret = drm_framebuffer_update_dirty(fb, file_priv, flags, r->color,
+					   clips, num_clips);
 
 out_err2:
 	kfree(clips);
include/drm/drm_framebuffer.h +6 -0
index f5ae1f436a4b..feabd9139fdb 100644
@@ -217,6 +217,12 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb);
 void drm_framebuffer_cleanup(struct drm_framebuffer *fb);
 void drm_framebuffer_unregister_private(struct drm_framebuffer *fb);
 
+int drm_framebuffer_update_dirty(struct drm_framebuffer *fb,
+				 struct drm_file *file_priv,
+				 unsigned flags, unsigned color,
+				 struct drm_clip_rect *clips,
+				 unsigned num_clips);
+
 /**
  * drm_framebuffer_reference - incr the fb refcnt
  * @fb: framebuffer
Signature · 2 lines
Ville Syrjälä
Intel OTC
#6 lore raw
In reply to Daniel Vetter
Quoted 64 lines · Daniel Vetter
Quoted 63 lines · Takashi Iwai
Quoted 60 lines · Ville Syrjälä
Quoted 44 lines · Takashi Iwai

Since 4.7 kernel, we've seen the error messages like

kernel: [TTM] Buffer eviction failed
kernel: qxl 0000:00:02.0: object_init failed for (4026540032, 0x00000001)
kernel: [drm:qxl_alloc_bo_reserved [qxl]] *ERROR* failed to allocate VRAM BO

on QXL when switching and accessing on VT. The culprit was the generic deferred_io code (qxl driver switched to it since 4.7). There is a race between the dirty clip update and the call of callback.

In drm_fb_helper_dirty(), the dirty clip is updated in the spinlock, while it kicks off the update worker outside the spinlock. Meanwhile the update worker clears the dirty clip in the spinlock, too. Thus, when drm_fb_helper_dirty() is called concurrently, schedule_work() is called after the clip is cleared in the first worker call.

This patch addresses it by validating the clip before calling the dirty fb callback.

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98322
Bugzilla: https://bugzilla.suse.com/show_bug.cgi?id=1003298

Fixes eaa434defaca ('drm/fb-helper: Add fb_deferred_io support')
---
 drivers/gpu/drm/drm_fb_helper.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
drivers/gpu/drm/drm_fb_helper.c +1 -3
index 03414bde1f15..d790d205129e 100644
@@ -636,15 +636,20 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
 						    dirty_work);
 	struct drm_clip_rect *clip = &helper->dirty_clip;
 	struct drm_clip_rect clip_copy;
+	bool dirty;
 	unsigned long flags;
 
 	spin_lock_irqsave(&helper->dirty_lock, flags);
-	clip_copy = *clip;
-	clip->x1 = clip->y1 = ~0;
-	clip->x2 = clip->y2 = 0;

drivers/gpu/drm/drm_fb_helper.c static void drm_fb_helper_dirty_work(struct work_struct *work) · line 651

Quoted 10 lines
drivers/gpu/drm/drm_fb_helper.c +7 -1
+	dirty = (clip->x1 < clip->x2 && clip->y1 < clip->y2);
+	if (dirty) {
+		clip_copy = *clip;
+		clip->x1 = clip->y1 = ~0;
+		clip->x2 = clip->y2 = 0;
+	}
 	spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
-	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+	if (dirty)

Could do it the other way too, ie. just make the copy, and then check the copy (can be done after dropping the lock even). Would avoid having to add the 'dirty' variable.

Quoted 7 lines
Quoted 1 line

Sounds good. Let me try...

Another thing: How do we prevent userspace from doing the same, i.e. submitting an empty rectangle? Do we need to patch up drm_mode_dirtyfb_ioctl too? Not much point if we fix this bug in the fb helpers and leave the barn door wide open for userspace to oops drivers ;-)

I think of a use for sending an empty clip: where you don't want to push any new pixel data, but you do want to be sure that the pipeline has been flushed.

The change I would suggest here would be

	dirty = clip->x1 <= clip->x2 && clip->y1 <= clip->y2

as the bug is not the empty rectangle but the invalid one. However, that may be overkill, and none of the backends care about the empty rect!

But, indeed, we do not validate the incoming dirtyfb either.

drivers/gpu/drm/drm_framebuffer.c +11 -0
index 49fd7db758e0..ada6a5517945 100644
@@ -504,10 +504,13 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
        }
 
        if (num_clips && clips_ptr) {
+               int i;
+
                if (num_clips < 0 || num_clips > DRM_MODE_FB_DIRTY_MAX_CLIPS) {
                        ret = -EINVAL;
                        goto out_err1;
                }
+
                clips = kcalloc(num_clips, sizeof(*clips), GFP_KERNEL);
                if (!clips) {
                        ret = -ENOMEM;
@@ -520,6 +523,14 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
                        ret = -EFAULT;
                        goto out_err2;
                }
+
+               for (i = 0; i < num_clips; i++) {
+                       if (clips[i].x2 < clips[i].x1 ||
+                           clips[i].y2 < clips[i].y1) {
+                               ret = -EINVAL;
+                               goto out_err2;
+                       }
+               }
        }
 
        if (fb->funcs->dirty) 
Signature · 1 line
Chris Wilson, Intel Open Source Technology Centre
#7 lore raw
Quoted 73 lines · Chris Wilson
Quoted 64 lines · Daniel Vetter
Quoted 63 lines · Takashi Iwai
Quoted 60 lines · Ville Syrjälä
Quoted 44 lines · Takashi Iwai

Since 4.7 kernel, we've seen the error messages like

kernel: [TTM] Buffer eviction failed
kernel: qxl 0000:00:02.0: object_init failed for (4026540032, 0x00000001)
kernel: [drm:qxl_alloc_bo_reserved [qxl]] *ERROR* failed to allocate VRAM BO

on QXL when switching and accessing on VT. The culprit was the generic deferred_io code (qxl driver switched to it since 4.7). There is a race between the dirty clip update and the call of callback.

In drm_fb_helper_dirty(), the dirty clip is updated in the spinlock, while it kicks off the update worker outside the spinlock. Meanwhile the update worker clears the dirty clip in the spinlock, too. Thus, when drm_fb_helper_dirty() is called concurrently, schedule_work() is called after the clip is cleared in the first worker call.

This patch addresses it by validating the clip before calling the dirty fb callback.

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=98322
Bugzilla: https://bugzilla.suse.com/show_bug.cgi?id=1003298

Fixes eaa434defaca ('drm/fb-helper: Add fb_deferred_io support')
---
 drivers/gpu/drm/drm_fb_helper.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
drivers/gpu/drm/drm_fb_helper.c +1 -3
index 03414bde1f15..d790d205129e 100644
@@ -636,15 +636,20 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
 						    dirty_work);
 	struct drm_clip_rect *clip = &helper->dirty_clip;
 	struct drm_clip_rect clip_copy;
+	bool dirty;
 	unsigned long flags;
 
 	spin_lock_irqsave(&helper->dirty_lock, flags);
-	clip_copy = *clip;
-	clip->x1 = clip->y1 = ~0;
-	clip->x2 = clip->y2 = 0;

drivers/gpu/drm/drm_fb_helper.c static void drm_fb_helper_dirty_work(struct work_struct *work) · line 651

Quoted 10 lines
drivers/gpu/drm/drm_fb_helper.c +7 -1
+	dirty = (clip->x1 < clip->x2 && clip->y1 < clip->y2);
+	if (dirty) {
+		clip_copy = *clip;
+		clip->x1 = clip->y1 = ~0;
+		clip->x2 = clip->y2 = 0;
+	}
 	spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
-	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+	if (dirty)

Could do it the other way too, ie. just make the copy, and then check the copy (can be done after dropping the lock even). Would avoid having to add the 'dirty' variable.

Quoted 7 lines
Quoted 1 line

Sounds good. Let me try...

Another thing: How do we prevent userspace from doing the same, i.e. submitting an empty rectangle? Do we need to patch up drm_mode_dirtyfb_ioctl too? Not much point if we fix this bug in the fb helpers and leave the barn door wide open for userspace to oops drivers ;-)

Quoted 3 lines

I think of a use for sending an empty clip: where you don't want to push any new pixel data, but you do want to be sure that the pipeline has been flushed.

What exactly should an empty rectangle flush out? It's a bit unclear, but for speed I guess drivers should be allowed to make dirty async ... -Daniel

Quoted 51 lines

The change I would suggest here would be

	dirty = clip->x1 <= clip->x2 && clip->y1 <= clip->y2

as the bug is not the empty rectangle but the invalid one. However, that may be overkill, and none of the backends care about the empty rect!

But, indeed, we do not validate the incoming dirtyfb either.

drivers/gpu/drm/drm_framebuffer.c +11 -0
index 49fd7db758e0..ada6a5517945 100644
@@ -504,10 +504,13 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
        }
 
        if (num_clips && clips_ptr) {
+               int i;
+
                if (num_clips < 0 || num_clips > DRM_MODE_FB_DIRTY_MAX_CLIPS) {
                        ret = -EINVAL;
                        goto out_err1;
                }
+
                clips = kcalloc(num_clips, sizeof(*clips), GFP_KERNEL);
                if (!clips) {
                        ret = -ENOMEM;
@@ -520,6 +523,14 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
                        ret = -EFAULT;
                        goto out_err2;
                }
+
+               for (i = 0; i < num_clips; i++) {
+                       if (clips[i].x2 < clips[i].x1 ||
+                           clips[i].y2 < clips[i].y1) {
+                               ret = -EINVAL;
+                               goto out_err2;
+                       }
+               }
        }
 
        if (fb->funcs->dirty) 
Signature · 6 lines
-- 
Chris Wilson, Intel Open Source Technology Centre
_______________________________________________
dri-devel mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/dri-devel
Signature · 3 lines
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
#8 lore raw
Quoted 7 lines · Daniel Vetter
Quoted 3 lines · Chris Wilson

I think of a use for sending an empty clip: where you don't want to push any new pixel data, but you do want to be sure that the pipeline has been flushed.

What exactly should an empty rectangle flush out? It's a bit unclear, but for speed I guess drivers should be allowed to make dirty async ...

No idea! I'm just speculating that I can see a use for a dirtyfb barrier even with an empty cliprect. Empty clips are a useful distinction elsewhere that I would suggest not forbidding them outright but defining their behaviour.
-Chris

Signature · 1 line
Chris Wilson, Intel Open Source Technology Centre
#9 lore raw
Quoted 6 lines · Chris Wilson
Quoted 5 lines · Daniel Vetter
Quoted 3 lines · Chris Wilson

I think of a use for sending an empty clip: where you don't want to push any new pixel data, but you do want to be sure that the pipeline has been flushed.

Quoted 7 lines
Quoted 2 lines

What exactly should an empty rectangle flush out? It's a bit unclear, but for speed I guess drivers should be allowed to make dirty async ...

No idea! I'm just speculating that I can see a use for a dirtyfb barrier even with an empty cliprect. Empty clips are a useful distinction elsewhere that I would suggest not forbidding them outright but defining their behaviour.

In general I prefer clarifying unused and undefined cases to "reject them". We can always add a cap later on when we want to give them some real meaning.
-Daniel

Signature · 7 lines
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/dri-devel