Index | Thread | Search

From:
Alexandre Ratchov <alex@caoua.org>
Subject:
uaudio: Handle certain devices with multiple clock domains
To:
tech@openbsd.org
Date:
Thu, 13 Nov 2025 14:11:18 +0100

Download raw body.

Thread
uaudio(4) doesn't support devices with multiple "clock domains". They
would be almost multiple independent devices in a single piece of
hardware.

Physical clocks cost money, so I suspect that most devices have one
physical clock only, i.e. that the different clocks the device exposes
are synchronous. If so, uaudio(4) could handle them with the diff
below.

To test this, first use a know working uaudio(4) device to validate
your setup (usb host controller, hub etc). Once you feel audio is
stable, try the device with the multiple clock domains on the same
port.

I'm insisting on this because there are still usb-related bugs, that
would prevent understanding the cause of a possible failure.

Index: uaudio.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/uaudio.c,v
diff -u -p -u -p -r1.180 uaudio.c
--- uaudio.c	2 Nov 2025 14:35:20 -0000	1.180
+++ uaudio.c	13 Nov 2025 13:03:24 -0000
@@ -263,7 +263,7 @@ struct uaudio_softc {
 	/*
 	 * Current clock, UAC v2.0 only
 	 */
-	struct uaudio_unit *clock;
+	struct uaudio_unit *pclock, *rclock;
 
 	/*
 	 * Number of input and output terminals
@@ -1023,7 +1023,7 @@ uaudio_alt_getrates(struct uaudio_softc 
 	case UAUDIO_V1:
 		return p->v1_rates;
 	case UAUDIO_V2:
-		u = sc->clock;
+		u = p->mode == AUMODE_PLAY ? sc->pclock : sc->rclock;
 		while (1) {
 			switch (u->type) {
 			case UAUDIO_AC_CLKSRC:
@@ -1050,11 +1050,8 @@ uaudio_alt_getrates(struct uaudio_softc 
  * return the clock source unit
  */
 struct uaudio_unit *
-uaudio_clock(struct uaudio_softc *sc)
+uaudio_clock(struct uaudio_unit *u)
 {
-	struct uaudio_unit *u;
-
-	u = sc->clock;
 	while (1) {
 		if (u == NULL) {
 			DPRINTF("%s: NULL clock pointer\n", __func__);
@@ -1181,6 +1178,54 @@ uaudio_feature_addent(struct uaudio_soft
 }
 
 /*
+ * Compare two clock source units. According to UAC2 each clock
+ * source defines its own clock domain. However there is hardware
+ * exposing multiple clock units that are clocked by the same source
+ * and consequently are equivalent to single domain.
+ */
+int
+uaudio_clock_equiv(struct uaudio_softc *sc,
+	struct uaudio_unit *u, struct uaudio_unit *v)
+{
+	struct uaudio_ranges_el *ur, *vr;
+
+	if (u == NULL || v == NULL)
+		return 1;
+
+	u = uaudio_clock(u);
+	if (u == NULL)
+		return 0;
+
+	v = uaudio_clock(v);
+	if (v == NULL)
+		return 0;
+
+	if (u->term != v->term) {
+		printf("%s: clock attributes differ\n", DEVNAME(sc));
+		return 0;
+	}
+
+	ur = u->rates.el;
+	vr = v->rates.el;
+	while (1) {
+		if (ur == NULL && vr == NULL)
+			break;
+		if (ur == NULL ||
+		    vr == NULL ||
+		    ur->min != vr->min ||
+		    ur->max != vr->max ||
+		    ur->res != vr->res) {
+			printf("%s: clock rates differ\n", DEVNAME(sc));
+			return 0;
+		}
+		ur = ur->next;
+		vr = vr->next;
+	}
+
+	DPRINTF("%s: %d, %d: equivalent\n", __func__, u->id, v->id);
+	return 1;
+}
+/*
  * For the given unit, parse the list of its sources and recursively
  * call uaudio_process_unit() for each.
  */
@@ -2264,34 +2309,39 @@ uaudio_process_ac(struct uaudio_softc *s
 	}
 
 	if (sc->version == UAUDIO_V2) {
+
 		/*
-		 * Find common clock unit. We assume all terminals
-		 * belong to the same clock domain (ie are connected
-		 * to the same source)
+		 * Find the clocks for the usb-streaming terminal units
 		 */
-		sc->clock = NULL;
+
 		for (u = sc->unit_list; u != NULL; u = u->unit_next) {
-			if (u->type != UAUDIO_AC_INPUT &&
-			    u->type != UAUDIO_AC_OUTPUT)
-				continue;
-			if (sc->clock == NULL) {
+			if (u->type == UAUDIO_AC_INPUT && (u->term >> 8) == 1) {
 				if (u->clock == NULL) {
-					printf("%s: terminal with no clock\n",
-					    DEVNAME(sc));
+					printf("%s: no play clock\n", DEVNAME(sc));
 					return 0;
 				}
-				sc->clock = u->clock;
-			} else if (u->clock != sc->clock) {
-				printf("%s: only one clock domain supported\n",
-				    DEVNAME(sc));
-				return 0;
+				sc->pclock = u->clock;
+				break;
 			}
 		}
 
-		if (sc->clock == NULL) {
-			printf("%s: no clock found\n", DEVNAME(sc));
-			return 0;
+		for (u = sc->unit_list; u != NULL; u = u->unit_next) {
+			if (u->type == UAUDIO_AC_OUTPUT && (u->term >> 8) == 1) {
+				if (u->clock == NULL) {
+					printf("%s: no rec clock\n", DEVNAME(sc));
+					return 0;
+				}
+				sc->rclock = u->clock;
+				break;
+			}
 		}
+
+		DPRINTF("%s: pclock = %d, rclock = %d\n", __func__,
+		    sc->pclock ? sc->pclock->id : -1,
+		    sc->rclock ? sc->rclock->id : -1);
+
+		if (!uaudio_clock_equiv(sc, sc->pclock, sc->rclock))
+			return 0;
 	}
 	return 1;
 }
@@ -3118,7 +3168,7 @@ uaudio_stream_open(struct uaudio_softc *
 		req_buf[1] = sc->rate >> 8;
 		req_buf[2] = sc->rate >> 16;
 		req_buf[3] = sc->rate >> 24;
-		clock = uaudio_clock(sc);
+		clock = uaudio_clock(dir == AUMODE_PLAY ? sc->pclock : sc->rclock);
 		if (clock == NULL) {
 			printf("%s: can't get clock\n", DEVNAME(sc));
 			goto failed;
@@ -3879,7 +3929,8 @@ uaudio_attach(struct device *parent, str
 	sc->names = NULL;
 	sc->alts = NULL;
 	sc->params_list = NULL;
-	sc->clock = NULL;
+	sc->rclock = NULL;
+	sc->pclock = NULL;
 	sc->params = NULL;
 	sc->rate = 0;
 	sc->mode = 0;