aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2014-12-04 16:03:25 -0500
committerDaniel Vetter <daniel.vetter@ffwll.ch>2015-01-21 08:57:03 -0500
commitcdd1cf799bd24ac0a4184549601ae302267301c5 (patch)
treebf666cc69457b61505a8c3528f8dd1a77de80979
parent01934c2a691882185b3021d437df13bcba07711d (diff)
drm: Make drm_read() more robust against multithreaded races
The current implementation of drm_read() faces a number of issues: 1. Upon an error, it consumes the event which may lead to the client blocking. 2. Upon an error, it forgets about events already copied 3. If it fails to copy a single event with O_NONBLOCK it falls into a infinite loop of reporting EAGAIN. 3. There is a race between multiple waiters and blocking reads of the events list. Here, we inline drm_dequeue_event() into drm_read() so that we can take the spinlock around the list walking and event copying, and importantly reorder the error handling to avoid the issues above. Cc: Takashi Iwai <tiwai@suse.de> Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> Reviewed-by: Takashi Iwai <tiwai@suse.de> Testcase: igt/drm_read Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
-rw-r--r--drivers/gpu/drm/drm_fops.c89
1 files changed, 42 insertions, 47 deletions
diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c
index 0b9514b6cd64..076dd606b580 100644
--- a/drivers/gpu/drm/drm_fops.c
+++ b/drivers/gpu/drm/drm_fops.c
@@ -478,64 +478,59 @@ int drm_release(struct inode *inode, struct file *filp)
478} 478}
479EXPORT_SYMBOL(drm_release); 479EXPORT_SYMBOL(drm_release);
480 480
481static bool 481ssize_t drm_read(struct file *filp, char __user *buffer,
482drm_dequeue_event(struct drm_file *file_priv, 482 size_t count, loff_t *offset)
483 size_t total, size_t max, struct drm_pending_event **out)
484{ 483{
484 struct drm_file *file_priv = filp->private_data;
485 struct drm_device *dev = file_priv->minor->dev; 485 struct drm_device *dev = file_priv->minor->dev;
486 struct drm_pending_event *e; 486 ssize_t ret = 0;
487 unsigned long flags;
488 bool ret = false;
489
490 spin_lock_irqsave(&dev->event_lock, flags);
491 487
492 *out = NULL; 488 if (!access_ok(VERIFY_WRITE, buffer, count))
493 if (list_empty(&file_priv->event_list)) 489 return -EFAULT;
494 goto out;
495 e = list_first_entry(&file_priv->event_list,
496 struct drm_pending_event, link);
497 if (e->event->length + total > max)
498 goto out;
499 490
500 file_priv->event_space += e->event->length; 491 spin_lock_irq(&dev->event_lock);
501 list_del(&e->link); 492 for (;;) {
502 *out = e; 493 if (list_empty(&file_priv->event_list)) {
503 ret = true; 494 if (ret)
495 break;
504 496
505out: 497 if (filp->f_flags & O_NONBLOCK) {
506 spin_unlock_irqrestore(&dev->event_lock, flags); 498 ret = -EAGAIN;
507 return ret; 499 break;
508} 500 }
509
510ssize_t drm_read(struct file *filp, char __user *buffer,
511 size_t count, loff_t *offset)
512{
513 struct drm_file *file_priv = filp->private_data;
514 struct drm_pending_event *e;
515 size_t total;
516 ssize_t ret;
517 501
518 if ((filp->f_flags & O_NONBLOCK) == 0) { 502 spin_unlock_irq(&dev->event_lock);
519 ret = wait_event_interruptible(file_priv->event_wait, 503 ret = wait_event_interruptible(file_priv->event_wait,
520 !list_empty(&file_priv->event_list)); 504 !list_empty(&file_priv->event_list));
521 if (ret < 0) 505 spin_lock_irq(&dev->event_lock);
522 return ret; 506 if (ret < 0)
523 } 507 break;
508
509 ret = 0;
510 } else {
511 struct drm_pending_event *e;
512
513 e = list_first_entry(&file_priv->event_list,
514 struct drm_pending_event, link);
515 if (e->event->length + ret > count)
516 break;
517
518 if (__copy_to_user_inatomic(buffer + ret,
519 e->event, e->event->length)) {
520 if (ret == 0)
521 ret = -EFAULT;
522 break;
523 }
524 524
525 total = 0; 525 file_priv->event_space += e->event->length;
526 while (drm_dequeue_event(file_priv, total, count, &e)) { 526 ret += e->event->length;
527 if (copy_to_user(buffer + total, 527 list_del(&e->link);
528 e->event, e->event->length)) {
529 total = -EFAULT;
530 e->destroy(e); 528 e->destroy(e);
531 break;
532 } 529 }
533
534 total += e->event->length;
535 e->destroy(e);
536 } 530 }
531 spin_unlock_irq(&dev->event_lock);
537 532
538 return total ?: -EAGAIN; 533 return ret;
539} 534}
540EXPORT_SYMBOL(drm_read); 535EXPORT_SYMBOL(drm_read);
541 536