Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#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);
}