1 /* $OpenBSD: drm_irq.c,v 1.43 2011/06/02 18:22:00 weerd Exp $ */ 2 /*- 3 * Copyright 2003 Eric Anholt 4 * All Rights Reserved. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the "Software"), 8 * to deal in the Software without restriction, including without limitation 9 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 * and/or sell copies of the Software, and to permit persons to whom the 11 * Software is furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice (including the next 14 * paragraph) shall be included in all copies or substantial portions of the 15 * Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 * ERIC ANHOLT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 * 24 * Authors: 25 * Eric Anholt <anholt@FreeBSD.org> 26 * 27 */ 28 29 /** @file drm_irq.c 30 * Support code for handling setup/teardown of interrupt handlers and 31 * handing interrupt handlers off to the drivers. 32 */ 33 34 #include <sys/workq.h> 35 36 #include "drmP.h" 37 #include "drm.h" 38 39 void drm_update_vblank_count(struct drm_device *, int); 40 void vblank_disable(void *); 41 int drm_queue_vblank_event(struct drm_device *, int, 42 union drm_wait_vblank *, struct drm_file *); 43 void drm_handle_vblank_events(struct drm_device *, int); 44 45 #ifdef DRM_VBLANK_DEBUG 46 #define DPRINTF(x...) do { printf(x); } while(/* CONSTCOND */ 0) 47 #else 48 #define DPRINTF(x...) do { } while(/* CONSTCOND */ 0) 49 #endif 50 51 int 52 drm_irq_by_busid(struct drm_device *dev, void *data, struct drm_file *file_priv) 53 { 54 struct drm_irq_busid *irq = data; 55 56 /* 57 * This is only ever called by root as part of a stupid interface. 58 * just hand over the irq without checking the busid. If all clients 59 * can be forced to use interface 1.2 then this can die. 60 */ 61 irq->irq = dev->irq; 62 63 DRM_DEBUG("%d:%d:%d => IRQ %d\n", irq->busnum, irq->devnum, 64 irq->funcnum, irq->irq); 65 66 return 0; 67 } 68 69 int 70 drm_irq_install(struct drm_device *dev) 71 { 72 int ret; 73 74 if (dev->irq == 0 || dev->dev_private == NULL) 75 return (EINVAL); 76 77 DRM_DEBUG("irq=%d\n", dev->irq); 78 79 DRM_LOCK(); 80 if (dev->irq_enabled) { 81 DRM_UNLOCK(); 82 return (EBUSY); 83 } 84 dev->irq_enabled = 1; 85 DRM_UNLOCK(); 86 87 if ((ret = dev->driver->irq_install(dev)) != 0) 88 goto err; 89 90 return (0); 91 err: 92 DRM_LOCK(); 93 dev->irq_enabled = 0; 94 DRM_UNLOCK(); 95 return (ret); 96 } 97 98 int 99 drm_irq_uninstall(struct drm_device *dev) 100 { 101 int i; 102 103 DRM_LOCK(); 104 if (!dev->irq_enabled) { 105 DRM_UNLOCK(); 106 return (EINVAL); 107 } 108 109 dev->irq_enabled = 0; 110 DRM_UNLOCK(); 111 112 /* 113 * Ick. we're about to turn of vblanks, so make sure anyone waiting 114 * on them gets woken up. Also make sure we update state correctly 115 * so that we can continue refcounting correctly. 116 */ 117 if (dev->vblank != NULL) { 118 mtx_enter(&dev->vblank->vb_lock); 119 for (i = 0; i < dev->vblank->vb_num; i++) { 120 wakeup(&dev->vblank->vb_crtcs[i]); 121 dev->vblank->vb_crtcs[i].vbl_enabled = 0; 122 dev->vblank->vb_crtcs[i].vbl_last = 123 dev->driver->get_vblank_counter(dev, i); 124 } 125 mtx_leave(&dev->vblank->vb_lock); 126 } 127 128 DRM_DEBUG("irq=%d\n", dev->irq); 129 130 dev->driver->irq_uninstall(dev); 131 132 return (0); 133 } 134 135 int 136 drm_control(struct drm_device *dev, void *data, struct drm_file *file_priv) 137 { 138 struct drm_control *ctl = data; 139 140 /* Handle drivers who used to require IRQ setup no longer does. */ 141 if (!(dev->driver->flags & DRIVER_IRQ)) 142 return (0); 143 144 switch (ctl->func) { 145 case DRM_INST_HANDLER: 146 if (dev->if_version < DRM_IF_VERSION(1, 2) && 147 ctl->irq != dev->irq) 148 return (EINVAL); 149 return (drm_irq_install(dev)); 150 case DRM_UNINST_HANDLER: 151 return (drm_irq_uninstall(dev)); 152 default: 153 return (EINVAL); 154 } 155 } 156 157 void 158 vblank_disable(void *arg) 159 { 160 struct drm_device *dev = (struct drm_device*)arg; 161 struct drm_vblank_info *vbl = dev->vblank; 162 struct drm_vblank *crtc; 163 int i; 164 165 mtx_enter(&vbl->vb_lock); 166 for (i = 0; i < vbl->vb_num; i++) { 167 crtc = &vbl->vb_crtcs[i]; 168 169 if (crtc->vbl_refs == 0 && crtc->vbl_enabled) { 170 DPRINTF("%s: disabling crtc %d\n", __func__, i); 171 crtc->vbl_last = 172 dev->driver->get_vblank_counter(dev, i); 173 dev->driver->disable_vblank(dev, i); 174 crtc->vbl_enabled = 0; 175 } 176 } 177 mtx_leave(&vbl->vb_lock); 178 } 179 180 void 181 drm_vblank_cleanup(struct drm_device *dev) 182 { 183 if (dev->vblank == NULL) 184 return; /* not initialised */ 185 186 timeout_del(&dev->vblank->vb_disable_timer); 187 188 vblank_disable(dev); 189 190 drm_free(dev->vblank); 191 dev->vblank = NULL; 192 } 193 194 int 195 drm_vblank_init(struct drm_device *dev, int num_crtcs) 196 { 197 int i; 198 199 dev->vblank = malloc(sizeof(*dev->vblank) + (num_crtcs * 200 sizeof(struct drm_vblank)), M_DRM, M_WAITOK | M_CANFAIL | M_ZERO); 201 if (dev->vblank == NULL) 202 return (ENOMEM); 203 204 dev->vblank->vb_num = num_crtcs; 205 mtx_init(&dev->vblank->vb_lock, IPL_TTY); 206 timeout_set(&dev->vblank->vb_disable_timer, vblank_disable, dev); 207 for (i = 0; i < num_crtcs; i++) 208 TAILQ_INIT(&dev->vblank->vb_crtcs[i].vbl_events); 209 210 return (0); 211 } 212 213 u_int32_t 214 drm_vblank_count(struct drm_device *dev, int crtc) 215 { 216 return (dev->vblank->vb_crtcs[crtc].vbl_count); 217 } 218 219 void 220 drm_update_vblank_count(struct drm_device *dev, int crtc) 221 { 222 u_int32_t cur_vblank, diff; 223 224 /* 225 * Interrupt was disabled prior to this call, so deal with counter wrap 226 * note that we may have lost a full vb_max events if 227 * the register is small or the interrupts were off for a long time. 228 */ 229 cur_vblank = dev->driver->get_vblank_counter(dev, crtc); 230 diff = cur_vblank - dev->vblank->vb_crtcs[crtc].vbl_last; 231 if (cur_vblank < dev->vblank->vb_crtcs[crtc].vbl_last) 232 diff += dev->vblank->vb_max; 233 234 dev->vblank->vb_crtcs[crtc].vbl_count += diff; 235 } 236 237 int 238 drm_vblank_get(struct drm_device *dev, int crtc) 239 { 240 struct drm_vblank_info *vbl = dev->vblank; 241 int ret = 0; 242 243 if (dev->irq_enabled == 0) 244 return (EINVAL); 245 246 mtx_enter(&vbl->vb_lock); 247 DPRINTF("%s: %d refs = %d\n", __func__, crtc, 248 vbl->vb_crtcs[crtc].vbl_refs); 249 vbl->vb_crtcs[crtc].vbl_refs++; 250 if (vbl->vb_crtcs[crtc].vbl_refs == 1 && 251 vbl->vb_crtcs[crtc].vbl_enabled == 0) { 252 if ((ret = dev->driver->enable_vblank(dev, crtc)) == 0) { 253 vbl->vb_crtcs[crtc].vbl_enabled = 1; 254 drm_update_vblank_count(dev, crtc); 255 } else { 256 vbl->vb_crtcs[crtc].vbl_refs--; 257 } 258 259 } 260 mtx_leave(&vbl->vb_lock); 261 262 return (ret); 263 } 264 265 void 266 drm_vblank_put(struct drm_device *dev, int crtc) 267 { 268 mtx_enter(&dev->vblank->vb_lock); 269 /* Last user schedules disable */ 270 DPRINTF("%s: %d refs = %d\n", __func__, crtc, 271 dev->vblank->vb_crtcs[crtc].vbl_refs); 272 KASSERT(dev->vblank->vb_crtcs[crtc].vbl_refs > 0); 273 if (--dev->vblank->vb_crtcs[crtc].vbl_refs == 0) 274 timeout_add_sec(&dev->vblank->vb_disable_timer, 5); 275 mtx_leave(&dev->vblank->vb_lock); 276 } 277 278 int 279 drm_modeset_ctl(struct drm_device *dev, void *data, struct drm_file *file_priv) 280 { 281 struct drm_modeset_ctl *modeset = data; 282 struct drm_vblank *vbl; 283 int crtc, ret = 0; 284 285 /* not initialised yet, just noop */ 286 if (dev->vblank == NULL) 287 return (0); 288 289 crtc = modeset->crtc; 290 if (crtc >= dev->vblank->vb_num || crtc < 0) 291 return (EINVAL); 292 293 vbl = &dev->vblank->vb_crtcs[crtc]; 294 295 /* 296 * If interrupts are enabled/disabled between calls to this ioctl then 297 * it can get nasty. So just grab a reference so that the interrupts 298 * keep going through the modeset 299 */ 300 switch (modeset->cmd) { 301 case _DRM_PRE_MODESET: 302 DPRINTF("%s: pre modeset on %d\n", __func__, crtc); 303 if (vbl->vbl_inmodeset == 0) { 304 mtx_enter(&dev->vblank->vb_lock); 305 vbl->vbl_inmodeset = 0x1; 306 mtx_leave(&dev->vblank->vb_lock); 307 if (drm_vblank_get(dev, crtc) == 0) 308 vbl->vbl_inmodeset |= 0x2; 309 } 310 break; 311 case _DRM_POST_MODESET: 312 DPRINTF("%s: post modeset on %d\n", __func__, crtc); 313 if (vbl->vbl_inmodeset) { 314 if (vbl->vbl_inmodeset & 0x2) 315 drm_vblank_put(dev, crtc); 316 vbl->vbl_inmodeset = 0; 317 } 318 break; 319 default: 320 ret = EINVAL; 321 break; 322 } 323 324 return (ret); 325 } 326 327 int 328 drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_priv) 329 { 330 struct timeval now; 331 union drm_wait_vblank *vblwait = data; 332 int ret, flags, crtc, seq; 333 334 if (!dev->irq_enabled || dev->vblank == NULL || 335 vblwait->request.type & _DRM_VBLANK_SIGNAL) 336 return (EINVAL); 337 338 flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK; 339 crtc = flags & _DRM_VBLANK_SECONDARY ? 1 : 0; 340 341 if (crtc >= dev->vblank->vb_num) 342 return (EINVAL); 343 344 if ((ret = drm_vblank_get(dev, crtc)) != 0) 345 return (ret); 346 seq = drm_vblank_count(dev, crtc); 347 348 if (vblwait->request.type & _DRM_VBLANK_RELATIVE) { 349 vblwait->request.sequence += seq; 350 vblwait->request.type &= ~_DRM_VBLANK_RELATIVE; 351 } 352 353 flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK; 354 if ((flags & _DRM_VBLANK_NEXTONMISS) && 355 (seq - vblwait->request.sequence) <= (1<<23)) { 356 vblwait->request.sequence = seq + 1; 357 } 358 359 if (flags & _DRM_VBLANK_EVENT) 360 return (drm_queue_vblank_event(dev, crtc, vblwait, file_priv)); 361 362 DPRINTF("%s: %d waiting on %d, current %d\n", __func__, crtc, 363 vblwait->request.sequence, drm_vblank_count(dev, crtc)); 364 DRM_WAIT_ON(ret, &dev->vblank->vb_crtcs[crtc], &dev->vblank->vb_lock, 365 3 * hz, "drmvblq", ((drm_vblank_count(dev, crtc) - 366 vblwait->request.sequence) <= (1 << 23)) || dev->irq_enabled == 0); 367 368 microtime(&now); 369 vblwait->reply.tval_sec = now.tv_sec; 370 vblwait->reply.tval_usec = now.tv_usec; 371 vblwait->reply.sequence = drm_vblank_count(dev, crtc); 372 DPRINTF("%s: %d done waiting, seq = %d\n", __func__, crtc, 373 vblwait->reply.sequence); 374 375 drm_vblank_put(dev, crtc); 376 return (ret); 377 } 378 379 int 380 drm_queue_vblank_event(struct drm_device *dev, int crtc, 381 union drm_wait_vblank *vblwait, struct drm_file *file_priv) 382 { 383 struct drm_pending_vblank_event *vev; 384 struct timeval now; 385 u_int seq; 386 387 388 vev = drm_calloc(1, sizeof(*vev)); 389 if (vev == NULL) 390 return (ENOMEM); 391 392 vev->event.base.type = DRM_EVENT_VBLANK; 393 vev->event.base.length = sizeof(vev->event); 394 vev->event.user_data = vblwait->request.signal; 395 vev->base.event = &vev->event.base; 396 vev->base.file_priv = file_priv; 397 vev->base.destroy = (void (*) (struct drm_pending_event *))drm_free; 398 399 microtime(&now); 400 401 mtx_enter(&dev->event_lock); 402 if (file_priv->event_space < sizeof(vev->event)) { 403 mtx_leave(&dev->event_lock); 404 drm_free(vev); 405 return (ENOMEM); 406 } 407 408 409 seq = drm_vblank_count(dev, crtc); 410 file_priv->event_space -= sizeof(vev->event); 411 412 DPRINTF("%s: queueing event %d on crtc %d\n", __func__, seq, crtc); 413 414 if ((vblwait->request.type & _DRM_VBLANK_NEXTONMISS) && 415 (seq - vblwait->request.sequence) <= (1 << 23)) { 416 vblwait->request.sequence = seq + 1; 417 vblwait->reply.sequence = vblwait->request.sequence; 418 } 419 420 vev->event.sequence = vblwait->request.sequence; 421 if ((seq - vblwait->request.sequence) <= (1 << 23)) { 422 vev->event.tv_sec = now.tv_sec; 423 vev->event.tv_usec = now.tv_usec; 424 DPRINTF("%s: already passed, dequeuing: crtc %d, value %d\n", 425 __func__, crtc, seq); 426 drm_vblank_put(dev, crtc); 427 TAILQ_INSERT_TAIL(&file_priv->evlist, &vev->base, link); 428 wakeup(&file_priv->evlist); 429 selwakeup(&file_priv->rsel); 430 } else { 431 TAILQ_INSERT_TAIL(&dev->vblank->vb_crtcs[crtc].vbl_events, 432 &vev->base, link); 433 } 434 mtx_leave(&dev->event_lock); 435 436 return (0); 437 } 438 439 void 440 drm_handle_vblank_events(struct drm_device *dev, int crtc) 441 { 442 struct drmevlist *list; 443 struct drm_pending_event *ev, *tmp; 444 struct drm_pending_vblank_event *vev; 445 struct timeval now; 446 u_int seq; 447 448 list = &dev->vblank->vb_crtcs[crtc].vbl_events; 449 microtime(&now); 450 seq = drm_vblank_count(dev, crtc); 451 452 mtx_enter(&dev->event_lock); 453 for (ev = TAILQ_FIRST(list); ev != TAILQ_END(list); ev = tmp) { 454 tmp = TAILQ_NEXT(ev, link); 455 456 vev = (struct drm_pending_vblank_event *)ev; 457 458 if ((seq - vev->event.sequence) > (1 << 23)) 459 continue; 460 DPRINTF("%s: got vblank event on crtc %d, value %d\n", 461 __func__, crtc, seq); 462 463 vev->event.sequence = seq; 464 vev->event.tv_sec = now.tv_sec; 465 vev->event.tv_usec = now.tv_usec; 466 drm_vblank_put(dev, crtc); 467 TAILQ_REMOVE(list, ev, link); 468 TAILQ_INSERT_TAIL(&ev->file_priv->evlist, ev, link); 469 wakeup(&ev->file_priv->evlist); 470 selwakeup(&ev->file_priv->rsel); 471 } 472 mtx_leave(&dev->event_lock); 473 } 474 475 void 476 drm_handle_vblank(struct drm_device *dev, int crtc) 477 { 478 /* 479 * XXX if we had proper atomic operations this mutex wouldn't 480 * XXX need to be held. 481 */ 482 mtx_enter(&dev->vblank->vb_lock); 483 dev->vblank->vb_crtcs[crtc].vbl_count++; 484 wakeup(&dev->vblank->vb_crtcs[crtc]); 485 mtx_leave(&dev->vblank->vb_lock); 486 drm_handle_vblank_events(dev, crtc); 487 } 488