#include <u.h>
#include <libc.h>
#include <thread.h>
#include <auth.h>
#include <fcall.h>
#include <libsec.h>
#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<<i) &&
c->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);
}
|