Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 22 Jul 2026 15:12:49 +0000
From:      Baptiste Daroussin <bapt@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 2120f3e5ec70 - main - uvideo: track streaming ownership per-fd and free buffers on STREAMOFF
Message-ID:  <6a60ddf1.33e3c.5b935d5f@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by bapt:

URL: https://cgit.FreeBSD.org/src/commit/?id=2120f3e5ec701d92848c259ece4dfbf1aa95f6cb

commit 2120f3e5ec701d92848c259ece4dfbf1aa95f6cb
Author:     Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-07-22 08:14:18 +0000
Commit:     Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2026-07-22 15:10:58 +0000

    uvideo: track streaming ownership per-fd and free buffers on STREAMOFF
    
    The driver shared a single streaming state and buffer pool across all
    open file descriptors, so a second client (e.g. another browser tab)
    could disrupt the first: its cleanup STREAMOFF would tear down the
    active stream, and stale buffers prevented re-acquisition.
    
    Add per-fd state via devfs cdevpriv tracking whether this fd started
    streaming.  STREAMOFF and close from a non-streaming fd are no-ops.
    STREAMOFF from the streaming fd stops the stream and frees the buffers
    so that a new fd can re-acquire the camera.  DQBUF returns EPIPE
    immediately when buffers are freed instead of waiting for a timeout.
---
 sys/dev/usb/video/uvideo.c | 79 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 76 insertions(+), 3 deletions(-)

diff --git a/sys/dev/usb/video/uvideo.c b/sys/dev/usb/video/uvideo.c
index ac54eba19695..bfcd6696a8e0 100644
--- a/sys/dev/usb/video/uvideo.c
+++ b/sys/dev/usb/video/uvideo.c
@@ -2822,10 +2822,31 @@ uvideo_read_frame(struct uvideo_softc *sc, uint8_t *buf, int len)
 /*  Character Device Operations                                     */
 /* ---------------------------------------------------------------- */
 
