diff options
Diffstat (limited to 'drivers/gpu/drm/drm_irq.c')
-rw-r--r-- | drivers/gpu/drm/drm_irq.c | 192 |
1 files changed, 135 insertions, 57 deletions
diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index e06cf11ebb4a..8c866cac62dd 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -90,6 +90,31 @@ static void store_vblank(struct drm_device *dev, unsigned int pipe, } /* + * "No hw counter" fallback implementation of .get_vblank_counter() hook, + * if there is no useable hardware frame counter available. + */ +static u32 drm_vblank_no_hw_counter(struct drm_device *dev, unsigned int pipe) +{ + WARN_ON_ONCE(dev->max_vblank_count != 0); + return 0; +} + +static u32 __get_vblank_counter(struct drm_device *dev, unsigned int pipe) +{ + if (drm_core_check_feature(dev, DRIVER_MODESET)) { + struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe); + + if (crtc->funcs->get_vblank_counter) + return crtc->funcs->get_vblank_counter(crtc); + } + + if (dev->driver->get_vblank_counter) + return dev->driver->get_vblank_counter(dev, pipe); + + return drm_vblank_no_hw_counter(dev, pipe); +} + +/* * Reset the stored timestamp for the current vblank count to correspond * to the last vblank occurred. * @@ -112,9 +137,9 @@ static void drm_reset_vblank_timestamp(struct drm_device *dev, unsigned int pipe * when drm_vblank_enable() applies the diff */ do { - cur_vblank = dev->driver->get_vblank_counter(dev, pipe); + cur_vblank = __get_vblank_counter(dev, pipe); rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, 0); - } while (cur_vblank != dev->driver->get_vblank_counter(dev, pipe) && --count > 0); + } while (cur_vblank != __get_vblank_counter(dev, pipe) && --count > 0); /* * Only reinitialize corresponding vblank timestamp if high-precision query @@ -168,9 +193,9 @@ static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe, * corresponding vblank timestamp. */ do { - cur_vblank = dev->driver->get_vblank_counter(dev, pipe); + cur_vblank = __get_vblank_counter(dev, pipe); rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, flags); - } while (cur_vblank != dev->driver->get_vblank_counter(dev, pipe) && --count > 0); + } while (cur_vblank != __get_vblank_counter(dev, pipe) && --count > 0); if (dev->max_vblank_count != 0) { /* trust the hw counter when it's around */ @@ -275,6 +300,20 @@ u32 drm_accurate_vblank_count(struct drm_crtc *crtc) } EXPORT_SYMBOL(drm_accurate_vblank_count); +static void __disable_vblank(struct drm_device *dev, unsigned int pipe) +{ + if (drm_core_check_feature(dev, DRIVER_MODESET)) { + struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe); + + if (crtc->funcs->disable_vblank) { + crtc->funcs->disable_vblank(crtc); + return; + } + } + + dev->driver->disable_vblank(dev, pipe); +} + /* * Disable vblank irq's on crtc, make sure that last vblank count * of hardware and corresponding consistent software vblank counter @@ -286,6 +325,8 @@ static void vblank_disable_and_save(struct drm_device *dev, unsigned int pipe) struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; unsigned long irqflags; + assert_spin_locked(&dev->vbl_lock); + /* Prevent vblank irq processing while disabling vblank irqs, * so no updates of timestamps or count can happen after we've * disabled. Needed to prevent races in case of delayed irq's. @@ -298,7 +339,7 @@ static void vblank_disable_and_save(struct drm_device *dev, unsigned int pipe) * hardware potentially runtime suspended. */ if (vblank->enabled) { - dev->driver->disable_vblank(dev, pipe); + __disable_vblank(dev, pipe); vblank->enabled = false; } @@ -345,7 +386,7 @@ void drm_vblank_cleanup(struct drm_device *dev) for (pipe = 0; pipe < dev->num_crtcs; pipe++) { struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - WARN_ON(vblank->enabled && + WARN_ON(READ_ONCE(vblank->enabled) && drm_core_check_feature(dev, DRIVER_MODESET)); del_timer_sync(&vblank->disable_timer); @@ -771,14 +812,6 @@ int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, /* Return upper bound of timestamp precision error. */ *max_error = duration_ns; - /* Check if in vblank area: - * vpos is >=0 in video scanout area, but negative - * within vblank area, counting down the number of lines until - * start of scanout. - */ - if (vbl_status & DRM_SCANOUTPOS_IN_VBLANK) - ret |= DRM_VBLANKTIME_IN_VBLANK; - /* Convert scanout position into elapsed time at raw_time query * since start of scanout at first display scanline. delta_ns * can be negative if start of scanout hasn't happened yet. @@ -939,7 +972,7 @@ static void send_vblank_event(struct drm_device *dev, e->event.tv_sec = now->tv_sec; e->event.tv_usec = now->tv_usec; - trace_drm_vblank_event_delivered(e->base.pid, e->pipe, + trace_drm_vblank_event_delivered(e->base.file_priv, e->pipe, e->event.sequence); drm_send_event_locked(dev, &e->base); @@ -993,6 +1026,7 @@ void drm_crtc_arm_vblank_event(struct drm_crtc *crtc, e->pipe = pipe; e->event.sequence = drm_vblank_count(dev, pipe); + e->event.crtc_id = crtc->base.id; list_add_tail(&e->base.link, &dev->vblank_event_list); } EXPORT_SYMBOL(drm_crtc_arm_vblank_event); @@ -1023,10 +1057,23 @@ void drm_crtc_send_vblank_event(struct drm_crtc *crtc, now = get_drm_timestamp(); } e->pipe = pipe; + e->event.crtc_id = crtc->base.id; send_vblank_event(dev, e, seq, &now); } EXPORT_SYMBOL(drm_crtc_send_vblank_event); +static int __enable_vblank(struct drm_device *dev, unsigned int pipe) +{ + if (drm_core_check_feature(dev, DRIVER_MODESET)) { + struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe); + + if (crtc->funcs->enable_vblank) + return crtc->funcs->enable_vblank(crtc); + } + + return dev->driver->enable_vblank(dev, pipe); +} + /** * drm_vblank_enable - enable the vblank interrupt on a CRTC * @dev: DRM device @@ -1052,13 +1099,18 @@ static int drm_vblank_enable(struct drm_device *dev, unsigned int pipe) * timestamps. Filtercode in drm_handle_vblank() will * prevent double-accounting of same vblank interval. */ - ret = dev->driver->enable_vblank(dev, pipe); + ret = __enable_vblank(dev, pipe); DRM_DEBUG("enabling vblank on crtc %u, ret: %d\n", pipe, ret); - if (ret) + if (ret) { atomic_dec(&vblank->refcount); - else { - vblank->enabled = true; + } else { drm_update_vblank_count(dev, pipe, 0); + /* drm_update_vblank_count() includes a wmb so we just + * need to ensure that the compiler emits the write + * to mark the vblank as enabled after the call + * to drm_update_vblank_count(). + */ + WRITE_ONCE(vblank->enabled, true); } } @@ -1147,9 +1199,9 @@ static void drm_vblank_put(struct drm_device *dev, unsigned int pipe) if (atomic_dec_and_test(&vblank->refcount)) { if (drm_vblank_offdelay == 0) return; - else if (dev->vblank_disable_immediate || drm_vblank_offdelay < 0) + else if (drm_vblank_offdelay < 0) vblank_disable_fn((unsigned long)vblank); - else + else if (!dev->vblank_disable_immediate) mod_timer(&vblank->disable_timer, jiffies + ((drm_vblank_offdelay * HZ)/1000)); } @@ -1436,6 +1488,11 @@ int drm_legacy_modeset_ctl(struct drm_device *dev, void *data, return 0; } +static inline bool vblank_passed(u32 seq, u32 ref) +{ + return (seq - ref) <= (1 << 23); +} + static int drm_queue_vblank_event(struct drm_device *dev, unsigned int pipe, union drm_wait_vblank *vblwait, struct drm_file *file_priv) @@ -1454,7 +1511,6 @@ static int drm_queue_vblank_event(struct drm_device *dev, unsigned int pipe, } e->pipe = pipe; - e->base.pid = current->pid; e->event.base.type = DRM_EVENT_VBLANK; e->event.base.length = sizeof(e->event); e->event.user_data = vblwait->request.signal; @@ -1467,7 +1523,7 @@ static int drm_queue_vblank_event(struct drm_device *dev, unsigned int pipe, * vblank disable, so no need for further locking. The reference from * drm_vblank_get() protects against vblank disable from another source. */ - if (!vblank->enabled) { + if (!READ_ONCE(vblank->enabled)) { ret = -EINVAL; goto err_unlock; } @@ -1483,11 +1539,11 @@ static int drm_queue_vblank_event(struct drm_device *dev, unsigned int pipe, DRM_DEBUG("event on vblank count %u, current %u, crtc %u\n", vblwait->request.sequence, seq, pipe); - trace_drm_vblank_event_queued(current->pid, pipe, + trace_drm_vblank_event_queued(file_priv, pipe, vblwait->request.sequence); e->event.sequence = vblwait->request.sequence; - if ((seq - vblwait->request.sequence) <= (1 << 23)) { + if (vblank_passed(seq, vblwait->request.sequence)) { drm_vblank_put(dev, pipe); send_vblank_event(dev, e, seq, &now); vblwait->reply.sequence = seq; @@ -1509,6 +1565,17 @@ err_put: return ret; } +static bool drm_wait_vblank_is_query(union drm_wait_vblank *vblwait) +{ + if (vblwait->request.sequence) + return false; + + return _DRM_VBLANK_RELATIVE == + (vblwait->request.type & (_DRM_VBLANK_TYPES_MASK | + _DRM_VBLANK_EVENT | + _DRM_VBLANK_NEXTONMISS)); +} + /* * Wait for VBLANK. * @@ -1558,9 +1625,24 @@ int drm_wait_vblank(struct drm_device *dev, void *data, vblank = &dev->vblank[pipe]; + /* If the counter is currently enabled and accurate, short-circuit + * queries to return the cached timestamp of the last vblank. + */ + if (dev->vblank_disable_immediate && + drm_wait_vblank_is_query(vblwait) && + READ_ONCE(vblank->enabled)) { + struct timeval now; + + vblwait->reply.sequence = + drm_vblank_count_and_time(dev, pipe, &now); + vblwait->reply.tval_sec = now.tv_sec; + vblwait->reply.tval_usec = now.tv_usec; + return 0; + } + ret = drm_vblank_get(dev, pipe); if (ret) { - DRM_DEBUG("failed to acquire vblank counter, %d\n", ret); + DRM_DEBUG("crtc %d failed to acquire vblank counter, %d\n", pipe, ret); return ret; } seq = drm_vblank_count(dev, pipe); @@ -1577,9 +1659,8 @@ int drm_wait_vblank(struct drm_device *dev, void *data, } if ((flags & _DRM_VBLANK_NEXTONMISS) && - (seq - vblwait->request.sequence) <= (1 << 23)) { + vblank_passed(seq, vblwait->request.sequence)) vblwait->request.sequence = seq + 1; - } if (flags & _DRM_VBLANK_EVENT) { /* must hold on to the vblank ref until the event fires @@ -1588,13 +1669,14 @@ int drm_wait_vblank(struct drm_device *dev, void *data, return drm_queue_vblank_event(dev, pipe, vblwait, file_priv); } - DRM_DEBUG("waiting on vblank count %u, crtc %u\n", - vblwait->request.sequence, pipe); - DRM_WAIT_ON(ret, vblank->queue, 3 * HZ, - (((drm_vblank_count(dev, pipe) - - vblwait->request.sequence) <= (1 << 23)) || - !vblank->enabled || - !dev->irq_enabled)); + if (vblwait->request.sequence != seq) { + DRM_DEBUG("waiting on vblank count %u, crtc %u\n", + vblwait->request.sequence, pipe); + DRM_WAIT_ON(ret, vblank->queue, 3 * HZ, + vblank_passed(drm_vblank_count(dev, pipe), + vblwait->request.sequence) || + !READ_ONCE(vblank->enabled)); + } if (ret != -EINTR) { struct timeval now; @@ -1603,10 +1685,10 @@ int drm_wait_vblank(struct drm_device *dev, void *data, vblwait->reply.tval_sec = now.tv_sec; vblwait->reply.tval_usec = now.tv_usec; - DRM_DEBUG("returning %u to client\n", - vblwait->reply.sequence); + DRM_DEBUG("crtc %d returning %u to client\n", + pipe, vblwait->reply.sequence); } else { - DRM_DEBUG("vblank wait interrupted by signal\n"); + DRM_DEBUG("crtc %d vblank wait interrupted by signal\n", pipe); } done: @@ -1627,7 +1709,7 @@ static void drm_handle_vblank_events(struct drm_device *dev, unsigned int pipe) list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) { if (e->pipe != pipe) continue; - if ((seq - e->event.sequence) > (1<<23)) + if (!vblank_passed(seq, e->event.sequence)) continue; DRM_DEBUG("vblank event on %u, current %u\n", @@ -1655,6 +1737,7 @@ bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe) { struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; unsigned long irqflags; + bool disable_irq; if (WARN_ON_ONCE(!dev->num_crtcs)) return false; @@ -1682,10 +1765,23 @@ bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe) spin_unlock(&dev->vblank_time_lock); wake_up(&vblank->queue); + + /* With instant-off, we defer disabling the interrupt until after + * we finish processing the following vblank after all events have + * been signaled. The disable has to be last (after + * drm_handle_vblank_events) so that the timestamp is always accurate. + */ + disable_irq = (dev->vblank_disable_immediate && + drm_vblank_offdelay > 0 && + !atomic_read(&vblank->refcount)); + drm_handle_vblank_events(dev, pipe); spin_unlock_irqrestore(&dev->event_lock, irqflags); + if (disable_irq) + vblank_disable_fn((unsigned long)vblank); + return true; } EXPORT_SYMBOL(drm_handle_vblank); @@ -1707,21 +1803,3 @@ bool drm_crtc_handle_vblank(struct drm_crtc *crtc) return drm_handle_vblank(crtc->dev, drm_crtc_index(crtc)); } EXPORT_SYMBOL(drm_crtc_handle_vblank); - -/** - * drm_vblank_no_hw_counter - "No hw counter" implementation of .get_vblank_counter() - * @dev: DRM device - * @pipe: CRTC for which to read the counter - * - * Drivers can plug this into the .get_vblank_counter() function if - * there is no useable hardware frame counter available. - * - * Returns: - * 0 - */ -u32 drm_vblank_no_hw_counter(struct drm_device *dev, unsigned int pipe) -{ - WARN_ON_ONCE(dev->max_vblank_count != 0); - return 0; -} -EXPORT_SYMBOL(drm_vblank_no_hw_counter); |