On Sat, Feb 13, 2021 at 10:45:17AM +0100, Claudio Jeker wrote:

> On Sat, Feb 13, 2021 at 10:26:48AM +0100, Marcus Glocker wrote:
> > On Sat, Feb 13, 2021 at 08:30:04AM +0100, Claudio Jeker wrote:
> > 
> > > On Fri, Feb 12, 2021 at 10:59:05PM +0100, Jeremie Courreges-Anglas wrote:
> > > > On Wed, Feb 10 2021, Martin Pieuchot <m...@openbsd.org> wrote:
> > > > 
> > > > [...]
> > > > 
> > > > > Which fields is the new lock protecting?  Why isn't the KERNEL_LOCK()
> > > > > enough?
> > > > 
> > > > When I mentioned this potential lack of locking to Marcus, I was
> > > > mistaken.  Some of the functions in video.c are called from syscalls
> > > > that are marked NOLOCK.  But now that I have added some printf
> > > > debugging, I can see that the kernel lock is held in places where
> > > > I didn't expect it to be held (videoioctl, videoread, videoclose...).
> > > > So there's probably a layer of locking that I am missing.
> > > > 
> > > > Marcus, sorry for my misleading diff. O:)
> > > > 
> > > > *crawls back under his rock*
> > > 
> > > Just because a syscall is marked NOLOCK does not mean that all of it will
> > > run without the KERNEL_LOCK. For example read and write are marked NOLOCK
> > > but the KERNEL_LOCK is grabbed later for all non-socket filedescriptors.
> > > This is why video(4) still runs with the KERNEL_LOCK.
> > > The NOLOCK in syscalls.master just tell the kernel to not grab the
> > > KERNEL_LOCK right at the start of the syscall.
> > 
> > OK, that wasn't clear to me either!  If Jeremie has some space left
> > under his rock, I would join :-)
> > 
> > That basically means we need no extra locking in video(4) for this
> > implementation, right?  For a test I replaced the rw_enter_write()
> > with KERNEL_ASSERT_LOCKED(), running a stream and multiple concurrent
> > ioctl programs, and all went fine.
> > 
> > I would come up with a revised diff then for the device owner part.
> > Just doing some polishing together with Anton on that.
> 
> For now this is OK. I have some diffs around which will allow unlocked
> read and write down to individual devices (I was experimenting with tun(4)
> and tap(4)). For ioctl() this will not happen any time soon. ioctl() is a
> world of pain. Maybe for devices like video(4) the call path will be
> manageable but for network devices it is just mess. The main issue is that
> ioctls work on multiple layers and sometimes the calls have layer
> violations and often multiple sleep points built into them.

