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 /** @file drm_irq.c 29 * Support code for handling setup/teardown of interrupt handlers and 30 * handing interrupt handlers off to the drivers. 31 */ 32 33 #include <sys/workq.h> 34 35 #include "drmP.h" 36 #include "drm.h" 37 38 void drm_update_vblank_count(struct drm_device *, int); 39 void vblank_disable(void *); 40 41 int 42 drm_irq_by_busid(struct drm_device *dev, void *data, struct drm_file *file_priv) 43 { 44 struct drm_irq_busid *irq = data; 45 46 /* 47 * This is only ever called by root as part of a stupid interface. 48 * just hand over the irq without checking the busid. If all clients 49 * can be forced to use interface 1.2 then this can die. 50 */ 51 irq->irq = dev->irq; 52 53 DRM_DEBUG("%d:%d:%d => IRQ %d\n", irq->busnum, irq->devnum, 54 irq->funcnum, irq->irq); 55 56 return 0; 57 } 58 59 int 60 drm_irq_install(struct drm_device *dev) 61 { 62 int ret; 63 64 if (dev->irq == 0 || dev->dev_private == NULL) 65 return (EINVAL); 66 67 DRM_DEBUG("irq=%d\n", dev->irq); 68 69 DRM_LOCK(); 70 if (dev->irq_enabled) { 71 DRM_UNLOCK(); 72 return (EBUSY); 73 } 74 dev->irq_enabled = 1; 75 DRM_UNLOCK(); 76 77 if ((ret = dev->driver->irq_install(dev)) != 0) 78 goto err; 79 80 return (0); 81 err: 82 DRM_LOCK(); 83 dev->irq_enabled = 0; 84 DRM_UNLOCK(); 85 return (ret); 86 } 87 88 int 89 drm_irq_uninstall(struct drm_device *dev) 90 { 91 int i; 92 93 DRM_LOCK(); 94 if (!dev->irq_enabled) { 95 DRM_UNLOCK(); 96 return (EINVAL); 97 } 98 99 dev->irq_enabled = 0; 100 DRM_UNLOCK(); 101 102 /* 103 * Ick. we're about to turn of vblanks, so make sure anyone waiting 104 * on them gets woken up. Also make sure we update state correctly 105 * so that we can continue refcounting correctly. 106 */ 107 if (dev->vblank != NULL) { 108 mtx_enter(&dev->vblank->vb_lock); 109 for (i = 0; i < dev->vblank->vb_num; i++) { 110 wakeup(&dev->vblank->vb_crtcs[i]); 111 dev->vblank->vb_crtcs[i].vbl_enabled = 0; 112 dev->vblank->vb_crtcs[i].vbl_last = 113 dev->driver->get_vblank_counter(dev, i); 114 } 115 mtx_leave(&dev->vblank->vb_lock); 116 } 117 118 DRM_DEBUG("irq=%d\n", dev->irq); 119 120 dev->driver->irq_uninstall(dev); 121 122 return (0); 123 } 124 125 int 126 drm_control(struct drm_device *dev, void *data, struct drm_file *file_priv) 127 { 128 struct drm_control *ctl = data; 129 130 /* Handle drivers who used to require IRQ setup no longer does. */ 131 if (!(dev->driver->flags & DRIVER_IRQ)) 132 return (0); 133 134 switch (ctl->func) { 135 case DRM_INST_HANDLER: 136 if (dev->if_version < DRM_IF_VERSION(1, 2) && 137 ctl->irq != dev->irq) 138 return (EINVAL); 139 return (drm_irq_install(dev)); 140 case DRM_UNINST_HANDLER: 141 return (drm_irq_uninstall(dev)); 142 default: 143 return (EINVAL); 144 } 145 } 146 147 void 148 vblank_disable(void *arg) 149 { 150 struct drm_device *dev = (struct drm_device*)arg; 151 struct drm_vblank_info *vbl = dev->vblank; 152 struct drm_vblank *crtc; 153 int i; 154 155 mtx_enter(&vbl->vb_lock); 156 if (!vbl->vb_disable_allowed) 157 goto out; 158 159 for (i = 0; i < vbl->vb_num; i++) { 160 crtc = &vbl->vb_crtcs[i]; 161 162 if (crtc->vbl_refs == 0 && crtc->vbl_enabled) { 163 crtc->vbl_last = 164 dev->driver->get_vblank_counter(dev, i); 165 dev->driver->disable_vblank(dev, i); 166 crtc->vbl_enabled = 0; 167 } 168 } 169 out: 170 mtx_leave(&vbl->vb_lock); 171 } 172 173 void 174 drm_vblank_cleanup(struct drm_device *dev) 175 { 176 if (dev->vblank == NULL) 177 return; /* not initialised */ 178 179 timeout_del(&dev->vblank->vb_disable_timer); 180 181 vblank_disable(dev); 182 183 drm_free(dev->vblank); 184 dev->vblank = NULL; 185 } 186 187 int 188 drm_vblank_init(struct drm_device *dev, int num_crtcs) 189 { 190 dev->vblank = malloc(sizeof(*dev->vblank) + (num_crtcs * 191 sizeof(struct drm_vblank)), M_DRM, M_WAITOK | M_CANFAIL | M_ZERO); 192 if (dev->vblank == NULL) 193 return (ENOMEM); 194 195 dev->vblank->vb_num = num_crtcs; 196 mtx_init(&dev->vblank->vb_lock, IPL_BIO); 197 timeout_set(&dev->vblank->vb_disable_timer, vblank_disable, dev); 198 199 return (0); 200 } 201 202 u_int32_t 203 drm_vblank_count(struct drm_device *dev, int crtc) 204 { 205 return (dev->vblank->vb_crtcs[crtc].vbl_count); 206 } 207 208 void 209 drm_update_vblank_count(struct drm_device *dev, int crtc) 210 { 211 u_int32_t cur_vblank, diff; 212 213 /* 214 * Interrupt was disabled prior to this call, so deal with counter wrap 215 * note that we may have lost a full vb_max events if 216 * the register is small or the interrupts were off for a long time. 217 */ 218 cur_vblank = dev->driver->get_vblank_counter(dev, crtc); 219 diff = cur_vblank - dev->vblank->vb_crtcs[crtc].vbl_last; 220 if (cur_vblank < dev->vblank->vb_crtcs[crtc].vbl_last) 221 diff += dev->vblank->vb_max; 222 223 dev->vblank->vb_crtcs[crtc].vbl_count += diff; 224 } 225 226 int 227 drm_vblank_get(struct drm_device *dev, int crtc) 228 { 229 struct drm_vblank_info *vbl = dev->vblank; 230 int ret = 0; 231 232 if (dev->irq_enabled == 0) 233 return (EINVAL); 234 235 mtx_enter(&vbl->vb_lock); 236 vbl->vb_crtcs[crtc].vbl_refs++; 237 if (vbl->vb_crtcs[crtc].vbl_refs == 1 && 238 vbl->vb_crtcs[crtc].vbl_enabled == 0) { 239 if ((ret = dev->driver->enable_vblank(dev, crtc)) == 0) { 240 vbl->vb_crtcs[crtc].vbl_enabled = 1; 241 drm_update_vblank_count(dev, crtc); 242 } else { 243 vbl->vb_crtcs[crtc].vbl_refs--; 244 } 245 246 } 247 mtx_leave(&vbl->vb_lock); 248 249 return (ret); 250 } 251 252 void 253 drm_vblank_put(struct drm_device *dev, int crtc) 254 { 255 mtx_enter(&dev->vblank->vb_lock); 256 /* Last user schedules disable */ 257 if (--dev->vblank->vb_crtcs[crtc].vbl_refs == 0) 258 timeout_add_sec(&dev->vblank->vb_disable_timer, 5); 259 mtx_leave(&dev->vblank->vb_lock); 260 } 261 262 int 263 drm_modeset_ctl(struct drm_device *dev, void *data, struct drm_file *file_priv) 264 { 265 struct drm_modeset_ctl *modeset = data; 266 int crtc, ret = 0; 267 struct drm_vblank *vbl; 268 269 /* not initialised yet, just noop */ 270 if (dev->vblank == NULL) 271 goto out; 272 273 crtc = modeset->crtc; 274 if (crtc >= dev->vblank->vb_num) { 275 ret = EINVAL; 276 goto out; 277 } 278 279 vbl = &dev->vblank->vb_crtcs[crtc]; 280 281 /* 282 * If interrupts are enabled/disabled between calls to this ioctl then 283 * it can get nasty. So just grab a reference so that the interrupts 284 * keep going through the modeset 285 */ 286 switch (modeset->cmd) { 287 case _DRM_PRE_MODESET: 288 if (vbl->vbl_inmodeset == 0) { 289 mtx_enter(&dev->vblank->vb_lock); 290 vbl->vbl_inmodeset = 0x1; 291 mtx_leave(&dev->vblank->vb_lock); 292 if (drm_vblank_get(dev, crtc) == 0) 293 vbl->vbl_inmodeset |= 0x2; 294 } 295 break; 296 case _DRM_POST_MODESET: 297 if (vbl->vbl_inmodeset) { 298 mtx_enter(&dev->vblank->vb_lock); 299 dev->vblank->vb_disable_allowed = 1; 300 mtx_leave(&dev->vblank->vb_lock); 301 if (vbl->vbl_inmodeset & 0x2) 302 drm_vblank_put(dev, crtc); 303 vbl->vbl_inmodeset = 0; 304 } 305 break; 306 default: 307 ret = EINVAL; 308 break; 309 } 310 311 out: 312 return (ret); 313 } 314 315 int 316 drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_priv) 317 { 318 struct timeval now; 319 union drm_wait_vblank *vblwait = data; 320 int ret, flags, crtc, seq; 321 322 if (!dev->irq_enabled || dev->vblank == NULL || 323 vblwait->request.type & _DRM_VBLANK_SIGNAL) 324 return (EINVAL); 325 326 flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK; 327 crtc = flags & _DRM_VBLANK_SECONDARY ? 1 : 0; 328 329 if (crtc >= dev->vblank->vb_num) 330 return (EINVAL); 331 332 if ((ret = drm_vblank_get(dev, crtc)) != 0) 333 return (ret); 334 seq = drm_vblank_count(dev,crtc); 335 336 if (vblwait->request.type & _DRM_VBLANK_RELATIVE) { 337 vblwait->request.sequence += seq; 338 vblwait->request.type &= ~_DRM_VBLANK_RELATIVE; 339 } 340 341 flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK; 342 if ((flags & _DRM_VBLANK_NEXTONMISS) && 343 (seq - vblwait->request.sequence) <= (1<<23)) { 344 vblwait->request.sequence = seq + 1; 345 } 346 347 DRM_WAIT_ON(ret, &dev->vblank->vb_crtcs[crtc], &dev->vblank->vb_lock, 348 3 * hz, "drmvblq", ((drm_vblank_count(dev, crtc) - 349 vblwait->request.sequence) <= (1 << 23)) || dev->irq_enabled == 0); 350 351 microtime(&now); 352 vblwait->reply.tval_sec = now.tv_sec; 353 vblwait->reply.tval_usec = now.tv_usec; 354 vblwait->reply.sequence = drm_vblank_count(dev, crtc); 355 356 drm_vblank_put(dev, crtc); 357 return (ret); 358 } 359 360 void 361 drm_handle_vblank(struct drm_device *dev, int crtc) 362 { 363 dev->vblank->vb_crtcs[crtc].vbl_count++; 364 wakeup(&dev->vblank->vb_crtcs[crtc]); 365 } 366