On Tue, Jan 05, 2021 at 11:54:31PM +0100, Jeremie Courreges-Anglas wrote: > > I hit a weird failure with firefox and BigBlueButton > (https://bigbluebutton.org/) where firefox can't use my webcam. > video(1) works, same for other webrtc sites in firefox, eg meet.jit.si. > ktrace shows that a single firefox process tries to open /dev/video0 > more than once, and that fails with EBUSY. The code lies in the > libwebrtc library > > > https://webrtc.googlesource.com/src/+/refs/heads/master/modules/video_capture/linux/ > > In my tests the multiple open() calls only happen at initialization > time, video streaming only uses one fd. > > Back to our kernel, the current restrictive behavior was introduced with > > revision 1.18 > date: 2008/07/23 22:10:21; author: mglocker; state: Exp; lines: +11 -4; > If /dev/video* is already used by an application, return EBUSY to other > applications. Fixes a kernel panic. > > Reported by ian@ > > Meanwhile, the V4L2 API specifies that a device "can be opened more than > once" from multiple processes, with access to certain methods blocked > when an application starts reading from the device. > > > https://www.kernel.org/doc/html/v5.10/userspace-api/media/v4l/open.html#multiple-opens > > So the API says we're being too restrictive, but we don't want to go > back to uncontrolled access either. I guess the ideal solution would be > to implement multiple process access with fine-grained checks, but... > I don't have time for that!
Oh, I guess that makes video(1) an "old and obscure" driver according to the API [2] footnote ;-) Finally we should implement the controls for multiple video device openings based on what has been exactly requested. Since the basic idea mentioned in the API is that other processes, like a panel application, can be started to e.g. change controls. I will have a look at that when I find some time. > An easy improvement for my use case would be to allow the same process > to open a device multiple times. It fixes firefox + BigBlueButton for > me and doesn't make concurrent accesses worse (multiple threads from the > same process can already do concurrent accesses, which is something > we might want to look at). Interesting. I was not aware that threads of the same process can do that. I need to have a closer look at what is happening here. > Here's the diff. IIUC the use of atomic operations isn't strictly > needed here since open(2) runs with the kernel lock, but the result > is easy to understand IMO. I don't agree with that, see my comments bellow in point a) and b). > Thoughts? ok? I think it doesn't harm to allow the same process to do multiple opens on video(1) as a first improvement. But I'm not happy using atomic_cas_ptr(9) and atomic_swap_ptr(9) for this because: a) As you already have mentioned, we don't require atomic operations here. I checked that those functions are very rarely used in the kernel, and only there were atomic operation is required. I'm afraid that when we use those in video(1), and somebody looks at the code later on to eventually replace it, there will be confusion if there was a specific reason why to use atomic functions. b) IMO it doesn't simplify the code. I first had to read the manual pages to understand how those functions work. I would prefer to do something simpler, like in this adapted diff. If it works for you, I'm OK if you commit this instead. > Index: dev/video.c > =================================================================== > RCS file: /d/cvs/src/sys/dev/video.c,v > retrieving revision 1.46 > diff -u -p -r1.46 video.c > --- dev/video.c 28 Dec 2020 18:28:11 -0000 1.46 > +++ dev/video.c 5 Jan 2021 22:34:04 -0000 > @@ -28,6 +28,8 @@ > #include <sys/kernel.h> > #include <sys/malloc.h> > #include <sys/conf.h> > +#include <sys/atomic.h> > +#include <sys/proc.h> > #include <sys/videoio.h> > > #include <dev/video_if.h> > @@ -46,8 +48,7 @@ struct video_softc { > struct device *sc_dev; /* hardware device struct */ > struct video_hw_if *hw_if; /* hardware interface */ > char sc_dying; /* device detached */ > -#define VIDEO_OPEN 0x01 > - char sc_open; > + struct process *sc_owner; /* owner process */ > > int sc_fsize; > uint8_t *sc_fbuffer; > @@ -101,6 +102,7 @@ videoattach(struct device *parent, struc > 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); > @@ -121,6 +123,7 @@ videoopen(dev_t dev, int flags, int fmt, > { > int unit; > struct video_softc *sc; > + struct process *owner; > > unit = VIDEOUNIT(dev); > if (unit >= video_cd.cd_ndevs || > @@ -128,9 +131,13 @@ videoopen(dev_t dev, int flags, int fmt, > sc->hw_if == NULL) > return (ENXIO); > > - if (sc->sc_open & VIDEO_OPEN) > - return (EBUSY); > - sc->sc_open |= VIDEO_OPEN; > + owner = atomic_cas_ptr(&sc->sc_owner, NULL, p->p_p); > + if (owner != NULL) { > + if (owner == p->p_p) > + return (0); > + else > + return (EBUSY); > + } > > sc->sc_vidmode = VIDMODE_NONE; > sc->sc_frames_ready = 0; > @@ -153,7 +160,7 @@ videoclose(dev_t dev, int flags, int fmt > if (sc->hw_if->close != NULL) > r = sc->hw_if->close(sc->hw_hdl); > > - sc->sc_open &= ~VIDEO_OPEN; > + atomic_swap_ptr(&sc->sc_owner, NULL); > > return (r); > } Index: dev/video.c =================================================================== RCS file: /cvs/src/sys/dev/video.c,v retrieving revision 1.46 diff -u -p -u -p -r1.46 video.c --- dev/video.c 28 Dec 2020 18:28:11 -0000 1.46 +++ dev/video.c 6 Jan 2021 13:19:38 -0000 @@ -28,6 +28,7 @@ #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> @@ -46,8 +47,7 @@ struct video_softc { struct device *sc_dev; /* hardware device struct */ struct video_hw_if *hw_if; /* hardware interface */ char sc_dying; /* device detached */ -#define VIDEO_OPEN 0x01 - char sc_open; + struct process *sc_owner; /* owner process */ int sc_fsize; uint8_t *sc_fbuffer; @@ -101,6 +101,7 @@ videoattach(struct device *parent, struc 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); @@ -128,9 +129,13 @@ videoopen(dev_t dev, int flags, int fmt, sc->hw_if == NULL) return (ENXIO); - if (sc->sc_open & VIDEO_OPEN) - return (EBUSY); - sc->sc_open |= VIDEO_OPEN; + if (sc->sc_owner != NULL) { + if (sc->sc_owner == p->p_p) + return (0); + else + return (EBUSY); + } else + sc->sc_owner = p->p_p; sc->sc_vidmode = VIDMODE_NONE; sc->sc_frames_ready = 0; @@ -153,7 +158,7 @@ videoclose(dev_t dev, int flags, int fmt if (sc->hw_if->close != NULL) r = sc->hw_if->close(sc->hw_hdl); - sc->sc_open &= ~VIDEO_OPEN; + sc->sc_owner = NULL; return (r); }