1
0
mirror of https://github.com/ericonr/sndio.git synced 2024-02-18 04:45:21 -06:00
sndio/sndiod/sock.c
Alexandre Ratchov 4f7c000acb Don't forget to notify clients when controls list is ready.
This is needed because when devices are swapped, clients need to know
when to rebuild their user interface.
2020-03-08 16:51:37 +01:00

1827 lines
35 KiB
C

/* $OpenBSD$ */
/*
* Copyright (c) 2008-2012 Alexandre Ratchov <alex@caoua.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "abuf.h"
#include "defs.h"
#include "dev.h"
#include "file.h"
#include "midi.h"
#include "opt.h"
#include "sock.h"
#include "utils.h"
#include "bsd-compat.h"
#define SOCK_CTLDESC_SIZE 16 /* number of entries in s->ctldesc */
void sock_log(struct sock *);
void sock_close(struct sock *);
void sock_slot_fill(void *);
void sock_slot_flush(void *);
void sock_slot_eof(void *);
void sock_slot_onmove(void *);
void sock_slot_onvol(void *);
void sock_midi_imsg(void *, unsigned char *, int);
void sock_midi_omsg(void *, unsigned char *, int);
void sock_midi_fill(void *, int);
void sock_ctl_sync(void *);
struct sock *sock_new(int);
void sock_exit(void *);
int sock_fdwrite(struct sock *, void *, int);
int sock_fdread(struct sock *, void *, int);
int sock_rmsg(struct sock *);
int sock_wmsg(struct sock *);
int sock_rdata(struct sock *);
int sock_wdata(struct sock *);
int sock_setpar(struct sock *);
int sock_auth(struct sock *);
int sock_hello(struct sock *);
int sock_execmsg(struct sock *);
int sock_buildmsg(struct sock *);
int sock_read(struct sock *);
int sock_write(struct sock *);
int sock_pollfd(void *, struct pollfd *);
int sock_revents(void *, struct pollfd *);
void sock_in(void *);
void sock_out(void *);
void sock_hup(void *);
struct fileops sock_fileops = {
"sock",
sock_pollfd,
sock_revents,
sock_in,
sock_out,
sock_hup
};
struct slotops sock_slotops = {
sock_slot_onmove,
sock_slot_onvol,
sock_slot_fill,
sock_slot_flush,
sock_slot_eof,
sock_exit
};
struct midiops sock_midiops = {
sock_midi_imsg,
sock_midi_omsg,
sock_midi_fill,
sock_exit
};
struct ctlops sock_ctlops = {
sock_exit,
sock_ctl_sync
};
struct sock *sock_list = NULL;
unsigned int sock_sesrefs = 0; /* connections to the session */
uint8_t sock_sescookie[AMSG_COOKIELEN]; /* owner of the session */
void
sock_log(struct sock *f)
{
#ifdef DEBUG
static char *rstates[] = { "ridl", "rmsg", "rdat", "rret" };
static char *wstates[] = { "widl", "wmsg", "wdat" };
#endif
if (f->slot)
slot_log(f->slot);
else if (f->midi)
midi_log(f->midi);
else if (f->ctlslot) {
log_puts("ctlslot");
log_putu(f->ctlslot - f->ctlslot->dev->ctlslot);
} else
log_puts("sock");
#ifdef DEBUG
if (log_level >= 3) {
log_puts(",");
log_puts(rstates[f->rstate]);
log_puts(",");
log_puts(wstates[f->wstate]);
}
#endif
}
void
sock_close(struct sock *f)
{
struct sock **pf;
for (pf = &sock_list; *pf != f; pf = &(*pf)->next) {
#ifdef DEBUG
if (*pf == NULL) {
log_puts("sock_close: not on list\n");
panic();
}
#endif
}
*pf = f->next;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": closing\n");
}
#endif
if (f->pstate > SOCK_AUTH)
sock_sesrefs--;
if (f->slot) {
slot_del(f->slot);
f->slot = NULL;
}
if (f->midi) {
midi_del(f->midi);
f->midi = NULL;
}
if (f->port) {
port_unref(f->port);
f->port = NULL;
}
if (f->ctlslot) {
ctlslot_del(f->ctlslot);
f->ctlslot = NULL;
xfree(f->ctldesc);
}
file_del(f->file);
close(f->fd);
file_slowaccept = 0;
xfree(f);
}
void
sock_slot_fill(void *arg)
{
struct sock *f = arg;
struct slot *s = f->slot;
f->fillpending += s->round;
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": fill, rmax -> ");
log_puti(f->rmax);
log_puts(", pending -> ");
log_puti(f->fillpending);
log_puts("\n");
}
#endif
}
void
sock_slot_flush(void *arg)
{
struct sock *f = arg;
struct slot *s = f->slot;
f->wmax += s->round * s->sub.bpf;
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": flush, wmax -> ");
log_puti(f->wmax);
log_puts("\n");
}
#endif
}
void
sock_slot_eof(void *arg)
{
struct sock *f = arg;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": stopped\n");
}
#endif
f->stoppending = 1;
}
void
sock_slot_onmove(void *arg)
{
struct sock *f = (struct sock *)arg;
struct slot *s = f->slot;
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": onmove: delta -> ");
log_puti(s->delta);
log_puts("\n");
}
#endif
if (s->pstate != SOCK_START)
return;
f->tickpending++;
}
void
sock_slot_onvol(void *arg)
{
struct sock *f = (struct sock *)arg;
struct slot *s = f->slot;
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": onvol: vol -> ");
log_puti(s->vol);
log_puts("\n");
}
#endif
if (s->pstate != SOCK_START)
return;
}
void
sock_midi_imsg(void *arg, unsigned char *msg, int size)
{
struct sock *f = arg;
midi_send(f->midi, msg, size);
}
void
sock_midi_omsg(void *arg, unsigned char *msg, int size)
{
struct sock *f = arg;
midi_out(f->midi, msg, size);
}
void
sock_midi_fill(void *arg, int count)
{
struct sock *f = arg;
f->fillpending += count;
}
void
sock_ctl_sync(void *arg)
{
struct sock *f = arg;
if (f->ctlops & SOCK_CTLDESC)
f->ctlsyncpending = 1;
}
struct sock *
sock_new(int fd)
{
struct sock *f;
f = xmalloc(sizeof(struct sock));
f->pstate = SOCK_AUTH;
f->slot = NULL;
f->port = NULL;
f->midi = NULL;
f->ctlslot = NULL;
f->tickpending = 0;
f->fillpending = 0;
f->stoppending = 0;
f->wstate = SOCK_WIDLE;
f->wtodo = 0xdeadbeef;
f->rstate = SOCK_RMSG;
f->rtodo = sizeof(struct amsg);
f->wmax = f->rmax = 0;
f->lastvol = -1;
f->ctlops = 0;
f->ctlsyncpending = 0;
f->file = file_new(&sock_fileops, f, "sock", 1);
f->fd = fd;
if (f->file == NULL) {
xfree(f);
return NULL;
}
f->next = sock_list;
sock_list = f;
return f;
}
void
sock_exit(void *arg)
{
struct sock *f = (struct sock *)arg;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": exit\n");
}
#endif
sock_close(f);
}
/*
* write on the socket fd and handle errors
*/
int
sock_fdwrite(struct sock *f, void *data, int count)
{
int n;
n = write(f->fd, data, count);
if (n == -1) {
#ifdef DEBUG
if (errno == EFAULT) {
log_puts("sock_fdwrite: fault\n");
panic();
}
#endif
if (errno != EAGAIN) {
if (log_level >= 1) {
sock_log(f);
log_puts(": write filed, errno = ");
log_puti(errno);
log_puts("\n");
}
sock_close(f);
} else {
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": write blocked\n");
}
#endif
}
return 0;
}
if (n == 0) {
sock_close(f);
return 0;
}
return n;
}
/*
* read from the socket fd and handle errors
*/
int
sock_fdread(struct sock *f, void *data, int count)
{
int n;
n = read(f->fd, data, count);
if (n == -1) {
#ifdef DEBUG
if (errno == EFAULT) {
log_puts("sock_fdread: fault\n");
panic();
}
#endif
if (errno != EAGAIN) {
if (log_level >= 1) {
sock_log(f);
log_puts(": read failed, errno = ");
log_puti(errno);
log_puts("\n");
}
sock_close(f);
} else {
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": read blocked\n");
}
#endif
}
return 0;
}
if (n == 0) {
sock_close(f);
return 0;
}
return n;
}
/*
* read the next message into f->rmsg, return 1 on success
*/
int
sock_rmsg(struct sock *f)
{
int n;
char *data;
#ifdef DEBUG
if (f->rtodo == 0) {
sock_log(f);
log_puts(": sock_rmsg: nothing to read\n");
panic();
}
#endif
data = (char *)&f->rmsg + sizeof(struct amsg) - f->rtodo;
n = sock_fdread(f, data, f->rtodo);
if (n == 0)
return 0;
if (n < f->rtodo) {
f->rtodo -= n;
return 0;
}
f->rtodo = 0;
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": read full message\n");
}
#endif
return 1;
}
/*
* write the message in f->rmsg, return 1 on success
*/
int
sock_wmsg(struct sock *f)
{
int n;
char *data;
#ifdef DEBUG
if (f->wtodo == 0) {
sock_log(f);
log_puts(": sock_wmsg: already written\n");
}
#endif
data = (char *)&f->wmsg + sizeof(struct amsg) - f->wtodo;
n = sock_fdwrite(f, data, f->wtodo);
if (n == 0)
return 0;
if (n < f->wtodo) {
f->wtodo -= n;
return 0;
}
f->wtodo = 0;
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": wrote full message\n");
}
#endif
return 1;
}
/*
* read data into the slot/midi ring buffer
*/
int
sock_rdata(struct sock *f)
{
unsigned char midibuf[MIDI_BUFSZ];
unsigned char *data;
int n, count;
#ifdef DEBUG
if (f->rtodo == 0) {
sock_log(f);
log_puts(": data block already read\n");
panic();
}
#endif
while (f->rtodo > 0) {
if (f->slot)
data = abuf_wgetblk(&f->slot->mix.buf, &count);
else {
data = midibuf;
count = MIDI_BUFSZ;
}
if (count > f->rtodo)
count = f->rtodo;
n = sock_fdread(f, data, count);
if (n == 0)
return 0;
f->rtodo -= n;
if (f->slot)
abuf_wcommit(&f->slot->mix.buf, n);
else
midi_in(f->midi, midibuf, n);
}
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": read complete block\n");
}
#endif
if (f->slot)
slot_write(f->slot);
return 1;
}
/*
* write data to the slot/midi ring buffer
*/
int
sock_wdata(struct sock *f)
{
static unsigned char dummy[AMSG_DATAMAX];
unsigned char *data = NULL;
int n, count;
#ifdef DEBUG
if (f->wtodo == 0) {
sock_log(f);
log_puts(": attempted to write zero-sized data block\n");
panic();
}
#endif
if (f->pstate == SOCK_STOP) {
while (f->wtodo > 0) {
n = sock_fdwrite(f, dummy, f->wtodo);
if (n == 0)
return 0;
f->wtodo -= n;
}
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": zero-filled remaining block\n");
}
#endif
return 1;
}
while (f->wtodo > 0) {
/*
* f->slot and f->midi are set by sock_hello(), so
* count is always properly initialized
*/
if (f->slot)
data = abuf_rgetblk(&f->slot->sub.buf, &count);
else if (f->midi)
data = abuf_rgetblk(&f->midi->obuf, &count);
else {
data = (unsigned char *)f->ctldesc +
(f->wsize - f->wtodo);
count = f->wtodo;
}
if (count > f->wtodo)
count = f->wtodo;
n = sock_fdwrite(f, data, count);
if (n == 0)
return 0;
f->wtodo -= n;
if (f->slot)
abuf_rdiscard(&f->slot->sub.buf, n);
else if (f->midi)
abuf_rdiscard(&f->midi->obuf, n);
}
if (f->slot)
slot_read(f->slot);
if (f->midi)
midi_fill(f->midi);
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": wrote complete block\n");
}
#endif
return 1;
}
int
sock_setpar(struct sock *f)
{
struct slot *s = f->slot;
struct dev *d = s->dev;
struct amsg_par *p = &f->rmsg.u.par;
unsigned int min, max;
uint32_t rate, appbufsz;
uint16_t pchan, rchan;
rchan = ntohs(p->rchan);
pchan = ntohs(p->pchan);
appbufsz = ntohl(p->appbufsz);
rate = ntohl(p->rate);
if (AMSG_ISSET(p->bits)) {
if (p->bits < BITS_MIN || p->bits > BITS_MAX) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": ");
log_putu(p->bits);
log_puts(": bits out of bounds\n");
}
#endif
return 0;
}
if (AMSG_ISSET(p->bps)) {
if (p->bps < ((p->bits + 7) / 8) || p->bps > 4) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": ");
log_putu(p->bps);
log_puts(": wrong bytes per sample\n");
}
#endif
return 0;
}
} else
p->bps = APARAMS_BPS(p->bits);
s->par.bits = p->bits;
s->par.bps = p->bps;
}
if (AMSG_ISSET(p->sig))
s->par.sig = p->sig ? 1 : 0;
if (AMSG_ISSET(p->le))
s->par.le = p->le ? 1 : 0;
if (AMSG_ISSET(p->msb))
s->par.msb = p->msb ? 1 : 0;
if (AMSG_ISSET(rchan) && (s->mode & MODE_RECMASK)) {
if (rchan < 1)
rchan = 1;
else if (rchan > NCHAN_MAX)
rchan = NCHAN_MAX;
s->sub.nch = rchan;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": recording channels ");
log_putu(s->opt->rmin);
log_puts(":");
log_putu(s->opt->rmax);
log_puts(" -> ");
log_putu(s->opt->rmin);
log_puts(":");
log_putu(s->opt->rmin + s->sub.nch - 1);
log_puts("\n");
}
#endif
}
if (AMSG_ISSET(pchan) && (s->mode & MODE_PLAY)) {
if (pchan < 1)
pchan = 1;
else if (pchan > NCHAN_MAX)
pchan = NCHAN_MAX;
s->mix.nch = pchan;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": playback channels ");
log_putu(s->opt->pmin);
log_puts(":");
log_putu(s->opt->pmin + s->mix.nch - 1);
log_puts(" -> ");
log_putu(s->opt->pmin);
log_puts(":");
log_putu(s->opt->pmax);
log_puts("\n");
}
#endif
}
if (AMSG_ISSET(rate)) {
if (rate < RATE_MIN)
rate = RATE_MIN;
else if (rate > RATE_MAX)
rate = RATE_MAX;
s->round = dev_roundof(d, rate);
s->rate = rate;
if (!AMSG_ISSET(appbufsz)) {
appbufsz = d->bufsz / d->round * s->round;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": ");
log_putu(appbufsz);
log_puts(" frame buffer\n");
}
#endif
}
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": ");
log_putu(rate);
log_puts("Hz sample rate, ");
log_putu(s->round);
log_puts(" frame blocks\n");
}
#endif
}
if (AMSG_ISSET(p->xrun)) {
if (p->xrun != XRUN_IGNORE &&
p->xrun != XRUN_SYNC &&
p->xrun != XRUN_ERROR) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": ");
log_putx(p->xrun);
log_puts(": bad xrun policy\n");
}
#endif
return 0;
}
s->xrun = p->xrun;
if (s->opt->mmc && s->xrun == XRUN_IGNORE)
s->xrun = XRUN_SYNC;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": 0x");
log_putx(s->xrun);
log_puts(" xrun policy\n");
}
#endif
}
if (AMSG_ISSET(appbufsz)) {
rate = s->rate;
min = 1;
max = 1 + rate / d->round;
min *= s->round;
max *= s->round;
appbufsz += s->round / 2;
appbufsz -= appbufsz % s->round;
if (appbufsz < min)
appbufsz = min;
if (appbufsz > max)
appbufsz = max;
s->appbufsz = appbufsz;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": ");
log_putu(s->appbufsz);
log_puts(" frame buffer\n");
}
#endif
}
return 1;
}
int
sock_auth(struct sock *f)
{
struct amsg_auth *p = &f->rmsg.u.auth;
if (sock_sesrefs == 0) {
/* start a new session */
memcpy(sock_sescookie, p->cookie, AMSG_COOKIELEN);
} else if (memcmp(sock_sescookie, p->cookie, AMSG_COOKIELEN) != 0) {
/* another session is active, drop connection */
return 0;
}
sock_sesrefs++;
f->pstate = SOCK_HELLO;
return 1;
}
int
sock_hello(struct sock *f)
{
struct amsg_hello *p = &f->rmsg.u.hello;
struct port *c;
struct dev *d;
struct opt *opt;
unsigned int mode;
unsigned int id;
mode = ntohs(p->mode);
id = ntohl(p->id);
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": hello from <");
log_puts(p->who);
log_puts(">, mode = ");
log_putx(mode);
log_puts(", ver ");
log_putu(p->version);
log_puts("\n");
}
#endif
if (p->version != AMSG_VERSION) {
if (log_level >= 1) {
sock_log(f);
log_puts(": ");
log_putu(p->version);
log_puts(": unsupported protocol version\n");
}
return 0;
}
switch (mode) {
case MODE_MIDIIN:
case MODE_MIDIOUT:
case MODE_MIDIOUT | MODE_MIDIIN:
case MODE_REC:
case MODE_PLAY:
case MODE_PLAY | MODE_REC:
case MODE_CTLREAD:
case MODE_CTLWRITE:
case MODE_CTLREAD | MODE_CTLWRITE:
break;
default:
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": ");
log_putx(mode);
log_puts(": unsupported mode\n");
}
#endif
return 0;
}
f->pstate = SOCK_INIT;
f->port = NULL;
if (mode & MODE_MIDIMASK) {
f->slot = NULL;
f->midi = midi_new(&sock_midiops, f, mode);
if (f->midi == NULL)
return 0;
/* XXX: add 'devtype' to libsndio */
if (p->devnum < 16) {
d = dev_bynum(p->devnum);
if (d == NULL)
return 0;
midi_tag(f->midi, p->devnum);
} else if (p->devnum < 32) {
midi_tag(f->midi, p->devnum);
} else if (p->devnum < 48) {
c = port_bynum(p->devnum - 32);
if (c == NULL || !port_ref(c))
return 0;
f->port = c;
midi_link(f->midi, c->midi);
} else
return 0;
return 1;
}
if (mode & MODE_CTLMASK) {
d = dev_bynum(p->devnum);
if (d == NULL) {
if (log_level >= 2) {
sock_log(f);
log_puts(": ");
log_putu(p->devnum);
log_puts(": no such device\n");
}
return 0;
}
f->ctlslot = ctlslot_new(d, &sock_ctlops, f);
if (f->ctlslot == NULL) {
if (log_level >= 2) {
sock_log(f);
log_puts(": couldn't get slot\n");
}
return 0;
}
f->ctldesc = xmalloc(SOCK_CTLDESC_SIZE *
sizeof(struct amsg_ctl_desc));
f->ctlops = 0;
f->ctlsyncpending = 0;
return 1;
}
d = dev_bynum(p->devnum);
if (d == NULL)
return 0;
opt = opt_byname(d, p->opt);
if (opt == NULL)
return 0;
f->slot = slot_new(d, opt, id, p->who, &sock_slotops, f, mode);
if (f->slot == NULL)
return 0;
f->midi = NULL;
return 1;
}
/*
* execute the message in f->rmsg, return 1 on success
*/
int
sock_execmsg(struct sock *f)
{
struct ctl *c;
struct slot *s = f->slot;
struct amsg *m = &f->rmsg;
unsigned char *data;
int size, ctl;
switch (ntohl(m->cmd)) {
case AMSG_DATA:
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": DATA message\n");
}
#endif
if (s != NULL && f->pstate != SOCK_START) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": DATA, wrong state\n");
}
#endif
sock_close(f);
return 0;
}
if ((f->slot && !(f->slot->mode & MODE_PLAY)) ||
(f->midi && !(f->midi->mode & MODE_MIDIOUT))) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": DATA, input-only mode\n");
}
#endif
sock_close(f);
return 0;
}
size = ntohl(m->u.data.size);
if (size <= 0) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": zero size payload\n");
}
#endif
sock_close(f);
return 0;
}
if (s != NULL && size % s->mix.bpf != 0) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": not aligned to frame\n");
}
#endif
sock_close(f);
return 0;
}
if (s != NULL && size > f->ralign) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": size = ");
log_puti(size);
log_puts(": ralign = ");
log_puti(f->ralign);
log_puts(": not aligned to block\n");
}
#endif
sock_close(f);
return 0;
}
f->rstate = SOCK_RDATA;
f->rsize = f->rtodo = size;
if (s != NULL) {
f->ralign -= size;
if (f->ralign == 0)
f->ralign = s->round * s->mix.bpf;
}
if (f->rtodo > f->rmax) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": unexpected data, size = ");
log_putu(size);
log_puts(", rmax = ");
log_putu(f->rmax);
log_puts("\n");
}
#endif
sock_close(f);
return 0;
}
f->rmax -= f->rtodo;
if (f->rtodo == 0) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": zero-length data chunk\n");
}
#endif
sock_close(f);
return 0;
}
break;
case AMSG_START:
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": START message\n");
}
#endif
if (f->pstate != SOCK_INIT || s == NULL) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": START, wrong state\n");
}
#endif
sock_close(f);
return 0;
}
f->tickpending = 0;
f->stoppending = 0;
slot_start(s);
if (s->mode & MODE_PLAY) {
f->fillpending = s->appbufsz;
f->ralign = s->round * s->mix.bpf;
f->rmax = 0;
}
if (s->mode & MODE_RECMASK) {
f->walign = s->round * s->sub.bpf;
f->wmax = 0;
}
f->pstate = SOCK_START;
f->rstate = SOCK_RMSG;
f->rtodo = sizeof(struct amsg);
if (log_level >= 2) {
slot_log(f->slot);
log_puts(": ");
log_putu(s->rate);
log_puts("Hz, ");
aparams_log(&s->par);
if (s->mode & MODE_PLAY) {
log_puts(", play ");
log_puti(s->opt->pmin);
log_puts(":");
log_puti(s->opt->pmin + s->mix.nch - 1);
}
if (s->mode & MODE_RECMASK) {
log_puts(", rec ");
log_puti(s->opt->rmin);
log_puts(":");
log_puti(s->opt->rmin + s->sub.nch - 1);
}
log_puts(", ");
log_putu(s->appbufsz / s->round);
log_puts(" blocks of ");
log_putu(s->round);
log_puts(" frames\n");
}
break;
case AMSG_STOP:
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": STOP message\n");
}
#endif
if (f->pstate != SOCK_START) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": STOP, wrong state\n");
}
#endif
sock_close(f);
return 0;
}
f->rmax = 0;
if (!(s->mode & MODE_PLAY))
f->stoppending = 1;
f->pstate = SOCK_STOP;
f->rstate = SOCK_RMSG;
f->rtodo = sizeof(struct amsg);
if (s->mode & MODE_PLAY) {
if (f->ralign < s->round * s->mix.bpf) {
data = abuf_wgetblk(&s->mix.buf, &size);
#ifdef DEBUG
if (size < f->ralign) {
sock_log(f);
log_puts(": unaligned stop, size = ");
log_putu(size);
log_puts(", ralign = ");
log_putu(f->ralign);
log_puts("\n");
panic();
}
#endif
memset(data, 0, f->ralign);
abuf_wcommit(&s->mix.buf, f->ralign);
f->ralign = s->round * s->mix.bpf;
}
}
slot_stop(s);
break;
case AMSG_SETPAR:
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": SETPAR message\n");
}
#endif
if (f->pstate != SOCK_INIT || s == NULL) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": SETPAR, wrong state\n");
}
#endif
sock_close(f);
return 0;
}
if (!sock_setpar(f)) {
sock_close(f);
return 0;
}
f->rtodo = sizeof(struct amsg);
f->rstate = SOCK_RMSG;
break;
case AMSG_GETPAR:
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": GETPAR message\n");
}
#endif
if (f->pstate != SOCK_INIT || s == NULL) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": GETPAR, wrong state\n");
}
#endif
sock_close(f);
return 0;
}
AMSG_INIT(m);
m->cmd = htonl(AMSG_GETPAR);
m->u.par.legacy_mode = s->mode;
m->u.par.xrun = s->xrun;
m->u.par.bits = s->par.bits;
m->u.par.bps = s->par.bps;
m->u.par.sig = s->par.sig;
m->u.par.le = s->par.le;
m->u.par.msb = s->par.msb;
if (s->mode & MODE_PLAY)
m->u.par.pchan = htons(s->mix.nch);
if (s->mode & MODE_RECMASK)
m->u.par.rchan = htons(s->sub.nch);
m->u.par.rate = htonl(s->rate);
m->u.par.appbufsz = htonl(s->appbufsz);
m->u.par.bufsz = htonl(SLOT_BUFSZ(s));
m->u.par.round = htonl(s->round);
f->rstate = SOCK_RRET;
f->rtodo = sizeof(struct amsg);
break;
case AMSG_SETVOL:
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": SETVOL message\n");
}
#endif
if (f->pstate < SOCK_INIT || s == NULL) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": SETVOL, wrong state\n");
}
#endif
sock_close(f);
return 0;
}
ctl = ntohl(m->u.vol.ctl);
if (ctl > MIDI_MAXCTL) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": SETVOL, volume out of range\n");
}
#endif
sock_close(f);
return 0;
}
f->rtodo = sizeof(struct amsg);
f->rstate = SOCK_RMSG;
f->lastvol = ctl; /* dont trigger feedback message */
slot_setvol(s, ctl);
dev_midi_vol(s->dev, s);
dev_onval(s->dev,
CTLADDR_SLOT_LEVEL(f->slot - s->dev->slot), ctl);
break;
case AMSG_CTLSUB:
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": CTLSUB message, desc = ");
log_putx(m->u.ctlsub.desc);
log_puts(", val = ");
log_putx(m->u.ctlsub.val);
log_puts("\n");
}
#endif
if (f->pstate != SOCK_INIT || f->ctlslot == NULL) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": CTLSUB, wrong state\n");
}
#endif
sock_close(f);
return 0;
}
if (m->u.ctlsub.desc) {
if (!(f->ctlops & SOCK_CTLDESC)) {
ctl = f->ctlslot->mask;
c = f->ctlslot->dev->ctl_list;
while (c != NULL) {
c->desc_mask |= ctl;
c = c->next;
}
f->ctlops |= SOCK_CTLDESC;
f->ctlsyncpending = 1;
}
} else
f->ctlops &= ~SOCK_CTLDESC;
if (m->u.ctlsub.val) {
f->ctlops |= SOCK_CTLVAL;
} else
f->ctlops &= ~SOCK_CTLVAL;
f->rstate = SOCK_RMSG;
f->rtodo = sizeof(struct amsg);
break;
case AMSG_CTLSET:
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": CTLSET message\n");
}
#endif
if (f->pstate < SOCK_INIT || f->ctlslot == NULL) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": CTLSET, wrong state\n");
}
#endif
sock_close(f);
return 0;
}
if (!dev_setctl(f->ctlslot->dev,
ntohs(m->u.ctlset.addr),
ntohs(m->u.ctlset.val))) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": CTLSET, wrong addr/val\n");
}
#endif
sock_close(f);
return 0;
}
f->rtodo = sizeof(struct amsg);
f->rstate = SOCK_RMSG;
break;
case AMSG_AUTH:
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": AUTH message\n");
}
#endif
if (f->pstate != SOCK_AUTH) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": AUTH, wrong state\n");
}
#endif
sock_close(f);
return 0;
}
if (!sock_auth(f)) {
sock_close(f);
return 0;
}
f->rstate = SOCK_RMSG;
f->rtodo = sizeof(struct amsg);
break;
case AMSG_HELLO:
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": HELLO message\n");
}
#endif
if (f->pstate != SOCK_HELLO) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": HELLO, wrong state\n");
}
#endif
sock_close(f);
return 0;
}
if (!sock_hello(f)) {
sock_close(f);
return 0;
}
AMSG_INIT(m);
m->cmd = htonl(AMSG_ACK);
f->rstate = SOCK_RRET;
f->rtodo = sizeof(struct amsg);
break;
case AMSG_BYE:
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": BYE message\n");
}
#endif
if (s != NULL && f->pstate != SOCK_INIT) {
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": BYE, wrong state\n");
}
#endif
}
sock_close(f);
return 0;
default:
#ifdef DEBUG
if (log_level >= 1) {
sock_log(f);
log_puts(": unknown command in message\n");
}
#endif
sock_close(f);
return 0;
}
return 1;
}
/*
* build a message in f->wmsg, return 1 on success and 0 if
* there's nothing to do. Assume f->wstate is SOCK_WIDLE
*/
int
sock_buildmsg(struct sock *f)
{
unsigned int size, mask;
struct amsg_ctl_desc *desc;
struct ctl *c, **pc;
/*
* If pos changed (or initial tick), build a MOVE message.
*/
if (f->tickpending) {
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": building MOVE message, delta = ");
log_puti(f->slot->delta);
log_puts("\n");
}
#endif
AMSG_INIT(&f->wmsg);
f->wmsg.cmd = htonl(AMSG_MOVE);
f->wmsg.u.ts.delta = htonl(f->slot->delta);
f->wtodo = sizeof(struct amsg);
f->wstate = SOCK_WMSG;
f->tickpending = 0;
/*
* XXX: use tickpending as accumulator rather than
* slot->delta
*/
f->slot->delta = 0;
return 1;
}
if (f->fillpending > 0) {
AMSG_INIT(&f->wmsg);
f->wmsg.cmd = htonl(AMSG_FLOWCTL);
f->wmsg.u.ts.delta = htonl(f->fillpending);
size = f->fillpending;
if (f->slot)
size *= f->slot->mix.bpf;
f->rmax += size;
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": building FLOWCTL message, count = ");
log_puti(f->fillpending);
log_puts(", rmax -> ");
log_puti(f->rmax);
log_puts("\n");
}
#endif
f->wtodo = sizeof(struct amsg);
f->wstate = SOCK_WMSG;
f->fillpending = 0;
return 1;
}
/*
* if volume changed build a SETVOL message
*/
if (f->pstate >= SOCK_START && f->slot->vol != f->lastvol) {
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": building SETVOL message, vol = ");
log_puti(f->slot->vol);
log_puts("\n");
}
#endif
AMSG_INIT(&f->wmsg);
f->wmsg.cmd = htonl(AMSG_SETVOL);
f->wmsg.u.vol.ctl = htonl(f->slot->vol);
f->wtodo = sizeof(struct amsg);
f->wstate = SOCK_WMSG;
f->lastvol = f->slot->vol;
return 1;
}
if (f->midi != NULL && f->midi->obuf.used > 0) {
size = f->midi->obuf.used;
if (size > AMSG_DATAMAX)
size = AMSG_DATAMAX;
AMSG_INIT(&f->wmsg);
f->wmsg.cmd = htonl(AMSG_DATA);
f->wmsg.u.data.size = htonl(size);
f->wtodo = sizeof(struct amsg);
f->wstate = SOCK_WMSG;
return 1;
}
/*
* If data available, build a DATA message.
*/
if (f->slot != NULL && f->wmax > 0 && f->slot->sub.buf.used > 0) {
size = f->slot->sub.buf.used;
if (size > AMSG_DATAMAX)
size = AMSG_DATAMAX;
if (size > f->walign)
size = f->walign;
if (size > f->wmax)
size = f->wmax;
size -= size % f->slot->sub.bpf;
#ifdef DEBUG
if (size == 0) {
sock_log(f);
log_puts(": sock_buildmsg size == 0\n");
panic();
}
#endif
f->walign -= size;
f->wmax -= size;
if (f->walign == 0)
f->walign = f->slot->round * f->slot->sub.bpf;
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": building audio DATA message, size = ");
log_puti(size);
log_puts("\n");
}
#endif
AMSG_INIT(&f->wmsg);
f->wmsg.cmd = htonl(AMSG_DATA);
f->wmsg.u.data.size = htonl(size);
f->wtodo = sizeof(struct amsg);
f->wstate = SOCK_WMSG;
return 1;
}
if (f->stoppending) {
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": building STOP message\n");
}
#endif
f->stoppending = 0;
f->pstate = SOCK_INIT;
AMSG_INIT(&f->wmsg);
f->wmsg.cmd = htonl(AMSG_STOP);
f->wtodo = sizeof(struct amsg);
f->wstate = SOCK_WMSG;
return 1;
}
/*
* XXX: add a flag indicating if there are changes
* in controls not seen by this client, rather
* than walking through the full list of control
* searching for the {desc,val}_mask bits
*/
if (f->ctlslot && (f->ctlops & SOCK_CTLDESC)) {
desc = f->ctldesc;
mask = f->ctlslot->mask;
size = 0;
pc = &f->ctlslot->dev->ctl_list;
while ((c = *pc) != NULL) {
if ((c->desc_mask & mask) == 0 ||
(c->refs_mask & mask) == 0) {
pc = &c->next;
continue;
}
if (size == SOCK_CTLDESC_SIZE *
sizeof(struct amsg_ctl_desc))
break;
c->desc_mask &= ~mask;
c->val_mask &= ~mask;
strlcpy(desc->group, c->group,
AMSG_CTL_NAMEMAX);
strlcpy(desc->node0.name, c->node0.name,
AMSG_CTL_NAMEMAX);
desc->node0.unit = ntohs(c->node0.unit);
strlcpy(desc->node1.name, c->node1.name,
AMSG_CTL_NAMEMAX);
desc->node1.unit = ntohs(c->node1.unit);
desc->type = c->type;
strlcpy(desc->func, c->func, AMSG_CTL_NAMEMAX);
desc->addr = htons(c->addr);
desc->maxval = htons(c->maxval);
desc->curval = htons(c->curval);
size += sizeof(struct amsg_ctl_desc);
desc++;
/* if this is a deleted entry unref it */
if (c->type == CTL_NONE) {
c->refs_mask &= ~mask;
if (c->refs_mask == 0) {
*pc = c->next;
xfree(c);
continue;
}
}
pc = &c->next;
}
if (size > 0) {
AMSG_INIT(&f->wmsg);
f->wmsg.cmd = htonl(AMSG_DATA);
f->wmsg.u.data.size = htonl(size);
f->wtodo = sizeof(struct amsg);
f->wstate = SOCK_WMSG;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": building control DATA message\n");
}
#endif
return 1;
}
}
if (f->ctlslot && (f->ctlops & SOCK_CTLVAL)) {
mask = f->ctlslot->mask;
for (c = f->ctlslot->dev->ctl_list; c != NULL; c = c->next) {
if ((c->val_mask & mask) == 0)
continue;
c->val_mask &= ~mask;
AMSG_INIT(&f->wmsg);
f->wmsg.cmd = htonl(AMSG_CTLSET);
f->wmsg.u.ctlset.addr = htons(c->addr);
f->wmsg.u.ctlset.val = htons(c->curval);
f->wtodo = sizeof(struct amsg);
f->wstate = SOCK_WMSG;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": building CTLSET message\n");
}
#endif
return 1;
}
}
if (f->ctlslot && f->ctlsyncpending) {
f->ctlsyncpending = 0;
f->wmsg.cmd = htonl(AMSG_CTLSYNC);
f->wtodo = sizeof(struct amsg);
f->wstate = SOCK_WMSG;
#ifdef DEBUG
if (log_level >= 3) {
sock_log(f);
log_puts(": building CTLSYNC message\n");
}
#endif
return 1;
}
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": no messages to build anymore, idling...\n");
}
#endif
f->wstate = SOCK_WIDLE;
return 0;
}
/*
* iteration of the socket reader loop, return 1 on success
*/
int
sock_read(struct sock *f)
{
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": reading ");
log_putu(f->rtodo);
log_puts(" todo\n");
}
#endif
switch (f->rstate) {
case SOCK_RIDLE:
return 0;
case SOCK_RMSG:
if (!sock_rmsg(f))
return 0;
if (!sock_execmsg(f))
return 0;
break;
case SOCK_RDATA:
if (!sock_rdata(f))
return 0;
f->rstate = SOCK_RMSG;
f->rtodo = sizeof(struct amsg);
break;
case SOCK_RRET:
if (f->wstate != SOCK_WIDLE) {
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": can't reply, write-end blocked\n");
}
#endif
return 0;
}
f->wmsg = f->rmsg;
f->wstate = SOCK_WMSG;
f->wtodo = sizeof(struct amsg);
f->rstate = SOCK_RMSG;
f->rtodo = sizeof(struct amsg);
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": copied RRET message\n");
}
#endif
}
return 1;
}
/*
* iteration of the socket writer loop, return 1 on success
*/
int
sock_write(struct sock *f)
{
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": writing");
if (f->wstate != SOCK_WIDLE) {
log_puts(" todo = ");
log_putu(f->wtodo);
}
log_puts("\n");
}
#endif
switch (f->wstate) {
case SOCK_WMSG:
if (!sock_wmsg(f))
return 0;
/*
* f->wmsg is either build by sock_buildmsg() or
* copied from f->rmsg (in the SOCK_RRET state), so
* it's safe.
*/
if (ntohl(f->wmsg.cmd) != AMSG_DATA) {
f->wstate = SOCK_WIDLE;
f->wtodo = 0xdeadbeef;
break;
}
f->wstate = SOCK_WDATA;
f->wsize = f->wtodo = ntohl(f->wmsg.u.data.size);
/* PASSTHROUGH */
case SOCK_WDATA:
if (!sock_wdata(f))
return 0;
if (f->wtodo > 0)
break;
f->wstate = SOCK_WIDLE;
f->wtodo = 0xdeadbeef;
if (f->pstate == SOCK_STOP) {
f->pstate = SOCK_INIT;
f->wmax = 0;
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": drained, moved to INIT state\n");
}
#endif
}
/* PASSTHROUGH */
case SOCK_WIDLE:
if (f->rstate == SOCK_RRET) {
f->wmsg = f->rmsg;
f->wstate = SOCK_WMSG;
f->wtodo = sizeof(struct amsg);
f->rstate = SOCK_RMSG;
f->rtodo = sizeof(struct amsg);
#ifdef DEBUG
if (log_level >= 4) {
sock_log(f);
log_puts(": copied RRET message\n");
}
#endif
} else {
if (!sock_buildmsg(f))
return 0;
}
break;
#ifdef DEBUG
default:
sock_log(f);
log_puts(": bad writing end state\n");
panic();
#endif
}
return 1;
}
int
sock_pollfd(void *arg, struct pollfd *pfd)
{
struct sock *f = arg;
int events = 0;
/*
* feedback counters, clock ticks and alike may have changed,
* prepare a message to trigger writes
*
* XXX: doing this at the beginning of the cycle is not optimal,
* because state is changed at the end of the read cycle, and
* thus counters, ret message and alike are generated then.
*/
if (f->wstate == SOCK_WIDLE && f->rstate != SOCK_RRET)
sock_buildmsg(f);
if (f->rstate == SOCK_RMSG ||
f->rstate == SOCK_RDATA)
events |= POLLIN;
if (f->rstate == SOCK_RRET ||
f->wstate == SOCK_WMSG ||
f->wstate == SOCK_WDATA)
events |= POLLOUT;
pfd->fd = f->fd;
pfd->events = events;
return 1;
}
int
sock_revents(void *arg, struct pollfd *pfd)
{
return pfd->revents;
}
void
sock_in(void *arg)
{
struct sock *f = arg;
while (sock_read(f))
;
}
void
sock_out(void *arg)
{
struct sock *f = arg;
while (sock_write(f))
;
}
void
sock_hup(void *arg)
{
struct sock *f = arg;
sock_close(f);
}