xref: /openbsd-src/sys/dev/video.c (revision 25c4e8bd056e974b28f4a0ffd39d76c190a56013)
1 /*	$OpenBSD: video.c,v 1.57 2022/07/02 08:50:41 visa Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Robert Nagy <robert@openbsd.org>
5  * Copyright (c) 2008 Marcus Glocker <mglocker@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/param.h>
21 #include <sys/systm.h>
22 #include <sys/errno.h>
23 #include <sys/ioctl.h>
24 #include <sys/fcntl.h>
25 #include <sys/device.h>
26 #include <sys/vnode.h>
27 #include <sys/kernel.h>
28 #include <sys/malloc.h>
29 #include <sys/conf.h>
30 #include <sys/proc.h>
31 #include <sys/videoio.h>
32 
33 #include <dev/video_if.h>
34 
35 #include <uvm/uvm_extern.h>
36 
37 #ifdef VIDEO_DEBUG
38 int video_debug = 1;
39 #define DPRINTF(l, x...) do { if ((l) <= video_debug) printf(x); } while (0)
40 #else
41 #define DPRINTF(l, x...)
42 #endif
43 
44 struct video_softc {
45 	struct device		 dev;
46 	void			*hw_hdl;	/* hardware driver handle */
47 	struct device		*sc_dev;	/* hardware device struct */
48 	const struct video_hw_if *hw_if;	/* hardware interface */
49 	char			 sc_dying;	/* device detached */
50 	struct process		*sc_owner;	/* owner process */
51 	uint8_t			 sc_open;	/* device opened */
52 
53 	int			 sc_fsize;
54 	uint8_t			*sc_fbuffer;
55 	caddr_t			 sc_fbuffer_mmap;
56 	size_t			 sc_fbufferlen;
57 	int			 sc_vidmode;	/* access mode */
58 #define		VIDMODE_NONE	0
59 #define		VIDMODE_MMAP	1
60 #define		VIDMODE_READ	2
61 	int			 sc_frames_ready;
62 
63 	struct selinfo		 sc_rsel;	/* read selector */
64 };
65 
66 int	videoprobe(struct device *, void *, void *);
67 void	videoattach(struct device *, struct device *, void *);
68 int	videodetach(struct device *, int);
69 int	videoactivate(struct device *, int);
70 int	videoprint(void *, const char *);
71 
72 void	video_intr(void *);
73 int	video_stop(struct video_softc *);
74 int	video_claim(struct video_softc *, struct process *);
75 
76 const struct cfattach video_ca = {
77 	sizeof(struct video_softc), videoprobe, videoattach,
78 	videodetach, videoactivate
79 };
80 
81 struct cfdriver video_cd = {
82 	NULL, "video", DV_DULL
83 };
84 
85 /*
86  * Global flag to control if video recording is enabled by kern.video.record.
87  */
88 int video_record_enable = 0;
89 
90 int
91 videoprobe(struct device *parent, void *match, void *aux)
92 {
93 	return (1);
94 }
95 
96 void
97 videoattach(struct device *parent, struct device *self, void *aux)
98 {
99 	struct video_softc *sc = (void *)self;
100 	struct video_attach_args *sa = aux;
101 
102 	printf("\n");
103 	sc->hw_if = sa->hwif;
104 	sc->hw_hdl = sa->hdl;
105 	sc->sc_dev = parent;
106 	sc->sc_fbufferlen = 0;
107 	sc->sc_owner = NULL;
108 
109 	if (sc->hw_if->get_bufsize)
110 		sc->sc_fbufferlen = (sc->hw_if->get_bufsize)(sc->hw_hdl);
111 	if (sc->sc_fbufferlen == 0) {
112 		printf("video: could not request frame buffer size\n");
113 		return;
114 	}
115 
116 	sc->sc_fbuffer = malloc(sc->sc_fbufferlen, M_DEVBUF, M_NOWAIT);
117 	if (sc->sc_fbuffer == NULL) {
118 		printf("video: could not allocate frame buffer\n");
119 		return;
120 	}
121 }
122 
123 int
124 videoopen(dev_t dev, int flags, int fmt, struct proc *p)
125 {
126 	int unit = VIDEOUNIT(dev);
127 	struct video_softc *sc;
128 	int error = 0;
129 
130 	KERNEL_ASSERT_LOCKED();
131 
132 	if (unit >= video_cd.cd_ndevs ||
133 	    (sc = video_cd.cd_devs[unit]) == NULL ||
134 	     sc->hw_if == NULL)
135 		return (ENXIO);
136 
137 	if (sc->sc_open) {
138 		DPRINTF(1, "%s: device already open\n", __func__);
139 		return (0);
140 	}
141 
142 	sc->sc_vidmode = VIDMODE_NONE;
143 	sc->sc_frames_ready = 0;
144 
145 	if (sc->hw_if->open != NULL) {
146 		error = sc->hw_if->open(sc->hw_hdl, flags, &sc->sc_fsize,
147 		    sc->sc_fbuffer, video_intr, sc);
148 	}
149 	if (error == 0) {
150 		sc->sc_open = 1;
151 		DPRINTF(1, "%s: set device to open\n", __func__);
152 	}
153 
154 	return (error);
155 }
156 
157 int
158 videoclose(dev_t dev, int flags, int fmt, struct proc *p)
159 {
160 	struct video_softc *sc;
161 	int error = 0;
162 
163 	KERNEL_ASSERT_LOCKED();
164 
165 	DPRINTF(1, "%s: last close\n", __func__);
166 
167 	sc = video_cd.cd_devs[VIDEOUNIT(dev)];
168 
169 	error = video_stop(sc);
170 	sc->sc_open = 0;
171 
172 	return (error);
173 }
174 
175 int
176 videoread(dev_t dev, struct uio *uio, int ioflag)
177 {
178 	int unit = VIDEOUNIT(dev);
179 	struct video_softc *sc;
180 	int error;
181 	size_t size;
182 
183 	KERNEL_ASSERT_LOCKED();
184 
185 	if (unit >= video_cd.cd_ndevs ||
186 	    (sc = video_cd.cd_devs[unit]) == NULL)
187 		return (ENXIO);
188 
189 	if (sc->sc_dying)
190 		return (EIO);
191 
192 	if (sc->sc_vidmode == VIDMODE_MMAP)
193 		return (EBUSY);
194 
195 	if ((error = video_claim(sc, curproc->p_p)))
196 		return (error);
197 
198 	/* start the stream if not already started */
199 	if (sc->sc_vidmode == VIDMODE_NONE && sc->hw_if->start_read) {
200  		error = sc->hw_if->start_read(sc->hw_hdl);
201  		if (error)
202  			return (error);
203 		sc->sc_vidmode = VIDMODE_READ;
204  	}
205 
206 	DPRINTF(1, "resid=%zu\n", uio->uio_resid);
207 
208 	if (sc->sc_frames_ready < 1) {
209 		/* block userland read until a frame is ready */
210 		error = tsleep_nsec(sc, PWAIT | PCATCH, "vid_rd", INFSLP);
211 		if (sc->sc_dying)
212 			error = EIO;
213 		if (error)
214 			return (error);
215 	}
216 
217 	/* move no more than 1 frame to userland, as per specification */
218 	size = ulmin(uio->uio_resid, sc->sc_fsize);
219 	if (!video_record_enable)
220 		bzero(sc->sc_fbuffer, size);
221 	error = uiomove(sc->sc_fbuffer, size, uio);
222 	sc->sc_frames_ready--;
223 	if (error)
224 		return (error);
225 
226 	DPRINTF(1, "uiomove successfully done (%zu bytes)\n", size);
227 
228 	return (0);
229 }
230 
231 int
232 videoioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct proc *p)
233 {
234 	int unit = VIDEOUNIT(dev);
235 	struct video_softc *sc;
236 	struct v4l2_buffer *vb = (struct v4l2_buffer *)data;
237 	int error;
238 
239 	KERNEL_ASSERT_LOCKED();
240 
241 	if (unit >= video_cd.cd_ndevs ||
242 	    (sc = video_cd.cd_devs[unit]) == NULL || sc->hw_if == NULL)
243 		return (ENXIO);
244 
245 	DPRINTF(3, "video_ioctl(%zu, '%c', %zu)\n",
246 	    IOCPARM_LEN(cmd), (int) IOCGROUP(cmd), cmd & 0xff);
247 
248 	error = EOPNOTSUPP;
249 	switch (cmd) {
250 	case VIDIOC_G_CTRL:
251 		if (sc->hw_if->g_ctrl)
252 			error = (sc->hw_if->g_ctrl)(sc->hw_hdl,
253 			    (struct v4l2_control *)data);
254 		break;
255 	case VIDIOC_S_CTRL:
256 		if (sc->hw_if->s_ctrl)
257 			error = (sc->hw_if->s_ctrl)(sc->hw_hdl,
258 			    (struct v4l2_control *)data);
259 		break;
260 	default:
261 		error = (ENOTTY);
262 	}
263 	if (error != ENOTTY)
264 		return (error);
265 
266 	if ((error = video_claim(sc, p->p_p)))
267 		return (error);
268 
269 	/*
270 	 * The following IOCTLs can only be called by the device owner.
271 	 * For further shared IOCTLs please move it up.
272 	 */
273 	error = EOPNOTSUPP;
274 	switch (cmd) {
275 	case VIDIOC_QUERYCAP:
276 		if (sc->hw_if->querycap)
277 			error = (sc->hw_if->querycap)(sc->hw_hdl,
278 			    (struct v4l2_capability *)data);
279 		break;
280 	case VIDIOC_ENUM_FMT:
281 		if (sc->hw_if->enum_fmt)
282 			error = (sc->hw_if->enum_fmt)(sc->hw_hdl,
283 			    (struct v4l2_fmtdesc *)data);
284 		break;
285 	case VIDIOC_ENUM_FRAMESIZES:
286 		if (sc->hw_if->enum_fsizes)
287 			error = (sc->hw_if->enum_fsizes)(sc->hw_hdl,
288 			    (struct v4l2_frmsizeenum *)data);
289 		break;
290 	case VIDIOC_ENUM_FRAMEINTERVALS:
291 		if (sc->hw_if->enum_fivals)
292 			error = (sc->hw_if->enum_fivals)(sc->hw_hdl,
293 			    (struct v4l2_frmivalenum *)data);
294 		break;
295 	case VIDIOC_S_FMT:
296 		if (!(flags & FWRITE))
297 			return (EACCES);
298 		if (sc->hw_if->s_fmt)
299 			error = (sc->hw_if->s_fmt)(sc->hw_hdl,
300 			    (struct v4l2_format *)data);
301 		break;
302 	case VIDIOC_G_FMT:
303 		if (sc->hw_if->g_fmt)
304 			error = (sc->hw_if->g_fmt)(sc->hw_hdl,
305 			    (struct v4l2_format *)data);
306 		break;
307 	case VIDIOC_S_PARM:
308 		if (sc->hw_if->s_parm)
309 			error = (sc->hw_if->s_parm)(sc->hw_hdl,
310 			    (struct v4l2_streamparm *)data);
311 		break;
312 	case VIDIOC_G_PARM:
313 		if (sc->hw_if->g_parm)
314 			error = (sc->hw_if->g_parm)(sc->hw_hdl,
315 			    (struct v4l2_streamparm *)data);
316 		break;
317 	case VIDIOC_ENUMINPUT:
318 		if (sc->hw_if->enum_input)
319 			error = (sc->hw_if->enum_input)(sc->hw_hdl,
320 			    (struct v4l2_input *)data);
321 		break;
322 	case VIDIOC_S_INPUT:
323 		if (sc->hw_if->s_input)
324 			error = (sc->hw_if->s_input)(sc->hw_hdl,
325 			    (int)*data);
326 		break;
327 	case VIDIOC_G_INPUT:
328 		if (sc->hw_if->g_input)
329 			error = (sc->hw_if->g_input)(sc->hw_hdl,
330 			    (int *)data);
331 		break;
332 	case VIDIOC_REQBUFS:
333 		if (sc->hw_if->reqbufs)
334 			error = (sc->hw_if->reqbufs)(sc->hw_hdl,
335 			    (struct v4l2_requestbuffers *)data);
336 		break;
337 	case VIDIOC_QUERYBUF:
338 		if (sc->hw_if->querybuf)
339 			error = (sc->hw_if->querybuf)(sc->hw_hdl,
340 			    (struct v4l2_buffer *)data);
341 		break;
342 	case VIDIOC_QBUF:
343 		if (sc->hw_if->qbuf)
344 			error = (sc->hw_if->qbuf)(sc->hw_hdl,
345 			    (struct v4l2_buffer *)data);
346 		break;
347 	case VIDIOC_DQBUF:
348 		if (!sc->hw_if->dqbuf)
349 			break;
350 		/* should have called mmap() before now */
351 		if (sc->sc_vidmode != VIDMODE_MMAP) {
352 			error = EINVAL;
353 			break;
354 		}
355 		error = (sc->hw_if->dqbuf)(sc->hw_hdl,
356 		    (struct v4l2_buffer *)data);
357 		if (!video_record_enable)
358 			bzero(sc->sc_fbuffer_mmap + vb->m.offset, vb->length);
359 		sc->sc_frames_ready--;
360 		break;
361 	case VIDIOC_STREAMON:
362 		if (sc->hw_if->streamon)
363 			error = (sc->hw_if->streamon)(sc->hw_hdl,
364 			    (int)*data);
365 		break;
366 	case VIDIOC_STREAMOFF:
367 		if (sc->hw_if->streamoff)
368 			error = (sc->hw_if->streamoff)(sc->hw_hdl,
369 			    (int)*data);
370 		if (!error) {
371 			/* Release device ownership and streaming buffers. */
372 			error = video_stop(sc);
373 		}
374 		break;
375 	case VIDIOC_TRY_FMT:
376 		if (sc->hw_if->try_fmt)
377 			error = (sc->hw_if->try_fmt)(sc->hw_hdl,
378 			    (struct v4l2_format *)data);
379 		break;
380 	case VIDIOC_QUERYCTRL:
381 		if (sc->hw_if->queryctrl)
382 			error = (sc->hw_if->queryctrl)(sc->hw_hdl,
383 			    (struct v4l2_queryctrl *)data);
384 		break;
385 	default:
386 		error = (ENOTTY);
387 	}
388 
389 	return (error);
390 }
391 
392 paddr_t
393 videommap(dev_t dev, off_t off, int prot)
394 {
395 	int unit = VIDEOUNIT(dev);
396 	struct video_softc *sc;
397 	caddr_t p;
398 	paddr_t pa;
399 
400 	KERNEL_ASSERT_LOCKED();
401 
402 	DPRINTF(2, "%s: off=%lld, prot=%d\n", __func__, off, prot);
403 
404 	if (unit >= video_cd.cd_ndevs ||
405 	    (sc = video_cd.cd_devs[unit]) == NULL)
406 		return (-1);
407 
408 	if (sc->sc_dying)
409 		return (-1);
410 
411 	if (sc->hw_if->mappage == NULL)
412 		return (-1);
413 
414 	p = sc->hw_if->mappage(sc->hw_hdl, off, prot);
415 	if (p == NULL)
416 		return (-1);
417 	if (pmap_extract(pmap_kernel(), (vaddr_t)p, &pa) == FALSE)
418 		panic("videommap: invalid page");
419 	sc->sc_vidmode = VIDMODE_MMAP;
420 
421 	/* store frame buffer base address for later blanking */
422 	if (off == 0)
423 		sc->sc_fbuffer_mmap = p;
424 
425 	return (pa);
426 }
427 
428 void
429 filt_videodetach(struct knote *kn)
430 {
431 	struct video_softc *sc = kn->kn_hook;
432 	int s;
433 
434 	s = splhigh();
435 	klist_remove_locked(&sc->sc_rsel.si_note, kn);
436 	splx(s);
437 }
438 
439 int
440 filt_videoread(struct knote *kn, long hint)
441 {
442 	struct video_softc *sc = kn->kn_hook;
443 
444 	if (sc->sc_frames_ready > 0)
445 		return (1);
446 
447 	return (0);
448 }
449 
450 const struct filterops video_filtops = {
451 	.f_flags	= FILTEROP_ISFD,
452 	.f_attach	= NULL,
453 	.f_detach	= filt_videodetach,
454 	.f_event	= filt_videoread,
455 };
456 
457 int
458 videokqfilter(dev_t dev, struct knote *kn)
459 {
460 	int unit = VIDEOUNIT(dev);
461 	struct video_softc *sc;
462 	int s, error;
463 
464 	KERNEL_ASSERT_LOCKED();
465 
466 	if (unit >= video_cd.cd_ndevs ||
467 	    (sc = video_cd.cd_devs[unit]) == NULL)
468 		return (ENXIO);
469 
470 	if (sc->sc_dying)
471 		return (ENXIO);
472 
473 	switch (kn->kn_filter) {
474 	case EVFILT_READ:
475 		kn->kn_fop = &video_filtops;
476 		kn->kn_hook = sc;
477 		break;
478 	default:
479 		return (EINVAL);
480 	}
481 
482 	if ((error = video_claim(sc, curproc->p_p)))
483 		return (error);
484 
485 	/*
486 	 * Start the stream in read() mode if not already started.  If
487 	 * the user wanted mmap() mode, he should have called mmap()
488 	 * before now.
489 	 */
490 	if (sc->sc_vidmode == VIDMODE_NONE && sc->hw_if->start_read) {
491 		if (sc->hw_if->start_read(sc->hw_hdl))
492 			return (ENXIO);
493 		sc->sc_vidmode = VIDMODE_READ;
494 	}
495 
496 	s = splhigh();
497 	klist_insert_locked(&sc->sc_rsel.si_note, kn);
498 	splx(s);
499 
500 	return (0);
501 }
502 
503 int
504 video_submatch(struct device *parent, void *match, void *aux)
505 {
506         struct cfdata *cf = match;
507 
508 	return (cf->cf_driver == &video_cd);
509 }
510 
511 /*
512  * Called from hardware driver. This is where the MI video driver gets
513  * probed/attached to the hardware driver
514  */
515 struct device *
516 video_attach_mi(const struct video_hw_if *rhwp, void *hdlp, struct device *dev)
517 {
518 	struct video_attach_args arg;
519 
520 	arg.hwif = rhwp;
521 	arg.hdl = hdlp;
522 	return (config_found_sm(dev, &arg, videoprint, video_submatch));
523 }
524 
525 void
526 video_intr(void *addr)
527 {
528 	struct video_softc *sc = (struct video_softc *)addr;
529 
530 	DPRINTF(3, "video_intr sc=%p\n", sc);
531 	if (sc->sc_vidmode != VIDMODE_NONE)
532 		sc->sc_frames_ready++;
533 	else
534 		printf("%s: interrupt but no streams!\n", __func__);
535 	if (sc->sc_vidmode == VIDMODE_READ)
536 		wakeup(sc);
537 	selwakeup(&sc->sc_rsel);
538 }
539 
540 int
541 video_stop(struct video_softc *sc)
542 {
543 	int error = 0;
544 
545 	DPRINTF(1, "%s: stream close\n", __func__);
546 
547 	if (sc->hw_if->close != NULL)
548 		error = sc->hw_if->close(sc->hw_hdl);
549 
550 	sc->sc_vidmode = VIDMODE_NONE;
551 	sc->sc_frames_ready = 0;
552 	sc->sc_owner = NULL;
553 
554 	return (error);
555 }
556 
557 int
558 video_claim(struct video_softc *sc, struct process *pr)
559 {
560 	if (sc->sc_owner != NULL && sc->sc_owner != pr) {
561 		DPRINTF(1, "%s: already owned=%p\n", __func__, sc->sc_owner);
562 		return (EBUSY);
563 	}
564 
565 	if (sc->sc_owner == NULL) {
566 		sc->sc_owner = pr;
567 		DPRINTF(1, "%s: new owner=%p\n", __func__, sc->sc_owner);
568 	}
569 
570 	return (0);
571 }
572 
573 int
574 videoprint(void *aux, const char *pnp)
575 {
576 	if (pnp != NULL)
577 		printf("video at %s", pnp);
578 	return (UNCONF);
579 }
580 
581 int
582 videodetach(struct device *self, int flags)
583 {
584 	struct video_softc *sc = (struct video_softc *)self;
585 	int s, maj, mn;
586 
587 	/* locate the major number */
588 	for (maj = 0; maj < nchrdev; maj++)
589 		if (cdevsw[maj].d_open == videoopen)
590 			break;
591 
592 	/* Nuke the vnodes for any open instances (calls close). */
593 	mn = self->dv_unit;
594 	vdevgone(maj, mn, mn, VCHR);
595 
596 	s = splhigh();
597 	klist_invalidate(&sc->sc_rsel.si_note);
598 	splx(s);
599 
600 	free(sc->sc_fbuffer, M_DEVBUF, sc->sc_fbufferlen);
601 
602 	return (0);
603 }
604 
605 int
606 videoactivate(struct device *self, int act)
607 {
608 	struct video_softc *sc = (struct video_softc *)self;
609 
610 	switch (act) {
611 	case DVACT_DEACTIVATE:
612 		sc->sc_dying = 1;
613 		break;
614 	}
615 	return (0);
616 }
617