xref: /openbsd-src/sys/dev/pci/drm/drm_irq.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
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