jcs's openbsd hax
openbsd
1/* $OpenBSD: video.c,v 1.61 2025/04/15 06:44:37 kirill 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/event.h>
24#include <sys/ioctl.h>
25#include <sys/fcntl.h>
26#include <sys/device.h>
27#include <sys/vnode.h>
28#include <sys/kernel.h>
29#include <sys/malloc.h>
30#include <sys/mutex.h>
31#include <sys/conf.h>
32#include <sys/proc.h>
33#include <sys/videoio.h>
34
35#include <dev/video_if.h>
36
37#include <uvm/uvm_extern.h>
38
39/*
40 * Locks used to protect struct members and global data
41 * a atomic
42 * m sc_mtx
43 */
44
45#ifdef VIDEO_DEBUG
46int video_debug = 1;
47#define DPRINTF(l, x...) do { if ((l) <= video_debug) printf(x); } while (0)
48#else
49#define DPRINTF(l, x...)
50#endif
51
52struct video_softc {
53 struct device dev;
54 void *hw_hdl; /* hardware driver handle */
55 struct device *sc_dev; /* hardware device struct */
56 const struct video_hw_if *hw_if; /* hardware interface */
57 char sc_dying; /* device detached */
58 struct process *sc_owner; /* owner process */
59 uint8_t sc_open; /* device opened */
60
61 struct mutex sc_mtx;
62 int sc_fsize;
63 uint8_t *sc_fbuffer;
64 caddr_t sc_fbuffer_mmap;
65 size_t sc_fbufferlen;
66 int sc_vidmode; /* access mode */
67#define VIDMODE_NONE 0
68#define VIDMODE_MMAP 1
69#define VIDMODE_READ 2
70 int sc_frames_ready; /* [m] */
71
72 struct klist sc_rklist; /* [m] read selector */
73};
74
75int videoprobe(struct device *, void *, void *);
76void videoattach(struct device *, struct device *, void *);
77int videodetach(struct device *, int);
78int videoactivate(struct device *, int);
79int videoprint(void *, const char *);
80
81void video_intr(void *);
82int video_stop(struct video_softc *);
83int video_claim(struct video_softc *, struct process *);
84
85const struct cfattach video_ca = {
86 sizeof(struct video_softc), videoprobe, videoattach,
87 videodetach, videoactivate
88};
89
90struct cfdriver video_cd = {
91 NULL, "video", DV_DULL
92};
93
94/*
95 * Global flag to control if video recording is enabled by kern.video.record.
96 */
97int video_record_enable = 0; /* [a] */
98
99int
100videoprobe(struct device *parent, void *match, void *aux)
101{
102 return (1);
103}
104
105void
106videoattach(struct device *parent, struct device *self, void *aux)
107{
108 struct video_softc *sc = (void *)self;
109 struct video_attach_args *sa = aux;
110
111 printf("\n");
112 sc->hw_if = sa->hwif;
113 sc->hw_hdl = sa->hdl;
114 sc->sc_dev = parent;
115 sc->sc_fbufferlen = 0;
116 sc->sc_owner = NULL;
117 mtx_init(&sc->sc_mtx, IPL_MPFLOOR);
118 klist_init_mutex(&sc->sc_rklist, &sc->sc_mtx);
119
120 if (sc->hw_if->get_bufsize)
121 sc->sc_fbufferlen = (sc->hw_if->get_bufsize)(sc->hw_hdl);
122 if (sc->sc_fbufferlen == 0) {
123 printf("video: could not request frame buffer size\n");
124 return;
125 }
126
127 sc->sc_fbuffer = malloc(sc->sc_fbufferlen, M_DEVBUF, M_NOWAIT);
128 if (sc->sc_fbuffer == NULL) {
129 printf("video: could not allocate frame buffer\n");
130 return;
131 }
132}
133
134int
135videoopen(dev_t dev, int flags, int fmt, struct proc *p)
136{
137 int unit = VIDEOUNIT(dev);
138 struct video_softc *sc;
139 int error = 0;
140
141 KERNEL_ASSERT_LOCKED();
142
143 sc = (struct video_softc *)device_lookup(&video_cd, unit);
144 if (sc == NULL)
145 return (ENXIO);
146
147 if (sc->sc_open) {
148 DPRINTF(1, "%s: device already open\n", __func__);
149 goto done;
150 }
151
152 sc->sc_vidmode = VIDMODE_NONE;
153 sc->sc_frames_ready = 0;
154
155 if (sc->hw_if->open != NULL) {
156 error = sc->hw_if->open(sc->hw_hdl, flags, &sc->sc_fsize,
157 sc->sc_fbuffer, video_intr, sc);
158 }
159 if (error == 0) {
160 sc->sc_open = 1;
161 DPRINTF(1, "%s: set device to open\n", __func__);
162 }
163
164done:
165 device_unref(&sc->dev);
166 return (error);
167}
168
169int
170videoclose(dev_t dev, int flags, int fmt, struct proc *p)
171{
172 int unit = VIDEOUNIT(dev);
173 struct video_softc *sc;
174 int error = 0;
175
176 KERNEL_ASSERT_LOCKED();
177
178 DPRINTF(1, "%s: last close\n", __func__);
179
180 sc = (struct video_softc *)device_lookup(&video_cd, unit);
181 if (sc == NULL)
182 return (ENXIO);
183
184 if (!sc->sc_open) {
185 error = ENXIO;
186 goto done;
187 }
188
189 error = video_stop(sc);
190 sc->sc_open = 0;
191
192done:
193 device_unref(&sc->dev);
194 return (error);
195}
196
197int
198videoread(dev_t dev, struct uio *uio, int ioflag)
199{
200 int unit = VIDEOUNIT(dev);
201 struct video_softc *sc;
202 int error = 0;
203 size_t size;
204
205 KERNEL_ASSERT_LOCKED();
206
207 sc = (struct video_softc *)device_lookup(&video_cd, unit);
208 if (sc == NULL)
209 return (ENXIO);
210
211 if (sc->sc_dying) {
212 error = EIO;
213 goto done;
214 }
215
216 if (sc->sc_vidmode == VIDMODE_MMAP) {
217 error = EBUSY;
218 goto done;
219 }
220
221 if ((error = video_claim(sc, curproc->p_p)))
222 goto done;
223
224 /* start the stream if not already started */
225 if (sc->sc_vidmode == VIDMODE_NONE && sc->hw_if->start_read) {
226 error = sc->hw_if->start_read(sc->hw_hdl);
227 if (error)
228 goto done;
229 sc->sc_vidmode = VIDMODE_READ;
230 }
231
232 DPRINTF(1, "resid=%zu\n", uio->uio_resid);
233
234 mtx_enter(&sc->sc_mtx);
235
236 if (sc->sc_frames_ready < 1) {
237 /* block userland read until a frame is ready */
238 error = msleep_nsec(sc, &sc->sc_mtx, PWAIT | PCATCH,
239 "vid_rd", INFSLP);
240 if (sc->sc_dying)
241 error = EIO;
242 if (error) {
243 mtx_leave(&sc->sc_mtx);
244 goto done;
245 }
246 }
247 sc->sc_frames_ready--;
248
249 mtx_leave(&sc->sc_mtx);
250
251 /* move no more than 1 frame to userland, as per specification */
252 size = ulmin(uio->uio_resid, sc->sc_fsize);
253 if (!atomic_load_int(&video_record_enable))
254 bzero(sc->sc_fbuffer, size);
255 error = uiomove(sc->sc_fbuffer, size, uio);
256 if (error)
257 goto done;
258
259 DPRINTF(1, "uiomove successfully done (%zu bytes)\n", size);
260
261done:
262 device_unref(&sc->dev);
263 return (error);
264}
265
266int
267videoioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct proc *p)
268{
269 int unit = VIDEOUNIT(dev);
270 struct video_softc *sc;
271 struct v4l2_buffer *vb = (struct v4l2_buffer *)data;
272 int error;
273
274 KERNEL_ASSERT_LOCKED();
275
276 sc = (struct video_softc *)device_lookup(&video_cd, unit);
277 if (sc == NULL)
278 return (ENXIO);
279
280 if (sc->hw_if == NULL) {
281 error = ENXIO;
282 goto done;
283 }
284
285 DPRINTF(3, "video_ioctl(%zu, '%c', %zu)\n",
286 IOCPARM_LEN(cmd), (int) IOCGROUP(cmd), cmd & 0xff);
287
288 error = EOPNOTSUPP;
289 switch (cmd) {
290 case VIDIOC_G_CTRL:
291 if (sc->hw_if->g_ctrl)
292 error = (sc->hw_if->g_ctrl)(sc->hw_hdl,
293 (struct v4l2_control *)data);
294 break;
295 case VIDIOC_S_CTRL:
296 if (sc->hw_if->s_ctrl)
297 error = (sc->hw_if->s_ctrl)(sc->hw_hdl,
298 (struct v4l2_control *)data);
299 break;
300 default:
301 error = (ENOTTY);
302 }
303 if (error != ENOTTY)
304 goto done;
305
306 if ((error = video_claim(sc, p->p_p)))
307 goto done;
308
309 /*
310 * The following IOCTLs can only be called by the device owner.
311 * For further shared IOCTLs please move it up.
312 */
313 error = EOPNOTSUPP;
314 switch (cmd) {
315 case VIDIOC_QUERYCAP:
316 if (sc->hw_if->querycap)
317 error = (sc->hw_if->querycap)(sc->hw_hdl,
318 (struct v4l2_capability *)data);
319 break;
320 case VIDIOC_ENUM_FMT:
321 if (sc->hw_if->enum_fmt)
322 error = (sc->hw_if->enum_fmt)(sc->hw_hdl,
323 (struct v4l2_fmtdesc *)data);
324 break;
325 case VIDIOC_ENUM_FRAMESIZES:
326 if (sc->hw_if->enum_fsizes)
327 error = (sc->hw_if->enum_fsizes)(sc->hw_hdl,
328 (struct v4l2_frmsizeenum *)data);
329 break;
330 case VIDIOC_ENUM_FRAMEINTERVALS:
331 if (sc->hw_if->enum_fivals)
332 error = (sc->hw_if->enum_fivals)(sc->hw_hdl,
333 (struct v4l2_frmivalenum *)data);
334 break;
335 case VIDIOC_S_FMT:
336 if (!(flags & FWRITE))
337 return (EACCES);
338 if (sc->hw_if->s_fmt)
339 error = (sc->hw_if->s_fmt)(sc->hw_hdl,
340 (struct v4l2_format *)data);
341 break;
342 case VIDIOC_G_FMT:
343 if (sc->hw_if->g_fmt)
344 error = (sc->hw_if->g_fmt)(sc->hw_hdl,
345 (struct v4l2_format *)data);
346 break;
347 case VIDIOC_S_PARM:
348 if (sc->hw_if->s_parm)
349 error = (sc->hw_if->s_parm)(sc->hw_hdl,
350 (struct v4l2_streamparm *)data);
351 break;
352 case VIDIOC_G_PARM:
353 if (sc->hw_if->g_parm)
354 error = (sc->hw_if->g_parm)(sc->hw_hdl,
355 (struct v4l2_streamparm *)data);
356 break;
357 case VIDIOC_ENUMINPUT:
358 if (sc->hw_if->enum_input)
359 error = (sc->hw_if->enum_input)(sc->hw_hdl,
360 (struct v4l2_input *)data);
361 break;
362 case VIDIOC_S_INPUT:
363 if (sc->hw_if->s_input)
364 error = (sc->hw_if->s_input)(sc->hw_hdl,
365 (int)*data);
366 break;
367 case VIDIOC_G_INPUT:
368 if (sc->hw_if->g_input)
369 error = (sc->hw_if->g_input)(sc->hw_hdl,
370 (int *)data);
371 break;
372 case VIDIOC_REQBUFS:
373 if (sc->hw_if->reqbufs)
374 error = (sc->hw_if->reqbufs)(sc->hw_hdl,
375 (struct v4l2_requestbuffers *)data);
376 break;
377 case VIDIOC_QUERYBUF:
378 if (sc->hw_if->querybuf)
379 error = (sc->hw_if->querybuf)(sc->hw_hdl,
380 (struct v4l2_buffer *)data);
381 break;
382 case VIDIOC_QBUF:
383 if (sc->hw_if->qbuf)
384 error = (sc->hw_if->qbuf)(sc->hw_hdl,
385 (struct v4l2_buffer *)data);
386 break;
387 case VIDIOC_DQBUF:
388 if (!sc->hw_if->dqbuf)
389 break;
390 /* should have called mmap() before now */
391 if (sc->sc_vidmode != VIDMODE_MMAP) {
392 error = EINVAL;
393 break;
394 }
395 error = (sc->hw_if->dqbuf)(sc->hw_hdl,
396 (struct v4l2_buffer *)data);
397 if (!atomic_load_int(&video_record_enable))
398 bzero(sc->sc_fbuffer_mmap + vb->m.offset, vb->length);
399 mtx_enter(&sc->sc_mtx);
400 sc->sc_frames_ready--;
401 mtx_leave(&sc->sc_mtx);
402 break;
403 case VIDIOC_STREAMON:
404 if (sc->hw_if->streamon)
405 error = (sc->hw_if->streamon)(sc->hw_hdl,
406 (int)*data);
407 break;
408 case VIDIOC_STREAMOFF:
409 if (sc->hw_if->streamoff)
410 error = (sc->hw_if->streamoff)(sc->hw_hdl,
411 (int)*data);
412 if (!error) {
413 /* Release device ownership and streaming buffers. */
414 error = video_stop(sc);
415 }
416 break;
417 case VIDIOC_TRY_FMT:
418 if (sc->hw_if->try_fmt)
419 error = (sc->hw_if->try_fmt)(sc->hw_hdl,
420 (struct v4l2_format *)data);
421 break;
422 case VIDIOC_QUERYCTRL:
423 if (sc->hw_if->queryctrl)
424 error = (sc->hw_if->queryctrl)(sc->hw_hdl,
425 (struct v4l2_queryctrl *)data);
426 break;
427 default:
428 error = (ENOTTY);
429 }
430
431done:
432 device_unref(&sc->dev);
433 return (error);
434}
435
436paddr_t
437videommap(dev_t dev, off_t off, int prot)
438{
439 int unit = VIDEOUNIT(dev);
440 struct video_softc *sc;
441 caddr_t p;
442 paddr_t pa;
443
444 KERNEL_ASSERT_LOCKED();
445
446 DPRINTF(2, "%s: off=%lld, prot=%d\n", __func__, off, prot);
447
448 sc = (struct video_softc *)device_lookup(&video_cd, unit);
449 if (sc == NULL)
450 return (-1);
451
452 if (sc->sc_dying)
453 goto err;
454
455 if (sc->hw_if->mappage == NULL)
456 goto err;
457
458 p = sc->hw_if->mappage(sc->hw_hdl, off, prot);
459 if (p == NULL)
460 goto err;
461 if (pmap_extract(pmap_kernel(), (vaddr_t)p, &pa) == FALSE)
462 panic("videommap: invalid page");
463 sc->sc_vidmode = VIDMODE_MMAP;
464
465 /* store frame buffer base address for later blanking */
466 if (off == 0)
467 sc->sc_fbuffer_mmap = p;
468
469 device_unref(&sc->dev);
470 return (pa);
471
472err:
473 device_unref(&sc->dev);
474 return (-1);
475}
476
477void
478filt_videodetach(struct knote *kn)
479{
480 struct video_softc *sc = kn->kn_hook;
481
482 klist_remove(&sc->sc_rklist, kn);
483}
484
485int
486filt_videoread(struct knote *kn, long hint)
487{
488 struct video_softc *sc = kn->kn_hook;
489
490 if (sc->sc_frames_ready > 0)
491 return (1);
492
493 return (0);
494}
495
496int
497filt_videomodify(struct kevent *kev, struct knote *kn)
498{
499 struct video_softc *sc = kn->kn_hook;
500 int active;
501
502 mtx_enter(&sc->sc_mtx);
503 active = knote_modify(kev, kn);
504 mtx_leave(&sc->sc_mtx);
505
506 return (active);
507}
508
509int
510filt_videoprocess(struct knote *kn, struct kevent *kev)
511{
512 struct video_softc *sc = kn->kn_hook;
513 int active;
514
515 mtx_enter(&sc->sc_mtx);
516 active = knote_process(kn, kev);
517 mtx_leave(&sc->sc_mtx);
518
519 return (active);
520}
521
522const struct filterops video_filtops = {
523 .f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
524 .f_attach = NULL,
525 .f_detach = filt_videodetach,
526 .f_event = filt_videoread,
527 .f_modify = filt_videomodify,
528 .f_process = filt_videoprocess,
529};
530
531int
532videokqfilter(dev_t dev, struct knote *kn)
533{
534 int unit = VIDEOUNIT(dev);
535 struct video_softc *sc;
536 int error = 0;
537
538 KERNEL_ASSERT_LOCKED();
539
540 sc = (struct video_softc *)device_lookup(&video_cd, unit);
541 if (sc == NULL)
542 return (ENXIO);
543
544 if (sc->sc_dying) {
545 error = ENXIO;
546 goto done;
547 }
548
549 switch (kn->kn_filter) {
550 case EVFILT_READ:
551 kn->kn_fop = &video_filtops;
552 kn->kn_hook = sc;
553 break;
554 default:
555 error = EINVAL;
556 goto done;
557 }
558
559 if ((error = video_claim(sc, curproc->p_p)))
560 goto done;
561
562 /*
563 * Start the stream in read() mode if not already started. If
564 * the user wanted mmap() mode, he should have called mmap()
565 * before now.
566 */
567 if (sc->sc_vidmode == VIDMODE_NONE && sc->hw_if->start_read) {
568 if (sc->hw_if->start_read(sc->hw_hdl))
569 return (ENXIO);
570 sc->sc_vidmode = VIDMODE_READ;
571 }
572
573 klist_insert(&sc->sc_rklist, kn);
574
575done:
576 device_unref(&sc->dev);
577 return (error);
578}
579
580int
581video_submatch(struct device *parent, void *match, void *aux)
582{
583 struct cfdata *cf = match;
584
585 return (cf->cf_driver == &video_cd);
586}
587
588/*
589 * Called from hardware driver. This is where the MI video driver gets
590 * probed/attached to the hardware driver
591 */
592struct device *
593video_attach_mi(const struct video_hw_if *rhwp, void *hdlp, struct device *dev)
594{
595 struct video_attach_args arg;
596
597 arg.hwif = rhwp;
598 arg.hdl = hdlp;
599 return (config_found_sm(dev, &arg, videoprint, video_submatch));
600}
601
602void
603video_intr(void *addr)
604{
605 struct video_softc *sc = (struct video_softc *)addr;
606
607 DPRINTF(3, "video_intr sc=%p\n", sc);
608 mtx_enter(&sc->sc_mtx);
609 if (sc->sc_vidmode != VIDMODE_NONE)
610 sc->sc_frames_ready++;
611 else
612 printf("%s: interrupt but no streams!\n", __func__);
613 if (sc->sc_vidmode == VIDMODE_READ)
614 wakeup(sc);
615 knote_locked(&sc->sc_rklist, 0);
616 mtx_leave(&sc->sc_mtx);
617}
618
619int
620video_stop(struct video_softc *sc)
621{
622 int error = 0;
623
624 DPRINTF(1, "%s: stream close\n", __func__);
625
626 if (sc->hw_if->close != NULL)
627 error = sc->hw_if->close(sc->hw_hdl);
628
629 sc->sc_vidmode = VIDMODE_NONE;
630 mtx_enter(&sc->sc_mtx);
631 sc->sc_frames_ready = 0;
632 mtx_leave(&sc->sc_mtx);
633 sc->sc_owner = NULL;
634
635 return (error);
636}
637
638int
639video_claim(struct video_softc *sc, struct process *pr)
640{
641 if (sc->sc_owner != NULL && sc->sc_owner != pr) {
642 DPRINTF(1, "%s: already owned=%p\n", __func__, sc->sc_owner);
643 return (EBUSY);
644 }
645
646 if (sc->sc_owner == NULL) {
647 sc->sc_owner = pr;
648 DPRINTF(1, "%s: new owner=%p\n", __func__, sc->sc_owner);
649 }
650
651 return (0);
652}
653
654int
655videoprint(void *aux, const char *pnp)
656{
657 if (pnp != NULL)
658 printf("video at %s", pnp);
659 return (UNCONF);
660}
661
662int
663videodetach(struct device *self, int flags)
664{
665 struct video_softc *sc = (struct video_softc *)self;
666 int maj, mn;
667
668 /* locate the major number */
669 for (maj = 0; maj < nchrdev; maj++)
670 if (cdevsw[maj].d_open == videoopen)
671 break;
672
673 /* Nuke the vnodes for any open instances (calls close). */
674 mn = self->dv_unit;
675 vdevgone(maj, mn, mn, VCHR);
676
677 klist_invalidate(&sc->sc_rklist);
678 klist_free(&sc->sc_rklist);
679
680 free(sc->sc_fbuffer, M_DEVBUF, sc->sc_fbufferlen);
681
682 return (0);
683}
684
685int
686videoactivate(struct device *self, int act)
687{
688 struct video_softc *sc = (struct video_softc *)self;
689
690 switch (act) {
691 case DVACT_DEACTIVATE:
692 sc->sc_dying = 1;
693 break;
694 }
695 return (0);
696}