#include <sys/cdefs.h>
#include <dev/drm2/drmP.h>
#define vblanktimestamp(dev, crtc, count) ( \
(dev)->_vblank_time[(crtc) * DRM_VBLANKTIME_RBSIZE + \
((count) % DRM_VBLANKTIME_RBSIZE)])
#define DRM_TIMESTAMP_MAXRETRIES 3
#define DRM_REDUNDANT_VBLIRQ_THRESH_NS 1000000
int drm_irq_by_busid(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct drm_irq_busid *p = data;
if (!dev->driver->bus->irq_by_busid)
return -EINVAL;
if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
return -EINVAL;
return dev->driver->bus->irq_by_busid(dev, p);
}
static void clear_vblank_timestamps(struct drm_device *dev, int crtc)
{
memset(&dev->_vblank_time[crtc * DRM_VBLANKTIME_RBSIZE], 0,
DRM_VBLANKTIME_RBSIZE * sizeof(struct timeval));
}
static void vblank_disable_and_save(struct drm_device *dev, int crtc)
{
u32 vblcount;
s64 diff_ns;
int vblrc;
struct timeval tvblank;
int count = DRM_TIMESTAMP_MAXRETRIES;
mtx_lock(&dev->vblank_time_lock);
dev->driver->disable_vblank(dev, crtc);
dev->vblank_enabled[crtc] = 0;
do {
dev->last_vblank[crtc] = dev->driver->get_vblank_counter(dev, crtc);
vblrc = drm_get_last_vbltimestamp(dev, crtc, &tvblank, 0);
} while (dev->last_vblank[crtc] != dev->driver->get_vblank_counter(dev, crtc) && (--count) && vblrc);
if (!count)
vblrc = 0;
vblcount = atomic_read(&dev->_vblank_count[crtc]);
diff_ns = timeval_to_ns(&tvblank) -
timeval_to_ns(&vblanktimestamp(dev, crtc, vblcount));
if ((vblrc > 0) && (abs64(diff_ns) > 1000000)) {
atomic_inc(&dev->_vblank_count[crtc]);
smp_mb__after_atomic_inc();
}
clear_vblank_timestamps(dev, crtc);
mtx_unlock(&dev->vblank_time_lock);
}
static void vblank_disable_fn(void *arg)
{
struct drm_device *dev = (struct drm_device *)arg;
int i;
if (!dev->vblank_disable_allowed)
return;
for (i = 0; i < dev->num_crtcs; i++) {
mtx_lock(&dev->vbl_lock);
if (atomic_read(&dev->vblank_refcount[i]) == 0 &&
dev->vblank_enabled[i]) {
DRM_DEBUG("disabling vblank on crtc %d\n", i);
vblank_disable_and_save(dev, i);
}
mtx_unlock(&dev->vbl_lock);
}
}
void drm_vblank_cleanup(struct drm_device *dev)
{
if (dev->num_crtcs == 0)
return;
callout_stop(&dev->vblank_disable_callout);
vblank_disable_fn(dev);
free(dev->_vblank_count, DRM_MEM_VBLANK);
free(dev->vblank_refcount, DRM_MEM_VBLANK);
free(dev->vblank_enabled, DRM_MEM_VBLANK);
free(dev->last_vblank, DRM_MEM_VBLANK);
free(dev->last_vblank_wait, DRM_MEM_VBLANK);
free(dev->vblank_inmodeset, DRM_MEM_VBLANK);
free(dev->_vblank_time, DRM_MEM_VBLANK);
mtx_destroy(&dev->vbl_lock);
mtx_destroy(&dev->vblank_time_lock);
dev->num_crtcs = 0;
}
EXPORT_SYMBOL(drm_vblank_cleanup);
int drm_vblank_init(struct drm_device *dev, int num_crtcs)
{
int i, ret = -ENOMEM;
callout_init(&dev->vblank_disable_callout, 1);
mtx_init(&dev->vbl_lock, "drmvbl", NULL, MTX_DEF);
mtx_init(&dev->vblank_time_lock, "drmvtl", NULL, MTX_DEF);
dev->num_crtcs = num_crtcs;
dev->_vblank_count = malloc(sizeof(atomic_t) * num_crtcs,
DRM_MEM_VBLANK, M_NOWAIT);
if (!dev->_vblank_count)
goto err;
dev->vblank_refcount = malloc(sizeof(atomic_t) * num_crtcs,
DRM_MEM_VBLANK, M_NOWAIT);
if (!dev->vblank_refcount)
goto err;
dev->vblank_enabled = malloc(num_crtcs * sizeof(int),
DRM_MEM_VBLANK, M_NOWAIT | M_ZERO);
if (!dev->vblank_enabled)
goto err;
dev->last_vblank = malloc(num_crtcs * sizeof(u32),
DRM_MEM_VBLANK, M_NOWAIT | M_ZERO);
if (!dev->last_vblank)
goto err;
dev->last_vblank_wait = malloc(num_crtcs * sizeof(u32),
DRM_MEM_VBLANK, M_NOWAIT | M_ZERO);
if (!dev->last_vblank_wait)
goto err;
dev->vblank_inmodeset = malloc(num_crtcs * sizeof(int),
DRM_MEM_VBLANK, M_NOWAIT | M_ZERO);
if (!dev->vblank_inmodeset)
goto err;
dev->_vblank_time = malloc(num_crtcs * DRM_VBLANKTIME_RBSIZE *
sizeof(struct timeval), DRM_MEM_VBLANK, M_NOWAIT | M_ZERO);
if (!dev->_vblank_time)
goto err;
DRM_INFO("Supports vblank timestamp caching Rev 1 (10.10.2010).\n");
if (dev->driver->get_vblank_timestamp)
DRM_INFO("Driver supports precise vblank timestamp query.\n");
else
DRM_INFO("No driver support for vblank timestamp query.\n");
for (i = 0; i < num_crtcs; i++) {
atomic_set(&dev->_vblank_count[i], 0);
atomic_set(&dev->vblank_refcount[i], 0);
}
dev->vblank_disable_allowed = 0;
return 0;
err:
drm_vblank_cleanup(dev);
return ret;
}
EXPORT_SYMBOL(drm_vblank_init);
int drm_irq_install(struct drm_device *dev)
{
int ret;
unsigned long sh_flags = 0;
if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
return -EINVAL;
if (drm_dev_to_irq(dev) == 0)
return -EINVAL;
DRM_LOCK(dev);
if (!dev->dev_private) {
DRM_UNLOCK(dev);
return -EINVAL;
}
if (dev->irq_enabled) {
DRM_UNLOCK(dev);
return -EBUSY;
}
dev->irq_enabled = 1;
DRM_UNLOCK(dev);
DRM_DEBUG("irq=%d\n", drm_dev_to_irq(dev));
if (dev->driver->irq_preinstall)
dev->driver->irq_preinstall(dev);
sh_flags = INTR_TYPE_TTY | INTR_MPSAFE;
if (!drm_core_check_feature(dev, DRIVER_IRQ_SHARED))
sh_flags |= INTR_EXCL;
ret = -bus_setup_intr(dev->dev, dev->irqr, sh_flags, NULL,
dev->driver->irq_handler, dev, &dev->irqh);
if (ret < 0) {
device_printf(dev->dev, "Error setting interrupt: %d\n", -ret);
DRM_LOCK(dev);
dev->irq_enabled = 0;
DRM_UNLOCK(dev);
return ret;
}
if (dev->driver->irq_postinstall)
ret = dev->driver->irq_postinstall(dev);
if (ret < 0) {
DRM_LOCK(dev);
dev->irq_enabled = 0;
DRM_UNLOCK(dev);
bus_teardown_intr(dev->dev, dev->irqr, dev->irqh);
dev->driver->bus->free_irq(dev);
}
return ret;
}
EXPORT_SYMBOL(drm_irq_install);
int drm_irq_uninstall(struct drm_device *dev)
{
int irq_enabled, i;
if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
return -EINVAL;
DRM_LOCK(dev);
irq_enabled = dev->irq_enabled;
dev->irq_enabled = 0;
DRM_UNLOCK(dev);
if (dev->num_crtcs) {
mtx_lock(&dev->vbl_lock);
for (i = 0; i < dev->num_crtcs; i++) {
DRM_WAKEUP(&dev->_vblank_count[i]);
dev->vblank_enabled[i] = 0;
dev->last_vblank[i] =
dev->driver->get_vblank_counter(dev, i);
}
mtx_unlock(&dev->vbl_lock);
}
if (!irq_enabled)
return -EINVAL;
DRM_DEBUG("irq=%d\n", drm_dev_to_irq(dev));
if (dev->driver->irq_uninstall)
dev->driver->irq_uninstall(dev);
bus_teardown_intr(dev->dev, dev->irqr, dev->irqh);
dev->driver->bus->free_irq(dev);
return 0;
}
EXPORT_SYMBOL(drm_irq_uninstall);
int drm_control(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct drm_control *ctl = data;
switch (ctl->func) {
case DRM_INST_HANDLER:
if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
return 0;
if (drm_core_check_feature(dev, DRIVER_MODESET))
return 0;
if (dev->if_version < DRM_IF_VERSION(1, 2) &&
ctl->irq != drm_dev_to_irq(dev))
return -EINVAL;
return drm_irq_install(dev);
case DRM_UNINST_HANDLER:
if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
return 0;
if (drm_core_check_feature(dev, DRIVER_MODESET))
return 0;
return drm_irq_uninstall(dev);
default:
return -EINVAL;
}
}
void drm_calc_timestamping_constants(struct drm_crtc *crtc)
{
s64 linedur_ns = 0, pixeldur_ns = 0, framedur_ns = 0;
u64 dotclock;
dotclock = (u64) crtc->hwmode.clock * 1000;
if (crtc->hwmode.flags & DRM_MODE_FLAG_INTERLACE)
dotclock *= 2;
if (dotclock > 0) {
pixeldur_ns = (s64) div64_u64(1000000000, dotclock);
linedur_ns = (s64) div64_u64(((u64) crtc->hwmode.crtc_htotal *
1000000000), dotclock);
framedur_ns = (s64) crtc->hwmode.crtc_vtotal * linedur_ns;
} else
DRM_ERROR("crtc %d: Can't calculate constants, dotclock = 0!\n",
crtc->base.id);
crtc->pixeldur_ns = pixeldur_ns;
crtc->linedur_ns = linedur_ns;
crtc->framedur_ns = framedur_ns;
DRM_DEBUG("crtc %d: hwmode: htotal %d, vtotal %d, vdisplay %d\n",
crtc->base.id, crtc->hwmode.crtc_htotal,
crtc->hwmode.crtc_vtotal, crtc->hwmode.crtc_vdisplay);
DRM_DEBUG("crtc %d: clock %d kHz framedur %d linedur %d, pixeldur %d\n",
crtc->base.id, (int) dotclock/1000, (int) framedur_ns,
(int) linedur_ns, (int) pixeldur_ns);
}
EXPORT_SYMBOL(drm_calc_timestamping_constants);
int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, int crtc,
int *max_error,
struct timeval *vblank_time,
unsigned flags,
struct drm_crtc *refcrtc)
{
struct timeval stime, raw_time;
struct drm_display_mode *mode;
int vbl_status, vtotal, vdisplay;
int vpos, hpos, i;
s64 framedur_ns, linedur_ns, pixeldur_ns, delta_ns, duration_ns;
bool invbl;
if (crtc < 0 || crtc >= dev->num_crtcs) {
DRM_ERROR("Invalid crtc %d\n", crtc);
return -EINVAL;
}
if (!dev->driver->get_scanout_position) {
DRM_ERROR("Called from driver w/o get_scanout_position()!?\n");
return -EIO;
}
mode = &refcrtc->hwmode;
vtotal = mode->crtc_vtotal;
vdisplay = mode->crtc_vdisplay;
framedur_ns = refcrtc->framedur_ns;
linedur_ns = refcrtc->linedur_ns;
pixeldur_ns = refcrtc->pixeldur_ns;
if (vtotal <= 0 || vdisplay <= 0 || framedur_ns == 0) {
DRM_DEBUG("crtc %d: Noop due to uninitialized mode.\n", crtc);
return -EAGAIN;
}
for (i = 0; i < DRM_TIMESTAMP_MAXRETRIES; i++) {
critical_enter();
getmicrouptime(&stime);
vbl_status = dev->driver->get_scanout_position(dev, crtc, &vpos, &hpos);
getmicrouptime(&raw_time);
#ifdef FREEBSD_NOTYET
if (!drm_timestamp_monotonic)
mono_time_offset = ktime_get_monotonic_offset();
#endif
critical_exit();
if (!(vbl_status & DRM_SCANOUTPOS_VALID)) {
DRM_DEBUG("crtc %d : scanoutpos query failed [%d].\n",
crtc, vbl_status);
return -EIO;
}
duration_ns = timeval_to_ns(&raw_time) - timeval_to_ns(&stime);
if (duration_ns <= (s64) *max_error)
break;
}
if (i == DRM_TIMESTAMP_MAXRETRIES) {
DRM_DEBUG("crtc %d: Noisy timestamp %d us > %d us [%d reps].\n",
crtc, (int) duration_ns/1000, *max_error/1000, i);
}
*max_error = (int) duration_ns;
invbl = vbl_status & DRM_SCANOUTPOS_INVBL;
delta_ns = (s64) vpos * linedur_ns + (s64) hpos * pixeldur_ns;
if ((flags & DRM_CALLED_FROM_VBLIRQ) && !invbl &&
((vdisplay - vpos) < vtotal / 100)) {
delta_ns = delta_ns - framedur_ns;
vbl_status |= 0x8;
}
#ifdef FREEBSD_NOTYET
if (!drm_timestamp_monotonic)
etime = ktime_sub(etime, mono_time_offset);
tv_etime = ktime_to_timeval(etime);
#endif
*vblank_time = ns_to_timeval(timeval_to_ns(&raw_time) - delta_ns);
DRM_DEBUG("crtc %d : v %d p(%d,%d)@ %jd.%jd -> %jd.%jd [e %d us, %d rep]\n",
crtc, (int)vbl_status, hpos, vpos, (uintmax_t)raw_time.tv_sec,
(uintmax_t)raw_time.tv_usec, (uintmax_t)vblank_time->tv_sec,
(uintmax_t)vblank_time->tv_usec, (int)duration_ns/1000, i);
vbl_status = DRM_VBLANKTIME_SCANOUTPOS_METHOD;
if (invbl)
vbl_status |= DRM_VBLANKTIME_INVBL;
return vbl_status;
}
EXPORT_SYMBOL(drm_calc_vbltimestamp_from_scanoutpos);
static struct timeval get_drm_timestamp(void)
{
struct timeval now;
microtime(&now);
#ifdef FREEBSD_NOTYET
if (!drm_timestamp_monotonic)
now = ktime_sub(now, ktime_get_monotonic_offset());
#endif
return now;
}
u32 drm_get_last_vbltimestamp(struct drm_device *dev, int crtc,
struct timeval *tvblank, unsigned flags)
{
int ret;
int max_error = (int) drm_timestamp_precision * 1000;
if (dev->driver->get_vblank_timestamp && (max_error > 0)) {
ret = dev->driver->get_vblank_timestamp(dev, crtc, &max_error,
tvblank, flags);
if (ret > 0)
return (u32) ret;
}
*tvblank = get_drm_timestamp();
return 0;
}
EXPORT_SYMBOL(drm_get_last_vbltimestamp);
u32 drm_vblank_count(struct drm_device *dev, int crtc)
{
return atomic_read(&dev->_vblank_count[crtc]);
}
EXPORT_SYMBOL(drm_vblank_count);
u32 drm_vblank_count_and_time(struct drm_device *dev, int crtc,
struct timeval *vblanktime)
{
u32 cur_vblank;
do {
cur_vblank = atomic_read(&dev->_vblank_count[crtc]);
*vblanktime = vblanktimestamp(dev, crtc, cur_vblank);
smp_rmb();
} while (cur_vblank != atomic_read(&dev->_vblank_count[crtc]));
return cur_vblank;
}
EXPORT_SYMBOL(drm_vblank_count_and_time);
static void send_vblank_event(struct drm_device *dev,
struct drm_pending_vblank_event *e,
unsigned long seq, struct timeval *now)
{
WARN_ON_SMP(!mtx_owned(&dev->event_lock));
e->event.sequence = seq;
e->event.tv_sec = now->tv_sec;
e->event.tv_usec = now->tv_usec;
list_add_tail(&e->base.link,
&e->base.file_priv->event_list);
drm_event_wakeup(&e->base);
CTR3(KTR_DRM, "vblank_event_delivered %d %d %d",
e->base.pid, e->pipe, e->event.sequence);
}
void drm_send_vblank_event(struct drm_device *dev, int crtc,
struct drm_pending_vblank_event *e)
{
struct timeval now;
unsigned int seq;
if (crtc >= 0) {
seq = drm_vblank_count_and_time(dev, crtc, &now);
} else {
seq = 0;
now = get_drm_timestamp();
}
send_vblank_event(dev, e, seq, &now);
}
EXPORT_SYMBOL(drm_send_vblank_event);
static void drm_update_vblank_count(struct drm_device *dev, int crtc)
{
u32 cur_vblank, diff, tslot, rc;
struct timeval t_vblank;
do {
cur_vblank = dev->driver->get_vblank_counter(dev, crtc);
rc = drm_get_last_vbltimestamp(dev, crtc, &t_vblank, 0);
} while (cur_vblank != dev->driver->get_vblank_counter(dev, crtc));
diff = cur_vblank - dev->last_vblank[crtc];
if (cur_vblank < dev->last_vblank[crtc]) {
diff += dev->max_vblank_count;
DRM_DEBUG("last_vblank[%d]=0x%x, cur_vblank=0x%x => diff=0x%x\n",
crtc, dev->last_vblank[crtc], cur_vblank, diff);
}
DRM_DEBUG("enabling vblank interrupts on crtc %d, missed %d\n",
crtc, diff);
if (rc) {
tslot = atomic_read(&dev->_vblank_count[crtc]) + diff;
vblanktimestamp(dev, crtc, tslot) = t_vblank;
}
smp_mb__before_atomic_inc();
atomic_add(diff, &dev->_vblank_count[crtc]);
smp_mb__after_atomic_inc();
}
int drm_vblank_get(struct drm_device *dev, int crtc)
{
int ret = 0;
mtx_lock(&dev->vbl_lock);
if (atomic_add_return(1, &dev->vblank_refcount[crtc]) == 1) {
mtx_lock(&dev->vblank_time_lock);
if (!dev->vblank_enabled[crtc]) {
ret = dev->driver->enable_vblank(dev, crtc);
DRM_DEBUG("enabling vblank on crtc %d, ret: %d\n",
crtc, ret);
if (ret)
atomic_dec(&dev->vblank_refcount[crtc]);
else {
dev->vblank_enabled[crtc] = 1;
drm_update_vblank_count(dev, crtc);
}
}
mtx_unlock(&dev->vblank_time_lock);
} else {
if (!dev->vblank_enabled[crtc]) {
atomic_dec(&dev->vblank_refcount[crtc]);
ret = -EINVAL;
}
}
mtx_unlock(&dev->vbl_lock);
return ret;
}
EXPORT_SYMBOL(drm_vblank_get);
void drm_vblank_put(struct drm_device *dev, int crtc)
{
BUG_ON(atomic_read(&dev->vblank_refcount[crtc]) == 0);
if (atomic_dec_and_test(&dev->vblank_refcount[crtc]) &&
(drm_vblank_offdelay > 0))
callout_reset(&dev->vblank_disable_callout,
(drm_vblank_offdelay * DRM_HZ) / 1000,
vblank_disable_fn, dev);
}
EXPORT_SYMBOL(drm_vblank_put);
void drm_vblank_off(struct drm_device *dev, int crtc)
{
struct drm_pending_vblank_event *e, *t;
struct timeval now;
unsigned int seq;
mtx_lock(&dev->vbl_lock);
vblank_disable_and_save(dev, crtc);
DRM_WAKEUP(&dev->_vblank_count[crtc]);
seq = drm_vblank_count_and_time(dev, crtc, &now);
mtx_lock(&dev->event_lock);
list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) {
if (e->pipe != crtc)
continue;
DRM_DEBUG("Sending premature vblank event on disable: \
wanted %d, current %d\n",
e->event.sequence, seq);
list_del(&e->base.link);
drm_vblank_put(dev, e->pipe);
send_vblank_event(dev, e, seq, &now);
}
mtx_unlock(&dev->event_lock);
mtx_unlock(&dev->vbl_lock);
}
EXPORT_SYMBOL(drm_vblank_off);
void drm_vblank_pre_modeset(struct drm_device *dev, int crtc)
{
if (!dev->num_crtcs)
return;
if (!dev->vblank_inmodeset[crtc]) {
dev->vblank_inmodeset[crtc] = 0x1;
if (drm_vblank_get(dev, crtc) == 0)
dev->vblank_inmodeset[crtc] |= 0x2;
}
}
EXPORT_SYMBOL(drm_vblank_pre_modeset);
void drm_vblank_post_modeset(struct drm_device *dev, int crtc)
{
if (!dev->num_crtcs)
return;
if (dev->vblank_inmodeset[crtc]) {
mtx_lock(&dev->vbl_lock);
dev->vblank_disable_allowed = 1;
mtx_unlock(&dev->vbl_lock);
if (dev->vblank_inmodeset[crtc] & 0x2)
drm_vblank_put(dev, crtc);
dev->vblank_inmodeset[crtc] = 0;
}
}
EXPORT_SYMBOL(drm_vblank_post_modeset);
int drm_modeset_ctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct drm_modeset_ctl *modeset = data;
unsigned int crtc;
if (!dev->num_crtcs)
return 0;
if (drm_core_check_feature(dev, DRIVER_MODESET))
return 0;
crtc = modeset->crtc;
if (crtc >= dev->num_crtcs)
return -EINVAL;
switch (modeset->cmd) {
case _DRM_PRE_MODESET:
drm_vblank_pre_modeset(dev, crtc);
break;
case _DRM_POST_MODESET:
drm_vblank_post_modeset(dev, crtc);
break;
default:
return -EINVAL;
}
return 0;
}
static void
drm_vblank_event_destroy(struct drm_pending_event *e)
{
free(e, DRM_MEM_VBLANK);
}
static int drm_queue_vblank_event(struct drm_device *dev, int pipe,
union drm_wait_vblank *vblwait,
struct drm_file *file_priv)
{
struct drm_pending_vblank_event *e;
struct timeval now;
unsigned int seq;
int ret;
e = malloc(sizeof *e, DRM_MEM_VBLANK, M_NOWAIT | M_ZERO);
if (e == NULL) {
ret = -ENOMEM;
goto err_put;
}
e->pipe = pipe;
e->base.pid = curproc->p_pid;
e->event.base.type = DRM_EVENT_VBLANK;
e->event.base.length = sizeof e->event;
e->event.user_data = vblwait->request.signal;
e->base.event = &e->event.base;
e->base.file_priv = file_priv;
e->base.destroy = drm_vblank_event_destroy;
mtx_lock(&dev->event_lock);
if (file_priv->event_space < sizeof e->event) {
ret = -EBUSY;
goto err_unlock;
}
file_priv->event_space -= sizeof e->event;
seq = drm_vblank_count_and_time(dev, pipe, &now);
if ((vblwait->request.type & _DRM_VBLANK_NEXTONMISS) &&
(seq - vblwait->request.sequence) <= (1 << 23)) {
vblwait->request.sequence = seq + 1;
vblwait->reply.sequence = vblwait->request.sequence;
}
DRM_DEBUG("event on vblank count %d, current %d, crtc %d\n",
vblwait->request.sequence, seq, pipe);
CTR4(KTR_DRM, "vblank_event_queued %d %d rt %x %d", curproc->p_pid, pipe,
vblwait->request.type, vblwait->request.sequence);
e->event.sequence = vblwait->request.sequence;
if ((seq - vblwait->request.sequence) <= (1 << 23)) {
drm_vblank_put(dev, pipe);
send_vblank_event(dev, e, seq, &now);
vblwait->reply.sequence = seq;
} else {
list_add_tail(&e->base.link, &dev->vblank_event_list);
vblwait->reply.sequence = vblwait->request.sequence;
}
mtx_unlock(&dev->event_lock);
return 0;
err_unlock:
mtx_unlock(&dev->event_lock);
free(e, DRM_MEM_VBLANK);
err_put:
drm_vblank_put(dev, pipe);
return ret;
}
int drm_wait_vblank(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
union drm_wait_vblank *vblwait = data;
int ret;
unsigned int flags, seq, crtc, high_crtc;
if ((!dev->irq_enabled))
return -EINVAL;
if (vblwait->request.type & _DRM_VBLANK_SIGNAL)
return -EINVAL;
if (vblwait->request.type &
~(_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK |
_DRM_VBLANK_HIGH_CRTC_MASK)) {
DRM_ERROR("Unsupported type value 0x%x, supported mask 0x%x\n",
vblwait->request.type,
(_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK |
_DRM_VBLANK_HIGH_CRTC_MASK));
return -EINVAL;
}
flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK;
high_crtc = (vblwait->request.type & _DRM_VBLANK_HIGH_CRTC_MASK);
if (high_crtc)
crtc = high_crtc >> _DRM_VBLANK_HIGH_CRTC_SHIFT;
else
crtc = flags & _DRM_VBLANK_SECONDARY ? 1 : 0;
if (crtc >= dev->num_crtcs)
return -EINVAL;
ret = drm_vblank_get(dev, crtc);
if (ret) {
DRM_DEBUG("failed to acquire vblank counter, %d\n", ret);
return ret;
}
seq = drm_vblank_count(dev, crtc);
switch (vblwait->request.type & _DRM_VBLANK_TYPES_MASK) {
case _DRM_VBLANK_RELATIVE:
vblwait->request.sequence += seq;
vblwait->request.type &= ~_DRM_VBLANK_RELATIVE;
case _DRM_VBLANK_ABSOLUTE:
break;
default:
ret = -EINVAL;
goto done;
}
if (flags & _DRM_VBLANK_EVENT) {
return drm_queue_vblank_event(dev, crtc, vblwait, file_priv);
}
if ((flags & _DRM_VBLANK_NEXTONMISS) &&
(seq - vblwait->request.sequence) <= (1<<23)) {
vblwait->request.sequence = seq + 1;
}
DRM_DEBUG("waiting on vblank count %d, crtc %d\n",
vblwait->request.sequence, crtc);
dev->last_vblank_wait[crtc] = vblwait->request.sequence;
mtx_lock(&dev->vblank_time_lock);
while (((drm_vblank_count(dev, crtc) - vblwait->request.sequence) >
(1 << 23)) && dev->irq_enabled) {
ret = -msleep(&dev->_vblank_count[crtc], &dev->vblank_time_lock,
PCATCH, "drmvbl", 3 * hz);
if (ret == -ERESTART)
ret = -ERESTARTSYS;
if (ret != 0)
break;
}
mtx_unlock(&dev->vblank_time_lock);
if (ret != -EINTR) {
struct timeval now;
long reply_seq;
reply_seq = drm_vblank_count_and_time(dev, crtc, &now);
CTR5(KTR_DRM, "wait_vblank %d %d rt %x success %d %d",
curproc->p_pid, crtc, vblwait->request.type,
vblwait->request.sequence, reply_seq);
vblwait->reply.sequence = reply_seq;
vblwait->reply.tval_sec = now.tv_sec;
vblwait->reply.tval_usec = now.tv_usec;
DRM_DEBUG("returning %d to client\n",
vblwait->reply.sequence);
} else {
CTR5(KTR_DRM, "wait_vblank %d %d rt %x error %d %d",
curproc->p_pid, crtc, vblwait->request.type, ret,
vblwait->request.sequence);
DRM_DEBUG("vblank wait interrupted by signal\n");
}
done:
drm_vblank_put(dev, crtc);
return ret;
}
static void drm_handle_vblank_events(struct drm_device *dev, int crtc)
{
struct drm_pending_vblank_event *e, *t;
struct timeval now;
unsigned int seq;
seq = drm_vblank_count_and_time(dev, crtc, &now);
mtx_lock(&dev->event_lock);
list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) {
if (e->pipe != crtc)
continue;
if ((seq - e->event.sequence) > (1<<23))
continue;
DRM_DEBUG("vblank event on %d, current %d\n",
e->event.sequence, seq);
list_del(&e->base.link);
drm_vblank_put(dev, e->pipe);
send_vblank_event(dev, e, seq, &now);
}
mtx_unlock(&dev->event_lock);
CTR2(KTR_DRM, "drm_handle_vblank_events %d %d", seq, crtc);
}
bool drm_handle_vblank(struct drm_device *dev, int crtc)
{
u32 vblcount;
s64 diff_ns;
struct timeval tvblank;
if (!dev->num_crtcs)
return false;
mtx_lock(&dev->vblank_time_lock);
if (!dev->vblank_enabled[crtc]) {
mtx_unlock(&dev->vblank_time_lock);
return false;
}
vblcount = atomic_read(&dev->_vblank_count[crtc]);
drm_get_last_vbltimestamp(dev, crtc, &tvblank, DRM_CALLED_FROM_VBLIRQ);
diff_ns = timeval_to_ns(&tvblank) -
timeval_to_ns(&vblanktimestamp(dev, crtc, vblcount));
if (abs64(diff_ns) > DRM_REDUNDANT_VBLIRQ_THRESH_NS) {
vblanktimestamp(dev, crtc, vblcount + 1) = tvblank;
smp_mb__before_atomic_inc();
atomic_inc(&dev->_vblank_count[crtc]);
smp_mb__after_atomic_inc();
} else {
DRM_DEBUG("crtc %d: Redundant vblirq ignored. diff_ns = %d\n",
crtc, (int) diff_ns);
}
DRM_WAKEUP(&dev->_vblank_count[crtc]);
drm_handle_vblank_events(dev, crtc);
mtx_unlock(&dev->vblank_time_lock);
return true;
}
EXPORT_SYMBOL(drm_handle_vblank);