Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 08 Jun 2026 12:04:11 +0000
From:      bugzilla-noreply@freebsd.org
To:        bugs@FreeBSD.org
Subject:   [Bug 295933] uaudio(4): idle capture stream clobbers active playback sample rate on  devices with a shared UAC2 Clock Source - device unusable
Message-ID:  <bug-295933-227@https.bugs.freebsd.org/bugzilla/>

index | next in thread | raw e-mail

https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=295933

            Bug ID: 295933
           Summary: uaudio(4): idle capture stream clobbers active
                    playback sample rate on  devices with a shared UAC2
                    Clock Source - device unusable
           Product: Base System
           Version: 15.0-RELEASE
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Only Me
          Priority: ---
         Component: kern
          Assignee: bugs@FreeBSD.org
          Reporter: delleceste@gmail.com

## TL;DR

On a UAC2 device that exposes **one Clock Source entity shared between its
playback and capture interfaces**, `uaudio(4)` programs that clock for *both*
directions. When playback uses a sample rate from the **44.1 kHz family**
(44100 / 88200 / 176400 / 352800 Hz) and the (idle, non‑streaming) capture
channel is configured for a **48 kHz‑family** default, the capture channel's
`SET_CUR(CUR_SAM_FREQ)` is issued **after** the playback one and **overwrites
it on the shared clock**. The device's converter then runs at ~48 kHz while the
host streams 44.1 kHz data → continuous input‑FIFO underrun inside the device →
the DAC repeatedly drops and re‑acquires USB streaming lock (audible dropouts;
front‑panel "play/idle" flicker several times per second).

* The **48 kHz family is unaffected** — both directions then request 48000, so
  there is no conflict on the shared clock.
* The **same device works perfectly under Linux** (`snd-usb-audio`), which does
  not let an idle capture stream reprogram the clock used by active playback.
* A device with **no capture interface** (Cambridge Audio DacMagic 100) never
  shows the problem on the same FreeBSD host.

The host side is verifiably healthy throughout (`underruns 0`, channel stays
`RUNNING`); the fault is the **wrong clock rate programmed into the device**,
not
data starvation.

---

## Affected component and environment

| Item | Value |
|------|-------|
| Driver | `sys/dev/sound/usb/uaudio.c` (`uaudio(4)` / `snd_uaudio.ko`) |
| OS (reproduced on) | FreeBSD **15.1‑RC1**, amd64, `GENERIC`
(`releng/15.1-n283533`) |
| Expected to affect | All branches; the code paths below are long‑standing |
| Host controller | Intel Sunrise Point‑LP xHCI, USB High‑Speed (480 Mbps) |
| Reference player | MPD 0.24.12, OSS output to `/dev/dsp0`,
`dev.pcm.0.bitperfect=1` |

### Device used to reproduce
| Item | Value |
|------|-------|
| Device | OKTO RESEARCH **DAC8 STEREO** (D/A only — no analog inputs) |
| USB IDs | idVendor `0x152a`, idProduct `0x88c5`, bcdDevice `0x0160` |
| Firmware | Thesycon UAC2 (`bInterfaceProtocol 0x20`), `bcdUSB 0x0200` |
| Link | USB High‑Speed |

> Note: although the DAC8 has **no physical inputs**, its USB descriptor still
> advertises a UAC2 **capture interface** (Interface 2) — common boilerplate in
> Thesycon/XMOS reference firmware. It is this *vestigial, never‑streaming*
> capture interface that triggers the bug.

## Root cause — measured

### 1. Two `SET_CUR` calls to the shared clock, capture wins

With `sysctl hw.usb.uaudio.debug=15` (GENERIC builds `options USB_DEBUG`), at
the
start of 44.1 kHz playback:

```
uaudio_chan_set_param_speed: Selecting alt 6
uaudio_chan_set_param_speed: Selecting alt 7
uaudio20_set_speed: ifaceno=0 clockid=41 speed=44100     <-- playback sets
shared clock to 44100
uaudio20_set_speed: ifaceno=0 clockid=41 speed=48000     <-- capture default
48000 then overwrites it
```

Same `clockid=41`. The second call wins; the device clock ends at 48 kHz.

### 2. The capture channel is idle, yet it owns the clock

`cat /dev/sndstat` with `hw.snd.verbose=2`, during 44.1 kHz playback:

```
[dsp0.play.0]:   spd 44100, fmt 0x00201000, flags
...<RUNNING,TRIGGERED,NBIO,BUSY,BITPERFECT>
                 interrupts <climbing>, underruns 0, feed <climbing>, ready
<near full>
[dsp0.record.0]: spd 48000, fmt 0x00200010/0x00201000, flags 0x00000000   <--
NOT running, but holds 48000
```

The record channel has flags `0x00000000` (not `RUNNING`) — it is not streaming
a single byte — yet its 48000 Hz configuration is what sits on the shared
clock.

### 3. The device confirms it is running at 48 kHz, not 44.1 kHz

The playback OUT endpoint is asynchronous with an explicit feedback IN endpoint
(`0x81`, Q16.16 at High‑Speed). While the host streams 44100:

```
uaudio_chan_play_sync_callback: Value = 0x0006000a
uaudio_chan_play_sync_callback: Comparing 48001 Hz :: 44100 Hz
```

`0x0006000a` in 16.16 = 6.0001 samples/microframe × 8000 = **~48001 Hz**. The
device's converter is clocked at 48 kHz while being fed 44.1 kHz frames → it
drains ~3900 samples/s faster than it is filled → underrun/mute/relock cycle.

### 4. It is a host/driver problem, not the device firmware

