diff options
| author | Chris Wilson <chris@chris-wilson.co.uk> | 2014-12-04 16:03:25 -0500 |
|---|---|---|
| committer | Daniel Vetter <daniel.vetter@ffwll.ch> | 2015-01-21 08:57:03 -0500 |
| commit | cdd1cf799bd24ac0a4184549601ae302267301c5 (patch) | |
| tree | bf666cc69457b61505a8c3528f8dd1a77de80979 | |
| parent | 01934c2a691882185b3021d437df13bcba07711d (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.c | 89 |
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 | } |
| 479 | EXPORT_SYMBOL(drm_release); | 479 | EXPORT_SYMBOL(drm_release); |
| 480 | 480 | ||
| 481 | static bool | 481 | ssize_t drm_read(struct file *filp, char __user *buffer, |
| 482 | drm_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 | ||
| 505 | out: | 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 | |||
| 510 | ssize_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 | } |
| 540 | EXPORT_SYMBOL(drm_read); | 535 | EXPORT_SYMBOL(drm_read); |
| 541 | 536 | ||
