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