Per fdo bz #18041, we timeout if a vblank event doesn't occur within a
specified time period.  However, this wait happens in libdrm, so old
userspace can still end up hanging, waiting for a vblank event that may never
occur.  So add a more reasonable timeout value to drm_wait_vblank and make
the wait uninterruptible, so that the X server's scheduling signals don't get
in the way.

Reported-by: Linus Torvalds <torva...@linux-foundation.org>
Tested-by: Linus Torvalds <torva...@linux-foundation.org>
Signed-off-by: Jesse Barnes <jbar...@virtuousgeek.org>

diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c
index 69aa0ab..c5df063 100644
--- a/drivers/gpu/drm/drm_irq.c
+++ b/drivers/gpu/drm/drm_irq.c
@@ -539,9 +539,11 @@ out:
 int drm_wait_vblank(struct drm_device *dev, void *data,
                    struct drm_file *file_priv)
 {
+       struct timeval now;
        union drm_wait_vblank *vblwait = data;
        int ret = 0;
        unsigned int flags, seq, crtc;
+       int timeout, max_wait = (DRM_HZ / 40) * 100; /* 100ms max */
 
        if ((!dev->pdev->irq) || (!dev->irq_enabled))
                return -EINVAL;
@@ -588,25 +590,24 @@ int drm_wait_vblank(struct drm_device *dev, void *data,
 
        DRM_DEBUG("waiting on vblank count %d, crtc %d\n",
                  vblwait->request.sequence, crtc);
+
+       /* Wait at most 25ms * number of frames */
+       timeout = (DRM_HZ / 40) * (vblwait->request.sequence - seq);
+       if (timeout > max_wait)
+               timeout = max_wait;
+
        dev->last_vblank_wait[crtc] = vblwait->request.sequence;
-       DRM_WAIT_ON(ret, dev->vbl_queue[crtc], 3 * DRM_HZ,
+       DRM_WAIT_ON_UNINTERRUPTIBLE(ret, dev->vbl_queue[crtc], timeout,
                    (((drm_vblank_count(dev, crtc) -
                       vblwait->request.sequence) <= (1 << 23)) ||
                     !dev->irq_enabled));
 
-       if (ret != -EINTR) {
-               struct timeval now;
+       do_gettimeofday(&now);
 
-               do_gettimeofday(&now);
-
-               vblwait->reply.tval_sec = now.tv_sec;
-               vblwait->reply.tval_usec = now.tv_usec;
-               vblwait->reply.sequence = drm_vblank_count(dev, crtc);
-               DRM_DEBUG("returning %d to client\n",
-                         vblwait->reply.sequence);
-       } else {
-               DRM_DEBUG("vblank wait interrupted by signal\n");
-       }
+       vblwait->reply.tval_sec = now.tv_sec;
+       vblwait->reply.tval_usec = now.tv_usec;
+       vblwait->reply.sequence = drm_vblank_count(dev, crtc);
+       DRM_DEBUG("returning %d to client\n", vblwait->reply.sequence);
 
 done:
        drm_vblank_put(dev, crtc);
diff --git a/include/drm/drm_os_linux.h b/include/drm/drm_os_linux.h
index 8dbd257..67afee8 100644
--- a/include/drm/drm_os_linux.h
+++ b/include/drm/drm_os_linux.h
@@ -104,5 +104,25 @@ do {                                                       
        \
        remove_wait_queue(&(queue), &entry);                    \
 } while (0)
 
+#define DRM_WAIT_ON_UNINTERRUPTIBLE( ret, queue, timeout, condition )  \
+do {                                                           \
+       DECLARE_WAITQUEUE(entry, current);                      \
+       unsigned long end = jiffies + (timeout);                \
+       add_wait_queue(&(queue), &entry);                       \
+                                                               \
+       for (;;) {                                              \
+               __set_current_state(TASK_UNINTERRUPTIBLE);      \
+               if (condition)                                  \
+                       break;                                  \
+               if (time_after_eq(jiffies, end)) {              \
+                       ret = -EBUSY;                           \
+                       break;                                  \
+               }                                               \
+               schedule_timeout((HZ/100 > 1) ? HZ/100 : 1);    \
+       }                                                       \
+       __set_current_state(TASK_RUNNING);                      \
+       remove_wait_queue(&(queue), &entry);                    \
+} while (0)
+
 #define DRM_WAKEUP( queue ) wake_up_interruptible( queue )
 #define DRM_INIT_WAITQUEUE( queue ) init_waitqueue_head( queue )

------------------------------------------------------------------------------
This SF.net email is sponsored by:
SourcForge Community
SourceForge wants to tell your story.
http://p.sf.net/sfu/sf-spreadtheword
--
_______________________________________________
Dri-devel mailing list
Dri-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/dri-devel

Reply via email to