#include #include #include #include #include #include #include "usb.h" #include "audio.h" #include "audioctl.h" int attachok; #define STACKSIZE 16*1024 enum { OPERM = 0x3, /* mask of all permission types in open mode */ }; typedef struct Fid Fid; typedef struct Audioctldata Audioctldata; typedef struct Worker Worker; struct Audioctldata { long offoff; /* offset of the offset for audioctl */ long values[2][Ncontrol][8]; /* last values transmitted */ char *s; int ns; }; enum { Busy = 0x01, Open = 0x02, Eof = 0x04, }; struct Fid { QLock; int fid; Dir *dir; ushort flags; short readers; void *fiddata; /* file specific per-fid data (used for audioctl) */ Fid *next; }; struct Worker { Fid *fid; ushort tag; Fcall *rhdr; Dir *dir; Channel *eventc; Worker *next; }; enum { /* Event channel messages for worker */ Work = 0x01, Check = 0x02, Flush = 0x03, }; enum { Qdir, Qvolume, Qaudioctl, Qaudiostat, Nqid, }; Dir dirs[] = { [Qdir] = {0,0,{Qdir, 0,QTDIR},0555|DMDIR,0,0,0, ".", nil,nil,nil}, [Qvolume] = {0,0,{Qvolume, 0,QTFILE},0666,0,0,0, "volume", nil,nil,nil}, [Qaudioctl] = {0,0,{Qaudioctl, 0,QTFILE},0666,0,0,0, "audioctl",nil,nil,nil}, [Qaudiostat] = {0,0,{Qaudiostat,0,QTFILE},0666,0,0,0, "audiostat",nil,nil,nil}, }; int messagesize = 4*1024+IOHDRSZ; uchar mdata[8*1024+IOHDRSZ]; uchar mbuf[8*1024+IOHDRSZ]; Fcall thdr; Fcall rhdr; Worker *workers; char srvfile[64], mntdir[64], epdata[64], audiofile[64]; int mfd[2], p[2]; char user[32]; char *srvpost; Channel *procchan; Channel *replchan; Fid *fids; Fid* newfid(int); void io(void *); void usage(void); extern char *mntpt; char *rflush(Fid*), *rauth(Fid*), *rattach(Fid*), *rwalk(Fid*), *ropen(Fid*), *rcreate(Fid*), *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*), *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*), *rversion(Fid*); char *(*fcalls[])(Fid*) = { [Tflush] rflush, [Tversion] rversion, [Tauth] rauth, [Tattach] rattach, [Twalk] rwalk, [Topen] ropen, [Tcreate] rcreate, [Tread] rread, [Twrite] rwrite, [Tclunk] rclunk, [Tremove] rremove, [Tstat] rstat, [Twstat] rwstat, }; char Eperm[] = "permission denied"; char Enotdir[] = "not a directory"; char Enoauth[] = "no authentication in ramfs"; char Enotexist[] = "file does not exist"; char Einuse[] = "file in use"; char Eexist[] = "file exists"; char Enotowner[] = "not owner"; char Eisopen[] = "file already open for I/O"; char Excl[] = "exclusive use file already open"; char Ename[] = "illegal name"; char Ebadctl[] = "unknown control message"; int notifyf(void *, char *s) { if(strncmp(s, "interrupt", 9) == 0) return 1; return 0; } void post(char *name, char *envname, int srvfd) { int fd; char buf[32]; fd = create(name, OWRITE, attachok?0666:0600); if(fd < 0) return; snprint(buf, sizeof buf, "%d", srvfd); if(write(fd, buf, strlen(buf)) != strlen(buf)) sysfatal("srv write"); close(fd); putenv(envname, name); } /* * BUG: If audio is later used on a different name space, the * audio/audioin files are not there because of the bind trick. * We should actually implement those files despite the binds. * If audio is used from within the same ns nothing would change, * otherwise, whoever would mount the audio files could still * play/record audio (unlike now). */ void serve(void *) { int i; ulong t; if(pipe(p) < 0) sysfatal("pipe failed"); mfd[0] = p[0]; mfd[1] = p[0]; atnotify(notifyf, 1); strcpy(user, getuser()); t = time(nil); for(i = 0; i < Nqid; i++){ dirs[i].uid = user; dirs[i].gid = user; dirs[i].muid = user; dirs[i].atime = t; dirs[i].mtime = t; } if(mntpt == nil){ snprint(mntdir, sizeof(mntdir), "/dev"); mntpt = mntdir; } if(usbdebug) fmtinstall('F', fcallfmt); procrfork(io, nil, STACKSIZE, RFFDG|RFNAMEG); close(p[0]); /* don't deadlock if child fails */ if(srvpost){ snprint(srvfile, sizeof srvfile, "/srv/%s", srvpost); remove(srvfile); post(srvfile, "usbaudio", p[1]); } if(mount(p[1], -1, mntpt, MBEFORE, "") < 0) sysfatal("mount failed"); if(endpt[Play] >= 0 && devctl(epdev[Play], "name audio") < 0) fprint(2, "audio: name audio: %r\n"); if(endpt[Record] >= 0 && devctl(epdev[Record], "name audioin") < 0) fprint(2, "audio: name audioin: %r\n"); threadexits(nil); } char* rversion(Fid*) { Fid *f; if(thdr.msize < 256) return "max messagesize too small"; if(thdr.msize < messagesize) messagesize = thdr.msize; rhdr.msize = messagesize; if(strncmp(thdr.version, "9P2000", 6) != 0) return "unknown 9P version"; else rhdr.version = "9P2000"; for(f = fids; f; f = f->next) if(f->flags & Busy) rclunk(f); return nil; } char* rauth(Fid*) { return "usbaudio: no authentication required"; } char* rflush(Fid *) { Worker *w; int waitflush; do { waitflush = 0; for(w = workers; w; w = w->next) if(w->tag == thdr.oldtag){ waitflush++; nbsendul(w->eventc, thdr.oldtag << 16 | Flush); } if(waitflush) sleep(50); } while(waitflush); dprint(2, "flush done on tag %d\n", thdr.oldtag); return 0; } char* rattach(Fid *f) { f->flags |= Busy; f->dir = &dirs[Qdir]; rhdr.qid = f->dir->qid; if(attachok == 0 && strcmp(thdr.uname, user) != 0) return Eperm; return 0; } static Fid* doclone(Fid *f, int nfid) { Fid *nf; nf = newfid(nfid); if(nf->flags & Busy) return nil; nf->flags |= Busy; nf->flags &= ~Open; nf->dir = f->dir; return nf; } char* dowalk(Fid *f, char *name) { int t; if(strcmp(name, ".") == 0) return nil; if(strcmp(name, "..") == 0){ f->dir = &dirs[Qdir]; return nil; } if(f->dir != &dirs[Qdir]) return Enotexist; for(t = 1; t < Nqid; t++){ if(strcmp(name, dirs[t].name) == 0){ f->dir = &dirs[t]; return nil; } } return Enotexist; } char* rwalk(Fid *f) { Fid *nf; char *rv; int i; Dir *savedir; if(f->flags & Open) return Eisopen; rhdr.nwqid = 0; nf = nil; savedir = f->dir; /* clone if requested */ if(thdr.newfid != thdr.fid){ nf = doclone(f, thdr.newfid); if(nf == nil) return "new fid in use"; f = nf; } /* if it's just a clone, return */ if(thdr.nwname == 0 && nf != nil) return nil; /* walk each element */ rv = nil; for(i = 0; i < thdr.nwname; i++){ rv = dowalk(f, thdr.wname[i]); if(rv != nil){ if(nf != nil) rclunk(nf); else f->dir = savedir; break; } rhdr.wqid[i] = f->dir->qid; } rhdr.nwqid = i; /* we only error out if no walk */ if(i > 0) rv = nil; return rv; } Audioctldata * allocaudioctldata(void) { int i, j, k; Audioctldata *a; a = emallocz(sizeof(Audioctldata), 1); for(i = 0; i < 2; i++) for(j=0; j < Ncontrol; j++) for(k=0; k < 8; k++) a->values[i][j][k] = Undef; return a; } char * ropen(Fid *f) { if(f->flags & Open) return Eisopen; if(thdr.mode != OREAD && (f->dir->mode & 0x2) == 0) return Eperm; qlock(f); if(f->dir == &dirs[Qaudioctl] && f->fiddata == nil) f->fiddata = allocaudioctldata(); qunlock(f); rhdr.iounit = 0; rhdr.qid = f->dir->qid; f->flags |= Open; return nil; } char * rcreate(Fid*) { return Eperm; } int readtopdir(Fid*, uchar *buf, long off, int cnt, int blen) { int i, m, n; long pos; n = 0; pos = 0; for(i = 1; i < Nqid; i++){ m = convD2M(&dirs[i], &buf[n], blen-n); if(off <= pos){ if(m <= BIT16SZ || m > cnt) break; n += m; cnt -= m; } pos += m; } return n; } enum { Chunk = 1024, }; int makeaudioctldata(Fid *f) { int rec, ctl, i, diff; long *actls; /* 8 of them */ char *p, *e; Audiocontrol *c; Audioctldata *a; if((a = f->fiddata) == nil) sysfatal("fiddata"); if((p = a->s) == nil) a->s = p = emallocz(Chunk, 0); e = p + Chunk - 1; /* e must point *at* last byte, not *after* */ for(rec = 0; rec < 2; rec++) for(ctl = 0; ctl < Ncontrol; ctl++){ c = &controls[rec][ctl]; actls = a->values[rec][ctl]; diff = 0; if(c->chans){ for(i = 1; i < 8; i++) if((c->chans & 1<value[i] != actls[i]) diff = 1; }else if(c->value[0] != actls[0]) diff = 1; if(diff){ p = seprint(p, e, "%s %s %A", c->name, rec? "in": "out", c); memmove(actls, c->value, sizeof c->value); if(c->min != Undef){ p = seprint(p, e, " %ld %ld", c->min, c->max); if(c->step != Undef) p = seprint(p, e, " %ld", c->step); } p = seprint(p, e, "\n"); } } assert(strlen(a->s) < Chunk); a->ns = p - a->s; return a->ns; } void readproc(void *x) { int n, cnt; ulong event; vlong off; uchar *mdata; Audioctldata *a; Fcall *rhdr; Fid *f; Worker *w; w = x; mdata = emallocz(8*1024+IOHDRSZ, 0); while(event = recvul(w->eventc)){ if(event != Work) continue; f = w->fid; rhdr = w->rhdr; a = f->fiddata; off = rhdr->offset; cnt = rhdr->count; assert(a->offoff == off); /* f is already locked */ for(;;){ qunlock(f); event = recvul(w->eventc); qlock(f); ddprint(2, "readproc unblocked fid %d %lld\n", f->fid, f->dir->qid.path); switch (event & 0xffff){ case Work: sysfatal("readproc phase error"); case Check: if(f->fiddata && makeaudioctldata(f) == 0) continue; break; case Flush: if((event >> 16) == rhdr->tag){ ddprint(2, "readproc flushing fid %d, tag %d\n", f->fid, rhdr->tag); goto flush; } continue; } if(f->fiddata){ rhdr->data = a->s; rhdr->count = a->ns; break; } yield(); } if(rhdr->count > cnt) rhdr->count = cnt; if(rhdr->count) f->flags &= ~Eof; ddprint(2, "readproc:->%F\n", rhdr); n = convS2M(rhdr, mdata, messagesize); if(write(mfd[1], mdata, n) != n) sysfatal("mount write"); flush: w->tag = NOTAG; f->readers--; assert(f->readers == 0); free(rhdr); w->rhdr = nil; qunlock(f); sendp(procchan, w); } threadexits(nil); } char* rread(Fid *f) { int i, n, cnt, rec, div; vlong off; char *p; Audiocontrol *c; Audioctldata *a; Worker *w; static char buf[1024]; rhdr.count = 0; off = thdr.offset; cnt = thdr.count; if(cnt > messagesize - IOHDRSZ) cnt = messagesize - IOHDRSZ; rhdr.data = (char*)mbuf; if(f->dir == &dirs[Qdir]){ n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ); rhdr.count = n; return nil; } if(f->dir == &dirs[Qvolume]){ p = buf; n = sizeof buf; for(rec = 0; rec < 2; rec++){ c = &controls[rec][Volume_control]; if(c->readable){ div = c->max - c->min; i = snprint(p, n, "audio %s %ld\n", rec? "in": "out", (c->min != Undef? 100*(c->value[0]-c->min)/(div? div: 1): c->value[0])); p += i; n -= i; } c = &controls[rec][Treble_control]; if(c->readable){ div = c->max - c->min; i = snprint(p, n, "treb %s %ld\n", rec? "in": "out", (c->min != Undef? 100*(c->value[0]-c->min)/(div? div: 1): c->value[0])); p += i; n -= i; } c = &controls[rec][Bass_control]; if(c->readable){ div = c->max - c->min; i = snprint(p, n, "bass %s %ld\n", rec? "in": "out", (c->min != Undef? 100*(c->value[0]-c->min)/(div? div: 1): c->value[0])); p += i; n -= i; } c = &controls[rec][Speed_control]; if(c->readable){ i = snprint(p, n, "speed %s %ld\n", rec? "in": "out", c->value[0]); p += i; n -= i; } } n = sizeof buf - n; if(off > n) rhdr.count = 0; else{ rhdr.data = buf + off; rhdr.count = n - off; if(rhdr.count > cnt) rhdr.count = cnt; } return nil; } if(f->dir == &dirs[Qaudioctl]){ Fcall *hdr; qlock(f); a = f->fiddata; if(off - a->offoff < 0){ /* there was a seek */ a->offoff = off; a->ns = 0; } do { if(off - a->offoff < a->ns){ rhdr.data = a->s + (off - a->offoff); rhdr.count = a->ns - (off - a->offoff); if(rhdr.count > cnt) rhdr.count = cnt; qunlock(f); return nil; } if(a->offoff != off){ a->ns = 0; a->offoff = off; rhdr.count = 0; qunlock(f); return nil; } } while(makeaudioctldata(f) != 0); assert(a->offoff == off); /* Wait for data off line */ f->readers++; w = nbrecvp(procchan); if(w == nil){ w = emallocz(sizeof(Worker), 1); w->eventc = chancreate(sizeof(ulong), 1); w->next = workers; workers = w; proccreate(readproc, w, 4096); } hdr = emallocz(sizeof(Fcall), 0); w->fid = f; w->tag = thdr.tag; assert(w->rhdr == nil); w->rhdr = hdr; hdr->count = cnt; hdr->offset = off; hdr->type = thdr.type+1; hdr->fid = thdr.fid; hdr->tag = thdr.tag; sendul(w->eventc, Work); return (char*)~0; } return Eperm; } char* rwrite(Fid *f) { long cnt, value; char *lines[2*Ncontrol], *fields[4], *subfields[9], *err, *p; int nlines, i, nf, nnf, rec, ctl; Audiocontrol *c; Worker *w; static char buf[256]; rhdr.count = 0; cnt = thdr.count; if(cnt > messagesize - IOHDRSZ) cnt = messagesize - IOHDRSZ; err = nil; if(f->dir == &dirs[Qvolume] || f->dir == &dirs[Qaudioctl]){ thdr.data[cnt] = '\0'; nlines = getfields(thdr.data, lines, 2*Ncontrol, 1, "\n"); for(i = 0; i < nlines; i++){ dprint(2, "line: %s\n", lines[i]); nf = tokenize(lines[i], fields, 4); if(nf == 0) continue; if(nf == 3) if(strcmp(fields[1], "in") == 0 || strcmp(fields[1], "record") == 0) rec = 1; else if(strcmp(fields[1], "out") == 0 || strcmp(fields[1], "playback") == 0) rec = 0; else{ dprint(2, "bad1\n"); return Ebadctl; } else if(nf == 2) rec = 0; else{ dprint(2, "bad2 %d\n", nf); return Ebadctl; } c = nil; if(strcmp(fields[0], "audio") == 0) /* special case */ fields[0] = "volume"; for(ctl = 0; ctl < Ncontrol; ctl++){ c = &controls[rec][ctl]; if(strcmp(fields[0], c->name) == 0) break; } if(ctl == Ncontrol){ dprint(2, "bad3\n"); return Ebadctl; } if(f->dir == &dirs[Qvolume] && ctl != Speed_control && c->min != Undef && c->max != Undef){ nnf = tokenize(fields[nf-1], subfields, nelem(subfields)); if(nnf <= 0 || nnf > 8){ dprint(2, "bad4\n"); return Ebadctl; } p = buf; for(i = 0; i < nnf; i++){ value = strtol(subfields[i], nil, 0); value = ((100 - value)*c->min + value*c->max) / 100; if(p == buf){ dprint(2, "rwrite: %s %s '%ld", c->name, rec? "record": "playback", value); }else dprint(2, " %ld", value); if(p == buf) p = seprint(p, buf+sizeof buf, "0x%p %s %s '%ld", replchan, c->name, rec? "record": "playback", value); else p = seprint(p, buf+sizeof buf, " %ld", value); } dprint(2, "'\n"); seprint(p, buf+sizeof buf-1, "'"); chanprint(controlchan, buf); }else{ dprint(2, "rwrite: %s %s %q", c->name, rec? "record": "playback", fields[nf-1]); chanprint(controlchan, "0x%p %s %s %q", replchan, c->name, rec? "record": "playback", fields[nf-1]); } p = recvp(replchan); if(p){ if(strcmp(p, "ok") == 0){ free(p); p = nil; } if(err == nil) err = p; } } for(w = workers; w; w = w->next) nbsendul(w->eventc, Qaudioctl << 16 | Check); rhdr.count = thdr.count; return err; } return Eperm; } char * rclunk(Fid *f) { Audioctldata *a; qlock(f); f->flags &= ~(Open|Busy); assert(f->readers ==0); if(f->fiddata){ a = f->fiddata; if(a->s) free(a->s); free(a); f->fiddata = nil; } qunlock(f); return 0; } char * rremove(Fid *) { return Eperm; } char * rstat(Fid *f) { Audioctldata *a; if(f->dir == &dirs[Qaudioctl]){ qlock(f); if(f->fiddata == nil) f->fiddata = allocaudioctldata(); a = f->fiddata; if(a->ns == 0) makeaudioctldata(f); f->dir->length = a->offoff + a->ns; qunlock(f); } rhdr.nstat = convD2M(f->dir, mbuf, messagesize - IOHDRSZ); rhdr.stat = mbuf; return 0; } char * rwstat(Fid*) { return Eperm; } Fid * newfid(int fid) { Fid *f, *ff; ff = nil; for(f = fids; f; f = f->next) if(f->fid == fid) return f; else if(ff == nil && (f->flags & Busy) == 0) ff = f; if(ff == nil){ ff = emallocz(sizeof *ff, 1); ff->next = fids; fids = ff; } ff->fid = fid; ff->flags &= ~(Busy|Open); ff->dir = nil; return ff; } void io(void *) { char *err, e[32]; int n; close(p[1]); procchan = chancreate(sizeof(Channel*), 8); replchan = chancreate(sizeof(char*), 0); for(;;){ /* * reading from a pipe or a network device * will give an error after a few eof reads * however, we cannot tell the difference * between a zero-length read and an interrupt * on the processes writing to us, * so we wait for the error */ n = read9pmsg(mfd[0], mdata, messagesize); if(n == 0) continue; if(n < 0){ rerrstr(e, sizeof e); if(strcmp(e, "interrupted") == 0){ dprint(2, "read9pmsg interrupted\n"); continue; } return; } if(convM2S(mdata, n, &thdr) == 0) continue; ddprint(2, "io:<-%F\n", &thdr); rhdr.data = (char*)mdata + messagesize; if(!fcalls[thdr.type]) err = "bad fcall type"; else err = (*fcalls[thdr.type])(newfid(thdr.fid)); if(err == (char*)~0) continue; /* handled off line */ if(err){ rhdr.type = Rerror; rhdr.ename = err; }else{ rhdr.type = thdr.type + 1; rhdr.fid = thdr.fid; } rhdr.tag = thdr.tag; ddprint(2, "io:->%F\n", &rhdr); n = convS2M(&rhdr, mdata, messagesize); if(write(mfd[1], mdata, n) != n) sysfatal("mount write"); } } int newid(void) { int rv; static int id; static Lock idlock; lock(&idlock); rv = ++id; unlock(&idlock); return rv; } void ctlevent(void) { Worker *w; for(w = workers; w; w = w->next) nbsendul(w->eventc, Qaudioctl << 16 | Check); }