From: Kristian H. <kr...@bi...> - 2009-05-08 21:06:40
|
From: Kristian Høgsberg <kr...@re...> This patch adds a vblank synced pageflip ioctl for to the modesetting family of ioctls. The ioctl takes a crtc and an fb and schedules a pageflip to the new fb at the next coming vertical blank event. This feature lets userspace implement tear-free updating of the screen contents with hw-guaranteed low latency page flipping. The ioctl is asynchronous in that it returns immediately and then later notifies the client by making an event available for reading on the drm fd. This lets applications add the drm fd to their main loop and handle other tasks while waiting for the flip to happen. The event includes the time of the flip, the frame counter and a 64 bit opaque token provided by user space in the ioctl. Based on initial work and suggestions from Jesse Barnes <jb...@vi...> and Jakob Bornecrantz <wal...@gm...>. Signed-off-by: Kristian Høgsberg <kr...@re...> --- drivers/gpu/drm/drm_crtc.c | 3 +- drivers/gpu/drm/drm_crtc_helper.c | 123 ++++++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_drv.c | 1 + drivers/gpu/drm/drm_fops.c | 72 +++++++++++++++++++- drivers/gpu/drm/drm_irq.c | 44 ++++++++++++ drivers/gpu/drm/i915/i915_drv.c | 1 + drivers/gpu/drm/i915/intel_display.c | 22 ++++-- include/drm/drm.h | 25 +++++++ include/drm/drmP.h | 30 ++++++++ include/drm/drm_crtc.h | 5 ++ include/drm/drm_crtc_helper.h | 15 ++++- include/drm/drm_mode.h | 16 +++++ 12 files changed, 345 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 94a7688..4c39350 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -352,11 +352,12 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup); * * Inits a new object created as base part of an driver crtc object. */ -void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, +void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, const struct drm_crtc_funcs *funcs) { crtc->dev = dev; crtc->funcs = funcs; + crtc->pipe = pipe; mutex_lock(&dev->mode_config.mutex); drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC); diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index a04639d..d53417e 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; } @@ -1007,3 +1011,122 @@ int drm_helper_resume_force_mode(struct drm_device *dev) return 0; } EXPORT_SYMBOL(drm_helper_resume_force_mode); + +/** + * drm_mode_page_flip - page flip ioctl + * @dev: DRM device + * @data: ioctl args + * @file_priv: file private data + * + * The page flip ioctl replaces the current front buffer with a new one, + * using the CRTC's mode_set_base function, which should just update + * the front buffer base pointer. It's up to mode_set_base to make + * sure the update doesn't result in tearing (on some hardware the base + * register is double buffered, so this is easy). + * + * Note that this covers just the simple case of flipping the front + * buffer immediately. Interval handling and interlaced modes have to + * be handled by userspace, or with new ioctls. + */ +int drm_mode_page_flip(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_pending_flip *pending; + struct drm_mode_page_flip *flip_data = data; + struct drm_mode_object *drm_obj, *fb_obj; + struct drm_crtc *crtc; + struct drm_crtc_helper_funcs *crtc_funcs; + int ret = 0; + + 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 & (~DRM_MODE_PAGE_FLIP_FLAGS_MASK)) { + DRM_DEBUG("bad page flip flags\n"); + return -EINVAL; + } + + pending = kzalloc(sizeof *pending, GFP_KERNEL); + if (pending == NULL) + return -ENOMEM; + + 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; + } + + drm_obj = drm_mode_object_find(dev, flip_data->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!drm_obj) { + DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_id); + ret = -ENOENT; + goto out_unlock; + } + crtc = obj_to_crtc(drm_obj); + if (!crtc->enabled) { + DRM_DEBUG("crtc %d not enabled\n", flip_data->crtc_id); + ret = -EINVAL; + goto out_unlock; + } + + crtc_funcs = crtc->helper_private; + if (crtc_funcs->mode_unpin_fb == NULL) { + DRM_DEBUG("crtc %d does not support delayed unpin\n", + flip_data->crtc_id); + ret = -ENODEV; + goto out_unlock; + } + + pending->crtc = crtc; + pending->old_fb = crtc->fb; + pending->pipe = crtc->pipe; + pending->event.base.type = DRM_EVENT_MODE_PAGE_FLIP; + pending->event.base.length = sizeof pending->event; + pending->event.base.user_data = flip_data->user_data; + pending->pending_event.event = &pending->event.base; + pending->pending_event.file_priv = file_priv; + pending->pending_event.destroy = + (void (*) (struct drm_pending_event *)) kfree; + + /* Get vblank ref for completion handling */ + ret = drm_vblank_get(dev, crtc->pipe); + if (ret) { + DRM_DEBUG("failed to take vblank ref\n"); + goto out_unlock; + } + + pending->frame = drm_vblank_count(dev, crtc->pipe); + list_add_tail(&pending->link, &dev->flip_list); + + /* + * The mode_set_base call will change the domain on the new + * fb, which will force the rendering to finish and block the + * ioctl. We need to do this last part from a work queue, to + * avoid blocking userspace here. + */ + crtc->fb = obj_to_fb(fb_obj); + ret = crtc_funcs->mode_set_base(crtc, 0, 0, NULL); + if (ret) { + DRM_ERROR("mode_set_base failed: %d\n", ret); + goto out_unlock; + } + + mutex_unlock(&dev->struct_mutex); + + return 0; + +out_unlock: + mutex_unlock(&dev->struct_mutex); + kfree(pending); + + return ret; +} diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index c4ada8b..352893c 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip, DRM_MASTER|DRM_CONTROL_ALLOW), }; #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 09a3571..07028b8 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -257,6 +257,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp, INIT_LIST_HEAD(&priv->lhead); INIT_LIST_HEAD(&priv->fbs); + INIT_LIST_HEAD(&priv->event_list); + init_waitqueue_head(&priv->event_wait); if (dev->driver->driver_features & DRIVER_GEM) drm_gem_open(dev, priv); @@ -429,6 +431,9 @@ int drm_release(struct inode *inode, struct file *filp) { struct drm_file *file_priv = filp->private_data; struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_flip *f, *ft; + struct drm_pending_event *e, *et; + int retcode = 0; lock_kernel(); @@ -451,6 +456,23 @@ int drm_release(struct inode *inode, struct file *filp) if (file_priv->minor->master) drm_master_release(dev, filp); + mutex_lock(&dev->struct_mutex); + + /* Remove pending flips */ + list_for_each_entry_safe(f, ft, &dev->flip_list, link) { + if (f->pending_event.file_priv == file_priv) { + drm_vblank_put(dev, f->pipe); + list_del(&f->link); + kfree(f); + } + } + + /* Remove unconsumed events */ + list_for_each_entry_safe(e, et, &file_priv->event_list, link) + kfree(e); + + mutex_unlock(&dev->struct_mutex); + if (dev->driver->driver_features & DRIVER_GEM) drm_gem_release(dev, file_priv); @@ -544,9 +566,55 @@ int drm_release(struct inode *inode, struct file *filp) } EXPORT_SYMBOL(drm_release); -/** No-op. */ +ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_event *event; + ssize_t total, ret; + + ret = wait_event_interruptible(file_priv->event_wait, + !list_empty(&file_priv->event_list)); + if (ret < 0) + return ret; + + total = 0; + while (!list_empty(&file_priv->event_list)) { + mutex_lock(&dev->struct_mutex); + event = list_first_entry(&file_priv->event_list, + struct drm_pending_event, link); + if (total + event->event->length > count) { + mutex_unlock(&dev->struct_mutex); + break; + } + list_del(&event->link); + mutex_unlock(&dev->struct_mutex); + + if (copy_to_user(buffer + total, + event->event, event->event->length)) { + total = -EFAULT; + break; + } + + total += event->event->length; + event->destroy(event); + } + + return total; +} +EXPORT_SYMBOL(drm_read); + unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) { - return 0; + struct drm_file *file_priv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &file_priv->event_wait, wait); + + if (!list_empty(&file_priv->event_list)) + mask |= POLLIN | POLLRDNORM; + + return mask; } EXPORT_SYMBOL(drm_poll); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 93e677a..eb66110 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -34,6 +34,7 @@ */ #include "drmP.h" +#include "drm_crtc_helper.h" #include <linux/interrupt.h> /* For task queue support */ @@ -71,6 +72,45 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, return 0; } +#define vblank_after(a,b) ((long)(b) - (long)(a) < 0) + +static void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame) +{ + struct drm_crtc_helper_funcs *crtc_funcs; + struct timeval now; + + f->event.frame = frame; + do_gettimeofday(&now); + f->event.tv_sec = now.tv_sec; + f->event.tv_usec = now.tv_usec; + drm_vblank_put(dev, f->pipe); + list_del_init(&f->link); + list_add_tail(&f->pending_event.link, + &f->pending_event.file_priv->event_list); + crtc_funcs = f->crtc->helper_private; + crtc_funcs->mode_unpin_fb(f->crtc, f->old_fb); + wake_up_interruptible(&f->pending_event.file_priv->event_wait); +} + +static void drm_flip_work_func(struct work_struct *work) +{ + struct drm_device *dev = + container_of(work, struct drm_device, flip_work); + struct drm_pending_flip *f, *t; + u32 frame; + + mutex_lock(&dev->struct_mutex); + + list_for_each_entry_safe(f, t, &dev->flip_list, link) { + frame = drm_vblank_count(dev, f->pipe); + if (vblank_after(frame, f->frame)) + drm_finish_pending_flip(dev, f, frame); + } + + mutex_unlock(&dev->struct_mutex); +} + static void vblank_disable_fn(unsigned long arg) { struct drm_device *dev = (struct drm_device *)arg; @@ -173,6 +213,8 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) atomic_set(&dev->vblank_refcount[i], 0); } + INIT_LIST_HEAD(&dev->flip_list); + INIT_WORK(&dev->flip_work, drm_flip_work_func); dev->vblank_disable_allowed = 0; return 0; @@ -632,5 +674,7 @@ void drm_handle_vblank(struct drm_device *dev, int crtc) { atomic_inc(&dev->_vblank_count[crtc]); DRM_WAKEUP(&dev->vbl_queue[crtc]); + schedule_work(&dev->flip_work); } EXPORT_SYMBOL(drm_handle_vblank); + diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 98560e1..85dde09 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -198,6 +198,7 @@ static struct drm_driver driver = { .mmap = drm_gem_mmap, .poll = drm_poll, .fasync = drm_fasync, + .read = drm_read, #ifdef CONFIG_COMPAT .compat_ioctl = i915_compat_ioctl, #endif diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index bdcda36..ba53acb 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)) { @@ -759,13 +757,11 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, I915_READ(dspbase); } - intel_wait_for_vblank(dev); - if (old_fb) { intel_fb = to_intel_framebuffer(old_fb); + intel_wait_for_vblank(dev); i915_gem_object_unpin(intel_fb->obj); } - mutex_unlock(&dev->struct_mutex); if (!dev->primary->master) return 0; @@ -785,7 +781,14 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, return 0; } +static void +intel_pipe_unpin_fb(struct drm_crtc *crtc, struct drm_framebuffer *old_fb) +{ + struct intel_framebuffer *intel_fb; + intel_fb = to_intel_framebuffer(old_fb); + i915_gem_object_unpin(intel_fb->obj); +} /** * Sets the power management mode of the pipe and plane. @@ -1314,7 +1317,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; @@ -1757,6 +1762,7 @@ static const struct drm_crtc_helper_funcs intel_helper_funcs = { .mode_fixup = intel_crtc_mode_fixup, .mode_set = intel_crtc_mode_set, .mode_set_base = intel_pipe_set_base, + .mode_unpin_fb = intel_pipe_unpin_fb, .prepare = intel_crtc_prepare, .commit = intel_crtc_commit, }; @@ -1779,7 +1785,7 @@ static void intel_crtc_init(struct drm_device *dev, int pipe) if (intel_crtc == NULL) return; - drm_crtc_init(dev, &intel_crtc->base, &intel_crtc_funcs); + drm_crtc_init(dev, &intel_crtc->base, pipe, &intel_crtc_funcs); drm_mode_crtc_set_gamma_size(&intel_crtc->base, 256); intel_crtc->pipe = pipe; diff --git a/include/drm/drm.h b/include/drm/drm.h index 7cb50bd..e385fe2 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -686,6 +686,7 @@ struct drm_gem_open { #define DRM_IOCTL_MODE_GETFB DRM_IOWR(0xAD, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) +#define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOW( 0xB0, struct drm_mode_page_flip) /** * Device specific ioctls should only be in their respective headers @@ -698,6 +699,30 @@ struct drm_gem_open { #define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_END 0xA0 +/** + * Header for events written back to userspace on the drm fd. The + * type defines the type of event, the length specifies the total + * length of the event (including the header), and user_data is + * typically a 64 bit value passed with the ioctl that triggered the + * event. A read on the drm fd will always only return complete + * events, that is, if for example the read buffer is 100 bytes, and + * there are two 64 byte events pending, only one will be returned. + */ +struct drm_event { + __u32 type; + __u32 length; + __u64 user_data; +}; + +#define DRM_EVENT_MODE_PAGE_FLIP 0x01 + +struct drm_event_page_flip { + struct drm_event base; + __u32 tv_sec; + __u32 tv_usec; + __u32 frame; +}; + /* typedef area */ #ifndef __KERNEL__ typedef struct drm_clip_rect drm_clip_rect_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index c8c4221..47dcdb9 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -378,6 +378,14 @@ struct drm_buf_entry { struct drm_freelist freelist; }; +/* Event queued up for userspace to read */ +struct drm_pending_event { + struct drm_event *event; + struct list_head link; + struct drm_file *file_priv; + void (*destroy) (struct drm_pending_event *event); +}; + /** File private data */ struct drm_file { int authenticated; @@ -401,6 +409,9 @@ struct drm_file { struct drm_master *master; /* master this node is currently associated with N.B. not always minor->master */ struct list_head fbs; + + wait_queue_head_t event_wait; + struct list_head event_list; }; /** Wait queue */ @@ -869,6 +880,16 @@ struct drm_minor { struct drm_mode_group mode_group; }; +struct drm_pending_flip { + struct drm_pending_event pending_event; + struct drm_framebuffer *old_fb; + struct drm_crtc *crtc; + u32 frame; + int pipe; + struct list_head link; + struct drm_event_page_flip event; +}; + /** * DRM device structure. This structure represent a complete card that * may contain multiple heads. @@ -968,6 +989,13 @@ struct drm_device { u32 max_vblank_count; /**< size of vblank counter register */ + struct work_struct flip_work; + + /** + * List of objects waiting on flip completion + */ + struct list_head flip_list; + /*@} */ cycles_t ctx_start; cycles_t lck_start; @@ -1104,6 +1132,8 @@ extern int drm_lastclose(struct drm_device *dev); extern int drm_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_fasync(int fd, struct file *filp, int on); +extern ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset); extern int drm_release(struct inode *inode, struct file *filp); /* Mapping support (drm_vm.h) */ diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 3c1924c..5c5c10f 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -336,6 +336,7 @@ struct drm_crtc_funcs { /** * drm_crtc - central CRTC control structure * @enabled: is this CRTC enabled? + * @pipe: pipe number (as seen by DRM vblank functions) * @x: x position on screen * @y: y position on screen * @desired_mode: new desired mode @@ -359,6 +360,7 @@ struct drm_crtc { struct drm_display_mode mode; + int pipe; int x, y; struct drm_display_mode *desired_mode; int desired_x, desired_y; @@ -586,6 +588,7 @@ struct drm_mode_config { extern void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, + int pipe, const struct drm_crtc_funcs *funcs); extern void drm_crtc_cleanup(struct drm_crtc *crtc); @@ -733,4 +736,6 @@ extern int drm_mode_gamma_get_ioctl(struct drm_device *dev, extern int drm_mode_gamma_set_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern bool drm_detect_hdmi_monitor(struct edid *edid); +extern int drm_mode_page_flip(struct drm_device *dev, void *data, + struct drm_file *file_priv); #endif /* __DRM_CRTC_H__ */ diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h index ec073d8..a0ae240 100644 --- a/include/drm/drm_crtc_helper.h +++ b/include/drm/drm_crtc_helper.h @@ -57,9 +57,22 @@ struct drm_crtc_helper_funcs { struct drm_display_mode *adjusted_mode, int x, int y, struct drm_framebuffer *old_fb); - /* Move the crtc on the current fb to the given position *optional* */ + /* + * Move the crtc on the current fb to the given position. + * This function is optional. If old_fb is provided, the + * function will wait for vblank and unpin it. If old_fb is + * NULL, nothing is unpinned and the caller must call + * mode_unpin_fb to release the old framebuffer. + */ int (*mode_set_base)(struct drm_crtc *crtc, int x, int y, struct drm_framebuffer *old_fb); + + /* + * Unpin the old fb after setting a mode. Must be called + * after the old framebuffer is no longer visible, ie, after + * the next vblank, typically. + */ + void (*mode_unpin_fb)(struct drm_crtc *crtc, struct drm_framebuffer *fb); }; struct drm_encoder_helper_funcs { diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index ae304cc..464b779 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -265,4 +265,20 @@ struct drm_mode_crtc_lut { __u64 blue; }; +#define DRM_MODE_PAGE_FLIP_WAIT (1<<0) /* block on previous page flip */ +#define DRM_MODE_PAGE_FLIP_FLAGS_MASK (DRM_MODE_PAGE_FLIP_WAIT) + +struct drm_mode_page_flip { + /** Handle of new front buffer */ + __u32 fb_id; + __u32 crtc_id; + + /* 64 bit cookie returned to userspace in the page flip event. */ + __u64 user_data; + /** + * page flip flags (wait on flip only for now) + */ + __u32 flags; +}; + #endif -- 1.6.2 |
From: Kristian H. <kr...@bi...> - 2009-05-11 02:58:07
|
From: Kristian Høgsberg <kr...@re...> This patch adds a vblank synced pageflip ioctl for to the modesetting family of ioctls. The ioctl takes a crtc and an fb and schedules a pageflip to the new fb at the next coming vertical blank event. This feature lets userspace implement tear-free updating of the screen contents with hw-guaranteed low latency page flipping. The ioctl is asynchronous in that it returns immediately and then later notifies the client by making an event available for reading on the drm fd. This lets applications add the drm fd to their main loop and handle other tasks while waiting for the flip to happen. The event includes the time of the flip, the frame counter and a 64 bit opaque token provided by user space in the ioctl. Based on initial work and suggestions from Jesse Barnes <jb...@vi...> and Jakob Bornecrantz <wal...@gm...>. Signed-off-by: Kristian Høgsberg <kr...@re...> --- drivers/gpu/drm/drm_crtc.c | 3 +- drivers/gpu/drm/drm_crtc_helper.c | 123 ++++++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_drv.c | 1 + drivers/gpu/drm/drm_fops.c | 72 +++++++++++++++++++- drivers/gpu/drm/drm_irq.c | 44 ++++++++++++ drivers/gpu/drm/i915/i915_drv.c | 1 + drivers/gpu/drm/i915/intel_display.c | 22 ++++-- include/drm/drm.h | 25 +++++++ include/drm/drmP.h | 30 ++++++++ include/drm/drm_crtc.h | 5 ++ include/drm/drm_crtc_helper.h | 15 ++++- include/drm/drm_mode.h | 16 +++++ 12 files changed, 345 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 94a7688..4c39350 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -352,11 +352,12 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup); * * Inits a new object created as base part of an driver crtc object. */ -void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, +void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, const struct drm_crtc_funcs *funcs) { crtc->dev = dev; crtc->funcs = funcs; + crtc->pipe = pipe; mutex_lock(&dev->mode_config.mutex); drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC); diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index a04639d..d53417e 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; } @@ -1007,3 +1011,122 @@ int drm_helper_resume_force_mode(struct drm_device *dev) return 0; } EXPORT_SYMBOL(drm_helper_resume_force_mode); + +/** + * drm_mode_page_flip - page flip ioctl + * @dev: DRM device + * @data: ioctl args + * @file_priv: file private data + * + * The page flip ioctl replaces the current front buffer with a new one, + * using the CRTC's mode_set_base function, which should just update + * the front buffer base pointer. It's up to mode_set_base to make + * sure the update doesn't result in tearing (on some hardware the base + * register is double buffered, so this is easy). + * + * Note that this covers just the simple case of flipping the front + * buffer immediately. Interval handling and interlaced modes have to + * be handled by userspace, or with new ioctls. + */ +int drm_mode_page_flip(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_pending_flip *pending; + struct drm_mode_page_flip *flip_data = data; + struct drm_mode_object *drm_obj, *fb_obj; + struct drm_crtc *crtc; + struct drm_crtc_helper_funcs *crtc_funcs; + int ret = 0; + + 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 & (~DRM_MODE_PAGE_FLIP_FLAGS_MASK)) { + DRM_DEBUG("bad page flip flags\n"); + return -EINVAL; + } + + pending = kzalloc(sizeof *pending, GFP_KERNEL); + if (pending == NULL) + return -ENOMEM; + + 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; + } + + drm_obj = drm_mode_object_find(dev, flip_data->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!drm_obj) { + DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_id); + ret = -ENOENT; + goto out_unlock; + } + crtc = obj_to_crtc(drm_obj); + if (!crtc->enabled) { + DRM_DEBUG("crtc %d not enabled\n", flip_data->crtc_id); + ret = -EINVAL; + goto out_unlock; + } + + crtc_funcs = crtc->helper_private; + if (crtc_funcs->mode_unpin_fb == NULL) { + DRM_DEBUG("crtc %d does not support delayed unpin\n", + flip_data->crtc_id); + ret = -ENODEV; + goto out_unlock; + } + + pending->crtc = crtc; + pending->old_fb = crtc->fb; + pending->pipe = crtc->pipe; + pending->event.base.type = DRM_EVENT_MODE_PAGE_FLIP; + pending->event.base.length = sizeof pending->event; + pending->event.base.user_data = flip_data->user_data; + pending->pending_event.event = &pending->event.base; + pending->pending_event.file_priv = file_priv; + pending->pending_event.destroy = + (void (*) (struct drm_pending_event *)) kfree; + + /* Get vblank ref for completion handling */ + ret = drm_vblank_get(dev, crtc->pipe); + if (ret) { + DRM_DEBUG("failed to take vblank ref\n"); + goto out_unlock; + } + + pending->frame = drm_vblank_count(dev, crtc->pipe); + list_add_tail(&pending->link, &dev->flip_list); + + /* + * The mode_set_base call will change the domain on the new + * fb, which will force the rendering to finish and block the + * ioctl. We need to do this last part from a work queue, to + * avoid blocking userspace here. + */ + crtc->fb = obj_to_fb(fb_obj); + ret = crtc_funcs->mode_set_base(crtc, 0, 0, NULL); + if (ret) { + DRM_ERROR("mode_set_base failed: %d\n", ret); + goto out_unlock; + } + + mutex_unlock(&dev->struct_mutex); + + return 0; + +out_unlock: + mutex_unlock(&dev->struct_mutex); + kfree(pending); + + return ret; +} diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index c4ada8b..352893c 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip, DRM_MASTER|DRM_CONTROL_ALLOW), }; #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 09a3571..07028b8 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -257,6 +257,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp, INIT_LIST_HEAD(&priv->lhead); INIT_LIST_HEAD(&priv->fbs); + INIT_LIST_HEAD(&priv->event_list); + init_waitqueue_head(&priv->event_wait); if (dev->driver->driver_features & DRIVER_GEM) drm_gem_open(dev, priv); @@ -429,6 +431,9 @@ int drm_release(struct inode *inode, struct file *filp) { struct drm_file *file_priv = filp->private_data; struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_flip *f, *ft; + struct drm_pending_event *e, *et; + int retcode = 0; lock_kernel(); @@ -451,6 +456,23 @@ int drm_release(struct inode *inode, struct file *filp) if (file_priv->minor->master) drm_master_release(dev, filp); + mutex_lock(&dev->struct_mutex); + + /* Remove pending flips */ + list_for_each_entry_safe(f, ft, &dev->flip_list, link) { + if (f->pending_event.file_priv == file_priv) { + drm_vblank_put(dev, f->pipe); + list_del(&f->link); + kfree(f); + } + } + + /* Remove unconsumed events */ + list_for_each_entry_safe(e, et, &file_priv->event_list, link) + kfree(e); + + mutex_unlock(&dev->struct_mutex); + if (dev->driver->driver_features & DRIVER_GEM) drm_gem_release(dev, file_priv); @@ -544,9 +566,55 @@ int drm_release(struct inode *inode, struct file *filp) } EXPORT_SYMBOL(drm_release); -/** No-op. */ +ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_event *event; + ssize_t total, ret; + + ret = wait_event_interruptible(file_priv->event_wait, + !list_empty(&file_priv->event_list)); + if (ret < 0) + return ret; + + total = 0; + while (!list_empty(&file_priv->event_list)) { + mutex_lock(&dev->struct_mutex); + event = list_first_entry(&file_priv->event_list, + struct drm_pending_event, link); + if (total + event->event->length > count) { + mutex_unlock(&dev->struct_mutex); + break; + } + list_del(&event->link); + mutex_unlock(&dev->struct_mutex); + + if (copy_to_user(buffer + total, + event->event, event->event->length)) { + total = -EFAULT; + break; + } + + total += event->event->length; + event->destroy(event); + } + + return total; +} +EXPORT_SYMBOL(drm_read); + unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) { - return 0; + struct drm_file *file_priv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &file_priv->event_wait, wait); + + if (!list_empty(&file_priv->event_list)) + mask |= POLLIN | POLLRDNORM; + + return mask; } EXPORT_SYMBOL(drm_poll); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 93e677a..eb66110 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -34,6 +34,7 @@ */ #include "drmP.h" +#include "drm_crtc_helper.h" #include <linux/interrupt.h> /* For task queue support */ @@ -71,6 +72,45 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, return 0; } +#define vblank_after(a,b) ((long)(b) - (long)(a) < 0) + +static void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame) +{ + struct drm_crtc_helper_funcs *crtc_funcs; + struct timeval now; + + f->event.frame = frame; + do_gettimeofday(&now); + f->event.tv_sec = now.tv_sec; + f->event.tv_usec = now.tv_usec; + drm_vblank_put(dev, f->pipe); + list_del_init(&f->link); + list_add_tail(&f->pending_event.link, + &f->pending_event.file_priv->event_list); + crtc_funcs = f->crtc->helper_private; + crtc_funcs->mode_unpin_fb(f->crtc, f->old_fb); + wake_up_interruptible(&f->pending_event.file_priv->event_wait); +} + +static void drm_flip_work_func(struct work_struct *work) +{ + struct drm_device *dev = + container_of(work, struct drm_device, flip_work); + struct drm_pending_flip *f, *t; + u32 frame; + + mutex_lock(&dev->struct_mutex); + + list_for_each_entry_safe(f, t, &dev->flip_list, link) { + frame = drm_vblank_count(dev, f->pipe); + if (vblank_after(frame, f->frame)) + drm_finish_pending_flip(dev, f, frame); + } + + mutex_unlock(&dev->struct_mutex); +} + static void vblank_disable_fn(unsigned long arg) { struct drm_device *dev = (struct drm_device *)arg; @@ -173,6 +213,8 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) atomic_set(&dev->vblank_refcount[i], 0); } + INIT_LIST_HEAD(&dev->flip_list); + INIT_WORK(&dev->flip_work, drm_flip_work_func); dev->vblank_disable_allowed = 0; return 0; @@ -632,5 +674,7 @@ void drm_handle_vblank(struct drm_device *dev, int crtc) { atomic_inc(&dev->_vblank_count[crtc]); DRM_WAKEUP(&dev->vbl_queue[crtc]); + schedule_work(&dev->flip_work); } EXPORT_SYMBOL(drm_handle_vblank); + diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 98560e1..85dde09 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -198,6 +198,7 @@ static struct drm_driver driver = { .mmap = drm_gem_mmap, .poll = drm_poll, .fasync = drm_fasync, + .read = drm_read, #ifdef CONFIG_COMPAT .compat_ioctl = i915_compat_ioctl, #endif diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index bdcda36..ba53acb 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)) { @@ -759,13 +757,11 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, I915_READ(dspbase); } - intel_wait_for_vblank(dev); - if (old_fb) { intel_fb = to_intel_framebuffer(old_fb); + intel_wait_for_vblank(dev); i915_gem_object_unpin(intel_fb->obj); } - mutex_unlock(&dev->struct_mutex); if (!dev->primary->master) return 0; @@ -785,7 +781,14 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, return 0; } +static void +intel_pipe_unpin_fb(struct drm_crtc *crtc, struct drm_framebuffer *old_fb) +{ + struct intel_framebuffer *intel_fb; + intel_fb = to_intel_framebuffer(old_fb); + i915_gem_object_unpin(intel_fb->obj); +} /** * Sets the power management mode of the pipe and plane. @@ -1314,7 +1317,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; @@ -1757,6 +1762,7 @@ static const struct drm_crtc_helper_funcs intel_helper_funcs = { .mode_fixup = intel_crtc_mode_fixup, .mode_set = intel_crtc_mode_set, .mode_set_base = intel_pipe_set_base, + .mode_unpin_fb = intel_pipe_unpin_fb, .prepare = intel_crtc_prepare, .commit = intel_crtc_commit, }; @@ -1779,7 +1785,7 @@ static void intel_crtc_init(struct drm_device *dev, int pipe) if (intel_crtc == NULL) return; - drm_crtc_init(dev, &intel_crtc->base, &intel_crtc_funcs); + drm_crtc_init(dev, &intel_crtc->base, pipe, &intel_crtc_funcs); drm_mode_crtc_set_gamma_size(&intel_crtc->base, 256); intel_crtc->pipe = pipe; diff --git a/include/drm/drm.h b/include/drm/drm.h index 7cb50bd..e385fe2 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -686,6 +686,7 @@ struct drm_gem_open { #define DRM_IOCTL_MODE_GETFB DRM_IOWR(0xAD, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) +#define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOW( 0xB0, struct drm_mode_page_flip) /** * Device specific ioctls should only be in their respective headers @@ -698,6 +699,30 @@ struct drm_gem_open { #define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_END 0xA0 +/** + * Header for events written back to userspace on the drm fd. The + * type defines the type of event, the length specifies the total + * length of the event (including the header), and user_data is + * typically a 64 bit value passed with the ioctl that triggered the + * event. A read on the drm fd will always only return complete + * events, that is, if for example the read buffer is 100 bytes, and + * there are two 64 byte events pending, only one will be returned. + */ +struct drm_event { + __u32 type; + __u32 length; + __u64 user_data; +}; + +#define DRM_EVENT_MODE_PAGE_FLIP 0x01 + +struct drm_event_page_flip { + struct drm_event base; + __u32 tv_sec; + __u32 tv_usec; + __u32 frame; +}; + /* typedef area */ #ifndef __KERNEL__ typedef struct drm_clip_rect drm_clip_rect_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index c8c4221..47dcdb9 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -378,6 +378,14 @@ struct drm_buf_entry { struct drm_freelist freelist; }; +/* Event queued up for userspace to read */ +struct drm_pending_event { + struct drm_event *event; + struct list_head link; + struct drm_file *file_priv; + void (*destroy) (struct drm_pending_event *event); +}; + /** File private data */ struct drm_file { int authenticated; @@ -401,6 +409,9 @@ struct drm_file { struct drm_master *master; /* master this node is currently associated with N.B. not always minor->master */ struct list_head fbs; + + wait_queue_head_t event_wait; + struct list_head event_list; }; /** Wait queue */ @@ -869,6 +880,16 @@ struct drm_minor { struct drm_mode_group mode_group; }; +struct drm_pending_flip { + struct drm_pending_event pending_event; + struct drm_framebuffer *old_fb; + struct drm_crtc *crtc; + u32 frame; + int pipe; + struct list_head link; + struct drm_event_page_flip event; +}; + /** * DRM device structure. This structure represent a complete card that * may contain multiple heads. @@ -968,6 +989,13 @@ struct drm_device { u32 max_vblank_count; /**< size of vblank counter register */ + struct work_struct flip_work; + + /** + * List of objects waiting on flip completion + */ + struct list_head flip_list; + /*@} */ cycles_t ctx_start; cycles_t lck_start; @@ -1104,6 +1132,8 @@ extern int drm_lastclose(struct drm_device *dev); extern int drm_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_fasync(int fd, struct file *filp, int on); +extern ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset); extern int drm_release(struct inode *inode, struct file *filp); /* Mapping support (drm_vm.h) */ diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 3c1924c..5c5c10f 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -336,6 +336,7 @@ struct drm_crtc_funcs { /** * drm_crtc - central CRTC control structure * @enabled: is this CRTC enabled? + * @pipe: pipe number (as seen by DRM vblank functions) * @x: x position on screen * @y: y position on screen * @desired_mode: new desired mode @@ -359,6 +360,7 @@ struct drm_crtc { struct drm_display_mode mode; + int pipe; int x, y; struct drm_display_mode *desired_mode; int desired_x, desired_y; @@ -586,6 +588,7 @@ struct drm_mode_config { extern void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, + int pipe, const struct drm_crtc_funcs *funcs); extern void drm_crtc_cleanup(struct drm_crtc *crtc); @@ -733,4 +736,6 @@ extern int drm_mode_gamma_get_ioctl(struct drm_device *dev, extern int drm_mode_gamma_set_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern bool drm_detect_hdmi_monitor(struct edid *edid); +extern int drm_mode_page_flip(struct drm_device *dev, void *data, + struct drm_file *file_priv); #endif /* __DRM_CRTC_H__ */ diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h index ec073d8..a0ae240 100644 --- a/include/drm/drm_crtc_helper.h +++ b/include/drm/drm_crtc_helper.h @@ -57,9 +57,22 @@ struct drm_crtc_helper_funcs { struct drm_display_mode *adjusted_mode, int x, int y, struct drm_framebuffer *old_fb); - /* Move the crtc on the current fb to the given position *optional* */ + /* + * Move the crtc on the current fb to the given position. + * This function is optional. If old_fb is provided, the + * function will wait for vblank and unpin it. If old_fb is + * NULL, nothing is unpinned and the caller must call + * mode_unpin_fb to release the old framebuffer. + */ int (*mode_set_base)(struct drm_crtc *crtc, int x, int y, struct drm_framebuffer *old_fb); + + /* + * Unpin the old fb after setting a mode. Must be called + * after the old framebuffer is no longer visible, ie, after + * the next vblank, typically. + */ + void (*mode_unpin_fb)(struct drm_crtc *crtc, struct drm_framebuffer *fb); }; struct drm_encoder_helper_funcs { diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index ae304cc..464b779 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -265,4 +265,20 @@ struct drm_mode_crtc_lut { __u64 blue; }; +#define DRM_MODE_PAGE_FLIP_WAIT (1<<0) /* block on previous page flip */ +#define DRM_MODE_PAGE_FLIP_FLAGS_MASK (DRM_MODE_PAGE_FLIP_WAIT) + +struct drm_mode_page_flip { + /** Handle of new front buffer */ + __u32 fb_id; + __u32 crtc_id; + + /* 64 bit cookie returned to userspace in the page flip event. */ + __u64 user_data; + /** + * page flip flags (wait on flip only for now) + */ + __u32 flags; +}; + #endif -- 1.6.2 |
From: Kristian H. <kr...@bi...> - 2009-05-15 03:04:40
|
From: Kristian Høgsberg <kr...@re...> This patch adds a vblank synced pageflip ioctl for to the modesetting family of ioctls. The ioctl takes a crtc and an fb and schedules a pageflip to the new fb at the next coming vertical blank event. This feature lets userspace implement tear-free updating of the screen contents with hw-guaranteed low latency page flipping. The ioctl is asynchronous in that it returns immediately and then later notifies the client by making an event available for reading on the drm fd. This lets applications add the drm fd to their main loop and handle other tasks while waiting for the flip to happen. The event includes the time of the flip, the frame counter and a 64 bit opaque token provided by user space in the ioctl. Based on initial work and suggestions from Jesse Barnes <jb...@vi...> and Jakob Bornecrantz <wal...@gm...>. Signed-off-by: Kristian Høgsberg <kr...@re...> --- This update addresses a few of the issues brought up by Jakob: - the u64 user_data field is moved the page flip event from the drm header struct. - the implementation of the page flip ioctl is moved to drm_crtc.h instead of drm_crtc_helper.c and cleanup in drm_release() is simpler and should clean up everything (it was missing an unpin before, now we just use the same code as the normal drm_read() case). What's still missing is: - don't block on setting the domain in set_base - the synchronous version of the ioctl; userspace can just use the ioctl followed by a read loop. I'll be updating userspace, but I'm moving slowly on this - I can't get to that before next week. Fedora 11 blockers and all... cheers, Kristian drivers/gpu/drm/drm_crtc.c | 120 +++++++++++++++++++++++++++++++++- drivers/gpu/drm/drm_crtc_helper.c | 14 +++-- drivers/gpu/drm/drm_drv.c | 1 + drivers/gpu/drm/drm_fops.c | 68 +++++++++++++++++++- drivers/gpu/drm/drm_irq.c | 42 ++++++++++++ drivers/gpu/drm/i915/i915_drv.c | 1 + drivers/gpu/drm/i915/intel_display.c | 27 +++++--- include/drm/drm.h | 25 +++++++ include/drm/drmP.h | 32 +++++++++ include/drm/drm_crtc.h | 21 ++++++ include/drm/drm_crtc_helper.h | 4 - include/drm/drm_mode.h | 16 +++++ 12 files changed, 348 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 94a7688..5f63ca0 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -352,11 +352,12 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup); * * Inits a new object created as base part of an driver crtc object. */ -void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, +void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, const struct drm_crtc_funcs *funcs) { crtc->dev = dev; crtc->funcs = funcs; + crtc->pipe = pipe; mutex_lock(&dev->mode_config.mutex); drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC); @@ -2447,3 +2448,120 @@ out: mutex_unlock(&dev->mode_config.mutex); return ret; } + +/** + * drm_mode_page_flip_ioctl - page flip ioctl + * @dev: DRM device + * @data: ioctl args + * @file_priv: file private data + * + * The page flip ioctl replaces the current front buffer with a new + * one, using the CRTC's set_base function, which should just update + * the front buffer base pointer. It's up to set_base to make + * sure the update doesn't result in tearing (on some hardware the + * base register is double buffered, so this is easy). + * + * Note that this covers just the simple case of flipping the front + * buffer immediately. Interval handling and interlaced modes have to + * be handled by userspace, or with new ioctls. + */ +int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_pending_flip *pending; + struct drm_mode_page_flip *flip_data = data; + struct drm_mode_object *drm_obj, *fb_obj; + struct drm_crtc *crtc; + int ret = 0; + + 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 & (~DRM_MODE_PAGE_FLIP_FLAGS_MASK)) { + DRM_DEBUG("bad page flip flags\n"); + return -EINVAL; + } + + pending = kzalloc(sizeof *pending, GFP_KERNEL); + if (pending == NULL) + return -ENOMEM; + + 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; + } + + drm_obj = drm_mode_object_find(dev, flip_data->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!drm_obj) { + DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_id); + ret = -ENOENT; + goto out_unlock; + } + crtc = obj_to_crtc(drm_obj); + if (!crtc->enabled) { + DRM_DEBUG("crtc %d not enabled\n", flip_data->crtc_id); + ret = -EINVAL; + goto out_unlock; + } + + if (crtc->fb->funcs->unpin == NULL) { + DRM_DEBUG("fb for crtc %d does not support delayed unpin\n", + flip_data->crtc_id); + ret = -ENODEV; + goto out_unlock; + } + + pending->crtc = crtc; + pending->old_fb = crtc->fb; + pending->pipe = crtc->pipe; + pending->event.base.type = DRM_EVENT_MODE_PAGE_FLIP; + pending->event.base.length = sizeof pending->event; + pending->event.base.user_data = flip_data->user_data; + pending->pending_event.event = &pending->event.base; + pending->pending_event.file_priv = file_priv; + pending->pending_event.destroy = + (void (*) (struct drm_pending_event *)) kfree; + + /* Get vblank ref for completion handling */ + ret = drm_vblank_get(dev, crtc->pipe); + if (ret) { + DRM_DEBUG("failed to take vblank ref\n"); + goto out_unlock; + } + + pending->frame = drm_vblank_count(dev, crtc->pipe); + list_add_tail(&pending->link, &dev->flip_list); + + /* + * The set_base call will change the domain on the new fb, + * which will force the rendering to finish and block the + * ioctl. We need to do this last part from a work queue, to + * avoid blocking userspace here. + */ + crtc->fb = obj_to_fb(fb_obj); + ret = (*crtc->funcs->set_base)(crtc, 0, 0, NULL); + if (ret) { + DRM_ERROR("set_base failed: %d\n", ret); + goto out_unlock; + } + + mutex_unlock(&dev->struct_mutex); + + return 0; + +out_unlock: + mutex_unlock(&dev->struct_mutex); + kfree(pending); + + return ret; +} diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index a04639d..4aed52d 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) { - ret = !crtc_funcs->mode_set_base(crtc, crtc->x, crtc->y, - old_fb); + mutex_lock(&dev->struct_mutex); + ret = !(*crtc->funcs->set_base)(crtc, crtc->x, crtc->y, + old_fb); + mutex_unlock(&dev->struct_mutex); goto done; } } @@ -836,7 +838,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set) } /* mode_set_base is not a required function */ - if (fb_changed && !crtc_funcs->mode_set_base) + if (fb_changed && !set->crtc->funcs->set_base) mode_changed = true; if (mode_changed) { @@ -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; - ret = crtc_funcs->mode_set_base(set->crtc, - set->x, set->y, old_fb); + mutex_lock(&dev->struct_mutex); + ret = (*set->crtc->funcs->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/drm_drv.c b/drivers/gpu/drm/drm_drv.c index c4ada8b..6cc61c4 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW), }; #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 09a3571..4b1b461 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -257,6 +257,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp, INIT_LIST_HEAD(&priv->lhead); INIT_LIST_HEAD(&priv->fbs); + INIT_LIST_HEAD(&priv->event_list); + init_waitqueue_head(&priv->event_wait); if (dev->driver->driver_features & DRIVER_GEM) drm_gem_open(dev, priv); @@ -429,6 +431,9 @@ int drm_release(struct inode *inode, struct file *filp) { struct drm_file *file_priv = filp->private_data; struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_flip *f, *ft; + struct drm_pending_event *e, *et; + int retcode = 0; lock_kernel(); @@ -451,6 +456,19 @@ int drm_release(struct inode *inode, struct file *filp) if (file_priv->minor->master) drm_master_release(dev, filp); + mutex_lock(&dev->struct_mutex); + + /* Remove pending flips */ + list_for_each_entry_safe(f, ft, &dev->flip_list, link) + if (f->pending_event.file_priv == file_priv) + drm_finish_pending_flip(dev, f, 0); + + /* Remove unconsumed events */ + list_for_each_entry_safe(e, et, &file_priv->event_list, link) + e->destroy(e); + + mutex_unlock(&dev->struct_mutex); + if (dev->driver->driver_features & DRIVER_GEM) drm_gem_release(dev, file_priv); @@ -544,9 +562,55 @@ int drm_release(struct inode *inode, struct file *filp) } EXPORT_SYMBOL(drm_release); -/** No-op. */ +ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_event *event; + ssize_t total, ret; + + ret = wait_event_interruptible(file_priv->event_wait, + !list_empty(&file_priv->event_list)); + if (ret < 0) + return ret; + + total = 0; + while (!list_empty(&file_priv->event_list)) { + mutex_lock(&dev->struct_mutex); + event = list_first_entry(&file_priv->event_list, + struct drm_pending_event, link); + if (total + event->event->length > count) { + mutex_unlock(&dev->struct_mutex); + break; + } + list_del(&event->link); + mutex_unlock(&dev->struct_mutex); + + if (copy_to_user(buffer + total, + event->event, event->event->length)) { + total = -EFAULT; + break; + } + + total += event->event->length; + event->destroy(event); + } + + return total; +} +EXPORT_SYMBOL(drm_read); + unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) { - return 0; + struct drm_file *file_priv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &file_priv->event_wait, wait); + + if (!list_empty(&file_priv->event_list)) + mask |= POLLIN | POLLRDNORM; + + return mask; } EXPORT_SYMBOL(drm_poll); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 93e677a..2825666 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -34,6 +34,7 @@ */ #include "drmP.h" +#include "drm_crtc_helper.h" #include <linux/interrupt.h> /* For task queue support */ @@ -71,6 +72,43 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, return 0; } +#define vblank_after(a,b) ((long)(b) - (long)(a) < 0) + +void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame) +{ + struct timeval now; + + f->event.frame = frame; + do_gettimeofday(&now); + f->event.tv_sec = now.tv_sec; + f->event.tv_usec = now.tv_usec; + drm_vblank_put(dev, f->pipe); + list_del_init(&f->link); + list_add_tail(&f->pending_event.link, + &f->pending_event.file_priv->event_list); + f->old_fb->funcs->unpin(f->old_fb); + wake_up_interruptible(&f->pending_event.file_priv->event_wait); +} + +static void drm_flip_work_func(struct work_struct *work) +{ + struct drm_device *dev = + container_of(work, struct drm_device, flip_work); + struct drm_pending_flip *f, *t; + u32 frame; + + mutex_lock(&dev->struct_mutex); + + list_for_each_entry_safe(f, t, &dev->flip_list, link) { + frame = drm_vblank_count(dev, f->pipe); + if (vblank_after(frame, f->frame)) + drm_finish_pending_flip(dev, f, frame); + } + + mutex_unlock(&dev->struct_mutex); +} + static void vblank_disable_fn(unsigned long arg) { struct drm_device *dev = (struct drm_device *)arg; @@ -173,6 +211,8 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) atomic_set(&dev->vblank_refcount[i], 0); } + INIT_LIST_HEAD(&dev->flip_list); + INIT_WORK(&dev->flip_work, drm_flip_work_func); dev->vblank_disable_allowed = 0; return 0; @@ -632,5 +672,7 @@ void drm_handle_vblank(struct drm_device *dev, int crtc) { atomic_inc(&dev->_vblank_count[crtc]); DRM_WAKEUP(&dev->vbl_queue[crtc]); + schedule_work(&dev->flip_work); } EXPORT_SYMBOL(drm_handle_vblank); + diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 98560e1..85dde09 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -198,6 +198,7 @@ static struct drm_driver driver = { .mmap = drm_gem_mmap, .poll = drm_poll, .fasync = drm_fasync, + .read = drm_read, #ifdef CONFIG_COMPAT .compat_ioctl = i915_compat_ioctl, #endif diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index bdcda36..6b21099 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)) { @@ -759,13 +757,11 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, I915_READ(dspbase); } - intel_wait_for_vblank(dev); - if (old_fb) { intel_fb = to_intel_framebuffer(old_fb); + intel_wait_for_vblank(dev); i915_gem_object_unpin(intel_fb->obj); } - mutex_unlock(&dev->struct_mutex); if (!dev->primary->master) return 0; @@ -785,8 +781,6 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, return 0; } - - /** * Sets the power management mode of the pipe and plane. * @@ -1314,7 +1308,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; @@ -1756,7 +1752,6 @@ static const struct drm_crtc_helper_funcs intel_helper_funcs = { .dpms = intel_crtc_dpms, .mode_fixup = intel_crtc_mode_fixup, .mode_set = intel_crtc_mode_set, - .mode_set_base = intel_pipe_set_base, .prepare = intel_crtc_prepare, .commit = intel_crtc_commit, }; @@ -1767,6 +1762,7 @@ static const struct drm_crtc_funcs intel_crtc_funcs = { .gamma_set = intel_crtc_gamma_set, .set_config = drm_crtc_helper_set_config, .destroy = intel_crtc_destroy, + .set_base = intel_pipe_set_base, }; @@ -1779,7 +1775,7 @@ static void intel_crtc_init(struct drm_device *dev, int pipe) if (intel_crtc == NULL) return; - drm_crtc_init(dev, &intel_crtc->base, &intel_crtc_funcs); + drm_crtc_init(dev, &intel_crtc->base, pipe, &intel_crtc_funcs); drm_mode_crtc_set_gamma_size(&intel_crtc->base, 256); intel_crtc->pipe = pipe; @@ -1938,9 +1934,18 @@ static int intel_user_framebuffer_create_handle(struct drm_framebuffer *fb, return drm_gem_handle_create(file_priv, object, handle); } +static void intel_user_framebuffer_unpin(struct drm_framebuffer *fb) +{ + struct intel_framebuffer *intel_fb; + + intel_fb = to_intel_framebuffer(fb); + i915_gem_object_unpin(intel_fb->obj); +} + static const struct drm_framebuffer_funcs intel_fb_funcs = { .destroy = intel_user_framebuffer_destroy, .create_handle = intel_user_framebuffer_create_handle, + .unpin = intel_user_framebuffer_unpin }; int intel_framebuffer_create(struct drm_device *dev, diff --git a/include/drm/drm.h b/include/drm/drm.h index 7cb50bd..1920323 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -686,6 +686,7 @@ struct drm_gem_open { #define DRM_IOCTL_MODE_GETFB DRM_IOWR(0xAD, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) +#define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOW( 0xB0, struct drm_mode_page_flip) /** * Device specific ioctls should only be in their respective headers @@ -698,6 +699,30 @@ struct drm_gem_open { #define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_END 0xA0 +/** + * Header for events written back to userspace on the drm fd. The + * type defines the type of event, the length specifies the total + * length of the event (including the header), and user_data is + * typically a 64 bit value passed with the ioctl that triggered the + * event. A read on the drm fd will always only return complete + * events, that is, if for example the read buffer is 100 bytes, and + * there are two 64 byte events pending, only one will be returned. + */ +struct drm_event { + __u32 type; + __u32 length; +}; + +#define DRM_EVENT_MODE_PAGE_FLIP 0x01 + +struct drm_event_page_flip { + struct drm_event base; + __u64 user_data; + __u32 tv_sec; + __u32 tv_usec; + __u32 frame; +}; + /* typedef area */ #ifndef __KERNEL__ typedef struct drm_clip_rect drm_clip_rect_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index c8c4221..d70462e 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -378,6 +378,14 @@ struct drm_buf_entry { struct drm_freelist freelist; }; +/* Event queued up for userspace to read */ +struct drm_pending_event { + struct drm_event *event; + struct list_head link; + struct drm_file *file_priv; + void (*destroy) (struct drm_pending_event *event); +}; + /** File private data */ struct drm_file { int authenticated; @@ -401,6 +409,9 @@ struct drm_file { struct drm_master *master; /* master this node is currently associated with N.B. not always minor->master */ struct list_head fbs; + + wait_queue_head_t event_wait; + struct list_head event_list; }; /** Wait queue */ @@ -869,6 +880,16 @@ struct drm_minor { struct drm_mode_group mode_group; }; +struct drm_pending_flip { + struct drm_pending_event pending_event; + struct drm_framebuffer *old_fb; + struct drm_crtc *crtc; + u32 frame; + int pipe; + struct list_head link; + struct drm_event_page_flip event; +}; + /** * DRM device structure. This structure represent a complete card that * may contain multiple heads. @@ -968,6 +989,13 @@ struct drm_device { u32 max_vblank_count; /**< size of vblank counter register */ + struct work_struct flip_work; + + /** + * List of objects waiting on flip completion + */ + struct list_head flip_list; + /*@} */ cycles_t ctx_start; cycles_t lck_start; @@ -1104,6 +1132,8 @@ extern int drm_lastclose(struct drm_device *dev); extern int drm_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_fasync(int fd, struct file *filp, int on); +extern ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset); extern int drm_release(struct inode *inode, struct file *filp); /* Mapping support (drm_vm.h) */ @@ -1270,6 +1300,8 @@ extern void drm_vblank_pre_modeset(struct drm_device *dev, int crtc); extern void drm_vblank_post_modeset(struct drm_device *dev, int crtc); extern int drm_modeset_ctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +extern void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame); /* AGP/GART support (drm_agpsupport.h) */ extern struct drm_agp_head *drm_agp_init(struct drm_device *dev); diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 3c1924c..a1be471 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -238,6 +238,12 @@ struct drm_display_info { }; struct drm_framebuffer_funcs { + /* + * Unpin the old fb after setting a mode. Must be called + * after the old framebuffer is no longer visible, ie, after + * the next vblank, typically. + */ + void (*unpin)(struct drm_framebuffer *fb); void (*destroy)(struct drm_framebuffer *framebuffer); int (*create_handle)(struct drm_framebuffer *fb, struct drm_file *file_priv, @@ -331,11 +337,22 @@ struct drm_crtc_funcs { void (*destroy)(struct drm_crtc *crtc); int (*set_config)(struct drm_mode_set *set); + + /* + * Move the crtc on the current fb to the given position. + * This function is optional. If old_fb is provided, the + * function will wait for vblank and unpin it. If old_fb is + * NULL, nothing is unpinned and the caller must call + * mode_unpin_fb to release the old framebuffer. + */ + int (*set_base)(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb); }; /** * drm_crtc - central CRTC control structure * @enabled: is this CRTC enabled? + * @pipe: pipe number (as seen by DRM vblank functions) * @x: x position on screen * @y: y position on screen * @desired_mode: new desired mode @@ -359,6 +376,7 @@ struct drm_crtc { struct drm_display_mode mode; + int pipe; int x, y; struct drm_display_mode *desired_mode; int desired_x, desired_y; @@ -586,6 +604,7 @@ struct drm_mode_config { extern void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, + int pipe, const struct drm_crtc_funcs *funcs); extern void drm_crtc_cleanup(struct drm_crtc *crtc); @@ -733,4 +752,6 @@ extern int drm_mode_gamma_get_ioctl(struct drm_device *dev, extern int drm_mode_gamma_set_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern bool drm_detect_hdmi_monitor(struct edid *edid); +extern int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); #endif /* __DRM_CRTC_H__ */ diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h index ec073d8..81fc313 100644 --- a/include/drm/drm_crtc_helper.h +++ b/include/drm/drm_crtc_helper.h @@ -56,10 +56,6 @@ struct drm_crtc_helper_funcs { int (*mode_set)(struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode, int x, int y, struct drm_framebuffer *old_fb); - - /* Move the crtc on the current fb to the given position *optional* */ - int (*mode_set_base)(struct drm_crtc *crtc, int x, int y, - struct drm_framebuffer *old_fb); }; struct drm_encoder_helper_funcs { diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index ae304cc..464b779 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -265,4 +265,20 @@ struct drm_mode_crtc_lut { __u64 blue; }; +#define DRM_MODE_PAGE_FLIP_WAIT (1<<0) /* block on previous page flip */ +#define DRM_MODE_PAGE_FLIP_FLAGS_MASK (DRM_MODE_PAGE_FLIP_WAIT) + +struct drm_mode_page_flip { + /** Handle of new front buffer */ + __u32 fb_id; + __u32 crtc_id; + + /* 64 bit cookie returned to userspace in the page flip event. */ + __u64 user_data; + /** + * page flip flags (wait on flip only for now) + */ + __u32 flags; +}; + #endif -- 1.6.2 |
From: Jesse B. <jb...@vi...> - 2009-05-15 17:58:57
|
On Thu, 14 May 2009 23:04:51 -0400 Kristian Høgsberg <kr...@bi...> wrote: > From: Kristian Høgsberg <kr...@re...> > > This patch adds a vblank synced pageflip ioctl for to the modesetting > family of ioctls. The ioctl takes a crtc and an fb and schedules a > pageflip to the new fb at the next coming vertical blank event. This > feature lets userspace implement tear-free updating of the screen > contents with hw-guaranteed low latency page flipping. > > The ioctl is asynchronous in that it returns immediately and then > later notifies the client by making an event available for reading on > the drm fd. This lets applications add the drm fd to their main loop > and handle other tasks while waiting for the flip to happen. The > event includes the time of the flip, the frame counter and a 64 bit > opaque token provided by user space in the ioctl. > > Based on initial work and suggestions from > Jesse Barnes <jb...@vi...> and > Jakob Bornecrantz <wal...@gm...>. > > Signed-off-by: Kristian Høgsberg <kr...@re...> > --- Hey, you didn't even compile this! :) I'm trying this version out now. I'll see if I can add the async set_pipe_base, then start hacking on updates to the dri2-swapbuffers branches to get it working with userspace. Thanks, -- Jesse Barnes, Intel Open Source Technology Center |
From: Jesse B. <jb...@vi...> - 2009-05-15 19:16:32
|
On Thu, 14 May 2009 23:04:51 -0400 Kristian Høgsberg <kr...@bi...> wrote: > From: Kristian Høgsberg <kr...@re...> > > This patch adds a vblank synced pageflip ioctl for to the modesetting > family of ioctls. The ioctl takes a crtc and an fb and schedules a > pageflip to the new fb at the next coming vertical blank event. This > feature lets userspace implement tear-free updating of the screen > contents with hw-guaranteed low latency page flipping. > > The ioctl is asynchronous in that it returns immediately and then > later notifies the client by making an event available for reading on > the drm fd. This lets applications add the drm fd to their main loop > and handle other tasks while waiting for the flip to happen. The > event includes the time of the flip, the frame counter and a 64 bit > opaque token provided by user space in the ioctl. > > Based on initial work and suggestions from > Jesse Barnes <jb...@vi...> and > Jakob Bornecrantz <wal...@gm...>. > > Signed-off-by: Kristian Høgsberg <kr...@re...> > --- > > This update addresses a few of the issues brought up by Jakob: > > - the u64 user_data field is moved the page flip event > from the drm header struct. > > - the implementation of the page flip ioctl is moved to > drm_crtc.h instead of drm_crtc_helper.c > > and cleanup in drm_release() is simpler and should clean up > everything (it was missing an unpin before, now we just use the > same code as the normal drm_read() case). > > What's still missing is: > > - don't block on setting the domain in set_base > > - the synchronous version of the ioctl; userspace can > just use the ioctl followed by a read loop. Here's an update that makes the set_base async using a new work_struct. I'll push the libdrm changes into the kms-pageflip branch now too. The set_base thing is really ugly; apparently on x86 there's a set_base macro in the global namespace now, gross. Hopefully that will be fixed before this is pushed upstream. Thanks, -- Jesse Barnes, Intel Open Source Technology Center diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 94a7688..54dac6c 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -34,6 +34,8 @@ #include "drmP.h" #include "drm_crtc.h" +#undef set_base + struct drm_prop_enum_list { int type; char *name; @@ -342,6 +344,24 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb) EXPORT_SYMBOL(drm_framebuffer_cleanup); /** + * drm_crtc_async_work - do a set_base call from a work queue + * @work: work struct + * + * Called when a set_base call is queued by the page flip code. This + * allows the flip ioctl itself to return immediately and allow userspace + * to continue working. + */ +static void drm_crtc_async_work(struct work_struct *work) +{ + struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_work); + struct drm_device *dev = crtc->dev; + + mutex_lock(&dev->struct_mutex); + crtc->funcs->set_base(crtc, 0, 0, NULL); + mutex_unlock(&dev->struct_mutex); +} + +/** * drm_crtc_init - Initialise a new CRTC object * @dev: DRM device * @crtc: CRTC object to init @@ -352,17 +372,19 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup); * * Inits a new object created as base part of an driver crtc object. */ -void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, +void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, const struct drm_crtc_funcs *funcs) { crtc->dev = dev; crtc->funcs = funcs; + crtc->pipe = pipe; mutex_lock(&dev->mode_config.mutex); drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC); list_add_tail(&crtc->head, &dev->mode_config.crtc_list); dev->mode_config.num_crtc++; + INIT_WORK(&crtc->async_work, drm_crtc_async_work); mutex_unlock(&dev->mode_config.mutex); } EXPORT_SYMBOL(drm_crtc_init); @@ -381,6 +403,9 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; + mutex_lock(&dev->mode_config.mutex); + flush_work(&crtc->async_work); + if (crtc->gamma_store) { kfree(crtc->gamma_store); crtc->gamma_store = NULL; @@ -388,6 +413,7 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) drm_mode_object_put(dev, &crtc->base); list_del(&crtc->head); + mutex_unlock(&dev->mode_config.mutex); dev->mode_config.num_crtc--; } EXPORT_SYMBOL(drm_crtc_cleanup); @@ -2447,3 +2473,117 @@ out: mutex_unlock(&dev->mode_config.mutex); return ret; } + +/** + * drm_mode_page_flip_ioctl - page flip ioctl + * @dev: DRM device + * @data: ioctl args + * @file_priv: file private data + * + * The page flip ioctl replaces the current front buffer with a new + * one, using the CRTC's set_base function, which should just update + * the front buffer base pointer. It's up to set_base to make + * sure the update doesn't result in tearing (on some hardware the + * base register is double buffered, so this is easy). + * + * Note that this covers just the simple case of flipping the front + * buffer immediately. Interval handling and interlaced modes have to + * be handled by userspace, or with new ioctls. + */ +int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_pending_flip *pending; + struct drm_mode_page_flip *flip_data = data; + struct drm_mode_object *drm_obj, *fb_obj; + struct drm_crtc *crtc; + int ret = 0; + + 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 & (~DRM_MODE_PAGE_FLIP_FLAGS_MASK)) { + DRM_DEBUG("bad page flip flags\n"); + return -EINVAL; + } + + pending = kzalloc(sizeof *pending, GFP_KERNEL); + if (pending == NULL) + return -ENOMEM; + + 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; + } + + drm_obj = drm_mode_object_find(dev, flip_data->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!drm_obj) { + DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_id); + ret = -ENOENT; + goto out_unlock; + } + crtc = obj_to_crtc(drm_obj); + if (!crtc->enabled) { + DRM_DEBUG("crtc %d not enabled\n", flip_data->crtc_id); + ret = -EINVAL; + goto out_unlock; + } + + if (crtc->fb->funcs->unpin == NULL) { + DRM_DEBUG("fb for crtc %d does not support delayed unpin\n", + flip_data->crtc_id); + ret = -ENODEV; + goto out_unlock; + } + + pending->crtc = crtc; + pending->old_fb = crtc->fb; + pending->pipe = crtc->pipe; + pending->event.base.type = DRM_EVENT_MODE_PAGE_FLIP; + pending->event.base.length = sizeof pending->event; + pending->event.user_data = flip_data->user_data; + pending->pending_event.event = &pending->event.base; + pending->pending_event.file_priv = file_priv; + pending->pending_event.destroy = + (void (*) (struct drm_pending_event *)) kfree; + + /* Get vblank ref for completion handling */ + ret = drm_vblank_get(dev, crtc->pipe); + if (ret) { + DRM_DEBUG("failed to take vblank ref\n"); + goto out_unlock; + } + + pending->frame = drm_vblank_count(dev, crtc->pipe); + list_add_tail(&pending->link, &dev->flip_list); + + /* + * The set_base call will change the domain on the new fb, + * which will force the rendering to finish and block the + * ioctl. We need to do this last part from a work queue, to + * avoid blocking userspace here. + */ + crtc->fb = obj_to_fb(fb_obj); + + schedule_work(&crtc->async_work); + + mutex_unlock(&dev->struct_mutex); + + return 0; + +out_unlock: + mutex_unlock(&dev->struct_mutex); + kfree(pending); + + return ret; +} diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index a04639d..4aed52d 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) { - ret = !crtc_funcs->mode_set_base(crtc, crtc->x, crtc->y, - old_fb); + mutex_lock(&dev->struct_mutex); + ret = !(*crtc->funcs->set_base)(crtc, crtc->x, crtc->y, + old_fb); + mutex_unlock(&dev->struct_mutex); goto done; } } @@ -836,7 +838,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set) } /* mode_set_base is not a required function */ - if (fb_changed && !crtc_funcs->mode_set_base) + if (fb_changed && !set->crtc->funcs->set_base) mode_changed = true; if (mode_changed) { @@ -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; - ret = crtc_funcs->mode_set_base(set->crtc, - set->x, set->y, old_fb); + mutex_lock(&dev->struct_mutex); + ret = (*set->crtc->funcs->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/drm_drv.c b/drivers/gpu/drm/drm_drv.c index c4ada8b..6cc61c4 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW), }; #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 09a3571..4b1b461 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -257,6 +257,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp, INIT_LIST_HEAD(&priv->lhead); INIT_LIST_HEAD(&priv->fbs); + INIT_LIST_HEAD(&priv->event_list); + init_waitqueue_head(&priv->event_wait); if (dev->driver->driver_features & DRIVER_GEM) drm_gem_open(dev, priv); @@ -429,6 +431,9 @@ int drm_release(struct inode *inode, struct file *filp) { struct drm_file *file_priv = filp->private_data; struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_flip *f, *ft; + struct drm_pending_event *e, *et; + int retcode = 0; lock_kernel(); @@ -451,6 +456,19 @@ int drm_release(struct inode *inode, struct file *filp) if (file_priv->minor->master) drm_master_release(dev, filp); + mutex_lock(&dev->struct_mutex); + + /* Remove pending flips */ + list_for_each_entry_safe(f, ft, &dev->flip_list, link) + if (f->pending_event.file_priv == file_priv) + drm_finish_pending_flip(dev, f, 0); + + /* Remove unconsumed events */ + list_for_each_entry_safe(e, et, &file_priv->event_list, link) + e->destroy(e); + + mutex_unlock(&dev->struct_mutex); + if (dev->driver->driver_features & DRIVER_GEM) drm_gem_release(dev, file_priv); @@ -544,9 +562,55 @@ int drm_release(struct inode *inode, struct file *filp) } EXPORT_SYMBOL(drm_release); -/** No-op. */ +ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_event *event; + ssize_t total, ret; + + ret = wait_event_interruptible(file_priv->event_wait, + !list_empty(&file_priv->event_list)); + if (ret < 0) + return ret; + + total = 0; + while (!list_empty(&file_priv->event_list)) { + mutex_lock(&dev->struct_mutex); + event = list_first_entry(&file_priv->event_list, + struct drm_pending_event, link); + if (total + event->event->length > count) { + mutex_unlock(&dev->struct_mutex); + break; + } + list_del(&event->link); + mutex_unlock(&dev->struct_mutex); + + if (copy_to_user(buffer + total, + event->event, event->event->length)) { + total = -EFAULT; + break; + } + + total += event->event->length; + event->destroy(event); + } + + return total; +} +EXPORT_SYMBOL(drm_read); + unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) { - return 0; + struct drm_file *file_priv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &file_priv->event_wait, wait); + + if (!list_empty(&file_priv->event_list)) + mask |= POLLIN | POLLRDNORM; + + return mask; } EXPORT_SYMBOL(drm_poll); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 93e677a..2825666 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -34,6 +34,7 @@ */ #include "drmP.h" +#include "drm_crtc_helper.h" #include <linux/interrupt.h> /* For task queue support */ @@ -71,6 +72,43 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, return 0; } +#define vblank_after(a,b) ((long)(b) - (long)(a) < 0) + +void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame) +{ + struct timeval now; + + f->event.frame = frame; + do_gettimeofday(&now); + f->event.tv_sec = now.tv_sec; + f->event.tv_usec = now.tv_usec; + drm_vblank_put(dev, f->pipe); + list_del_init(&f->link); + list_add_tail(&f->pending_event.link, + &f->pending_event.file_priv->event_list); + f->old_fb->funcs->unpin(f->old_fb); + wake_up_interruptible(&f->pending_event.file_priv->event_wait); +} + +static void drm_flip_work_func(struct work_struct *work) +{ + struct drm_device *dev = + container_of(work, struct drm_device, flip_work); + struct drm_pending_flip *f, *t; + u32 frame; + + mutex_lock(&dev->struct_mutex); + + list_for_each_entry_safe(f, t, &dev->flip_list, link) { + frame = drm_vblank_count(dev, f->pipe); + if (vblank_after(frame, f->frame)) + drm_finish_pending_flip(dev, f, frame); + } + + mutex_unlock(&dev->struct_mutex); +} + static void vblank_disable_fn(unsigned long arg) { struct drm_device *dev = (struct drm_device *)arg; @@ -173,6 +211,8 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) atomic_set(&dev->vblank_refcount[i], 0); } + INIT_LIST_HEAD(&dev->flip_list); + INIT_WORK(&dev->flip_work, drm_flip_work_func); dev->vblank_disable_allowed = 0; return 0; @@ -632,5 +672,7 @@ void drm_handle_vblank(struct drm_device *dev, int crtc) { atomic_inc(&dev->_vblank_count[crtc]); DRM_WAKEUP(&dev->vbl_queue[crtc]); + schedule_work(&dev->flip_work); } EXPORT_SYMBOL(drm_handle_vblank); + diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 98560e1..85dde09 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -198,6 +198,7 @@ static struct drm_driver driver = { .mmap = drm_gem_mmap, .poll = drm_poll, .fasync = drm_fasync, + .read = drm_read, #ifdef CONFIG_COMPAT .compat_ioctl = i915_compat_ioctl, #endif diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 3387cf3..663edf2 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)) { @@ -759,13 +757,11 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, I915_READ(dspbase); } - intel_wait_for_vblank(dev); - if (old_fb) { intel_fb = to_intel_framebuffer(old_fb); + intel_wait_for_vblank(dev); i915_gem_object_unpin(intel_fb->obj); } - mutex_unlock(&dev->struct_mutex); if (!dev->primary->master) return 0; @@ -785,8 +781,6 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, return 0; } - - /** * Sets the power management mode of the pipe and plane. * @@ -1314,7 +1308,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; @@ -1756,7 +1752,6 @@ static const struct drm_crtc_helper_funcs intel_helper_funcs = { .dpms = intel_crtc_dpms, .mode_fixup = intel_crtc_mode_fixup, .mode_set = intel_crtc_mode_set, - .mode_set_base = intel_pipe_set_base, .prepare = intel_crtc_prepare, .commit = intel_crtc_commit, }; @@ -1767,6 +1762,7 @@ static const struct drm_crtc_funcs intel_crtc_funcs = { .gamma_set = intel_crtc_gamma_set, .set_config = drm_crtc_helper_set_config, .destroy = intel_crtc_destroy, + .set_base = intel_pipe_set_base, }; @@ -1779,7 +1775,7 @@ static void intel_crtc_init(struct drm_device *dev, int pipe) if (intel_crtc == NULL) return; - drm_crtc_init(dev, &intel_crtc->base, &intel_crtc_funcs); + drm_crtc_init(dev, &intel_crtc->base, pipe, &intel_crtc_funcs); drm_mode_crtc_set_gamma_size(&intel_crtc->base, 256); intel_crtc->pipe = pipe; @@ -1969,9 +1965,18 @@ static int intel_user_framebuffer_create_handle(struct drm_framebuffer *fb, return drm_gem_handle_create(file_priv, object, handle); } +static void intel_user_framebuffer_unpin(struct drm_framebuffer *fb) +{ + struct intel_framebuffer *intel_fb; + + intel_fb = to_intel_framebuffer(fb); + i915_gem_object_unpin(intel_fb->obj); +} + static const struct drm_framebuffer_funcs intel_fb_funcs = { .destroy = intel_user_framebuffer_destroy, .create_handle = intel_user_framebuffer_create_handle, + .unpin = intel_user_framebuffer_unpin }; int intel_framebuffer_create(struct drm_device *dev, diff --git a/include/drm/drm.h b/include/drm/drm.h index 7cb50bd..1920323 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -686,6 +686,7 @@ struct drm_gem_open { #define DRM_IOCTL_MODE_GETFB DRM_IOWR(0xAD, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) +#define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOW( 0xB0, struct drm_mode_page_flip) /** * Device specific ioctls should only be in their respective headers @@ -698,6 +699,30 @@ struct drm_gem_open { #define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_END 0xA0 +/** + * Header for events written back to userspace on the drm fd. The + * type defines the type of event, the length specifies the total + * length of the event (including the header), and user_data is + * typically a 64 bit value passed with the ioctl that triggered the + * event. A read on the drm fd will always only return complete + * events, that is, if for example the read buffer is 100 bytes, and + * there are two 64 byte events pending, only one will be returned. + */ +struct drm_event { + __u32 type; + __u32 length; +}; + +#define DRM_EVENT_MODE_PAGE_FLIP 0x01 + +struct drm_event_page_flip { + struct drm_event base; + __u64 user_data; + __u32 tv_sec; + __u32 tv_usec; + __u32 frame; +}; + /* typedef area */ #ifndef __KERNEL__ typedef struct drm_clip_rect drm_clip_rect_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index c8c4221..d70462e 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -378,6 +378,14 @@ struct drm_buf_entry { struct drm_freelist freelist; }; +/* Event queued up for userspace to read */ +struct drm_pending_event { + struct drm_event *event; + struct list_head link; + struct drm_file *file_priv; + void (*destroy) (struct drm_pending_event *event); +}; + /** File private data */ struct drm_file { int authenticated; @@ -401,6 +409,9 @@ struct drm_file { struct drm_master *master; /* master this node is currently associated with N.B. not always minor->master */ struct list_head fbs; + + wait_queue_head_t event_wait; + struct list_head event_list; }; /** Wait queue */ @@ -869,6 +880,16 @@ struct drm_minor { struct drm_mode_group mode_group; }; +struct drm_pending_flip { + struct drm_pending_event pending_event; + struct drm_framebuffer *old_fb; + struct drm_crtc *crtc; + u32 frame; + int pipe; + struct list_head link; + struct drm_event_page_flip event; +}; + /** * DRM device structure. This structure represent a complete card that * may contain multiple heads. @@ -968,6 +989,13 @@ struct drm_device { u32 max_vblank_count; /**< size of vblank counter register */ + struct work_struct flip_work; + + /** + * List of objects waiting on flip completion + */ + struct list_head flip_list; + /*@} */ cycles_t ctx_start; cycles_t lck_start; @@ -1104,6 +1132,8 @@ extern int drm_lastclose(struct drm_device *dev); extern int drm_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_fasync(int fd, struct file *filp, int on); +extern ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset); extern int drm_release(struct inode *inode, struct file *filp); /* Mapping support (drm_vm.h) */ @@ -1270,6 +1300,8 @@ extern void drm_vblank_pre_modeset(struct drm_device *dev, int crtc); extern void drm_vblank_post_modeset(struct drm_device *dev, int crtc); extern int drm_modeset_ctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +extern void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame); /* AGP/GART support (drm_agpsupport.h) */ extern struct drm_agp_head *drm_agp_init(struct drm_device *dev); diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 3c1924c..066c6d3 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -238,6 +238,12 @@ struct drm_display_info { }; struct drm_framebuffer_funcs { + /* + * Unpin the old fb after setting a mode. Must be called + * after the old framebuffer is no longer visible, ie, after + * the next vblank, typically. + */ + void (*unpin)(struct drm_framebuffer *fb); void (*destroy)(struct drm_framebuffer *framebuffer); int (*create_handle)(struct drm_framebuffer *fb, struct drm_file *file_priv, @@ -331,17 +337,29 @@ struct drm_crtc_funcs { void (*destroy)(struct drm_crtc *crtc); int (*set_config)(struct drm_mode_set *set); + + /* + * Move the crtc on the current fb to the given position. + * This function is optional. If old_fb is provided, the + * function will wait for vblank and unpin it. If old_fb is + * NULL, nothing is unpinned and the caller must call + * mode_unpin_fb to release the old framebuffer. + */ + int (*set_base)(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb); }; /** * drm_crtc - central CRTC control structure * @enabled: is this CRTC enabled? + * @pipe: pipe number (as seen by DRM vblank functions) * @x: x position on screen * @y: y position on screen * @desired_mode: new desired mode * @desired_x: desired x for desired_mode * @desired_y: desired y for desired_mode * @funcs: CRTC control functions + * @async_work: work queue for async set base calls * * Each CRTC may have one or more connectors associated with it. This structure * allows the CRTC to be controlled. @@ -359,6 +377,7 @@ struct drm_crtc { struct drm_display_mode mode; + int pipe; int x, y; struct drm_display_mode *desired_mode; int desired_x, desired_y; @@ -368,6 +387,9 @@ struct drm_crtc { uint32_t gamma_size; uint16_t *gamma_store; + /* Allow async set_pipe_base calls for flipping */ + struct work_struct async_work; + /* if you are using the helper */ void *helper_private; }; @@ -586,6 +608,7 @@ struct drm_mode_config { extern void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, + int pipe, const struct drm_crtc_funcs *funcs); extern void drm_crtc_cleanup(struct drm_crtc *crtc); @@ -733,4 +756,6 @@ extern int drm_mode_gamma_get_ioctl(struct drm_device *dev, extern int drm_mode_gamma_set_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern bool drm_detect_hdmi_monitor(struct edid *edid); +extern int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); #endif /* __DRM_CRTC_H__ */ diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h index ec073d8..81fc313 100644 --- a/include/drm/drm_crtc_helper.h +++ b/include/drm/drm_crtc_helper.h @@ -56,10 +56,6 @@ struct drm_crtc_helper_funcs { int (*mode_set)(struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode, int x, int y, struct drm_framebuffer *old_fb); - - /* Move the crtc on the current fb to the given position *optional* */ - int (*mode_set_base)(struct drm_crtc *crtc, int x, int y, - struct drm_framebuffer *old_fb); }; struct drm_encoder_helper_funcs { diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index ae304cc..464b779 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -265,4 +265,20 @@ struct drm_mode_crtc_lut { __u64 blue; }; +#define DRM_MODE_PAGE_FLIP_WAIT (1<<0) /* block on previous page flip */ +#define DRM_MODE_PAGE_FLIP_FLAGS_MASK (DRM_MODE_PAGE_FLIP_WAIT) + +struct drm_mode_page_flip { + /** Handle of new front buffer */ + __u32 fb_id; + __u32 crtc_id; + + /* 64 bit cookie returned to userspace in the page flip event. */ + __u64 user_data; + /** + * page flip flags (wait on flip only for now) + */ + __u32 flags; +}; + #endif |
From: Jesse B. <jb...@vi...> - 2009-05-15 20:13:36
|
On Fri, 15 May 2009 12:16:14 -0700 Jesse Barnes <jb...@vi...> wrote: > On Thu, 14 May 2009 23:04:51 -0400 > Kristian Høgsberg <kr...@bi...> wrote: > > > From: Kristian Høgsberg <kr...@re...> > > > > This patch adds a vblank synced pageflip ioctl for to the > > modesetting family of ioctls. The ioctl takes a crtc and an fb and > > schedules a pageflip to the new fb at the next coming vertical > > blank event. This feature lets userspace implement tear-free > > updating of the screen contents with hw-guaranteed low latency page > > flipping. > > > > The ioctl is asynchronous in that it returns immediately and then > > later notifies the client by making an event available for reading > > on the drm fd. This lets applications add the drm fd to their main > > loop and handle other tasks while waiting for the flip to happen. > > The event includes the time of the flip, the frame counter and a 64 > > bit opaque token provided by user space in the ioctl. > > > > Based on initial work and suggestions from > > Jesse Barnes <jb...@vi...> and > > Jakob Bornecrantz <wal...@gm...>. > > > > Signed-off-by: Kristian Høgsberg <kr...@re...> > > --- > > > > This update addresses a few of the issues brought up by Jakob: > > > > - the u64 user_data field is moved the page flip event > > from the drm header struct. > > > > - the implementation of the page flip ioctl is moved to > > drm_crtc.h instead of drm_crtc_helper.c > > > > and cleanup in drm_release() is simpler and should clean up > > everything (it was missing an unpin before, now we just use the > > same code as the normal drm_read() case). > > > > What's still missing is: > > > > - don't block on setting the domain in set_base > > > > - the synchronous version of the ioctl; userspace can > > just use the ioctl followed by a read loop. > > Here's an update that makes the set_base async using a new > work_struct. I'll push the libdrm changes into the kms-pageflip > branch now too. > > The set_base thing is really ugly; apparently on x86 there's a > set_base macro in the global namespace now, gross. Hopefully that > will be fixed before this is pushed upstream. Just realized this one has a potential deadlock in the teardown path. We should probably flush the work queue *after* the crtc has been removed from the list, but w/o holding the struct mutex, since the async_work func needs that. Doesn't look like I'll have much more time today to work on the userspace side though... -- Jesse Barnes, Intel Open Source Technology Center |
From: Chris W. <ch...@ch...> - 2009-06-30 18:38:06
|
I've just got around to playing with the modesetting page-flip ioctl and found a nasty rendering glitch where the flip occurred before the rendering was flushed. This appears to be because the finish of the pending_flip is queued at the same time as the async set_base(). Applying the following patch prevents the glitch for me: >From aa017e6056cf2faf6be7eeaa71d2fded4a9f6295 Mon Sep 17 00:00:00 2001 From: Chris Wilson <ch...@ch...> Date: Tue, 30 Jun 2009 18:21:54 +0100 Subject: [PATCH 1/3] drm: delay unpinning the current fb til after the flip is complete --- drivers/gpu/drm/drm_crtc.c | 45 +++++++++++++++++++++++++++++++++++-------- drivers/gpu/drm/drm_irq.c | 7 +++-- include/drm/drm_crtc.h | 4 ++- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 6a5a779..32212e6 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -344,20 +344,30 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb) EXPORT_SYMBOL(drm_framebuffer_cleanup); /** - * drm_crtc_async_work - do a set_base call from a work queue + * drm_crtc_async_flip - do a set_base call from a work queue * @work: work struct * * Called when a set_base call is queued by the page flip code. This * allows the flip ioctl itself to return immediately and allow userspace * to continue working. */ -static void drm_crtc_async_work(struct work_struct *work) +static void drm_crtc_async_flip(struct work_struct *work) { - struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_work); + struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_flip); struct drm_device *dev = crtc->dev; + struct drm_pending_flip *pending; + + BUG_ON(crtc->pending_flip == NULL); mutex_lock(&dev->struct_mutex); crtc->funcs->set_base(crtc, 0, 0, NULL); + + pending = crtc->pending_flip; + crtc->pending_flip = NULL; + + pending->frame = drm_vblank_count(dev, crtc->pipe); + list_add_tail(&pending->link, &dev->flip_list); + mutex_unlock(&dev->struct_mutex); } @@ -384,7 +394,7 @@ void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, list_add_tail(&crtc->head, &dev->mode_config.crtc_list); dev->mode_config.num_crtc++; - INIT_WORK(&crtc->async_work, drm_crtc_async_work); + INIT_WORK(&crtc->async_flip, drm_crtc_async_flip); mutex_unlock(&dev->mode_config.mutex); } EXPORT_SYMBOL(drm_crtc_init); @@ -404,7 +414,7 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) struct drm_device *dev = crtc->dev; mutex_lock(&dev->mode_config.mutex); - flush_work(&crtc->async_work); + flush_work(&crtc->async_flip); if (crtc->gamma_store) { kfree(crtc->gamma_store); @@ -2569,9 +2579,6 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, goto out_unlock; } - pending->frame = drm_vblank_count(dev, crtc->pipe); - list_add_tail(&pending->link, &dev->flip_list); - /* * The set_base call will change the domain on the new fb, * which will force the rendering to finish and block the @@ -2580,7 +2587,27 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, */ crtc->fb = obj_to_fb(fb_obj); - schedule_work(&crtc->async_work); + if (crtc->pending_flip != NULL) { + struct drm_pending_flip *old_flip; + + /* We have an outstanding flip request for this crtc/pipe. + * In order to satisfy the user we can either queue the requests + * and apply them on sequential vblanks, or we can drop old + * requests. + * + * Here we choose to discard the previous request for + * simplicity. Note that since we have not yet applied the + * previous flip, we need to preserve the original (i.e. still + * current) fb. + */ + + old_flip = crtc->pending_flip; + pending->old_fb = old_flip->old_fb; + old_flip->old_fb = NULL; + drm_finish_pending_flip (dev, old_flip, 0); + } else + schedule_work(&crtc->async_flip); + crtc->pending_flip = pending; mutex_unlock(&dev->struct_mutex); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 8b4d0c8..c7a17f6 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -72,7 +72,7 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, return 0; } -#define vblank_after(a,b) ((long)(b) - (long)(a) < 0) +#define vblank_passed(a,b) ((long)(a - b) > 0) void drm_finish_pending_flip(struct drm_device *dev, struct drm_pending_flip *f, u32 frame) @@ -87,7 +87,8 @@ void drm_finish_pending_flip(struct drm_device *dev, list_del_init(&f->link); list_add_tail(&f->pending_event.link, &f->pending_event.file_priv->event_list); - f->old_fb->funcs->unpin(f->old_fb); + if (f->old_fb) + f->old_fb->funcs->unpin(f->old_fb); wake_up_interruptible(&f->pending_event.file_priv->event_wait); } @@ -102,7 +103,7 @@ static void drm_flip_work_func(struct work_struct *work) list_for_each_entry_safe(f, t, &dev->flip_list, link) { frame = drm_vblank_count(dev, f->pipe); - if (vblank_after(frame, f->frame)) + if (vblank_passed(frame, f->frame)) drm_finish_pending_flip(dev, f, frame); } diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 99fae10..0b5dc47 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -294,6 +294,7 @@ struct drm_property { struct drm_crtc; struct drm_connector; struct drm_encoder; +struct drm_pending_flip; /** * drm_crtc_funcs - control CRTCs for a given device @@ -388,7 +389,8 @@ struct drm_crtc { uint16_t *gamma_store; /* Allow async set_pipe_base calls for flipping */ - struct work_struct async_work; + struct work_struct async_flip; + struct drm_pending_flip *pending_flip; /* if you are using the helper */ void *helper_private; -- 1.6.3.3 |
From: Kristian H. <kr...@bi...> - 2009-07-01 03:40:34
|
On Tue, Jun 30, 2009 at 2:37 PM, Chris Wilson<ch...@ch...> wrote: > I've just got around to playing with the modesetting page-flip ioctl and > found a nasty rendering glitch where the flip occurred before the > rendering was flushed. This appears to be because the finish of the > pending_flip is queued at the same time as the async set_base(). > > Applying the following patch prevents the glitch for me: Yikes, that's embarassing. I saw the same kind of glitch that when running this with wayland too but never debugged it. Your patch looks good, but I haven't tested it yet. thanks, Kristian > >From aa017e6056cf2faf6be7eeaa71d2fded4a9f6295 Mon Sep 17 00:00:00 2001 > From: Chris Wilson <ch...@ch...> > Date: Tue, 30 Jun 2009 18:21:54 +0100 > Subject: [PATCH 1/3] drm: delay unpinning the current fb til after the flip is complete > > --- > drivers/gpu/drm/drm_crtc.c | 45 +++++++++++++++++++++++++++++++++++-------- > drivers/gpu/drm/drm_irq.c | 7 +++-- > include/drm/drm_crtc.h | 4 ++- > 3 files changed, 43 insertions(+), 13 deletions(-) > > diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c > index 6a5a779..32212e6 100644 > --- a/drivers/gpu/drm/drm_crtc.c > +++ b/drivers/gpu/drm/drm_crtc.c > @@ -344,20 +344,30 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb) > EXPORT_SYMBOL(drm_framebuffer_cleanup); > > /** > - * drm_crtc_async_work - do a set_base call from a work queue > + * drm_crtc_async_flip - do a set_base call from a work queue > * @work: work struct > * > * Called when a set_base call is queued by the page flip code. This > * allows the flip ioctl itself to return immediately and allow userspace > * to continue working. > */ > -static void drm_crtc_async_work(struct work_struct *work) > +static void drm_crtc_async_flip(struct work_struct *work) > { > - struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_work); > + struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_flip); > struct drm_device *dev = crtc->dev; > + struct drm_pending_flip *pending; > + > + BUG_ON(crtc->pending_flip == NULL); > > mutex_lock(&dev->struct_mutex); > crtc->funcs->set_base(crtc, 0, 0, NULL); > + > + pending = crtc->pending_flip; > + crtc->pending_flip = NULL; > + > + pending->frame = drm_vblank_count(dev, crtc->pipe); > + list_add_tail(&pending->link, &dev->flip_list); > + > mutex_unlock(&dev->struct_mutex); > } > > @@ -384,7 +394,7 @@ void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, > > list_add_tail(&crtc->head, &dev->mode_config.crtc_list); > dev->mode_config.num_crtc++; > - INIT_WORK(&crtc->async_work, drm_crtc_async_work); > + INIT_WORK(&crtc->async_flip, drm_crtc_async_flip); > mutex_unlock(&dev->mode_config.mutex); > } > EXPORT_SYMBOL(drm_crtc_init); > @@ -404,7 +414,7 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) > struct drm_device *dev = crtc->dev; > > mutex_lock(&dev->mode_config.mutex); > - flush_work(&crtc->async_work); > + flush_work(&crtc->async_flip); > > if (crtc->gamma_store) { > kfree(crtc->gamma_store); > @@ -2569,9 +2579,6 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, > goto out_unlock; > } > > - pending->frame = drm_vblank_count(dev, crtc->pipe); > - list_add_tail(&pending->link, &dev->flip_list); > - > /* > * The set_base call will change the domain on the new fb, > * which will force the rendering to finish and block the > @@ -2580,7 +2587,27 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, > */ > crtc->fb = obj_to_fb(fb_obj); > > - schedule_work(&crtc->async_work); > + if (crtc->pending_flip != NULL) { > + struct drm_pending_flip *old_flip; > + > + /* We have an outstanding flip request for this crtc/pipe. > + * In order to satisfy the user we can either queue the requests > + * and apply them on sequential vblanks, or we can drop old > + * requests. > + * > + * Here we choose to discard the previous request for > + * simplicity. Note that since we have not yet applied the > + * previous flip, we need to preserve the original (i.e. still > + * current) fb. > + */ > + > + old_flip = crtc->pending_flip; > + pending->old_fb = old_flip->old_fb; > + old_flip->old_fb = NULL; > + drm_finish_pending_flip (dev, old_flip, 0); > + } else > + schedule_work(&crtc->async_flip); > + crtc->pending_flip = pending; > > mutex_unlock(&dev->struct_mutex); > > diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c > index 8b4d0c8..c7a17f6 100644 > --- a/drivers/gpu/drm/drm_irq.c > +++ b/drivers/gpu/drm/drm_irq.c > @@ -72,7 +72,7 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, > return 0; > } > > -#define vblank_after(a,b) ((long)(b) - (long)(a) < 0) > +#define vblank_passed(a,b) ((long)(a - b) > 0) > > void drm_finish_pending_flip(struct drm_device *dev, > struct drm_pending_flip *f, u32 frame) > @@ -87,7 +87,8 @@ void drm_finish_pending_flip(struct drm_device *dev, > list_del_init(&f->link); > list_add_tail(&f->pending_event.link, > &f->pending_event.file_priv->event_list); > - f->old_fb->funcs->unpin(f->old_fb); > + if (f->old_fb) > + f->old_fb->funcs->unpin(f->old_fb); > wake_up_interruptible(&f->pending_event.file_priv->event_wait); > } > > @@ -102,7 +103,7 @@ static void drm_flip_work_func(struct work_struct *work) > > list_for_each_entry_safe(f, t, &dev->flip_list, link) { > frame = drm_vblank_count(dev, f->pipe); > - if (vblank_after(frame, f->frame)) > + if (vblank_passed(frame, f->frame)) > drm_finish_pending_flip(dev, f, frame); > } > > diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h > index 99fae10..0b5dc47 100644 > --- a/include/drm/drm_crtc.h > +++ b/include/drm/drm_crtc.h > @@ -294,6 +294,7 @@ struct drm_property { > struct drm_crtc; > struct drm_connector; > struct drm_encoder; > +struct drm_pending_flip; > > /** > * drm_crtc_funcs - control CRTCs for a given device > @@ -388,7 +389,8 @@ struct drm_crtc { > uint16_t *gamma_store; > > /* Allow async set_pipe_base calls for flipping */ > - struct work_struct async_work; > + struct work_struct async_flip; > + struct drm_pending_flip *pending_flip; > > /* if you are using the helper */ > void *helper_private; > -- > 1.6.3.3 > > > > > > ------------------------------------------------------------------------------ > -- > _______________________________________________ > Dri-devel mailing list > Dri...@li... > https://lists.sourceforge.net/lists/listinfo/dri-devel > |
From: <kr...@bi...> - 2009-07-16 19:03:42
|
From: Kristian Høgsberg <krh@hinata.local> This patch adds a vblank synced pageflip ioctl for to the modesetting family of ioctls. The ioctl takes a crtc and an fb and schedules a pageflip to the new fb at the next coming vertical blank event. This feature lets userspace implement tear-free updating of the screen contents with hw-guaranteed low latency page flipping. The ioctl is asynchronous in that it returns immediately and then later notifies the client by making an event available for reading on the drm fd. This lets applications add the drm fd to their main loop and handle other tasks while waiting for the flip to happen. The event includes the time of the flip, the frame counter and a 64 bit opaque token provided by user space in the ioctl. Based on work and suggestions from Jesse Barnes <jb...@vi...>, Jakob Bornecrantz <wal...@gm...>, Chris Wilson <ch...@ch...> Signed-off-by: Kristian Høgsberg <kr...@re...> --- Ok, here's the latest roll-up of the page flipping patch with the fixes from Jesse and Chris. drivers/gpu/drm/drm_crtc.c | 169 +++++++++++++++++++++++++++++++++- drivers/gpu/drm/drm_crtc_helper.c | 8 +- drivers/gpu/drm/drm_drv.c | 1 + drivers/gpu/drm/drm_fops.c | 68 +++++++++++++- drivers/gpu/drm/drm_irq.c | 43 +++++++++ drivers/gpu/drm/i915/i915_drv.c | 1 + drivers/gpu/drm/i915/intel_display.c | 25 +++-- include/drm/drm.h | 25 +++++ include/drm/drmP.h | 32 +++++++ include/drm/drm_crtc.h | 27 ++++++ include/drm/drm_crtc_helper.h | 4 - include/drm/drm_mode.h | 16 +++ 12 files changed, 400 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 8fab789..32212e6 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -34,6 +34,8 @@ #include "drmP.h" #include "drm_crtc.h" +#undef set_base + struct drm_prop_enum_list { int type; char *name; @@ -342,6 +344,34 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb) EXPORT_SYMBOL(drm_framebuffer_cleanup); /** + * drm_crtc_async_flip - do a set_base call from a work queue + * @work: work struct + * + * Called when a set_base call is queued by the page flip code. This + * allows the flip ioctl itself to return immediately and allow userspace + * to continue working. + */ +static void drm_crtc_async_flip(struct work_struct *work) +{ + struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_flip); + struct drm_device *dev = crtc->dev; + struct drm_pending_flip *pending; + + BUG_ON(crtc->pending_flip == NULL); + + mutex_lock(&dev->struct_mutex); + crtc->funcs->set_base(crtc, 0, 0, NULL); + + pending = crtc->pending_flip; + crtc->pending_flip = NULL; + + pending->frame = drm_vblank_count(dev, crtc->pipe); + list_add_tail(&pending->link, &dev->flip_list); + + mutex_unlock(&dev->struct_mutex); +} + +/** * drm_crtc_init - Initialise a new CRTC object * @dev: DRM device * @crtc: CRTC object to init @@ -352,17 +382,19 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup); * * Inits a new object created as base part of an driver crtc object. */ -void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, +void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, const struct drm_crtc_funcs *funcs) { crtc->dev = dev; crtc->funcs = funcs; + crtc->pipe = pipe; mutex_lock(&dev->mode_config.mutex); drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC); list_add_tail(&crtc->head, &dev->mode_config.crtc_list); dev->mode_config.num_crtc++; + INIT_WORK(&crtc->async_flip, drm_crtc_async_flip); mutex_unlock(&dev->mode_config.mutex); } EXPORT_SYMBOL(drm_crtc_init); @@ -381,6 +413,9 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; + mutex_lock(&dev->mode_config.mutex); + flush_work(&crtc->async_flip); + if (crtc->gamma_store) { kfree(crtc->gamma_store); crtc->gamma_store = NULL; @@ -388,6 +423,7 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) drm_mode_object_put(dev, &crtc->base); list_del(&crtc->head); + mutex_unlock(&dev->mode_config.mutex); dev->mode_config.num_crtc--; } EXPORT_SYMBOL(drm_crtc_cleanup); @@ -2452,3 +2488,134 @@ out: mutex_unlock(&dev->mode_config.mutex); return ret; } + +/** + * drm_mode_page_flip_ioctl - page flip ioctl + * @dev: DRM device + * @data: ioctl args + * @file_priv: file private data + * + * The page flip ioctl replaces the current front buffer with a new + * one, using the CRTC's set_base function, which should just update + * the front buffer base pointer. It's up to set_base to make + * sure the update doesn't result in tearing (on some hardware the + * base register is double buffered, so this is easy). + * + * Note that this covers just the simple case of flipping the front + * buffer immediately. Interval handling and interlaced modes have to + * be handled by userspace, or with new ioctls. + */ +int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_pending_flip *pending; + struct drm_mode_page_flip *flip_data = data; + struct drm_mode_object *drm_obj, *fb_obj; + struct drm_crtc *crtc; + int ret = 0; + + 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 & (~DRM_MODE_PAGE_FLIP_FLAGS_MASK)) { + DRM_DEBUG("bad page flip flags\n"); + return -EINVAL; + } + + pending = kzalloc(sizeof *pending, GFP_KERNEL); + if (pending == NULL) + return -ENOMEM; + + 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; + } + + drm_obj = drm_mode_object_find(dev, flip_data->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!drm_obj) { + DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_id); + ret = -ENOENT; + goto out_unlock; + } + crtc = obj_to_crtc(drm_obj); + if (!crtc->enabled) { + DRM_DEBUG("crtc %d not enabled\n", flip_data->crtc_id); + ret = -EINVAL; + goto out_unlock; + } + + if (crtc->fb->funcs->unpin == NULL) { + DRM_DEBUG("fb for crtc %d does not support delayed unpin\n", + flip_data->crtc_id); + ret = -ENODEV; + goto out_unlock; + } + + pending->crtc = crtc; + pending->old_fb = crtc->fb; + pending->pipe = crtc->pipe; + pending->event.base.type = DRM_EVENT_MODE_PAGE_FLIP; + pending->event.base.length = sizeof pending->event; + pending->event.user_data = flip_data->user_data; + pending->pending_event.event = &pending->event.base; + pending->pending_event.file_priv = file_priv; + pending->pending_event.destroy = + (void (*) (struct drm_pending_event *)) kfree; + + /* Get vblank ref for completion handling */ + ret = drm_vblank_get(dev, crtc->pipe); + if (ret) { + DRM_DEBUG("failed to take vblank ref\n"); + goto out_unlock; + } + + /* + * The set_base call will change the domain on the new fb, + * which will force the rendering to finish and block the + * ioctl. We need to do this last part from a work queue, to + * avoid blocking userspace here. + */ + crtc->fb = obj_to_fb(fb_obj); + + if (crtc->pending_flip != NULL) { + struct drm_pending_flip *old_flip; + + /* We have an outstanding flip request for this crtc/pipe. + * In order to satisfy the user we can either queue the requests + * and apply them on sequential vblanks, or we can drop old + * requests. + * + * Here we choose to discard the previous request for + * simplicity. Note that since we have not yet applied the + * previous flip, we need to preserve the original (i.e. still + * current) fb. + */ + + old_flip = crtc->pending_flip; + pending->old_fb = old_flip->old_fb; + old_flip->old_fb = NULL; + drm_finish_pending_flip (dev, old_flip, 0); + } else + schedule_work(&crtc->async_flip); + crtc->pending_flip = pending; + + mutex_unlock(&dev->struct_mutex); + + return 0; + +out_unlock: + mutex_unlock(&dev->struct_mutex); + kfree(pending); + + return ret; +} diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index 3da9cfa..7be7003 100644 --- a/drivers/gpu/drm/drm_crtc_helper.c +++ b/drivers/gpu/drm/drm_crtc_helper.c @@ -840,7 +840,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set) } /* mode_set_base is not a required function */ - if (fb_changed && !crtc_funcs->mode_set_base) + if (fb_changed && !set->crtc->funcs->set_base) mode_changed = true; if (mode_changed) { @@ -868,8 +868,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; - ret = crtc_funcs->mode_set_base(set->crtc, - set->x, set->y, old_fb); + mutex_lock(&dev->struct_mutex); + ret = (*set->crtc->funcs->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/drm_drv.c b/drivers/gpu/drm/drm_drv.c index b39d7bf..c66c993 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW), }; #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 251bc0e..dcd9c66 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -257,6 +257,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp, INIT_LIST_HEAD(&priv->lhead); INIT_LIST_HEAD(&priv->fbs); + INIT_LIST_HEAD(&priv->event_list); + init_waitqueue_head(&priv->event_wait); if (dev->driver->driver_features & DRIVER_GEM) drm_gem_open(dev, priv); @@ -429,6 +431,9 @@ int drm_release(struct inode *inode, struct file *filp) { struct drm_file *file_priv = filp->private_data; struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_flip *f, *ft; + struct drm_pending_event *e, *et; + int retcode = 0; lock_kernel(); @@ -451,6 +456,19 @@ int drm_release(struct inode *inode, struct file *filp) if (file_priv->minor->master) drm_master_release(dev, filp); + mutex_lock(&dev->struct_mutex); + + /* Remove pending flips */ + list_for_each_entry_safe(f, ft, &dev->flip_list, link) + if (f->pending_event.file_priv == file_priv) + drm_finish_pending_flip(dev, f, 0); + + /* Remove unconsumed events */ + list_for_each_entry_safe(e, et, &file_priv->event_list, link) + e->destroy(e); + + mutex_unlock(&dev->struct_mutex); + if (dev->driver->driver_features & DRIVER_GEM) drm_gem_release(dev, file_priv); @@ -544,9 +562,55 @@ int drm_release(struct inode *inode, struct file *filp) } EXPORT_SYMBOL(drm_release); -/** No-op. */ +ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_event *event; + ssize_t total, ret; + + ret = wait_event_interruptible(file_priv->event_wait, + !list_empty(&file_priv->event_list)); + if (ret < 0) + return ret; + + total = 0; + while (!list_empty(&file_priv->event_list)) { + mutex_lock(&dev->struct_mutex); + event = list_first_entry(&file_priv->event_list, + struct drm_pending_event, link); + if (total + event->event->length > count) { + mutex_unlock(&dev->struct_mutex); + break; + } + list_del(&event->link); + mutex_unlock(&dev->struct_mutex); + + if (copy_to_user(buffer + total, + event->event, event->event->length)) { + total = -EFAULT; + break; + } + + total += event->event->length; + event->destroy(event); + } + + return total; +} +EXPORT_SYMBOL(drm_read); + unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) { - return 0; + struct drm_file *file_priv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &file_priv->event_wait, wait); + + if (!list_empty(&file_priv->event_list)) + mask |= POLLIN | POLLRDNORM; + + return mask; } EXPORT_SYMBOL(drm_poll); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index b4a3dbc..c7a17f6 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -34,6 +34,7 @@ */ #include "drmP.h" +#include "drm_crtc_helper.h" #include <linux/interrupt.h> /* For task queue support */ @@ -71,6 +72,44 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, return 0; } +#define vblank_passed(a,b) ((long)(a - b) > 0) + +void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame) +{ + struct timeval now; + + f->event.frame = frame; + do_gettimeofday(&now); + f->event.tv_sec = now.tv_sec; + f->event.tv_usec = now.tv_usec; + drm_vblank_put(dev, f->pipe); + list_del_init(&f->link); + list_add_tail(&f->pending_event.link, + &f->pending_event.file_priv->event_list); + if (f->old_fb) + f->old_fb->funcs->unpin(f->old_fb); + wake_up_interruptible(&f->pending_event.file_priv->event_wait); +} + +static void drm_flip_work_func(struct work_struct *work) +{ + struct drm_device *dev = + container_of(work, struct drm_device, flip_work); + struct drm_pending_flip *f, *t; + u32 frame; + + mutex_lock(&dev->struct_mutex); + + list_for_each_entry_safe(f, t, &dev->flip_list, link) { + frame = drm_vblank_count(dev, f->pipe); + if (vblank_passed(frame, f->frame)) + drm_finish_pending_flip(dev, f, frame); + } + + mutex_unlock(&dev->struct_mutex); +} + static void vblank_disable_fn(unsigned long arg) { struct drm_device *dev = (struct drm_device *)arg; @@ -161,6 +200,8 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) atomic_set(&dev->vblank_refcount[i], 0); } + INIT_LIST_HEAD(&dev->flip_list); + INIT_WORK(&dev->flip_work, drm_flip_work_func); dev->vblank_disable_allowed = 0; return 0; @@ -626,5 +667,7 @@ void drm_handle_vblank(struct drm_device *dev, int crtc) { atomic_inc(&dev->_vblank_count[crtc]); DRM_WAKEUP(&dev->vbl_queue[crtc]); + schedule_work(&dev->flip_work); } EXPORT_SYMBOL(drm_handle_vblank); + diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index fc4b68a..322b0f2 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -203,6 +203,7 @@ static struct drm_driver driver = { .mmap = drm_gem_mmap, .poll = drm_poll, .fasync = drm_fasync, + .read = drm_read, #ifdef CONFIG_COMPAT .compat_ioctl = i915_compat_ioctl, #endif diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 508838e..4085e58 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -863,6 +863,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"); @@ -898,17 +900,14 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, BUG(); } - mutex_lock(&dev->struct_mutex); ret = i915_gem_object_pin(obj, alignment); if (ret != 0) { - mutex_unlock(&dev->struct_mutex); return ret; } ret = i915_gem_object_set_to_gtt_domain(obj, 1); if (ret != 0) { i915_gem_object_unpin(obj); - mutex_unlock(&dev->struct_mutex); return ret; } @@ -944,7 +943,6 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, default: DRM_ERROR("Unknown color depth\n"); i915_gem_object_unpin(obj); - mutex_unlock(&dev->struct_mutex); return -EINVAL; } if (IS_I965G(dev)) { @@ -972,13 +970,11 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, I915_READ(dspbase); } - intel_wait_for_vblank(dev); - if (old_fb) { intel_fb = to_intel_framebuffer(old_fb); + intel_wait_for_vblank(dev); i915_gem_object_unpin(intel_fb->obj); } - mutex_unlock(&dev->struct_mutex); if (!dev->primary->master) return 0; @@ -2364,7 +2360,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); intel_update_watermarks(dev); @@ -2829,7 +2827,6 @@ static const struct drm_crtc_helper_funcs intel_helper_funcs = { .dpms = intel_crtc_dpms, .mode_fixup = intel_crtc_mode_fixup, .mode_set = intel_crtc_mode_set, - .mode_set_base = intel_pipe_set_base, .prepare = intel_crtc_prepare, .commit = intel_crtc_commit, }; @@ -2840,6 +2837,7 @@ static const struct drm_crtc_funcs intel_crtc_funcs = { .gamma_set = intel_crtc_gamma_set, .set_config = drm_crtc_helper_set_config, .destroy = intel_crtc_destroy, + .set_base = intel_pipe_set_base, }; @@ -2852,7 +2850,7 @@ static void intel_crtc_init(struct drm_device *dev, int pipe) if (intel_crtc == NULL) return; - drm_crtc_init(dev, &intel_crtc->base, &intel_crtc_funcs); + drm_crtc_init(dev, &intel_crtc->base, pipe, &intel_crtc_funcs); drm_mode_crtc_set_gamma_size(&intel_crtc->base, 256); intel_crtc->pipe = pipe; @@ -3071,9 +3069,18 @@ static int intel_user_framebuffer_create_handle(struct drm_framebuffer *fb, return drm_gem_handle_create(file_priv, object, handle); } +static void intel_user_framebuffer_unpin(struct drm_framebuffer *fb) +{ + struct intel_framebuffer *intel_fb; + + intel_fb = to_intel_framebuffer(fb); + i915_gem_object_unpin(intel_fb->obj); +} + static const struct drm_framebuffer_funcs intel_fb_funcs = { .destroy = intel_user_framebuffer_destroy, .create_handle = intel_user_framebuffer_create_handle, + .unpin = intel_user_framebuffer_unpin }; int intel_framebuffer_create(struct drm_device *dev, diff --git a/include/drm/drm.h b/include/drm/drm.h index 7cb50bd..1920323 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -686,6 +686,7 @@ struct drm_gem_open { #define DRM_IOCTL_MODE_GETFB DRM_IOWR(0xAD, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) +#define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOW( 0xB0, struct drm_mode_page_flip) /** * Device specific ioctls should only be in their respective headers @@ -698,6 +699,30 @@ struct drm_gem_open { #define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_END 0xA0 +/** + * Header for events written back to userspace on the drm fd. The + * type defines the type of event, the length specifies the total + * length of the event (including the header), and user_data is + * typically a 64 bit value passed with the ioctl that triggered the + * event. A read on the drm fd will always only return complete + * events, that is, if for example the read buffer is 100 bytes, and + * there are two 64 byte events pending, only one will be returned. + */ +struct drm_event { + __u32 type; + __u32 length; +}; + +#define DRM_EVENT_MODE_PAGE_FLIP 0x01 + +struct drm_event_page_flip { + struct drm_event base; + __u64 user_data; + __u32 tv_sec; + __u32 tv_usec; + __u32 frame; +}; + /* typedef area */ #ifndef __KERNEL__ typedef struct drm_clip_rect drm_clip_rect_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 45b67d9..4ff43ab 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -402,6 +402,14 @@ struct drm_buf_entry { struct drm_freelist freelist; }; +/* Event queued up for userspace to read */ +struct drm_pending_event { + struct drm_event *event; + struct list_head link; + struct drm_file *file_priv; + void (*destroy) (struct drm_pending_event *event); +}; + /** File private data */ struct drm_file { int authenticated; @@ -425,6 +433,9 @@ struct drm_file { struct drm_master *master; /* master this node is currently associated with N.B. not always minor->master */ struct list_head fbs; + + wait_queue_head_t event_wait; + struct list_head event_list; }; /** Wait queue */ @@ -873,6 +884,16 @@ struct drm_minor { struct drm_mode_group mode_group; }; +struct drm_pending_flip { + struct drm_pending_event pending_event; + struct drm_framebuffer *old_fb; + struct drm_crtc *crtc; + u32 frame; + int pipe; + struct list_head link; + struct drm_event_page_flip event; +}; + /** * DRM device structure. This structure represent a complete card that * may contain multiple heads. @@ -972,6 +993,13 @@ struct drm_device { u32 max_vblank_count; /**< size of vblank counter register */ + struct work_struct flip_work; + + /** + * List of objects waiting on flip completion + */ + struct list_head flip_list; + /*@} */ cycles_t ctx_start; cycles_t lck_start; @@ -1108,6 +1136,8 @@ extern int drm_lastclose(struct drm_device *dev); extern int drm_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_fasync(int fd, struct file *filp, int on); +extern ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset); extern int drm_release(struct inode *inode, struct file *filp); /* Mapping support (drm_vm.h) */ @@ -1274,6 +1304,8 @@ extern void drm_vblank_pre_modeset(struct drm_device *dev, int crtc); extern void drm_vblank_post_modeset(struct drm_device *dev, int crtc); extern int drm_modeset_ctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +extern void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame); /* AGP/GART support (drm_agpsupport.h) */ extern struct drm_agp_head *drm_agp_init(struct drm_device *dev); diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 7300fb8..0b5dc47 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -238,6 +238,12 @@ struct drm_display_info { }; struct drm_framebuffer_funcs { + /* + * Unpin the old fb after setting a mode. Must be called + * after the old framebuffer is no longer visible, ie, after + * the next vblank, typically. + */ + void (*unpin)(struct drm_framebuffer *fb); void (*destroy)(struct drm_framebuffer *framebuffer); int (*create_handle)(struct drm_framebuffer *fb, struct drm_file *file_priv, @@ -288,6 +294,7 @@ struct drm_property { struct drm_crtc; struct drm_connector; struct drm_encoder; +struct drm_pending_flip; /** * drm_crtc_funcs - control CRTCs for a given device @@ -331,17 +338,29 @@ struct drm_crtc_funcs { void (*destroy)(struct drm_crtc *crtc); int (*set_config)(struct drm_mode_set *set); + + /* + * Move the crtc on the current fb to the given position. + * This function is optional. If old_fb is provided, the + * function will wait for vblank and unpin it. If old_fb is + * NULL, nothing is unpinned and the caller must call + * mode_unpin_fb to release the old framebuffer. + */ + int (*set_base)(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb); }; /** * drm_crtc - central CRTC control structure * @enabled: is this CRTC enabled? + * @pipe: pipe number (as seen by DRM vblank functions) * @x: x position on screen * @y: y position on screen * @desired_mode: new desired mode * @desired_x: desired x for desired_mode * @desired_y: desired y for desired_mode * @funcs: CRTC control functions + * @async_work: work queue for async set base calls * * Each CRTC may have one or more connectors associated with it. This structure * allows the CRTC to be controlled. @@ -359,6 +378,7 @@ struct drm_crtc { struct drm_display_mode mode; + int pipe; int x, y; struct drm_display_mode *desired_mode; int desired_x, desired_y; @@ -368,6 +388,10 @@ struct drm_crtc { uint32_t gamma_size; uint16_t *gamma_store; + /* Allow async set_pipe_base calls for flipping */ + struct work_struct async_flip; + struct drm_pending_flip *pending_flip; + /* if you are using the helper */ void *helper_private; }; @@ -589,6 +613,7 @@ struct drm_mode_config { extern void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, + int pipe, const struct drm_crtc_funcs *funcs); extern void drm_crtc_cleanup(struct drm_crtc *crtc); @@ -736,4 +761,6 @@ extern int drm_mode_gamma_get_ioctl(struct drm_device *dev, extern int drm_mode_gamma_set_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern bool drm_detect_hdmi_monitor(struct edid *edid); +extern int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); #endif /* __DRM_CRTC_H__ */ diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h index 6769ff6..8173146 100644 --- a/include/drm/drm_crtc_helper.h +++ b/include/drm/drm_crtc_helper.h @@ -56,10 +56,6 @@ struct drm_crtc_helper_funcs { int (*mode_set)(struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode, int x, int y, struct drm_framebuffer *old_fb); - - /* Move the crtc on the current fb to the given position *optional* */ - int (*mode_set_base)(struct drm_crtc *crtc, int x, int y, - struct drm_framebuffer *old_fb); }; struct drm_encoder_helper_funcs { diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index ae304cc..464b779 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -265,4 +265,20 @@ struct drm_mode_crtc_lut { __u64 blue; }; +#define DRM_MODE_PAGE_FLIP_WAIT (1<<0) /* block on previous page flip */ +#define DRM_MODE_PAGE_FLIP_FLAGS_MASK (DRM_MODE_PAGE_FLIP_WAIT) + +struct drm_mode_page_flip { + /** Handle of new front buffer */ + __u32 fb_id; + __u32 crtc_id; + + /* 64 bit cookie returned to userspace in the page flip event. */ + __u64 user_data; + /** + * page flip flags (wait on flip only for now) + */ + __u32 flags; +}; + #endif -- 1.6.2.2 |
From: Kristian H. <kr...@re...> - 2009-07-16 19:03:50
|
From: Kristian Høgsberg <krh@hinata.local> This patch adds a vblank synced pageflip ioctl for to the modesetting family of ioctls. The ioctl takes a crtc and an fb and schedules a pageflip to the new fb at the next coming vertical blank event. This feature lets userspace implement tear-free updating of the screen contents with hw-guaranteed low latency page flipping. The ioctl is asynchronous in that it returns immediately and then later notifies the client by making an event available for reading on the drm fd. This lets applications add the drm fd to their main loop and handle other tasks while waiting for the flip to happen. The event includes the time of the flip, the frame counter and a 64 bit opaque token provided by user space in the ioctl. Based on work and suggestions from Jesse Barnes <jb...@vi...>, Jakob Bornecrantz <wal...@gm...>, Chris Wilson <ch...@ch...> Signed-off-by: Kristian Høgsberg <kr...@re...> --- Ok, here's the latest roll-up of the page flipping patch with the fixes from Jesse and Chris. drivers/gpu/drm/drm_crtc.c | 169 +++++++++++++++++++++++++++++++++- drivers/gpu/drm/drm_crtc_helper.c | 8 +- drivers/gpu/drm/drm_drv.c | 1 + drivers/gpu/drm/drm_fops.c | 68 +++++++++++++- drivers/gpu/drm/drm_irq.c | 43 +++++++++ drivers/gpu/drm/i915/i915_drv.c | 1 + drivers/gpu/drm/i915/intel_display.c | 25 +++-- include/drm/drm.h | 25 +++++ include/drm/drmP.h | 32 +++++++ include/drm/drm_crtc.h | 27 ++++++ include/drm/drm_crtc_helper.h | 4 - include/drm/drm_mode.h | 16 +++ 12 files changed, 400 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 8fab789..32212e6 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -34,6 +34,8 @@ #include "drmP.h" #include "drm_crtc.h" +#undef set_base + struct drm_prop_enum_list { int type; char *name; @@ -342,6 +344,34 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb) EXPORT_SYMBOL(drm_framebuffer_cleanup); /** + * drm_crtc_async_flip - do a set_base call from a work queue + * @work: work struct + * + * Called when a set_base call is queued by the page flip code. This + * allows the flip ioctl itself to return immediately and allow userspace + * to continue working. + */ +static void drm_crtc_async_flip(struct work_struct *work) +{ + struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_flip); + struct drm_device *dev = crtc->dev; + struct drm_pending_flip *pending; + + BUG_ON(crtc->pending_flip == NULL); + + mutex_lock(&dev->struct_mutex); + crtc->funcs->set_base(crtc, 0, 0, NULL); + + pending = crtc->pending_flip; + crtc->pending_flip = NULL; + + pending->frame = drm_vblank_count(dev, crtc->pipe); + list_add_tail(&pending->link, &dev->flip_list); + + mutex_unlock(&dev->struct_mutex); +} + +/** * drm_crtc_init - Initialise a new CRTC object * @dev: DRM device * @crtc: CRTC object to init @@ -352,17 +382,19 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup); * * Inits a new object created as base part of an driver crtc object. */ -void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, +void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, const struct drm_crtc_funcs *funcs) { crtc->dev = dev; crtc->funcs = funcs; + crtc->pipe = pipe; mutex_lock(&dev->mode_config.mutex); drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC); list_add_tail(&crtc->head, &dev->mode_config.crtc_list); dev->mode_config.num_crtc++; + INIT_WORK(&crtc->async_flip, drm_crtc_async_flip); mutex_unlock(&dev->mode_config.mutex); } EXPORT_SYMBOL(drm_crtc_init); @@ -381,6 +413,9 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; + mutex_lock(&dev->mode_config.mutex); + flush_work(&crtc->async_flip); + if (crtc->gamma_store) { kfree(crtc->gamma_store); crtc->gamma_store = NULL; @@ -388,6 +423,7 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) drm_mode_object_put(dev, &crtc->base); list_del(&crtc->head); + mutex_unlock(&dev->mode_config.mutex); dev->mode_config.num_crtc--; } EXPORT_SYMBOL(drm_crtc_cleanup); @@ -2452,3 +2488,134 @@ out: mutex_unlock(&dev->mode_config.mutex); return ret; } + +/** + * drm_mode_page_flip_ioctl - page flip ioctl + * @dev: DRM device + * @data: ioctl args + * @file_priv: file private data + * + * The page flip ioctl replaces the current front buffer with a new + * one, using the CRTC's set_base function, which should just update + * the front buffer base pointer. It's up to set_base to make + * sure the update doesn't result in tearing (on some hardware the + * base register is double buffered, so this is easy). + * + * Note that this covers just the simple case of flipping the front + * buffer immediately. Interval handling and interlaced modes have to + * be handled by userspace, or with new ioctls. + */ +int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_pending_flip *pending; + struct drm_mode_page_flip *flip_data = data; + struct drm_mode_object *drm_obj, *fb_obj; + struct drm_crtc *crtc; + int ret = 0; + + 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 & (~DRM_MODE_PAGE_FLIP_FLAGS_MASK)) { + DRM_DEBUG("bad page flip flags\n"); + return -EINVAL; + } + + pending = kzalloc(sizeof *pending, GFP_KERNEL); + if (pending == NULL) + return -ENOMEM; + + 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; + } + + drm_obj = drm_mode_object_find(dev, flip_data->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!drm_obj) { + DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_id); + ret = -ENOENT; + goto out_unlock; + } + crtc = obj_to_crtc(drm_obj); + if (!crtc->enabled) { + DRM_DEBUG("crtc %d not enabled\n", flip_data->crtc_id); + ret = -EINVAL; + goto out_unlock; + } + + if (crtc->fb->funcs->unpin == NULL) { + DRM_DEBUG("fb for crtc %d does not support delayed unpin\n", + flip_data->crtc_id); + ret = -ENODEV; + goto out_unlock; + } + + pending->crtc = crtc; + pending->old_fb = crtc->fb; + pending->pipe = crtc->pipe; + pending->event.base.type = DRM_EVENT_MODE_PAGE_FLIP; + pending->event.base.length = sizeof pending->event; + pending->event.user_data = flip_data->user_data; + pending->pending_event.event = &pending->event.base; + pending->pending_event.file_priv = file_priv; + pending->pending_event.destroy = + (void (*) (struct drm_pending_event *)) kfree; + + /* Get vblank ref for completion handling */ + ret = drm_vblank_get(dev, crtc->pipe); + if (ret) { + DRM_DEBUG("failed to take vblank ref\n"); + goto out_unlock; + } + + /* + * The set_base call will change the domain on the new fb, + * which will force the rendering to finish and block the + * ioctl. We need to do this last part from a work queue, to + * avoid blocking userspace here. + */ + crtc->fb = obj_to_fb(fb_obj); + + if (crtc->pending_flip != NULL) { + struct drm_pending_flip *old_flip; + + /* We have an outstanding flip request for this crtc/pipe. + * In order to satisfy the user we can either queue the requests + * and apply them on sequential vblanks, or we can drop old + * requests. + * + * Here we choose to discard the previous request for + * simplicity. Note that since we have not yet applied the + * previous flip, we need to preserve the original (i.e. still + * current) fb. + */ + + old_flip = crtc->pending_flip; + pending->old_fb = old_flip->old_fb; + old_flip->old_fb = NULL; + drm_finish_pending_flip (dev, old_flip, 0); + } else + schedule_work(&crtc->async_flip); + crtc->pending_flip = pending; + + mutex_unlock(&dev->struct_mutex); + + return 0; + +out_unlock: + mutex_unlock(&dev->struct_mutex); + kfree(pending); + + return ret; +} diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index 3da9cfa..7be7003 100644 --- a/drivers/gpu/drm/drm_crtc_helper.c +++ b/drivers/gpu/drm/drm_crtc_helper.c @@ -840,7 +840,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set) } /* mode_set_base is not a required function */ - if (fb_changed && !crtc_funcs->mode_set_base) + if (fb_changed && !set->crtc->funcs->set_base) mode_changed = true; if (mode_changed) { @@ -868,8 +868,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; - ret = crtc_funcs->mode_set_base(set->crtc, - set->x, set->y, old_fb); + mutex_lock(&dev->struct_mutex); + ret = (*set->crtc->funcs->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/drm_drv.c b/drivers/gpu/drm/drm_drv.c index b39d7bf..c66c993 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW), }; #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 251bc0e..dcd9c66 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -257,6 +257,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp, INIT_LIST_HEAD(&priv->lhead); INIT_LIST_HEAD(&priv->fbs); + INIT_LIST_HEAD(&priv->event_list); + init_waitqueue_head(&priv->event_wait); if (dev->driver->driver_features & DRIVER_GEM) drm_gem_open(dev, priv); @@ -429,6 +431,9 @@ int drm_release(struct inode *inode, struct file *filp) { struct drm_file *file_priv = filp->private_data; struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_flip *f, *ft; + struct drm_pending_event *e, *et; + int retcode = 0; lock_kernel(); @@ -451,6 +456,19 @@ int drm_release(struct inode *inode, struct file *filp) if (file_priv->minor->master) drm_master_release(dev, filp); + mutex_lock(&dev->struct_mutex); + + /* Remove pending flips */ + list_for_each_entry_safe(f, ft, &dev->flip_list, link) + if (f->pending_event.file_priv == file_priv) + drm_finish_pending_flip(dev, f, 0); + + /* Remove unconsumed events */ + list_for_each_entry_safe(e, et, &file_priv->event_list, link) + e->destroy(e); + + mutex_unlock(&dev->struct_mutex); + if (dev->driver->driver_features & DRIVER_GEM) drm_gem_release(dev, file_priv); @@ -544,9 +562,55 @@ int drm_release(struct inode *inode, struct file *filp) } EXPORT_SYMBOL(drm_release); -/** No-op. */ +ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_event *event; + ssize_t total, ret; + + ret = wait_event_interruptible(file_priv->event_wait, + !list_empty(&file_priv->event_list)); + if (ret < 0) + return ret; + + total = 0; + while (!list_empty(&file_priv->event_list)) { + mutex_lock(&dev->struct_mutex); + event = list_first_entry(&file_priv->event_list, + struct drm_pending_event, link); + if (total + event->event->length > count) { + mutex_unlock(&dev->struct_mutex); + break; + } + list_del(&event->link); + mutex_unlock(&dev->struct_mutex); + + if (copy_to_user(buffer + total, + event->event, event->event->length)) { + total = -EFAULT; + break; + } + + total += event->event->length; + event->destroy(event); + } + + return total; +} +EXPORT_SYMBOL(drm_read); + unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) { - return 0; + struct drm_file *file_priv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &file_priv->event_wait, wait); + + if (!list_empty(&file_priv->event_list)) + mask |= POLLIN | POLLRDNORM; + + return mask; } EXPORT_SYMBOL(drm_poll); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index b4a3dbc..c7a17f6 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -34,6 +34,7 @@ */ #include "drmP.h" +#include "drm_crtc_helper.h" #include <linux/interrupt.h> /* For task queue support */ @@ -71,6 +72,44 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, return 0; } +#define vblank_passed(a,b) ((long)(a - b) > 0) + +void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame) +{ + struct timeval now; + + f->event.frame = frame; + do_gettimeofday(&now); + f->event.tv_sec = now.tv_sec; + f->event.tv_usec = now.tv_usec; + drm_vblank_put(dev, f->pipe); + list_del_init(&f->link); + list_add_tail(&f->pending_event.link, + &f->pending_event.file_priv->event_list); + if (f->old_fb) + f->old_fb->funcs->unpin(f->old_fb); + wake_up_interruptible(&f->pending_event.file_priv->event_wait); +} + +static void drm_flip_work_func(struct work_struct *work) +{ + struct drm_device *dev = + container_of(work, struct drm_device, flip_work); + struct drm_pending_flip *f, *t; + u32 frame; + + mutex_lock(&dev->struct_mutex); + + list_for_each_entry_safe(f, t, &dev->flip_list, link) { + frame = drm_vblank_count(dev, f->pipe); + if (vblank_passed(frame, f->frame)) + drm_finish_pending_flip(dev, f, frame); + } + + mutex_unlock(&dev->struct_mutex); +} + static void vblank_disable_fn(unsigned long arg) { struct drm_device *dev = (struct drm_device *)arg; @@ -161,6 +200,8 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) atomic_set(&dev->vblank_refcount[i], 0); } + INIT_LIST_HEAD(&dev->flip_list); + INIT_WORK(&dev->flip_work, drm_flip_work_func); dev->vblank_disable_allowed = 0; return 0; @@ -626,5 +667,7 @@ void drm_handle_vblank(struct drm_device *dev, int crtc) { atomic_inc(&dev->_vblank_count[crtc]); DRM_WAKEUP(&dev->vbl_queue[crtc]); + schedule_work(&dev->flip_work); } EXPORT_SYMBOL(drm_handle_vblank); + diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index fc4b68a..322b0f2 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -203,6 +203,7 @@ static struct drm_driver driver = { .mmap = drm_gem_mmap, .poll = drm_poll, .fasync = drm_fasync, + .read = drm_read, #ifdef CONFIG_COMPAT .compat_ioctl = i915_compat_ioctl, #endif diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 508838e..4085e58 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -863,6 +863,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"); @@ -898,17 +900,14 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, BUG(); } - mutex_lock(&dev->struct_mutex); ret = i915_gem_object_pin(obj, alignment); if (ret != 0) { - mutex_unlock(&dev->struct_mutex); return ret; } ret = i915_gem_object_set_to_gtt_domain(obj, 1); if (ret != 0) { i915_gem_object_unpin(obj); - mutex_unlock(&dev->struct_mutex); return ret; } @@ -944,7 +943,6 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, default: DRM_ERROR("Unknown color depth\n"); i915_gem_object_unpin(obj); - mutex_unlock(&dev->struct_mutex); return -EINVAL; } if (IS_I965G(dev)) { @@ -972,13 +970,11 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, I915_READ(dspbase); } - intel_wait_for_vblank(dev); - if (old_fb) { intel_fb = to_intel_framebuffer(old_fb); + intel_wait_for_vblank(dev); i915_gem_object_unpin(intel_fb->obj); } - mutex_unlock(&dev->struct_mutex); if (!dev->primary->master) return 0; @@ -2364,7 +2360,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); intel_update_watermarks(dev); @@ -2829,7 +2827,6 @@ static const struct drm_crtc_helper_funcs intel_helper_funcs = { .dpms = intel_crtc_dpms, .mode_fixup = intel_crtc_mode_fixup, .mode_set = intel_crtc_mode_set, - .mode_set_base = intel_pipe_set_base, .prepare = intel_crtc_prepare, .commit = intel_crtc_commit, }; @@ -2840,6 +2837,7 @@ static const struct drm_crtc_funcs intel_crtc_funcs = { .gamma_set = intel_crtc_gamma_set, .set_config = drm_crtc_helper_set_config, .destroy = intel_crtc_destroy, + .set_base = intel_pipe_set_base, }; @@ -2852,7 +2850,7 @@ static void intel_crtc_init(struct drm_device *dev, int pipe) if (intel_crtc == NULL) return; - drm_crtc_init(dev, &intel_crtc->base, &intel_crtc_funcs); + drm_crtc_init(dev, &intel_crtc->base, pipe, &intel_crtc_funcs); drm_mode_crtc_set_gamma_size(&intel_crtc->base, 256); intel_crtc->pipe = pipe; @@ -3071,9 +3069,18 @@ static int intel_user_framebuffer_create_handle(struct drm_framebuffer *fb, return drm_gem_handle_create(file_priv, object, handle); } +static void intel_user_framebuffer_unpin(struct drm_framebuffer *fb) +{ + struct intel_framebuffer *intel_fb; + + intel_fb = to_intel_framebuffer(fb); + i915_gem_object_unpin(intel_fb->obj); +} + static const struct drm_framebuffer_funcs intel_fb_funcs = { .destroy = intel_user_framebuffer_destroy, .create_handle = intel_user_framebuffer_create_handle, + .unpin = intel_user_framebuffer_unpin }; int intel_framebuffer_create(struct drm_device *dev, diff --git a/include/drm/drm.h b/include/drm/drm.h index 7cb50bd..1920323 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -686,6 +686,7 @@ struct drm_gem_open { #define DRM_IOCTL_MODE_GETFB DRM_IOWR(0xAD, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) +#define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOW( 0xB0, struct drm_mode_page_flip) /** * Device specific ioctls should only be in their respective headers @@ -698,6 +699,30 @@ struct drm_gem_open { #define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_END 0xA0 +/** + * Header for events written back to userspace on the drm fd. The + * type defines the type of event, the length specifies the total + * length of the event (including the header), and user_data is + * typically a 64 bit value passed with the ioctl that triggered the + * event. A read on the drm fd will always only return complete + * events, that is, if for example the read buffer is 100 bytes, and + * there are two 64 byte events pending, only one will be returned. + */ +struct drm_event { + __u32 type; + __u32 length; +}; + +#define DRM_EVENT_MODE_PAGE_FLIP 0x01 + +struct drm_event_page_flip { + struct drm_event base; + __u64 user_data; + __u32 tv_sec; + __u32 tv_usec; + __u32 frame; +}; + /* typedef area */ #ifndef __KERNEL__ typedef struct drm_clip_rect drm_clip_rect_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 45b67d9..4ff43ab 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -402,6 +402,14 @@ struct drm_buf_entry { struct drm_freelist freelist; }; +/* Event queued up for userspace to read */ +struct drm_pending_event { + struct drm_event *event; + struct list_head link; + struct drm_file *file_priv; + void (*destroy) (struct drm_pending_event *event); +}; + /** File private data */ struct drm_file { int authenticated; @@ -425,6 +433,9 @@ struct drm_file { struct drm_master *master; /* master this node is currently associated with N.B. not always minor->master */ struct list_head fbs; + + wait_queue_head_t event_wait; + struct list_head event_list; }; /** Wait queue */ @@ -873,6 +884,16 @@ struct drm_minor { struct drm_mode_group mode_group; }; +struct drm_pending_flip { + struct drm_pending_event pending_event; + struct drm_framebuffer *old_fb; + struct drm_crtc *crtc; + u32 frame; + int pipe; + struct list_head link; + struct drm_event_page_flip event; +}; + /** * DRM device structure. This structure represent a complete card that * may contain multiple heads. @@ -972,6 +993,13 @@ struct drm_device { u32 max_vblank_count; /**< size of vblank counter register */ + struct work_struct flip_work; + + /** + * List of objects waiting on flip completion + */ + struct list_head flip_list; + /*@} */ cycles_t ctx_start; cycles_t lck_start; @@ -1108,6 +1136,8 @@ extern int drm_lastclose(struct drm_device *dev); extern int drm_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_fasync(int fd, struct file *filp, int on); +extern ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset); extern int drm_release(struct inode *inode, struct file *filp); /* Mapping support (drm_vm.h) */ @@ -1274,6 +1304,8 @@ extern void drm_vblank_pre_modeset(struct drm_device *dev, int crtc); extern void drm_vblank_post_modeset(struct drm_device *dev, int crtc); extern int drm_modeset_ctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +extern void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame); /* AGP/GART support (drm_agpsupport.h) */ extern struct drm_agp_head *drm_agp_init(struct drm_device *dev); diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 7300fb8..0b5dc47 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -238,6 +238,12 @@ struct drm_display_info { }; struct drm_framebuffer_funcs { + /* + * Unpin the old fb after setting a mode. Must be called + * after the old framebuffer is no longer visible, ie, after + * the next vblank, typically. + */ + void (*unpin)(struct drm_framebuffer *fb); void (*destroy)(struct drm_framebuffer *framebuffer); int (*create_handle)(struct drm_framebuffer *fb, struct drm_file *file_priv, @@ -288,6 +294,7 @@ struct drm_property { struct drm_crtc; struct drm_connector; struct drm_encoder; +struct drm_pending_flip; /** * drm_crtc_funcs - control CRTCs for a given device @@ -331,17 +338,29 @@ struct drm_crtc_funcs { void (*destroy)(struct drm_crtc *crtc); int (*set_config)(struct drm_mode_set *set); + + /* + * Move the crtc on the current fb to the given position. + * This function is optional. If old_fb is provided, the + * function will wait for vblank and unpin it. If old_fb is + * NULL, nothing is unpinned and the caller must call + * mode_unpin_fb to release the old framebuffer. + */ + int (*set_base)(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb); }; /** * drm_crtc - central CRTC control structure * @enabled: is this CRTC enabled? + * @pipe: pipe number (as seen by DRM vblank functions) * @x: x position on screen * @y: y position on screen * @desired_mode: new desired mode * @desired_x: desired x for desired_mode * @desired_y: desired y for desired_mode * @funcs: CRTC control functions + * @async_work: work queue for async set base calls * * Each CRTC may have one or more connectors associated with it. This structure * allows the CRTC to be controlled. @@ -359,6 +378,7 @@ struct drm_crtc { struct drm_display_mode mode; + int pipe; int x, y; struct drm_display_mode *desired_mode; int desired_x, desired_y; @@ -368,6 +388,10 @@ struct drm_crtc { uint32_t gamma_size; uint16_t *gamma_store; + /* Allow async set_pipe_base calls for flipping */ + struct work_struct async_flip; + struct drm_pending_flip *pending_flip; + /* if you are using the helper */ void *helper_private; }; @@ -589,6 +613,7 @@ struct drm_mode_config { extern void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, + int pipe, const struct drm_crtc_funcs *funcs); extern void drm_crtc_cleanup(struct drm_crtc *crtc); @@ -736,4 +761,6 @@ extern int drm_mode_gamma_get_ioctl(struct drm_device *dev, extern int drm_mode_gamma_set_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern bool drm_detect_hdmi_monitor(struct edid *edid); +extern int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); #endif /* __DRM_CRTC_H__ */ diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h index 6769ff6..8173146 100644 --- a/include/drm/drm_crtc_helper.h +++ b/include/drm/drm_crtc_helper.h @@ -56,10 +56,6 @@ struct drm_crtc_helper_funcs { int (*mode_set)(struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode, int x, int y, struct drm_framebuffer *old_fb); - - /* Move the crtc on the current fb to the given position *optional* */ - int (*mode_set_base)(struct drm_crtc *crtc, int x, int y, - struct drm_framebuffer *old_fb); }; struct drm_encoder_helper_funcs { diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index ae304cc..464b779 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -265,4 +265,20 @@ struct drm_mode_crtc_lut { __u64 blue; }; +#define DRM_MODE_PAGE_FLIP_WAIT (1<<0) /* block on previous page flip */ +#define DRM_MODE_PAGE_FLIP_FLAGS_MASK (DRM_MODE_PAGE_FLIP_WAIT) + +struct drm_mode_page_flip { + /** Handle of new front buffer */ + __u32 fb_id; + __u32 crtc_id; + + /* 64 bit cookie returned to userspace in the page flip event. */ + __u64 user_data; + /** + * page flip flags (wait on flip only for now) + */ + __u32 flags; +}; + #endif -- 1.6.2.2 |
From: Kristian H. <kr...@bi...> - 2009-08-17 16:38:27
|
This patch adds a vblank synced pageflip ioctl for to the modesetting family of ioctls. The ioctl takes a crtc and an fb and schedules a pageflip to the new fb at the next coming vertical blank event. This feature lets userspace implement tear-free updating of the screen contents with hw-guaranteed low latency page flipping. The ioctl is asynchronous in that it returns immediately and then later notifies the client by making an event available for reading on the drm fd. This lets applications add the drm fd to their main loop and handle other tasks while waiting for the flip to happen. The event includes the time of the flip, the frame counter and a 64 bit opaque token provided by user space in the ioctl. Based on work and suggestions from Jesse Barnes <jb...@vi...>, Jakob Bornecrantz <wal...@gm...>, Chris Wilson <ch...@ch...> Signed-off-by: Kristian Høgsberg <kr...@re...> Signed-off-by: Jesse Barnes <jb...@vi...> --- Ok, another version of this patch. This one has fixes to work with radeon kms plus a missing list head init that would cause an oops in the case where we schedule a flip before the previous one has been queued. I'm now ready to propose this patch for the 2.6.32 merge window. Kristian drivers/gpu/drm/drm_crtc.c | 170 ++++++++++++++++++++++++++++++- drivers/gpu/drm/drm_crtc_helper.c | 12 ++ drivers/gpu/drm/drm_drv.c | 1 + drivers/gpu/drm/drm_fops.c | 68 ++++++++++++- drivers/gpu/drm/drm_irq.c | 43 ++++++++ drivers/gpu/drm/i915/i915_drv.c | 1 + drivers/gpu/drm/i915/intel_display.c | 24 +++-- drivers/gpu/drm/radeon/radeon_display.c | 3 +- include/drm/drm.h | 25 +++++ include/drm/drmP.h | 32 ++++++ include/drm/drm_crtc.h | 27 +++++ include/drm/drm_crtc_helper.h | 4 + include/drm/drm_mode.h | 16 +++ 13 files changed, 414 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 8fab789..0906cb3 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -34,6 +34,8 @@ #include "drmP.h" #include "drm_crtc.h" +#undef set_base + struct drm_prop_enum_list { int type; char *name; @@ -342,6 +344,34 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb) EXPORT_SYMBOL(drm_framebuffer_cleanup); /** + * drm_crtc_async_flip - do a set_base call from a work queue + * @work: work struct + * + * Called when a set_base call is queued by the page flip code. This + * allows the flip ioctl itself to return immediately and allow userspace + * to continue working. + */ +static void drm_crtc_async_flip(struct work_struct *work) +{ + struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_flip); + struct drm_device *dev = crtc->dev; + struct drm_pending_flip *pending; + + BUG_ON(crtc->pending_flip == NULL); + + mutex_lock(&dev->struct_mutex); + crtc->funcs->set_base(crtc, crtc->x, crtc->y, NULL); + + pending = crtc->pending_flip; + crtc->pending_flip = NULL; + + pending->frame = drm_vblank_count(dev, crtc->pipe); + list_add_tail(&pending->link, &dev->flip_list); + + mutex_unlock(&dev->struct_mutex); +} + +/** * drm_crtc_init - Initialise a new CRTC object * @dev: DRM device * @crtc: CRTC object to init @@ -352,17 +382,19 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup); * * Inits a new object created as base part of an driver crtc object. */ -void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, +void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, const struct drm_crtc_funcs *funcs) { crtc->dev = dev; crtc->funcs = funcs; + crtc->pipe = pipe; mutex_lock(&dev->mode_config.mutex); drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC); list_add_tail(&crtc->head, &dev->mode_config.crtc_list); dev->mode_config.num_crtc++; + INIT_WORK(&crtc->async_flip, drm_crtc_async_flip); mutex_unlock(&dev->mode_config.mutex); } EXPORT_SYMBOL(drm_crtc_init); @@ -381,6 +413,9 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; + mutex_lock(&dev->mode_config.mutex); + flush_work(&crtc->async_flip); + if (crtc->gamma_store) { kfree(crtc->gamma_store); crtc->gamma_store = NULL; @@ -388,6 +423,7 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) drm_mode_object_put(dev, &crtc->base); list_del(&crtc->head); + mutex_unlock(&dev->mode_config.mutex); dev->mode_config.num_crtc--; } EXPORT_SYMBOL(drm_crtc_cleanup); @@ -2452,3 +2488,135 @@ out: mutex_unlock(&dev->mode_config.mutex); return ret; } + +/** + * drm_mode_page_flip_ioctl - page flip ioctl + * @dev: DRM device + * @data: ioctl args + * @file_priv: file private data + * + * The page flip ioctl replaces the current front buffer with a new + * one, using the CRTC's set_base function, which should just update + * the front buffer base pointer. It's up to set_base to make + * sure the update doesn't result in tearing (on some hardware the + * base register is double buffered, so this is easy). + * + * Note that this covers just the simple case of flipping the front + * buffer immediately. Interval handling and interlaced modes have to + * be handled by userspace, or with new ioctls. + */ +int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_pending_flip *pending; + struct drm_mode_page_flip *flip_data = data; + struct drm_mode_object *drm_obj, *fb_obj; + struct drm_crtc *crtc; + int ret = 0; + + 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 & (~DRM_MODE_PAGE_FLIP_FLAGS_MASK)) { + DRM_DEBUG("bad page flip flags\n"); + return -EINVAL; + } + + pending = kzalloc(sizeof *pending, GFP_KERNEL); + if (pending == NULL) + return -ENOMEM; + + 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; + } + + drm_obj = drm_mode_object_find(dev, flip_data->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!drm_obj) { + DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_id); + ret = -ENOENT; + goto out_unlock; + } + crtc = obj_to_crtc(drm_obj); + if (!crtc->enabled) { + DRM_DEBUG("crtc %d not enabled\n", flip_data->crtc_id); + ret = -EINVAL; + goto out_unlock; + } + + if (crtc->fb->funcs->unpin == NULL) { + DRM_DEBUG("fb for crtc %d does not support delayed unpin\n", + flip_data->crtc_id); + ret = -ENODEV; + goto out_unlock; + } + + pending->crtc = crtc; + pending->old_fb = crtc->fb; + pending->pipe = crtc->pipe; + INIT_LIST_HEAD(&pending->link); + pending->event.base.type = DRM_EVENT_MODE_PAGE_FLIP; + pending->event.base.length = sizeof pending->event; + pending->event.user_data = flip_data->user_data; + pending->pending_event.event = &pending->event.base; + pending->pending_event.file_priv = file_priv; + pending->pending_event.destroy = + (void (*) (struct drm_pending_event *)) kfree; + + /* Get vblank ref for completion handling */ + ret = drm_vblank_get(dev, crtc->pipe); + if (ret) { + DRM_DEBUG("failed to take vblank ref\n"); + goto out_unlock; + } + + /* + * The set_base call will change the domain on the new fb, + * which will force the rendering to finish and block the + * ioctl. We need to do this last part from a work queue, to + * avoid blocking userspace here. + */ + crtc->fb = obj_to_fb(fb_obj); + + if (crtc->pending_flip != NULL) { + struct drm_pending_flip *old_flip; + + /* We have an outstanding flip request for this crtc/pipe. + * In order to satisfy the user we can either queue the requests + * and apply them on sequential vblanks, or we can drop old + * requests. + * + * Here we choose to discard the previous request for + * simplicity. Note that since we have not yet applied the + * previous flip, we need to preserve the original (i.e. still + * current) fb. + */ + + old_flip = crtc->pending_flip; + pending->old_fb = old_flip->old_fb; + old_flip->old_fb = NULL; + drm_finish_pending_flip (dev, old_flip, 0); + } else + schedule_work(&crtc->async_flip); + crtc->pending_flip = pending; + + mutex_unlock(&dev->struct_mutex); + + return 0; + +out_unlock: + mutex_unlock(&dev->struct_mutex); + kfree(pending); + + return ret; +} diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index 3da9cfa..5a26bab 100644 --- a/drivers/gpu/drm/drm_crtc_helper.c +++ b/drivers/gpu/drm/drm_crtc_helper.c @@ -868,8 +868,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; } @@ -1095,3 +1097,13 @@ int drm_helper_resume_force_mode(struct drm_device *dev) return 0; } EXPORT_SYMBOL(drm_helper_resume_force_mode); + +int +drm_crtc_helper_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; + + return crtc_funcs->mode_set_base(crtc, x, y, old_fb); +} +EXPORT_SYMBOL(drm_crtc_helper_set_base); diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index b39d7bf..c66c993 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW), }; #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 251bc0e..dcd9c66 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -257,6 +257,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp, INIT_LIST_HEAD(&priv->lhead); INIT_LIST_HEAD(&priv->fbs); + INIT_LIST_HEAD(&priv->event_list); + init_waitqueue_head(&priv->event_wait); if (dev->driver->driver_features & DRIVER_GEM) drm_gem_open(dev, priv); @@ -429,6 +431,9 @@ int drm_release(struct inode *inode, struct file *filp) { struct drm_file *file_priv = filp->private_data; struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_flip *f, *ft; + struct drm_pending_event *e, *et; + int retcode = 0; lock_kernel(); @@ -451,6 +456,19 @@ int drm_release(struct inode *inode, struct file *filp) if (file_priv->minor->master) drm_master_release(dev, filp); + mutex_lock(&dev->struct_mutex); + + /* Remove pending flips */ + list_for_each_entry_safe(f, ft, &dev->flip_list, link) + if (f->pending_event.file_priv == file_priv) + drm_finish_pending_flip(dev, f, 0); + + /* Remove unconsumed events */ + list_for_each_entry_safe(e, et, &file_priv->event_list, link) + e->destroy(e); + + mutex_unlock(&dev->struct_mutex); + if (dev->driver->driver_features & DRIVER_GEM) drm_gem_release(dev, file_priv); @@ -544,9 +562,55 @@ int drm_release(struct inode *inode, struct file *filp) } EXPORT_SYMBOL(drm_release); -/** No-op. */ +ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_event *event; + ssize_t total, ret; + + ret = wait_event_interruptible(file_priv->event_wait, + !list_empty(&file_priv->event_list)); + if (ret < 0) + return ret; + + total = 0; + while (!list_empty(&file_priv->event_list)) { + mutex_lock(&dev->struct_mutex); + event = list_first_entry(&file_priv->event_list, + struct drm_pending_event, link); + if (total + event->event->length > count) { + mutex_unlock(&dev->struct_mutex); + break; + } + list_del(&event->link); + mutex_unlock(&dev->struct_mutex); + + if (copy_to_user(buffer + total, + event->event, event->event->length)) { + total = -EFAULT; + break; + } + + total += event->event->length; + event->destroy(event); + } + + return total; +} +EXPORT_SYMBOL(drm_read); + unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) { - return 0; + struct drm_file *file_priv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &file_priv->event_wait, wait); + + if (!list_empty(&file_priv->event_list)) + mask |= POLLIN | POLLRDNORM; + + return mask; } EXPORT_SYMBOL(drm_poll); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index b4a3dbc..c7a17f6 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -34,6 +34,7 @@ */ #include "drmP.h" +#include "drm_crtc_helper.h" #include <linux/interrupt.h> /* For task queue support */ @@ -71,6 +72,44 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, return 0; } +#define vblank_passed(a,b) ((long)(a - b) > 0) + +void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame) +{ + struct timeval now; + + f->event.frame = frame; + do_gettimeofday(&now); + f->event.tv_sec = now.tv_sec; + f->event.tv_usec = now.tv_usec; + drm_vblank_put(dev, f->pipe); + list_del_init(&f->link); + list_add_tail(&f->pending_event.link, + &f->pending_event.file_priv->event_list); + if (f->old_fb) + f->old_fb->funcs->unpin(f->old_fb); + wake_up_interruptible(&f->pending_event.file_priv->event_wait); +} + +static void drm_flip_work_func(struct work_struct *work) +{ + struct drm_device *dev = + container_of(work, struct drm_device, flip_work); + struct drm_pending_flip *f, *t; + u32 frame; + + mutex_lock(&dev->struct_mutex); + + list_for_each_entry_safe(f, t, &dev->flip_list, link) { + frame = drm_vblank_count(dev, f->pipe); + if (vblank_passed(frame, f->frame)) + drm_finish_pending_flip(dev, f, frame); + } + + mutex_unlock(&dev->struct_mutex); +} + static void vblank_disable_fn(unsigned long arg) { struct drm_device *dev = (struct drm_device *)arg; @@ -161,6 +200,8 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) atomic_set(&dev->vblank_refcount[i], 0); } + INIT_LIST_HEAD(&dev->flip_list); + INIT_WORK(&dev->flip_work, drm_flip_work_func); dev->vblank_disable_allowed = 0; return 0; @@ -626,5 +667,7 @@ void drm_handle_vblank(struct drm_device *dev, int crtc) { atomic_inc(&dev->_vblank_count[crtc]); DRM_WAKEUP(&dev->vbl_queue[crtc]); + schedule_work(&dev->flip_work); } EXPORT_SYMBOL(drm_handle_vblank); + diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index fc4b68a..322b0f2 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -203,6 +203,7 @@ static struct drm_driver driver = { .mmap = drm_gem_mmap, .poll = drm_poll, .fasync = drm_fasync, + .read = drm_read, #ifdef CONFIG_COMPAT .compat_ioctl = i915_compat_ioctl, #endif diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 508838e..697c31a 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -863,6 +863,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"); @@ -898,17 +900,14 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, BUG(); } - mutex_lock(&dev->struct_mutex); ret = i915_gem_object_pin(obj, alignment); if (ret != 0) { - mutex_unlock(&dev->struct_mutex); return ret; } ret = i915_gem_object_set_to_gtt_domain(obj, 1); if (ret != 0) { i915_gem_object_unpin(obj); - mutex_unlock(&dev->struct_mutex); return ret; } @@ -944,7 +943,6 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, default: DRM_ERROR("Unknown color depth\n"); i915_gem_object_unpin(obj); - mutex_unlock(&dev->struct_mutex); return -EINVAL; } if (IS_I965G(dev)) { @@ -972,13 +970,11 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, I915_READ(dspbase); } - intel_wait_for_vblank(dev); - if (old_fb) { intel_fb = to_intel_framebuffer(old_fb); + intel_wait_for_vblank(dev); i915_gem_object_unpin(intel_fb->obj); } - mutex_unlock(&dev->struct_mutex); if (!dev->primary->master) return 0; @@ -2364,7 +2360,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); intel_update_watermarks(dev); @@ -2840,6 +2838,7 @@ static const struct drm_crtc_funcs intel_crtc_funcs = { .gamma_set = intel_crtc_gamma_set, .set_config = drm_crtc_helper_set_config, .destroy = intel_crtc_destroy, + .set_base = drm_crtc_helper_set_base, }; @@ -2852,7 +2851,7 @@ static void intel_crtc_init(struct drm_device *dev, int pipe) if (intel_crtc == NULL) return; - drm_crtc_init(dev, &intel_crtc->base, &intel_crtc_funcs); + drm_crtc_init(dev, &intel_crtc->base, pipe, &intel_crtc_funcs); drm_mode_crtc_set_gamma_size(&intel_crtc->base, 256); intel_crtc->pipe = pipe; @@ -3071,9 +3070,18 @@ static int intel_user_framebuffer_create_handle(struct drm_framebuffer *fb, return drm_gem_handle_create(file_priv, object, handle); } +static void intel_user_framebuffer_unpin(struct drm_framebuffer *fb) +{ + struct intel_framebuffer *intel_fb; + + intel_fb = to_intel_framebuffer(fb); + i915_gem_object_unpin(intel_fb->obj); +} + static const struct drm_framebuffer_funcs intel_fb_funcs = { .destroy = intel_user_framebuffer_destroy, .create_handle = intel_user_framebuffer_create_handle, + .unpin = intel_user_framebuffer_unpin }; int intel_framebuffer_create(struct drm_device *dev, diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c index 3efcf1a..4d73f0b 100644 --- a/drivers/gpu/drm/radeon/radeon_display.c +++ b/drivers/gpu/drm/radeon/radeon_display.c @@ -171,6 +171,7 @@ static const struct drm_crtc_funcs radeon_crtc_funcs = { .gamma_set = radeon_crtc_gamma_set, .set_config = drm_crtc_helper_set_config, .destroy = radeon_crtc_destroy, + .set_base = drm_crtc_helper_set_base, }; static void radeon_crtc_init(struct drm_device *dev, int index) @@ -183,7 +184,7 @@ static void radeon_crtc_init(struct drm_device *dev, int index) if (radeon_crtc == NULL) return; - drm_crtc_init(dev, &radeon_crtc->base, &radeon_crtc_funcs); + drm_crtc_init(dev, &radeon_crtc->base, index, &radeon_crtc_funcs); drm_mode_crtc_set_gamma_size(&radeon_crtc->base, 256); radeon_crtc->crtc_id = index; diff --git a/include/drm/drm.h b/include/drm/drm.h index 7cb50bd..1920323 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -686,6 +686,7 @@ struct drm_gem_open { #define DRM_IOCTL_MODE_GETFB DRM_IOWR(0xAD, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) +#define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOW( 0xB0, struct drm_mode_page_flip) /** * Device specific ioctls should only be in their respective headers @@ -698,6 +699,30 @@ struct drm_gem_open { #define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_END 0xA0 +/** + * Header for events written back to userspace on the drm fd. The + * type defines the type of event, the length specifies the total + * length of the event (including the header), and user_data is + * typically a 64 bit value passed with the ioctl that triggered the + * event. A read on the drm fd will always only return complete + * events, that is, if for example the read buffer is 100 bytes, and + * there are two 64 byte events pending, only one will be returned. + */ +struct drm_event { + __u32 type; + __u32 length; +}; + +#define DRM_EVENT_MODE_PAGE_FLIP 0x01 + +struct drm_event_page_flip { + struct drm_event base; + __u64 user_data; + __u32 tv_sec; + __u32 tv_usec; + __u32 frame; +}; + /* typedef area */ #ifndef __KERNEL__ typedef struct drm_clip_rect drm_clip_rect_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 45b67d9..4ff43ab 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -402,6 +402,14 @@ struct drm_buf_entry { struct drm_freelist freelist; }; +/* Event queued up for userspace to read */ +struct drm_pending_event { + struct drm_event *event; + struct list_head link; + struct drm_file *file_priv; + void (*destroy) (struct drm_pending_event *event); +}; + /** File private data */ struct drm_file { int authenticated; @@ -425,6 +433,9 @@ struct drm_file { struct drm_master *master; /* master this node is currently associated with N.B. not always minor->master */ struct list_head fbs; + + wait_queue_head_t event_wait; + struct list_head event_list; }; /** Wait queue */ @@ -873,6 +884,16 @@ struct drm_minor { struct drm_mode_group mode_group; }; +struct drm_pending_flip { + struct drm_pending_event pending_event; + struct drm_framebuffer *old_fb; + struct drm_crtc *crtc; + u32 frame; + int pipe; + struct list_head link; + struct drm_event_page_flip event; +}; + /** * DRM device structure. This structure represent a complete card that * may contain multiple heads. @@ -972,6 +993,13 @@ struct drm_device { u32 max_vblank_count; /**< size of vblank counter register */ + struct work_struct flip_work; + + /** + * List of objects waiting on flip completion + */ + struct list_head flip_list; + /*@} */ cycles_t ctx_start; cycles_t lck_start; @@ -1108,6 +1136,8 @@ extern int drm_lastclose(struct drm_device *dev); extern int drm_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_fasync(int fd, struct file *filp, int on); +extern ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset); extern int drm_release(struct inode *inode, struct file *filp); /* Mapping support (drm_vm.h) */ @@ -1274,6 +1304,8 @@ extern void drm_vblank_pre_modeset(struct drm_device *dev, int crtc); extern void drm_vblank_post_modeset(struct drm_device *dev, int crtc); extern int drm_modeset_ctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +extern void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame); /* AGP/GART support (drm_agpsupport.h) */ extern struct drm_agp_head *drm_agp_init(struct drm_device *dev); diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 7300fb8..0b5dc47 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -238,6 +238,12 @@ struct drm_display_info { }; struct drm_framebuffer_funcs { + /* + * Unpin the old fb after setting a mode. Must be called + * after the old framebuffer is no longer visible, ie, after + * the next vblank, typically. + */ + void (*unpin)(struct drm_framebuffer *fb); void (*destroy)(struct drm_framebuffer *framebuffer); int (*create_handle)(struct drm_framebuffer *fb, struct drm_file *file_priv, @@ -288,6 +294,7 @@ struct drm_property { struct drm_crtc; struct drm_connector; struct drm_encoder; +struct drm_pending_flip; /** * drm_crtc_funcs - control CRTCs for a given device @@ -331,17 +338,29 @@ struct drm_crtc_funcs { void (*destroy)(struct drm_crtc *crtc); int (*set_config)(struct drm_mode_set *set); + + /* + * Move the crtc on the current fb to the given position. + * This function is optional. If old_fb is provided, the + * function will wait for vblank and unpin it. If old_fb is + * NULL, nothing is unpinned and the caller must call + * mode_unpin_fb to release the old framebuffer. + */ + int (*set_base)(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb); }; /** * drm_crtc - central CRTC control structure * @enabled: is this CRTC enabled? + * @pipe: pipe number (as seen by DRM vblank functions) * @x: x position on screen * @y: y position on screen * @desired_mode: new desired mode * @desired_x: desired x for desired_mode * @desired_y: desired y for desired_mode * @funcs: CRTC control functions + * @async_work: work queue for async set base calls * * Each CRTC may have one or more connectors associated with it. This structure * allows the CRTC to be controlled. @@ -359,6 +378,7 @@ struct drm_crtc { struct drm_display_mode mode; + int pipe; int x, y; struct drm_display_mode *desired_mode; int desired_x, desired_y; @@ -368,6 +388,10 @@ struct drm_crtc { uint32_t gamma_size; uint16_t *gamma_store; + /* Allow async set_pipe_base calls for flipping */ + struct work_struct async_flip; + struct drm_pending_flip *pending_flip; + /* if you are using the helper */ void *helper_private; }; @@ -589,6 +613,7 @@ struct drm_mode_config { extern void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, + int pipe, const struct drm_crtc_funcs *funcs); extern void drm_crtc_cleanup(struct drm_crtc *crtc); @@ -736,4 +761,6 @@ extern int drm_mode_gamma_get_ioctl(struct drm_device *dev, extern int drm_mode_gamma_set_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern bool drm_detect_hdmi_monitor(struct edid *edid); +extern int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); #endif /* __DRM_CRTC_H__ */ diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h index 6769ff6..dd10566 100644 --- a/include/drm/drm_crtc_helper.h +++ b/include/drm/drm_crtc_helper.h @@ -123,4 +123,8 @@ static inline void drm_connector_helper_add(struct drm_connector *connector, } extern int drm_helper_resume_force_mode(struct drm_device *dev); + +extern int drm_crtc_helper_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb); + #endif diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index ae304cc..464b779 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -265,4 +265,20 @@ struct drm_mode_crtc_lut { __u64 blue; }; +#define DRM_MODE_PAGE_FLIP_WAIT (1<<0) /* block on previous page flip */ +#define DRM_MODE_PAGE_FLIP_FLAGS_MASK (DRM_MODE_PAGE_FLIP_WAIT) + +struct drm_mode_page_flip { + /** Handle of new front buffer */ + __u32 fb_id; + __u32 crtc_id; + + /* 64 bit cookie returned to userspace in the page flip event. */ + __u64 user_data; + /** + * page flip flags (wait on flip only for now) + */ + __u32 flags; +}; + #endif -- 1.6.4 |
From: Kristian H. <kr...@re...> - 2009-08-17 17:36:41
|
This patch adds a vblank synced pageflip ioctl for to the modesetting family of ioctls. The ioctl takes a crtc and an fb and schedules a pageflip to the new fb at the next coming vertical blank event. This feature lets userspace implement tear-free updating of the screen contents with hw-guaranteed low latency page flipping. The ioctl is asynchronous in that it returns immediately and then later notifies the client by making an event available for reading on the drm fd. This lets applications add the drm fd to their main loop and handle other tasks while waiting for the flip to happen. The event includes the time of the flip, the frame counter and a 64 bit opaque token provided by user space in the ioctl. Based on work and suggestions from Jesse Barnes <jb...@vi...>, Jakob Bornecrantz <wal...@gm...>, Chris Wilson <ch...@ch...> Signed-off-by: Kristian Høgsberg <kr...@re...> Signed-off-by: Jesse Barnes <jb...@vi...> --- Ok, another version of this patch. This one has fixes to work with radeon kms plus a missing list head init that would cause an oops in the case where we schedule a flip before the previous one has been queued. I'm now ready to propose this patch for the 2.6.32 merge window. Kristian drivers/gpu/drm/drm_crtc.c | 170 ++++++++++++++++++++++++++++++- drivers/gpu/drm/drm_crtc_helper.c | 12 ++ drivers/gpu/drm/drm_drv.c | 1 + drivers/gpu/drm/drm_fops.c | 68 ++++++++++++- drivers/gpu/drm/drm_irq.c | 43 ++++++++ drivers/gpu/drm/i915/i915_drv.c | 1 + drivers/gpu/drm/i915/intel_display.c | 24 +++-- drivers/gpu/drm/radeon/radeon_display.c | 3 +- include/drm/drm.h | 25 +++++ include/drm/drmP.h | 32 ++++++ include/drm/drm_crtc.h | 27 +++++ include/drm/drm_crtc_helper.h | 4 + include/drm/drm_mode.h | 16 +++ 13 files changed, 414 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 8fab789..0906cb3 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -34,6 +34,8 @@ #include "drmP.h" #include "drm_crtc.h" +#undef set_base + struct drm_prop_enum_list { int type; char *name; @@ -342,6 +344,34 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb) EXPORT_SYMBOL(drm_framebuffer_cleanup); /** + * drm_crtc_async_flip - do a set_base call from a work queue + * @work: work struct + * + * Called when a set_base call is queued by the page flip code. This + * allows the flip ioctl itself to return immediately and allow userspace + * to continue working. + */ +static void drm_crtc_async_flip(struct work_struct *work) +{ + struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_flip); + struct drm_device *dev = crtc->dev; + struct drm_pending_flip *pending; + + BUG_ON(crtc->pending_flip == NULL); + + mutex_lock(&dev->struct_mutex); + crtc->funcs->set_base(crtc, crtc->x, crtc->y, NULL); + + pending = crtc->pending_flip; + crtc->pending_flip = NULL; + + pending->frame = drm_vblank_count(dev, crtc->pipe); + list_add_tail(&pending->link, &dev->flip_list); + + mutex_unlock(&dev->struct_mutex); +} + +/** * drm_crtc_init - Initialise a new CRTC object * @dev: DRM device * @crtc: CRTC object to init @@ -352,17 +382,19 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup); * * Inits a new object created as base part of an driver crtc object. */ -void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, +void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, const struct drm_crtc_funcs *funcs) { crtc->dev = dev; crtc->funcs = funcs; + crtc->pipe = pipe; mutex_lock(&dev->mode_config.mutex); drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC); list_add_tail(&crtc->head, &dev->mode_config.crtc_list); dev->mode_config.num_crtc++; + INIT_WORK(&crtc->async_flip, drm_crtc_async_flip); mutex_unlock(&dev->mode_config.mutex); } EXPORT_SYMBOL(drm_crtc_init); @@ -381,6 +413,9 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; + mutex_lock(&dev->mode_config.mutex); + flush_work(&crtc->async_flip); + if (crtc->gamma_store) { kfree(crtc->gamma_store); crtc->gamma_store = NULL; @@ -388,6 +423,7 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) drm_mode_object_put(dev, &crtc->base); list_del(&crtc->head); + mutex_unlock(&dev->mode_config.mutex); dev->mode_config.num_crtc--; } EXPORT_SYMBOL(drm_crtc_cleanup); @@ -2452,3 +2488,135 @@ out: mutex_unlock(&dev->mode_config.mutex); return ret; } + +/** + * drm_mode_page_flip_ioctl - page flip ioctl + * @dev: DRM device + * @data: ioctl args + * @file_priv: file private data + * + * The page flip ioctl replaces the current front buffer with a new + * one, using the CRTC's set_base function, which should just update + * the front buffer base pointer. It's up to set_base to make + * sure the update doesn't result in tearing (on some hardware the + * base register is double buffered, so this is easy). + * + * Note that this covers just the simple case of flipping the front + * buffer immediately. Interval handling and interlaced modes have to + * be handled by userspace, or with new ioctls. + */ +int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_pending_flip *pending; + struct drm_mode_page_flip *flip_data = data; + struct drm_mode_object *drm_obj, *fb_obj; + struct drm_crtc *crtc; + int ret = 0; + + 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 & (~DRM_MODE_PAGE_FLIP_FLAGS_MASK)) { + DRM_DEBUG("bad page flip flags\n"); + return -EINVAL; + } + + pending = kzalloc(sizeof *pending, GFP_KERNEL); + if (pending == NULL) + return -ENOMEM; + + 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; + } + + drm_obj = drm_mode_object_find(dev, flip_data->crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!drm_obj) { + DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_id); + ret = -ENOENT; + goto out_unlock; + } + crtc = obj_to_crtc(drm_obj); + if (!crtc->enabled) { + DRM_DEBUG("crtc %d not enabled\n", flip_data->crtc_id); + ret = -EINVAL; + goto out_unlock; + } + + if (crtc->fb->funcs->unpin == NULL) { + DRM_DEBUG("fb for crtc %d does not support delayed unpin\n", + flip_data->crtc_id); + ret = -ENODEV; + goto out_unlock; + } + + pending->crtc = crtc; + pending->old_fb = crtc->fb; + pending->pipe = crtc->pipe; + INIT_LIST_HEAD(&pending->link); + pending->event.base.type = DRM_EVENT_MODE_PAGE_FLIP; + pending->event.base.length = sizeof pending->event; + pending->event.user_data = flip_data->user_data; + pending->pending_event.event = &pending->event.base; + pending->pending_event.file_priv = file_priv; + pending->pending_event.destroy = + (void (*) (struct drm_pending_event *)) kfree; + + /* Get vblank ref for completion handling */ + ret = drm_vblank_get(dev, crtc->pipe); + if (ret) { + DRM_DEBUG("failed to take vblank ref\n"); + goto out_unlock; + } + + /* + * The set_base call will change the domain on the new fb, + * which will force the rendering to finish and block the + * ioctl. We need to do this last part from a work queue, to + * avoid blocking userspace here. + */ + crtc->fb = obj_to_fb(fb_obj); + + if (crtc->pending_flip != NULL) { + struct drm_pending_flip *old_flip; + + /* We have an outstanding flip request for this crtc/pipe. + * In order to satisfy the user we can either queue the requests + * and apply them on sequential vblanks, or we can drop old + * requests. + * + * Here we choose to discard the previous request for + * simplicity. Note that since we have not yet applied the + * previous flip, we need to preserve the original (i.e. still + * current) fb. + */ + + old_flip = crtc->pending_flip; + pending->old_fb = old_flip->old_fb; + old_flip->old_fb = NULL; + drm_finish_pending_flip (dev, old_flip, 0); + } else + schedule_work(&crtc->async_flip); + crtc->pending_flip = pending; + + mutex_unlock(&dev->struct_mutex); + + return 0; + +out_unlock: + mutex_unlock(&dev->struct_mutex); + kfree(pending); + + return ret; +} diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index 3da9cfa..5a26bab 100644 --- a/drivers/gpu/drm/drm_crtc_helper.c +++ b/drivers/gpu/drm/drm_crtc_helper.c @@ -868,8 +868,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; } @@ -1095,3 +1097,13 @@ int drm_helper_resume_force_mode(struct drm_device *dev) return 0; } EXPORT_SYMBOL(drm_helper_resume_force_mode); + +int +drm_crtc_helper_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; + + return crtc_funcs->mode_set_base(crtc, x, y, old_fb); +} +EXPORT_SYMBOL(drm_crtc_helper_set_base); diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index b39d7bf..c66c993 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW), }; #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 251bc0e..dcd9c66 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -257,6 +257,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp, INIT_LIST_HEAD(&priv->lhead); INIT_LIST_HEAD(&priv->fbs); + INIT_LIST_HEAD(&priv->event_list); + init_waitqueue_head(&priv->event_wait); if (dev->driver->driver_features & DRIVER_GEM) drm_gem_open(dev, priv); @@ -429,6 +431,9 @@ int drm_release(struct inode *inode, struct file *filp) { struct drm_file *file_priv = filp->private_data; struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_flip *f, *ft; + struct drm_pending_event *e, *et; + int retcode = 0; lock_kernel(); @@ -451,6 +456,19 @@ int drm_release(struct inode *inode, struct file *filp) if (file_priv->minor->master) drm_master_release(dev, filp); + mutex_lock(&dev->struct_mutex); + + /* Remove pending flips */ + list_for_each_entry_safe(f, ft, &dev->flip_list, link) + if (f->pending_event.file_priv == file_priv) + drm_finish_pending_flip(dev, f, 0); + + /* Remove unconsumed events */ + list_for_each_entry_safe(e, et, &file_priv->event_list, link) + e->destroy(e); + + mutex_unlock(&dev->struct_mutex); + if (dev->driver->driver_features & DRIVER_GEM) drm_gem_release(dev, file_priv); @@ -544,9 +562,55 @@ int drm_release(struct inode *inode, struct file *filp) } EXPORT_SYMBOL(drm_release); -/** No-op. */ +ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev = file_priv->minor->dev; + struct drm_pending_event *event; + ssize_t total, ret; + + ret = wait_event_interruptible(file_priv->event_wait, + !list_empty(&file_priv->event_list)); + if (ret < 0) + return ret; + + total = 0; + while (!list_empty(&file_priv->event_list)) { + mutex_lock(&dev->struct_mutex); + event = list_first_entry(&file_priv->event_list, + struct drm_pending_event, link); + if (total + event->event->length > count) { + mutex_unlock(&dev->struct_mutex); + break; + } + list_del(&event->link); + mutex_unlock(&dev->struct_mutex); + + if (copy_to_user(buffer + total, + event->event, event->event->length)) { + total = -EFAULT; + break; + } + + total += event->event->length; + event->destroy(event); + } + + return total; +} +EXPORT_SYMBOL(drm_read); + unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) { - return 0; + struct drm_file *file_priv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &file_priv->event_wait, wait); + + if (!list_empty(&file_priv->event_list)) + mask |= POLLIN | POLLRDNORM; + + return mask; } EXPORT_SYMBOL(drm_poll); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index b4a3dbc..c7a17f6 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -34,6 +34,7 @@ */ #include "drmP.h" +#include "drm_crtc_helper.h" #include <linux/interrupt.h> /* For task queue support */ @@ -71,6 +72,44 @@ int drm_irq_by_busid(struct drm_device *dev, void *data, return 0; } +#define vblank_passed(a,b) ((long)(a - b) > 0) + +void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame) +{ + struct timeval now; + + f->event.frame = frame; + do_gettimeofday(&now); + f->event.tv_sec = now.tv_sec; + f->event.tv_usec = now.tv_usec; + drm_vblank_put(dev, f->pipe); + list_del_init(&f->link); + list_add_tail(&f->pending_event.link, + &f->pending_event.file_priv->event_list); + if (f->old_fb) + f->old_fb->funcs->unpin(f->old_fb); + wake_up_interruptible(&f->pending_event.file_priv->event_wait); +} + +static void drm_flip_work_func(struct work_struct *work) +{ + struct drm_device *dev = + container_of(work, struct drm_device, flip_work); + struct drm_pending_flip *f, *t; + u32 frame; + + mutex_lock(&dev->struct_mutex); + + list_for_each_entry_safe(f, t, &dev->flip_list, link) { + frame = drm_vblank_count(dev, f->pipe); + if (vblank_passed(frame, f->frame)) + drm_finish_pending_flip(dev, f, frame); + } + + mutex_unlock(&dev->struct_mutex); +} + static void vblank_disable_fn(unsigned long arg) { struct drm_device *dev = (struct drm_device *)arg; @@ -161,6 +200,8 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) atomic_set(&dev->vblank_refcount[i], 0); } + INIT_LIST_HEAD(&dev->flip_list); + INIT_WORK(&dev->flip_work, drm_flip_work_func); dev->vblank_disable_allowed = 0; return 0; @@ -626,5 +667,7 @@ void drm_handle_vblank(struct drm_device *dev, int crtc) { atomic_inc(&dev->_vblank_count[crtc]); DRM_WAKEUP(&dev->vbl_queue[crtc]); + schedule_work(&dev->flip_work); } EXPORT_SYMBOL(drm_handle_vblank); + diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index fc4b68a..322b0f2 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -203,6 +203,7 @@ static struct drm_driver driver = { .mmap = drm_gem_mmap, .poll = drm_poll, .fasync = drm_fasync, + .read = drm_read, #ifdef CONFIG_COMPAT .compat_ioctl = i915_compat_ioctl, #endif diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 508838e..697c31a 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -863,6 +863,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"); @@ -898,17 +900,14 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, BUG(); } - mutex_lock(&dev->struct_mutex); ret = i915_gem_object_pin(obj, alignment); if (ret != 0) { - mutex_unlock(&dev->struct_mutex); return ret; } ret = i915_gem_object_set_to_gtt_domain(obj, 1); if (ret != 0) { i915_gem_object_unpin(obj); - mutex_unlock(&dev->struct_mutex); return ret; } @@ -944,7 +943,6 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, default: DRM_ERROR("Unknown color depth\n"); i915_gem_object_unpin(obj); - mutex_unlock(&dev->struct_mutex); return -EINVAL; } if (IS_I965G(dev)) { @@ -972,13 +970,11 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, I915_READ(dspbase); } - intel_wait_for_vblank(dev); - if (old_fb) { intel_fb = to_intel_framebuffer(old_fb); + intel_wait_for_vblank(dev); i915_gem_object_unpin(intel_fb->obj); } - mutex_unlock(&dev->struct_mutex); if (!dev->primary->master) return 0; @@ -2364,7 +2360,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); intel_update_watermarks(dev); @@ -2840,6 +2838,7 @@ static const struct drm_crtc_funcs intel_crtc_funcs = { .gamma_set = intel_crtc_gamma_set, .set_config = drm_crtc_helper_set_config, .destroy = intel_crtc_destroy, + .set_base = drm_crtc_helper_set_base, }; @@ -2852,7 +2851,7 @@ static void intel_crtc_init(struct drm_device *dev, int pipe) if (intel_crtc == NULL) return; - drm_crtc_init(dev, &intel_crtc->base, &intel_crtc_funcs); + drm_crtc_init(dev, &intel_crtc->base, pipe, &intel_crtc_funcs); drm_mode_crtc_set_gamma_size(&intel_crtc->base, 256); intel_crtc->pipe = pipe; @@ -3071,9 +3070,18 @@ static int intel_user_framebuffer_create_handle(struct drm_framebuffer *fb, return drm_gem_handle_create(file_priv, object, handle); } +static void intel_user_framebuffer_unpin(struct drm_framebuffer *fb) +{ + struct intel_framebuffer *intel_fb; + + intel_fb = to_intel_framebuffer(fb); + i915_gem_object_unpin(intel_fb->obj); +} + static const struct drm_framebuffer_funcs intel_fb_funcs = { .destroy = intel_user_framebuffer_destroy, .create_handle = intel_user_framebuffer_create_handle, + .unpin = intel_user_framebuffer_unpin }; int intel_framebuffer_create(struct drm_device *dev, diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c index 3efcf1a..4d73f0b 100644 --- a/drivers/gpu/drm/radeon/radeon_display.c +++ b/drivers/gpu/drm/radeon/radeon_display.c @@ -171,6 +171,7 @@ static const struct drm_crtc_funcs radeon_crtc_funcs = { .gamma_set = radeon_crtc_gamma_set, .set_config = drm_crtc_helper_set_config, .destroy = radeon_crtc_destroy, + .set_base = drm_crtc_helper_set_base, }; static void radeon_crtc_init(struct drm_device *dev, int index) @@ -183,7 +184,7 @@ static void radeon_crtc_init(struct drm_device *dev, int index) if (radeon_crtc == NULL) return; - drm_crtc_init(dev, &radeon_crtc->base, &radeon_crtc_funcs); + drm_crtc_init(dev, &radeon_crtc->base, index, &radeon_crtc_funcs); drm_mode_crtc_set_gamma_size(&radeon_crtc->base, 256); radeon_crtc->crtc_id = index; diff --git a/include/drm/drm.h b/include/drm/drm.h index 7cb50bd..1920323 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -686,6 +686,7 @@ struct drm_gem_open { #define DRM_IOCTL_MODE_GETFB DRM_IOWR(0xAD, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) #define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) +#define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOW( 0xB0, struct drm_mode_page_flip) /** * Device specific ioctls should only be in their respective headers @@ -698,6 +699,30 @@ struct drm_gem_open { #define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_END 0xA0 +/** + * Header for events written back to userspace on the drm fd. The + * type defines the type of event, the length specifies the total + * length of the event (including the header), and user_data is + * typically a 64 bit value passed with the ioctl that triggered the + * event. A read on the drm fd will always only return complete + * events, that is, if for example the read buffer is 100 bytes, and + * there are two 64 byte events pending, only one will be returned. + */ +struct drm_event { + __u32 type; + __u32 length; +}; + +#define DRM_EVENT_MODE_PAGE_FLIP 0x01 + +struct drm_event_page_flip { + struct drm_event base; + __u64 user_data; + __u32 tv_sec; + __u32 tv_usec; + __u32 frame; +}; + /* typedef area */ #ifndef __KERNEL__ typedef struct drm_clip_rect drm_clip_rect_t; diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 45b67d9..4ff43ab 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -402,6 +402,14 @@ struct drm_buf_entry { struct drm_freelist freelist; }; +/* Event queued up for userspace to read */ +struct drm_pending_event { + struct drm_event *event; + struct list_head link; + struct drm_file *file_priv; + void (*destroy) (struct drm_pending_event *event); +}; + /** File private data */ struct drm_file { int authenticated; @@ -425,6 +433,9 @@ struct drm_file { struct drm_master *master; /* master this node is currently associated with N.B. not always minor->master */ struct list_head fbs; + + wait_queue_head_t event_wait; + struct list_head event_list; }; /** Wait queue */ @@ -873,6 +884,16 @@ struct drm_minor { struct drm_mode_group mode_group; }; +struct drm_pending_flip { + struct drm_pending_event pending_event; + struct drm_framebuffer *old_fb; + struct drm_crtc *crtc; + u32 frame; + int pipe; + struct list_head link; + struct drm_event_page_flip event; +}; + /** * DRM device structure. This structure represent a complete card that * may contain multiple heads. @@ -972,6 +993,13 @@ struct drm_device { u32 max_vblank_count; /**< size of vblank counter register */ + struct work_struct flip_work; + + /** + * List of objects waiting on flip completion + */ + struct list_head flip_list; + /*@} */ cycles_t ctx_start; cycles_t lck_start; @@ -1108,6 +1136,8 @@ extern int drm_lastclose(struct drm_device *dev); extern int drm_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_fasync(int fd, struct file *filp, int on); +extern ssize_t drm_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset); extern int drm_release(struct inode *inode, struct file *filp); /* Mapping support (drm_vm.h) */ @@ -1274,6 +1304,8 @@ extern void drm_vblank_pre_modeset(struct drm_device *dev, int crtc); extern void drm_vblank_post_modeset(struct drm_device *dev, int crtc); extern int drm_modeset_ctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +extern void drm_finish_pending_flip(struct drm_device *dev, + struct drm_pending_flip *f, u32 frame); /* AGP/GART support (drm_agpsupport.h) */ extern struct drm_agp_head *drm_agp_init(struct drm_device *dev); diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 7300fb8..0b5dc47 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -238,6 +238,12 @@ struct drm_display_info { }; struct drm_framebuffer_funcs { + /* + * Unpin the old fb after setting a mode. Must be called + * after the old framebuffer is no longer visible, ie, after + * the next vblank, typically. + */ + void (*unpin)(struct drm_framebuffer *fb); void (*destroy)(struct drm_framebuffer *framebuffer); int (*create_handle)(struct drm_framebuffer *fb, struct drm_file *file_priv, @@ -288,6 +294,7 @@ struct drm_property { struct drm_crtc; struct drm_connector; struct drm_encoder; +struct drm_pending_flip; /** * drm_crtc_funcs - control CRTCs for a given device @@ -331,17 +338,29 @@ struct drm_crtc_funcs { void (*destroy)(struct drm_crtc *crtc); int (*set_config)(struct drm_mode_set *set); + + /* + * Move the crtc on the current fb to the given position. + * This function is optional. If old_fb is provided, the + * function will wait for vblank and unpin it. If old_fb is + * NULL, nothing is unpinned and the caller must call + * mode_unpin_fb to release the old framebuffer. + */ + int (*set_base)(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb); }; /** * drm_crtc - central CRTC control structure * @enabled: is this CRTC enabled? + * @pipe: pipe number (as seen by DRM vblank functions) * @x: x position on screen * @y: y position on screen * @desired_mode: new desired mode * @desired_x: desired x for desired_mode * @desired_y: desired y for desired_mode * @funcs: CRTC control functions + * @async_work: work queue for async set base calls * * Each CRTC may have one or more connectors associated with it. This structure * allows the CRTC to be controlled. @@ -359,6 +378,7 @@ struct drm_crtc { struct drm_display_mode mode; + int pipe; int x, y; struct drm_display_mode *desired_mode; int desired_x, desired_y; @@ -368,6 +388,10 @@ struct drm_crtc { uint32_t gamma_size; uint16_t *gamma_store; + /* Allow async set_pipe_base calls for flipping */ + struct work_struct async_flip; + struct drm_pending_flip *pending_flip; + /* if you are using the helper */ void *helper_private; }; @@ -589,6 +613,7 @@ struct drm_mode_config { extern void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, + int pipe, const struct drm_crtc_funcs *funcs); extern void drm_crtc_cleanup(struct drm_crtc *crtc); @@ -736,4 +761,6 @@ extern int drm_mode_gamma_get_ioctl(struct drm_device *dev, extern int drm_mode_gamma_set_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); extern bool drm_detect_hdmi_monitor(struct edid *edid); +extern int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); #endif /* __DRM_CRTC_H__ */ diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h index 6769ff6..dd10566 100644 --- a/include/drm/drm_crtc_helper.h +++ b/include/drm/drm_crtc_helper.h @@ -123,4 +123,8 @@ static inline void drm_connector_helper_add(struct drm_connector *connector, } extern int drm_helper_resume_force_mode(struct drm_device *dev); + +extern int drm_crtc_helper_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb); + #endif diff --git a/include/drm/drm_mode.h b/include/drm/drm_mode.h index ae304cc..464b779 100644 --- a/include/drm/drm_mode.h +++ b/include/drm/drm_mode.h @@ -265,4 +265,20 @@ struct drm_mode_crtc_lut { __u64 blue; }; +#define DRM_MODE_PAGE_FLIP_WAIT (1<<0) /* block on previous page flip */ +#define DRM_MODE_PAGE_FLIP_FLAGS_MASK (DRM_MODE_PAGE_FLIP_WAIT) + +struct drm_mode_page_flip { + /** Handle of new front buffer */ + __u32 fb_id; + __u32 crtc_id; + + /* 64 bit cookie returned to userspace in the page flip event. */ + __u64 user_data; + /** + * page flip flags (wait on flip only for now) + */ + __u32 flags; +}; + #endif -- 1.6.4 |
From: Thomas H. <th...@sh...> - 2009-08-17 20:23:52
|
Kristian Høgsberg wrote: > This patch adds a vblank synced pageflip ioctl for to the modesetting > family of ioctls. The ioctl takes a crtc and an fb and schedules a > pageflip to the new fb at the next coming vertical blank event. This > feature lets userspace implement tear-free updating of the screen contents > with hw-guaranteed low latency page flipping. > > The ioctl is asynchronous in that it returns immediately and then later > notifies the client by making an event available for reading on the drm fd. > This lets applications add the drm fd to their main loop and handle other > tasks while waiting for the flip to happen. The event includes the time > of the flip, the frame counter and a 64 bit opaque token provided by > user space in the ioctl. > > Based on work and suggestions from > Jesse Barnes <jb...@vi...>, > Jakob Bornecrantz <wal...@gm...>, > Chris Wilson <ch...@ch...> > > Signed-off-by: Kristian Høgsberg <kr...@re...> > Signed-off-by: Jesse Barnes <jb...@vi...> > --- > > Ok, another version of this patch. This one has fixes to work with radeon > kms plus a missing list head init that would cause an oops in the case > where we schedule a flip before the previous one has been queued. > > I'm now ready to propose this patch for the 2.6.32 merge window. > > Hi! First a general question: There is some hardware (for example Unichromes and Chrome9) that can schedule page-flips in the command stream after optional vblank barriers. For this kind of hardware the pageflip would be a matter of scheduling the flip and fence the old and new buffers. No need for delayed unpins and explicit user-space notifications, although the workqueue could be handy to avoid command processing stalls in the vblank barriers. How would such a scheme fit into the framework below? > Kristian > > drivers/gpu/drm/drm_crtc.c | 170 ++++++++++++++++++++++++++++++- > drivers/gpu/drm/drm_crtc_helper.c | 12 ++ > drivers/gpu/drm/drm_drv.c | 1 + > drivers/gpu/drm/drm_fops.c | 68 ++++++++++++- > drivers/gpu/drm/drm_irq.c | 43 ++++++++ > drivers/gpu/drm/i915/i915_drv.c | 1 + > drivers/gpu/drm/i915/intel_display.c | 24 +++-- > drivers/gpu/drm/radeon/radeon_display.c | 3 +- > include/drm/drm.h | 25 +++++ > include/drm/drmP.h | 32 ++++++ > include/drm/drm_crtc.h | 27 +++++ > include/drm/drm_crtc_helper.h | 4 + > include/drm/drm_mode.h | 16 +++ > 13 files changed, 414 insertions(+), 12 deletions(-) > > diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c > index 8fab789..0906cb3 100644 > --- a/drivers/gpu/drm/drm_crtc.c > +++ b/drivers/gpu/drm/drm_crtc.c > @@ -34,6 +34,8 @@ > #include "drmP.h" > #include "drm_crtc.h" > > +#undef set_base > + > struct drm_prop_enum_list { > int type; > char *name; > @@ -342,6 +344,34 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb) > EXPORT_SYMBOL(drm_framebuffer_cleanup); > > /** > + * drm_crtc_async_flip - do a set_base call from a work queue > + * @work: work struct > + * > + * Called when a set_base call is queued by the page flip code. This > + * allows the flip ioctl itself to return immediately and allow userspace > + * to continue working. > + */ > +static void drm_crtc_async_flip(struct work_struct *work) > +{ > + struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_flip); > + struct drm_device *dev = crtc->dev; > + struct drm_pending_flip *pending; > + > + BUG_ON(crtc->pending_flip == NULL); > + > + mutex_lock(&dev->struct_mutex); > Here the struct mutex is taken before a call to a function that doesn't even have dev as an argument. Exactly what in the generic modesetting code is protected by the struct mutex here? If the driver needs the struct mutex to protect internal data structures, let the driver take care of the locking. If the struct mutex protects the dev->flip_list, then we should take it around the manipulation of that list only. I'd hate to see the not-too-careful use of struct_mutex in some drivers leak out to generic code. > + crtc->funcs->set_base(crtc, crtc->x, crtc->y, NULL); > + > + pending = crtc->pending_flip; > + crtc->pending_flip = NULL; > + > + pending->frame = drm_vblank_count(dev, crtc->pipe); > + list_add_tail(&pending->link, &dev->flip_list); > + > + mutex_unlock(&dev->struct_mutex); > +} > + > +/** > * drm_crtc_init - Initialise a new CRTC object > * @dev: DRM device > * @crtc: CRTC object to init > @@ -352,17 +382,19 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup); > * > * Inits a new object created as base part of an driver crtc object. > */ > -void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, > +void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, > const struct drm_crtc_funcs *funcs) > { > crtc->dev = dev; > crtc->funcs = funcs; > + crtc->pipe = pipe; > > mutex_lock(&dev->mode_config.mutex); > drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC); > > list_add_tail(&crtc->head, &dev->mode_config.crtc_list); > dev->mode_config.num_crtc++; > + INIT_WORK(&crtc->async_flip, drm_crtc_async_flip); > mutex_unlock(&dev->mode_config.mutex); > } > EXPORT_SYMBOL(drm_crtc_init); > @@ -381,6 +413,9 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) > { > struct drm_device *dev = crtc->dev; > > + mutex_lock(&dev->mode_config.mutex); > + flush_work(&crtc->async_flip); > + > if (crtc->gamma_store) { > kfree(crtc->gamma_store); > crtc->gamma_store = NULL; > @@ -388,6 +423,7 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) > > drm_mode_object_put(dev, &crtc->base); > list_del(&crtc->head); > + mutex_unlock(&dev->mode_config.mutex); > dev->mode_config.num_crtc--; > } > EXPORT_SYMBOL(drm_crtc_cleanup); > @@ -2452,3 +2488,135 @@ out: > mutex_unlock(&dev->mode_config.mutex); > return ret; > } > + > +/** > + * drm_mode_page_flip_ioctl - page flip ioctl > + * @dev: DRM device > + * @data: ioctl args > + * @file_priv: file private data > + * > + * The page flip ioctl replaces the current front buffer with a new > + * one, using the CRTC's set_base function, which should just update > + * the front buffer base pointer. It's up to set_base to make > + * sure the update doesn't result in tearing (on some hardware the > + * base register is double buffered, so this is easy). > + * > + * Note that this covers just the simple case of flipping the front > + * buffer immediately. Interval handling and interlaced modes have to > + * be handled by userspace, or with new ioctls. > + */ > +int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, > + struct drm_file *file_priv) > +{ > + struct drm_pending_flip *pending; > + struct drm_mode_page_flip *flip_data = data; > + struct drm_mode_object *drm_obj, *fb_obj; > + struct drm_crtc *crtc; > + int ret = 0; > + > + 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 & (~DRM_MODE_PAGE_FLIP_FLAGS_MASK)) { > + DRM_DEBUG("bad page flip flags\n"); > + return -EINVAL; > + } > + > + pending = kzalloc(sizeof *pending, GFP_KERNEL); > + if (pending == NULL) > + return -ENOMEM; > + > + 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; > + } > + > + drm_obj = drm_mode_object_find(dev, flip_data->crtc_id, > + DRM_MODE_OBJECT_CRTC); > + if (!drm_obj) { > + DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_id); > + ret = -ENOENT; > + goto out_unlock; > + } > + crtc = obj_to_crtc(drm_obj); > + if (!crtc->enabled) { > + DRM_DEBUG("crtc %d not enabled\n", flip_data->crtc_id); > + ret = -EINVAL; > + goto out_unlock; > + } > + > + if (crtc->fb->funcs->unpin == NULL) { > + DRM_DEBUG("fb for crtc %d does not support delayed unpin\n", > + flip_data->crtc_id); > + ret = -ENODEV; > + goto out_unlock; > + } > + > + pending->crtc = crtc; > + pending->old_fb = crtc->fb; > + pending->pipe = crtc->pipe; > + INIT_LIST_HEAD(&pending->link); > + pending->event.base.type = DRM_EVENT_MODE_PAGE_FLIP; > + pending->event.base.length = sizeof pending->event; > + pending->event.user_data = flip_data->user_data; > + pending->pending_event.event = &pending->event.base; > + pending->pending_event.file_priv = file_priv; > + pending->pending_event.destroy = > + (void (*) (struct drm_pending_event *)) kfree; > + > + /* Get vblank ref for completion handling */ > + ret = drm_vblank_get(dev, crtc->pipe); > + if (ret) { > + DRM_DEBUG("failed to take vblank ref\n"); > + goto out_unlock; > + } > + > + /* > + * The set_base call will change the domain on the new fb, > + * which will force the rendering to finish and block the > + * ioctl. We need to do this last part from a work queue, to > + * avoid blocking userspace here. > + */ > + crtc->fb = obj_to_fb(fb_obj); > + > + if (crtc->pending_flip != NULL) { > + struct drm_pending_flip *old_flip; > + > + /* We have an outstanding flip request for this crtc/pipe. > + * In order to satisfy the user we can either queue the requests > + * and apply them on sequential vblanks, or we can drop old > + * requests. > + * > + * Here we choose to discard the previous request for > + * simplicity. Note that since we have not yet applied the > + * previous flip, we need to preserve the original (i.e. still > + * current) fb. > + */ > + > + old_flip = crtc->pending_flip; > + pending->old_fb = old_flip->old_fb; > + old_flip->old_fb = NULL; > + drm_finish_pending_flip (dev, old_flip, 0); > + } else > + schedule_work(&crtc->async_flip); > + crtc->pending_flip = pending; > + > + mutex_unlock(&dev->struct_mutex); > + > + return 0; > + > +out_unlock: > + mutex_unlock(&dev->struct_mutex); > + kfree(pending); > + > + return ret; > +} > diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c > index 3da9cfa..5a26bab 100644 > --- a/drivers/gpu/drm/drm_crtc_helper.c > +++ b/drivers/gpu/drm/drm_crtc_helper.c > @@ -868,8 +868,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); > dev->struct_mutex again! > 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; > } > @@ -1095,3 +1097,13 @@ int drm_helper_resume_force_mode(struct drm_device *dev) > return 0; > } > EXPORT_SYMBOL(drm_helper_resume_force_mode); > + > +int > +drm_crtc_helper_set_base(struct drm_crtc *crtc, int x, int y, > + struct drm_framebuffer *old_fb) > +{ > + struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; > + > + return crtc_funcs->mode_set_base(crtc, x, y, old_fb); > +} > +EXPORT_SYMBOL(drm_crtc_helper_set_base); > diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c > index b39d7bf..c66c993 100644 > --- a/drivers/gpu/drm/drm_drv.c > +++ b/drivers/gpu/drm/drm_drv.c > @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { > DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), > DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), > DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), > + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW), > }; > > #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) > diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c > index 251bc0e..dcd9c66 100644 > --- a/drivers/gpu/drm/drm_fops.c > +++ b/drivers/gpu/drm/drm_fops.c > @@ -257,6 +257,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp, > > INIT_LIST_HEAD(&priv->lhead); > INIT_LIST_HEAD(&priv->fbs); > + INIT_LIST_HEAD(&priv->event_list); > + init_waitqueue_head(&priv->event_wait); > > if (dev->driver->driver_features & DRIVER_GEM) > drm_gem_open(dev, priv); > @@ -429,6 +431,9 @@ int drm_release(struct inode *inode, struct file *filp) > { > struct drm_file *file_priv = filp->private_data; > struct drm_device *dev = file_priv->minor->dev; > + struct drm_pending_flip *f, *ft; > + struct drm_pending_event *e, *et; > + > int retcode = 0; > > lock_kernel(); > @@ -451,6 +456,19 @@ int drm_release(struct inode *inode, struct file *filp) > if (file_priv->minor->master) > drm_master_release(dev, filp); > > + mutex_lock(&dev->struct_mutex); > + > + /* Remove pending flips */ > + list_for_each_entry_safe(f, ft, &dev->flip_list, link) > + if (f->pending_event.file_priv == file_priv) > + drm_finish_pending_flip(dev, f, 0); > + > + /* Remove unconsumed events */ > + list_for_each_entry_safe(e, et, &file_priv->event_list, link) > + e->destroy(e); > + > + mutex_unlock(&dev->struct_mutex); > + > if (dev->driver->driver_features & DRIVER_GEM) > drm_gem_release(dev, file_priv); > > @@ -544,9 +562,55 @@ int drm_release(struct inode *inode, struct file *filp) > } > EXPORT_SYMBOL(drm_release); > > -/** No-op. */ > +ssize_t drm_read(struct file *filp, char __user *buffer, > + size_t count, loff_t *offset) > The usage of drm_read here will clash with TTM bo read / write, which suggests perhaps moving TTM bo read / write to a character device of its own. That would affect Radeon to some extent if there is a wish to use consistently implemented read / write to Radeon buffer objects. > +{ > + struct drm_file *file_priv = filp->private_data; > + struct drm_device *dev = file_priv->minor->dev; > + struct drm_pending_event *event; > + ssize_t total, ret; > + > + ret = wait_event_interruptible(file_priv->event_wait, > + !list_empty(&file_priv->event_list)); > + if (ret < 0) > + return ret; > + > + total = 0; > + while (!list_empty(&file_priv->event_list)) { > + mutex_lock(&dev->struct_mutex); > + event = list_first_entry(&file_priv->event_list, > + struct drm_pending_event, link); > + if (total + event->event->length > count) { > + mutex_unlock(&dev->struct_mutex); > + break; > + } > + list_del(&event->link); > + mutex_unlock(&dev->struct_mutex); > + > + if (copy_to_user(buffer + total, > + event->event, event->event->length)) { > + total = -EFAULT; > + break; > + } > + > + total += event->event->length; > + event->destroy(event); > + } > + > + return total; > +} > +EXPORT_SYMBOL(drm_read); > + > unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) > { > A couple of years ago, any attempt to return anything else than 0 from drm poll resulted in an X server error. http://freedesktop.org/bugzilla/show_bug.cgi?id=1505. The fix mentioned in the bug was actually to return 0 from drm poll, and a comment about this is still present in drm.git. The above breaks drm for old X servers and all drivers, which I think is against drm policy? /Thomas |
From: Jesse B. <jb...@vi...> - 2009-08-17 21:03:46
|
On Mon, 17 Aug 2009 22:22:34 +0200 Thomas Hellström <th...@sh...> wrote: > Kristian Høgsberg wrote: > > This patch adds a vblank synced pageflip ioctl for to the > > modesetting family of ioctls. The ioctl takes a crtc and an fb and > > schedules a pageflip to the new fb at the next coming vertical > > blank event. This feature lets userspace implement tear-free > > updating of the screen contents with hw-guaranteed low latency page > > flipping. > > > > The ioctl is asynchronous in that it returns immediately and then > > later notifies the client by making an event available for reading > > on the drm fd. This lets applications add the drm fd to their main > > loop and handle other tasks while waiting for the flip to happen. > > The event includes the time of the flip, the frame counter and a 64 > > bit opaque token provided by user space in the ioctl. > > > > Based on work and suggestions from > > Jesse Barnes <jb...@vi...>, > > Jakob Bornecrantz <wal...@gm...>, > > Chris Wilson <ch...@ch...> > > > > Signed-off-by: Kristian Høgsberg <kr...@re...> > > Signed-off-by: Jesse Barnes <jb...@vi...> > > --- > > > > Ok, another version of this patch. This one has fixes to work with > > radeon kms plus a missing list head init that would cause an oops > > in the case where we schedule a flip before the previous one has > > been queued. > > > > I'm now ready to propose this patch for the 2.6.32 merge window. > > > > > Hi! > First a general question: There is some hardware (for example > Unichromes and Chrome9) that can schedule > page-flips in the command stream after optional vblank barriers. For > this kind of hardware the pageflip would be a matter of scheduling > the flip and fence the old and new buffers. No need for delayed > unpins and explicit user-space notifications, although the workqueue > could be handy to avoid command processing stalls in the vblank > barriers. How would such a scheme fit into the framework below? If you fence the flip, doesn't that mean you only unpin after it's completed? The initial version of this patch used a command stream flip, but I still had to worry about pinning... If you don't need the userspace notifications we could probably factor that out; do you see any issues with the flip ioctl itself? If not, we can change things as needed if/when more drivers support it... > A couple of years ago, any attempt to return anything else than 0 > from drm poll resulted in an X server error. > http://freedesktop.org/bugzilla/show_bug.cgi?id=1505. The fix > mentioned in the bug was actually to return 0 from drm poll, and a > comment about this is still present in drm.git. The above breaks drm > for old X servers and all drivers, which I think is against drm > policy? Ouch... I remember Kristian had some issues with this part, but I don't think this was one of them. -- Jesse Barnes, Intel Open Source Technology Center |
From: Kristian H. <kr...@bi...> - 2009-08-17 21:12:23
|
On Mon, Aug 17, 2009 at 4:36 PM, Jesse Barnes<jb...@vi...> wrote: > On Mon, 17 Aug 2009 22:22:34 +0200 > Thomas Hellström <th...@sh...> wrote: > >> Kristian Høgsberg wrote: >> > This patch adds a vblank synced pageflip ioctl for to the >> > modesetting family of ioctls. The ioctl takes a crtc and an fb and >> > schedules a pageflip to the new fb at the next coming vertical >> > blank event. This feature lets userspace implement tear-free >> > updating of the screen contents with hw-guaranteed low latency page >> > flipping. >> > >> > The ioctl is asynchronous in that it returns immediately and then >> > later notifies the client by making an event available for reading >> > on the drm fd. This lets applications add the drm fd to their main >> > loop and handle other tasks while waiting for the flip to happen. >> > The event includes the time of the flip, the frame counter and a 64 >> > bit opaque token provided by user space in the ioctl. >> > >> > Based on work and suggestions from >> > Jesse Barnes <jb...@vi...>, >> > Jakob Bornecrantz <wal...@gm...>, >> > Chris Wilson <ch...@ch...> >> > >> > Signed-off-by: Kristian Høgsberg <kr...@re...> >> > Signed-off-by: Jesse Barnes <jb...@vi...> >> > --- >> > >> > Ok, another version of this patch. This one has fixes to work with >> > radeon kms plus a missing list head init that would cause an oops >> > in the case where we schedule a flip before the previous one has >> > been queued. >> > >> > I'm now ready to propose this patch for the 2.6.32 merge window. >> > >> > >> Hi! >> First a general question: There is some hardware (for example >> Unichromes and Chrome9) that can schedule >> page-flips in the command stream after optional vblank barriers. For >> this kind of hardware the pageflip would be a matter of scheduling >> the flip and fence the old and new buffers. No need for delayed >> unpins and explicit user-space notifications, although the workqueue >> could be handy to avoid command processing stalls in the vblank >> barriers. How would such a scheme fit into the framework below? > > If you fence the flip, doesn't that mean you only unpin after it's > completed? The initial version of this patch used a command stream > flip, but I still had to worry about pinning... > > If you don't need the userspace notifications we could probably factor > that out; do you see any issues with the flip ioctl itself? If not, we > can change things as needed if/when more drivers support it... We need the async notiifcation for AIGLX. We can't just block the X server on the flip finishing, we need to be able to suspend and resume the AIGLX client. >> A couple of years ago, any attempt to return anything else than 0 >> from drm poll resulted in an X server error. >> http://freedesktop.org/bugzilla/show_bug.cgi?id=1505. The fix >> mentioned in the bug was actually to return 0 from drm poll, and a >> comment about this is still present in drm.git. The above breaks drm >> for old X servers and all drivers, which I think is against drm >> policy? > > Ouch... I remember Kristian had some issues with this part, but I don't > think this was one of them. The trouble I ran into here is that the WakeupHandler call chain is called even if select returns with an error, specifically ERESTART when the input SIGIO fires. When that happens the select read fd_set hasn't been changed, which means the drm fd looks readable. cheers, Kristian |
From: Kristian H. <kr...@bi...> - 2009-08-17 21:08:16
|
2009/8/17 Thomas Hellström <th...@sh...>: > Kristian Høgsberg wrote: >> This patch adds a vblank synced pageflip ioctl for to the modesetting >> family of ioctls. The ioctl takes a crtc and an fb and schedules a >> pageflip to the new fb at the next coming vertical blank event. This >> feature lets userspace implement tear-free updating of the screen contents >> with hw-guaranteed low latency page flipping. >> >> The ioctl is asynchronous in that it returns immediately and then later >> notifies the client by making an event available for reading on the drm fd. >> This lets applications add the drm fd to their main loop and handle other >> tasks while waiting for the flip to happen. The event includes the time >> of the flip, the frame counter and a 64 bit opaque token provided by >> user space in the ioctl. >> >> Based on work and suggestions from >> Jesse Barnes <jb...@vi...>, >> Jakob Bornecrantz <wal...@gm...>, >> Chris Wilson <ch...@ch...> >> >> Signed-off-by: Kristian Høgsberg <kr...@re...> >> Signed-off-by: Jesse Barnes <jb...@vi...> >> --- >> >> Ok, another version of this patch. This one has fixes to work with radeon >> kms plus a missing list head init that would cause an oops in the case >> where we schedule a flip before the previous one has been queued. >> >> I'm now ready to propose this patch for the 2.6.32 merge window. >> >> > Hi! > First a general question: There is some hardware (for example Unichromes > and Chrome9) that can schedule > page-flips in the command stream after optional vblank barriers. For > this kind of hardware the pageflip would be a matter of scheduling the > flip and fence the old and new buffers. No need for delayed unpins and > explicit user-space notifications, although the workqueue could be handy > to avoid command processing stalls in the vblank barriers. How would > such a scheme fit into the framework below? By sending an event back on the file descriptor we allow users of the API to wait on the flip to finish in a standard poll or select mainloop, where it can handle input from other sources while waiting. If you rely on fences, the application will block when it tries to access the buffers protected by the fence, and is unable to handle input from other sources while it's blocking. Even with the vsync barrier in the command stream (which intel hw also supports) you do need to keep the current front buffer pinned for the remainder of the frame, and then you need notification when the swap has happened so that you can unpin the old buffer. I'm sure Unichrome has a command to send an interrupt, which can be queued after the vsync barrier to run the workqueue and trigger this cleanup as well as sending the userspace notification. >> Kristian >> >> drivers/gpu/drm/drm_crtc.c | 170 ++++++++++++++++++++++++++++++- >> drivers/gpu/drm/drm_crtc_helper.c | 12 ++ >> drivers/gpu/drm/drm_drv.c | 1 + >> drivers/gpu/drm/drm_fops.c | 68 ++++++++++++- >> drivers/gpu/drm/drm_irq.c | 43 ++++++++ >> drivers/gpu/drm/i915/i915_drv.c | 1 + >> drivers/gpu/drm/i915/intel_display.c | 24 +++-- >> drivers/gpu/drm/radeon/radeon_display.c | 3 +- >> include/drm/drm.h | 25 +++++ >> include/drm/drmP.h | 32 ++++++ >> include/drm/drm_crtc.h | 27 +++++ >> include/drm/drm_crtc_helper.h | 4 + >> include/drm/drm_mode.h | 16 +++ >> 13 files changed, 414 insertions(+), 12 deletions(-) >> >> diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c >> index 8fab789..0906cb3 100644 >> --- a/drivers/gpu/drm/drm_crtc.c >> +++ b/drivers/gpu/drm/drm_crtc.c >> @@ -34,6 +34,8 @@ >> #include "drmP.h" >> #include "drm_crtc.h" >> >> +#undef set_base >> + >> struct drm_prop_enum_list { >> int type; >> char *name; >> @@ -342,6 +344,34 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb) >> EXPORT_SYMBOL(drm_framebuffer_cleanup); >> >> /** >> + * drm_crtc_async_flip - do a set_base call from a work queue >> + * @work: work struct >> + * >> + * Called when a set_base call is queued by the page flip code. This >> + * allows the flip ioctl itself to return immediately and allow userspace >> + * to continue working. >> + */ >> +static void drm_crtc_async_flip(struct work_struct *work) >> +{ >> + struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_flip); >> + struct drm_device *dev = crtc->dev; >> + struct drm_pending_flip *pending; >> + >> + BUG_ON(crtc->pending_flip == NULL); >> + >> + mutex_lock(&dev->struct_mutex); >> > > Here the struct mutex is taken before a call to a function that doesn't > even have dev as an argument. It's a work queue callback, there's no way we can change that to include a dev argument :) > Exactly what in the generic modesetting code is protected by the struct > mutex here? I'm a little fuzzy on the details, this part of the patch was carried over from Jesse's original work. I believe set_base is supposed to be called with the struct mutex held. > If the driver needs the struct mutex to protect internal data > structures, let the driver take care of the locking. If the struct mutex > protects the dev->flip_list, then we should take it around the > manipulation of that list only. I'd hate to see the not-too-careful use > of struct_mutex in some drivers leak out to generic code. I do believe it's needed for both set_base, and the pending_flip manpulation. >> + crtc->funcs->set_base(crtc, crtc->x, crtc->y, NULL); >> + >> + pending = crtc->pending_flip; >> + crtc->pending_flip = NULL; >> + >> + pending->frame = drm_vblank_count(dev, crtc->pipe); >> + list_add_tail(&pending->link, &dev->flip_list); >> + >> + mutex_unlock(&dev->struct_mutex); >> +} >> + >> +/** >> * drm_crtc_init - Initialise a new CRTC object >> * @dev: DRM device >> * @crtc: CRTC object to init >> @@ -352,17 +382,19 @@ EXPORT_SYMBOL(drm_framebuffer_cleanup); >> * >> * Inits a new object created as base part of an driver crtc object. >> */ >> -void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, >> +void drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc, int pipe, >> const struct drm_crtc_funcs *funcs) >> { >> crtc->dev = dev; >> crtc->funcs = funcs; >> + crtc->pipe = pipe; >> >> mutex_lock(&dev->mode_config.mutex); >> drm_mode_object_get(dev, &crtc->base, DRM_MODE_OBJECT_CRTC); >> >> list_add_tail(&crtc->head, &dev->mode_config.crtc_list); >> dev->mode_config.num_crtc++; >> + INIT_WORK(&crtc->async_flip, drm_crtc_async_flip); >> mutex_unlock(&dev->mode_config.mutex); >> } >> EXPORT_SYMBOL(drm_crtc_init); >> @@ -381,6 +413,9 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) >> { >> struct drm_device *dev = crtc->dev; >> >> + mutex_lock(&dev->mode_config.mutex); >> + flush_work(&crtc->async_flip); >> + >> if (crtc->gamma_store) { >> kfree(crtc->gamma_store); >> crtc->gamma_store = NULL; >> @@ -388,6 +423,7 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) >> >> drm_mode_object_put(dev, &crtc->base); >> list_del(&crtc->head); >> + mutex_unlock(&dev->mode_config.mutex); >> dev->mode_config.num_crtc--; >> } >> EXPORT_SYMBOL(drm_crtc_cleanup); >> @@ -2452,3 +2488,135 @@ out: >> mutex_unlock(&dev->mode_config.mutex); >> return ret; >> } >> + >> +/** >> + * drm_mode_page_flip_ioctl - page flip ioctl >> + * @dev: DRM device >> + * @data: ioctl args >> + * @file_priv: file private data >> + * >> + * The page flip ioctl replaces the current front buffer with a new >> + * one, using the CRTC's set_base function, which should just update >> + * the front buffer base pointer. It's up to set_base to make >> + * sure the update doesn't result in tearing (on some hardware the >> + * base register is double buffered, so this is easy). >> + * >> + * Note that this covers just the simple case of flipping the front >> + * buffer immediately. Interval handling and interlaced modes have to >> + * be handled by userspace, or with new ioctls. >> + */ >> +int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, >> + struct drm_file *file_priv) >> +{ >> + struct drm_pending_flip *pending; >> + struct drm_mode_page_flip *flip_data = data; >> + struct drm_mode_object *drm_obj, *fb_obj; >> + struct drm_crtc *crtc; >> + int ret = 0; >> + >> + 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 & (~DRM_MODE_PAGE_FLIP_FLAGS_MASK)) { >> + DRM_DEBUG("bad page flip flags\n"); >> + return -EINVAL; >> + } >> + >> + pending = kzalloc(sizeof *pending, GFP_KERNEL); >> + if (pending == NULL) >> + return -ENOMEM; >> + >> + 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; >> + } >> + >> + drm_obj = drm_mode_object_find(dev, flip_data->crtc_id, >> + DRM_MODE_OBJECT_CRTC); >> + if (!drm_obj) { >> + DRM_DEBUG("unknown crtc %d\n", flip_data->crtc_id); >> + ret = -ENOENT; >> + goto out_unlock; >> + } >> + crtc = obj_to_crtc(drm_obj); >> + if (!crtc->enabled) { >> + DRM_DEBUG("crtc %d not enabled\n", flip_data->crtc_id); >> + ret = -EINVAL; >> + goto out_unlock; >> + } >> + >> + if (crtc->fb->funcs->unpin == NULL) { >> + DRM_DEBUG("fb for crtc %d does not support delayed unpin\n", >> + flip_data->crtc_id); >> + ret = -ENODEV; >> + goto out_unlock; >> + } >> + >> + pending->crtc = crtc; >> + pending->old_fb = crtc->fb; >> + pending->pipe = crtc->pipe; >> + INIT_LIST_HEAD(&pending->link); >> + pending->event.base.type = DRM_EVENT_MODE_PAGE_FLIP; >> + pending->event.base.length = sizeof pending->event; >> + pending->event.user_data = flip_data->user_data; >> + pending->pending_event.event = &pending->event.base; >> + pending->pending_event.file_priv = file_priv; >> + pending->pending_event.destroy = >> + (void (*) (struct drm_pending_event *)) kfree; >> + >> + /* Get vblank ref for completion handling */ >> + ret = drm_vblank_get(dev, crtc->pipe); >> + if (ret) { >> + DRM_DEBUG("failed to take vblank ref\n"); >> + goto out_unlock; >> + } >> + >> + /* >> + * The set_base call will change the domain on the new fb, >> + * which will force the rendering to finish and block the >> + * ioctl. We need to do this last part from a work queue, to >> + * avoid blocking userspace here. >> + */ >> + crtc->fb = obj_to_fb(fb_obj); >> + >> + if (crtc->pending_flip != NULL) { >> + struct drm_pending_flip *old_flip; >> + >> + /* We have an outstanding flip request for this crtc/pipe. >> + * In order to satisfy the user we can either queue the requests >> + * and apply them on sequential vblanks, or we can drop old >> + * requests. >> + * >> + * Here we choose to discard the previous request for >> + * simplicity. Note that since we have not yet applied the >> + * previous flip, we need to preserve the original (i.e. still >> + * current) fb. >> + */ >> + >> + old_flip = crtc->pending_flip; >> + pending->old_fb = old_flip->old_fb; >> + old_flip->old_fb = NULL; >> + drm_finish_pending_flip (dev, old_flip, 0); >> + } else >> + schedule_work(&crtc->async_flip); >> + crtc->pending_flip = pending; >> + >> + mutex_unlock(&dev->struct_mutex); >> + >> + return 0; >> + >> +out_unlock: >> + mutex_unlock(&dev->struct_mutex); >> + kfree(pending); >> + >> + return ret; >> +} >> diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c >> index 3da9cfa..5a26bab 100644 >> --- a/drivers/gpu/drm/drm_crtc_helper.c >> +++ b/drivers/gpu/drm/drm_crtc_helper.c >> @@ -868,8 +868,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); >> > > dev->struct_mutex again! Protecting mode_set_base again. >> 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; >> } >> @@ -1095,3 +1097,13 @@ int drm_helper_resume_force_mode(struct drm_device *dev) >> return 0; >> } >> EXPORT_SYMBOL(drm_helper_resume_force_mode); >> + >> +int >> +drm_crtc_helper_set_base(struct drm_crtc *crtc, int x, int y, >> + struct drm_framebuffer *old_fb) >> +{ >> + struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; >> + >> + return crtc_funcs->mode_set_base(crtc, x, y, old_fb); >> +} >> +EXPORT_SYMBOL(drm_crtc_helper_set_base); >> diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c >> index b39d7bf..c66c993 100644 >> --- a/drivers/gpu/drm/drm_drv.c >> +++ b/drivers/gpu/drm/drm_drv.c >> @@ -145,6 +145,7 @@ static struct drm_ioctl_desc drm_ioctls[] = { >> DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_MASTER|DRM_CONTROL_ALLOW), >> DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_MASTER|DRM_CONTROL_ALLOW), >> DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_MASTER|DRM_CONTROL_ALLOW), >> + DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW), >> }; >> >> #define DRM_CORE_IOCTL_COUNT ARRAY_SIZE( drm_ioctls ) >> diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c >> index 251bc0e..dcd9c66 100644 >> --- a/drivers/gpu/drm/drm_fops.c >> +++ b/drivers/gpu/drm/drm_fops.c >> @@ -257,6 +257,8 @@ static int drm_open_helper(struct inode *inode, struct file *filp, >> >> INIT_LIST_HEAD(&priv->lhead); >> INIT_LIST_HEAD(&priv->fbs); >> + INIT_LIST_HEAD(&priv->event_list); >> + init_waitqueue_head(&priv->event_wait); >> >> if (dev->driver->driver_features & DRIVER_GEM) >> drm_gem_open(dev, priv); >> @@ -429,6 +431,9 @@ int drm_release(struct inode *inode, struct file *filp) >> { >> struct drm_file *file_priv = filp->private_data; >> struct drm_device *dev = file_priv->minor->dev; >> + struct drm_pending_flip *f, *ft; >> + struct drm_pending_event *e, *et; >> + >> int retcode = 0; >> >> lock_kernel(); >> @@ -451,6 +456,19 @@ int drm_release(struct inode *inode, struct file *filp) >> if (file_priv->minor->master) >> drm_master_release(dev, filp); >> >> + mutex_lock(&dev->struct_mutex); >> + >> + /* Remove pending flips */ >> + list_for_each_entry_safe(f, ft, &dev->flip_list, link) >> + if (f->pending_event.file_priv == file_priv) >> + drm_finish_pending_flip(dev, f, 0); >> + >> + /* Remove unconsumed events */ >> + list_for_each_entry_safe(e, et, &file_priv->event_list, link) >> + e->destroy(e); >> + >> + mutex_unlock(&dev->struct_mutex); >> + >> if (dev->driver->driver_features & DRIVER_GEM) >> drm_gem_release(dev, file_priv); >> >> @@ -544,9 +562,55 @@ int drm_release(struct inode *inode, struct file *filp) >> } >> EXPORT_SYMBOL(drm_release); >> >> -/** No-op. */ >> +ssize_t drm_read(struct file *filp, char __user *buffer, >> + size_t count, loff_t *offset) >> > > The usage of drm_read here will clash with TTM bo read / write, which > suggests perhaps moving TTM bo read / write to a character device of its > own. That would affect Radeon to some extent if there is a wish to use > consistently implemented read / write to Radeon buffer objects. Perhaps TTM bo read/write could use ioctls like the gem pwrite/pread ioctls? The only way we can get asynchronous notifications from the drm to userspace is through readable events on the drm fd. How were you planning to implement read and write from multiple bo's through one fd anyway? Reusing the fake offset we use for mmap? I think the ioctl approach is cleaner since you can just pass in the object handle and the offset into the object. Maybe even add src and dst stride arguments. >> +{ >> + struct drm_file *file_priv = filp->private_data; >> + struct drm_device *dev = file_priv->minor->dev; >> + struct drm_pending_event *event; >> + ssize_t total, ret; >> + >> + ret = wait_event_interruptible(file_priv->event_wait, >> + !list_empty(&file_priv->event_list)); >> + if (ret < 0) >> + return ret; >> + >> + total = 0; >> + while (!list_empty(&file_priv->event_list)) { >> + mutex_lock(&dev->struct_mutex); >> + event = list_first_entry(&file_priv->event_list, >> + struct drm_pending_event, link); >> + if (total + event->event->length > count) { >> + mutex_unlock(&dev->struct_mutex); >> + break; >> + } >> + list_del(&event->link); >> + mutex_unlock(&dev->struct_mutex); >> + >> + if (copy_to_user(buffer + total, >> + event->event, event->event->length)) { >> + total = -EFAULT; >> + break; >> + } >> + >> + total += event->event->length; >> + event->destroy(event); >> + } >> + >> + return total; >> +} >> +EXPORT_SYMBOL(drm_read); >> + >> unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) >> { >> > > A couple of years ago, any attempt to return anything else than 0 from > drm poll resulted in an X server error. > http://freedesktop.org/bugzilla/show_bug.cgi?id=1505. The fix mentioned > in the bug was actually to return 0 from drm poll, and a comment about > this is still present in drm.git. The above breaks drm for old X servers > and all drivers, which I think is against drm policy? That can't be the real problem. The X server polls on a ton of file descriptors already; sockets from clients, dbus, input devices. They all have poll implementations that don't return 0... I mean, otherwise they wouldn't work. Look at evdev_poll() in drivers/input/evdev.c for the evdev poll implementation, for example. cheers, Kristian |
From: Thomas H. <th...@sh...> - 2009-08-18 07:52:59
|
Kristian Høgsberg wrote: > 2009/8/17 Thomas Hellström <th...@sh...>: > >> Kristian Høgsberg wrote: >> >>> This patch adds a vblank synced pageflip ioctl for to the modesetting >>> family of ioctls. The ioctl takes a crtc and an fb and schedules a >>> pageflip to the new fb at the next coming vertical blank event. This >>> feature lets userspace implement tear-free updating of the screen contents >>> with hw-guaranteed low latency page flipping. >>> >>> The ioctl is asynchronous in that it returns immediately and then later >>> notifies the client by making an event available for reading on the drm fd. >>> This lets applications add the drm fd to their main loop and handle other >>> tasks while waiting for the flip to happen. The event includes the time >>> of the flip, the frame counter and a 64 bit opaque token provided by >>> user space in the ioctl. >>> >>> Based on work and suggestions from >>> Jesse Barnes <jb...@vi...>, >>> Jakob Bornecrantz <wal...@gm...>, >>> Chris Wilson <ch...@ch...> >>> >>> Signed-off-by: Kristian Høgsberg <kr...@re...> >>> Signed-off-by: Jesse Barnes <jb...@vi...> >>> --- >>> >>> Ok, another version of this patch. This one has fixes to work with radeon >>> kms plus a missing list head init that would cause an oops in the case >>> where we schedule a flip before the previous one has been queued. >>> >>> I'm now ready to propose this patch for the 2.6.32 merge window. >>> >>> >>> >> Hi! >> First a general question: There is some hardware (for example Unichromes >> and Chrome9) that can schedule >> page-flips in the command stream after optional vblank barriers. For >> this kind of hardware the pageflip would be a matter of scheduling the >> flip and fence the old and new buffers. No need for delayed unpins and >> explicit user-space notifications, although the workqueue could be handy >> to avoid command processing stalls in the vblank barriers. How would >> such a scheme fit into the framework below? >> > > By sending an event back on the file descriptor we allow users of the > API to wait on the flip to finish in a standard poll or select > mainloop, where it can handle input from other sources while waiting. > If you rely on fences, the application will block when it tries to > access the buffers protected by the fence, and is unable to handle > input from other sources while it's blocking. > Yes. you're right. How is the flip_finished event typically used by user space? Is it mostly for convenience or is it a must have? > Even with the vsync barrier in the command stream (which intel hw also > supports) you do need to keep the current front buffer pinned for the > remainder of the frame, and then you need notification when the swap > has happened so that you can unpin the old buffer. That would be taken care of by fences (see previous mail), but that's merely a terminology thing. The big difference is that the driver would handle that synchronization as part of command submission. > I'm sure Unichrome > has a command to send an interrupt, which can be queued after the > vsync barrier to run the workqueue and trigger this cleanup as well as > sending the userspace notification. > Actually it hasn't (stupid hardware), so a medium frequency polling scheme needs to be used for most GPU waiting. So the user-space notification would require some additional thoughts but nothing that can't be implemented when the need for it arises. /Thomas |
From: Kristian H. <kr...@bi...> - 2009-08-18 15:44:43
|
On Tue, Aug 18, 2009 at 3:52 AM, Thomas Hellström<th...@sh...> wrote: > Kristian Høgsberg wrote: >> By sending an event back on the file descriptor we allow users of the >> API to wait on the flip to finish in a standard poll or select >> mainloop, where it can handle input from other sources while waiting. >> If you rely on fences, the application will block when it tries to >> access the buffers protected by the fence, and is unable to handle >> input from other sources while it's blocking. >> > > Yes. you're right. How is the flip_finished event typically used by user > space? > Is it mostly for convenience or is it a must have? In the server we use the event to wake up the client who calls glXSwapBuffer(). We block the client (SuspendClient) if it tries to draw before the swap is complete and wake it up again when we get the event from drm. See the dri2-swapbuffers branch in xorg/xserver git for details. >> Even with the vsync barrier in the command stream (which intel hw also >> supports) you do need to keep the current front buffer pinned for the >> remainder of the frame, and then you need notification when the swap >> has happened so that you can unpin the old buffer. > > That would be taken care of by fences (see previous mail), but that's merely > a terminology thing. The big difference is that the driver would handle that > synchronization as part of command submission. Right, but you'll still need an interrupt inside the drm driver to let you know that the vblank barrier has been processed so you can clear the fence. >> I'm sure Unichrome >> has a command to send an interrupt, which can be queued after the >> vsync barrier to run the workqueue and trigger this cleanup as well as >> sending the userspace notification. >> > > Actually it hasn't (stupid hardware), so a medium frequency polling scheme > needs to be used for most GPU waiting. > So the user-space notification would require some additional thoughts but > nothing > that can't be implemented when the need for it arises. Yikes. Is there a way to at least get a timestamp from the hw at the time the swap happens? cheers, Kristian |
From: Thomas H. <th...@sh...> - 2009-08-18 17:36:57
|
Kristian Høgsberg wrote: > On Tue, Aug 18, 2009 at 3:52 AM, Thomas Hellström<th...@sh...> wrote: > >> Kristian Høgsberg wrote: >> >>> By sending an event back on the file descriptor we allow users of the >>> API to wait on the flip to finish in a standard poll or select >>> mainloop, where it can handle input from other sources while waiting. >>> If you rely on fences, the application will block when it tries to >>> access the buffers protected by the fence, and is unable to handle >>> input from other sources while it's blocking. >>> >>> >> Yes. you're right. How is the flip_finished event typically used by user >> space? >> Is it mostly for convenience or is it a must have? >> > > In the server we use the event to wake up the client who calls > glXSwapBuffer(). We block the client (SuspendClient) if it tries to > draw before the swap is complete and wake it up again when we get the > event from drm. See the dri2-swapbuffers branch in xorg/xserver git > for details. > So in the case where you have the command submission inserting relevant barriers (like hardware barriers in the Unichrome case, or fence class barriers in the general TTM fence-aware driver case, this notification is not really necessary since the flip is guaranteed to happen before any other rendering commands that touch the buffers in question. So modulo the flip timing notifications, the flip_finished event can be sent already when the flip is scheduled. Are the flip timing notifications required to be accurate? If so we have a slight problem, because we don't unnecessarily want to put clients to sleep, and we don't know when the flip has happened until it has actually happened. /Thomas |
From: Thomas H. <th...@sh...> - 2009-08-18 19:40:40
|
Keith Whitwell wrote: > This seems wrong to me -- the client doesn't need to sleep - all it's going to do is build a command buffer targeting the new backbuffer. There's no problem with that, it should be the preserve of the GPU scheduler (TTM or GEM) to ensure those commands (once submitted) don't get executed until the buffer is available - otherwise you're potentially pausing your application for no good reason. The app should be throttled if it gets more than a couple of frames ahead, but there should be 100% overlap with hardware otherwise. > > If you need a solution that doesn't rely on the buffer manager, perhaps resort to triple-buffering, or else create a new buffer and return that in DRI2GetBuffers (and let the scanout one be freed once the flip is done). > > It seems like arbitrating command execution against on-hardware buffers should be the preserve of the kernel memory manager & other actors shouldn't be double-guessing that. > I agree. It would be fairly trivial to use TTMs synchronization mechanisms to put special fences on buffers with outstanding flips. These fences signal when a "software flipper" engine has programmed a flip. Any command submission involving these buffers while fenced will effectively put the rendering client to sleep. Rendering to other buffers, including a potential third buffer would continue undisturbed. Pinning and unpinning of scanouts would take place at command submission time as detailed before. Of course, in the case of AIGLX, the X server might temporarily be put to sleep, which might not be fully optimal. A "software flipper" engine would be a simple vblank irq handler that flips and signals the flipper fence when the previous fences of the buffers (if present) have signaled (to finish any outstanding rendering before flipping). I guess GEM's buffer object busy lists be used to accomodate something similar? /Thomas > Keith > ________________________________________ > From: Kristian Høgsberg [kr...@bi...] > Sent: Tuesday, August 18, 2009 11:46 AM > To: Thomas Hellström > Cc: Kristian Høgsberg; Jesse Barnes; dri...@li... > Subject: Re: [PATCH] Add modesetting pageflip ioctl and corresponding drm event > > We don't put clients to sleep until they try to render to the new > backbuffer. For direct rendering this happens when the client calls > DRI2GetBuffers() after having called DRI2SwapBuffers(). If the flip > is not yet finished at that time, we restart the X request and suspend > the client. When the drm event fires it is read by the ddx driver, > which then calls DRI2SwapComplete() which will wake the client up > again. For AIGLX, we suspend the client in __glXForceCurrent(), but > the wakeup happens the same way. > |
From: Thomas H. <th...@sh...> - 2009-08-18 19:47:53
|
Thomas Hellström wrote: > Keith Whitwell wrote: >> This seems wrong to me -- the client doesn't need to sleep - all it's >> going to do is build a command buffer targeting the new backbuffer. >> There's no problem with that, it should be the preserve of the GPU >> scheduler (TTM or GEM) to ensure those commands (once submitted) >> don't get executed until the buffer is available - otherwise you're >> potentially pausing your application for no good reason. The app >> should be throttled if it gets more than a couple of frames ahead, >> but there should be 100% overlap with hardware otherwise. >> >> If you need a solution that doesn't rely on the buffer manager, >> perhaps resort to triple-buffering, or else create a new buffer and >> return that in DRI2GetBuffers (and let the scanout one be freed once >> the flip is done). >> It seems like arbitrating command execution against on-hardware >> buffers should be the preserve of the kernel memory manager & other >> actors shouldn't be double-guessing that. >> > I agree. > > It would be fairly trivial to use TTMs synchronization mechanisms to > put special fences on buffers with outstanding flips. These fences > signal when a "software flipper" engine has programmed a flip. Any > command submission involving these buffers while fenced will > effectively put the rendering client to sleep. Rendering to other > buffers, including a potential third buffer would continue > undisturbed. Pinning and unpinning of scanouts would take place at > command submission time as detailed before. > Of course, in the case of AIGLX, the X server might temporarily be put > to sleep, which might not be fully optimal. > > A "software flipper" engine would be a simple vblank irq handler that > flips and signals the flipper fence when the previous fences of the > buffers (if present) have signaled (to finish any outstanding > rendering before flipping). > > I guess GEM's buffer object busy lists be used to accomodate something > similar? > > /Thomas > Oh, and a fully-fledged GPU scheduler might of course temporarily buffer the hw commands without putting the client to sleep, just as Keith's suggests. In any case there's a full range of hw and scheduler capabilities that won't require the X server to do scheduling and put clients to sleep. Why not leave this to the kernel? /Thomas > >> Keith >> ________________________________________ >> From: Kristian Høgsberg [kr...@bi...] >> Sent: Tuesday, August 18, 2009 11:46 AM >> To: Thomas Hellström >> Cc: Kristian Høgsberg; Jesse Barnes; dri...@li... >> Subject: Re: [PATCH] Add modesetting pageflip ioctl and corresponding >> drm event >> >> We don't put clients to sleep until they try to render to the new >> backbuffer. For direct rendering this happens when the client calls >> DRI2GetBuffers() after having called DRI2SwapBuffers(). If the flip >> is not yet finished at that time, we restart the X request and suspend >> the client. When the drm event fires it is read by the ddx driver, >> which then calls DRI2SwapComplete() which will wake the client up >> again. For AIGLX, we suspend the client in __glXForceCurrent(), but >> the wakeup happens the same way. >> > > |
From: Luc V. <li...@sk...> - 2009-08-18 23:30:53
|
On Wed, Aug 19, 2009 at 09:22:10AM +1000, Dave Airlie wrote: > On Wed, Aug 19, 2009 at 9:12 AM, Luc Verhaegen<li...@sk...> wrote: > > On Wed, Aug 19, 2009 at 09:07:55AM +1000, Dave Airlie wrote: > >> On Wed, Aug 19, 2009 at 8:03 AM, Luc Verhaegen<li...@sk...> wrote: > >> > On Wed, Aug 19, 2009 at 07:03:41AM +1000, Dave Airlie wrote: > >> >> On Wed, Aug 19, 2009 at 2:12 AM, Keith Whitwell<ke...@vm...> wrote: > >> >> > I think the bug in question was because somebody (Jon Smirl??) > >> >> > removed the empty & apparently unused poll implementation from the > >> >> > drm fd, only to discover that the X server was actually polling the > >> >> > fd. > >> >> > >> >> You have a better memory than me, but thats exactly what happened alright. > >> >> > >> >> Dave. > >> > > >> > You never did any of the sort, right? > >> > > >> > Try pouncing issues created by those who are currently still active, > >> > preferably shortly after they made their mistakes, even if they belong > >> > to your current political faction. > >> > > >> > >> Thanks Luc, yet again your technical contribution to the topic at hand > >> is outstandingly useful, hopefully any future employers understand your > >> ability to involve yourself in technical discourse. > >> > >> Dave. > > > > And on the flip side of this, what you do is purely technical, always. > > > > #dri-devel 00:05 <+airlied> krh: you've been smirled > > > > Thanks Luc, yet again your technical contribution to the topic at hand > is outstandingly useful, hopefully any future employers understand your > ability to involve yourself in technical discourse. > > Dave. Dave, Ask yourself whether your statement, the one i replied to, was a technical contribution, or something else? Luc Verhaegen. |
From: Dave A. <ai...@gm...> - 2009-08-18 23:37:17
|
On Wed, Aug 19, 2009 at 9:31 AM, Luc Verhaegen<li...@sk...> wrote: > On Wed, Aug 19, 2009 at 09:22:10AM +1000, Dave Airlie wrote: >> On Wed, Aug 19, 2009 at 9:12 AM, Luc Verhaegen<li...@sk...> wrote: >> > On Wed, Aug 19, 2009 at 09:07:55AM +1000, Dave Airlie wrote: >> >> On Wed, Aug 19, 2009 at 8:03 AM, Luc Verhaegen<li...@sk...> wrote: >> >> > On Wed, Aug 19, 2009 at 07:03:41AM +1000, Dave Airlie wrote: >> >> >> On Wed, Aug 19, 2009 at 2:12 AM, Keith Whitwell<ke...@vm...> wrote: >> >> >> > I think the bug in question was because somebody (Jon Smirl??) >> >> >> > removed the empty & apparently unused poll implementation from the >> >> >> > drm fd, only to discover that the X server was actually polling the >> >> >> > fd. >> >> >> >> >> >> You have a better memory than me, but thats exactly what happened alright. >> >> >> >> >> >> Dave. >> >> > >> >> > You never did any of the sort, right? >> >> > >> >> > Try pouncing issues created by those who are currently still active, >> >> > preferably shortly after they made their mistakes, even if they belong >> >> > to your current political faction. >> >> > >> >> >> >> Thanks Luc, yet again your technical contribution to the topic at hand >> >> is outstandingly useful, hopefully any future employers understand your >> >> ability to involve yourself in technical discourse. >> >> >> >> Dave. >> > >> > And on the flip side of this, what you do is purely technical, always. >> > >> > #dri-devel 00:05 <+airlied> krh: you've been smirled >> > >> >> Thanks Luc, yet again your technical contribution to the topic at hand >> is outstandingly useful, hopefully any future employers understand your >> ability to involve yourself in technical discourse. >> >> Dave. > > Dave, > > Ask yourself whether your statement, the one i replied to, was a > technical contribution, or something else? > this email has a subject line, you are replying. so Thanks Luc, yet again your technical contribution to the topic at hand is outstandingly useful, hopefully any future employers understand your ability to involve yourself in technical discourse. Dave. |
From: Daniel S. <da...@fo...> - 2009-08-19 00:07:37
|
On Wed, Aug 19, 2009 at 01:31:06AM +0200, Luc Verhaegen wrote: > Dave, > > Ask yourself whether your statement, the one i replied to, was a > technical contribution, or something else? Luc, Just ... stop. This is embarassing. Daniel |
From: Thomas H. <th...@sh...> - 2009-08-18 06:08:39
|
Kristian Høgsberg wrote: > 2009/8/17 Thomas Hellström <th...@sh...>: > >> Kristian Høgsberg wrote: >> >>> This patch adds a vblank synced pageflip ioctl for to the modesetting >>> family of ioctls. The ioctl takes a crtc and an fb and schedules a >>> pageflip to the new fb at the next coming vertical blank event. This >>> feature lets userspace implement tear-free updating of the screen contents >>> with hw-guaranteed low latency page flipping. >>> >>> The ioctl is asynchronous in that it returns immediately and then later >>> notifies the client by making an event available for reading on the drm fd. >>> This lets applications add the drm fd to their main loop and handle other >>> tasks while waiting for the flip to happen. The event includes the time >>> of the flip, the frame counter and a 64 bit opaque token provided by >>> user space in the ioctl. >>> >>> Based on work and suggestions from >>> Jesse Barnes <jb...@vi...>, >>> Jakob Bornecrantz <wal...@gm...>, >>> Chris Wilson <ch...@ch...> >>> >>> Signed-off-by: Kristian Høgsberg <kr...@re...> >>> Signed-off-by: Jesse Barnes <jb...@vi...> >>> --- >>> >>> Ok, another version of this patch. This one has fixes to work with radeon >>> kms plus a missing list head init that would cause an oops in the case >>> where we schedule a flip before the previous one has been queued. >>> >>> I'm now ready to propose this patch for the 2.6.32 merge window. >>> >>> >>> >> Hi! >> First a general question: There is some hardware (for example Unichromes >> and Chrome9) that can schedule >> page-flips in the command stream after optional vblank barriers. For >> this kind of hardware the pageflip would be a matter of scheduling the >> flip and fence the old and new buffers. No need for delayed unpins and >> explicit user-space notifications, although the workqueue could be handy >> to avoid command processing stalls in the vblank barriers. How would >> such a scheme fit into the framework below? >> > > By sending an event back on the file descriptor we allow users of the > API to wait on the flip to finish in a standard poll or select > mainloop, where it can handle input from other sources while waiting. > If you rely on fences, the application will block when it tries to > access the buffers protected by the fence, and is unable to handle > input from other sources while it's blocking. > > Even with the vsync barrier in the command stream (which intel hw also > supports) you do need to keep the current front buffer pinned for the > remainder of the frame, and then you need notification when the swap > has happened so that you can unpin the old buffer. I'm sure Unichrome > has a command to send an interrupt, which can be queued after the > vsync barrier to run the workqueue and trigger this cleanup as well as > sending the userspace notification. > > >>> Kristian >>> >>> drivers/gpu/drm/drm_crtc.c | 170 ++++++++++++++++++++++++++++++- >>> drivers/gpu/drm/drm_crtc_helper.c | 12 ++ >>> drivers/gpu/drm/drm_drv.c | 1 + >>> drivers/gpu/drm/drm_fops.c | 68 ++++++++++++- >>> drivers/gpu/drm/drm_irq.c | 43 ++++++++ >>> drivers/gpu/drm/i915/i915_drv.c | 1 + >>> drivers/gpu/drm/i915/intel_display.c | 24 +++-- >>> drivers/gpu/drm/radeon/radeon_display.c | 3 +- >>> include/drm/drm.h | 25 +++++ >>> include/drm/drmP.h | 32 ++++++ >>> include/drm/drm_crtc.h | 27 +++++ >>> include/drm/drm_crtc_helper.h | 4 + >>> include/drm/drm_mode.h | 16 +++ >>> 13 files changed, 414 insertions(+), 12 deletions(-) >>> >>> diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c >>> index 8fab789..0906cb3 100644 >>> --- a/drivers/gpu/drm/drm_crtc.c >>> +++ b/drivers/gpu/drm/drm_crtc.c >>> @@ -34,6 +34,8 @@ >>> #include "drmP.h" >>> #include "drm_crtc.h" >>> >>> +#undef set_base >>> + >>> struct drm_prop_enum_list { >>> int type; >>> char *name; >>> @@ -342,6 +344,34 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb) >>> EXPORT_SYMBOL(drm_framebuffer_cleanup); >>> >>> /** >>> + * drm_crtc_async_flip - do a set_base call from a work queue >>> + * @work: work struct >>> + * >>> + * Called when a set_base call is queued by the page flip code. This >>> + * allows the flip ioctl itself to return immediately and allow userspace >>> + * to continue working. >>> + */ >>> +static void drm_crtc_async_flip(struct work_struct *work) >>> +{ >>> + struct drm_crtc *crtc = container_of(work, struct drm_crtc, async_flip); >>> + struct drm_device *dev = crtc->dev; >>> + struct drm_pending_flip *pending; >>> + >>> + BUG_ON(crtc->pending_flip == NULL); >>> + >>> + mutex_lock(&dev->struct_mutex); >>> >>> >> Here the struct mutex is taken before a call to a function that doesn't >> even have dev as an argument. >> > > It's a work queue callback, there's no way we can change that to > include a dev argument :) > > >> Exactly what in the generic modesetting code is protected by the struct >> mutex here? >> > > I'm a little fuzzy on the details, this part of the patch was carried > over from Jesse's original work. I believe set_base is supposed to be > called with the struct mutex held. > > We shouldn't be this sloppy about locking, and in particular we shouldn't protect a callback with a lock without knowing why. A lock should protect data, not serialize calls to functions, and it should ideally be documented somewhere. Why does set_base need the _caller_ to take the struct_mutex lock? Are there more driver callbacks that need the struct_mutex held? I mean if I implement a modesetting driver that takes care to use device private locks not visible to the caller to avoid polluting the struct_mutex with possible contention and lockdep errors as a result, I'd still find the generic code taking the struct_mutex for me becase it assumes that I need it? This can't be right and should go away. > > > Protecting mode_set_base again. > > > Same arguments again... :) > > Perhaps TTM bo read/write could use ioctls like the gem pwrite/pread > ioctls? The only way we can get asynchronous notifications from the > drm to userspace is through readable events on the drm fd. How were > you planning to implement read and write from multiple bo's through > one fd anyway? Reusing the fake offset we use for mmap? I think the > ioctl approach is cleaner since you can just pass in the object handle > and the offset into the object. Maybe even add src and dst stride > arguments. > > It's a common misconception that the TTM bo offsets are fake offsets from outer space. The offsets aren't fake offsets but real offsets into the device address space, so a logical consequence of mapping a part of that address space is to be able to read and write to the same address space, That is read / write implemented using syscalls as intended. That said, rectangular bo blit ioctls is probably a good idea as well, and some drivers, like via, already have them. But I do think that if we claim device reads and writes for modesetting, we should move bo IO to another device to be consistent. >>> +{ >>> + struct drm_file *file_priv = filp->private_data; >>> + struct drm_device *dev = file_priv->minor->dev; >>> + struct drm_pending_event *event; >>> + ssize_t total, ret; >>> + >>> + ret = wait_event_interruptible(file_priv->event_wait, >>> + !list_empty(&file_priv->event_list)); >>> + if (ret < 0) >>> + return ret; >>> + >>> + total = 0; >>> + while (!list_empty(&file_priv->event_list)) { >>> + mutex_lock(&dev->struct_mutex); >>> + event = list_first_entry(&file_priv->event_list, >>> + struct drm_pending_event, link); >>> + if (total + event->event->length > count) { >>> + mutex_unlock(&dev->struct_mutex); >>> + break; >>> + } >>> + list_del(&event->link); >>> + mutex_unlock(&dev->struct_mutex); >>> + >>> + if (copy_to_user(buffer + total, >>> + event->event, event->event->length)) { >>> + total = -EFAULT; >>> + break; >>> + } >>> + >>> + total += event->event->length; >>> + event->destroy(event); >>> + } >>> + >>> + return total; >>> +} >>> +EXPORT_SYMBOL(drm_read); >>> + >>> unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) >>> { >>> >>> >> A couple of years ago, any attempt to return anything else than 0 from >> drm poll resulted in an X server error. >> http://freedesktop.org/bugzilla/show_bug.cgi?id=1505. The fix mentioned >> in the bug was actually to return 0 from drm poll, and a comment about >> this is still present in drm.git. The above breaks drm for old X servers >> and all drivers, which I think is against drm policy? >> > > That can't be the real problem. The X server polls on a ton of file > descriptors already; sockets from clients, dbus, input devices. They > all have poll implementations that don't return 0... I mean, otherwise > they wouldn't work. Look at evdev_poll() in drivers/input/evdev.c for > the evdev poll implementation, for example. > You're probably right, but we should probably find out what went wrong and make sure it doesn't happen again with non-modesetting drivers + dri1 before pushing this. > cheers, > Kristian > Thanks, Thomas |