audiohda: first attempt on audio recording support for intel hda audio, distinguish mode in audio code

This commit is contained in:
cinap_lenrek 2013-01-19 01:12:39 +01:00
parent ece0b66739
commit e4875c1ae7
5 changed files with 309 additions and 165 deletions

View file

@ -306,14 +306,14 @@ static int
inavail(void *arg)
{
Ring *r = arg;
return buffered(r);
return buffered(r) > 0;
}
static int
outavail(void *arg)
{
Ring *r = arg;
return available(r);
return available(r) > 0;
}
static int
@ -386,12 +386,15 @@ ac97write(Audio *adev, void *vp, long n, vlong)
}
static void
ac97close(Audio *adev)
ac97close(Audio *adev, int mode)
{
Ctlr *ctlr;
Ring *ring;
uchar z[1];
if(mode == OREAD)
return;
z[0] = 0;
ctlr = adev->ctlr;
ring = &ctlr->outring;

View file

@ -11,6 +11,8 @@ typedef struct Codec Codec;
typedef struct Ctlr Ctlr;
typedef struct Bld Bld;
typedef struct Ring Ring;
typedef struct Stream Stream;
typedef struct Id Id;
typedef struct Widget Widget;
typedef struct Codec Codec;
@ -226,6 +228,26 @@ struct Ring {
ulong wi;
};
struct Stream {
Ring ring;
Bld *blds;
uint sdctl;
uint sdintr;
uint sdnum;
uint afmt;
uint atag;
int active;
uint pin;
uint cad;
Widget *conv; /* DAC or ADC */
Widget *jack; /* the pin jack */
};
struct Id {
Ctlr *ctlr;
uint codec, nid;
@ -245,8 +267,10 @@ struct Widget {
uint convrate, convfmt;
};
};
Widget *next;
Widget *from;
Widget *next; /* next in function group */
Widget *path; /* next in audio path */
Widget *link; /* temporary for findpath */
};
struct Fungroup {
@ -254,8 +278,6 @@ struct Fungroup {
Codec *codec;
uint type;
Widget *first;
Widget *mixer;
Widget *src, *dst;
Fungroup *next;
};
@ -294,25 +316,13 @@ struct Ctlr {
ulong *rirb;
ulong rirbsize;
uint sdctl;
uint sdintr;
uint sdnum;
Bld *blds;
Ring ring;
Stream sout;
Stream sin;
uint iss, oss, bss;
uint codecmask;
Codec *codec[Maxcodecs];
Widget *amp, *src;
uint pin;
uint cad;
int active;
uint afmt, atag;
};
#define csr32(c, r) (*(ulong *)&(c)->mem[r])
@ -589,55 +599,84 @@ setinamp(Widget *w, Widget *in, int mute, int *vol)
}
static Widget *
findpath(Widget *src)
findpath(Widget *jack, int type)
{
Widget *q[Maxwidgets];
uint l, r, i;
Widget *w, *v;
Widget *w, *to;
l = r = 0;
q[r++] = src;
for(w=src->fg->first; w; w=w->next)
w->from = nil;
src->from = src;
for(w=jack->fg->first; w != nil; w = w->next)
w->link = nil;
if(type == Waout){
q[r++] = jack;
jack->link = jack;
} else {
for(w=jack->fg->first; w != nil; w = w->next)
if(w->type == type){
q[r++] = w;
w->link = w;
}
}
while(l < r){
w = q[l++];
if(w->type == Waout)
if(type == Waout){
if(w->type == type)
return w;
} else if(w == jack){
for(w = jack->link; w != nil; w = w->link)
if(w->type == type)
return w;
break;
}
for(i=0; i<w->nlist; i++){
v = w->list[i];
if(v == nil || v->from)
to = w->list[i];
if(to == nil || to->link)
continue;
v->from = w;
q[r++] = v;
to->link = w;
q[r++] = to;
}
}
if(w->type != Waout)
return nil;
return w;
return nil;
}
static void
connectpath(Widget *src, Widget *dst)
disconnectpath(Widget *from, Widget *to)
{
Widget *w, *v;
Widget *next;
for(; from != nil && from != to; from = next){
next = from->path;
from->path = nil;
setoutamp(from, 1, nil);
setinamp(next, from, 1, nil);
}
setoutamp(to, 1, nil);
}
static void
connectpath(Widget *from, Widget *to)
{
Widget *next;
uint i;
for(w=dst; w != src; w=v){
v = w->from;
setoutamp(w, 0, nil);
setinamp(v, w, 0, nil);
if(v->nlist == 1)
for(; from != nil && from != to; from = next){
next = from->link;
from->path = next;
setoutamp(from, 0, nil);
setinamp(next, from, 0, nil);
if(next->nlist == 1)
continue;
for(i=0; i < v->nlist; i++)
if(v->list[i] == w){
cmd(v->id, Setconn, i);
for(i=0; i < next->nlist; i++)
if(next->list[i] == from){
cmd(next->id, Setconn, i);
break;
}
}
setoutamp(src, 0, nil);
cmd(src->id, Setpinctl, Pinctlout);
setoutamp(to, 0, nil);
}
static void
@ -844,41 +883,48 @@ enumdev(Ctlr *ctlr)
}
static int
connectpin(Ctlr *ctlr, uint pin, uint cad)
connectpin(Ctlr *ctlr, Stream *s, int type, uint pin, uint cad)
{
Widget *w, *src, *dst;
Widget *jack, *conv;
if(cad >= Maxcodecs || pin >= Maxwidgets || ctlr->codec[cad] == nil)
return -1;
src = ctlr->codec[cad]->widgets[pin];
if(src == nil)
jack = ctlr->codec[cad]->widgets[pin];
if(jack == nil)
return -1;
if(src->type != Wpin)
return -1;
if((src->pincap & Pout) == 0)
if(jack->type != Wpin)
return -1;
dst = findpath(src);
if(dst == nil)
conv = findpath(jack, type);
if(conv == nil)
return -1;
/* mute all widgets, clear stream */
for(w=src->fg->first; w != nil; w=w->next){
setoutamp(w, 1, nil);
setinamp(w, nil, 1, nil);
cmd(w->id, Setstream, 0);
if(s->conv != nil && s->jack != nil){
if(s->conv->type == Waout)
disconnectpath(s->conv, s->jack);
else
disconnectpath(s->jack, s->conv);
cmd(s->conv->id, Setstream, 0);
cmd(s->jack->id, Setpinctl, 0);
}
connectpath(src, dst);
if(type == Waout){
connectpath(conv, jack);
cmd(jack->id, Setpinctl, Pinctlout);
} else {
connectpath(jack, conv);
cmd(jack->id, Setpinctl, Pinctlin);
}
cmd(dst->id, Setconvfmt, ctlr->afmt);
cmd(dst->id, Setstream, (ctlr->atag << 4) | 0);
cmd(dst->id, Setchancnt, 1);
cmd(conv->id, Setconvfmt, s->afmt);
cmd(conv->id, Setstream, (s->atag << 4) | 0);
cmd(conv->id, Setchancnt, 1);
s->conv = conv;
s->jack = jack;
s->pin = pin;
s->cad = cad;
ctlr->amp = dst;
ctlr->src = src;
ctlr->pin = pin;
ctlr->cad = cad;
return 0;
}
@ -949,6 +995,29 @@ available(Ring *r)
return m;
}
static long
readring(Ring *r, uchar *p, long n)
{
long n0, m;
n0 = n;
while(n > 0){
if((m = buffered(r)) <= 0)
break;
if(m > n)
m = n;
if(p){
if(r->ri + m > r->nbuf)
m = r->nbuf - r->ri;
memmove(p, r->buf + r->ri, m);
p += m;
}
r->ri = (r->ri + m) % r->nbuf;
n -= m;
}
return n0 - n;
}
static long
writering(Ring *r, uchar *p, long n)
{
@ -973,93 +1042,87 @@ writering(Ring *r, uchar *p, long n)
}
static int
streamalloc(Ctlr *ctlr)
streamalloc(Ctlr *ctlr, Stream *s, int num)
{
Ring *r;
int i;
r = &ctlr->ring;
memset(r, 0, sizeof(*r));
r = &s->ring;
r->buf = xspanalloc(r->nbuf = Bufsize, 128, 0);
ctlr->blds = xspanalloc(Nblocks * sizeof(Bld), 128, 0);
if(r->buf == nil || ctlr->blds == nil){
s->blds = xspanalloc(Nblocks * sizeof(Bld), 128, 0);
if(r->buf == nil || s->blds == nil){
print("hda: no memory for stream\n");
return -1;
}
for(i=0; i<Nblocks; i++){
ctlr->blds[i].addrlo = PADDR(r->buf) + i*Blocksize;
ctlr->blds[i].addrhi = 0;
ctlr->blds[i].len = Blocksize;
ctlr->blds[i].flags = 0x01; /* interrupt on completion */
s->blds[i].addrlo = PADDR(r->buf) + i*Blocksize;
s->blds[i].addrhi = 0;
s->blds[i].len = Blocksize;
s->blds[i].flags = 0x01; /* interrupt on completion */
}
/* output dma engine starts after inputs */
ctlr->sdnum = ctlr->iss;
ctlr->sdctl = Sdctl0 + ctlr->sdnum*0x20;
ctlr->sdintr = 1<<ctlr->sdnum;
ctlr->atag = ctlr->sdnum+1;
ctlr->afmt = Fmtstereo | Fmtsampw | Fmtdiv1 | Fmtmul1 | Fmtbase441;
ctlr->active = 0;
s->sdnum = num;
s->sdctl = Sdctl0 + s->sdnum*0x20;
s->sdintr = 1<<s->sdnum;
s->atag = s->sdnum+1;
s->afmt = Fmtstereo | Fmtsampw | Fmtdiv1 | Fmtmul1 | Fmtbase441;
s->active = 0;
/* perform reset */
csr8(ctlr, ctlr->sdctl) &= ~(Srst | Srun | Scie | Seie | Sdie);
csr8(ctlr, ctlr->sdctl) |= Srst;
csr8(ctlr, s->sdctl) &= ~(Srst | Srun | Scie | Seie | Sdie);
csr8(ctlr, s->sdctl) |= Srst;
microdelay(Codecdelay);
waitup8(ctlr, ctlr->sdctl, Srst, Srst);
csr8(ctlr, ctlr->sdctl) &= ~Srst;
waitup8(ctlr, s->sdctl, Srst, Srst);
csr8(ctlr, s->sdctl) &= ~Srst;
microdelay(Codecdelay);
waitup8(ctlr, ctlr->sdctl, Srst, 0);
waitup8(ctlr, s->sdctl, Srst, 0);
/* set stream number */
csr32(ctlr, ctlr->sdctl) = (ctlr->atag << Stagbit) |
(csr32(ctlr, ctlr->sdctl) & ~(0xF << Stagbit));
csr32(ctlr, s->sdctl) = (s->atag << Stagbit) |
(csr32(ctlr, s->sdctl) & ~(0xF << Stagbit));
/* set stream format */
csr16(ctlr, Sdfmt+ctlr->sdctl) = ctlr->afmt;
csr16(ctlr, Sdfmt+s->sdctl) = s->afmt;
/* program stream DMA & parms */
csr32(ctlr, Sdbdplo+ctlr->sdctl) = PADDR(ctlr->blds);
csr32(ctlr, Sdbdphi+ctlr->sdctl) = 0;
csr32(ctlr, Sdcbl+ctlr->sdctl) = r->nbuf;
csr16(ctlr, Sdlvi+ctlr->sdctl) = (Nblocks - 1) & 0xff;
csr32(ctlr, Sdbdplo+s->sdctl) = PADDR(s->blds);
csr32(ctlr, Sdbdphi+s->sdctl) = 0;
csr32(ctlr, Sdcbl+s->sdctl) = r->nbuf;
csr16(ctlr, Sdlvi+s->sdctl) = (Nblocks - 1) & 0xff;
/* mask out ints */
csr8(ctlr, Sdsts+ctlr->sdctl) = Scompl | Sfifoerr | Sdescerr;
csr8(ctlr, Sdsts+s->sdctl) = Scompl | Sfifoerr | Sdescerr;
/* enable global intrs for this stream */
csr32(ctlr, Intctl) |= ctlr->sdintr;
csr8(ctlr, ctlr->sdctl) |= Scie | Seie | Sdie;
csr32(ctlr, Intctl) |= s->sdintr;
csr8(ctlr, s->sdctl) |= Scie | Seie | Sdie;
return 0;
}
static void
streamstart(Ctlr *ctlr)
streamstart(Ctlr *ctlr, Stream *s)
{
ctlr->active = 1;
csr8(ctlr, ctlr->sdctl) |= Srun;
waitup8(ctlr, ctlr->sdctl, Srun, Srun);
s->active = 1;
csr8(ctlr, s->sdctl) |= Srun;
waitup8(ctlr, s->sdctl, Srun, Srun);
}
static void
streamstop(Ctlr *ctlr)
streamstop(Ctlr *ctlr, Stream *s)
{
csr8(ctlr, ctlr->sdctl) &= ~Srun;
waitup8(ctlr, ctlr->sdctl, Srun, 0);
ctlr->active = 0;
csr8(ctlr, s->sdctl) &= ~Srun;
waitup8(ctlr, s->sdctl, Srun, 0);
s->active = 0;
}
static uint
streampos(Ctlr *ctlr)
streampos(Ctlr *ctlr, Stream *s)
{
Ring *r;
uint p;
r = &ctlr->ring;
p = csr32(ctlr, Sdlpib+ctlr->sdctl);
if(p >= r->nbuf)
p = csr32(ctlr, Sdlpib+s->sdctl);
if(p >= s->ring.nbuf)
p = 0;
return p;
}
@ -1085,22 +1148,38 @@ hdactl(Audio *adev, void *va, long n, vlong)
if(ntok <= 0)
continue;
if(cistrcmp(tok[0], "pin") == 0 && ntok >= 2){
cad = ctlr->cad;
cad = ctlr->sout.cad;
pin = strtoul(tok[1], 0, 0);
if(ntok > 2)
cad = strtoul(tok[2], 0, 0);
connectpin(ctlr, pin, cad);
if(connectpin(ctlr, &ctlr->sout, Waout, pin, cad) < 0)
error("connectpin failed");
}else
if(cistrcmp(tok[0], "inpin") == 0 && ntok >= 2){
cad = ctlr->sout.cad;
pin = strtoul(tok[1], 0, 0);
if(ntok > 2)
cad = strtoul(tok[2], 0, 0);
if(connectpin(ctlr, &ctlr->sin, Wain, pin, cad) < 0)
error("connectpin failed");
}else
error(Ebadctl);
}
return n;
}
static int
inavail(void *arg)
{
Ring *r = arg;
return buffered(r) > 0;
}
static int
outavail(void *arg)
{
Ctlr *ctlr = arg;
return available(&ctlr->ring) > 0;
Ring *r = arg;
return available(r) > 0;
}
static int
@ -1108,7 +1187,7 @@ outrate(void *arg)
{
Ctlr *ctlr = arg;
int delay = ctlr->adev->delay*BytesPerSample;
return (delay <= 0) || (buffered(&ctlr->ring) <= delay) || (ctlr->active == 0);
return (delay <= 0) || (buffered(&ctlr->sout.ring) <= delay) || (ctlr->sout.active == 0);
}
static long
@ -1116,16 +1195,41 @@ hdabuffered(Audio *adev)
{
Ctlr *ctlr;
ctlr = adev->ctlr;
return buffered(&ctlr->ring);
return buffered(&ctlr->sout.ring);
}
static void
hdakick(Ctlr *ctlr)
{
if(ctlr->active)
if(ctlr->sout.active)
return;
if(buffered(&ctlr->ring) > Blocksize)
streamstart(ctlr);
if(buffered(&ctlr->sout.ring) > Blocksize)
streamstart(ctlr, &ctlr->sout);
}
static long
hdaread(Audio *adev, void *vp, long n, vlong)
{
uchar *p, *e;
Ctlr *ctlr;
Ring *ring;
p = vp;
e = p + n;
ctlr = adev->ctlr;
ring = &ctlr->sin.ring;
if(ring->buf == nil || ctlr->sin.conv == nil)
return 0;
while(p < e) {
if((n = readring(ring, p, e - p)) <= 0){
if(!ctlr->sin.active)
streamstart(ctlr, &ctlr->sin);
sleep(&ring->r, inavail, ring);
continue;
}
p += n;
}
return p - (uchar*)vp;
}
static long
@ -1138,11 +1242,11 @@ hdawrite(Audio *adev, void *vp, long n, vlong)
p = vp;
e = p + n;
ctlr = adev->ctlr;
ring = &ctlr->ring;
ring = &ctlr->sout.ring;
while(p < e) {
if((n = writering(ring, p, e - p)) <= 0){
hdakick(ctlr);
sleep(&ring->r, outavail, ctlr);
sleep(&ring->r, outavail, ring);
continue;
}
p += n;
@ -1153,23 +1257,28 @@ hdawrite(Audio *adev, void *vp, long n, vlong)
}
static void
hdaclose(Audio *adev)
hdaclose(Audio *adev, int mode)
{
Ctlr *ctlr;
uchar z[1];
Ring *r;
ctlr = adev->ctlr;
if(!ctlr->active)
return;
z[0] = 0;
r = &ctlr->ring;
while(r->wi % Blocksize)
hdawrite(adev, z, sizeof(z), 0);
if(mode == OREAD || mode == ORDWR){
if(ctlr->sin.active)
streamstop(ctlr, &ctlr->sin);
}
if(mode == OWRITE || mode == ORDWR){
if(ctlr->sout.active){
z[0] = 0;
while(ctlr->sout.ring.wi % Blocksize)
hdawrite(adev, z, sizeof(z), 0);
}
}
}
enum {
Vmaster,
Vrecord,
Vspeed,
Vdelay,
Nvol,
@ -1177,6 +1286,7 @@ enum {
static Volume voltab[] = {
[Vmaster] "master", 0, 0x7f, Stereo, 0,
[Vrecord] "record", 0, 0x7f, Stereo, 0,
[Vspeed] "speed", 0, 0, Absolute, 0,
[Vdelay] "delay", 0, 0, Absolute, 0,
0
@ -1189,8 +1299,8 @@ hdagetvol(Audio *adev, int x, int a[2])
switch(x){
case Vmaster:
if(ctlr->amp != nil)
getoutamp(ctlr->amp, a);
if(ctlr->sout.conv != nil)
getoutamp(ctlr->sout.conv, a);
break;
case Vspeed:
a[0] = adev->speed;
@ -1209,8 +1319,12 @@ hdasetvol(Audio *adev, int x, int a[2])
switch(x){
case Vmaster:
if(ctlr->amp != nil)
setoutamp(ctlr->amp, 0, a);
if(ctlr->sout.conv != nil)
setoutamp(ctlr->sout.conv, 0, a);
break;
case Vrecord:
if(ctlr->sin.conv != nil)
setinamp(ctlr->sin.conv, nil, 0, a);
break;
case Vspeed:
adev->speed = a[0];
@ -1226,8 +1340,8 @@ static void
fillvoltab(Ctlr *ctlr, Volume *vt)
{
memmove(vt, voltab, sizeof(voltab));
if(ctlr->amp != nil)
vt[Vmaster].range = getoutamprange(ctlr->amp);
if(ctlr->sout.conv != nil)
vt[Vmaster].range = getoutamprange(ctlr->sout.conv);
}
static long
@ -1251,22 +1365,32 @@ hdainterrupt(Ureg *, void *arg)
{
Ctlr *ctlr;
Audio *adev;
uint sts;
Ring *r;
uint sts;
adev = arg;
ctlr = adev->ctlr;
ilock(ctlr);
sts = csr32(ctlr, Intsts);
if(sts & ctlr->sdintr){
/* ack interrupt */
csr8(ctlr, Sdsts+ctlr->sdctl) |= Scompl;
r = &ctlr->ring;
r->ri = streampos(ctlr);
if(ctlr->active && buffered(r) < Blocksize){
streamstop(ctlr);
r->ri = r->wi = streampos(ctlr);
if(sts & ctlr->sout.sdintr){
csr8(ctlr, Sdsts+ctlr->sout.sdctl) |= Scompl;
r = &ctlr->sout.ring;
r->ri = streampos(ctlr, &ctlr->sout);
if(ctlr->sout.active && buffered(r) < Blocksize){
streamstop(ctlr, &ctlr->sout);
r->ri = r->wi = streampos(ctlr, &ctlr->sout);
}
wakeup(&r->r);
}
if(sts & ctlr->sin.sdintr){
csr8(ctlr, Sdsts+ctlr->sin.sdctl) |= Scompl;
r = &ctlr->sin.ring;
r->wi = streampos(ctlr, &ctlr->sin);
if(ctlr->sin.active && available(r) < Blocksize){
streamstop(ctlr, &ctlr->sin);
r->ri = r->wi = streampos(ctlr, &ctlr->sin);
}
wakeup(&r->r);
}
@ -1286,20 +1410,21 @@ hdastatus(Audio *adev, void *a, long n, vlong)
s = a;
e = s + n;
s = seprint(s, e, "bufsize %6d buffered %6ld\n", Blocksize, buffered(&ctlr->ring));
s = seprint(s, e, "bufsize %6d buffered %6ld\n", Blocksize, buffered(&ctlr->sout.ring));
for(i=0; i<Maxcodecs; i++){
if((codec = ctlr->codec[i]) == nil)
continue;
s = seprint(s, e, "codec %2d pin %3d\n",
codec->id.codec, ctlr->pin);
s = seprint(s, e, "codec %2d pin %3d inpin %3d\n",
codec->id.codec, ctlr->sout.pin, ctlr->sin.pin);
for(fg=codec->fgroup; fg; fg=fg->next){
for(w=fg->first; w; w=w->next){
if(w->type != Wpin)
continue;
r = w->pin;
s = seprint(s, e, "pin %3d %s %s %s %s %s %s%s%s\n",
s = seprint(s, e, "pin %3d %s%s %s %s %s %s %s%s%s\n",
w->id.nid,
(w->pincap & Pout) != 0 ? "out" : "in",
(w->pincap & Pin) != 0 ? "in" : "",
(w->pincap & Pout) != 0 ? "out" : "",
pinport[(r >> 30) & 0x3],
pinloc2[(r >> 28) & 0x3],
pinloc[(r >> 24) & 0xf],
@ -1312,11 +1437,21 @@ hdastatus(Audio *adev, void *a, long n, vlong)
}
}
s = seprint(s, e, "path ");
for(w=ctlr->amp; w != nil; w = w->from){
s = seprint(s, e, "outpath ");
for(w=ctlr->sout.conv; w != nil; w = w->path){
s = seprint(s, e, "%s %3d %lux %lux %lux", widtype[w->type&7], w->id.nid,
(ulong)w->cap, (ulong)w->pin, (ulong)w->pincap);
if(w == ctlr->src)
if(w == ctlr->sout.jack)
break;
s = seprint(s, e, "");
}
s = seprint(s, e, "\n");
s = seprint(s, e, "inpath ");
for(w=ctlr->sin.jack; w != nil; w = w->path){
s = seprint(s, e, "%s %3d %lux %lux %lux", widtype[w->type&7], w->id.nid,
(ulong)w->cap, (ulong)w->pin, (ulong)w->pincap);
if(w == ctlr->sin.conv)
break;
s = seprint(s, e, "");
}
@ -1561,10 +1696,13 @@ Found:
print("#A%d: unable to start hda\n", ctlr->no);
return -1;
}
if(streamalloc(ctlr) < 0){
print("#A%d: streamalloc failed\n", ctlr->no);
if(streamalloc(ctlr, &ctlr->sout, ctlr->iss) < 0){
print("#A%d: output streamalloc failed\n", ctlr->no);
return -1;
}
if(ctlr->iss > 0)
if(streamalloc(ctlr, &ctlr->sin, 0) < 0)
print("#A%d: input streamalloc failed\n", ctlr->no);
if(enumdev(ctlr) < 0){
print("#A%d: no audio codecs found\n", ctlr->no);
return -1;
@ -1574,11 +1712,12 @@ Found:
print("#A%d: no output pins found!\n", ctlr->no);
return -1;
}
if(connectpin(ctlr, best, cad) < 0){
if(connectpin(ctlr, &ctlr->sout, Waout, best, cad) < 0){
print("#A%d: error connecting pin\n", ctlr->no);
return -1;
}
adev->read = hdaread;
adev->write = hdawrite;
adev->close = hdaclose;
adev->buffered = hdabuffered;

View file

@ -554,10 +554,12 @@ audiowrite(Audio *adev, void *vp, long n, vlong)
}
static void
audioclose(Audio *adev)
audioclose(Audio *adev, int mode)
{
Ctlr *ctlr;
if(mode == OREAD)
return;
ctlr = adev->ctlr;
sleep(&ctlr->vous, inactive, ctlr);
setempty(ctlr);

View file

@ -13,7 +13,7 @@ struct Audio
long (*read)(Audio *, void *, long, vlong);
long (*write)(Audio *, void *, long, vlong);
void (*close)(Audio *);
void (*close)(Audio *, int);
long (*volread)(Audio *, void *, long, vlong);
long (*volwrite)(Audio *, void *, long, vlong);

View file

@ -311,7 +311,7 @@ audioclose(Chan *c)
if((c->qid.path == Qaudio) && (c->flag & COPEN)){
if(adev->close){
if(!waserror()){
adev->close(adev);
adev->close(adev, c->mode);
poperror();
}
}