[BACK]Return to video.c CVS log [TXT][DIR] Up to [local] / src / sys / dev

File: [local] / src / sys / dev / video.c (download)

Revision 1.57, Sat Jul 2 08:50:41 2022 UTC (23 months, 1 week ago) by visa
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, HEAD
Changes since 1.56: +1 -50 lines

Remove unused device poll functions.

Also remove unneeded includes of <sys/poll.h> and <sys/select.h>.

Some addenda from jsg@.

OK miod@ mpi@

/*	$OpenBSD: video.c,v 1.57 2022/07/02 08:50:41 visa 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/ioctl.h>
#include <sys/fcntl.h>
#include <sys/device.h>
#include <sys/vnode.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/videoio.h>

#include <dev/video_if.h>

#include <uvm/uvm_extern.h>

#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 */

	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;

	struct selinfo		 sc_rsel;	/* 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;

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;

	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();

	if (unit >= video_cd.cd_ndevs ||
	    (sc = video_cd.cd_devs[unit]) == NULL ||
	     sc->hw_if == NULL)
		return (ENXIO);

	if (sc->sc_open) {
		DPRINTF(1, "%s: device already open\n", __func__);
		return (0);
	}

	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__);
	}

	return (error);
}

int
videoclose(dev_t dev, int flags, int fmt, struct proc *p)
{
	struct video_softc *sc;
	int error = 0;

	KERNEL_ASSERT_LOCKED();

	DPRINTF(1, "%s: last close\n", __func__);

	sc = video_cd.cd_devs[VIDEOUNIT(dev)];

	error = video_stop(sc);
	sc->sc_open = 0;

	return (error);
}

int
videoread(dev_t dev, struct uio *uio, int ioflag)
{
	int unit = VIDEOUNIT(dev);
	struct video_softc *sc;
	int error;
	size_t size;

	KERNEL_ASSERT_LOCKED();

	if (unit >= video_cd.cd_ndevs ||
	    (sc = video_cd.cd_devs[unit]) == NULL)
		return (ENXIO);

	if (sc->sc_dying)
		return (EIO);

	if (sc->sc_vidmode == VIDMODE_MMAP)
		return (EBUSY);

	if ((error = video_claim(sc, curproc->p_p)))
		return (error);

	/* 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)
 			return (error);
		sc->sc_vidmode = VIDMODE_READ;
 	}

	DPRINTF(1, "resid=%zu\n", uio->uio_resid);

	if (sc->sc_frames_ready < 1) {
		/* block userland read until a frame is ready */
		error = tsleep_nsec(sc, PWAIT | PCATCH, "vid_rd", INFSLP);
		if (sc->sc_dying)
			error = EIO;
		if (error)
			return (error);
	}

	/* move no more than 1 frame to userland, as per specification */
	size = ulmin(uio->uio_resid, sc->sc_fsize);
	if (!video_record_enable)
		bzero(sc->sc_fbuffer, size);
	error = uiomove(sc->sc_fbuffer, size, uio);
	sc->sc_frames_ready--;
	if (error)
		return (error);

	DPRINTF(1, "uiomove successfully done (%zu bytes)\n", size);

	return (0);
}

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();

	if (unit >= video_cd.cd_ndevs ||
	    (sc = video_cd.cd_devs[unit]) == NULL || sc->hw_if == NULL)
		return (ENXIO);

	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)
		return (error);

	if ((error = video_claim(sc, p->p_p)))
		return (error);

	/*
	 * 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 (!video_record_enable)
			bzero(sc->sc_fbuffer_mmap + vb->m.offset, vb->length);
		sc->sc_frames_ready--;
		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);
	}

	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);

	if (unit >= video_cd.cd_ndevs ||
	    (sc = video_cd.cd_devs[unit]) == NULL)
		return (-1);

	if (sc->sc_dying)
		return (-1);

	if (sc->hw_if->mappage == NULL)
		return (-1);

	p = sc->hw_if->mappage(sc->hw_hdl, off, prot);
	if (p == NULL)
		return (-1);
	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;

	return (pa);
}

void
filt_videodetach(struct knote *kn)
{
	struct video_softc *sc = kn->kn_hook;
	int s;

	s = splhigh();
	klist_remove_locked(&sc->sc_rsel.si_note, kn);
	splx(s);
}

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);
}

const struct filterops video_filtops = {
	.f_flags	= FILTEROP_ISFD,
	.f_attach	= NULL,
	.f_detach	= filt_videodetach,
	.f_event	= filt_videoread,
};

int
videokqfilter(dev_t dev, struct knote *kn)
{
	int unit = VIDEOUNIT(dev);
	struct video_softc *sc;
	int s, error;

	KERNEL_ASSERT_LOCKED();

	if (unit >= video_cd.cd_ndevs ||
	    (sc = video_cd.cd_devs[unit]) == NULL)
		return (ENXIO);

	if (sc->sc_dying)
		return (ENXIO);

	switch (kn->kn_filter) {
	case EVFILT_READ:
		kn->kn_fop = &video_filtops;
		kn->kn_hook = sc;
		break;
	default:
		return (EINVAL);
	}

	if ((error = video_claim(sc, curproc->p_p)))
		return (error);

	/*
	 * 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;
	}

	s = splhigh();
	klist_insert_locked(&sc->sc_rsel.si_note, kn);
	splx(s);

	return (0);
}

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);
	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);
	selwakeup(&sc->sc_rsel);
}

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;
	sc->sc_frames_ready = 0;
	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 s, 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);

	s = splhigh();
	klist_invalidate(&sc->sc_rsel.si_note);
	splx(s);

	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);
}