We've had some IRC and off-list discussions about how to improve the
DRM's vblank code to be a bit more power friendly.  The core requirement
is that we only enable vblank interrupts when a client is actually waiting
for a vblank event (be it a signal or a wakeup).

This patch updates the DRM core, requiring drivers to provide vblank
enable and disable hooks, as well as a counter updater, and adds some
i915 code to use it.

When the DRM vblank code is called, the core will update the counter,
add the desired sequence value to it, and either setup a signal or
wait for the desired sequence number to be hit, enabling vblanks around
the operation.  Once complete, vblank interrupts will again be disabled
to save power.

The patch doesn't yet deal with two obvious cases (and probably more
that I'm missing, it's untested yet):
  - the hardware counter resets on mode switch, we need to rebase
    the appropriate last_counter at that point so it's not treated as
    a counter wrap
  - a client interested in signals but also blocked on a vblank event
    may cause vblanks to be disabled if it received signal at the wrong
    time

I'll be happy to fix it up and/or restructure as requested.  I think the
basic approach should be fairly sound (even devices that don't support a
counter register could fake it using total time/vrefresh or similar), but
if not I'd love to hear about it. :)

Thanks,
Jesse

diff --git a/linux-core/drmP.h b/linux-core/drmP.h
index dd3a69d..31293a8 100644
--- a/linux-core/drmP.h
+++ b/linux-core/drmP.h
@@ -627,8 +627,9 @@ struct drm_driver {
        int (*kernel_context_switch) (struct drm_device * dev, int old,
                                      int new);
        void (*kernel_context_switch_unlock) (struct drm_device * dev);
-       int (*vblank_wait) (struct drm_device * dev, unsigned int *sequence);
-       int (*vblank_wait2) (struct drm_device * dev, unsigned int *sequence);
+       u32 (*update_vblank_count) (struct drm_device *dev, int crtc);
+       void (*enable_vblank) (struct drm_device *dev, int crtc);
+       void (*disable_vblank) (struct drm_device *dev, int crtc);
        int (*dri_library_name) (struct drm_device * dev, char * buf);
 
        /**
@@ -783,8 +784,6 @@ typedef struct drm_device {
        /[EMAIL PROTECTED] */
 
        wait_queue_head_t vbl_queue;    /**< VBLANK wait queue */
-       atomic_t vbl_received;
-       atomic_t vbl_received2;         /**< number of secondary VBLANK 
interrupts */
        spinlock_t vbl_lock;
        struct list_head vbl_sigs;              /**< signal list to send on 
VBLANK */
        struct list_head vbl_sigs2;     /**< signals to send on secondary 
VBLANK */
diff --git a/linux-core/drm_irq.c b/linux-core/drm_irq.c
index 8871671..ad3ceff 100644
--- a/linux-core/drm_irq.c
+++ b/linux-core/drm_irq.c
@@ -248,7 +248,7 @@ int drm_wait_vblank(DRM_IOCTL_ARGS)
        drm_wait_vblank_t vblwait;
        struct timeval now;
        int ret = 0;
-       unsigned int flags, seq;
+       unsigned int flags, seq, crtc, cur_vblank;
 
        if ((!dev->irq) || (!dev->irq_enabled))
                return -EINVAL;
@@ -265,13 +265,13 @@ int drm_wait_vblank(DRM_IOCTL_ARGS)
        }
 
        flags = vblwait.request.type & _DRM_VBLANK_FLAGS_MASK;
+       crtc = flags & _DRM_VBLANK_SECONDARY ? 1 : 0;
 
        if (!drm_core_check_feature(dev, (flags & _DRM_VBLANK_SECONDARY) ?
                                    DRIVER_IRQ_VBL2 : DRIVER_IRQ_VBL))
                return -EINVAL;
 
