[Intel-gfx] [PATCH] i915: page flipping support

Jesse Barnes jbarnes at virtuousgeek.org
Wed Feb 25 21:30:14 CET 2009


This one still doesn't include a KMS version, but the rest of it should be
nearly ready.  I've taken Chris & Nanhai's feedback from the last round and
incorporated it, so we'll no longer block with the struct_mutex held at
execbuffer time if a flip is pending, and the ring command submission has
been pulled out of the spinlock, since that could have been bad (though it
does introduce a harmless race).

The basic idea is the same: a client calls the flip ioctl with a buffer object
it would like to use as the new front buffer (it's up to the client to make
sure any future drawing points at the right buffer; either the new front
buffer in the case of X rendering, or the new back buffer in the case of new
GL rendering from that client).  Also in the non-KMS case, the client has to
ensure that the new buffer is pinned, and that the old one is in the right
mode for whatever is expected to happen with it following the flip.

diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c
index 2d797ff..8868ad2 100644
--- a/drivers/gpu/drm/i915/i915_dma.c
+++ b/drivers/gpu/drm/i915/i915_dma.c
@@ -829,6 +829,172 @@ static int i915_set_status_page(struct drm_device *dev, void *data,
 	return 0;
 }
 
+static int i915_pipe_to_plane(struct drm_device *dev, int pipe)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	u32 reg, pipe_mask;
+
+	if (pipe == 0)
+		pipe_mask = DISPPLANE_SEL_PIPE_A;
+	else
+		pipe_mask = DISPPLANE_SEL_PIPE_B;
+
+	pipe_mask |= DISPLAY_PLANE_ENABLE;
+
+	reg = I915_READ(DSPACNTR);
+	if ((reg & (DISPLAY_PLANE_ENABLE | DISPPLANE_SEL_PIPE_MASK)) ==
+	    pipe_mask)
+		return 0;
+
+	reg = I915_READ(DSPBCNTR);
+	if ((reg & (DISPLAY_PLANE_ENABLE | DISPPLANE_SEL_PIPE_MASK)) ==
+	    pipe_mask)
+		return 1;
+
+	return -1;
+}
+
+bool
+i915_gem_flip_pending(struct drm_gem_object *obj)
+{
+	struct drm_device *dev = obj->dev;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	struct drm_i915_gem_object *obj_priv = obj->driver_private;
+	unsigned long flags;
+	bool pending = false;
+
+	spin_lock_irqsave(&dev_priv->vblank_lock, flags);
+	if (!list_empty(&obj_priv->vblank_head))
+		pending = true;
+	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_gem_object *obj;
+	struct drm_i915_gem_object *obj_priv;
+	unsigned long flags;
+	uint32_t offset;
+	unsigned int pitch, pipe, plane, tiled;
+	int ret = 0;
+	RING_LOCALS;
+
+	if (!(dev->driver->driver_features & DRIVER_GEM))
+		return -ENODEV;
+
+	/*
+	 * Reject unknown flags so future userspace knows what we (don't)
+	 * support
+	 */
+	if (flip_data->flags & (~I915_PAGE_FLIP_WAIT)) {
+		DRM_ERROR("bad page flip flags\n");
+		return -EINVAL;
+	}
+
+	if ((pipe = flip_data->pipe) > 1) {
+		DRM_ERROR("bad pipe\n");
+		return -EINVAL;
+	}
+
+	plane = i915_pipe_to_plane(dev, pipe);
+	if (plane < 0) {
+		DRM_ERROR("bad pipe (no planes enabled?)\n");
+		return -EINVAL;
+	}
+
+	obj = drm_gem_object_lookup(dev, file_priv, flip_data->handle);
+	if (obj == NULL)
+		return -EBADF;
+
+	/*
+	 * Make sure the new buffer is in bounds
+	 * FIXME: should probably check against current mode as well
+	 */
+	if (flip_data->offset >= obj->size) {
+		DRM_ERROR("bad flip offset\n");
+		ret = -EINVAL;
+		goto out_unref;
+	}
+
+	obj_priv = obj->driver_private;
+
+	if (i915_gem_flip_pending(obj))
+		wait_for_completion(&obj_priv->vblank);
+
+	mutex_lock(&dev->struct_mutex);
+	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_unref;
+	}
+
+#if 0
+	ret = i915_gem_object_pin(obj, 0);
+	if (ret) {
+		DRM_ERROR("failed to pin object for flip\n");
+		ret = -EBUSY;
+		goto out_unref;
+	}
+#endif
+	/*
+	 * Put the object in the GTT domain before the flip,
+	 * since there may be outstanding rendering
+	 */
+	i915_gem_object_set_to_gtt_domain(obj, 0);
+
+	offset = obj_priv->gtt_offset + flip_data->offset;
+
+	pitch = obj_priv->stride;
+	tiled = !!(obj_priv->tiling_mode == I915_TILING_X);
+
+	BEGIN_LP_RING(4);
+	OUT_RING(CMD_OP_DISPLAYBUFFER_INFO | (plane << 20));
+	OUT_RING(pitch);
+	if (IS_I965G(dev)) {
+		OUT_RING(offset | tiled);
+		OUT_RING(((flip_data->x - 1) << 16) | (flip_data->y - 1));
+	} else {
+		OUT_RING(offset);
+		OUT_RING(MI_NOOP);
+	}
+	ADVANCE_LP_RING();
+
+	mutex_unlock(&dev->struct_mutex);
+
+	/*
+	 * This is a bit racy; the flip above may have already happened
+	 * by the time we get here.  If that happens, the new back buffer
+	 * won't be available for rendering for one extra frame, since
+	 * the vblank list won't have the object, and its seqno will
+	 * be later.
+	 */
+	spin_lock_irqsave(&dev_priv->vblank_lock, flags);
+	ret = drm_vblank_get(dev, pipe);
+	if (!ret)
+		list_add_tail(&obj_priv->vblank_head,
+			      &dev_priv->mm.vblank_list[pipe]);
+
+	obj_priv->flip_seqno = i915_add_request(dev, 0);
+	spin_unlock_irqrestore(&dev_priv->vblank_lock, flags);
+
+	if (!ret && (flip_data->flags & I915_PAGE_FLIP_WAIT))
+		wait_for_completion(&obj_priv->vblank);
+
+	/* Take lock again for unref */
+	mutex_lock(&dev->struct_mutex);
+out_unref:
+	drm_gem_object_unreference(obj);
+	mutex_unlock(&dev->struct_mutex);
+
+	return ret;
+}
+
 /**
  * i915_probe_agp - get AGP bootup configuration
  * @pdev: PCI device
@@ -1307,6 +1473,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 3951a12..c44f29c 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -159,6 +159,9 @@ typedef struct drm_i915_private {
 	u32 irq_mask_reg;
 	u32 pipestat[2];
 
+	/** Protects vblank list */
+	spinlock_t vblank_lock;
+
 	int tex_lru_log_granularity;
 	int allow_batchbuffer;
 	struct mem_block *agp_heap;