Got it.  Thanks for the explanation.
Here the adapted diff, including a lot of input from anton@.
I sprinkled KERNEL_ASSERT_LOCKED() in those functions where we require
locking.  Not sure if we should keep them in?


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     13 Feb 2021 09:49:45 -0000
@@ -47,7 +47,8 @@ struct video_softc {
        struct device           *sc_dev;        /* hardware device struct */
        struct video_hw_if      *hw_if;         /* hardware interface */
        char                     sc_dying;      /* device detached */
-       struct process           *sc_owner;     /* owner process */
+       struct process          *sc_owner;      /* owner process */
+       uint8_t                  sc_open;       /* device opened */
 
        int                      sc_fsize;
        uint8_t                 *sc_fbuffer;
@@ -69,6 +70,8 @@ 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 *);
 
 struct cfattach video_ca = {
        sizeof(struct video_softc), videoprobe, videoattach,
@@ -122,6 +125,9 @@ videoopen(dev_t dev, int flags, int fmt,
 {
        int     unit;
        struct video_softc *sc;
+       int error = 0;
+
+       KERNEL_ASSERT_LOCKED();
 
        unit = VIDEOUNIT(dev);
        if (unit >= video_cd.cd_ndevs ||
@@ -129,38 +135,40 @@ 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;
+       if (sc->sc_open) {
+               DPRINTF(("%s: device already open\n", __func__));
+               return (0);
+       } 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);
+               error = sc->hw_if->open(sc->hw_hdl, flags, &sc->sc_fsize,
+                   sc->sc_fbuffer, video_intr, sc);
+
+       return (error);
 }
 
 int
 videoclose(dev_t dev, int flags, int fmt, struct proc *p)
 {
        struct video_softc *sc;
-       int r = 0;
+       int error = 0;
 
-       sc = video_cd.cd_devs[VIDEOUNIT(dev)];
+       KERNEL_ASSERT_LOCKED();
 
-       if (sc->hw_if->close != NULL)
-               r = sc->hw_if->close(sc->hw_hdl);
+       DPRINTF(("%s: last close\n", __func__));
 
-       sc->sc_owner = NULL;
+       sc = video_cd.cd_devs[VIDEOUNIT(dev)];
+
+       sc->sc_open = 0;
+       error = video_stop(sc);
 
-       return (r);
+       return (error);
 }
 
 int
@@ -170,6 +178,8 @@ videoread(dev_t dev, struct uio *uio, in
        int unit, error;
        size_t size;
 
+       KERNEL_ASSERT_LOCKED();
+
        unit = VIDEOUNIT(dev);
        if (unit >= video_cd.cd_ndevs ||
            (sc = video_cd.cd_devs[unit]) == NULL)
@@ -181,6 +191,9 @@ videoread(dev_t dev, struct uio *uio, in
        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);
@@ -221,6 +234,8 @@ videoioctl(dev_t dev, u_long cmd, caddr_
        struct v4l2_buffer *vb = (struct v4l2_buffer *)data;
        int unit, error;
 
+       KERNEL_ASSERT_LOCKED();
+
        unit = VIDEOUNIT(dev);
        if (unit >= video_cd.cd_ndevs ||
            (sc = video_cd.cd_devs[unit]) == NULL || sc->hw_if == NULL)
@@ -231,6 +246,31 @@ videoioctl(dev_t dev, u_long cmd, caddr_
 
        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,
@@ -326,6 +366,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. */
+                       video_stop(sc);
                break;
        case VIDIOC_TRY_FMT:
                if (sc->hw_if->try_fmt)
@@ -337,16 +380,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);
        }
@@ -361,6 +394,8 @@ videopoll(dev_t dev, int events, struct 
        struct video_softc *sc;
        int error, revents = 0;
 
+       KERNEL_ASSERT_LOCKED();
+
        if (unit >= video_cd.cd_ndevs ||
            (sc = video_cd.cd_devs[unit]) == NULL)
                return (POLLERR);
@@ -368,6 +403,9 @@ videopoll(dev_t dev, int events, struct 
        if (sc->sc_dying)
                return (POLLERR);
 
+       if ((error = video_claim(sc, p->p_p)))
+               return (error);
+
        DPRINTF(("%s: events=0x%x\n", __func__, events));
 
        if (events & (POLLIN | POLLRDNORM)) {
@@ -405,6 +443,8 @@ videommap(dev_t dev, off_t off, int prot
        caddr_t p;
        paddr_t pa;
 
+       KERNEL_ASSERT_LOCKED();
+
        DPRINTF(("%s: off=%lld, prot=%d\n", __func__, off, prot));
 
        unit = VIDEOUNIT(dev);
@@ -466,7 +506,9 @@ videokqfilter(dev_t dev, struct knote *k
 {
        int unit = VIDEOUNIT(dev);
        struct video_softc *sc;
-       int s;
+       int s, error;
+
+       KERNEL_ASSERT_LOCKED();
 
        if (unit >= video_cd.cd_ndevs ||
            (sc = video_cd.cd_devs[unit]) == NULL)
@@ -484,6 +526,9 @@ videokqfilter(dev_t dev, struct knote *k
                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()
@@ -537,6 +582,39 @@ video_intr(void *addr)
        if (sc->sc_vidmode == VIDMODE_READ)
                wakeup(sc);
        selwakeup(&sc->sc_rsel);
+}
+
+int
+video_stop(struct video_softc *sc)
+{
+       int error = 0;
+
+       DPRINTF(("%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 *p)
+{
+       if (sc->sc_owner != NULL && sc->sc_owner != p) {
+               DPRINTF(("%s: already owned=%p\n", __func__, sc->sc_owner));
+               return (EBUSY);
+       }
+
+       if (sc->sc_owner == NULL) {
+               sc->sc_owner = p;
+               DPRINTF(("%s: new owner=%p\n", __func__, sc->sc_owner));
+       }
+
+       return (0);
 }
 
 int

Reply via email to