From nobody Tue Aug 11 18:24:30 2026 X-Original-To: dev-commits-src-branches@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 4hKKlh0PLcz6p711 for ; Tue, 11 Aug 2026 18:24:32 +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 4hKKlg3cf6z3fgg for ; Tue, 11 Aug 2026 18:24:31 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1786472671; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=tSrrhbwgxIGgUzk9D4rq+YIlFsKth2zhdCwVP6TUWbA=; b=Bwob6LeCuvvYOJ6lYvd0LlP+cJUHdAn7LcZ5FVIKxtZZNcjZbVI3OilX/6PYpXWYn/F7tY jrGAABfDjJeIKI+8kKvbbCQKhoi15uhBPNaMHcBxg7U+deS7/nTCVswEFFqQh/5krU6Dgt RerImdOJjMGuQ1X8znRV4JgSIcAGlCRWAqOJXvbE7nl2Ej9XAAVckIZlcqL7Atcu7r3BIK Z3FkDEQbolilGk+H6mKZHkFlqdQjmzpgM1norEdspxkO14NeTa2D9PZch/FXLYsWn8Zfwn M/k/arkeE3gbA2GNS4SIN46+2TEI6FoQzFU8Nrre9C7Fb9O48MejUftzKWddTA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1786472671; a=rsa-sha256; cv=none; b=EwPxl6ZpDLIh9teozAilr4R4DR7XMLviGKeVsPbCAqY17nsA0oGuxd1VVC1pwuCSoqGCIO wpZxjnqubTLyXqBN4QJdvo0E2LYVmrU8xRjZ115Bdq+SFyeC5a0kdnVBHdcBomxUAs1T9i tokSqa29+78pNFdSPLfbx1Ld1VoSfuKcY+QRX5aqTpN7pJ9PfzWiysY1AvRV8gr4AT/iNR th77WTom6gTVp6gCxbMFLIky4Q2FxW/d+OtKriGnqZbWSHodWTBRL/CcLaPzepnZ/gWnsA 73gag5IVrVYM6ALzJbKn+bVwDfZxNxiKGNticoI8qIuC4DNXTtTGsCLJ81akpw== 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=1786472671; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=tSrrhbwgxIGgUzk9D4rq+YIlFsKth2zhdCwVP6TUWbA=; b=AiYyzeO5DfB6P1jzz5iVONksjXbPkER3Z5B81en/gOPNrLX3NYOGdPftJZvvnPGVvkIjgZ gyPtGpq8ycSQzm2/3bdUDf5ZQQXm5SvZcf6FY7oXVXOGntCCiUPZTukWMH+kDupW5YLs6D imfDJJEqeoyNEVRVwwQDa8zgsRj4mx2LPk0LnehJpnsictQKU61ipKUy4Grez+/qUQ1ACM Ke19p644XXThON9rbB8drfngPpgCpRrUnis0HfMQ9G0JjZALnaNPLbxgVJ6KnWbej4d+DR 8Pq1RuPpr78JKle9LHvoepEdJAUnwqxpzapq6/4y9qaOZO84GQ9wjqB2iZ/ayg== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4hKKlf5yr1z15sF for ; Tue, 11 Aug 2026 18:24:30 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 1e6e6 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 11 Aug 2026 18:24:30 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org Cc: giacomo From: Christos Margiolis Subject: git: 6886e8a9a0aa - stable/15 - snd_uaudio: Don't let an idle stream reprogram a shared UAC2 clock List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-branches@freebsd.org Sender: owner-dev-commits-src-branches@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: christos X-Git-Repository: src X-Git-Refname: refs/heads/stable/15 X-Git-Reftype: branch X-Git-Commit: 6886e8a9a0aaef8d5666933c40d5dc4a3d098b70 Auto-Submitted: auto-generated Date: Tue, 11 Aug 2026 18:24:30 +0000 Message-Id: <6a7b68de.1e6e6.50e950cb@gitrepo.freebsd.org> The branch stable/15 has been updated by christos: URL: https://cgit.FreeBSD.org/src/commit/?id=6886e8a9a0aaef8d5666933c40d5dc4a3d098b70 commit 6886e8a9a0aaef8d5666933c40d5dc4a3d098b70 Author: giacomo AuthorDate: 2026-07-15 11:34:34 +0000 Commit: Christos Margiolis CommitDate: 2026-08-11 18:24:04 +0000 snd_uaudio: Don't let an idle stream reprogram a shared UAC2 clock Some UAC2 devices expose a single Clock Source entity that is shared between their playback and capture interfaces (it appears in both the output and input clock bitmaps). On such a device uaudio(4) programs the sample rate for both directions when a stream starts. If playback runs at a 44.1 kHz-family rate while the idle capture channel is left at its 48 kHz-family default, the capture SET_CUR(UA20_CS_SAM_FREQ_CONTROL) is issued after the playback one and overwrites the rate on the shared clock. The device then runs at ~48 kHz while the playback stream carries 44.1 kHz data. Consuming samples faster than they arrive, the device repeatedly runs out of data, loses sync with the playback stream, and re-locks onto it (audible dropouts, front-panel play/idle flicker). The 48 kHz family is unaffected because both directions then agree on the rate. Fix it in three parts: - Add a shared-clock guard: before issuing SET_CUR to a clock id, if that clock is shared between playback and capture and the other direction is already streaming at a different rate, skip it. The first active stream owns the clock; a later one follows it. - When the recording channel is auto-started only as a source of jitter information for asynchronous playback, align its nominal rate to the playback rate before starting it, so it neither reprograms the shared clock to a conflicting rate nor produces mismatched frame sizes. - Always submit the explicit-feedback SYNC transfer so dev.pcm.%d.feedback_rate stays live as a diagnostic even when a capture stream is present. Reproduced on an OKTO RESEARCH DAC8 STEREO (0x152a:0x88c5), whose vestigial capture interface never streams; the same device plays the 44.1 kHz family correctly under Linux's snd-usb-audio. As a side effect, this patch also fixes the sample rate bug mentioned in the BUGS section of sound(4)'s man page, where a device needs to have the same sample rate set for both playback and recording in order to work properly. PR: 295933 Assisted-By: Claude Opus 4.8 (claude-opus-4-8) Signed-off-by: giacomo MFC after: 2 weeks Reviewed by: christos Pull-Request: https://github.com/freebsd/freebsd-src/pull/2323 (cherry picked from commit 755685dd665ef209912c59da6a7d0e7f2c9f464b) --- sys/dev/sound/usb/uaudio.c | 103 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 8 deletions(-) diff --git a/sys/dev/sound/usb/uaudio.c b/sys/dev/sound/usb/uaudio.c index 2425666f224d..12823f6de6eb 100644 --- a/sys/dev/sound/usb/uaudio.c +++ b/sys/dev/sound/usb/uaudio.c @@ -1358,6 +1358,46 @@ uaudio_max_buffer_size(struct uaudio_chan *ch, uint8_t alt) return (buf_size); } +static bool +uaudio20_clock_is_shared(struct uaudio_softc *sc, unsigned int x) +{ + /* + * A clock entity that feeds both an OUTPUT (playback) and an + * INPUT (capture) terminal shows up in both bitmaps: it is a + * sample clock shared between the two directions. + */ + return ((sc->sc_mixer_clocks.bit_output[x / 8] & (1 << (x % 8))) != 0 && + (sc->sc_mixer_clocks.bit_input[x / 8] & (1 << (x % 8))) != 0); +} + +static uint32_t +uaudio_dir_running_rate(struct uaudio_chan *chans) +{ + unsigned int i; + + for (i = 0; i != UAUDIO_MAX_CHILD; i++) { + struct uaudio_chan *ch = &chans[i]; + + if (ch->running != 0 && ch->cur_alt < ch->num_alt) + return (ch->usb_alt[ch->cur_alt].sample_rate); + } + return (0); /* nothing streaming in this direction */ +} + +static bool +uaudio_chan_match_rate(struct uaudio_chan *ch, uint32_t rate, uint8_t *p_alt) +{ + uint8_t x; + + for (x = 0; x != ch->num_alt; x++) { + if (ch->usb_alt[x].sample_rate == rate) { + *p_alt = x; + return (true); + } + } + return (false); +} + static void uaudio_configure_msg_sub(struct uaudio_softc *sc, struct uaudio_chan *chan, int dir) @@ -1455,6 +1495,35 @@ uaudio_configure_msg_sub(struct uaudio_softc *sc, } } + /* + * Shared-clock guard. If this clock entity is + * shared between the playback and capture paths, + * and the OTHER direction is already streaming at + * a different rate, do not reprogram the clock -- + * the rate that is already locked wins. This + * stops an idle or secondary stream from yanking + * the shared clock out from under an active + * stream and dropping USB stream lock. The first + * active stream owns the clock; a later one + * follows it (see uaudio_chan_start(), which + * re-aligns the jitter-info record stream to the + * playback rate before it is started). + */ + if (uaudio20_clock_is_shared(sc, x)) { + uint32_t other = (dir == PCMDIR_PLAY) ? + uaudio_dir_running_rate(sc->sc_rec_chan) : + uaudio_dir_running_rate(sc->sc_play_chan); + + if (other != 0 && + other != chan_alt->sample_rate) { + DPRINTF("shared clock ID=%u busy at " + "%u Hz; not reprogramming to " + "%u Hz\n", x, other, + chan_alt->sample_rate); + continue; + } + } + if (uaudio20_set_speed(sc->sc_udev, sc->sc_mixer_iface_no, x, chan_alt->sample_rate)) { /* @@ -2334,13 +2403,10 @@ uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error) case USB_ST_SETUP: /* - * Check if the recording stream can be used as a - * source of jitter information to save some - * isochronous bandwidth: + * Submit the transfer even when the recording stream + * provides the jitter information, so that the feedback + * rate keeps being sampled for diagnostic purposes. */ - if (ch->priv_sc->sc_rec_chan[i].num_alt != 0 && - uaudio_debug == 0) - break; usbd_xfer_set_frames(xfer, 1); usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_framelen(xfer)); usbd_transfer_submit(xfer); @@ -2884,12 +2950,33 @@ uaudio_chan_start(struct uaudio_chan *ch) if (uaudio_chan_need_both( &sc->sc_play_chan[i], &sc->sc_rec_chan[i])) { + struct uaudio_chan *ch_play = &sc->sc_play_chan[i]; + struct uaudio_chan *ch_rec = &sc->sc_rec_chan[i]; + uint8_t rec_alt; + + /* + * The recording channel is only being started as a + * source of jitter information for the playback + * stream. Align its nominal rate with the + * playback rate so that (a) it does not reprogram + * a sample clock shared with the playback path to + * a conflicting rate, and (b) its expected frame + * sizes match what the device actually produces, + * keeping the derived jitter information valid. + * The USB explore lock is held here, which also + * serializes against uaudio_chan_set_param_speed(). + */ + if (uaudio_chan_match_rate(ch_rec, + ch_play->usb_alt[ch_play->set_alt].sample_rate, + &rec_alt)) + ch_rec->set_alt = rec_alt; + /* * Start both endpoints because of need for * jitter information: */ - uaudio_chan_reconfigure(&sc->sc_rec_chan[i], CHAN_OP_START); - uaudio_chan_reconfigure(&sc->sc_play_chan[i], CHAN_OP_START); + uaudio_chan_reconfigure(ch_rec, CHAN_OP_START); + uaudio_chan_reconfigure(ch_play, CHAN_OP_START); } else { uaudio_chan_reconfigure(ch, CHAN_OP_START); }