On 09/02/21(Tue) 20:35, Marcus Glocker wrote:
> jca@ has recently committed a change to video(4) to allow the same
> process to do multiple opens on the same video device to satisfy
> certain applications, and start to go in to the V4L2 "1.1.4 Multiple
> Opens" specification direction as described here:
>
> https://www.kernel.org/doc/html/v5.10/userspace-api/media/v4l/open.html#f1
>
> As well he recently sent me some locking code to prevent concurrent
> access to certain video(4) functions. On that I think it makes more
> sense to introduce the locking code together with the next step, which
> is to allow different processes to open the same video device.
>
> Therefore I have added on top of jca@s locking code the code to define
> a device owner, and based on that distinguish between which process is
> allowed to call certain video(4) functions. Basically the process
> starting with the buffer memory allocation or/and starting the video
> stream becomes the device owner. Other processes can do things like
> calling VIDIOC_G_CTRL or VIDIOC_S_CTRL ioctls. In this diff certainly
> more ioctls can be moved up to the "shared" part, but I only started
> with the video controls for now.
>
> Also video(1) requires a small change to make the read(2) method
> signal that the stream needs to be stopped on close. I checked that
> other applications do implement this behavior as well. Otherwise
> if you have multiple file handles open on a video device, and the
> read(2) process exists before another process, we won't notice that
> the stream needs to be stopped, since only the last file handle
> close will call the videoclose() function.
>
> I would appreciate some regression testing and feedback to make a
> start on implementing this specification.
Which fields is the new lock protecting? Why isn't the KERNEL_LOCK()
enough? Is it because of sleeping points? Could you annotate them?
> Index: sys/dev/video.c
> ===================================================================
> RCS file: /cvs/src/sys/dev/video.c,v
> retrieving revision 1.48
> diff -u -p -u -p -r1.48 video.c
> --- sys/dev/video.c 31 Jan 2021 19:32:01 -0000 1.48
> +++ sys/dev/video.c 7 Feb 2021 15:33:52 -0000
> @@ -48,6 +48,8 @@ struct video_softc {
> struct video_hw_if *hw_if; /* hardware interface */
> char sc_dying; /* device detached */
> struct process *sc_owner; /* owner process */
> + struct rwlock sc_lock; /* device lock */
> + uint8_t sc_open; /* device opened */
>
> int sc_fsize;
> uint8_t *sc_fbuffer;
> @@ -122,6 +124,7 @@ videoopen(dev_t dev, int flags, int fmt,
> {
> int unit;
> struct video_softc *sc;
> + int r = 0;
>
> unit = VIDEOUNIT(dev);
> if (unit >= video_cd.cd_ndevs ||
> @@ -129,22 +132,27 @@ videoopen(dev_t dev, int flags, int fmt,
> sc->hw_if == NULL)
> return (ENXIO);
>
> - if (sc->sc_owner != NULL) {
> - if (sc->sc_owner == p->p_p)
> - return (0);
> - else
> - return (EBUSY);
> - } else
> - sc->sc_owner = p->p_p;
> + rw_enter_write(&sc->sc_lock);
> +
> + if (sc->sc_open) {
> + DPRINTF(("%s: device already open\n", __func__));
> + rw_exit_write(&sc->sc_lock);
> + return (r);
> + } else {
> + sc->sc_open = 1;
> + DPRINTF(("%s: set device to open\n", __func__));
> + }
>
> sc->sc_vidmode = VIDMODE_NONE;
> sc->sc_frames_ready = 0;
>
> if (sc->hw_if->open != NULL)
> - return (sc->hw_if->open(sc->hw_hdl, flags, &sc->sc_fsize,
> - sc->sc_fbuffer, video_intr, sc));
> - else
> - return (0);
> + r = sc->hw_if->open(sc->hw_hdl, flags, &sc->sc_fsize,
> + sc->sc_fbuffer, video_intr, sc);
> +
> + rw_exit_write(&sc->sc_lock);
> +
> + return (r);
> }
>
> int
> @@ -155,11 +163,23 @@ videoclose(dev_t dev, int flags, int fmt
>
> sc = video_cd.cd_devs[VIDEOUNIT(dev)];
>
> + rw_enter_write(&sc->sc_lock);
> +
> if (sc->hw_if->close != NULL)
> r = sc->hw_if->close(sc->hw_hdl);
>
> + if (p != NULL) {
> + sc->sc_open = 0;
> + DPRINTF(("%s: last close\n", __func__));
> + }
> + DPRINTF(("%s: stream close\n", __func__));
> +
> + sc->sc_vidmode = VIDMODE_NONE;
> + sc->sc_frames_ready = 0;
> sc->sc_owner = NULL;
>
> + rw_exit_write(&sc->sc_lock);
> +
> return (r);
> }
>
> @@ -175,11 +195,28 @@ videoread(dev_t dev, struct uio *uio, in
> (sc = video_cd.cd_devs[unit]) == NULL)
> return (ENXIO);
>
> - if (sc->sc_dying)
> + rw_enter_write(&sc->sc_lock);
> +
> + if (sc->sc_dying) {
> + rw_exit_write(&sc->sc_lock);
> return (EIO);
> + }
>
> - if (sc->sc_vidmode == VIDMODE_MMAP)
> + if (sc->sc_vidmode == VIDMODE_MMAP) {
> + rw_exit_write(&sc->sc_lock);
> return (EBUSY);
> + }
> +
> + if (sc->sc_owner != NULL && sc->sc_owner != uio->uio_procp->p_p) {
> + DPRINTF(("%s: already owned=%p\n", __func__, sc->sc_owner));
> + return (EBUSY);
> + }
> + if (sc->sc_owner == NULL) {
> + sc->sc_owner = uio->uio_procp->p_p;
> + DPRINTF(("%s: new owner=%p\n", __func__, sc->sc_owner));
> + }
> +
> + rw_exit_write(&sc->sc_lock);
>
> /* start the stream if not already started */
> if (sc->sc_vidmode == VIDMODE_NONE && sc->hw_if->start_read) {
> @@ -205,7 +242,7 @@ videoread(dev_t dev, struct uio *uio, in
> if (!video_record_enable)
> bzero(sc->sc_fbuffer, size);
> error = uiomove(sc->sc_fbuffer, size, uio);
> - sc->sc_frames_ready--;
> + atomic_dec_int_nv(&sc->sc_frames_ready);
> if (error)
> return (error);
>
> @@ -229,6 +266,44 @@ videoioctl(dev_t dev, u_long cmd, caddr_
> DPRINTF(("video_ioctl(%zu, '%c', %zu)\n",
> IOCPARM_LEN(cmd), (int) IOCGROUP(cmd), cmd & 0xff));
>
> + rw_enter_write(&sc->sc_lock);
> +
> + 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) {
> + rw_exit_write(&sc->sc_lock);
> + return (error);
> + }
> +
> + if (sc->sc_owner != NULL && sc->sc_owner != p->p_p) {
> + DPRINTF(("%s: already owned=%p\n", __func__, sc->sc_owner));
> + rw_exit_write(&sc->sc_lock);
> + return (EBUSY);
> + }
> + if (sc->sc_owner == NULL) {
> + sc->sc_owner = p->p_p;
> + DPRINTF(("%s: new owner=%p\n", __func__, sc->sc_owner));
> + }
> +
> + rw_exit_write(&sc->sc_lock);
> +
> + /*
> + * 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:
> @@ -326,6 +401,9 @@ videoioctl(dev_t dev, u_long cmd, caddr_
> if (sc->hw_if->streamoff)
> error = (sc->hw_if->streamoff)(sc->hw_hdl,
> (int)*data);
> + if (!error)
> + /* Release device ownership and streaming buffers. */
> + videoclose(dev, 0, 0, NULL);
> break;
> case VIDIOC_TRY_FMT:
> if (sc->hw_if->try_fmt)
> @@ -337,16 +415,6 @@ videoioctl(dev_t dev, u_long cmd, caddr_
> error = (sc->hw_if->queryctrl)(sc->hw_hdl,
> (struct v4l2_queryctrl *)data);
> break;
> - 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);
> }
> @@ -365,8 +433,24 @@ videopoll(dev_t dev, int events, struct
> (sc = video_cd.cd_devs[unit]) == NULL)
> return (POLLERR);
>
> - if (sc->sc_dying)
> + rw_enter_write(&sc->sc_lock);
> +
> + if (sc->sc_dying) {
> + rw_exit_write(&sc->sc_lock);
> return (POLLERR);
> + }
> +
> + if (sc->sc_owner != NULL && sc->sc_owner != p->p_p) {
> + DPRINTF(("%s: already owned=%p\n", __func__, sc->sc_owner));
> + rw_exit_write(&sc->sc_lock);
> + return (EBUSY);
> + }
> + if (sc->sc_owner == NULL) {
> + sc->sc_owner = p->p_p;
> + DPRINTF(("%s: new owner=%p\n", __func__, sc->sc_owner));
> + }
> +
> + rw_exit_write(&sc->sc_lock);
>
> DPRINTF(("%s: events=0x%x\n", __func__, events));
>
> @@ -412,15 +496,23 @@ videommap(dev_t dev, off_t off, int prot
> (sc = video_cd.cd_devs[unit]) == NULL)
> return (-1);
>
> - if (sc->sc_dying)
> + rw_enter_write(&sc->sc_lock);
> +
> + if (sc->sc_dying) {
> + rw_exit_write(&sc->sc_lock);
> return (-1);
> + }
>
> - if (sc->hw_if->mappage == NULL)
> + if (sc->hw_if->mappage == NULL) {
> + rw_exit_write(&sc->sc_lock);
> return (-1);
> + }
>
> p = sc->hw_if->mappage(sc->hw_hdl, off, prot);
> - if (p == NULL)
> + if (p == NULL) {
> + rw_exit_write(&sc->sc_lock);
> return (-1);
> + }
> if (pmap_extract(pmap_kernel(), (vaddr_t)p, &pa) == FALSE)
> panic("videommap: invalid page");
> sc->sc_vidmode = VIDMODE_MMAP;
> @@ -429,6 +521,8 @@ videommap(dev_t dev, off_t off, int prot
> if (off == 0)
> sc->sc_fbuffer_mmap = p;
>
> + rw_exit_write(&sc->sc_lock);
> +
> return (pa);
> }
>
> @@ -472,8 +566,12 @@ videokqfilter(dev_t dev, struct knote *k
> (sc = video_cd.cd_devs[unit]) == NULL)
> return (ENXIO);
>
> - if (sc->sc_dying)
> + rw_enter_write(&sc->sc_lock);
> +
> + if (sc->sc_dying) {
> + rw_exit_write(&sc->sc_lock);
> return (ENXIO);
> + }
>
> switch (kn->kn_filter) {
> case EVFILT_READ:
> @@ -481,9 +579,22 @@ videokqfilter(dev_t dev, struct knote *k
> kn->kn_hook = sc;
> break;
> default:
> + rw_exit_write(&sc->sc_lock);
> return (EINVAL);
> }
>
> + if (sc->sc_owner != NULL && sc->sc_owner != kn->kn_ptr.p_process) {
> + DPRINTF(("%s: already owned=%p\n", __func__, sc->sc_owner));
> + rw_exit_write(&sc->sc_lock);
> + return (EBUSY);
> + }
> + if (sc->sc_owner == NULL) {
> + sc->sc_owner = kn->kn_ptr.p_process;
> + DPRINTF(("%s: new owner=%p\n", __func__, sc->sc_owner));
> + }
> +
> + rw_exit_write(&sc->sc_lock);
> +
> /*
> * Start the stream in read() mode if not already started. If
> * the user wanted mmap() mode, he should have called mmap()
> @@ -531,7 +642,7 @@ video_intr(void *addr)
>
> DPRINTF(("video_intr sc=%p\n", sc));
> if (sc->sc_vidmode != VIDMODE_NONE)
> - sc->sc_frames_ready++;
> + atomic_inc_int_nv(&sc->sc_frames_ready);
> else
> printf("%s: interrupt but no streams!\n", __func__);
> if (sc->sc_vidmode == VIDMODE_READ)
>
> Index: app/video/video.c
> ===================================================================
> RCS file: /cvs/xenocara/app/video/video.c,v
> retrieving revision 1.39
> diff -u -p -u -p -r1.39 video.c
> --- app/video/video.c 7 Sep 2020 10:35:22 -0000 1.39
> +++ app/video/video.c 7 Feb 2021 12:05:36 -0000
> @@ -2056,6 +2056,8 @@ cleanup(struct video *vid, int excode)
> if (vid->dev.fd >= 0) {
> if (vid->mmap_on)
> mmap_stop(vid);
> + else
> + (void)ioctl(vid->dev.fd, VIDIOC_STREAMOFF, &type);
> close(vid->dev.fd);
> }
>
>