@@ -338,6 +341,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;
 
 		/**
@@ -389,6 +397,13 @@ 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 list (protected by vblank_lock)*/
+	struct list_head vblank_head;
+	/** sequence number for flip (when it passes the flip is done),
+	 *  protected by vblank lock
+	 */
+	int flip_seqno;
+
 	/**
 	 * 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
@@ -457,6 +472,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;
 };
 
 /**
@@ -516,6 +534,7 @@ extern long i915_compat_ioctl(struct file *filp, unsigned int cmd,
 extern int i915_emit_box(struct drm_device *dev,
 			 struct drm_clip_rect __user *boxes,
 			 int i, int DR1, int DR4);
+extern bool i915_gem_flip_pending(struct drm_gem_object *obj);
 
 /* i915_irq.c */
 extern int i915_irq_emit(struct drm_device *dev, void *data,
@@ -625,6 +644,9 @@ int i915_gem_attach_phys_object(struct drm_device *dev,
 void i915_gem_detach_phys_object(struct drm_device *dev,
 				 struct drm_gem_object *obj);
 void i915_gem_free_all_phys_object(struct drm_device *dev);
+uint32_t i915_add_request(struct drm_device *dev, uint32_t flush_domains);
+int i915_seqno_passed(uint32_t seq1, uint32_t seq2);
+uint32_t i915_get_gem_seqno(struct drm_device *dev);
 
 /* i915_gem_tiling.c */
 void i915_gem_detect_bit_6_swizzle(struct drm_device *dev);
diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c
index 3e025d5..cafaa20 100644
--- a/drivers/gpu/drm/i915/i915_gem.c
+++ b/drivers/gpu/drm/i915/i915_gem.c
@@ -900,7 +900,7 @@ i915_gem_object_move_to_inactive(struct drm_gem_object *obj)
  *
  * Returned sequence numbers are nonzero on success.
  */
-static uint32_t
+uint32_t
 i915_add_request(struct drm_device *dev, uint32_t flush_domains)
 {
 	drm_i915_private_t *dev_priv = dev->dev_private;
@@ -1028,7 +1028,7 @@ i915_gem_retire_request(struct drm_device *dev,
 /**
  * Returns true if seq1 is later than seq2.
  */
-static int
+int
 i915_seqno_passed(uint32_t seq1, uint32_t seq2)
 {
 	return (int32_t)(seq1 - seq2) >= 0;
@@ -2498,6 +2498,25 @@ i915_gem_execbuffer(struct drm_device *dev, void *data,
 		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;
+			goto pre_mutex_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__);
@@ -2516,18 +2535,6 @@ i915_gem_execbuffer(struct drm_device *dev, void *data,
 		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;
-			goto err;
-		}
-	}
-
 	/* Pin and relocate */
 	for (pin_tries = 0; ; pin_tries++) {
 		ret = 0;
@@ -2920,6 +2927,9 @@ 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);
+
+	init_completion(&obj_priv->vblank);
 
 	return 0;
 }
@@ -3358,9 +3368,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 548ff2c..5fece37 100644
--- a/drivers/gpu/drm/i915/i915_irq.c
+++ b/drivers/gpu/drm/i915/i915_irq.c
@@ -187,6 +187,25 @@ u32 gm45_get_vblank_counter(struct drm_device *dev, int pipe)
 	return I915_READ(reg);
 }
 
+static void i915_complete_vblank(struct drm_device *dev, int pipe)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	struct drm_i915_gem_object *obj_priv, *tmp;
+	unsigned long irqflags;
+
+	spin_lock_irqsave(&dev_priv->vblank_lock, irqflags);
+	list_for_each_entry_safe(obj_priv, tmp, &dev_priv->mm.vblank_list[pipe],
+				 vblank_head) {
+		int cur_seqno = i915_get_gem_seqno(dev);
+		if (i915_seqno_passed(cur_seqno, obj_priv->flip_seqno)) {
+			complete(&obj_priv->vblank);
+			list_del_init(&obj_priv->vblank_head);
+			drm_vblank_put(dev, pipe);
+		}
+	}
+	spin_unlock_irqrestore(&dev_priv->vblank_lock, irqflags);
+}
+
 irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
 {
 	struct drm_device *dev = (struct drm_device *) arg;
@@ -261,11 +280,13 @@ irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
 
 		if (pipea_stats & vblank_status) {
 			vblank++;
+			i915_complete_vblank(dev, 0);
 			drm_handle_vblank(dev, 0);
 		}
 
 		if (pipeb_stats & vblank_status) {
 			vblank++;
+			i915_complete_vblank(dev, 1);
 			drm_handle_vblank(dev, 1);
 		}
 
diff --git a/include/drm/i915_drm.h b/include/drm/i915_drm.h
index 912cd52..1ac4ded 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.
@@ -654,4 +656,30 @@ struct drm_i915_gem_get_aperture {
 	uint64_t aper_available_size;
 };
 
+#define I915_PAGE_FLIP_WAIT (1<<0) /* block on page flip completion */
+
+struct drm_i915_gem_page_flip {
+	/** Handle of new front buffer */
+	uint32_t handle;
+
+	/** Offset into the object to use */
+	uint64_t offset;
+
+	/**
+	 * page flip flags (wait on flip only for now)
+	 */
+	uint32_t flags;
+
+	/**
+	 * pipe to flip
+	 */
+	uint32_t pipe;
+
+	/**
+	 * screen dimensions for flip
+	 */
+	uint32_t x;
+	uint32_t y;
+};
+
 #endif				/* _I915_DRM_H_ */




More information about the Intel-gfx mailing list