On Wed, 29 Apr 2009 15:09:33 -0700
Jesse Barnes <jbar...@virtuousgeek.org> wrote:
> I'm still working through mutlihead issues on the kernel side; the
> flip waits should wait for *both* vblank events before completing the
> flip.  But other than that, I'm pretty happy with things.

This incremental set fixes up the multihead handling and adds swap
interval support as a bonus.  It's nice to see flipping & no tearing on
two heads at once!

-- 
Jesse Barnes, Intel Open Source Technology Center
diff --git a/libdrm/intel/intel_bufmgr.c b/libdrm/intel/intel_bufmgr.c
index 25a6828..80d5d3f 100644
--- a/libdrm/intel/intel_bufmgr.c
+++ b/libdrm/intel/intel_bufmgr.c
@@ -36,6 +36,7 @@
 #include <errno.h>
 #include <drm.h>
 #include <i915_drm.h>
+#include <xf86drm.h>
 #include "intel_bufmgr.h"
 #include "intel_bufmgr_priv.h"
 
@@ -212,3 +213,16 @@ int drm_intel_bo_get_tiling(drm_intel_bo *bo, uint32_t *tiling_mode,
     *swizzle_mode = I915_BIT_6_SWIZZLE_NONE;
     return 0;
 }
+
+int drm_intel_flip_bufs(int fd, int *crtc_ids, int crtc_count, int fb_id)
+{
+    struct drm_i915_gem_page_flip flip;
+
+    flip.fb_id = fb_id;
+    flip.crtc_ids = (uint32_t *)crtc_ids;
+    flip.crtc_count = (uint32_t)crtc_count;
+    flip.flags = 0;
+    flip.interval = 1;
+
+    return drmCommandWrite(fd, DRM_I915_GEM_PAGE_FLIP, &flip, sizeof(flip));
+}
diff --git a/libdrm/intel/intel_bufmgr.h b/libdrm/intel/intel_bufmgr.h
index 542dc06..1e32351 100644
--- a/libdrm/intel/intel_bufmgr.h
+++ b/libdrm/intel/intel_bufmgr.h
@@ -107,6 +107,7 @@ int drm_intel_bo_set_tiling(drm_intel_bo *bo, uint32_t *tiling_mode,
 int drm_intel_bo_get_tiling(drm_intel_bo *bo, uint32_t *tiling_mode,
 			uint32_t *swizzle_mode);
 int drm_intel_bo_flink(drm_intel_bo *bo, uint32_t *name);
+int drm_intel_flip_bufs(int fd, int *crtc_ids, int crtc_count, int fb_id);
 
 /* drm_intel_bufmgr_gem.c */
 drm_intel_bufmgr *drm_intel_bufmgr_gem_init(int fd, int batch_size);
diff --git a/shared-core/i915_drm.h b/shared-core/i915_drm.h
index 5456e91..066e448 100644
--- a/shared-core/i915_drm.h
+++ b/shared-core/i915_drm.h
@@ -205,6 +205,7 @@ typedef struct drm_i915_sarea {
 #define DRM_I915_GEM_GET_TILING	0x22
 #define DRM_I915_GEM_GET_APERTURE 0x23
 #define DRM_I915_GEM_MMAP_GTT	0x24
+#define DRM_I915_GEM_PAGE_FLIP	0x25
 
 #define DRM_IOCTL_I915_INIT		DRM_IOW( DRM_COMMAND_BASE + DRM_I915_INIT, drm_i915_init_t)
 #define DRM_IOCTL_I915_FLUSH		DRM_IO ( DRM_COMMAND_BASE + DRM_I915_FLUSH)
@@ -769,4 +770,29 @@ struct drm_i915_gem_get_aperture {
 	uint64_t aper_available_size;
 };
 
+#define I915_PAGE_FLIP_WAIT (1<<0) /* block on page flip completion */
+#define I915_PAGE_FLIP_ASYNC (1<<1) /* flip immediately */
+
+struct drm_i915_gem_page_flip {
+	/** Handle of new front buffer */
+	uint32_t fb_id;
+
+	/**
+	 * crtcs to flip
+	 */
+	uint32_t crtc_count;
+	uint32_t *crtc_ids;
+
+	/**
+	 * interval of the flip, i.e. this flip won't complete until
+	 * @interval frames have occurred
+	 */
+	uint32_t interval;
+
+	/**
+	 * page flip flags (wait on flip only for now)
+	 */
+	uint32_t flags;
+};
+
 #endif				/* _I915_DRM_H_ */
diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c
index a04639d..b4f1ae7 100644
--- a/drivers/gpu/drm/drm_crtc_helper.c
+++ b/drivers/gpu/drm/drm_crtc_helper.c
@@ -593,8 +593,10 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
 	if (drm_mode_equal(&saved_mode, &crtc->mode)) {
 		if (saved_x != crtc->x || saved_y != crtc->y ||
 		    depth_changed || bpp_changed) {
+			mutex_lock(&dev->struct_mutex);
 			ret = !crtc_funcs->mode_set_base(crtc, crtc->x, crtc->y,
 							 old_fb);
+			mutex_unlock(&dev->struct_mutex);
 			goto done;
 		}
 	}
@@ -864,8 +866,10 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
 		old_fb = set->crtc->fb;
 		if (set->crtc->fb != set->fb)
 			set->crtc->fb = set->fb;
+		mutex_lock(&dev->struct_mutex);
 		ret = crtc_funcs->mode_set_base(set->crtc,
 						set->x, set->y, old_fb);
+		mutex_unlock(&dev->struct_mutex);
 		if (ret != 0)
 		    goto fail_set_mode;
 	}
diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c
index 2726143..bf25d39 100644
--- a/drivers/gpu/drm/i915/i915_dma.c
+++ b/drivers/gpu/drm/i915/i915_dma.c
@@ -871,6 +871,186 @@ static int i915_set_status_page(struct drm_device *dev, void *data,
 	return 0;
 }
 
+int
+i915_gem_flip_pending_locked(struct drm_i915_gem_object *obj_priv)
+{
+	int pending = 0;
+	int pipe;
+
+	for (pipe = 0; pipe < 2; pipe++)
+		if (!list_empty(&obj_priv->vblank_head[pipe]))
+			pending++;
+
+	return pending;
+}
+
+int
+i915_gem_flip_pending(struct drm_gem_object *obj)
+{
+	struct drm_device *dev = obj->dev;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	unsigned long flags;
+	int pending = 0;
+
+	spin_lock_irqsave(&dev_priv->vblank_lock, flags);
+	pending = i915_gem_flip_pending_locked(obj->driver_private);
+	spin_unlock_irqrestore(&dev_priv->vblank_lock, flags);
+
+	return pending;
+}
+
+static int i915_gem_page_flip(struct drm_device *dev, void *data,
+			      struct drm_file *file_priv)
+{
+	struct drm_i915_gem_page_flip *flip_data = data;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	struct drm_mode_object *drm_obj, *fb_obj;
+	struct drm_crtc *crtc;
+	struct intel_crtc *intel_crtc;
+	struct intel_framebuffer *intel_fb;
+	struct drm_framebuffer *old_fb;
+	struct drm_i915_gem_object *obj_priv;
+	unsigned long flags;
+	unsigned int pipe;
+	u32 frame, crtc_ids[2];
+	int i, ret = 0;
+	struct drm_crtc_helper_funcs *crtc_funcs;
+	bool async_flip = false, block_on_flip = false;
+
+	if (!(drm_core_check_feature(dev, DRIVER_MODESET)))
+		return -ENODEV;
+
+	/*
+	 * Reject unknown flags so future userspace knows what we (don't)
+	 * support
+	 */
+	if (flip_data->flags & (~I915_PAGE_FLIP_FLAGS_MASK)) {
+		DRM_DEBUG("bad page flip flags\n");
+		return -EINVAL;
+	}
+
+	if (flip_data->interval == 0) {
+		DRM_DEBUG("bad flip interval\n");
+		return -EINVAL;
+	}
+
+	if (flip_data->crtc_count > 2) {
+		DRM_DEBUG("bad crtc count\n");
+		return -EINVAL;
+	}
+
+	ret = copy_from_user(crtc_ids, flip_data->crtc_ids,
+			     sizeof(u32) * flip_data->crtc_count);
+	if (ret)
+		return -EFAULT;
+
+	if (flip_data->flags & I915_PAGE_FLIP_WAIT)
+		block_on_flip = true;
+	if (flip_data->flags & I915_PAGE_FLIP_ASYNC)
+		async_flip = true;
+
+	mutex_lock(&dev->struct_mutex);
+
+	fb_obj = drm_mode_object_find(dev, flip_data->fb_id,
+				       DRM_MODE_OBJECT_FB);
+	if (!fb_obj) {
+		DRM_DEBUG("unknown fb %d\n", flip_data->fb_id);
+		ret = -ENOENT;
+		goto out_unlock;
+	}
+
+	intel_fb = to_intel_framebuffer(obj_to_fb(fb_obj));
+	obj_priv = intel_fb->obj->driver_private;
+
+	/* Sanity check tiling */
+	if (obj_priv->tiling_mode != I915_TILING_NONE &&
+	    obj_priv->tiling_mode != I915_TILING_X) {
+		DRM_ERROR("can only flip non-tiled or X tiled pages\n");
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	for (i = 0; i < flip_data->crtc_count; i++) {
+		drm_obj = drm_mode_object_find(dev, crtc_ids[i],
+					       DRM_MODE_OBJECT_CRTC);
+		if (!drm_obj) {
+			DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_ids[i]);
+			ret = -ENOENT;
+			goto out_unlock;
+		}
+		crtc = obj_to_crtc(drm_obj);
+		if (!crtc->enabled) {
+			DRM_ERROR("crtc %d not enabled\n",
+				  flip_data->crtc_ids[i]);
+			ret = -EINVAL;
+			goto out_unlock;
+		}
+		old_fb = crtc->fb;
+		intel_crtc = to_intel_crtc(crtc);
+		pipe = intel_crtc->pipe;
+
+		crtc->fb = obj_to_fb(fb_obj);
+
+		/*
+		 * We always need to wait for previous flips since we'll
+		 * be overwriting the flip data
+		 */
+retry:
+		if (i915_gem_flip_pending(intel_fb->obj)) {
+			mutex_unlock(&dev->struct_mutex);
+			wait_for_completion(&obj_priv->vblank);
+			mutex_lock(&dev->struct_mutex);
+			goto retry;
+		}
+
+		/* Get vblank ref for completion handling */
+		ret = drm_vblank_get(dev, pipe);
+		if (ret) {
+			DRM_ERROR("failed to take vblank ref\n");
+			goto out_unlock;
+		}
+
+		/*
+		 * On an async flip we don't bother to track the flip data.
+		 * This may mean drawing into a buffer during retrace, but
+		 * OMG MOAR FPS!
+		 */
+		if (!async_flip) {
+			spin_lock_irqsave(&dev_priv->vblank_lock, flags);
+
+			/* Should be in the clear at this point, check */
+			WARN(i915_gem_flip_pending_locked(obj_priv),
+			     "flip pending not clear at flip request time\n");
+
+			frame = drm_vblank_count(dev, pipe);
+			obj_priv->flip_frame[pipe] =
+				frame + flip_data->interval;
+			list_add_tail(&obj_priv->vblank_head[pipe],
+				      &dev_priv->mm.vblank_list[pipe]);
+			spin_unlock_irqrestore(&dev_priv->vblank_lock, flags);
+		}
+
+		crtc_funcs = crtc->helper_private;
+		ret = crtc_funcs->mode_set_base(crtc, 0, 0, old_fb);
+		if (ret) {
+			DRM_ERROR("mode_set_base failed: %d\n", ret);
+			goto out_unlock;
+		}
+	}
+
+	mutex_unlock(&dev->struct_mutex);
+
+	if (block_on_flip && i915_gem_flip_pending(intel_fb->obj))
+		wait_for_completion(&obj_priv->vblank);
+
+	return 0;
+
+out_unlock:
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
 /**
  * i915_probe_agp - get AGP bootup configuration
  * @pdev: PCI device
@@ -1350,6 +1530,7 @@ struct drm_ioctl_desc i915_ioctls[] = {
 	DRM_IOCTL_DEF(DRM_I915_GEM_SET_TILING, i915_gem_set_tiling, 0),
 	DRM_IOCTL_DEF(DRM_I915_GEM_GET_TILING, i915_gem_get_tiling, 0),
 	DRM_IOCTL_DEF(DRM_I915_GEM_GET_APERTURE, i915_gem_get_aperture_ioctl, 0),
+	DRM_IOCTL_DEF(DRM_I915_GEM_PAGE_FLIP, i915_gem_page_flip, DRM_AUTH|DRM_MASTER),
 };
 
 int i915_max_ioctl = DRM_ARRAY_SIZE(i915_ioctls);
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index d7471fe..b77fa2a 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -162,6 +162,10 @@ typedef struct drm_i915_private {
 	u32 hotplug_supported_mask;
 	struct work_struct hotplug_work;
 
+	/** Protects vblank list */
+	spinlock_t vblank_lock;
+	struct work_struct vblank_work;
+
 	int tex_lru_log_granularity;
 	int allow_batchbuffer;
 	struct mem_block *agp_heap;
@@ -341,6 +345,11 @@ typedef struct drm_i915_private {
 		 */
 		struct delayed_work retire_work;
 
+		/**
+		 * List of objects waiting on vblank events (one per pipe)
+		 */
+		struct list_head vblank_list[2];
+
 		uint32_t next_gem_seqno;
 
 		/**
@@ -392,6 +401,11 @@ struct drm_i915_gem_object {
 	/** This object's place on the active/flushing/inactive lists */
 	struct list_head list;
 
+	/** Object's place on the vblank lists (protected by vblank_lock)*/
+	struct list_head vblank_head[2];
+	/** vblank frame to wait for */
+	u32 flip_frame[2];
+
 	/**
 	 * This is set if the object is on the active or flushing lists
 	 * (has pending rendering), and is not set if it's on inactive (ready
@@ -465,6 +479,9 @@ struct drm_i915_gem_object {
 	/** for phy allocated objects */
 	struct drm_i915_gem_phys_object *phys_obj;
 
+	/** for page flips and other vblank related blocks */
+	struct completion vblank;
+
 	/**
 	 * Used for checking the object doesn't appear more than once
 	 * in an execbuffer object list.
@@ -529,6 +546,8 @@ extern long i915_compat_ioctl(struct file *filp, unsigned int cmd,
 extern int i915_emit_box(struct drm_device *dev,
 			 struct drm_clip_rect *boxes,
 			 int i, int DR1, int DR4);
+extern int i915_gem_flip_pending(struct drm_gem_object *obj);
+extern int i915_gem_flip_pending_locked(struct drm_i915_gem_object *obj_priv);
 
 /* i915_irq.c */
 extern int i915_irq_emit(struct drm_device *dev, void *data,
diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c
index 4642115..98f4051 100644
--- a/drivers/gpu/drm/i915/i915_gem.c
+++ b/drivers/gpu/drm/i915/i915_gem.c
@@ -3232,6 +3232,26 @@ i915_gem_execbuffer(struct drm_device *dev, void *data,
 	if (ret != 0)
 		goto pre_mutex_err;
 
+	/* Look up object handles */
+	for (i = 0; i < args->buffer_count; i++) {
+		object_list[i] = drm_gem_object_lookup(dev, file_priv,
+						       exec_list[i].handle);
+		if (object_list[i] == NULL) {
+			DRM_ERROR("Invalid object handle %d at index %d\n",
+				   exec_list[i].handle, i);
+			ret = -EBADF;
+			mutex_lock(&dev->struct_mutex);
+			goto err;
+		}
+
+		if (i915_gem_flip_pending(object_list[i])) {
+			struct drm_i915_gem_object *obj_priv;
+
+			obj_priv = object_list[i]->driver_private;
+			wait_for_completion(&obj_priv->vblank);
+		}
+	}
+
 	mutex_lock(&dev->struct_mutex);
 
 	i915_verify_inactive(dev, __FILE__, __LINE__);
@@ -3250,17 +3270,8 @@ i915_gem_execbuffer(struct drm_device *dev, void *data,
 		goto pre_mutex_err;
 	}
 
-	/* Look up object handles */
+	/* Sanity check list for double entries */
 	for (i = 0; i < args->buffer_count; i++) {
-		object_list[i] = drm_gem_object_lookup(dev, file_priv,
-						       exec_list[i].handle);
-		if (object_list[i] == NULL) {
-			DRM_ERROR("Invalid object handle %d at index %d\n",
-				   exec_list[i].handle, i);
-			ret = -EBADF;
-			goto err;
-		}
-
 		obj_priv = object_list[i]->driver_private;
 		if (obj_priv->in_execbuffer) {
 			DRM_ERROR("Object %p appears more than once in object list\n",
@@ -3697,6 +3708,10 @@ int i915_gem_init_object(struct drm_gem_object *obj)
 	obj_priv->obj = obj;
 	obj_priv->fence_reg = I915_FENCE_REG_NONE;
 	INIT_LIST_HEAD(&obj_priv->list);
+	INIT_LIST_HEAD(&obj_priv->vblank_head[0]);
+	INIT_LIST_HEAD(&obj_priv->vblank_head[1]);
+
+	init_completion(&obj_priv->vblank);
 
 	return 0;
 }
@@ -3758,8 +3773,10 @@ int
 i915_gem_idle(struct drm_device *dev)
 {
 	drm_i915_private_t *dev_priv = dev->dev_private;
+	struct drm_i915_gem_object *obj_priv, *tmp;
+	unsigned long irqflags;
 	uint32_t seqno, cur_seqno, last_seqno;
-	int stuck, ret;
+	int stuck, ret, pipe;
 
 	mutex_lock(&dev->struct_mutex);
 
@@ -3773,9 +3790,23 @@ i915_gem_idle(struct drm_device *dev)
 	 */
 	dev_priv->mm.suspended = 1;
 
+	mutex_unlock(&dev->struct_mutex);
+
+	/* Wait for any outstanding flips */
+	spin_lock_irqsave(&dev_priv->vblank_lock, irqflags);
+	for (pipe = 0; pipe < 2; pipe++) {
+		list_for_each_entry_safe(obj_priv, tmp,
+					 &dev_priv->mm.vblank_list[pipe],
+					 vblank_head[pipe]) {
+			spin_unlock_irqrestore(&dev_priv->vblank_lock, irqflags);
+			wait_for_completion(&obj_priv->vblank);
+			spin_lock_irqsave(&dev_priv->vblank_lock, irqflags);
+		}
+	}
+	spin_unlock_irqrestore(&dev_priv->vblank_lock, irqflags);
+
 	/* Cancel the retire work handler, wait for it to finish if running
 	 */
-	mutex_unlock(&dev->struct_mutex);
 	cancel_delayed_work_sync(&dev_priv->mm.retire_work);
 	mutex_lock(&dev->struct_mutex);
 
@@ -4142,9 +4173,12 @@ i915_gem_load(struct drm_device *dev)
 	INIT_LIST_HEAD(&dev_priv->mm.flushing_list);
 	INIT_LIST_HEAD(&dev_priv->mm.inactive_list);
 	INIT_LIST_HEAD(&dev_priv->mm.request_list);
+	INIT_LIST_HEAD(&dev_priv->mm.vblank_list[0]);
+	INIT_LIST_HEAD(&dev_priv->mm.vblank_list[1]);
 	INIT_DELAYED_WORK(&dev_priv->mm.retire_work,
 			  i915_gem_retire_work_handler);
 	dev_priv->mm.next_gem_seqno = 1;
+	spin_lock_init(&dev_priv->vblank_lock);
 
 	/* Old X drivers will take 0-2 for front, back, depth buffers */
 	dev_priv->fence_reg_start = 3;
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c
index 98bb4c8..5e29d29 100644
--- a/drivers/gpu/drm/i915/i915_irq.c
+++ b/drivers/gpu/drm/i915/i915_irq.c
@@ -196,6 +196,48 @@ static void i915_hotplug_work_func(struct work_struct *work)
 	drm_sysfs_hotplug_event(dev);
 }
 
+#define vblank_after(a,b) ((long)(b) - (long)(a) < 0)
+
+static void i915_handle_flip_locked(struct drm_device *dev,
+				    struct drm_i915_gem_object *obj_priv,
+				    int pipe)
+{
+	if (vblank_after(drm_vblank_count(dev, pipe),
+			 obj_priv->flip_frame[pipe])) {
+		list_del_init(&obj_priv->vblank_head[pipe]);
+		drm_vblank_put(dev, pipe);
+		/*
+		 * If all the vblanks for this object have completed,
+		 * we can wake up any waiters.
+		 */
+		if (!i915_gem_flip_pending_locked(obj_priv))
+			complete(&obj_priv->vblank);
+	}
+}
+
+static void i915_vblank_work_func(struct work_struct *work)
+{
+	drm_i915_private_t *dev_priv = container_of(work, drm_i915_private_t,
+						    vblank_work);
+	struct drm_device *dev = dev_priv->dev;
+	struct drm_i915_gem_object *obj_priv, *tmp;
+	unsigned long irqflags;
+	int pipe;
+
+	mutex_lock(&dev->struct_mutex);
+
+	spin_lock_irqsave(&dev_priv->vblank_lock, irqflags);
+	for (pipe = 0; pipe < 2; pipe++) {
+		list_for_each_entry_safe(obj_priv, tmp,
+					 &dev_priv->mm.vblank_list[pipe],
+					 vblank_head[pipe]) {
+			i915_handle_flip_locked(dev, obj_priv, pipe);
+		}
+	}
+	spin_unlock_irqrestore(&dev_priv->vblank_lock, irqflags);
+	mutex_unlock(&dev->struct_mutex);
+}
+
 irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
 {
 	struct drm_device *dev = (struct drm_device *) arg;
@@ -292,6 +334,9 @@ irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
 			drm_handle_vblank(dev, 1);
 		}
 
+		if (vblank)
+			schedule_work(&dev_priv->vblank_work);
+
 		if ((pipeb_stats & I915_LEGACY_BLC_EVENT_STATUS) ||
 		    (iir & I915_ASLE_INTERRUPT))
 			opregion_asle_intr(dev);
@@ -563,6 +608,7 @@ void i915_driver_irq_preinstall(struct drm_device * dev)
 	I915_WRITE(IER, 0x0);
 	(void) I915_READ(IER);
 	INIT_WORK(&dev_priv->hotplug_work, i915_hotplug_work_func);
+	INIT_WORK(&dev_priv->vblank_work, i915_vblank_work_func);
 }
 
 int i915_driver_irq_postinstall(struct drm_device *dev)
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index bdcda36..0178da5 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -662,6 +662,8 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y,
 	u32 dspcntr, alignment;
 	int ret;
 
+	BUG_ON(!mutex_is_locked(&dev->struct_mutex));
+
 	/* no fb bound */
 	if (!crtc->fb) {
 		DRM_DEBUG("No FB bound\n");
@@ -697,17 +699,14 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y,
 		BUG();
 	}
 
-	mutex_lock(&dev->struct_mutex);
 	ret = i915_gem_object_pin(intel_fb->obj, alignment);
 	if (ret != 0) {
-		mutex_unlock(&dev->struct_mutex);
 		return ret;
 	}
 
 	ret = i915_gem_object_set_to_gtt_domain(intel_fb->obj, 1);
 	if (ret != 0) {
 		i915_gem_object_unpin(intel_fb->obj);
-		mutex_unlock(&dev->struct_mutex);
 		return ret;
 	}
 
@@ -731,7 +730,6 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y,
 	default:
 		DRM_ERROR("Unknown color depth\n");
 		i915_gem_object_unpin(intel_fb->obj);
-		mutex_unlock(&dev->struct_mutex);
 		return -EINVAL;
 	}
 	if (IS_I965G(dev)) {
@@ -765,7 +763,6 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y,
 		intel_fb = to_intel_framebuffer(old_fb);
 		i915_gem_object_unpin(intel_fb->obj);
 	}
-	mutex_unlock(&dev->struct_mutex);
 
 	if (!dev->primary->master)
 		return 0;
@@ -1314,7 +1311,9 @@ static int intel_crtc_mode_set(struct drm_crtc *crtc,
 	I915_WRITE(dspcntr_reg, dspcntr);
 
 	/* Flush the plane changes */
+	mutex_lock(&dev->struct_mutex);
 	ret = intel_pipe_set_base(crtc, x, y, old_fb);
+	mutex_unlock(&dev->struct_mutex);
 	if (ret != 0)
 	    return ret;
 
diff --git a/include/drm/i915_drm.h b/include/drm/i915_drm.h
index 95962fa..8e7f389 100644
--- a/include/drm/i915_drm.h
+++ b/include/drm/i915_drm.h
@@ -184,6 +184,7 @@ typedef struct _drm_i915_sarea {
 #define DRM_I915_GEM_GET_TILING	0x22
 #define DRM_I915_GEM_GET_APERTURE 0x23
 #define DRM_I915_GEM_MMAP_GTT	0x24
+#define DRM_I915_GEM_PAGE_FLIP	0x25
 
 #define DRM_IOCTL_I915_INIT		DRM_IOW( DRM_COMMAND_BASE + DRM_I915_INIT, drm_i915_init_t)
 #define DRM_IOCTL_I915_FLUSH		DRM_IO ( DRM_COMMAND_BASE + DRM_I915_FLUSH)
@@ -219,6 +220,7 @@ typedef struct _drm_i915_sarea {
 #define DRM_IOCTL_I915_GEM_SET_TILING	DRM_IOWR (DRM_COMMAND_BASE + DRM_I915_GEM_SET_TILING, struct drm_i915_gem_set_tiling)
 #define DRM_IOCTL_I915_GEM_GET_TILING	DRM_IOWR (DRM_COMMAND_BASE + DRM_I915_GEM_GET_TILING, struct drm_i915_gem_get_tiling)
 #define DRM_IOCTL_I915_GEM_GET_APERTURE	DRM_IOR  (DRM_COMMAND_BASE + DRM_I915_GEM_GET_APERTURE, struct drm_i915_gem_get_aperture)
+#define DRM_IOCTL_I915_GEM_PAGE_FLIP	DRM_IOW (DRM_COMMAND_BASE + DRM_I915_GEM_PAGE_FLIP, struct drm_i915_gem_page_flip)
 
 /* Allow drivers to submit batchbuffers directly to hardware, relying
  * on the security mechanisms provided by hardware.
@@ -657,4 +659,29 @@ struct drm_i915_gem_get_aperture {
 	__u64 aper_available_size;
 };
 
+#define I915_PAGE_FLIP_WAIT (1<<0) /* block on page flip completion */
+#define I915_PAGE_FLIP_ASYNC (1<<1) /* flip immediately */
+#define I915_PAGE_FLIP_FLAGS_MASK (I915_PAGE_FLIP_WAIT|I915_PAGE_FLIP_ASYNC)
+
+struct drm_i915_gem_page_flip {
+	/** Handle of new front buffer */
+	uint32_t fb_id;
+	/**
+	 * crtcs to flip
+	 */
+	uint32_t crtc_count;
+	uint32_t *crtc_ids;
+
+	/**
+	 * interval of the flip, i.e. this flip won't complete until
+	 * @interval frames have occurred
+	 */
+	uint32_t interval;
+
+	/**
+	 * page flip flags (wait on flip only for now)
+	 */
+	uint32_t flags;
+};
+
 #endif				/* _I915_DRM_H_ */
diff --git a/src/drmmode_display.c b/src/drmmode_display.c
index 15ffc29..20cc9bc 100644
--- a/src/drmmode_display.c
+++ b/src/drmmode_display.c
@@ -29,6 +29,8 @@
 #include "config.h"
 #endif
 
+#include <errno.h>
+
 #include "xorgVersion.h"
 
 #include "i830.h"
@@ -338,6 +340,7 @@ static PixmapPtr
 drmmode_crtc_shadow_create(xf86CrtcPtr crtc, void *data, int width, int height)
 {
 	ScrnInfoPtr pScrn = crtc->scrn;
+	I830Ptr     pI830 = I830PTR(pScrn);
 	drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
 	drmmode_ptr drmmode = drmmode_crtc->drmmode;
 	unsigned long rotate_pitch;
@@ -363,12 +366,16 @@ drmmode_crtc_shadow_create(xf86CrtcPtr crtc, void *data, int width, int height)
 	if (drmmode_crtc->rotate_bo)
 		i830_set_pixmap_bo(rotate_pixmap, drmmode_crtc->rotate_bo);
 
+	pI830->shadow_present = TRUE;
+
 	return rotate_pixmap;
 }
 
 static void
 drmmode_crtc_shadow_destroy(xf86CrtcPtr crtc, PixmapPtr rotate_pixmap, void *data)
 {
+	ScrnInfoPtr pScrn = crtc->scrn;
+	I830Ptr     pI830 = I830PTR(pScrn);
 	drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
 	drmmode_ptr drmmode = drmmode_crtc->drmmode;
 
@@ -385,6 +392,7 @@ drmmode_crtc_shadow_destroy(xf86CrtcPtr crtc, PixmapPtr rotate_pixmap, void *dat
 		dri_bo_unreference(drmmode_crtc->rotate_bo);
 		drmmode_crtc->rotate_bo = NULL;
 	}
+	pI830->shadow_present = FALSE;
 }
 
 static void
@@ -886,6 +894,64 @@ drmmode_xf86crtc_resize (ScrnInfoPtr scrn, int width, int height)
 	return FALSE;
 }
 
+Bool
+drmmode_do_pageflip(DrawablePtr pDraw, dri_bo *new_front, dri_bo *old_front)
+{
+    ScreenPtr pScreen = pDraw->pScreen;
+    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
+    I830Ptr pI830 = I830PTR(pScrn);
+    xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(pScrn);
+    drmmode_crtc_private_ptr drmmode_crtc = config->crtc[0]->driver_private;
+    drmmode_ptr drmmode = drmmode_crtc->drmmode;
+    unsigned int pitch = pScrn->displayWidth * pI830->cpp;
+    int i, old_fb_id;
+    int crtc_ids[2], crtc_count = 0;
+
+    /*
+     * Create a new handle for the back buffer
+     */
+    old_fb_id = drmmode->fb_id;
+    if (drmModeAddFB(drmmode->fd, pScrn->virtualX, pScrn->virtualY,
+		     pScrn->depth, pScrn->bitsPerPixel, pitch,
+		     new_front->handle, &drmmode->fb_id))
+	    goto error_out;
+
+    /* Queue flips on all enabled CRTCs */
+    /* FIXME: cloned configs? */
+    for (i = 0; i < config->num_crtc; i++) {
+	    xf86CrtcPtr crtc = config->crtc[i];
+
+	    if (!crtc->enabled)
+		    continue;
+
+	    drmmode_crtc = crtc->driver_private;
+	    crtc_ids[crtc_count++] = drmmode_crtc->mode_crtc->crtc_id;
+    }
+
+    if (drm_intel_flip_bufs(drmmode->fd, crtc_ids, crtc_count, drmmode->fb_id))
+	    goto error_undo;
+
+    dri_bo_pin(new_front, 0);
+    dri_bo_unpin(new_front);
+
+    pScrn->fbOffset = new_front->offset;
+    pI830->front_buffer->bo = new_front;
+    pI830->front_buffer->offset = new_front->offset;
+
+    drmModeRmFB(drmmode->fd, old_fb_id);
+
+    return TRUE;
+
+error_undo:
+    drmModeRmFB(drmmode->fd, drmmode->fb_id);
+    drmmode->fb_id = old_fb_id;
+
+error_out:
+    xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Page flip failed: %s\n",
+	       strerror(errno));
+    return FALSE;
+}
+
 static const xf86CrtcConfigFuncsRec drmmode_xf86crtc_config_funcs = {
 	drmmode_xf86crtc_resize
 };
@@ -893,8 +959,9 @@ static const xf86CrtcConfigFuncsRec drmmode_xf86crtc_config_funcs = {
 Bool drmmode_pre_init(ScrnInfoPtr pScrn, int fd, int cpp)
 {
 	xf86CrtcConfigPtr   xf86_config;
+	I830Ptr pI830 = I830PTR(pScrn);
 	drmmode_ptr drmmode;
-	int i;
+	int i, bad_crtc = -1;
 
 	drmmode = xnfalloc(sizeof *drmmode);
 	drmmode->fd = fd;
@@ -918,5 +985,13 @@ Bool drmmode_pre_init(ScrnInfoPtr pScrn, int fd, int cpp)
 
 	xf86InitialConfiguration(pScrn, TRUE);
 
+	/* Check for swapbuffers support */
+	drm_intel_flip_bufs(drmmode->fd, &bad_crtc, 1, -1);
+	if (errno != -EBADF) { /* bad CRTC or FB number */
+		xf86DrvMsg(pScrn->scrnIndex, X_INFO,
+			   "Kernel page flipping support detected, enabling\n");
+		pI830->use_swap_buffers = TRUE;
+	}
+
 	return TRUE;
 }
diff --git a/src/i830.h b/src/i830.h
index 207d4ec..0d6ce5a 100644
--- a/src/i830.h
+++ b/src/i830.h
@@ -65,6 +65,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 #include "sarea.h"
 #define _XF86DRI_SERVER_
 #include "dri.h"
+#include "dri2.h"
 #include "GL/glxint.h"
 #include "i830_dri.h"
 #include "intel_bufmgr.h"
@@ -397,6 +398,7 @@ typedef struct _I830Rec {
 #endif
 
    XF86ModReqInfo shadowReq; /* to test for later libshadow */
+   Bool shadow_present;
    Rotation rotation;
    void (*PointerMoved)(int, int, int);
    CreateScreenResourcesProcPtr    CreateScreenResources;
@@ -489,6 +491,8 @@ typedef struct _I830Rec {
    int drmSubFD;
    char deviceName[64];
 
+   Bool use_swap_buffers;
+
    /* Broken-out options. */
    OptionInfoPtr Options;
 
@@ -678,6 +682,8 @@ Bool I830DRI2ScreenInit(ScreenPtr pScreen);
 void I830DRI2CloseScreen(ScreenPtr pScreen);
 
 extern Bool drmmode_pre_init(ScrnInfoPtr pScrn, int fd, int cpp);
+extern Bool drmmode_do_pageflip(DrawablePtr pDraw, dri_bo *new_front,
+				dri_bo *old_front);
 
 extern Bool I830AccelInit(ScreenPtr pScreen);
 extern void I830SetupForScreenToScreenCopy(ScrnInfoPtr pScrn, int xdir,
diff --git a/src/i830_dri.c b/src/i830_dri.c
index 0648249..32201f1 100644
--- a/src/i830_dri.c
+++ b/src/i830_dri.c
@@ -45,6 +45,8 @@ USE OR OTHER DEALINGS IN THE SOFTWARE.
 #include <errno.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <sys/time.h>
+#include <time.h>
 
 #include "xf86.h"
 #include "xf86_OSproc.h"
@@ -70,13 +72,12 @@ USE OR OTHER DEALINGS IN THE SOFTWARE.
 #define USE_DRI2_1_1_0
 #endif
 
-extern XF86ModuleData dri2ModuleData;
+#if DRI2INFOREC_VERSION >= 2
+#define USE_DRI2_1_2_0
 #endif
 
-typedef struct {
-    PixmapPtr pPixmap;
-    unsigned int attachment;
-} I830DRI2BufferPrivateRec, *I830DRI2BufferPrivatePtr;
+extern XF86ModuleData dri2ModuleData;
+#endif
 
 #ifndef USE_DRI2_1_1_0
 static DRI2BufferPtr
@@ -259,7 +260,7 @@ I830DRI2DestroyBuffers(DrawablePtr pDraw, DRI2BufferPtr buffers, int count)
 
     if (buffers)
     {
-	xfree(buffers[0].driverPrivate);
+	xfree(buffers[i].driverPrivate);
 	xfree(buffers);
     }
 }
@@ -324,6 +325,127 @@ I830DRI2CopyRegion(DrawablePtr pDraw, RegionPtr pRegion,
 
 }
 
+/* Check various flip constraints (drawable parameters vs screen params) */
+static Bool
+i830_flip_ok(DrawablePtr pDraw)
+{
+    ScreenPtr pScreen = pDraw->pScreen;
+    ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
+    I830Ptr pI830 = I830PTR(pScrn);
+
+    if (pI830->shadow_present)
+	return FALSE;
+    if (pDraw->width != pScrn->virtualX)
+	return FALSE;
+    if (pDraw->height != pScrn->virtualY)
+	return FALSE;
+    if (pDraw->depth != pScrn->depth)
+	return FALSE;
+
+    return TRUE;
+}
+
+/*
+ * DRI2SwapBuffers should try to do a buffer swap if possible, however:
+ *   - if we're swapping buffers smaller than the screen, we have to blit
+ *   - if the back buffer doesn't match the screen depth, we have to blit
+ *   - otherwise we try to swap, and return to the caller the new front
+ *     and back buffers
+ */
+static DRI2BufferPtr *
+I830DRI2SwapBuffers(DrawablePtr pDraw, DRI2BufferPtr *buffers, int count)
+{
+    DRI2BufferPtr new_buffer, *new_buffer_ptrs;
+    I830DRI2BufferPrivatePtr private, old_priv;
+    PixmapPtr pPixmap, new_front_pixmap, old_front_pixmap;
+    int i, j, front_buffer, back_buffer, tmp;
+    dri_bo *bo, *new_front_bo, *old_front_bo;
+
+    if (!i830_flip_ok(pDraw))
+	return NULL;
+
+    new_buffer_ptrs = xcalloc(count, sizeof(DRI2BufferRec *));
+    if (new_buffer_ptrs == NULL)
+	return NULL;
+
+    for (i = 0; i < count; i++) {
+	old_priv = buffers[i]->driverPrivate;
+	pPixmap = old_priv->pPixmap;
+	pPixmap->refcnt++;
+
+	new_buffer = xcalloc(1, sizeof(DRI2BufferRec));
+	if (!new_buffer) {
+	    for (j = 0; j < i; j++) {
+		xfree(new_buffer_ptrs[j]->driverPrivate);
+		xfree(new_buffer_ptrs[j]);
+	    }
+	    return NULL;
+	}
+
+	private = xcalloc(1, sizeof *private);
+	if (!private) {
+	    for (j = 0; j < i; j++) {
+		xfree(new_buffer_ptrs[j]->driverPrivate);
+		xfree(new_buffer_ptrs[j]);
+	    }
+	    return NULL;
+	}
+
+
+	private->pPixmap = pPixmap;
+	private->attachment = buffers[i]->attachment;
+
+	new_buffer->attachment = buffers[i]->attachment;
+	new_buffer->pitch = buffers[i]->pitch;
+	new_buffer->cpp = buffers[i]->cpp;
+	new_buffer->driverPrivate = private;
+	new_buffer->name = buffers[i]->name;
+
+	bo = i830_get_pixmap_bo(pPixmap);
+
+	new_buffer_ptrs[i] = new_buffer;
+
+	/* Swap the attachment types */
+	if (buffers[i]->attachment == DRI2BufferFrontLeft) {
+	    old_front_bo = bo;
+	    old_front_pixmap = pPixmap;
+	    front_buffer = i;
+	}
+	if (buffers[i]->attachment == DRI2BufferBackLeft) {
+	    new_front_bo = bo;
+	    new_front_pixmap = pPixmap;
+	    back_buffer = i;
+	    drm_intel_bo_reference(bo);
+	}
+    }
+
+    /* Swap BO names so DRI works */
+    tmp = new_buffer_ptrs[front_buffer]->name;
+    new_buffer_ptrs[front_buffer]->name = new_buffer_ptrs[back_buffer]->name;
+    new_buffer_ptrs[back_buffer]->name = tmp;
+
+    /* Swap pixmaps */
+    private = new_buffer_ptrs[back_buffer]->driverPrivate;
+    i830_set_pixmap_bo(private->pPixmap, old_front_bo);
+
+    private = new_buffer_ptrs[front_buffer]->driverPrivate;
+    i830_set_pixmap_bo(private->pPixmap, new_front_bo); /* should be screen */
+
+    /* Needed to hold refs around the set_bo calls... */
+    drm_intel_bo_unreference(old_front_bo);
+
+    /* Page flip the full screen buffer */
+    if (!drmmode_do_pageflip(pDraw, new_front_bo, old_front_bo)) {
+	for (i = 0; i < count; i++) {
+	    xfree(new_buffer_ptrs[i]->driverPrivate);
+	    xfree(new_buffer_ptrs[i]);
+	}
+	return NULL;
+    }
+
+    return new_buffer_ptrs;
+}
+
 Bool I830DRI2ScreenInit(ScreenPtr pScreen)
 {
     ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
@@ -423,6 +545,13 @@ Bool I830DRI2ScreenInit(ScreenPtr pScreen)
 
     info.CopyRegion = I830DRI2CopyRegion;
 
+#ifdef USE_DRI2_1_2_0
+    if (pI830->use_swap_buffers) {
+	info.version = 3;
+	info.SwapBuffers = I830DRI2SwapBuffers;
+    }
+#endif
+
     pI830->drmSubFD = info.fd;
 
     return DRI2ScreenInit(pScreen, &info);
diff --git a/src/i830_dri.h b/src/i830_dri.h
index bedbcbe..e35e940 100644
--- a/src/i830_dri.h
+++ b/src/i830_dri.h
@@ -2,6 +2,7 @@
 #ifndef _I830_DRI_H
 #define _I830_DRI_H
 
+#include "xorg-server.h"
 #include "xf86drm.h"
 #include "i830_common.h"
 
@@ -58,4 +59,9 @@ typedef struct {
    int dummy;
 } I830DRIContextRec, *I830DRIContextPtr;
 
+typedef struct {
+    PixmapPtr pPixmap;
+    unsigned int attachment;
+} I830DRI2BufferPrivateRec, *I830DRI2BufferPrivatePtr;
+
 #endif
------------------------------------------------------------------------------
Register Now & Save for Velocity, the Web Performance & Operations 
Conference from O'Reilly Media. Velocity features a full day of 
expert-led, hands-on workshops and two days of sessions from industry 
leaders in dedicated Performance & Operations tracks. Use code vel09scf 
and Save an extra 15% before 5/3. http://p.sf.net/sfu/velocityconf
--
_______________________________________________
Dri-devel mailing list
Dri-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/dri-devel

Reply via email to