From 57c5ee79acba9582762c09c269e0e2ae1adf1b31 Mon Sep 17 00:00:00 2001 From: Thomas Hellstrom Date: Mon, 10 Oct 2011 12:23:26 +0200 Subject: [PATCH] vmwgfx: Add fence events Add a way to send DRM events down the gpu fifo by attaching them to fence objects. This may be useful for Xserver swapbuffer throttling and page-flip done notifications. Bump version to 2.2 to signal the availability of the FENCE_EVENT ioctl. Signed-off-by: Thomas Hellstrom Reviewed-by: Jakob Bornecrantz Signed-off-by: Dave Airlie --- drivers/gpu/drm/vmwgfx/vmwgfx_drv.c | 6 + drivers/gpu/drm/vmwgfx/vmwgfx_drv.h | 16 +- drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c | 2 +- drivers/gpu/drm/vmwgfx/vmwgfx_fence.c | 533 +++++++++++++++++++++++++++++++- drivers/gpu/drm/vmwgfx/vmwgfx_fence.h | 12 +- drivers/gpu/drm/vmwgfx/vmwgfx_fifo.c | 10 +- drivers/gpu/drm/vmwgfx/vmwgfx_irq.c | 67 ++-- include/drm/vmwgfx_drm.h | 47 ++- 8 files changed, 644 insertions(+), 49 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c index ddb5abd..5e1994a 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c @@ -94,6 +94,9 @@ #define DRM_IOCTL_VMW_FENCE_UNREF \ DRM_IOW(DRM_COMMAND_BASE + DRM_VMW_FENCE_UNREF, \ struct drm_vmw_fence_arg) +#define DRM_IOCTL_VMW_FENCE_EVENT \ + DRM_IOW(DRM_COMMAND_BASE + DRM_VMW_FENCE_EVENT, \ + struct drm_vmw_fence_event_arg) #define DRM_IOCTL_VMW_PRESENT \ DRM_IOW(DRM_COMMAND_BASE + DRM_VMW_PRESENT, \ struct drm_vmw_present_arg) @@ -150,6 +153,9 @@ static struct drm_ioctl_desc vmw_ioctls[] = { DRM_AUTH | DRM_UNLOCKED), VMW_IOCTL_DEF(VMW_FENCE_UNREF, vmw_fence_obj_unref_ioctl, DRM_AUTH | DRM_UNLOCKED), + VMW_IOCTL_DEF(VMW_FENCE_EVENT, + vmw_fence_event_ioctl, + DRM_AUTH | DRM_UNLOCKED), VMW_IOCTL_DEF(VMW_GET_3D_CAP, vmw_get_cap_3d_ioctl, DRM_AUTH | DRM_UNLOCKED), diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h index 8cce73e..83b2563 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h @@ -40,9 +40,9 @@ #include "ttm/ttm_module.h" #include "vmwgfx_fence.h" -#define VMWGFX_DRIVER_DATE "20110927" +#define VMWGFX_DRIVER_DATE "20111008" #define VMWGFX_DRIVER_MAJOR 2 -#define VMWGFX_DRIVER_MINOR 1 +#define VMWGFX_DRIVER_MINOR 2 #define VMWGFX_DRIVER_PATCHLEVEL 0 #define VMWGFX_FILE_PAGE_OFFSET 0x00100000 #define VMWGFX_FIFO_STATIC_SIZE (1024*1024) @@ -264,10 +264,12 @@ struct vmw_private { wait_queue_head_t fence_queue; wait_queue_head_t fifo_queue; int fence_queue_waiters; /* Protected by hw_mutex */ + int goal_queue_waiters; /* Protected by hw_mutex */ atomic_t fifo_queue_waiters; uint32_t last_read_seqno; spinlock_t irq_lock; struct vmw_fence_manager *fman; + uint32_t irq_mask; /* * Device state @@ -532,7 +534,13 @@ extern int vmw_execbuf_fence_commands(struct drm_file *file_priv, struct vmw_private *dev_priv, struct vmw_fence_obj **p_fence, uint32_t *p_handle); - +extern void vmw_execbuf_copy_fence_user(struct vmw_private *dev_priv, + struct vmw_fpriv *vmw_fp, + int ret, + struct drm_vmw_fence_rep __user + *user_fence_rep, + struct vmw_fence_obj *fence, + uint32_t fence_handle); /** * IRQs and wating - vmwgfx_irq.c @@ -557,6 +565,8 @@ extern void vmw_update_seqno(struct vmw_private *dev_priv, struct vmw_fifo_state *fifo_state); extern void vmw_seqno_waiter_add(struct vmw_private *dev_priv); extern void vmw_seqno_waiter_remove(struct vmw_private *dev_priv); +extern void vmw_goal_waiter_add(struct vmw_private *dev_priv); +extern void vmw_goal_waiter_remove(struct vmw_private *dev_priv); /** * Rudimentary fence-like objects currently used only for throttling - diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c index f53b99d3..d4a1d8b 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_execbuf.c @@ -1057,7 +1057,7 @@ int vmw_execbuf_fence_commands(struct drm_file *file_priv, * object so we wait for it immediately, and then unreference the * user-space reference. */ -static void +void vmw_execbuf_copy_fence_user(struct vmw_private *dev_priv, struct vmw_fpriv *vmw_fp, int ret, diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_fence.c b/drivers/gpu/drm/vmwgfx/vmwgfx_fence.c index 5f60be7..35d5f61 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_fence.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_fence.c @@ -34,13 +34,18 @@ struct vmw_fence_manager { int num_fence_objects; struct vmw_private *dev_priv; spinlock_t lock; - u32 next_seqno; struct list_head fence_list; struct work_struct work; u32 user_fence_size; u32 fence_size; + u32 event_fence_action_size; bool fifo_down; struct list_head cleanup_list; + uint32_t pending_actions[VMW_ACTION_MAX]; + struct mutex goal_irq_mutex; + bool goal_irq_on; /* Protected by @goal_irq_mutex */ + bool seqno_valid; /* Protected by @lock, and may not be set to true + without the @goal_irq_mutex held. */ }; struct vmw_user_fence { @@ -49,8 +54,51 @@ struct vmw_user_fence { }; /** - * vmw_fence_destroy_locked + * struct vmw_event_fence_action - fence action that delivers a drm event. * + * @e: A struct drm_pending_event that controls the event delivery. + * @action: A struct vmw_fence_action to hook up to a fence. + * @fence: A referenced pointer to the fence to keep it alive while @action + * hangs on it. + * @dev: Pointer to a struct drm_device so we can access the event stuff. + * @kref: Both @e and @action has destructors, so we need to refcount. + * @size: Size accounted for this object. + * @tv_sec: If non-null, the variable pointed to will be assigned + * current time tv_sec val when the fence signals. + * @tv_usec: Must be set if @tv_sec is set, and the variable pointed to will + * be assigned the current time tv_usec val when the fence signals. + */ +struct vmw_event_fence_action { + struct drm_pending_event e; + struct vmw_fence_action action; + struct vmw_fence_obj *fence; + struct drm_device *dev; + struct kref kref; + uint32_t size; + uint32_t *tv_sec; + uint32_t *tv_usec; +}; + +/** + * Note on fencing subsystem usage of irqs: + * Typically the vmw_fences_update function is called + * + * a) When a new fence seqno has been submitted by the fifo code. + * b) On-demand when we have waiters. Sleeping waiters will switch on the + * ANY_FENCE irq and call vmw_fences_update function each time an ANY_FENCE + * irq is received. When the last fence waiter is gone, that IRQ is masked + * away. + * + * In situations where there are no waiters and we don't submit any new fences, + * fence objects may not be signaled. This is perfectly OK, since there are + * no consumers of the signaled data, but that is NOT ok when there are fence + * actions attached to a fence. The fencing subsystem then makes use of the + * FENCE_GOAL irq and sets the fence goal seqno to that of the next fence + * which has an action attached, and each time vmw_fences_update is called, + * the subsystem makes sure the fence goal seqno is updated. + * + * The fence goal seqno irq is on as long as there are unsignaled fence + * objects with actions attached to them. */ static void vmw_fence_obj_destroy_locked(struct kref *kref) @@ -85,24 +133,36 @@ static void vmw_fence_work_func(struct work_struct *work) container_of(work, struct vmw_fence_manager, work); struct list_head list; struct vmw_fence_action *action, *next_action; + bool seqno_valid; do { INIT_LIST_HEAD(&list); + mutex_lock(&fman->goal_irq_mutex); + spin_lock_irq(&fman->lock); list_splice_init(&fman->cleanup_list, &list); + seqno_valid = fman->seqno_valid; spin_unlock_irq(&fman->lock); + if (!seqno_valid && fman->goal_irq_on) { + fman->goal_irq_on = false; + vmw_goal_waiter_remove(fman->dev_priv); + } + mutex_unlock(&fman->goal_irq_mutex); + if (list_empty(&list)) return; /* * At this point, only we should be able to manipulate the * list heads of the actions we have on the private list. + * hence fman::lock not held. */ list_for_each_entry_safe(action, next_action, &list, head) { list_del_init(&action->head); - action->cleanup(action); + if (action->cleanup) + action->cleanup(action); } } while (1); } @@ -122,6 +182,9 @@ struct vmw_fence_manager *vmw_fence_manager_init(struct vmw_private *dev_priv) fman->fifo_down = true; fman->user_fence_size = ttm_round_pot(sizeof(struct vmw_user_fence)); fman->fence_size = ttm_round_pot(sizeof(struct vmw_fence_obj)); + fman->event_fence_action_size = + ttm_round_pot(sizeof(struct vmw_event_fence_action)); + mutex_init(&fman->goal_irq_mutex); return fman; } @@ -214,6 +277,7 @@ void vmw_fences_perform_actions(struct vmw_fence_manager *fman, list_for_each_entry_safe(action, next_action, list, head) { list_del_init(&action->head); + fman->pending_actions[action->type]--; if (action->seq_passed != NULL) action->seq_passed(action); @@ -222,17 +286,101 @@ void vmw_fences_perform_actions(struct vmw_fence_manager *fman, * it will be performed by a worker task. */ - if (action->cleanup != NULL) - list_add_tail(&action->head, &fman->cleanup_list); + list_add_tail(&action->head, &fman->cleanup_list); + } +} + +/** + * vmw_fence_goal_new_locked - Figure out a new device fence goal + * seqno if needed. + * + * @fman: Pointer to a fence manager. + * @passed_seqno: The seqno the device currently signals as passed. + * + * This function should be called with the fence manager lock held. + * It is typically called when we have a new passed_seqno, and + * we might need to update the fence goal. It checks to see whether + * the current fence goal has already passed, and, in that case, + * scans through all unsignaled fences to get the next fence object with an + * action attached, and sets the seqno of that fence as a new fence goal. + * + * returns true if the device goal seqno was updated. False otherwise. + */ +static bool vmw_fence_goal_new_locked(struct vmw_fence_manager *fman, + u32 passed_seqno) +{ + u32 goal_seqno; + __le32 __iomem *fifo_mem; + struct vmw_fence_obj *fence; + + if (likely(!fman->seqno_valid)) + return false; + + fifo_mem = fman->dev_priv->mmio_virt; + goal_seqno = ioread32(fifo_mem + SVGA_FIFO_FENCE_GOAL); + if (likely(passed_seqno - goal_seqno >= VMW_FENCE_WRAP)) + return false; + + fman->seqno_valid = false; + list_for_each_entry(fence, &fman->fence_list, head) { + if (!list_empty(&fence->seq_passed_actions)) { + fman->seqno_valid = true; + iowrite32(fence->seqno, + fifo_mem + SVGA_FIFO_FENCE_GOAL); + break; + } } + + return true; +} + + +/** + * vmw_fence_goal_check_locked - Replace the device fence goal seqno if + * needed. + * + * @fence: Pointer to a struct vmw_fence_obj the seqno of which should be + * considered as a device fence goal. + * + * This function should be called with the fence manager lock held. + * It is typically called when an action has been attached to a fence to + * check whether the seqno of that fence should be used for a fence + * goal interrupt. This is typically needed if the current fence goal is + * invalid, or has a higher seqno than that of the current fence object. + * + * returns true if the device goal seqno was updated. False otherwise. + */ +static bool vmw_fence_goal_check_locked(struct vmw_fence_obj *fence) +{ + u32 goal_seqno; + __le32 __iomem *fifo_mem; + + if (fence->signaled & DRM_VMW_FENCE_FLAG_EXEC) + return false; + + fifo_mem = fence->fman->dev_priv->mmio_virt; + goal_seqno = ioread32(fifo_mem + SVGA_FIFO_FENCE_GOAL); + if (likely(fence->fman->seqno_valid && + goal_seqno - fence->seqno < VMW_FENCE_WRAP)) + return false; + + iowrite32(fence->seqno, fifo_mem + SVGA_FIFO_FENCE_GOAL); + fence->fman->seqno_valid = true; + + return true; } -void vmw_fences_update(struct vmw_fence_manager *fman, u32 seqno) +void vmw_fences_update(struct vmw_fence_manager *fman) { unsigned long flags; struct vmw_fence_obj *fence, *next_fence; struct list_head action_list; + bool needs_rerun; + uint32_t seqno, new_seqno; + __le32 __iomem *fifo_mem = fman->dev_priv->mmio_virt; + seqno = ioread32(fifo_mem + SVGA_FIFO_FENCE); +rerun: spin_lock_irqsave(&fman->lock, flags); list_for_each_entry_safe(fence, next_fence, &fman->fence_list, head) { if (seqno - fence->seqno < VMW_FENCE_WRAP) { @@ -243,14 +391,30 @@ void vmw_fences_update(struct vmw_fence_manager *fman, u32 seqno) &action_list); vmw_fences_perform_actions(fman, &action_list); wake_up_all(&fence->queue); - } - + } else + break; } + + needs_rerun = vmw_fence_goal_new_locked(fman, seqno); + if (!list_empty(&fman->cleanup_list)) (void) schedule_work(&fman->work); spin_unlock_irqrestore(&fman->lock, flags); -} + /* + * Rerun if the fence goal seqno was updated, and the + * hardware might have raced with that update, so that + * we missed a fence_goal irq. + */ + + if (unlikely(needs_rerun)) { + new_seqno = ioread32(fifo_mem + SVGA_FIFO_FENCE); + if (new_seqno != seqno) { + seqno = new_seqno; + goto rerun; + } + } +} bool vmw_fence_obj_signaled(struct vmw_fence_obj *fence, uint32_t flags) @@ -267,14 +431,8 @@ bool vmw_fence_obj_signaled(struct vmw_fence_obj *fence, if ((signaled & flags) == flags) return 1; - if ((signaled & DRM_VMW_FENCE_FLAG_EXEC) == 0) { - struct vmw_private *dev_priv = fman->dev_priv; - __le32 __iomem *fifo_mem = dev_priv->mmio_virt; - u32 seqno; - - seqno = ioread32(fifo_mem + SVGA_FIFO_FENCE); - vmw_fences_update(fman, seqno); - } + if ((signaled & DRM_VMW_FENCE_FLAG_EXEC) == 0) + vmw_fences_update(fman); spin_lock_irqsave(&fman->lock, irq_flags); signaled = fence->signaled; @@ -624,3 +782,344 @@ int vmw_fence_obj_unref_ioctl(struct drm_device *dev, void *data, arg->handle, TTM_REF_USAGE); } + +/** + * vmw_event_fence_action_destroy + * + * @kref: The struct kref embedded in a struct vmw_event_fence_action. + * + * The vmw_event_fence_action destructor that may be called either after + * the fence action cleanup, or when the event is delivered. + * It frees both the vmw_event_fence_action struct and the actual + * event structure copied to user-space. + */ +static void vmw_event_fence_action_destroy(struct kref *kref) +{ + struct vmw_event_fence_action *eaction = + container_of(kref, struct vmw_event_fence_action, kref); + struct ttm_mem_global *mem_glob = + vmw_mem_glob(vmw_priv(eaction->dev)); + uint32_t size = eaction->size; + + kfree(eaction->e.event); + kfree(eaction); + ttm_mem_global_free(mem_glob, size); +} + + +/** + * vmw_event_fence_action_delivered + * + * @e: The struct drm_pending_event embedded in a struct + * vmw_event_fence_action. + * + * The struct drm_pending_event destructor that is called by drm + * once the event is delivered. Since we don't know whether this function + * will be called before or after the fence action destructor, we + * free a refcount and destroy if it becomes zero. + */ +static void vmw_event_fence_action_delivered(struct drm_pending_event *e) +{ + struct vmw_event_fence_action *eaction = + container_of(e, struct vmw_event_fence_action, e); + + kref_put(&eaction->kref, vmw_event_fence_action_destroy); +} + + +/** + * vmw_event_fence_action_seq_passed + * + * @action: The struct vmw_fence_action embedded in a struct + * vmw_event_fence_action. + * + * This function is called when the seqno of the fence where @action is + * attached has passed. It queues the event on the submitter's event list. + * This function is always called from atomic context, and may be called + * from irq context. It ups a refcount reflecting that we now have two + * destructors. + */ +static void vmw_event_fence_action_seq_passed(struct vmw_fence_action *action) +{ + struct vmw_event_fence_action *eaction = + container_of(action, struct vmw_event_fence_action, action); + struct drm_device *dev = eaction->dev; + struct drm_file *file_priv = eaction->e.file_priv; + unsigned long irq_flags; + + kref_get(&eaction->kref); + spin_lock_irqsave(&dev->event_lock, irq_flags); + + if (likely(eaction->tv_sec != NULL)) { + struct timeval tv; + + do_gettimeofday(&tv); + *eaction->tv_sec = tv.tv_sec; + *eaction->tv_usec = tv.tv_usec; + } + + list_add_tail(&eaction->e.link, &file_priv->event_list); + wake_up_all(&file_priv->event_wait); + spin_unlock_irqrestore(&dev->event_lock, irq_flags); +} + +/** + * vmw_event_fence_action_cleanup + * + * @action: The struct vmw_fence_action embedded in a struct + * vmw_event_fence_action. + * + * This function is the struct vmw_fence_action destructor. It's typically + * called from a workqueue. + */ +static void vmw_event_fence_action_cleanup(struct vmw_fence_action *action) +{ + struct vmw_event_fence_action *eaction = + container_of(action, struct vmw_event_fence_action, action); + + vmw_fence_obj_unreference(&eaction->fence); + kref_put(&eaction->kref, vmw_event_fence_action_destroy); +} + + +/** + * vmw_fence_obj_add_action - Add an action to a fence object. + * + * @fence - The fence object. + * @action - The action to add. + * + * Note that the action callbacks may be executed before this function + * returns. + */ +void vmw_fence_obj_add_action(struct vmw_fence_obj *fence, + struct vmw_fence_action *action) +{ + struct vmw_fence_manager *fman = fence->fman; + unsigned long irq_flags; + bool run_update = false; + + mutex_lock(&fman->goal_irq_mutex); + spin_lock_irqsave(&fman->lock, irq_flags); + + fman->pending_actions[action->type]++; + if (fence->signaled & DRM_VMW_FENCE_FLAG_EXEC) { + struct list_head action_list; + + INIT_LIST_HEAD(&action_list); + list_add_tail(&action->head, &action_list); + vmw_fences_perform_actions(fman, &action_list); + } else { + list_add_tail(&action->head, &fence->seq_passed_actions); + + /* + * This function may set fman::seqno_valid, so it must + * be run with the goal_irq_mutex held. + */ + run_update = vmw_fence_goal_check_locked(fence); + } + + spin_unlock_irqrestore(&fman->lock, irq_flags); + + if (run_update) { + if (!fman->goal_irq_on) { + fman->goal_irq_on = true; + vmw_goal_waiter_add(fman->dev_priv); + } + vmw_fences_update(fman); + } + mutex_unlock(&fman->goal_irq_mutex); + +} + +/** + * vmw_event_fence_action_create - Post an event for sending when a fence + * object seqno has passed. + * + * @file_priv: The file connection on which the event should be posted. + * @fence: The fence object on which to post the event. + * @event: Event to be posted. This event should've been alloced + * using k[mz]alloc, and should've been completely initialized. + * @interruptible: Interruptible waits if possible. + * + * As a side effect, the object pointed to by @event may have been + * freed when this function returns. If this function returns with + * an error code, the caller needs to free that object. + */ + +int vmw_event_fence_action_create(struct drm_file *file_priv, + struct vmw_fence_obj *fence, + struct drm_event *event, + uint32_t *tv_sec, + uint32_t *tv_usec, + bool interruptible) +{ + struct vmw_event_fence_action *eaction = + kzalloc(sizeof(*eaction), GFP_KERNEL); + struct ttm_mem_global *mem_glob = + vmw_mem_glob(fence->fman->dev_priv); + struct vmw_fence_manager *fman = fence->fman; + uint32_t size = fman->event_fence_action_size + + ttm_round_pot(event->length); + int ret; + + /* + * Account for internal structure size as well as the + * event size itself. + */ + + ret = ttm_mem_global_alloc(mem_glob, size, false, interruptible); + if (unlikely(ret != 0)) + return ret; + + eaction = kzalloc(sizeof(*eaction), GFP_KERNEL); + if (unlikely(eaction == NULL)) { + ttm_mem_global_free(mem_glob, size); + return -ENOMEM; + } + + eaction->e.event = event; + eaction->e.file_priv = file_priv; + eaction->e.destroy = vmw_event_fence_action_delivered; + + eaction->action.seq_passed = vmw_event_fence_action_seq_passed; + eaction->action.cleanup = vmw_event_fence_action_cleanup; + eaction->action.type = VMW_ACTION_EVENT; + + eaction->fence = vmw_fence_obj_reference(fence); + eaction->dev = fman->dev_priv->dev; + eaction->size = size; + eaction->tv_sec = tv_sec; + eaction->tv_usec = tv_usec; + + kref_init(&eaction->kref); + vmw_fence_obj_add_action(fence, &eaction->action); + + return 0; +} + +int vmw_fence_event_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct vmw_private *dev_priv = vmw_priv(dev); + struct drm_vmw_fence_event_arg *arg = + (struct drm_vmw_fence_event_arg *) data; + struct vmw_fence_obj *fence = NULL; + struct vmw_fpriv *vmw_fp = vmw_fpriv(file_priv); + struct drm_vmw_fence_rep __user *user_fence_rep = + (struct drm_vmw_fence_rep __user *)(unsigned long) + arg->fence_rep; + uint32_t handle; + unsigned long irq_flags; + struct drm_vmw_event_fence *event; + int ret; + + /* + * Look up an existing fence object, + * and if user-space wants a new reference, + * add one. + */ + if (arg->handle) { + struct ttm_base_object *base = + ttm_base_object_lookup(vmw_fp->tfile, arg->handle); + + if (unlikely(base == NULL)) { + DRM_ERROR("Fence event invalid fence object handle " + "0x%08lx.\n", + (unsigned long)arg->handle); + return -EINVAL; + } + fence = &(container_of(base, struct vmw_user_fence, + base)->fence); + (void) vmw_fence_obj_reference(fence); + + if (user_fence_rep != NULL) { + bool existed; + + ret = ttm_ref_object_add(vmw_fp->tfile, base, + TTM_REF_USAGE, &existed); + if (unlikely(ret != 0)) { + DRM_ERROR("Failed to reference a fence " + "object.\n"); + goto out_no_ref_obj; + } + handle = base->hash.key; + } + ttm_base_object_unref(&base); + } + + /* + * Create a new fence object. + */ + if (!fence) { + ret = vmw_execbuf_fence_commands(file_priv, dev_priv, + &fence, + (user_fence_rep) ? + &handle : NULL); + if (unlikely(ret != 0)) { + DRM_ERROR("Fence event failed to create fence.\n"); + return ret; + } + } + + BUG_ON(fence == NULL); + + spin_lock_irqsave(&dev->event_lock, irq_flags); + + ret = (file_priv->event_space < sizeof(*event)) ? -EBUSY : 0; + if (likely(ret == 0)) + file_priv->event_space -= sizeof(*event); + + spin_unlock_irqrestore(&dev->event_lock, irq_flags); + + if (unlikely(ret != 0)) { + DRM_ERROR("Failed to allocate event space for this file.\n"); + goto out_no_event_space; + } + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (unlikely(event == NULL)) { + DRM_ERROR("Failed to allocate an event.\n"); + goto out_no_event; + } + + event->base.type = DRM_VMW_EVENT_FENCE_SIGNALED; + event->base.length = sizeof(*event); + event->user_data = arg->user_data; + + if (arg->flags & DRM_VMW_FE_FLAG_REQ_TIME) + ret = vmw_event_fence_action_create(file_priv, fence, + &event->base, + &event->tv_sec, + &event->tv_usec, + true); + else + ret = vmw_event_fence_action_create(file_priv, fence, + &event->base, + NULL, + NULL, + true); + + if (unlikely(ret != 0)) { + if (ret != -ERESTARTSYS) + DRM_ERROR("Failed to attach event to fence.\n"); + goto out_no_attach; + } + + vmw_execbuf_copy_fence_user(dev_priv, vmw_fp, 0, user_fence_rep, fence, + handle); + vmw_fence_obj_unreference(&fence); + return 0; +out_no_attach: + kfree(event); +out_no_event: + spin_lock_irqsave(&dev->event_lock, irq_flags); + file_priv->event_space += sizeof(*event); + spin_unlock_irqrestore(&dev->event_lock, irq_flags); +out_no_event_space: + if (user_fence_rep != NULL) + ttm_ref_object_base_unref(vmw_fpriv(file_priv)->tfile, + handle, TTM_REF_USAGE); +out_no_ref_obj: + vmw_fence_obj_unreference(&fence); + return ret; +} diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_fence.h b/drivers/gpu/drm/vmwgfx/vmwgfx_fence.h index 9307406..0854a20 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_fence.h +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_fence.h @@ -37,8 +37,14 @@ struct vmw_fence_manager; * * */ +enum vmw_action_type { + VMW_ACTION_EVENT = 0, + VMW_ACTION_MAX +}; + struct vmw_fence_action { struct list_head head; + enum vmw_action_type type; void (*seq_passed) (struct vmw_fence_action *action); void (*cleanup) (struct vmw_fence_action *action); }; @@ -66,8 +72,7 @@ extern void vmw_fence_obj_unreference(struct vmw_fence_obj **fence_p); extern struct vmw_fence_obj * vmw_fence_obj_reference(struct vmw_fence_obj *fence); -extern void vmw_fences_update(struct vmw_fence_manager *fman, - u32 sequence); +extern void vmw_fences_update(struct vmw_fence_manager *fman); extern bool vmw_fence_obj_signaled(struct vmw_fence_obj *fence, uint32_t flags); @@ -102,4 +107,7 @@ extern int vmw_fence_obj_signaled_ioctl(struct drm_device *dev, void *data, extern int vmw_fence_obj_unref_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +extern int vmw_fence_event_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); + #endif /* _VMWGFX_FENCE_H_ */ diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_fifo.c b/drivers/gpu/drm/vmwgfx/vmwgfx_fifo.c index 62d6377..03bbc2a 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_fifo.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_fifo.c @@ -247,9 +247,8 @@ static int vmw_fifo_wait(struct vmw_private *dev_priv, spin_lock_irqsave(&dev_priv->irq_lock, irq_flags); outl(SVGA_IRQFLAG_FIFO_PROGRESS, dev_priv->io_start + VMWGFX_IRQSTATUS_PORT); - vmw_write(dev_priv, SVGA_REG_IRQMASK, - vmw_read(dev_priv, SVGA_REG_IRQMASK) | - SVGA_IRQFLAG_FIFO_PROGRESS); + dev_priv->irq_mask |= SVGA_IRQFLAG_FIFO_PROGRESS; + vmw_write(dev_priv, SVGA_REG_IRQMASK, dev_priv->irq_mask); spin_unlock_irqrestore(&dev_priv->irq_lock, irq_flags); } mutex_unlock(&dev_priv->hw_mutex); @@ -271,9 +270,8 @@ static int vmw_fifo_wait(struct vmw_private *dev_priv, mutex_lock(&dev_priv->hw_mutex); if (atomic_dec_and_test(&dev_priv->fifo_queue_waiters)) { spin_lock_irqsave(&dev_priv->irq_lock, irq_flags); - vmw_write(dev_priv, SVGA_REG_IRQMASK, - vmw_read(dev_priv, SVGA_REG_IRQMASK) & - ~SVGA_IRQFLAG_FIFO_PROGRESS); + dev_priv->irq_mask &= ~SVGA_IRQFLAG_FIFO_PROGRESS; + vmw_write(dev_priv, SVGA_REG_IRQMASK, dev_priv->irq_mask); spin_unlock_irqrestore(&dev_priv->irq_lock, irq_flags); } mutex_unlock(&dev_priv->hw_mutex); diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c b/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c index a005292..cabc95f 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c @@ -34,28 +34,30 @@ irqreturn_t vmw_irq_handler(DRM_IRQ_ARGS) { struct drm_device *dev = (struct drm_device *)arg; struct vmw_private *dev_priv = vmw_priv(dev); - uint32_t status; + uint32_t status, masked_status; spin_lock(&dev_priv->irq_lock); status = inl(dev_priv->io_start + VMWGFX_IRQSTATUS_PORT); + masked_status = status & dev_priv->irq_mask; spin_unlock(&dev_priv->irq_lock); - if (status & SVGA_IRQFLAG_ANY_FENCE) { - __le32 __iomem *fifo_mem = dev_priv->mmio_virt; - uint32_t seqno = ioread32(fifo_mem + SVGA_FIFO_FENCE); + if (likely(status)) + outl(status, dev_priv->io_start + VMWGFX_IRQSTATUS_PORT); + + if (!masked_status) + return IRQ_NONE; - vmw_fences_update(dev_priv->fman, seqno); + if (masked_status & (SVGA_IRQFLAG_ANY_FENCE | + SVGA_IRQFLAG_FENCE_GOAL)) { + vmw_fences_update(dev_priv->fman); wake_up_all(&dev_priv->fence_queue); } - if (status & SVGA_IRQFLAG_FIFO_PROGRESS) + + if (masked_status & SVGA_IRQFLAG_FIFO_PROGRESS) wake_up_all(&dev_priv->fifo_queue); - if (likely(status)) { - outl(status, dev_priv->io_start + VMWGFX_IRQSTATUS_PORT); - return IRQ_HANDLED; - } - return IRQ_NONE; + return IRQ_HANDLED; } static bool vmw_fifo_idle(struct vmw_private *dev_priv, uint32_t seqno) @@ -78,7 +80,7 @@ void vmw_update_seqno(struct vmw_private *dev_priv, if (dev_priv->last_read_seqno != seqno) { dev_priv->last_read_seqno = seqno; vmw_marker_pull(&fifo_state->marker_queue, seqno); - vmw_fences_update(dev_priv->fman, seqno); + vmw_fences_update(dev_priv->fman); } } @@ -189,9 +191,8 @@ void vmw_seqno_waiter_add(struct vmw_private *dev_priv) spin_lock_irqsave(&dev_priv->irq_lock, irq_flags); outl(SVGA_IRQFLAG_ANY_FENCE, dev_priv->io_start + VMWGFX_IRQSTATUS_PORT); - vmw_write(dev_priv, SVGA_REG_IRQMASK, - vmw_read(dev_priv, SVGA_REG_IRQMASK) | - SVGA_IRQFLAG_ANY_FENCE); + dev_priv->irq_mask |= SVGA_IRQFLAG_ANY_FENCE; + vmw_write(dev_priv, SVGA_REG_IRQMASK, dev_priv->irq_mask); spin_unlock_irqrestore(&dev_priv->irq_lock, irq_flags); } mutex_unlock(&dev_priv->hw_mutex); @@ -204,9 +205,39 @@ void vmw_seqno_waiter_remove(struct vmw_private *dev_priv) unsigned long irq_flags; spin_lock_irqsave(&dev_priv->irq_lock, irq_flags); - vmw_write(dev_priv, SVGA_REG_IRQMASK, - vmw_read(dev_priv, SVGA_REG_IRQMASK) & - ~SVGA_IRQFLAG_ANY_FENCE); + dev_priv->irq_mask &= ~SVGA_IRQFLAG_ANY_FENCE; + vmw_write(dev_priv, SVGA_REG_IRQMASK, dev_priv->irq_mask); + spin_unlock_irqrestore(&dev_priv->irq_lock, irq_flags); + } + mutex_unlock(&dev_priv->hw_mutex); +} + + +void vmw_goal_waiter_add(struct vmw_private *dev_priv) +{ + mutex_lock(&dev_priv->hw_mutex); + if (dev_priv->goal_queue_waiters++ == 0) { + unsigned long irq_flags; + + spin_lock_irqsave(&dev_priv->irq_lock, irq_flags); + outl(SVGA_IRQFLAG_FENCE_GOAL, + dev_priv->io_start + VMWGFX_IRQSTATUS_PORT); + dev_priv->irq_mask |= SVGA_IRQFLAG_FENCE_GOAL; + vmw_write(dev_priv, SVGA_REG_IRQMASK, dev_priv->irq_mask); + spin_unlock_irqrestore(&dev_priv->irq_lock, irq_flags); + } + mutex_unlock(&dev_priv->hw_mutex); +} + +void vmw_goal_waiter_remove(struct vmw_private *dev_priv) +{ + mutex_lock(&dev_priv->hw_mutex); + if (--dev_priv->goal_queue_waiters == 0) { + unsigned long irq_flags; + + spin_lock_irqsave(&dev_priv->irq_lock, irq_flags); + dev_priv->irq_mask &= ~SVGA_IRQFLAG_FENCE_GOAL; + vmw_write(dev_priv, SVGA_REG_IRQMASK, dev_priv->irq_mask); spin_unlock_irqrestore(&dev_priv->irq_lock, irq_flags); } mutex_unlock(&dev_priv->hw_mutex); diff --git a/include/drm/vmwgfx_drm.h b/include/drm/vmwgfx_drm.h index 5b5b0a8..cd7cd81 100644 --- a/include/drm/vmwgfx_drm.h +++ b/include/drm/vmwgfx_drm.h @@ -685,6 +685,51 @@ struct drm_vmw_fence_arg { /*************************************************************************/ /** + * DRM_VMW_FENCE_EVENT + * + * Queues an event on a fence to be delivered on the drm character device + * when the fence has signaled the DRM_VMW_FENCE_FLAG_EXEC flag. + * Optionally the approximate time when the fence signaled is + * given by the event. + */ + +/* + * The event type + */ +#define DRM_VMW_EVENT_FENCE_SIGNALED 0x80000000 + +struct drm_vmw_event_fence { + struct drm_event base; + uint64_t user_data; + uint32_t tv_sec; + uint32_t tv_usec; +}; + +/* + * Flags that may be given to the command. + */ +/* Request fence signaled time on the event. */ +#define DRM_VMW_FE_FLAG_REQ_TIME (1 << 0) + +/** + * struct drm_vmw_fence_event_arg + * + * @fence_rep: Pointer to fence_rep structure cast to uint64_t or 0 if + * the fence is not supposed to be referenced by user-space. + * @user_info: Info to be delivered with the event. + * @handle: Attach the event to this fence only. + * @flags: A set of flags as defined above. + */ +struct drm_vmw_fence_event_arg { + uint64_t fence_rep; + uint64_t user_data; + uint32_t handle; + uint32_t flags; +}; + + +/*************************************************************************/ +/** * DRM_VMW_PRESENT * * Executes an SVGA present on a given fb for a given surface. The surface @@ -743,6 +788,4 @@ struct drm_vmw_present_readback_arg { uint64_t clips_ptr; uint64_t fence_rep; }; - - #endif -- 2.7.4