Edit

IABSD.fr/src/sys/dev/video.c

Branch :

  • Show log

    Commit

  • Author : kirill
    Date : 2025-04-15 06:44:37
    Hash : bb834979
    Message : sys/video: avoid call video_stop multiple times OK: mpi@

  • sys/dev/video.c
  • /*	$OpenBSD: video.c,v 1.61 2025/04/15 06:44:37 kirill Exp $	*/
    
    /*
     * Copyright (c) 2008 Robert Nagy <robert@openbsd.org>
     * Copyright (c) 2008 Marcus Glocker <mglocker@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <sys/errno.h>
    #include <sys/event.h>
    #include <sys/ioctl.h>
    #include <sys/fcntl.h>
    #include <sys/device.h>
    #include <sys/vnode.h>
    #include <sys/kernel.h>
    #include <sys/malloc.h>
    #include <sys/mutex.h>
    #include <sys/conf.h>
    #include <sys/proc.h>
    #include <sys/videoio.h>
    
    #include <dev/video_if.h>
    
    #include <uvm/uvm_extern.h>
    
    /*
     * Locks used to protect struct members and global data
     *	a	atomic
     *	m	sc_mtx
     */
    
    #ifdef VIDEO_DEBUG
    int video_debug = 1;
    #define DPRINTF(l, x...) do { if ((l) <= video_debug) printf(x); } while (0)
    #else
    #define DPRINTF(l, x...)
    #endif
    
    struct video_softc {
    	struct device		 dev;
    	void			*hw_hdl;	/* hardware driver handle */
    	struct device		*sc_dev;	/* hardware device struct */
    	const struct video_hw_if *hw_if;	/* hardware interface */
    	char			 sc_dying;	/* device detached */
    	struct process		*sc_owner;	/* owner process */
    	uint8_t			 sc_open;	/* device opened */
    
    	struct mutex		 sc_mtx;
    	int			 sc_fsize;
    	uint8_t			*sc_fbuffer;
    	caddr_t			 sc_fbuffer_mmap;
    	size_t			 sc_fbufferlen;
    	int			 sc_vidmode;	/* access mode */
    #define		VIDMODE_NONE	0
    #define		VIDMODE_MMAP	1
    #define		VIDMODE_READ	2
    	int			 sc_frames_ready;	/* [m] */
    
    	struct klist		 sc_rklist;		/* [m] read selector */
    };
    
    int	videoprobe(struct device *, void *, void *);
    void	videoattach(struct device *, struct device *, void *);
    int	videodetach(struct device *, int);
    int	videoactivate(struct device *, int);
    int	videoprint(void *, const char *);
    
    void	video_intr(void *);
    int	video_stop(struct video_softc *);
    int	video_claim(struct video_softc *, struct process *);
    
    const struct cfattach video_ca = {
    	sizeof(struct video_softc), videoprobe, videoattach,
    	videodetach, videoactivate
    };
    
    struct cfdriver video_cd = {
    	NULL, "video", DV_DULL
    };
    
    /*
     * Global flag to control if video recording is enabled by kern.video.record.
     */
    int video_record_enable = 0;	/* [a] */
    
    int
    videoprobe(struct device *parent, void *match, void *aux)
    {
    	return (1);
    }
    
    void
    videoattach(struct device *parent, struct device *self, void *aux)
    {
    	struct video_softc *sc = (void *)self;
    	struct video_attach_args *sa = aux;
    
    	printf("\n");
    	sc->hw_if = sa->hwif;
    	sc->hw_hdl = sa->hdl;
    	sc->sc_dev = parent;
    	sc->sc_fbufferlen = 0;
    	sc->sc_owner = NULL;
    	mtx_init(&sc->sc_mtx, IPL_MPFLOOR);
    	klist_init_mutex(&sc->sc_rklist, &sc->sc_mtx);
    
    	if (sc->hw_if->get_bufsize)
    		sc->sc_fbufferlen = (sc->hw_if->get_bufsize)(sc->hw_hdl);
    	if (sc->sc_fbufferlen == 0) {
    		printf("video: could not request frame buffer size\n");
    		return;
    	}
    
    	sc->sc_fbuffer = malloc(sc->sc_fbufferlen, M_DEVBUF, M_NOWAIT);
    	if (sc->sc_fbuffer == NULL) {
    		printf("video: could not allocate frame buffer\n");
    		return;
    	}
    }
    
    int
    videoopen(dev_t dev, int flags, int fmt, struct proc *p)
    {
    	int unit = VIDEOUNIT(dev);
    	struct video_softc *sc;
    	int error = 0;
    
    	KERNEL_ASSERT_LOCKED();
    
    	sc = (struct video_softc *)device_lookup(&video_cd, unit);
    	if (sc == NULL)
    		return (ENXIO);
    
    	if (sc->sc_open) {
    		DPRINTF(1, "%s: device already open\n", __func__);
    		goto done;
    	}
    
    	sc->sc_vidmode = VIDMODE_NONE;
    	sc->sc_frames_ready = 0;
    
    	if (sc->hw_if->open != NULL) {
    		error = sc->hw_if->open(sc->hw_hdl, flags, &sc->sc_fsize,
    		    sc->sc_fbuffer, video_intr, sc);
    	}
    	if (error == 0) {
    		sc->sc_open = 1;
    		DPRINTF(1, "%s: set device to open\n", __func__);
    	}
    
    done:
    	device_unref(&sc->dev);
    	return (error);
    }
    
    int
    videoclose(dev_t dev, int flags, int fmt, struct proc *p)
    {
    	int unit = VIDEOUNIT(dev);
    	struct video_softc *sc;
    	int error = 0;
    
    	KERNEL_ASSERT_LOCKED();
    
    	DPRINTF(1, "%s: last close\n", __func__);
    
    	sc = (struct video_softc *)device_lookup(&video_cd, unit);
    	if (sc == NULL)
    		return (ENXIO);
    
    	if (!sc->sc_open) {
    		error = ENXIO;
    		goto done;
    	}
    
    	error = video_stop(sc);
    	sc->sc_open = 0;
    
    done:
    	device_unref(&sc->dev);
    	return (error);
    }
    
    int
    videoread(dev_t dev, struct uio *uio, int ioflag)
    {
    	int unit = VIDEOUNIT(dev);
    	struct video_softc *sc;
    	int error = 0;
    	size_t size;
    
    	KERNEL_ASSERT_LOCKED();
    
    	sc = (struct video_softc *)device_lookup(&video_cd, unit);
    	if (sc == NULL)
    		return (ENXIO);
    
    	if (sc->sc_dying) {
    		error = EIO;
    		goto done;
    	}
    
    	if (sc->sc_vidmode == VIDMODE_MMAP) {
    		error = EBUSY;
    		goto done;
    	}
    
    	if ((error = video_claim(sc, curproc->p_p)))
    		goto done;
    
    	/* start the stream if not already started */
    	if (sc->sc_vidmode == VIDMODE_NONE && sc->hw_if->start_read) {
     		error = sc->hw_if->start_read(sc->hw_hdl);
     		if (error)
    			goto done;
    		sc->sc_vidmode = VIDMODE_READ;
     	}
    
    	DPRINTF(1, "resid=%zu\n", uio->uio_resid);
    
    	mtx_enter(&sc->sc_mtx);
    
    	if (sc->sc_frames_ready < 1) {
    		/* block userland read until a frame is ready */
    		error = msleep_nsec(sc, &sc->sc_mtx, PWAIT | PCATCH,
    		    "vid_rd", INFSLP);
    		if (sc->sc_dying)
    			error = EIO;
    		if (error) {
    			mtx_leave(&sc->sc_mtx);
    			goto done;
    		}
    	}
    	sc->sc_frames_ready--;
    
    	mtx_leave(&sc->sc_mtx);
    
    	/* move no more than 1 frame to userland, as per specification */
    	size = ulmin(uio->uio_resid, sc->sc_fsize);
    	if (!atomic_load_int(&video_record_enable))
    		bzero(sc->sc_fbuffer, size);
    	error = uiomove(sc->sc_fbuffer, size, uio);
    	if (error)
    		goto done;
    
    	DPRINTF(1, "uiomove successfully done (%zu bytes)\n", size);
    
    done:
    	device_unref(&sc->dev);
    	return (error);
    }
    
    int
    videoioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct proc *p)
    {
    	int unit = VIDEOUNIT(dev);
    	struct video_softc *sc;
    	struct v4l2_buffer *vb = (struct v4l2_buffer *)data;
    	int error;
    
    	KERNEL_ASSERT_LOCKED();
    
    	sc = (struct video_softc *)device_lookup(&video_cd, unit);
    	if (sc == NULL)
    		return (ENXIO);
    
    	if (sc->hw_if == NULL) {
    		error = ENXIO;
    		goto done;
    	}
    
    	DPRINTF(3, "video_ioctl(%zu, '%c', %zu)\n",
    	    IOCPARM_LEN(cmd), (int) IOCGROUP(cmd), cmd & 0xff);
    
    	error = EOPNOTSUPP;
    	switch (cmd) {
    	case VIDIOC_G_CTRL:
    		if (sc->hw_if->g_ctrl)
    			error = (sc->hw_if->g_ctrl)(sc->hw_hdl,
    			    (struct v4l2_control *)data);
    		break;
    	case VIDIOC_S_CTRL:
    		if (sc->hw_if->s_ctrl)
    			error = (sc->hw_if->s_ctrl)(sc->hw_hdl,
    			    (struct v4l2_control *)data);
    		break;
    	default:
    		error = (ENOTTY);
    	}
    	if (error != ENOTTY)
    		goto done;
    
    	if ((error = video_claim(sc, p->p_p)))
    		goto done;
    
    	/*
    	 * The following IOCTLs can only be called by the device owner.
    	 * For further shared IOCTLs please move it up.
    	 */
    	error = EOPNOTSUPP;
    	switch (cmd) {
    	case VIDIOC_QUERYCAP:
    		if (sc->hw_if->querycap)
    			error = (sc->hw_if->querycap)(sc->hw_hdl,
    			    (struct v4l2_capability *)data);
    		break;
    	case VIDIOC_ENUM_FMT:
    		if (sc->hw_if->enum_fmt)
    			error = (sc->hw_if->enum_fmt)(sc->hw_hdl,
    			    (struct v4l2_fmtdesc *)data);
    		break;
    	case VIDIOC_ENUM_FRAMESIZES:
    		if (sc->hw_if->enum_fsizes)
    			error = (sc->hw_if->enum_fsizes)(sc->hw_hdl,
    			    (struct v4l2_frmsizeenum *)data);
    		break;
    	case VIDIOC_ENUM_FRAMEINTERVALS:
    		if (sc->hw_if->enum_fivals)
    			error = (sc->hw_if->enum_fivals)(sc->hw_hdl,
    			    (struct v4l2_frmivalenum *)data);
    		break;
    	case VIDIOC_S_FMT:
    		if (!(flags & FWRITE))
    			return (EACCES);
    		if (sc->hw_if->s_fmt)
    			error = (sc->hw_if->s_fmt)(sc->hw_hdl,
    			    (struct v4l2_format *)data);
    		break;
    	case VIDIOC_G_FMT:
    		if (sc->hw_if->g_fmt)
    			error = (sc->hw_if->g_fmt)(sc->hw_hdl,
    			    (struct v4l2_format *)data);
    		break;
    	case VIDIOC_S_PARM:
    		if (sc->hw_if->s_parm)
    			error = (sc->hw_if->s_parm)(sc->hw_hdl,
    			    (struct v4l2_streamparm *)data);
    		break;
    	case VIDIOC_G_PARM:
    		if (sc->hw_if->g_parm)
    			error = (sc->hw_if->g_parm)(sc->hw_hdl,
    			    (struct v4l2_streamparm *)data);
    		break;
    	case VIDIOC_ENUMINPUT:
    		if (sc->hw_if->enum_input)
    			error = (sc->hw_if->enum_input)(sc->hw_hdl,
    			    (struct v4l2_input *)data);
    		break;
    	case VIDIOC_S_INPUT:
    		if (sc->hw_if->s_input)
    			error = (sc->hw_if->s_input)(sc->hw_hdl,
    			    (int)*data);
    		break;
    	case VIDIOC_G_INPUT:
    		if (sc->hw_if->g_input)
    			error = (sc->hw_if->g_input)(sc->hw_hdl,
    			    (int *)data);
    		break;
    	case VIDIOC_REQBUFS:
    		if (sc->hw_if->reqbufs)
    			error = (sc->hw_if->reqbufs)(sc->hw_hdl,
    			    (struct v4l2_requestbuffers *)data);
    		break;
    	case VIDIOC_QUERYBUF:
    		if (sc->hw_if->querybuf)
    			error = (sc->hw_if->querybuf)(sc->hw_hdl,
    			    (struct v4l2_buffer *)data);
    		break;
    	case VIDIOC_QBUF:
    		if (sc->hw_if->qbuf)
    			error = (sc->hw_if->qbuf)(sc->hw_hdl,
    			    (struct v4l2_buffer *)data);
    		break;
    	case VIDIOC_DQBUF:
    		if (!sc->hw_if->dqbuf)
    			break;
    		/* should have called mmap() before now */
    		if (sc->sc_vidmode != VIDMODE_MMAP) {
    			error = EINVAL;
    			break;
    		}
    		error = (sc->hw_if->dqbuf)(sc->hw_hdl,
    		    (struct v4l2_buffer *)data);
    		if (!atomic_load_int(&video_record_enable))
    			bzero(sc->sc_fbuffer_mmap + vb->m.offset, vb->length);
    		mtx_enter(&sc->sc_mtx);
    		sc->sc_frames_ready--;
    		mtx_leave(&sc->sc_mtx);
    		break;
    	case VIDIOC_STREAMON:
    		if (sc->hw_if->streamon)
    			error = (sc->hw_if->streamon)(sc->hw_hdl,
    			    (int)*data);
    		break;
    	case VIDIOC_STREAMOFF:
    		if (sc->hw_if->streamoff)
    			error = (sc->hw_if->streamoff)(sc->hw_hdl,
    			    (int)*data);
    		if (!error) {
    			/* Release device ownership and streaming buffers. */
    			error = video_stop(sc);
    		}
    		break;
    	case VIDIOC_TRY_FMT:
    		if (sc->hw_if->try_fmt)
    			error = (sc->hw_if->try_fmt)(sc->hw_hdl,
    			    (struct v4l2_format *)data);
    		break;
    	case VIDIOC_QUERYCTRL:
    		if (sc->hw_if->queryctrl)
    			error = (sc->hw_if->queryctrl)(sc->hw_hdl,
    			    (struct v4l2_queryctrl *)data);
    		break;
    	default:
    		error = (ENOTTY);
    	}
    
    done:
    	device_unref(&sc->dev);
    	return (error);
    }
    
    paddr_t
    videommap(dev_t dev, off_t off, int prot)
    {
    	int unit = VIDEOUNIT(dev);
    	struct video_softc *sc;
    	caddr_t p;
    	paddr_t pa;
    
    	KERNEL_ASSERT_LOCKED();
    
    	DPRINTF(2, "%s: off=%lld, prot=%d\n", __func__, off, prot);
    
    	sc = (struct video_softc *)device_lookup(&video_cd, unit);
    	if (sc == NULL)
    		return (-1);
    
    	if (sc->sc_dying)
    		goto err;
    
    	if (sc->hw_if->mappage == NULL)
    		goto err;
    
    	p = sc->hw_if->mappage(sc->hw_hdl, off, prot);
    	if (p == NULL)
    		goto err;
    	if (pmap_extract(pmap_kernel(), (vaddr_t)p, &pa) == FALSE)
    		panic("videommap: invalid page");
    	sc->sc_vidmode = VIDMODE_MMAP;
    
    	/* store frame buffer base address for later blanking */
    	if (off == 0)
    		sc->sc_fbuffer_mmap = p;
    
    	device_unref(&sc->dev);
    	return (pa);
    
    err:
    	device_unref(&sc->dev);
    	return (-1);
    }
    
    void
    filt_videodetach(struct knote *kn)
    {
    	struct video_softc *sc = kn->kn_hook;
    
    	klist_remove(&sc->sc_rklist, kn);
    }
    
    int
    filt_videoread(struct knote *kn, long hint)
    {
    	struct video_softc *sc = kn->kn_hook;
    
    	if (sc->sc_frames_ready > 0)
    		return (1);
    
    	return (0);
    }
    
    int
    filt_videomodify(struct kevent *kev, struct knote *kn)
    {
    	struct video_softc *sc = kn->kn_hook;
    	int active;
    
    	mtx_enter(&sc->sc_mtx);
    	active = knote_modify(kev, kn); 
    	mtx_leave(&sc->sc_mtx);
    
    	return (active);
    }
    
    int
    filt_videoprocess(struct knote *kn, struct kevent *kev)
    {
    	struct video_softc *sc = kn->kn_hook;
    	int active;
    
    	mtx_enter(&sc->sc_mtx);
    	active = knote_process(kn, kev);
    	mtx_leave(&sc->sc_mtx);
    
    	return (active);
    }
    
    const struct filterops video_filtops = {
    	.f_flags	= FILTEROP_ISFD | FILTEROP_MPSAFE,
    	.f_attach	= NULL,
    	.f_detach	= filt_videodetach,
    	.f_event	= filt_videoread,
    	.f_modify	= filt_videomodify,
    	.f_process	= filt_videoprocess,
    };
    
    int
    videokqfilter(dev_t dev, struct knote *kn)
    {
    	int unit = VIDEOUNIT(dev);
    	struct video_softc *sc;
    	int error = 0;
    
    	KERNEL_ASSERT_LOCKED();
    
    	sc = (struct video_softc *)device_lookup(&video_cd, unit);
    	if (sc == NULL)
    		return (ENXIO);
    
    	if (sc->sc_dying) {
    		error = ENXIO;
    		goto done;
    	}
    
    	switch (kn->kn_filter) {
    	case EVFILT_READ:
    		kn->kn_fop = &video_filtops;
    		kn->kn_hook = sc;
    		break;
    	default:
    		error  = EINVAL;
    		goto done;
    	}
    
    	if ((error = video_claim(sc, curproc->p_p)))
    		goto done;
    
    	/*
    	 * Start the stream in read() mode if not already started.  If
    	 * the user wanted mmap() mode, he should have called mmap()
    	 * before now.
    	 */
    	if (sc->sc_vidmode == VIDMODE_NONE && sc->hw_if->start_read) {
    		if (sc->hw_if->start_read(sc->hw_hdl))
    			return (ENXIO);
    		sc->sc_vidmode = VIDMODE_READ;
    	}
    
    	klist_insert(&sc->sc_rklist, kn);
    
    done:
    	device_unref(&sc->dev);
    	return (error);
    }
    
    int
    video_submatch(struct device *parent, void *match, void *aux)
    {
            struct cfdata *cf = match;
    
    	return (cf->cf_driver == &video_cd);
    }
    
    /*
     * Called from hardware driver. This is where the MI video driver gets
     * probed/attached to the hardware driver
     */
    struct device *
    video_attach_mi(const struct video_hw_if *rhwp, void *hdlp, struct device *dev)
    {
    	struct video_attach_args arg;
    
    	arg.hwif = rhwp;
    	arg.hdl = hdlp;
    	return (config_found_sm(dev, &arg, videoprint, video_submatch));
    }
    
    void
    video_intr(void *addr)
    {
    	struct video_softc *sc = (struct video_softc *)addr;
    
    	DPRINTF(3, "video_intr sc=%p\n", sc);
    	mtx_enter(&sc->sc_mtx);
    	if (sc->sc_vidmode != VIDMODE_NONE)
    		sc->sc_frames_ready++;
    	else
    		printf("%s: interrupt but no streams!\n", __func__);
    	if (sc->sc_vidmode == VIDMODE_READ)
    		wakeup(sc);
    	knote_locked(&sc->sc_rklist, 0);
    	mtx_leave(&sc->sc_mtx);
    }
    
    int
    video_stop(struct video_softc *sc)
    {
    	int error = 0;
    
    	DPRINTF(1, "%s: stream close\n", __func__);
    
    	if (sc->hw_if->close != NULL)
    		error = sc->hw_if->close(sc->hw_hdl);
    
    	sc->sc_vidmode = VIDMODE_NONE;
    	mtx_enter(&sc->sc_mtx);
    	sc->sc_frames_ready = 0;
    	mtx_leave(&sc->sc_mtx);
    	sc->sc_owner = NULL;
    
    	return (error);
    }
    
    int
    video_claim(struct video_softc *sc, struct process *pr)
    {
    	if (sc->sc_owner != NULL && sc->sc_owner != pr) {
    		DPRINTF(1, "%s: already owned=%p\n", __func__, sc->sc_owner);
    		return (EBUSY);
    	}
    
    	if (sc->sc_owner == NULL) {
    		sc->sc_owner = pr;
    		DPRINTF(1, "%s: new owner=%p\n", __func__, sc->sc_owner);
    	}
    
    	return (0);
    }
    
    int
    videoprint(void *aux, const char *pnp)
    {
    	if (pnp != NULL)
    		printf("video at %s", pnp);
    	return (UNCONF);
    }
    
    int
    videodetach(struct device *self, int flags)
    {
    	struct video_softc *sc = (struct video_softc *)self;
    	int maj, mn;
    
    	/* locate the major number */
    	for (maj = 0; maj < nchrdev; maj++)
    		if (cdevsw[maj].d_open == videoopen)
    			break;
    
    	/* Nuke the vnodes for any open instances (calls close). */
    	mn = self->dv_unit;
    	vdevgone(maj, mn, mn, VCHR);
    
    	klist_invalidate(&sc->sc_rklist);
    	klist_free(&sc->sc_rklist);
    
    	free(sc->sc_fbuffer, M_DEVBUF, sc->sc_fbufferlen);
    
    	return (0);
    }
    
    int
    videoactivate(struct device *self, int act)
    {
    	struct video_softc *sc = (struct video_softc *)self;
    
    	switch (act) {
    	case DVACT_DEACTIVATE:
    		sc->sc_dying = 1;
    		break;
    	}
    	return (0);
    }