-       seq = atomic_read((flags & _DRM_VBLANK_SECONDARY) ? &dev->vbl_received2
-                         : &dev->vbl_received);
+       seq = dev->driver->update_vblank_count(dev, crtc);
 
        switch (vblwait.request.type & _DRM_VBLANK_TYPES_MASK) {
        case _DRM_VBLANK_RELATIVE:
@@ -332,6 +332,8 @@ int drm_wait_vblank(DRM_IOCTL_ARGS)
                vbl_sig->info.si_signo = vblwait.request.signal;
                vbl_sig->task = current;
 
+               dev->driver->enable_vblank(dev, crtc);
+
                spin_lock_irqsave(&dev->vbl_lock, irqflags);
 
                list_add_tail(&vbl_sig->head, vbl_sigs);
@@ -340,17 +342,15 @@ int drm_wait_vblank(DRM_IOCTL_ARGS)
 
                vblwait.reply.sequence = seq;
        } else {
-               if (flags & _DRM_VBLANK_SECONDARY) {
-                       if (dev->driver->vblank_wait2)
-                               ret = dev->driver->vblank_wait2(dev, 
&vblwait.request.sequence);
-               } else if (dev->driver->vblank_wait)
-                       ret =
-                           dev->driver->vblank_wait(dev,
-                                                    &vblwait.request.sequence);
-
+               dev->driver->enable_vblank(dev, crtc);
+               DRM_WAIT_ON(ret, dev->vbl_queue, 3 * DRM_HZ,
+                           (((cur_vblank =
+                              dev->driver->update_vblank_count(dev, crtc))
+                             - seq) <= (1 << 23)));
                do_gettimeofday(&now);
                vblwait.reply.tval_sec = now.tv_sec;
                vblwait.reply.tval_usec = now.tv_usec;
+               dev->driver->disable_vblank(dev, crtc);
        }
 
       done:
@@ -379,8 +379,7 @@ void drm_vbl_send_signals(drm_device_t * dev)
        for (i = 0; i < 2; i++) {
                drm_vbl_sig_t *vbl_sig, *tmp;
                struct list_head *vbl_sigs = i ? &dev->vbl_sigs2 : 
&dev->vbl_sigs;
-               unsigned int vbl_seq = atomic_read(i ? &dev->vbl_received2 :
-                                                  &dev->vbl_received);
+               unsigned int vbl_seq = dev->driver->update_vblank_count(dev, i);
 
                list_for_each_entry_safe(vbl_sig, tmp, vbl_sigs, head) {
                        if ((vbl_seq - vbl_sig->sequence) <= (1 << 23)) {
@@ -396,6 +395,13 @@ void drm_vbl_send_signals(drm_device_t * dev)
                                dev->vbl_pending--;
                        }
                }
+               /*
+                * If there are no more signals requested, we can disable
+                * interrupts.
+                * FIXME: make sure we don't disable an active waiter!
+                */
+               if (list_empty(vbl_sigs))
+                       dev->driver->disable_vblank(dev, i);
        }
 
        spin_unlock_irqrestore(&dev->vbl_lock, flags);
diff --git a/linux-core/i915_drv.c b/linux-core/i915_drv.c
index 7fdb083..46a1061 100644
--- a/linux-core/i915_drv.c
+++ b/linux-core/i915_drv.c
@@ -83,8 +83,9 @@ static struct drm_driver driver = {
        .lastclose = i915_driver_lastclose,
        .preclose = i915_driver_preclose,
        .device_is_agp = i915_driver_device_is_agp,
-       .vblank_wait = i915_driver_vblank_wait,
-       .vblank_wait2 = i915_driver_vblank_wait2,
+       .update_vblank_count = i915_update_vblank_count,
+       .enable_vblank = i915_enable_vblank,
+       .disable_vblank = i915_disable_vblank,
        .irq_preinstall = i915_driver_irq_preinstall,
        .irq_postinstall = i915_driver_irq_postinstall,
        .irq_uninstall = i915_driver_irq_uninstall,
diff --git a/shared-core/i915_drv.h b/shared-core/i915_drv.h
index 9deee8e..917428c 100644
--- a/shared-core/i915_drv.h
+++ b/shared-core/i915_drv.h
@@ -132,6 +132,8 @@ typedef struct drm_i915_private {
        spinlock_t swaps_lock;
        drm_i915_vbl_swap_t vbl_swaps;
        unsigned int swaps_pending;
+       unsigned long last_vblank_count[2];
+       atomic_t vblank_count[2];
 } drm_i915_private_t;
 
 enum intel_chip_family {
@@ -161,8 +163,6 @@ extern int i915_driver_firstopen(struct drm_device *dev);
 extern int i915_irq_emit(DRM_IOCTL_ARGS);
 extern int i915_irq_wait(DRM_IOCTL_ARGS);
 
-extern int i915_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence);
-extern int i915_driver_vblank_wait2(drm_device_t *dev, unsigned int *sequence);
 extern irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS);
 extern void i915_driver_irq_preinstall(drm_device_t * dev);
 extern void i915_driver_irq_postinstall(drm_device_t * dev);