* The **same device, cable and host play the 44.1 kHz family perfectly under
  Linux** (`snd-usb-audio`). Linux programs the shared clock from the active
  playback stream and does not let the idle capture stream override it.
* A DAC with **no capture interface** (DacMagic 100) is stable at every rate on
  the same FreeBSD host.

---

## Relevant code paths

Line numbers are approximate, from FreeBSD **15.1‑RC1**
`sys/dev/sound/usb/uaudio.c`.

1. **`uaudio_configure_msg()` (~line 1539)** — runs on the USB explore task and
   unconditionally reconfigures *both* directions of every child:
   ```c
   for (i = 0; i != UAUDIO_MAX_CHILD; i++) {
       uaudio_configure_msg_sub(sc, &sc->sc_play_chan[i], PCMDIR_PLAY);
       uaudio_configure_msg_sub(sc, &sc->sc_rec_chan[i], PCMDIR_REC);
   }
   ```

2. **`uaudio_configure_msg_sub()` (~line 1352)** — on `CHAN_OP_START` it
selects
   the alt setting and then, for UAC2, programs the sample rate by iterating
   every clock id flagged for the channel's direction and calling
   `uaudio20_set_speed()` (~line 1449):
   ```c
   } else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
       for (x = 0; x != 256; x++) {
           if (dir == PCMDIR_PLAY) {
               if (!(sc->sc_mixer_clocks.bit_output[x/8] & (1u << (x%8))))
continue;
           } else {
               if (!(sc->sc_mixer_clocks.bit_input[x/8]  & (1u << (x%8))))
continue;
           }
           if (uaudio20_set_speed(sc->sc_udev, sc->sc_mixer_iface_no, x,
chan_alt->sample_rate))
               DPRINTF("setting of sample rate failed! (continuing anyway)\n");
       }
   }
   ```
   Because clock id 41 is set in **both** `bit_output` and `bit_input`
   (shared clock — see `uaudio20_mixer_find_clocks_sub()` ~line 4858), the REC
   pass reprograms the very clock the PLAY pass just set.

3. **Asynchronous feedback handling — secondary issue.** In
   `uaudio_chan_play_sync_callback()` (~line 2255) the explicit feedback
endpoint
   value is only turned into a rate correction when there is **no** capture
   channel (`if (ch->priv_sc->sc_rec_chan[i].num_alt == 0)`, ~line 2306), and
the
   feedback transfer is not even submitted when a capture channel exists
   (~line 2332). For a device whose capture interface never streams, this means
   the explicit feedback endpoint is ignored, so there is no closed‑loop
   correction to mask a mis‑programmed clock either.

---

## Proposed fix (direction)

**Primary — do not let an idle/secondary direction reprogram a clock that is
shared with the active streaming direction.** The active stream must own the
shared Clock Source. Concretely, in the UAC2 rate‑setting loop of
`uaudio_configure_msg_sub()`, before issuing `SET_CUR` to a clock id, skip it
if
that clock id is also referenced by the *other* direction's channel that is
currently `RUNNING` at a different rate. Equivalently: only program a clock for
a
channel that is the one actually transitioning to `RUNNING`, and never let a
non‑running channel push its default rate onto a clock another running channel
depends on.

UAC2 constraint to respect: a single Clock Source physically cannot serve two
different rates simultaneously, so when both directions stream concurrently
they
must already agree on one rate; that concurrent case needs its own coherent
handling (e.g. the second stream adopts the first's rate). The common failure
mode here, however, is purely an **idle** capture stream forcing its default —
which should never override active playback.

**Secondary (optional, matches Linux behaviour).** Consider honouring the
device's explicit asynchronous feedback endpoint even when a capture interface
is present, rather than only when `sc_rec_chan[i].num_alt == 0`. Not required
if
the clock is programmed correctly, but it brings `uaudio` in line with
`snd-usb-audio` for async devices.

### Diagnostic stopgap used to confirm the hypothesis (NOT proposed for
upstream)

To verify the diagnosis, the reporter dropped the capture channels for this one
device, right after `uaudio_chan_fill_info()` in `uaudio_attach()`:

```c
/* local diagnostic only — confirms the shared-clock hypothesis */
if (uaa->info.idVendor == 0x152a && uaa->info.idProduct == 0x88c5) {
    for (i = 0; i != UAUDIO_MAX_CHILD; i++)
        sc->sc_rec_chan[i].num_alt = 0;
}
```

Result: the OKTO then enumerates play‑only, the shared clock follows playback,
and **44.1 kHz locks cleanly and bit‑perfect** (`underruns 0`, gap‑free, stable
front panel). This is offered only as confirmation of the root cause; the real
fix should be the general clock‑ownership change above, which preserves capture
for devices that genuinely record.

---

## Reproduction

1. FreeBSD 15.1‑RC1, `uaudio(4)`, a UAC2 device whose playback and capture
   interfaces share one Clock Source, on a High‑Speed port.
2. Play bit‑perfect audio at any **44.1 kHz‑family** rate to `/dev/dsp0`
   (e.g. MPD OSS output, `dev.pcm.0.bitperfect=1`, no rate enforcement).
   → device drops/re‑acquires lock continuously; `sndstat` shows the play
   channel `RUNNING` with `underruns 0`, and the record channel idle at a
   48 kHz‑family rate.
3. Play any **48 kHz‑family** rate → stable.
4. Same machine/cable/port under Linux (`snd-usb-audio`) → 44.1 kHz plays
   perfectly.

---

-- 
You are receiving this mail because:
You are the assignee for the bug.

home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?bug-295933-227>