From nobody Wed Jul 22 15:12:49 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4h4yRk1bW4z6mFFc for ; Wed, 22 Jul 2026 15:12:50 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4h4yRk03bfz3klv for ; Wed, 22 Jul 2026 15:12:50 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1784733170; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=dOckhgicRN0blltWDs244CLYuSs0+/7pN2MFPSElFIg=; b=m8aYWPXE96E87T6ZWDnylzOj1+X3gmOZ1KCs3GnnA+eiTD8G2FvH5i4w+WAWBaemMs/pcp Cu2M63ABq38IuPzLPZ0Y7ix3e4bB6Fwwl+pp6GXYquda+FqeoGjCNP/mF2Flc2FsGmTbsJ TY3Tc4X9EJLR0p50ZH5hGperry+3yi2Np8SwlUr2yfcyXKo0gFqljWto+oDlVXnY3KFHAX u+iUTBSDfqkcHv0d43VQYEEGt+OCaE2rrGMZ4IWDPqbI4KEz237oLLkjsY5O9m24Rm22eD Q9WeUIDGKru1rMix8pR0k5VWWmvjyCdslnKHQf662fkQaZxnJwleE2WqvD+xQA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1784733170; a=rsa-sha256; cv=none; b=kQIOhdoIJR+rm0TmxVwVfu+cQ04O1HCmpGThcBsTtk/940n7J4rIyJepH5IARTI/lwRHoj tI0M2IMM4TXYkuabQjNEvT+6vQjl6G04b6bZ2YF7Jrd9NDBmmBmc7tJV99dx0QyN51M2cb A1iko94/aL371oFl3tYxPULLXsIQwEGuC1Aa85t4Yzqv20K97epN/+LqzdCdsAsISRdeEG 0azx663aBtaipeom9AqYDLqmp+fgV39t05IW7OGJqxQ4POH24aXSyoA2BQnAjyLjCnsnr5 u5aUwzkakLm7bmaKn5tFsCB407UeZfYMMnXNvxJgK5ae3/zr0C3wtrGdvkpqIA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1784733170; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=dOckhgicRN0blltWDs244CLYuSs0+/7pN2MFPSElFIg=; b=lsMVQI2BFCNOQkUip6S6OsqRtYvTEzEk31nTcLQwwsczzfEG2y5/1cJM+qX4dftt8XKbo3 9Sf/jS2yimM3pL0tRRnpZQ1qJlC5F1zh54QdNsT4+RBsz5Faxhk2HZJJImOAIxEcUPZqqH 4/pM5UWNTLNfga5oL1if/HhiOVDeQLgrwtCrM2MLCka6sf8axIoapzL43ooOhEwxrX+uco pL5ZwQ3GgSGJJ7e/K/QUdC1mxaBqlaQdWsRr98h8iBb3dNuUBmifazXQR5uAvKFgj6GsfZ MAYrqqe1SUyek+13xmryQz+nyERxHgAPRaefY9S8V8+SGuKR6HPwGqSMT4FcAQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4h4yRj5jTZz7dV for ; Wed, 22 Jul 2026 15:12:49 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 33e3c by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Wed, 22 Jul 2026 15:12:49 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Baptiste Daroussin Subject: git: 2120f3e5ec70 - main - uvideo: track streaming ownership per-fd and free buffers on STREAMOFF List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: bapt X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 2120f3e5ec701d92848c259ece4dfbf1aa95f6cb Auto-Submitted: auto-generated Date: Wed, 22 Jul 2026 15:12:49 +0000 Message-Id: <6a60ddf1.33e3c.5b935d5f@gitrepo.freebsd.org> The branch main has been updated by bapt: URL: https://cgit.FreeBSD.org/src/commit/?id=2120f3e5ec701d92848c259ece4dfbf1aa95f6cb commit 2120f3e5ec701d92848c259ece4dfbf1aa95f6cb Author: Baptiste Daroussin AuthorDate: 2026-07-22 08:14:18 +0000 Commit: Baptiste Daroussin 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); }