#include #include #include #include #include #include #include "usb.h" #include "usbaudio.h" #include "usbaudioctl.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, Qaudio, Qaudioin, Nqid, }; Dir dirs[] = { /* Note: Qaudio{in} used as mount point for /dev/usb/%d/ep%ddata only */ [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}, [Qaudio] = {0,0,{Qaudio, 0,QTFILE},0222,0,0,0, "audio", nil,nil,nil}, [Qaudioin] = {0,0,{Qaudioin, 0,QTFILE},0444,0,0,0, "audioin", 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; sprint(buf, "%d",srvfd); if(write(fd, buf, strlen(buf)) != strlen(buf)) sysfatal("srv write"); close(fd); putenv(envname, name); } 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(debug) fmtinstall('F', fcallfmt); procrfork(io, nil, STACKSIZE, RFFDG|RFNAMEG); close(p[0]); /* don't deadlock if child fails */ if(srvpost){ sprint(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){ sprint(epdata, "#U/usb%d/%d/ep%ddata", ad->ctlrno, ad->id, endpt[Play]); sprint(audiofile, "%s/audio", mntpt); if(bind(epdata, audiofile, MREPL) < 0) sysfatal("bind failed"); } if (endpt[Record] >= 0){ sprint(epdata, "#U/usb%d/%d/ep%ddata", ad->ctlrno, ad->id, endpt[Record]); sprint(audiofile, "%s/audioin", mntpt); if(bind(epdata, audiofile, MREPL) < 0) sysfatal("bind failed"); } 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); if (debug & Dbgproc) fprint(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 (t == Qaudio && endpt[Play] < 0) continue; if (t == Qaudioin && endpt[Record] < 0) continue; 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(f->dir == &dirs[Qaudio] || f->dir == &dirs[Qaudioin]) return Eperm; 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++){ if (endpt[Play] < 0 && i == Qaudio) continue; if (endpt[Record] < 0 && i == Qaudioin) continue; 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; } int makeaudioctldata(Fid *f) { int rec, ctl, i, different; char *p, *e; Audiocontrol *c; Audioctldata *a; if ((a = f->fiddata) == nil) sysfatal("fiddata"); if ((p = a->s) == nil){ p = emalloc(1024); a->s = p; } e = p + 1024; for (rec = 0; rec < 2; rec++) for (ctl = 0; ctl < Ncontrol; ctl++) { c = &controls[rec][ctl]; different = 0; if (c->chans){ for (i = 1; i < 8; i++) if ((c->chans & 1<value[i] != a->values[rec][ctl][i]) different = 1; }else if (c->value[0] != a->values[rec][ctl][0]) different = 1; if (different){ p = seprint(p, e, "%s %s %A", c->name, rec?"in":"out", c); memmove(a->values[rec][ctl], 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) < 1024); a->ns = p - a->s; return a->ns; } void readproc(void *x) { Fcall *rhdr; Audioctldata *a; Fid *f; uchar *mdata; int n, cnt; long off; Worker *w; ulong event; w = x; mdata = emalloc(8*1024+IOHDRSZ); 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); if (debug & Dbgproc) fprint(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) { if (debug & Dbgproc) fprint(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; if(debug & (Dbgproc|Dbgfs)) fprint(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) { long off; int i, n, cnt, rec; char *p; static char buf[1024]; Audiocontrol *c; Audioctldata *a; Worker *w; 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){ i = snprint(p, n, "audio %s %ld\n", rec?"in":"out", (c->min != Undef) ? 100*(c->value[0]-c->min)/(c->max-c->min) : c->value[0]); p+=i; n-=i; } c = &controls[rec][Treble_control]; if (c->readable){ i = snprint(p, n, "treb %s %ld\n", rec?"in":"out", (c->min != Undef) ? 100*(c->value[0]-c->min)/(c->max-c->min) : c->value[0]); p+=i; n-=i; } c = &controls[rec][Bass_control]; if (c->readable){ i = snprint(p, n, "bass %s %ld\n", rec?"in":"out", (c->min != Undef) ? 100*(c->value[0]-c->min)/(c->max-c->min) : 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 = emalloc(sizeof(Fcall)); 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++){ if (debug) fprint(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 { if (debug) fprint(2, "bad1\n"); return Ebadctl; } }else if (nf == 2) rec = 0; else { if (debug) fprint(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){ if (debug) fprint(2, "bad3\n"); return Ebadctl; } if (f->dir == &dirs[Qvolume] && c->min != Undef && c->max != Undef){ nnf = tokenize(fields[nf-1], subfields, nelem(subfields)); if (nnf <= 0 || nnf > 8){ if (debug) fprint(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 (debug) { if (p == buf) fprint(2, "rwrite: %s %s '%ld", c->name, rec?"record":"playback", value); else fprint(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); } if (debug) fprint(2, "'\n"); seprint(p, buf+sizeof buf, "'"); chanprint(controlchan, buf); } else { if (debug) fprint(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[Qaudio] && endpt[Play] < 0) return Enotexist; if (f->dir == &dirs[Qaudioin] && endpt[Record] < 0) return Enotexist; 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){ if (debug) fprint(2, "read9pmsg interrupted\n"); continue; } return; } if(convM2S(mdata, n, &thdr) == 0) continue; if(debug & Dbgfs) fprint(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; if(debug & Dbgfs) fprint(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); }