@@ -173,6 +173,9 @@ extern int i915_emit_irq(drm_device_t * dev);
 extern void i915_user_irq_on(drm_i915_private_t *dev_priv);
 extern void i915_user_irq_off(drm_i915_private_t *dev_priv);
 extern int i915_vblank_swap(DRM_IOCTL_ARGS);
+extern void i915_enable_vblank(drm_device_t *dev, int crtc);
+extern void i915_disable_vblank(drm_device_t *dev, int crtc);
+extern u32 i915_update_vblank_count(drm_device_t *dev, int crtc);
 
 /* i915_mem.c */
 extern int i915_mem_alloc(DRM_IOCTL_ARGS);
@@ -271,6 +274,36 @@ extern int i915_wait_ring(drm_device_t * dev, int n, const 
char *caller);
 
 #define I915REG_PIPEASTAT      0x70024
 #define I915REG_PIPEBSTAT      0x71024
+/*
+ * The two pipe frame counter registers are not synchronized, so
+ * reading a stable value is somewhat tricky. The following code 
+ * should work:
+ *
+ *  do {
+ *    high1 = ((INREG(PIPEAFRAMEHIGH) & PIPE_FRAME_HIGH_MASK) >>
+ *             PIPE_FRAME_HIGH_SHIFT;
+ *    low1 =  ((INREG(PIPEAFRAMEPIXEL) & PIPE_FRAME_LOW_MASK) >>
+ *             PIPE_FRAME_LOW_SHIFT);
+ *    high2 = ((INREG(PIPEAFRAMEHIGH) & PIPE_FRAME_HIGH_MASK) >>
+ *             PIPE_FRAME_HIGH_SHIFT);
+ *  } while (high1 != high2);
+ *  frame = (high1 << 8) | low1;
+ */
+#define PIPEAFRAMEHIGH          0x70040
+#define PIPEBFRAMEHIGH         0x71040
+#define PIPE_FRAME_HIGH_MASK    0x0000ffff
+#define PIPE_FRAME_HIGH_SHIFT   0
+#define PIPEAFRAMEPIXEL         0x70044
+#define PIPEBFRAMEPIXEL                0x71044
+
+#define PIPE_FRAME_LOW_MASK     0xff000000
+#define PIPE_FRAME_LOW_SHIFT    24
+/*
+ * Pixel within the current frame is counted in the PIPEAFRAMEPIXEL register
+ * and is 24 bits wide.
+ */
+#define PIPE_PIXEL_MASK         0x00ffffff
+#define PIPE_PIXEL_SHIFT        0
 
 #define I915_VBLANK_INTERRUPT_ENABLE   (1UL<<17)
 #define I915_VBLANK_CLEAR              (1UL<<1)
diff --git a/shared-core/i915_irq.c b/shared-core/i915_irq.c
index dc00f98..58758bf 100644
--- a/shared-core/i915_irq.c
+++ b/shared-core/i915_irq.c
@@ -92,8 +92,8 @@ static void i915_vblank_tasklet(drm_device_t *dev)
        unsigned long irqflags;
        struct list_head *list, *tmp, hits, *hit;
        int nhits, nrects, slice[2], upper[2], lower[2], i, num_pages;
-       unsigned counter[2] = { atomic_read(&dev->vbl_received),
-                               atomic_read(&dev->vbl_received2) };
+       unsigned counter[2] = { atomic_read(&dev_priv->vblank_count[0]),
+                               atomic_read(&dev_priv->vblank_count[1]) };
        drm_drawable_info_t *drw;
        drm_i915_sarea_t *sarea_priv = dev_priv->sarea_priv;
        u32 cpp = dev_priv->cpp,  offsets[3];
