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.


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

Reply via email to