aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Isely2008-03-13 18:53:05 -0500
committerDave Airlie2008-03-13 18:53:05 -0500
commitae1bb96a7e24362500e02cf3a86bd268c2dcc835 (patch)
treebee269a19128e50d324f045da79d0b552b28eb17 /linux-core
parent9be916f3537599489e083437c9a948eb93004904 (diff)
downloadexternal-libdrm-ae1bb96a7e24362500e02cf3a86bd268c2dcc835.tar.gz
external-libdrm-ae1bb96a7e24362500e02cf3a86bd268c2dcc835.tar.xz
external-libdrm-ae1bb96a7e24362500e02cf3a86bd268c2dcc835.zip
drm: Fix race that can lockup the kernel
The i915_vblank_swap() function schedules an automatic buffer swap upon receipt of the vertical sync interrupt. Such an operation is lengthy so it can't be allowed to happen in normal interrupt context, thus the DRM implements this by scheduling the work in a kernel softirq-scheduled tasklet. In order for the buffer swap to work safely, the DRM's central lock must be taken, via a call to drm_lock_take() located in drivers/char/drm/drm_irq.c within the function drm_locked_tasklet_func(). The lock-taking logic uses a non-interrupt-blocking spinlock to implement the manipulations needed to take the lock. This semantic would be safe if all attempts to use the spinlock only happen from process context. However this buffer swap happens from softirq context which is really a form of interrupt context. Thus we have an unsafe situation, in that drm_locked_tasklet_func() can block on a spinlock already taken by a thread in process context which will never get scheduled again because of the blocked softirq tasklet. This wedges the kernel hard. To trigger this bug, run a dual-head cloned mode configuration which uses the i915 drm, then execute an opengl application which synchronizes buffer swaps against the vertical sync interrupt. In my testing, a lockup always results after running anywhere from 5 minutes to an hour and a half. I believe dual-head is needed to really trigger the problem because then the vertical sync interrupt handling is no longer predictable (due to being interrupt-sourced from two different heads running at different speeds). This raises the probability of the tasklet trying to run while the userspace DRI is doing things to the GPU (and manipulating the DRM lock). The fix is to change the relevant spinlock semantics to be the interrupt-blocking form. After this change I am no longer able to trigger the lockup; the longest test run so far was 20 hours (test stopped after that point). Note: I have examined the places where this spinlock is being employed; all are reasonably short bounded sequences and should be suitable for interrupts being blocked without impacting overall kernel interrupt response latency. Signed-off-by: Mike Isely <isely@pobox.com>
Diffstat (limited to 'linux-core')
-rw-r--r--linux-core/drm_fops.c7
-rw-r--r--linux-core/drm_lock.c35
2 files changed, 25 insertions, 17 deletions
diff --git a/linux-core/drm_fops.c b/linux-core/drm_fops.c
index 7fe274a2..a4c76f75 100644
--- a/linux-core/drm_fops.c
+++ b/linux-core/drm_fops.c
@@ -373,6 +373,7 @@ int drm_release(struct inode *inode, struct file *filp)
373 struct drm_file *file_priv = filp->private_data; 373 struct drm_file *file_priv = filp->private_data;
374 struct drm_device *dev = file_priv->minor->dev; 374 struct drm_device *dev = file_priv->minor->dev;
375 int retcode = 0; 375 int retcode = 0;
376 unsigned long irqflags;
376 377
377 lock_kernel(); 378 lock_kernel();
378 379
@@ -403,9 +404,11 @@ int drm_release(struct inode *inode, struct file *filp)
403 */ 404 */
404 405
405 do{ 406 do{
406 spin_lock(&dev->lock.spinlock); 407 spin_lock_irqsave(&dev->lock.spinlock,
408 irqflags);
407 locked = dev->lock.idle_has_lock; 409 locked = dev->lock.idle_has_lock;
408 spin_unlock(&dev->lock.spinlock); 410 spin_unlock_irqrestore(&dev->lock.spinlock,
411 irqflags);
409 if (locked) 412 if (locked)
410 break; 413 break;
411 schedule(); 414 schedule();
diff --git a/linux-core/drm_lock.c b/linux-core/drm_lock.c
index b8e4a5d9..39e1261c 100644
--- a/linux-core/drm_lock.c
+++ b/linux-core/drm_lock.c
@@ -53,6 +53,7 @@ int drm_lock(struct drm_device *dev, void *data, struct drm_file *file_priv)
53 DECLARE_WAITQUEUE(entry, current); 53 DECLARE_WAITQUEUE(entry, current);
54 struct drm_lock *lock = data; 54 struct drm_lock *lock = data;
55 int ret = 0; 55 int ret = 0;
56 unsigned long irqflags;
56 57
57 ++file_priv->lock_count; 58 ++file_priv->lock_count;
58 59
@@ -71,9 +72,9 @@ int drm_lock(struct drm_device *dev, void *data, struct drm_file *file_priv)
71 return -EINVAL; 72 return -EINVAL;
72 73
73 add_wait_queue(&dev->lock.lock_queue, &entry); 74 add_wait_queue(&dev->lock.lock_queue, &entry);
74 spin_lock(&dev->lock.spinlock); 75 spin_lock_irqsave(&dev->lock.spinlock, irqflags);
75 dev->lock.user_waiters++; 76 dev->lock.user_waiters++;
76 spin_unlock(&dev->lock.spinlock); 77 spin_unlock_irqsave(&dev->lock.spinlock, irqflags);
77 for (;;) { 78 for (;;) {
78 __set_current_state(TASK_INTERRUPTIBLE); 79 __set_current_state(TASK_INTERRUPTIBLE);
79 if (!dev->lock.hw_lock) { 80 if (!dev->lock.hw_lock) {
@@ -95,9 +96,9 @@ int drm_lock(struct drm_device *dev, void *data, struct drm_file *file_priv)
95 break; 96 break;
96 } 97 }
97 } 98 }
98 spin_lock(&dev->lock.spinlock); 99 spin_lock_irqsave(&dev->lock.spinlock, irqflags);
99 dev->lock.user_waiters--; 100 dev->lock.user_waiters--;
100 spin_unlock(&dev->lock.spinlock); 101 spin_unlock_irqrestore(&dev->lock.spinlock, irqflags);
101 __set_current_state(TASK_RUNNING); 102 __set_current_state(TASK_RUNNING);
102 remove_wait_queue(&dev->lock.lock_queue, &entry); 103 remove_wait_queue(&dev->lock.lock_queue, &entry);
103 104
@@ -198,8 +199,9 @@ int drm_lock_take(struct drm_lock_data *lock_data,
198{ 199{
199 unsigned int old, new, prev; 200 unsigned int old, new, prev;
200 volatile unsigned int *lock = &lock_data->hw_lock->lock; 201 volatile unsigned int *lock = &lock_data->hw_lock->lock;
202 unsigned long irqflags;
201 203
202 spin_lock(&lock_data->spinlock); 204 spin_lock_irqsave(&lock_data->spinlock, irqflags);
203 do { 205 do {
204 old = *lock; 206 old = *lock;
205 if (old & _DRM_LOCK_HELD) 207 if (old & _DRM_LOCK_HELD)
@@ -211,7 +213,7 @@ int drm_lock_take(struct drm_lock_data *lock_data,
211 } 213 }
212 prev = cmpxchg(lock, old, new); 214 prev = cmpxchg(lock, old, new);
213 } while (prev != old); 215 } while (prev != old);
214 spin_unlock(&lock_data->spinlock); 216 spin_unlock_irqrestore(&lock_data->spinlock, irqflags);
215 217
216 if (_DRM_LOCKING_CONTEXT(old) == context) { 218 if (_DRM_LOCKING_CONTEXT(old) == context) {
217 if (old & _DRM_LOCK_HELD) { 219 if (old & _DRM_LOCK_HELD) {
@@ -273,15 +275,16 @@ int drm_lock_free(struct drm_lock_data *lock_data, unsigned int context)
273{ 275{
274 unsigned int old, new, prev; 276 unsigned int old, new, prev;
275 volatile unsigned int *lock = &lock_data->hw_lock->lock; 277 volatile unsigned int *lock = &lock_data->hw_lock->lock;
278 unsigned long irqflags;
276 279
277 spin_lock(&lock_data->spinlock); 280 spin_lock_irqsave(&lock_data->spinlock, irqflags);
278 if (lock_data->kernel_waiters != 0) { 281 if (lock_data->kernel_waiters != 0) {
279 drm_lock_transfer(lock_data, 0); 282 drm_lock_transfer(lock_data, 0);
280 lock_data->idle_has_lock = 1; 283 lock_data->idle_has_lock = 1;
281 spin_unlock(&lock_data->spinlock); 284 spin_unlock_irqrestore(&lock_data->spinlock, irqflags);
282 return 1; 285 return 1;
283 } 286 }
284 spin_unlock(&lock_data->spinlock); 287 spin_unlock_irqrestore(&lock_data->spinlock, irqflags);
285 288
286 do { 289 do {
287 old = *lock; 290 old = *lock;
@@ -345,19 +348,20 @@ static int drm_notifier(void *priv)
345void drm_idlelock_take(struct drm_lock_data *lock_data) 348void drm_idlelock_take(struct drm_lock_data *lock_data)
346{ 349{
347 int ret = 0; 350 int ret = 0;
351 unsigned long irqflags;
348 352
349 spin_lock(&lock_data->spinlock); 353 spin_lock_irqsave(&lock_data->spinlock, irqflags);
350 lock_data->kernel_waiters++; 354 lock_data->kernel_waiters++;
351 if (!lock_data->idle_has_lock) { 355 if (!lock_data->idle_has_lock) {
352 356
353 spin_unlock(&lock_data->spinlock); 357 spin_unlock_irqrestore(&lock_data->spinlock, irqflags);
354 ret = drm_lock_take(lock_data, DRM_KERNEL_CONTEXT); 358 ret = drm_lock_take(lock_data, DRM_KERNEL_CONTEXT);
355 spin_lock(&lock_data->spinlock); 359 spin_lock_irqsave(&lock_data->spinlock, irqflags);
356 360
357 if (ret == 1) 361 if (ret == 1)
358 lock_data->idle_has_lock = 1; 362 lock_data->idle_has_lock = 1;
359 } 363 }
360 spin_unlock(&lock_data->spinlock); 364 spin_unlock_irqrestore(&lock_data->spinlock, irq_flags);
361} 365}
362EXPORT_SYMBOL(drm_idlelock_take); 366EXPORT_SYMBOL(drm_idlelock_take);
363 367
@@ -365,8 +369,9 @@ void drm_idlelock_release(struct drm_lock_data *lock_data)
365{ 369{
366 unsigned int old, prev; 370 unsigned int old, prev;
367 volatile unsigned int *lock = &lock_data->hw_lock->lock; 371 volatile unsigned int *lock = &lock_data->hw_lock->lock;
372 unsigned long irqflags;
368 373
369 spin_lock(&lock_data->spinlock); 374 spin_lock_irqsave(&lock_data->spinlock, irqflags);
370 if (--lock_data->kernel_waiters == 0) { 375 if (--lock_data->kernel_waiters == 0) {
371 if (lock_data->idle_has_lock) { 376 if (lock_data->idle_has_lock) {
372 do { 377 do {
@@ -377,7 +382,7 @@ void drm_idlelock_release(struct drm_lock_data *lock_data)
377 lock_data->idle_has_lock = 0; 382 lock_data->idle_has_lock = 0;
378 } 383 }
379 } 384 }
380 spin_unlock(&lock_data->spinlock); 385 spin_unlock_irqrestore(&lock_data->spinlock, irqflags);
381} 386}
382EXPORT_SYMBOL(drm_idlelock_release); 387EXPORT_SYMBOL(drm_idlelock_release);
383 388