+/*
+ * Per-fd state (via devfs cdevpriv).  Tracks whether this fd started
+ * streaming so that STREAMOFF or close from a non-streaming fd (e.g. a
+ * second tab that failed REQBUFS) does not tear down the active stream
+ * owned by another fd.
+ */
+struct uvideo_cdevpriv {
+	int			streaming;
+};
+
+static void	uvideo_cdevpriv_dtor(void *);
+
+static void
+uvideo_cdevpriv_dtor(void *data)
+{
+
+	free(data, M_USBDEV);
+}
+
 static int
 uvideo_cdev_open(struct cdev *dev, int flags, int fmt, struct thread *td)
 {
 	struct uvideo_softc *sc = dev->si_drv1;
+	struct uvideo_cdevpriv *priv;
+	int error;
 
 	if (sc == NULL || sc->sc_dying)
 		return (ENXIO);
@@ -2833,6 +2854,13 @@ uvideo_cdev_open(struct cdev *dev, int flags, int fmt, struct thread *td)
 	if (sc->sc_vs_cur == NULL)
 		return (EIO);
 
+	priv = malloc(sizeof(*priv), M_USBDEV, M_WAITOK | M_ZERO);
+	error = devfs_set_cdevpriv(priv, uvideo_cdevpriv_dtor);
+	if (error != 0) {
+		free(priv, M_USBDEV);
+		return (error);
+	}
+
 	mtx_lock(&sc->sc_mtx);
 	if (sc->sc_open == 0) {
 		/* First open: initialize state */
@@ -2853,10 +2881,27 @@ static int
 uvideo_cdev_close(struct cdev *dev, int flags, int fmt, struct thread *td)
 {
 	struct uvideo_softc *sc = dev->si_drv1;
+	struct uvideo_cdevpriv *priv;
 
 	if (sc == NULL)
 		return (0);
 
+	/*
+	 * If this fd started streaming, stop the stream and free the
+	 * buffers so that a new fd (e.g. a refreshed browser tab) can
+	 * re-acquire the camera.  Other fds sharing the stream will get
+	 * EPIPE on DQBUF and should re-open.
+	 */
+	if (devfs_get_cdevpriv((void **)&priv) == 0 && priv != NULL &&
+	    priv->streaming) {
+		priv->streaming = 0;
+		mtx_lock(&sc->sc_mtx);
+		sc->sc_streaming = 0;
+		mtx_unlock(&sc->sc_mtx);
+		uvideo_vs_close(sc);
+		uvideo_vs_free_frame(sc);
+	}
+
 	mtx_lock(&sc->sc_mtx);
 	sc->sc_open--;
 	if (sc->sc_open > 0) {
@@ -2865,7 +2910,7 @@ uvideo_cdev_close(struct cdev *dev, int flags, int fmt, struct thread *td)
 	}
 	mtx_unlock(&sc->sc_mtx);
 
-	/* Last close: stop streaming if active */
+	/* Last close: stop streaming if still active (safety net) */
 	if (sc->sc_streaming) {
 		mtx_lock(&sc->sc_mtx);
 		sc->sc_streaming = 0;
@@ -2892,6 +2937,7 @@ static int
 uvideo_cdev_read(struct cdev *dev, struct uio *uio, int ioflag)
 {
 	struct uvideo_softc *sc = dev->si_drv1;
+	struct uvideo_cdevpriv *priv;
 	usb_error_t error;
 	int ret, fsize;
 
@@ -2903,6 +2949,8 @@ uvideo_cdev_read(struct cdev *dev, struct uio *uio, int ioflag)
 
 	/* Start streaming in read mode if not already running */
 	if (sc->sc_vidmode == VIDMODE_NONE) {
+		if (devfs_get_cdevpriv((void **)&priv) != 0 || priv == NULL)
+			return (EINVAL);
 		sc->sc_mmap_flag = 0;
 		sc->sc_vidmode = VIDMODE_READ;
 
@@ -2928,6 +2976,7 @@ uvideo_cdev_read(struct cdev *dev, struct uio *uio, int ioflag)
 
 		mtx_lock(&sc->sc_mtx);
 		sc->sc_streaming = 1;
+		priv->streaming = 1;
 		if (sc->sc_vs_cur->bulk_endpoint)
 			usbd_transfer_start(sc->sc_xfer[0]);
 		else {
@@ -3702,6 +3751,11 @@ uvideo_dqbuf(struct uvideo_softc *sc, struct v4l2_buffer *dqb)
 	    dqb->memory != V4L2_MEMORY_MMAP)
 		return (EINVAL);
 
+	/* Buffers were freed (e.g. the streaming fd closed); fail fast
+	 * so the caller can re-open instead of waiting for a timeout. */
+	if (sc->sc_mmap_count == 0 || sc->sc_mmap_buffer == NULL)
+		return (EPIPE);
+
 	/*
 	 * Serialize with the USB transfer callbacks (producer) that insert
 	 * completed buffers into sc_mmap_q under sc_mtx.  Use mtx_sleep so
@@ -3743,13 +3797,21 @@ uvideo_dqbuf(struct uvideo_softc *sc, struct v4l2_buffer *dqb)
 static int
 uvideo_streamon(struct uvideo_softc *sc, int type)
 {
+	struct uvideo_cdevpriv *priv;
 	usb_error_t error;
 
 	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
 		return (EINVAL);
 
-	if (sc->sc_streaming)
+	if (devfs_get_cdevpriv((void **)&priv) != 0 || priv == NULL)
+		return (EINVAL);
+
+	mtx_lock(&sc->sc_mtx);
+	if (priv->streaming || sc->sc_streaming) {
+		mtx_unlock(&sc->sc_mtx);
 		return (0);
+	}
+	mtx_unlock(&sc->sc_mtx);
 
 	sc->sc_vidmode = VIDMODE_MMAP;
 
@@ -3759,6 +3821,8 @@ uvideo_streamon(struct uvideo_softc *sc, int type)
 
 	mtx_lock(&sc->sc_mtx);
 	sc->sc_streaming = 1;
+	priv->streaming = 1;
+
 	if (sc->sc_vs_cur->bulk_endpoint)
 		usbd_transfer_start(sc->sc_xfer[0]);
 	else {
@@ -3774,18 +3838,27 @@ uvideo_streamon(struct uvideo_softc *sc, int type)
 static int
 uvideo_streamoff(struct uvideo_softc *sc, int type)
 {
+	struct uvideo_cdevpriv *priv;
 
 	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
 		return (EINVAL);
 
-	if (!sc->sc_streaming)
+	if (devfs_get_cdevpriv((void **)&priv) != 0 || priv == NULL)
+		return (EINVAL);
+
+	/* Only the fd that started streaming may stop it. */
+	if (!priv->streaming) {
 		return (0);
+	}
+
+	priv->streaming = 0;
 
 	mtx_lock(&sc->sc_mtx);
 	sc->sc_streaming = 0;
 	mtx_unlock(&sc->sc_mtx);
 
 	uvideo_vs_close(sc);
+	uvideo_vs_free_frame(sc);
 
 	return (0);
 }


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a60ddf1.33e3c.5b935d5f>