ref: 8074ea5bd3e2ed9f9add960a732ad21ad3955253
parent: 229d14ae06ab148aa7185ed7cb21f410f49a93a9
author: Michael Forney <mforney@mforney.org>
date: Fri Nov 18 04:21:12 EST 2022
nusb/audio: enumerate streams through control interface USB audio 2.0 splits the sample rate control from the AudioStreaming alternate settings to a clock entity connected to input/output terminals associated with the AudioStreaming interfaces. So, in order to pair endpoints with their corresponding clock, we must have a better picture of the audio function topology. The AudioControl interface tells us which streams are available, so we can go through each one, determining the possible stream parameters and locating the associated endpoint. This will give us the necessary context to locate the corresponding terminal (and soon, clock). When we go to setup input/output endpoints, we now only have to consider those that we have selected earlier.
--- a/sys/src/cmd/nusb/audio/audio.c
+++ b/sys/src/cmd/nusb/audio/audio.c
@@ -19,16 +19,21 @@
typedef struct Range Range;
struct Range
{
- Range *next;
- int min;
- int max;
+ uint min;
+ uint max;
};
typedef struct Aconf Aconf;
struct Aconf
{
+ Ep *ep;
+ int bits;
+ int bps; /* subslot size (bytes per sample) */
+ int format;
+ int channels;
+ int controls;
Range *freq;
- int caps;
+ int nfreq;
};
int audiodelay = 1764; /* 40 ms */
@@ -39,59 +44,128 @@
char user[] = "audio";
Dev *audiodev = nil;
-
+Iface *audiocontrol = nil;
Ep *audioepin = nil;
Ep *audioepout = nil;
-void
-parsedescr(Desc *dd)
+Iface*
+findiface(Conf *conf, int class, int subclass, int id)
{
- Aconf *c;
- Range *f;
- uchar *b;
int i;
+ Iface *iface;
- if(dd == nil || dd->iface == nil)
- return;
- if(Subclass(dd->iface->csp) != 2)
- return;
+ for(i = 0; i < nelem(conf->iface); i++){
+ iface = conf->iface[i];
+ if(iface == nil || Class(iface->csp) != class || Subclass(iface->csp) != subclass)
+ continue;
+ if(id == -1 || iface->id == id)
+ return iface;
+ }
+ return nil;
+}
- c = dd->iface->aux;
- if(c == nil){
- c = mallocz(sizeof(*c), 1);
- dd->iface->aux = c;
+Desc*
+findacheader(Usbdev *u, Iface *ac)
+{
+ Desc *dd;
+ uchar *b;
+ int i;
+
+ for(i = 0; i < nelem(u->ddesc); i++){
+ dd = u->ddesc[i];
+ if(dd == nil || dd->iface != ac || dd->data.bDescriptorType != 0x24)
+ continue;
+ if(dd->data.bLength < 8 || dd->data.bbytes[0] != 1)
+ continue;
+ b = dd->data.bbytes;
+ if(dd->data.bLength == 8+b[5])
+ return dd;
}
+ return nil;
+}
- b = (uchar*)&dd->data;
- switch(b[1]<<8 | b[2]){
+void
+parseasdesc(Desc *dd, Aconf *c)
+{
+ uchar *b;
+ Range *f;
+
+ b = dd->data.bbytes;
+ switch(dd->data.bDescriptorType<<8 | b[0]){
case 0x2501: /* CS_ENDPOINT, EP_GENERAL */
- c->caps |= b[3];
+ if(dd->data.bLength != 7)
+ return;
+ c->controls = b[1];
break;
+ case 0x2401: /* CS_INTERFACE, AS_GENERAL */
+ if(dd->data.bLength != 7)
+ return;
+ c->format = GET2(&b[3]);
+ break;
+
case 0x2402: /* CS_INTERFACE, FORMAT_TYPE */
- if(b[4] != audiochan)
- break;
- if(b[6] != audiores)
- break;
+ if(dd->data.bLength < 8 || b[1] != 1)
+ return;
+ c->channels = b[2];
+ c->bps = b[3];
+ c->bits = b[4];
+ if(b[5] == 0){ /* continuous frequency range */
+ c->nfreq = 1;
+ c->freq = emallocz(sizeof(*f), 1);
+ c->freq->min = b[6] | b[7]<<8 | b[8]<<16;
+ c->freq->max = b[9] | b[10]<<8 | b[11]<<16;
+ }else{ /* discrete sampling frequencies */
+ c->nfreq = b[5];
+ c->freq = emallocz(c->nfreq * sizeof(*f), 1);
+ b += 6;
+ for(f = c->freq; f < c->freq+c->nfreq; f++, b += 3){
+ f->min = b[0] | b[1]<<8 | b[2]<<16;
+ f->max = f->min;
+ }
+ }
+ break;
+ }
+}
- if(b[7] == 0){
- f = mallocz(sizeof(*f), 1);
- f->min = b[8] | b[9]<<8 | b[10]<<16;
- f->max = b[11] | b[12]<<8 | b[13]<<16;
+void
+parsestream(Dev *d, int id)
+{
+ Iface *as;
+ Desc *dd;
+ Ep *e;
+ Aconf *c;
+ int i;
- f->next = c->freq;
- c->freq = f;
- } else {
- for(i=0; i<b[7]; i++){
- f = mallocz(sizeof(*f), 1);
- f->min = b[8+3*i] | b[9+3*i]<<8 | b[10+3*i]<<16;
- f->max = f->min;
+ /* find AS interface */
+ as = findiface(d->usb->conf[0], Claudio, 2, id);
- f->next = c->freq;
- c->freq = f;
+ /* enumerate through alt. settings */
+ for(; as != nil; as = as->next){
+ c = emallocz(sizeof(*c), 1);
+ as->aux = c;
+
+ /* find AS endpoint */
+ for(i = 0; i < nelem(as->ep); i++){
+ e = as->ep[i];
+ if(e != nil && e->type == Eiso && (e->attrib>>4 & 3) == Edata){
+ c->ep = e;
+ break;
}
}
- break;
+ if(c->ep == nil){
+ free(c);
+ as->aux = nil;
+ continue;
+ }
+
+ /* parse AS descriptors */
+ for(i = 0; i < nelem(d->usb->ddesc); i++){
+ dd = d->usb->ddesc[i];
+ if(dd == nil || dd->iface != as)
+ continue;
+ parseasdesc(dd, c);
+ }
}
}
@@ -103,12 +177,12 @@
Range *f;
for(;e != nil; e = e->next){
- if(e->dir!=dir)
- continue;
c = e->iface->aux;
- if(c == nil)
+ if(c == nil || e != c->ep || e->dir != dir)
continue;
- for(f = c->freq; f != nil; f = f->next)
+ if(c->format != 1 || c->bits != audiores || 8*c->bps != audiores || c->channels != audiochan)
+ continue;
+ for(f = c->freq; f != c->freq+c->nfreq; f++)
if(speed >= f->min && speed <= f->max)
goto Foundaltc;
}
@@ -119,7 +193,7 @@
if(setalt(d, e->iface) < 0)
return nil;
- if(c->caps & 1){
+ if(c->controls & 1){
uchar b[4];
b[0] = speed;
@@ -207,6 +281,10 @@
{
char buf[32];
Dev *d, *ed;
+ Desc *dd;
+ Conf *conf;
+ Iface *ac;
+ Aconf *c;
Ep *e;
int i;
@@ -226,15 +304,22 @@
sysfatal("getdev: %r");
audiodev = d;
- /* parse descriptors, mark valid altc */
- for(i = 0; i < nelem(d->usb->ddesc); i++)
- parsedescr(d->usb->ddesc[i]);
+ conf = d->usb->conf[0];
+ ac = findiface(conf, Claudio, 1, -1);
+ if(ac == nil)
+ sysfatal("no audio control interface");
+ audiocontrol = ac;
+
+ dd = findacheader(d->usb, ac);
+ if(dd == nil)
+ sysfatal("no audio control header");
+ for(i = 6; i < dd->data.bLength-2; i++)
+ parsestream(d, dd->data.bbytes[i]);
+
for(i = 0; i < nelem(d->usb->ep); i++){
- e = d->usb->ep[i];
- if(e == nil || e->type != Eiso || e->iface->csp != CSP(Claudio, 2, 0))
- continue;
- for(; e != nil; e = e->next){
- if((e->attrib>>4 & 3) == Edata)
+ for(e = d->usb->ep[i]; e != nil; e = e->next){
+ c = e->iface->aux;
+ if(c != nil && c->ep == e)
break;
}
if(e == nil)
@@ -263,7 +348,7 @@
closedev(ed);
}
if(audioepout == nil)
- sysfatal("no endpoints found");
+ sysfatal("no output stream found");
fs.tree = alloctree(user, "usb", DMDIR|0555, nil);
createfile(fs.tree->root, "volume", user, 0666, nil);