@@ -313,14 +313,14 @@ irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
                     (DRM_I915_VBLANK_PIPE_A | DRM_I915_VBLANK_PIPE_B))
                    == (DRM_I915_VBLANK_PIPE_A | DRM_I915_VBLANK_PIPE_B)) {
                        if (temp & VSYNC_PIPEA_FLAG)
-                               atomic_inc(&dev->vbl_received);
+                               atomic_inc(&dev_priv->vblank_count[0]);
                        if (temp & VSYNC_PIPEB_FLAG)
-                               atomic_inc(&dev->vbl_received2);
+                               atomic_inc(&dev_priv->vblank_count[1]);
                } else if (((temp & VSYNC_PIPEA_FLAG) &&
                            (vblank_pipe & DRM_I915_VBLANK_PIPE_A)) ||
                           ((temp & VSYNC_PIPEB_FLAG) &&
                            (vblank_pipe & DRM_I915_VBLANK_PIPE_B)))
-                       atomic_inc(&dev->vbl_received);
+                       atomic_inc(&dev_priv->vblank_count[0]);
 
                DRM_WAKEUP(&dev->vbl_queue);
                drm_vbl_send_signals(dev);
@@ -410,37 +410,6 @@ static int i915_wait_irq(drm_device_t * dev, int irq_nr)
        return ret;
 }
 
-static int i915_driver_vblank_do_wait(drm_device_t *dev, unsigned int 
*sequence,
-                                     atomic_t *counter)
-{
-       drm_i915_private_t *dev_priv = dev->dev_private;
-       unsigned int cur_vblank;
-       int ret = 0;
-
-       if (!dev_priv) {
-               DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
-               return DRM_ERR(EINVAL);
-       }
-
-       DRM_WAIT_ON(ret, dev->vbl_queue, 3 * DRM_HZ,
-                   (((cur_vblank = atomic_read(counter))
-                       - *sequence) <= (1<<23)));
-       
-       *sequence = cur_vblank;
-
-       return ret;
-}
-
-int i915_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence)
-{
-       return i915_driver_vblank_do_wait(dev, sequence, &dev->vbl_received);
-}
-
-int i915_driver_vblank_wait2(drm_device_t *dev, unsigned int *sequence)
-{
-       return i915_driver_vblank_do_wait(dev, sequence, &dev->vbl_received2);
-}
-
 /* Needs the lock as it touches the ring.
  */
 int i915_irq_emit(DRM_IOCTL_ARGS)
@@ -489,15 +458,95 @@ int i915_irq_wait(DRM_IOCTL_ARGS)
        return i915_wait_irq(dev, irqwait.irq_seq);
 }
 
