sndio/sndiod/sock.c

1586 lines
30 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"
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);
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 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
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;
}
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;
}
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->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->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);
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;
mode = ntohs(p->mode);
#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:
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;
}
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, 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 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);
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;
/*
* 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;
}
#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);
}