plan9fox/sys/src/9/port/devaudio.c
cinap_lenrek b2d7992025 kernel: properly handle bad attach specifiers
- only accept decimal for numeric device id's
- exclude negative device id's
- device id's out of range yield Enodev
2018-02-25 17:11:18 +01:00

509 lines
8.8 KiB
C

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
#include "../port/audioif.h"
typedef struct Audioprobe Audioprobe;
typedef struct Audiochan Audiochan;
struct Audioprobe
{
char *name;
int (*probe)(Audio*);
};
struct Audiochan
{
QLock;
Chan *owner;
Audio *adev;
char *data;
char buf[4000+1];
};
enum {
Qdir = 0,
Qaudio,
Qaudioctl,
Qaudiostat,
Qvolume,
};
static Dirtab audiodir[] = {
".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
"audio", {Qaudio}, 0, 0666,
"audioctl", {Qaudioctl}, 0, 0222,
"audiostat", {Qaudiostat}, 0, 0444,
"volume", {Qvolume}, 0, 0666,
};
static int naudioprobes;
static Audioprobe audioprobes[16];
static Audio *audiodevs;
static char Evolume[] = "illegal volume specifier";
static char Ebusy[] = "device is busy";
void
addaudiocard(char *name, int (*probefn)(Audio *))
{
Audioprobe *probe;
if(naudioprobes >= nelem(audioprobes))
return;
probe = &audioprobes[naudioprobes++];
probe->name = name;
probe->probe = probefn;
}
static void
audioreset(void)
{
int i, ctlrno = 0;
Audio **pp;
Audioprobe *probe;
pp = &audiodevs;
*pp = malloc(sizeof(Audio));
for(i=0; i<naudioprobes; i++){
probe = &audioprobes[i];
for(;;){
if(*pp == nil){
print("audio: no memory\n");
break;
}
memset(*pp, 0, sizeof(Audio));
(*pp)->ctlrno = ctlrno;
(*pp)->name = probe->name;
if(probe->probe(*pp))
break;
ctlrno++;
pp = &(*pp)->next;
*pp = malloc(sizeof(Audio));
}
}
free(*pp);
*pp = nil;
}
static Audiochan*
audioclone(Chan *c, Audio *adev)
{
Audiochan *ac;
ac = malloc(sizeof(Audiochan));
if(ac == nil){
cclose(c);
return nil;
}
c->aux = ac;
ac->owner = c;
ac->adev = adev;
ac->data = nil;
return ac;
}
static Chan*
audioattach(char *spec)
{
static ulong attached = 0;
Audiochan *ac;
Audio *adev;
Chan *c;
ulong i;
i = strtoul(spec, nil, 10);
for(adev = audiodevs; adev; adev = adev->next)
if(adev->ctlrno == i)
break;
if(adev == nil)
error(Enodev);
c = devattach('A', spec);
c->qid.path = Qdir;
if((ac = audioclone(c, adev)) == nil)
error(Enomem);
i = 1<<adev->ctlrno;
if((attached & i) == 0){
static char *settings[] = {
"speed 44100",
"delay 1764", /* 40 ms */
"master 100",
"audio 100",
"head 100",
"recgain 0",
};
attached |= i;
for(i=0; i<nelem(settings) && adev->volwrite; i++){
strcpy(ac->buf, settings[i]);
if(!waserror()){
adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);
poperror();
}
}
}
return c;
}
static Chan*
audioopen(Chan *c, int omode)
{
Audiochan *ac;
Audio *adev;
int mode;
ac = c->aux;
adev = ac->adev;
if(c->qid.path == Qaudio){
mode = openmode(omode);
if(waserror()){
if(mode == OREAD || mode == ORDWR)
decref(&adev->audioopenr);
nexterror();
}
if(mode == OREAD || mode == ORDWR)
if(incref(&adev->audioopenr) != 1)
error(Ebusy);
if(waserror()){
if(mode == OWRITE || mode == ORDWR)
decref(&adev->audioopenw);
nexterror();
}
if(mode == OWRITE || mode == ORDWR)
if(incref(&adev->audioopenw) != 1)
error(Ebusy);
c = devopen(c, omode, audiodir, nelem(audiodir), devgen);
poperror();
poperror();
return c;
}
return devopen(c, omode, audiodir, nelem(audiodir), devgen);
}
static long
audioread(Chan *c, void *a, long n, vlong off)
{
Audiochan *ac;
Audio *adev;
long (*fn)(Audio *, void *, long, vlong);
ac = c->aux;
adev = ac->adev;
fn = nil;
switch((ulong)c->qid.path){
case Qdir:
audiodir[Qaudio].length = adev->buffered ? adev->buffered(adev) : 0;
return devdirread(c, a, n, audiodir, nelem(audiodir), devgen);
case Qaudio:
fn = adev->read;
break;
case Qaudiostat:
fn = adev->status;
break;
case Qvolume:
fn = adev->volread;
break;
}
if(fn == nil)
error(Egreg);
eqlock(ac);
if(waserror()){
qunlock(ac);
nexterror();
}
switch((ulong)c->qid.path){
case Qaudiostat:
case Qvolume:
/* generate the text on first read */
if(ac->data == nil || off == 0){
long l;
ac->data = nil;
l = fn(adev, ac->buf, sizeof(ac->buf)-1, 0);
if(l < 0)
l = 0;
ac->buf[l] = 0;
ac->data = ac->buf;
}
/* then serve all requests from buffer */
n = readstr(off, a, n, ac->data);
break;
default:
n = fn(adev, a, n, off);
}
qunlock(ac);
poperror();
return n;
}
static long
audiowrite(Chan *c, void *a, long n, vlong off)
{
Audiochan *ac;
Audio *adev;
long (*fn)(Audio *, void *, long, vlong);
ac = c->aux;
adev = ac->adev;
fn = nil;
switch((ulong)c->qid.path){
case Qaudio:
fn = adev->write;
break;
case Qaudioctl:
fn = adev->ctl;
break;
case Qvolume:
fn = adev->volwrite;
break;
}
if(fn == nil)
error(Egreg);
eqlock(ac);
if(waserror()){
qunlock(ac);
nexterror();
}
switch((ulong)c->qid.path){
case Qaudioctl:
case Qvolume:
if(n >= sizeof(ac->buf))
error(Etoobig);
/* copy data to audiochan buffer so it can be modified */
ac->data = nil;
memmove(ac->buf, a, n);
ac->buf[n] = 0;
a = ac->buf;
off = 0;
}
n = fn(adev, a, n, off);
qunlock(ac);
poperror();
return n;
}
static void
audioclose(Chan *c)
{
Audiochan *ac;
Audio *adev;
ac = c->aux;
adev = ac->adev;
if((c->qid.path == Qaudio) && (c->flag & COPEN)){
if(adev->close){
if(!waserror()){
adev->close(adev, c->mode);
poperror();
}
}
if(c->mode == OWRITE || c->mode == ORDWR)
decref(&adev->audioopenw);
if(c->mode == OREAD || c->mode == ORDWR)
decref(&adev->audioopenr);
}
if(ac->owner == c){
ac->owner = nil;
c->aux = nil;
free(ac);
}
}
static Walkqid*
audiowalk(Chan *c, Chan *nc, char **name, int nname)
{
Audiochan *ac;
Audio *adev;
Walkqid *wq;
ac = c->aux;
adev = ac->adev;
wq = devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);
if(wq && wq->clone){
if(audioclone(wq->clone, adev) == nil){
free(wq);
wq = nil;
}
}
return wq;
}
static int
audiostat(Chan *c, uchar *dp, int n)
{
Audiochan *ac;
Audio *adev;
ac = c->aux;
adev = ac->adev;
if((ulong)c->qid.path == Qaudio)
audiodir[Qaudio].length = adev->buffered ? adev->buffered(adev) : 0;
return devstat(c, dp, n, audiodir, nelem(audiodir), devgen);
}
/*
* audioread() made sure the buffer is big enougth so a full volume
* table can be serialized in one pass.
*/
long
genaudiovolread(Audio *adev, void *a, long n, vlong,
Volume *vol, int (*volget)(Audio *, int, int *), ulong caps)
{
int i, j, r, v[2];
char *p, *e;
p = a;
e = p + n;
for(i = 0; vol[i].name != 0; ++i){
if(vol[i].cap && (vol[i].cap & caps) == 0)
continue;
v[0] = 0;
v[1] = 0;
if((*volget)(adev, i, v) != 0)
continue;
if(vol[i].type == Absolute)
p += snprint(p, e - p, "%s %d\n", vol[i].name, v[0]);
else {
r = abs(vol[i].range);
if(r == 0)
continue;
for(j=0; j<2; j++){
if(v[j] < 0)
v[j] = 0;
if(v[j] > r)
v[j] = r;
if(vol[i].range < 0)
v[j] = r - v[j];
v[j] = (v[j]*100)/r;
}
switch(vol[i].type){
case Left:
p += snprint(p, e - p, "%s %d\n", vol[i].name, v[0]);
break;
case Right:
p += snprint(p, e - p, "%s %d\n", vol[i].name, v[1]);
break;
case Stereo:
p += snprint(p, e - p, "%s %d %d\n", vol[i].name, v[0], v[1]);
break;
}
}
}
return p - (char*)a;
}
/*
* genaudiovolwrite modifies the buffer that gets passed to it. this
* is ok as long as it is called from inside Audio.volwrite() because
* audiowrite() copies the data to Audiochan.buf[] and inserts a
* terminating \0 byte before calling Audio.volwrite().
*/
long
genaudiovolwrite(Audio *adev, void *a, long n, vlong,
Volume *vol, int (*volset)(Audio *, int, int *), ulong caps)
{
int ntok, i, j, r, v[2];
char *p, *e, *x, *tok[4];
p = a;
e = p + n;
for(;p < e; p = x){
if(x = strchr(p, '\n'))
*x++ = 0;
else
x = e;
ntok = tokenize(p, tok, 4);
if(ntok <= 0)
continue;
if(ntok == 1){
tok[1] = tok[0];
tok[0] = "master";
ntok = 2;
}
for(i = 0; vol[i].name != 0; i++){
if(vol[i].cap && (vol[i].cap & caps) == 0)
continue;
if(cistrcmp(vol[i].name, tok[0]))
continue;
if((ntok>2) && (!cistrcmp(tok[1], "out") || !cistrcmp(tok[1], "in")))
memmove(tok+1, tok+2, --ntok);
v[0] = 0;
v[1] = 0;
if(ntok > 1)
v[0] = v[1] = atoi(tok[1]);
if(ntok > 2)
v[1] = atoi(tok[2]);
if(vol[i].type == Absolute)
(*volset)(adev, i, v);
else {
r = abs(vol[i].range);
for(j=0; j<2; j++){
v[j] = (50+(v[j]*r))/100;
if(v[j] < 0)
v[j] = 0;
if(v[j] > r)
v[j] = r;
if(vol[i].range < 0)
v[j] = r - v[j];
}
(*volset)(adev, i, v);
}
break;
}
if(vol[i].name == nil)
error(Evolume);
}
return n;
}
Dev audiodevtab = {
'A',
"audio",
audioreset,
devinit,
devshutdown,
audioattach,
audiowalk,
audiostat,
audioopen,
devcreate,
audioclose,
audioread,
devbread,
audiowrite,
devbwrite,
devremove,
devwstat,
};