drm_irq.c revision 194965
1/*- 2 * Copyright 2003 Eric Anholt 3 * All Rights Reserved. 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a 6 * copy of this software and associated documentation files (the "Software"), 7 * to deal in the Software without restriction, including without limitation 8 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 * and/or sell copies of the Software, and to permit persons to whom the 10 * Software is furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice (including the next 13 * paragraph) shall be included in all copies or substantial portions of the 14 * Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 * ERIC ANHOLT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 * 23 * Authors: 24 * Eric Anholt <anholt@FreeBSD.org> 25 * 26 */ 27 28#include <sys/cdefs.h> 29__FBSDID("$FreeBSD: head/sys/dev/drm/drm_irq.c 194965 2009-06-25 15:36:11Z rnoland $"); 30 31/** @file drm_irq.c 32 * Support code for handling setup/teardown of interrupt handlers and 33 * handing interrupt handlers off to the drivers. 34 */ 35 36#include "dev/drm/drmP.h" 37#include "dev/drm/drm.h" 38 39int drm_irq_by_busid(struct drm_device *dev, void *data, 40 struct drm_file *file_priv) 41{ 42 struct drm_irq_busid *irq = data; 43 44 if ((irq->busnum >> 8) != dev->pci_domain || 45 (irq->busnum & 0xff) != dev->pci_bus || 46 irq->devnum != dev->pci_slot || 47 irq->funcnum != dev->pci_func) 48 return EINVAL; 49 50 irq->irq = dev->irq; 51 52 DRM_DEBUG("%d:%d:%d => IRQ %d\n", 53 irq->busnum, irq->devnum, irq->funcnum, irq->irq); 54 55 return 0; 56} 57 58static irqreturn_t 59drm_irq_handler_wrap(DRM_IRQ_ARGS) 60{ 61 struct drm_device *dev = arg; 62 63 DRM_SPINLOCK(&dev->irq_lock); 64 dev->driver->irq_handler(arg); 65 DRM_SPINUNLOCK(&dev->irq_lock); 66} 67 68static void vblank_disable_fn(void *arg) 69{ 70 struct drm_device *dev = (struct drm_device *)arg; 71 int i; 72 73 /* Make sure that we are called with the lock held */ 74 mtx_assert(&dev->vbl_lock, MA_OWNED); 75 76 if (callout_pending(&dev->vblank_disable_timer)) { 77 /* callout was reset */ 78 return; 79 } 80 if (!callout_active(&dev->vblank_disable_timer)) { 81 /* callout was stopped */ 82 return; 83 } 84 callout_deactivate(&dev->vblank_disable_timer); 85 86 DRM_DEBUG("vblank_disable: %s\n", dev->vblank_disable_allowed ? 87 "allowed" : "denied"); 88 if (!dev->vblank_disable_allowed) 89 return; 90 91 for (i = 0; i < dev->num_crtcs; i++) { 92 if (dev->vblank[i].refcount == 0 && 93 dev->vblank[i].enabled && !dev->vblank[i].inmodeset) { 94 DRM_DEBUG("disabling vblank on crtc %d\n", i); 95 dev->vblank[i].last = 96 dev->driver->get_vblank_counter(dev, i); 97 dev->driver->disable_vblank(dev, i); 98 dev->vblank[i].enabled = 0; 99 } 100 } 101} 102 103void drm_vblank_cleanup(struct drm_device *dev) 104{ 105 /* Bail if the driver didn't call drm_vblank_init() */ 106 if (dev->num_crtcs == 0) 107 return; 108 109 DRM_SPINLOCK(&dev->vbl_lock); 110 callout_stop(&dev->vblank_disable_timer); 111 DRM_SPINUNLOCK(&dev->vbl_lock); 112 113 callout_drain(&dev->vblank_disable_timer); 114 115 DRM_SPINLOCK(&dev->vbl_lock); 116 vblank_disable_fn((void *)dev); 117 DRM_SPINUNLOCK(&dev->vbl_lock); 118 119 free(dev->vblank, DRM_MEM_DRIVER); 120 121 dev->num_crtcs = 0; 122} 123 124int drm_vblank_init(struct drm_device *dev, int num_crtcs) 125{ 126 int i, ret = ENOMEM; 127 128 callout_init_mtx(&dev->vblank_disable_timer, &dev->vbl_lock, 0); 129 dev->num_crtcs = num_crtcs; 130 131 dev->vblank = malloc(sizeof(struct drm_vblank_info) * num_crtcs, 132 DRM_MEM_DRIVER, M_NOWAIT | M_ZERO); 133 if (!dev->vblank) 134 goto err; 135 136 DRM_DEBUG("\n"); 137 138 /* Zero per-crtc vblank stuff */ 139 DRM_SPINLOCK(&dev->vbl_lock); 140 for (i = 0; i < num_crtcs; i++) { 141 DRM_INIT_WAITQUEUE(&dev->vblank[i].queue); 142 dev->vblank[i].refcount = 0; 143 atomic_set_rel_32(&dev->vblank[i].count, 0); 144 } 145 dev->vblank_disable_allowed = 0; 146 DRM_SPINUNLOCK(&dev->vbl_lock); 147 148 return 0; 149 150err: 151 drm_vblank_cleanup(dev); 152 return ret; 153} 154 155int drm_irq_install(struct drm_device *dev) 156{ 157 int crtc, retcode; 158 159 if (dev->irq == 0 || dev->dev_private == NULL) 160 return EINVAL; 161 162 DRM_DEBUG("irq=%d\n", dev->irq); 163 164 DRM_LOCK(); 165 if (dev->irq_enabled) { 166 DRM_UNLOCK(); 167 return EBUSY; 168 } 169 dev->irq_enabled = 1; 170 171 dev->context_flag = 0; 172 173 /* Before installing handler */ 174 dev->driver->irq_preinstall(dev); 175 DRM_UNLOCK(); 176 177 /* Install handler */ 178#if __FreeBSD_version >= 700031 179 retcode = bus_setup_intr(dev->device, dev->irqr, 180 INTR_TYPE_TTY | INTR_MPSAFE, 181 NULL, drm_irq_handler_wrap, dev, &dev->irqh); 182#else 183 retcode = bus_setup_intr(dev->device, dev->irqr, 184 INTR_TYPE_TTY | INTR_MPSAFE, 185 drm_irq_handler_wrap, dev, &dev->irqh); 186#endif 187 if (retcode != 0) 188 goto err; 189 190 /* After installing handler */ 191 DRM_LOCK(); 192 dev->driver->irq_postinstall(dev); 193 DRM_UNLOCK(); 194 if (dev->driver->enable_vblank) { 195 DRM_SPINLOCK(&dev->vbl_lock); 196 for( crtc = 0 ; crtc < dev->num_crtcs ; crtc++) { 197 if (dev->driver->enable_vblank(dev, crtc) == 0) { 198 dev->vblank[crtc].enabled = 1; 199 } 200 } 201 callout_reset(&dev->vblank_disable_timer, 5 * DRM_HZ, 202 (timeout_t *)vblank_disable_fn, (void *)dev); 203 DRM_SPINUNLOCK(&dev->vbl_lock); 204 } 205 206 return 0; 207err: 208 DRM_LOCK(); 209 dev->irq_enabled = 0; 210 DRM_UNLOCK(); 211 212 return retcode; 213} 214 215int drm_irq_uninstall(struct drm_device *dev) 216{ 217 int crtc; 218 219 if (!dev->irq_enabled) 220 return EINVAL; 221 222 dev->irq_enabled = 0; 223 224 /* 225 * Wake up any waiters so they don't hang. 226 */ 227 DRM_SPINLOCK(&dev->vbl_lock); 228 for (crtc = 0; crtc < dev->num_crtcs; crtc++) { 229 if (dev->vblank[crtc].enabled) { 230 DRM_WAKEUP(&dev->vblank[crtc].queue); 231 dev->vblank[crtc].last = 232 dev->driver->get_vblank_counter(dev, crtc); 233 dev->vblank[crtc].enabled = 0; 234 } 235 } 236 DRM_SPINUNLOCK(&dev->vbl_lock); 237 238 DRM_DEBUG("irq=%d\n", dev->irq); 239 240 dev->driver->irq_uninstall(dev); 241 242 DRM_UNLOCK(); 243 bus_teardown_intr(dev->device, dev->irqr, dev->irqh); 244 DRM_LOCK(); 245 246 return 0; 247} 248 249int drm_control(struct drm_device *dev, void *data, struct drm_file *file_priv) 250{ 251 struct drm_control *ctl = data; 252 int err; 253 254 switch (ctl->func) { 255 case DRM_INST_HANDLER: 256 /* Handle drivers whose DRM used to require IRQ setup but the 257 * no longer does. 258 */ 259 if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) 260 return 0; 261 if (dev->if_version < DRM_IF_VERSION(1, 2) && 262 ctl->irq != dev->irq) 263 return EINVAL; 264 return drm_irq_install(dev); 265 case DRM_UNINST_HANDLER: 266 if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) 267 return 0; 268 DRM_LOCK(); 269 err = drm_irq_uninstall(dev); 270 DRM_UNLOCK(); 271 return err; 272 default: 273 return EINVAL; 274 } 275} 276 277u32 drm_vblank_count(struct drm_device *dev, int crtc) 278{ 279 return atomic_load_acq_32(&dev->vblank[crtc].count); 280} 281 282static void drm_update_vblank_count(struct drm_device *dev, int crtc) 283{ 284 u32 cur_vblank, diff; 285 286 /* 287 * Interrupts were disabled prior to this call, so deal with counter 288 * wrap if needed. 289 * NOTE! It's possible we lost a full dev->max_vblank_count events 290 * here if the register is small or we had vblank interrupts off for 291 * a long time. 292 */ 293 cur_vblank = dev->driver->get_vblank_counter(dev, crtc); 294 diff = cur_vblank - dev->vblank[crtc].last; 295 if (cur_vblank < dev->vblank[crtc].last) { 296 diff += dev->max_vblank_count; 297 298 DRM_DEBUG("vblank[%d].last=0x%x, cur_vblank=0x%x => diff=0x%x\n", 299 crtc, dev->vblank[crtc].last, cur_vblank, diff); 300 } 301 302 DRM_DEBUG("enabling vblank interrupts on crtc %d, missed %d\n", 303 crtc, diff); 304 305 atomic_add_rel_32(&dev->vblank[crtc].count, diff); 306} 307 308int drm_vblank_get(struct drm_device *dev, int crtc) 309{ 310 int ret = 0; 311 312 /* Make sure that we are called with the lock held */ 313 mtx_assert(&dev->vbl_lock, MA_OWNED); 314 315 /* Going from 0->1 means we have to enable interrupts again */ 316 if (++dev->vblank[crtc].refcount == 1 && 317 !dev->vblank[crtc].enabled) { 318 ret = dev->driver->enable_vblank(dev, crtc); 319 DRM_DEBUG("enabling vblank on crtc %d, ret: %d\n", crtc, ret); 320 if (ret) 321 --dev->vblank[crtc].refcount; 322 else { 323 dev->vblank[crtc].enabled = 1; 324 drm_update_vblank_count(dev, crtc); 325 } 326 } 327 328 return ret; 329} 330 331void drm_vblank_put(struct drm_device *dev, int crtc) 332{ 333 /* Make sure that we are called with the lock held */ 334 mtx_assert(&dev->vbl_lock, MA_OWNED); 335 336 KASSERT(dev->vblank[crtc].refcount > 0, 337 ("invalid refcount")); 338 339 /* Last user schedules interrupt disable */ 340 if (--dev->vblank[crtc].refcount == 0) 341 callout_reset(&dev->vblank_disable_timer, 5 * DRM_HZ, 342 (timeout_t *)vblank_disable_fn, (void *)dev); 343} 344 345int drm_modeset_ctl(struct drm_device *dev, void *data, 346 struct drm_file *file_priv) 347{ 348 struct drm_modeset_ctl *modeset = data; 349 int crtc, ret = 0; 350 351 /* If drm_vblank_init() hasn't been called yet, just no-op */ 352 if (!dev->num_crtcs) 353 goto out; 354 355 crtc = modeset->crtc; 356 if (crtc >= dev->num_crtcs) { 357 ret = EINVAL; 358 goto out; 359 } 360 361 /* 362 * To avoid all the problems that might happen if interrupts 363 * were enabled/disabled around or between these calls, we just 364 * have the kernel take a reference on the CRTC (just once though 365 * to avoid corrupting the count if multiple, mismatch calls occur), 366 * so that interrupts remain enabled in the interim. 367 */ 368 switch (modeset->cmd) { 369 case _DRM_PRE_MODESET: 370 DRM_DEBUG("pre-modeset, crtc %d\n", crtc); 371 DRM_SPINLOCK(&dev->vbl_lock); 372 if (!dev->vblank[crtc].inmodeset) { 373 dev->vblank[crtc].inmodeset = 0x1; 374 if (drm_vblank_get(dev, crtc) == 0) 375 dev->vblank[crtc].inmodeset |= 0x2; 376 } 377 DRM_SPINUNLOCK(&dev->vbl_lock); 378 break; 379 case _DRM_POST_MODESET: 380 DRM_DEBUG("post-modeset, crtc %d\n", crtc); 381 DRM_SPINLOCK(&dev->vbl_lock); 382 if (dev->vblank[crtc].inmodeset) { 383 if (dev->vblank[crtc].inmodeset & 0x2) 384 drm_vblank_put(dev, crtc); 385 dev->vblank[crtc].inmodeset = 0; 386 } 387 dev->vblank_disable_allowed = 1; 388 DRM_SPINUNLOCK(&dev->vbl_lock); 389 break; 390 default: 391 ret = EINVAL; 392 break; 393 } 394 395out: 396 return ret; 397} 398 399int drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_priv) 400{ 401 union drm_wait_vblank *vblwait = data; 402 unsigned int flags, seq, crtc; 403 int ret = 0; 404 405 if (!dev->irq_enabled) 406 return EINVAL; 407 408 if (vblwait->request.type & 409 ~(_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK)) { 410 DRM_ERROR("Unsupported type value 0x%x, supported mask 0x%x\n", 411 vblwait->request.type, 412 (_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK)); 413 return EINVAL; 414 } 415 416 flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK; 417 crtc = flags & _DRM_VBLANK_SECONDARY ? 1 : 0; 418 419 if (crtc >= dev->num_crtcs) 420 return EINVAL; 421 422 DRM_SPINLOCK(&dev->vbl_lock); 423 ret = drm_vblank_get(dev, crtc); 424 DRM_SPINUNLOCK(&dev->vbl_lock); 425 if (ret) { 426 DRM_ERROR("failed to acquire vblank counter, %d\n", ret); 427 return ret; 428 } 429 seq = drm_vblank_count(dev, crtc); 430 431 switch (vblwait->request.type & _DRM_VBLANK_TYPES_MASK) { 432 case _DRM_VBLANK_RELATIVE: 433 vblwait->request.sequence += seq; 434 vblwait->request.type &= ~_DRM_VBLANK_RELATIVE; 435 case _DRM_VBLANK_ABSOLUTE: 436 break; 437 default: 438 ret = EINVAL; 439 goto done; 440 } 441 442 if ((flags & _DRM_VBLANK_NEXTONMISS) && 443 (seq - vblwait->request.sequence) <= (1<<23)) { 444 vblwait->request.sequence = seq + 1; 445 } 446 447 if (flags & _DRM_VBLANK_SIGNAL) { 448 /* There have never been any consumers */ 449 ret = EINVAL; 450 } else { 451 DRM_DEBUG("waiting on vblank count %d, crtc %d\n", 452 vblwait->request.sequence, crtc); 453 for ( ret = 0 ; !ret && !(((drm_vblank_count(dev, crtc) - 454 vblwait->request.sequence) <= (1 << 23)) || 455 !dev->irq_enabled) ; ) { 456 mtx_lock(&dev->irq_lock); 457 if (!(((drm_vblank_count(dev, crtc) - 458 vblwait->request.sequence) <= (1 << 23)) || 459 !dev->irq_enabled)) 460 ret = mtx_sleep(&dev->vblank[crtc].queue, 461 &dev->irq_lock, PCATCH, "vblwtq", 462 DRM_HZ); 463 mtx_unlock(&dev->irq_lock); 464 } 465 466 if (ret != EINTR && ret != ERESTART) { 467 struct timeval now; 468 469 microtime(&now); 470 vblwait->reply.tval_sec = now.tv_sec; 471 vblwait->reply.tval_usec = now.tv_usec; 472 vblwait->reply.sequence = drm_vblank_count(dev, crtc); 473 DRM_DEBUG("returning %d to client, irq_enabled %d\n", 474 vblwait->reply.sequence, dev->irq_enabled); 475 } else { 476 DRM_DEBUG("vblank wait interrupted by signal\n"); 477 } 478 } 479 480done: 481 DRM_SPINLOCK(&dev->vbl_lock); 482 drm_vblank_put(dev, crtc); 483 DRM_SPINUNLOCK(&dev->vbl_lock); 484 485 return ret; 486} 487 488void drm_handle_vblank(struct drm_device *dev, int crtc) 489{ 490 atomic_add_rel_32(&dev->vblank[crtc].count, 1); 491 DRM_WAKEUP(&dev->vblank[crtc].queue); 492} 493 494