-static void i915_enable_interrupt (drm_device_t *dev)
+void i915_enable_vblank(drm_device_t *dev, int crtc)
 {
        drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
        
-       dev_priv->irq_enable_reg = USER_INT_FLAG; 
-       if (dev_priv->vblank_pipe & DRM_I915_VBLANK_PIPE_A)
+       switch (crtc) {
+       case 0:
                dev_priv->irq_enable_reg |= VSYNC_PIPEA_FLAG;
-       if (dev_priv->vblank_pipe & DRM_I915_VBLANK_PIPE_B)
+               break;
+       case 1:
                dev_priv->irq_enable_reg |= VSYNC_PIPEB_FLAG;
+               break;
+       default:
+               DRM_ERROR("tried to enable vblank on non-existent crtc %d\n",
+                         crtc);
+               break;
+       }
+
+       I915_WRITE16(I915REG_INT_ENABLE_R, dev_priv->irq_enable_reg);
+}
+
+void i915_disable_vblank(drm_device_t *dev, int crtc)
+{
+       drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+       
+       switch (crtc) {
+       case 0:
+               dev_priv->irq_enable_reg &= ~VSYNC_PIPEA_FLAG;
+               break;
+       case 1:
+               dev_priv->irq_enable_reg &= ~VSYNC_PIPEB_FLAG;
+               break;
+       default:
+               DRM_ERROR("tried to disable vblank on non-existent crtc %d\n",
+                         crtc);
+               break;
+       }
+
+       I915_WRITE16(I915REG_INT_ENABLE_R, dev_priv->irq_enable_reg);
+}
+
+u32 i915_update_vblank_count(drm_device_t *dev, int crtc)
+{
+       drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+       unsigned long high_frame = crtc ? PIPEBFRAMEHIGH : PIPEAFRAMEHIGH;
+       unsigned long low_frame = crtc ? PIPEBFRAMEPIXEL : PIPEAFRAMEPIXEL;
+       unsigned long irqflags;
+       u32 high1, high2, low, count, diff;
+
+       /*
+        * High & low register fields aren't synchronized, so make sure
+        * we get a low value that's stable across two reads of the high
+        * register.
+        */
+       do {
+               high1 = ((I915_READ(high_frame) & PIPE_FRAME_HIGH_MASK) >>
+                        PIPE_FRAME_HIGH_SHIFT);
+               low =  ((I915_READ(low_frame) & PIPE_FRAME_LOW_MASK) >>
+                       PIPE_FRAME_LOW_SHIFT);
+               high2 = ((I915_READ(high_frame) & PIPE_FRAME_HIGH_MASK) >>
+                        PIPE_FRAME_HIGH_SHIFT);
+       } while (high1 != high2);
+
+       count = (high1 << 8) | low;
+
+       /*
+        * Now update the appropriate counter.
+        * Assume we haven't missed a full 24 bits of vblank
+        * events, or that it won't matter if they're not accounted
+        * for when we adjust for wrapping.
+        * FIXME: if count was zero'd due to modeset, need to rebase
+        */
+       spin_lock_irqsave(&dev->vbl_lock, irqflags);
+       if (count < dev_priv->last_vblank_count[crtc]) {
+               diff = 0xffffff - dev_priv->last_vblank_count[crtc];
+               diff += count;
+       } else {
+               diff = count - dev_priv->last_vblank_count[crtc];
+       }
+       dev_priv->last_vblank_count[crtc] = count;
+       spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
+       atomic_add(diff, &dev_priv->vblank_count[crtc]);
+       return atomic_read(&dev_priv->vblank_count[crtc]);
+}
+
+static void i915_enable_interrupt (drm_device_t *dev)
+{
+       drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+       
+       dev_priv->irq_enable_reg |= USER_INT_FLAG;
 
        I915_WRITE16(I915REG_INT_ENABLE_R, dev_priv->irq_enable_reg);
        dev_priv->irq_enabled = 1;
@@ -607,7 +656,7 @@ int i915_vblank_swap(DRM_IOCTL_ARGS)
 
        spin_unlock_irqrestore(&dev->drw_lock, irqflags);
 
-       curseq = atomic_read(pipe ? &dev->vbl_received2 : &dev->vbl_received);
+       curseq = atomic_read(&dev_priv->vblank_count[pipe]);
 
        if (seqtype == _DRM_VBLANK_RELATIVE)
                swap.sequence += curseq;
@@ -714,6 +763,7 @@ void i915_driver_irq_preinstall(drm_device_t * dev)
 void i915_driver_irq_postinstall(drm_device_t * dev)
 {
        drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+       int i;
 
        spin_lock_init(&dev_priv->swaps_lock);
        INIT_LIST_HEAD(&dev_priv->vbl_swaps.head);
@@ -721,6 +771,12 @@ void i915_driver_irq_postinstall(drm_device_t * dev)
 
        dev_priv->user_irq_lock = SPIN_LOCK_UNLOCKED;
        dev_priv->user_irq_refcount = 0;
+       dev_priv->irq_enable_reg = 0;
+       /* Zero per-crtc vblank stuff */
+       for (i = 0; i < 2; i++) {
+               atomic_set(&dev_priv->vblank_count[i], 0);
+               dev_priv->last_vblank_count[i] = 0;
+       }
 
        i915_enable_interrupt(dev);
        DRM_INIT_WAITQUEUE(&dev_priv->irq_queue);

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/
--
_______________________________________________
Dri-devel mailing list
Dri-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/dri-devel

Reply via email to