Skip to content
Permalink
14a9cff0c2
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
2467 lines (2088 sloc) 58.1 KB
/*
* osspd - OSS Proxy Daemon: emulate OSS device using CUSE
*
* Copyright (C) 2008-2010 SUSE Linux Products GmbH
* Copyright (C) 2008-2010 Tejun Heo <tj@kernel.org>
*
* This file is released under the GPLv2.
*/
#define FUSE_USE_VERSION 28
#define _GNU_SOURCE
#include <assert.h>
#include <cuse_lowlevel.h>
#include <fcntl.h>
#include <fuse_opt.h>
#include <libgen.h>
#include <limits.h>
#include <pthread.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/soundcard.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include "ossp.h"
#include "ossp-util.h"
#define OSSP_MMAP
#define DFL_MIXER_NAME "mixer"
#define DFL_DSP_NAME "dsp"
#define DFL_ADSP_NAME "adsp"
#define STRFMT "S[%u/%d]"
#define STRID(os) os->id, os->pid
#define dbg1_os(os, fmt, args...) dbg1(STRFMT" "fmt, STRID(os) , ##args)
#define dbg0_os(os, fmt, args...) dbg0(STRFMT" "fmt, STRID(os) , ##args)
#define warn_os(os, fmt, args...) warn(STRFMT" "fmt, STRID(os) , ##args)
#define err_os(os, fmt, args...) err(STRFMT" "fmt, STRID(os) , ##args)
#define warn_ose(os, err, fmt, args...) \
warn_e(err, STRFMT" "fmt, STRID(os) , ##args)
#define err_ose(os, err, fmt, args...) \
err_e(err, STRFMT" "fmt, STRID(os) , ##args)
enum {
SNDRV_OSS_VERSION = ((3<<16)|(8<<8)|(1<<4)|(0)), /* 3.8.1a */
DFL_MIXER_MAJOR = 14,
DFL_MIXER_MINOR = 0,
DFL_DSP_MAJOR = 14,
DFL_DSP_MINOR = 3,
DFL_ADSP_MAJOR = 14,
DFL_ADSP_MINOR = 12,
DFL_MAX_STREAMS = 128,
MIXER_PUT_DELAY = 600, /* 10 mins */
/* DSPS_MMAP_SIZE / 2 must be multiple of SHMLBA */
DSPS_MMAP_SIZE = 2 * (512 << 10), /* 512k for each dir */
};
struct ossp_uid_cnt {
struct list_head link;
uid_t uid;
unsigned nr_os;
};
struct ossp_mixer {
pid_t pgrp;
struct list_head link;
struct list_head delayed_put_link;
unsigned refcnt;
/* the following two fields are protected by mixer_mutex */
int vol[2][2];
int modify_counter;
time_t put_expires;
};
struct ossp_mixer_cmd {
struct ossp_mixer *mixer;
struct ossp_mixer_arg set;
int out_dir;
int rvol;
};
#define for_each_vol(i, j) \
for (i = 0, j = 0; i < 2; j += i << 1, j++, i = j >> 1, j &= 1)
struct ossp_stream {
unsigned id; /* stream ID */
struct list_head link;
struct list_head pgrp_link;
struct list_head notify_link;
unsigned refcnt;
pthread_mutex_t cmd_mutex;
pthread_mutex_t mmap_mutex;
struct fuse_pollhandle *ph;
/* stream owner info */
pid_t pid;
pid_t pgrp;
uid_t uid;
gid_t gid;
/* slave info */
pid_t slave_pid;
int cmd_fd;
int notify_tx;
int notify_rx;
/* the following dead flag is set asynchronously, keep it separate. */
int dead;
/* stream mixer state, protected by mixer_mutex */
int mixer_pending;
int vol[2][2];
int vol_set[2][2];
int mmap_fd;
size_t mmap_size;
void *mmap;
void *mmap_addr[2];
struct ossp_transfer *mmap_transfer;
struct ossp_uid_cnt *ucnt;
struct fuse_session *se; /* associated fuse session */
struct ossp_mixer *mixer;
};
struct ossp_dsp_stream {
struct ossp_stream os;
unsigned rw;
unsigned mmapped;
int nonblock;
};
#define os_to_dsps(_os) container_of(_os, struct ossp_dsp_stream, os)
static unsigned max_streams;
static unsigned umax_streams;
static unsigned hashtbl_size;
static char dsp_slave_path[PATH_MAX];
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t mixer_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned long *os_id_bitmap;
static unsigned nr_mixers;
static struct list_head *mixer_tbl; /* indexed by PGRP */
static struct list_head *os_tbl; /* indexed by ID */
static struct list_head *os_pgrp_tbl; /* indexed by PGRP */
static struct list_head *os_notify_tbl; /* indexed by notify fd */
static LIST_HEAD(uid_cnt_list);
static int notify_epfd; /* epoll used to monitor notify fds */
static pthread_t notify_poller_thread;
static pthread_t slave_reaper_thread;
static pthread_t mixer_delayed_put_thread;
static pthread_t cuse_mixer_thread;
static pthread_t cuse_adsp_thread;
static pthread_cond_t notify_poller_kill_wait = PTHREAD_COND_INITIALIZER;
static pthread_cond_t slave_reaper_wait = PTHREAD_COND_INITIALIZER;
static LIST_HEAD(slave_corpse_list);
static LIST_HEAD(mixer_delayed_put_head); /* delayed reference */
static pthread_cond_t mixer_delayed_put_cond = PTHREAD_COND_INITIALIZER;
static int init_wait_fd = -1;
static int exit_on_idle;
static struct fuse_session *mixer_se;
static struct fuse_session *dsp_se;
static struct fuse_session *adsp_se;
static void put_os(struct ossp_stream *os);
/***************************************************************************
* Accessors
*/
static struct list_head *mixer_tbl_head(pid_t pid)
{
return &mixer_tbl[pid % hashtbl_size];
}
static struct list_head *os_tbl_head(uint64_t id)
{
return &os_tbl[id % hashtbl_size];
}
static struct list_head *os_pgrp_tbl_head(pid_t pgrp)
{
return &os_pgrp_tbl[pgrp % hashtbl_size];
}
static struct list_head *os_notify_tbl_head(int notify_rx)
{
return &os_notify_tbl[notify_rx % hashtbl_size];
}
static struct ossp_mixer *find_mixer_locked(pid_t pgrp)
{
struct ossp_mixer *mixer;
list_for_each_entry(mixer, mixer_tbl_head(pgrp), link)
if (mixer->pgrp == pgrp)
return mixer;
return NULL;
}
static struct ossp_mixer *find_mixer(pid_t pgrp)
{
struct ossp_mixer *mixer;
pthread_mutex_lock(&mutex);
mixer = find_mixer_locked(pgrp);
pthread_mutex_unlock(&mutex);
return mixer;
}
static struct ossp_stream *find_os(unsigned id)
{
struct ossp_stream *os, *found = NULL;
pthread_mutex_lock(&mutex);
list_for_each_entry(os, os_tbl_head(id), link)
if (os->id == id) {
found = os;
break;
}
pthread_mutex_unlock(&mutex);
return found;
}
static struct ossp_stream *find_os_by_notify_rx(int notify_rx)
{
struct ossp_stream *os, *found = NULL;
pthread_mutex_lock(&mutex);
list_for_each_entry(os, os_notify_tbl_head(notify_rx), notify_link)
if (os->notify_rx == notify_rx) {
found = os;
break;
}
pthread_mutex_unlock(&mutex);
return found;
}
/***************************************************************************
* Command and ioctl helpers
*/
static ssize_t exec_cmd_intern(struct ossp_stream *os, enum ossp_opcode opcode,
const void *carg, size_t carg_size, const void *din, size_t din_size,
void *rarg, size_t rarg_size, void *dout, size_t *dout_sizep, int fd)
{
size_t dout_size = dout_sizep ? *dout_sizep : 0;
struct ossp_cmd cmd = { .magic = OSSP_CMD_MAGIC, .opcode = opcode,
.din_size = din_size,
.dout_size = dout_size };
struct iovec iov = { &cmd, sizeof(cmd) };
struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
struct ossp_reply reply = { };
char cmsg_buf[CMSG_SPACE(sizeof(fd))];
char reason[512];
int rc;
if (os->dead)
return -EIO;
dbg1_os(os, "%s carg=%zu din=%zu rarg=%zu dout=%zu",
ossp_cmd_str[opcode], carg_size, din_size, rarg_size,
dout_size);
if (fd >= 0) {
struct cmsghdr *cmsg;
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
*(int *)CMSG_DATA(cmsg) = fd;
msg.msg_controllen = cmsg->cmsg_len;
}
if (sendmsg(os->cmd_fd, &msg, 0) <= 0) {
rc = -errno;
snprintf(reason, sizeof(reason), "command sendmsg failed: %s",
strerror(-rc));
goto fail;
}
if ((rc = write_fill(os->cmd_fd, carg, carg_size)) < 0 ||
(rc = write_fill(os->cmd_fd, din, din_size)) < 0) {
snprintf(reason, sizeof(reason),
"can't tranfer command argument and/or data: %s",
strerror(-rc));
goto fail;
}
if ((rc = read_fill(os->cmd_fd, &reply, sizeof(reply))) < 0) {
snprintf(reason, sizeof(reason), "can't read reply: %s",
strerror(-rc));
goto fail;
}
if (reply.magic != OSSP_REPLY_MAGIC) {
snprintf(reason, sizeof(reason),
"reply magic mismatch %x != %x",
reply.magic, OSSP_REPLY_MAGIC);
rc = -EINVAL;
goto fail;
}
if (reply.result < 0)
goto out_unlock;
if (reply.dout_size > dout_size) {
snprintf(reason, sizeof(reason),
"data out size overflow %zu > %zu",
reply.dout_size, dout_size);
rc = -EINVAL;
goto fail;
}
dout_size = reply.dout_size;
if (dout_sizep)
*dout_sizep = dout_size;
if ((rc = read_fill(os->cmd_fd, rarg, rarg_size)) < 0 ||
(rc = read_fill(os->cmd_fd, dout, dout_size)) < 0) {
snprintf(reason, sizeof(reason), "can't read data out: %s",
strerror(-rc));
goto fail;
}
out_unlock:
dbg1_os(os, " completed, result=%d dout=%zu",
reply.result, dout_size);
return reply.result;
fail:
warn_os(os, "communication with slave failed (%s)", reason);
os->dead = 1;
return rc;
}
static ssize_t exec_cmd(struct ossp_stream *os, enum ossp_opcode opcode,
const void *carg, size_t carg_size, const void *din, size_t din_size,
void *rarg, size_t rarg_size, void *dout, size_t *dout_sizep, int fd)
{
int is_mixer;
int i, j;
ssize_t ret, mret;
/* mixer command is handled exlicitly below */
is_mixer = opcode == OSSP_MIXER;
if (is_mixer) {
ret = -pthread_mutex_trylock(&os->cmd_mutex);
if (ret)
return ret;
} else {
pthread_mutex_lock(&os->cmd_mutex);
ret = exec_cmd_intern(os, opcode, carg, carg_size,
din, din_size, rarg, rarg_size,
dout, dout_sizep, fd);
}
/* lazy mixer handling */
pthread_mutex_lock(&mixer_mutex);
if (os->mixer_pending) {
struct ossp_mixer_arg marg;
repeat_mixer:
/* we have mixer command pending */
memcpy(marg.vol, os->vol_set, sizeof(os->vol_set));
memset(os->vol_set, -1, sizeof(os->vol_set));
pthread_mutex_unlock(&mixer_mutex);
mret = exec_cmd_intern(os, OSSP_MIXER, &marg, sizeof(marg),
NULL, 0, &marg, sizeof(marg), NULL, NULL,
-1);
pthread_mutex_lock(&mixer_mutex);
/* was there mixer set request while executing mixer command? */
for_each_vol(i, j)
if (os->vol_set[i][j] >= 0)
goto repeat_mixer;
/* update internal mixer state */
if (mret == 0) {
for_each_vol(i, j) {
if (marg.vol[i][j] >= 0) {
if (os->vol[i][j] != marg.vol[i][j])
os->mixer->modify_counter++;
os->vol[i][j] = marg.vol[i][j];
}
}
}
os->mixer_pending = 0;
}
pthread_mutex_unlock(&os->cmd_mutex);
/*
* mixer mutex must be released after cmd_mutex so that
* exec_mixer_cmd() can guarantee that mixer_pending flags
* will be handled immediately or when the currently
* in-progress command completes.
*/
pthread_mutex_unlock(&mixer_mutex);
return is_mixer ? mret : ret;
}
static ssize_t exec_simple_cmd(struct ossp_stream *os,
enum ossp_opcode opcode, void *carg, void *rarg)
{
return exec_cmd(os, opcode,
carg, ossp_arg_sizes[opcode].carg_size, NULL, 0,
rarg, ossp_arg_sizes[opcode].rarg_size, NULL, NULL, -1);
}
static int ioctl_prep_uarg(fuse_req_t req, void *in, size_t in_sz, void *out,
size_t out_sz, void *uarg, const void *in_buf,
size_t in_bufsz, size_t out_bufsz)
{
struct iovec in_iov = { }, out_iov = { };
int retry = 0;
if (in) {
if (!in_bufsz) {
in_iov.iov_base = uarg;
in_iov.iov_len = in_sz;
retry = 1;
} else {
assert(in_bufsz == in_sz);
memcpy(in, in_buf, in_sz);
}
}
if (out) {
if (!out_bufsz) {
out_iov.iov_base = uarg;
out_iov.iov_len = out_sz;
retry = 1;
} else
assert(out_bufsz == out_sz);
}
if (retry)
fuse_reply_ioctl_retry(req, &in_iov, 1, &out_iov, 1);
return retry;
}
#define PREP_UARG(inp, outp) do { \
if (ioctl_prep_uarg(req, (inp), sizeof(*(inp)), \
(outp), sizeof(*(outp)), uarg, \
in_buf, in_bufsz, out_bufsz)) \
return; \
} while (0)
/***************************************************************************
* Mixer implementation
*/
static void put_mixer_real(struct ossp_mixer *mixer)
{
if (!--mixer->refcnt) {
dbg0("DESTROY mixer(%d)", mixer->pgrp);
list_del_init(&mixer->link);
list_del_init(&mixer->delayed_put_link);
free(mixer);
nr_mixers--;
/*
* If exit_on_idle, mixer for pgrp0 is touched during
* init and each stream has mixer attached. As mixers
* are destroyed after they have been idle for
* MIXER_PUT_DELAY seconds, we can use it for idle
* detection. Note that this might race with
* concurrent open. The race is inherent.
*/
if (exit_on_idle && !nr_mixers) {
info("idle, exiting");
exit(0);
}
}
}
static struct ossp_mixer *get_mixer(pid_t pgrp)
{
struct ossp_mixer *mixer;
pthread_mutex_lock(&mutex);
/* is there a matching one? */
mixer = find_mixer_locked(pgrp);
if (mixer) {
if (list_empty(&mixer->delayed_put_link))
mixer->refcnt++;
else
list_del_init(&mixer->delayed_put_link);
goto out_unlock;
}
/* reap delayed put list if there are too many mixers */
while (nr_mixers > 2 * max_streams &&
!list_empty(&mixer_delayed_put_head)) {
struct ossp_mixer *mixer =
list_first_entry(&mixer_delayed_put_head,
struct ossp_mixer, delayed_put_link);
assert(mixer->refcnt == 1);
put_mixer_real(mixer);
}
/* create a new one */
mixer = calloc(1, sizeof(*mixer));
if (!mixer) {
warn("failed to allocate mixer for %d", pgrp);
mixer = NULL;
goto out_unlock;
}
mixer->pgrp = pgrp;
INIT_LIST_HEAD(&mixer->link);
INIT_LIST_HEAD(&mixer->delayed_put_link);
mixer->refcnt = 1;
memset(mixer->vol, -1, sizeof(mixer->vol));
list_add(&mixer->link, mixer_tbl_head(pgrp));
nr_mixers++;
dbg0("CREATE mixer(%d)", pgrp);
out_unlock:
pthread_mutex_unlock(&mutex);
return mixer;
}
static void put_mixer(struct ossp_mixer *mixer)
{
pthread_mutex_lock(&mutex);
if (mixer) {
if (mixer->refcnt == 1) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
mixer->put_expires = ts.tv_sec + MIXER_PUT_DELAY;
list_add_tail(&mixer->delayed_put_link,
&mixer_delayed_put_head);
pthread_cond_signal(&mixer_delayed_put_cond);
} else
put_mixer_real(mixer);
}
pthread_mutex_unlock(&mutex);
}
static void *mixer_delayed_put_worker(void *arg)
{
struct ossp_mixer *mixer;
struct timespec ts;
time_t now;
pthread_mutex_lock(&mutex);
again:
clock_gettime(CLOCK_REALTIME, &ts);
now = ts.tv_sec;
mixer = NULL;
while (!list_empty(&mixer_delayed_put_head)) {
mixer = list_first_entry(&mixer_delayed_put_head,
struct ossp_mixer, delayed_put_link);
if (now <= mixer->put_expires)
break;
assert(mixer->refcnt == 1);
put_mixer_real(mixer);
mixer = NULL;
}
if (mixer) {
ts.tv_sec = mixer->put_expires + 1;
pthread_cond_timedwait(&mixer_delayed_put_cond, &mutex, &ts);
} else
pthread_cond_wait(&mixer_delayed_put_cond, &mutex);
goto again;
}
static void init_mixer_cmd(struct ossp_mixer_cmd *mxcmd,
struct ossp_mixer *mixer)
{
memset(mxcmd, 0, sizeof(*mxcmd));
memset(&mxcmd->set.vol, -1, sizeof(mxcmd->set.vol));
mxcmd->mixer = mixer;
mxcmd->out_dir = -1;
}
static int exec_mixer_cmd(struct ossp_mixer_cmd *mxcmd, struct ossp_stream *os)
{
int i, j, rc;
/*
* Set pending flags before trying to execute mixer command.
* Combined with lock release order in exec_cmd(), this
* guarantees that the mixer command will be executed
* immediately or when the current command completes.
*/
pthread_mutex_lock(&mixer_mutex);
os->mixer_pending = 1;
for_each_vol(i, j)
if (mxcmd->set.vol[i][j] >= 0)
os->vol_set[i][j] = mxcmd->set.vol[i][j];
pthread_mutex_unlock(&mixer_mutex);
rc = exec_simple_cmd(os, OSSP_MIXER, NULL, NULL);
if (rc >= 0) {
dbg0_os(os, "volume set=%d/%d:%d/%d get=%d/%d:%d/%d",
mxcmd->set.vol[PLAY][LEFT], mxcmd->set.vol[PLAY][RIGHT],
mxcmd->set.vol[REC][LEFT], mxcmd->set.vol[REC][RIGHT],
os->vol[PLAY][LEFT], os->vol[PLAY][RIGHT],
os->vol[REC][LEFT], os->vol[REC][RIGHT]);
} else if (rc != -EBUSY)
warn_ose(os, rc, "mixer command failed");
return rc;
}
static void finish_mixer_cmd(struct ossp_mixer_cmd *mxcmd)
{
struct ossp_mixer *mixer = mxcmd->mixer;
struct ossp_stream *os;
int dir = mxcmd->out_dir;
int vol[2][2] = { };
int cnt[2][2] = { };
int i, j;
pthread_mutex_lock(&mixer_mutex);
/* get volume of all streams attached to this mixer */
pthread_mutex_lock(&mutex);
list_for_each_entry(os, os_pgrp_tbl_head(mixer->pgrp), pgrp_link) {
if (os->pgrp != mixer->pgrp)
continue;
for_each_vol(i, j) {
if (os->vol[i][j] < 0)
continue;
vol[i][j] += os->vol[i][j];
cnt[i][j]++;
}
}
pthread_mutex_unlock(&mutex);
/* calculate the summary volume values */
for_each_vol(i, j) {
if (mxcmd->set.vol[i][j] >= 0)
vol[i][j] = mxcmd->set.vol[i][j];
else if (cnt[i][j])
vol[i][j] = vol[i][j] / cnt[i][j];
else if (mixer->vol[i][j] >= 0)
vol[i][j] = mixer->vol[i][j];
else
vol[i][j] = 100;
vol[i][j] = min(max(0, vol[i][j]), 100);
}
if (dir >= 0)
mxcmd->rvol = vol[dir][LEFT] | (vol[dir][RIGHT] << 8);
pthread_mutex_unlock(&mixer_mutex);
}
static void mixer_simple_ioctl(fuse_req_t req, struct ossp_mixer *mixer,
unsigned cmd, void *uarg, const void *in_buf,
size_t in_bufsz, size_t out_bufsz,
int *not_minep)
{
const char *id = "OSS Proxy", *name = "Mixer";
int i;
switch (cmd) {
case SOUND_MIXER_INFO: {
struct mixer_info info = { };
PREP_UARG(NULL, &info);
strncpy(info.id, id, sizeof(info.id) - 1);
strncpy(info.name, name, sizeof(info.name) - 1);
info.modify_counter = mixer->modify_counter;
fuse_reply_ioctl(req, 0, &info, sizeof(info));
break;
}
case SOUND_OLD_MIXER_INFO: {
struct _old_mixer_info info = { };
PREP_UARG(NULL, &info);
strncpy(info.id, id, sizeof(info.id) - 1);
strncpy(info.name, name, sizeof(info.name) - 1);
fuse_reply_ioctl(req, 0, &info, sizeof(info));
break;
}
case OSS_GETVERSION:
i = SNDRV_OSS_VERSION;
goto puti;
case SOUND_MIXER_READ_DEVMASK:
case SOUND_MIXER_READ_STEREODEVS:
i = SOUND_MASK_PCM | SOUND_MASK_IGAIN;
goto puti;
case SOUND_MIXER_READ_CAPS:
i = SOUND_CAP_EXCL_INPUT;
goto puti;
case SOUND_MIXER_READ_RECMASK:
case SOUND_MIXER_READ_RECSRC:
i = SOUND_MASK_IGAIN;
goto puti;
puti:
PREP_UARG(NULL, &i);
fuse_reply_ioctl(req, 0, &i, sizeof(i));
break;
case SOUND_MIXER_WRITE_RECSRC:
fuse_reply_ioctl(req, 0, NULL, 0);
break;
default:
*not_minep = 1;
}
}
static void mixer_do_ioctl(fuse_req_t req, struct ossp_mixer *mixer,
unsigned cmd, void *uarg, const void *in_buf,
size_t in_bufsz, size_t out_bufsz)
{
struct ossp_mixer_cmd mxcmd;
struct ossp_stream *os, **osa;
int not_mine = 0;
int slot = cmd & 0xff, dir;
int nr_os;
int i, rc;
mixer_simple_ioctl(req, mixer, cmd, uarg, in_buf, in_bufsz, out_bufsz,
&not_mine);
if (!not_mine)
return;
rc = -ENXIO;
if (!(cmd & (SIOC_IN | SIOC_OUT)))
goto err;
/*
* Okay, it's not one of the easy ones. Build mxcmd for
* actual volume control.
*/
if (cmd & SIOC_IN)
PREP_UARG(&i, &i);
else
PREP_UARG(NULL, &i);
switch (slot) {
case SOUND_MIXER_PCM:
dir = PLAY;
break;
case SOUND_MIXER_IGAIN:
dir = REC;
break;
default:
i = 0;
fuse_reply_ioctl(req, 0, &i, sizeof(i));
return;
}
init_mixer_cmd(&mxcmd, mixer);
if (cmd & SIOC_IN) {
unsigned l, r;
rc = -EINVAL;
l = i & 0xff;
r = (i >> 8) & 0xff;
if (l > 100 || r > 100)
goto err;
mixer->vol[dir][LEFT] = mxcmd.set.vol[dir][LEFT] = l;
mixer->vol[dir][RIGHT] = mxcmd.set.vol[dir][RIGHT] = r;
}
mxcmd.out_dir = dir;
/*
* Apply volume conrol
*/
/* acquire target streams */
pthread_mutex_lock(&mutex);
osa = calloc(max_streams, sizeof(osa[0]));
if (!osa) {
pthread_mutex_unlock(&mutex);
rc = -ENOMEM;
goto err;
}
nr_os = 0;
list_for_each_entry(os, os_pgrp_tbl_head(mixer->pgrp), pgrp_link) {
if (os->pgrp == mixer->pgrp) {
osa[nr_os++] = os;
os->refcnt++;
}
}
pthread_mutex_unlock(&mutex);
/* execute mxcmd for each stream and put it */
for (i = 0; i < nr_os; i++) {
exec_mixer_cmd(&mxcmd, osa[i]);
put_os(osa[i]);
}
finish_mixer_cmd(&mxcmd);
free(osa);
if (out_bufsz)
fuse_reply_ioctl(req, 0, &mxcmd.rvol, sizeof(mxcmd.rvol));
else
fuse_reply_ioctl(req, 0, NULL, 0);
return;
err:
fuse_reply_err(req, -rc);
}
static void mixer_open(fuse_req_t req, struct fuse_file_info *fi)
{
pid_t pid = fuse_req_ctx(req)->pid, pgrp;
struct ossp_mixer *mixer;
int rc;
rc = get_proc_self_info(pid, &pgrp, NULL, 0);
if (rc) {
err_e(rc, "get_proc_self_info(%d) failed", pid);
fuse_reply_err(req, -rc);
return;
}
mixer = get_mixer(pgrp);
fi->fh = pgrp;
if (mixer)
fuse_reply_open(req, fi);
else
fuse_reply_err(req, ENOMEM);
}
static void mixer_ioctl(fuse_req_t req, int signed_cmd, void *uarg,
struct fuse_file_info *fi, unsigned int flags,
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
{
struct ossp_mixer *mixer;
mixer = find_mixer(fi->fh);
if (!mixer) {
fuse_reply_err(req, EBADF);
return;
}
mixer_do_ioctl(req, mixer, signed_cmd, uarg, in_buf, in_bufsz,
out_bufsz);
}
static void mixer_release(fuse_req_t req, struct fuse_file_info *fi)
{
struct ossp_mixer *mixer;
mixer = find_mixer(fi->fh);
if (mixer) {
put_mixer(mixer);
fuse_reply_err(req, 0);
} else
fuse_reply_err(req, EBADF);
}
/***************************************************************************
* Stream implementation
*/
static int os_create_shared_memory(struct ossp_stream *os, size_t mmap_size)
{
int rc;
os->mmap_fd = -1;
os->mmap_size = 0;
#ifdef OSSP_MMAP
if (mmap_size) {
char shmname[32];
int fd;
void *p;
sprintf(shmname, "/ossp.%i", getpid());
fd = shm_open(shmname, O_RDWR | O_CREAT | O_EXCL | O_TRUNC,
0600);
if (fd == -1) {
rc = -errno;
warn_ose(os, rc, "failed to open shared memory");
return rc;
}
rc = shm_unlink(shmname);
if (rc == -1) {
rc = -errno;
close(fd);
warn_ose(os, rc, "failed to unlink shared memory");
return rc;
}
rc = ftruncate(fd, mmap_size + 2*sizeof(struct ossp_transfer));
if (rc == -1) {
rc = -errno;
close(fd);
warn_ose(os, rc, "failed to set shared memory size");
return rc;
}
rc = fcntl(fd, F_SETFD, 0); /* reset cloexec */
if (rc == -1) {
rc = -errno;
close(fd);
warn_ose(os, rc, "failed to reset close-on-exec for shared memory");
return rc;
}
p = mmap(NULL, mmap_size + 2 * sizeof(struct ossp_transfer),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
rc = -errno;
close(fd);
warn_ose(os, rc, "failed to mmap shared memory");
return rc;
}
os->mmap_addr[PLAY] = p;
os->mmap_addr[REC] = p + mmap_size / 2;
os->mmap_transfer = p + mmap_size;
if (sem_init(&os->mmap_transfer[PLAY].sem, 1, 0) == -1 ||
sem_init(&os->mmap_transfer[REC].sem, 1, 0) == -1) {
rc = -errno;
close(fd);
munmap(p, mmap_size + 2 * sizeof(struct ossp_transfer));
warn_ose(os, rc, "failed to init shared semaphore");
return rc;
}
os->mmap = p;
os->mmap_size = mmap_size;
os->mmap_fd = fd;
#endif
}
return 0;
}
static int alloc_os(size_t stream_size, size_t mmap_size, pid_t pid, uid_t pgrp,
uid_t uid, gid_t gid, int cmd_sock,
const int *notify, struct fuse_session *se,
struct ossp_stream **osp)
{
struct ossp_uid_cnt *tmp_ucnt, *ucnt = NULL;
struct ossp_stream *os;
int rc;
assert(stream_size >= sizeof(struct ossp_stream));
os = calloc(1, stream_size);
if (!os)
return -ENOMEM;
INIT_LIST_HEAD(&os->link);
INIT_LIST_HEAD(&os->pgrp_link);
INIT_LIST_HEAD(&os->notify_link);
os->refcnt = 1;
rc = -pthread_mutex_init(&os->cmd_mutex, NULL);
if (rc)
goto err_free;
rc = -pthread_mutex_init(&os->mmap_mutex, NULL);
if (rc)
goto err_destroy_cmd_mutex;
pthread_mutex_lock(&mutex);
list_for_each_entry(tmp_ucnt, &uid_cnt_list, link)
if (tmp_ucnt->uid == uid) {
ucnt = tmp_ucnt;
break;
}
if (!ucnt) {
rc = -ENOMEM;
ucnt = calloc(1, sizeof(*ucnt));
if (!ucnt)
goto err_unlock;
ucnt->uid = uid;
list_add(&ucnt->link, &uid_cnt_list);
}
rc = -EBUSY;
if (ucnt->nr_os + 1 > umax_streams)
goto err_unlock;
/* everything looks fine, allocate id and init stream */
rc = -EBUSY;
os->id = find_next_zero_bit(os_id_bitmap, max_streams, 0);
if (os->id >= max_streams)
goto err_unlock;
__set_bit(os->id, os_id_bitmap);
os->cmd_fd = cmd_sock;
os->notify_tx = notify[1];
os->notify_rx = notify[0];
os->pid = pid;
os->pgrp = pgrp;
os->uid = uid;
os->gid = gid;
os->ucnt = ucnt;
os->se = se;
rc = os_create_shared_memory(os, mmap_size);
if (rc)
goto err_unlock;
memset(os->vol, -1, sizeof(os->vol));
memset(os->vol_set, -1, sizeof(os->vol));
list_add(&os->link, os_tbl_head(os->id));
list_add(&os->pgrp_link, os_pgrp_tbl_head(os->pgrp));
ucnt->nr_os++;
*osp = os;
pthread_mutex_unlock(&mutex);
return 0;
err_unlock:
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&os->mmap_mutex);
err_destroy_cmd_mutex:
pthread_mutex_destroy(&os->cmd_mutex);
err_free:
free(os);
return rc;
}
static void shutdown_notification(struct ossp_stream *os)
{
struct ossp_notify obituary = { .magic = OSSP_NOTIFY_MAGIC,
.opcode = OSSP_NOTIFY_OBITUARY };
ssize_t ret;
/*
* Shutdown notification for this stream. We politely ask
* notify_poller to shut the receive side down to avoid racing
* with it.
*/
while (os->notify_rx >= 0) {
ret = write(os->notify_tx, &obituary, sizeof(obituary));
if (ret <= 0) {
if (ret == 0)
warn_os(os, "unexpected EOF on notify_tx");
else if (errno != EPIPE)
warn_ose(os, -errno,
"unexpected error on notify_tx");
close(os->notify_rx);
os->notify_rx = -1;
break;
}
if (ret != sizeof(obituary))
warn_os(os, "short transfer on notify_tx");
pthread_cond_wait(&notify_poller_kill_wait, &mutex);
}
}
static void put_os(struct ossp_stream *os)
{
if (!os)
return;
pthread_mutex_lock(&mutex);
assert(os->refcnt);
if (--os->refcnt) {
pthread_mutex_unlock(&mutex);
return;
}
os->dead = 1;
shutdown_notification(os);
dbg0_os(os, "DESTROY");
list_del_init(&os->link);
list_del_init(&os->pgrp_link);
list_del_init(&os->notify_link);
os->ucnt->nr_os--;
pthread_mutex_unlock(&mutex);
close(os->cmd_fd);
close(os->notify_tx);
put_mixer(os->mixer);
pthread_mutex_destroy(&os->cmd_mutex);
pthread_mutex_destroy(&os->mmap_mutex);
pthread_mutex_lock(&mutex);
dbg1_os(os, "stream dead, requesting reaping");
list_add_tail(&os->link, &slave_corpse_list);
pthread_cond_signal(&slave_reaper_wait);
pthread_mutex_unlock(&mutex);
}
static void set_extra_env(pid_t pid)
{
char procenviron[32];
const int step = 1024;
char *data = malloc(step + 1);
int ofs = 0;
int fd;
int ret;
if (!data)
return;
sprintf(procenviron, "/proc/%d/environ", pid);
fd = open(procenviron, O_RDONLY);
if (fd < 0)
return;
/*
* There should really be a 'read whole file to a newly allocated
* buffer' function.
*/
while ((ret = read(fd, data + ofs, step)) > 0) {
char *newdata;
ofs += ret;
newdata = realloc(data, ofs + step + 1);
if (!newdata) {
ret = -1;
break;
}
data = newdata;
}
if (ret == 0) {
char *ptr = data;
/* Append the extra 0 for end condition */
data[ofs] = 0;
while ((ret = strlen(ptr)) > 0) {
/*
* Copy all PULSE variables and DISPLAY so that
* ssh -X remotehost 'mplayer -ao oss' will work
*/
if (!strncmp(ptr, "DISPLAY=", 8) ||
!strncmp(ptr, "PULSE_", 6))
putenv(ptr);
ptr += ret + 1;
}
}
free(data);
close(fd);
}
static int create_os(const char *slave_path,
size_t stream_size, size_t mmap_size,
pid_t pid, pid_t pgrp, uid_t uid, gid_t gid,
struct fuse_session *se, struct ossp_stream **osp)
{
static pthread_mutex_t create_mutex = PTHREAD_MUTEX_INITIALIZER;
int cmd_sock[2] = { -1, -1 };
int notify_sock[2] = { -1, -1 };
struct ossp_stream *os = NULL;
struct epoll_event ev = { };
int i, rc;
/*
* Only one thread can be creating a stream. This is to avoid
* leaking unwanted fds into slaves.
*/
pthread_mutex_lock(&create_mutex);
/* prepare communication channels */
if (socketpair(AF_UNIX, SOCK_STREAM, 0, cmd_sock) ||
socketpair(AF_UNIX, SOCK_STREAM, 0, notify_sock)) {
rc = -errno;
warn_e(rc, "failed to create slave command channel");
goto close_all;
}
if (fcntl(notify_sock[0], F_SETFL, O_NONBLOCK) < 0) {
rc = -errno;
warn_e(rc, "failed to set NONBLOCK on notify sock");
goto close_all;
}
/*
* Alloc stream which will be responsible for all server side
* resources from now on.
*/
rc = alloc_os(stream_size, mmap_size, pid, pgrp, uid, gid, cmd_sock[0],
notify_sock, se, &os);
if (rc) {
warn_e(rc, "failed to allocate stream for %d", pid);
goto close_all;
}
rc = -ENOMEM;
os->mixer = get_mixer(pgrp);
if (!os->mixer)
goto put_os;
/*
* Register notification. If successful, notify_poller has
* custody of notify_rx fd.
*/
pthread_mutex_lock(&mutex);
list_add(&os->notify_link, os_notify_tbl_head(os->notify_rx));
pthread_mutex_unlock(&mutex);
ev.events = EPOLLIN;
ev.data.fd = notify_sock[0];
if (epoll_ctl(notify_epfd, EPOLL_CTL_ADD, notify_sock[0], &ev)) {
/*
* Without poller watching this notify sock, poller
* shutdown sequence in shutdown_notification() can't
* be used. Kill notification rx manually.
*/
rc = -errno;
warn_ose(os, rc, "failed to add notify epoll");
close(os->notify_rx);
os->notify_rx = -1;
goto put_os;
}
/* start slave */
os->slave_pid = fork();
if (os->slave_pid < 0) {
rc = -errno;
warn_ose(os, rc, "failed to fork slave");
goto put_os;
}
if (os->slave_pid == 0) {
/* child */
char id_str[2][16], fd_str[3][16];
char mmap_size_str[32];
char log_str[16], slave_path_copy[PATH_MAX];
char *argv[] = { slave_path_copy, "-u", id_str[0],
"-g", id_str[1], "-c", fd_str[0],
"-n", fd_str[1], "-m", fd_str[2],
"-s", mmap_size_str,
"-l", log_str, NULL, NULL };
struct passwd *pwd;
/* drop stuff we don't need */
if (close(cmd_sock[0]) || close(notify_sock[0]))
fatal_e(-errno, "failed to close server pipe fds");
clearenv();
pwd = getpwuid(os->uid);
if (pwd) {
setenv("LOGNAME", pwd->pw_name, 1);
setenv("USER", pwd->pw_name, 1);
setenv("HOME", pwd->pw_dir, 1);
}
/* Set extra environment variables from the caller */
set_extra_env(pid);
/* prep and exec */
slave_path_copy[sizeof(slave_path_copy) - 1] = '\0';
strncpy(slave_path_copy, slave_path, sizeof(slave_path_copy) - 1);
if (slave_path_copy[sizeof(slave_path_copy) - 1] != '\0') {
rc = -errno;
err_ose(os, rc, "slave path too long");
goto child_fail;
}
snprintf(id_str[0], sizeof(id_str[0]), "%d", os->uid);
snprintf(id_str[1], sizeof(id_str[0]), "%d", os->gid);
snprintf(fd_str[0], sizeof(fd_str[0]), "%d", cmd_sock[1]);
snprintf(fd_str[1], sizeof(fd_str[1]), "%d", notify_sock[1]);
snprintf(fd_str[2], sizeof(fd_str[2]), "%d", os->mmap_fd);
snprintf(mmap_size_str, sizeof(mmap_size_str), "0x%zx",
os->mmap_size);
snprintf(log_str, sizeof(log_str), "%d", ossp_log_level);
if (ossp_log_timestamp)
argv[ARRAY_SIZE(argv) - 2] = "-t";
execv(slave_path, argv);
rc = -errno;
err_ose(os, rc, "execv failed for <%d>", pid);
child_fail:
_exit(1);
}
/* turn on CLOEXEC on all server side fds */
if (fcntl(os->cmd_fd, F_SETFD, FD_CLOEXEC) < 0 ||
fcntl(os->notify_tx, F_SETFD, FD_CLOEXEC) < 0 ||
fcntl(os->notify_rx, F_SETFD, FD_CLOEXEC) < 0) {
rc = -errno;
err_ose(os, rc, "failed to set CLOEXEC on server side fds");
goto put_os;
}
dbg0_os(os, "CREATE slave=%d %s", os->slave_pid, slave_path);
dbg0_os(os, " client=%d cmd=%d:%d notify=%d:%d mmap=%d:%zu",
pid, cmd_sock[0], cmd_sock[1], notify_sock[0], notify_sock[1],
os->mmap_fd, os->mmap_size);
close(os->mmap_fd);
os->mmap_fd = -1;
*osp = os;
rc = 0;
goto close_client_fds;
put_os:
put_os(os);
close_client_fds:
close(cmd_sock[1]);
pthread_mutex_unlock(&create_mutex);
return rc;
close_all:
for (i = 0; i < 2; i++) {
close(cmd_sock[i]);
close(notify_sock[i]);
}
pthread_mutex_unlock(&create_mutex);
return rc;
}
static void dsp_open_common(fuse_req_t req, struct fuse_file_info *fi,
struct fuse_session *se)
{
const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req);
struct ossp_dsp_open_arg arg = { };
struct ossp_stream *os = NULL;
struct ossp_mixer *mixer;
struct ossp_dsp_stream *dsps;
struct ossp_mixer_cmd mxcmd;
pid_t pgrp;
ssize_t ret;
ret = get_proc_self_info(fuse_ctx->pid, &pgrp, NULL, 0);
if (ret) {
err_e(ret, "get_proc_self_info(%d) failed", fuse_ctx->pid);
goto err;
}
ret = create_os(dsp_slave_path, sizeof(*dsps), DSPS_MMAP_SIZE,
fuse_ctx->pid, pgrp, fuse_ctx->uid, fuse_ctx->gid,
se, &os);
if (ret)
goto err;
dsps = os_to_dsps(os);
mixer = os->mixer;
switch (fi->flags & O_ACCMODE) {
case O_WRONLY:
dsps->rw |= 1 << PLAY;
break;
case O_RDONLY:
dsps->rw |= 1 << REC;
break;
case O_RDWR:
dsps->rw |= (1 << PLAY) | (1 << REC);
break;
default:
assert(0);
}
arg.flags = fi->flags;
arg.opener_pid = os->pid;
ret = exec_simple_cmd(&dsps->os, OSSP_DSP_OPEN, &arg, NULL);
if (ret < 0) {
put_os(os);
goto err;
}
memcpy(os->vol, mixer->vol, sizeof(os->vol));
if (os->vol[PLAY][0] >= 0 || os->vol[REC][0] >= 0) {
init_mixer_cmd(&mxcmd, mixer);
memcpy(mxcmd.set.vol, os->vol, sizeof(os->vol));
exec_mixer_cmd(&mxcmd, os);
finish_mixer_cmd(&mxcmd);
}
fi->direct_io = 1;
fi->nonseekable = 1;
fi->fh = os->id;
fuse_reply_open(req, fi);
return;
err:
fuse_reply_err(req, -ret);
}
static void dsp_open(fuse_req_t req, struct fuse_file_info *fi)
{
dsp_open_common(req, fi, dsp_se);
}
static void adsp_open(fuse_req_t req, struct fuse_file_info *fi)
{
dsp_open_common(req, fi, adsp_se);
}
static void dsp_release(fuse_req_t req, struct fuse_file_info *fi)
{
struct ossp_stream *os;
os = find_os(fi->fh);
if (os) {
put_os(os);
fuse_reply_err(req, 0);
} else
fuse_reply_err(req, EBADF);
}
static void dsp_read(fuse_req_t req, size_t size, off_t off,
struct fuse_file_info *fi)
{
struct ossp_dsp_rw_arg arg = { };
struct ossp_stream *os;
struct ossp_dsp_stream *dsps;
void *buf = NULL;
ssize_t ret;
ret = -EBADF;
os = find_os(fi->fh);
if (!os)
goto out;
dsps = os_to_dsps(os);
ret = -EINVAL;
if (!(dsps->rw & (1 << REC)))
goto out;
ret = -ENXIO;
if (dsps->mmapped)
goto out;
ret = -ENOMEM;
buf = malloc(size);
if (!buf)
goto out;
arg.nonblock = (fi->flags & O_NONBLOCK) || dsps->nonblock;
ret = exec_cmd(os, OSSP_DSP_READ, &arg, sizeof(arg),
NULL, 0, NULL, 0, buf, &size, -1);
out:
if (ret >= 0)
fuse_reply_buf(req, buf, size);
else
fuse_reply_err(req, -ret);
free(buf);
}
static void dsp_write(fuse_req_t req, const char *buf, size_t size, off_t off,
struct fuse_file_info *fi)
{
struct ossp_dsp_rw_arg arg = { };
struct ossp_stream *os;
struct ossp_dsp_stream *dsps;
ssize_t ret;
ret = -EBADF;
os = find_os(fi->fh);
if (!os)
goto out;
dsps = os_to_dsps(os);
ret = -EINVAL;
if (!(dsps->rw & (1 << PLAY)))
goto out;
ret = -ENXIO;
if (dsps->mmapped)
goto out;
arg.nonblock = (fi->flags & O_NONBLOCK) || dsps->nonblock;
ret = exec_cmd(os, OSSP_DSP_WRITE, &arg, sizeof(arg),
buf, size, NULL, 0, NULL, NULL, -1);
out:
if (ret >= 0)
fuse_reply_write(req, ret);
else
fuse_reply_err(req, -ret);
}
static void dsp_poll(fuse_req_t req, struct fuse_file_info *fi,
struct fuse_pollhandle *ph)
{
int notify = ph != NULL;
unsigned revents = 0;
struct ossp_stream *os;
ssize_t ret;
ret = -EBADF;
os = find_os(fi->fh);
if (!os)
goto out;
if (ph) {
pthread_mutex_lock(&mutex);
if (os->ph)
fuse_pollhandle_destroy(os->ph);
os->ph = ph;
pthread_mutex_unlock(&mutex);
}
ret = exec_simple_cmd(os, OSSP_DSP_POLL, &notify, &revents);
out:
if (ret >= 0)
fuse_reply_poll(req, revents);
else
fuse_reply_err(req, -ret);
}
static void dsp_ioctl(fuse_req_t req, int signed_cmd, void *uarg,
struct fuse_file_info *fi, unsigned int flags,
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
{
/* some ioctl constants are long and has the highest bit set */
unsigned cmd = signed_cmd;
struct ossp_stream *os;
struct ossp_dsp_stream *dsps;
enum ossp_opcode op;
ssize_t ret;
int i;
ret = -EBADF;
os = find_os(fi->fh);
if (!os)
goto err;
dsps = os_to_dsps(os);
/* mixer commands are allowed on DSP devices */
if (((cmd >> 8) & 0xff) == 'M') {
mixer_do_ioctl(req, os->mixer, cmd, uarg, in_buf, in_bufsz,
out_bufsz);
return;
}
/* and the rest */
switch (cmd) {
case OSS_GETVERSION:
i = SNDRV_OSS_VERSION;
PREP_UARG(NULL, &i);
fuse_reply_ioctl(req, 0, &i, sizeof(i));
break;
case SNDCTL_DSP_GETCAPS:
i = DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER |
#ifdef OSSP_MMAP
DSP_CAP_MMAP |
#endif
DSP_CAP_MULTI;
PREP_UARG(NULL, &i);
fuse_reply_ioctl(req, 0, &i, sizeof(i));
break;
case SNDCTL_DSP_NONBLOCK:
dsps->nonblock = 1;
ret = 0;
fuse_reply_ioctl(req, 0, NULL, 0);
break;
case SNDCTL_DSP_RESET: op = OSSP_DSP_RESET; goto nd;
case SNDCTL_DSP_SYNC: op = OSSP_DSP_SYNC; goto nd;
case SNDCTL_DSP_POST: op = OSSP_DSP_POST; goto nd;
nd:
ret = exec_simple_cmd(&dsps->os, op, NULL, NULL);
if (ret)
goto err;
fuse_reply_ioctl(req, 0, NULL, 0);
break;
case SOUND_PCM_READ_RATE: op = OSSP_DSP_GET_RATE; goto ri;
case SOUND_PCM_READ_BITS: op = OSSP_DSP_GET_FORMAT; goto ri;
case SOUND_PCM_READ_CHANNELS: op = OSSP_DSP_GET_CHANNELS; goto ri;
case SNDCTL_DSP_GETBLKSIZE: op = OSSP_DSP_GET_BLKSIZE; goto ri;
case SNDCTL_DSP_GETFMTS: op = OSSP_DSP_GET_FORMATS; goto ri;
case SNDCTL_DSP_GETTRIGGER: op = OSSP_DSP_GET_TRIGGER; goto ri;
ri:
PREP_UARG(NULL, &i);
ret = exec_simple_cmd(&dsps->os, op, NULL, &i);
if (ret)
goto err;
fuse_reply_ioctl(req, 0, &i, sizeof(i));
break;
case SNDCTL_DSP_SPEED: op = OSSP_DSP_SET_RATE; goto wi;
case SNDCTL_DSP_SETFMT: op = OSSP_DSP_SET_FORMAT; goto wi;
case SNDCTL_DSP_CHANNELS: op = OSSP_DSP_SET_CHANNELS; goto wi;
case SNDCTL_DSP_SUBDIVIDE: op = OSSP_DSP_SET_SUBDIVISION; goto wi;
wi:
PREP_UARG(&i, &i);
ret = exec_simple_cmd(&dsps->os, op, &i, &i);
if (ret)
goto err;
fuse_reply_ioctl(req, 0, &i, sizeof(i));
break;
case SNDCTL_DSP_STEREO:
PREP_UARG(NULL, &i);
i = 2;
ret = exec_simple_cmd(&dsps->os, OSSP_DSP_SET_CHANNELS, &i, &i);
i--;
if (ret)
goto err;
fuse_reply_ioctl(req, 0, &i, sizeof(i));
break;
case SNDCTL_DSP_SETFRAGMENT:
PREP_UARG(&i, NULL);
ret = exec_simple_cmd(&dsps->os,
OSSP_DSP_SET_FRAGMENT, &i, NULL);
if (ret)
goto err;
fuse_reply_ioctl(req, 0, NULL, 0);
break;
case SNDCTL_DSP_SETTRIGGER:
PREP_UARG(&i, NULL);
ret = exec_simple_cmd(&dsps->os,
OSSP_DSP_SET_TRIGGER, &i, NULL);
if (ret)
goto err;
fuse_reply_ioctl(req, 0, NULL, 0);
break;
case SNDCTL_DSP_GETOSPACE:
case SNDCTL_DSP_GETISPACE: {
struct audio_buf_info info;
ret = -EINVAL;
if (cmd == SNDCTL_DSP_GETOSPACE) {
if (!(dsps->rw & (1 << PLAY)))
goto err;
op = OSSP_DSP_GET_OSPACE;
} else {
if (!(dsps->rw & (1 << REC)))
goto err;
op = OSSP_DSP_GET_ISPACE;
}
PREP_UARG(NULL, &info);
ret = exec_simple_cmd(&dsps->os, op, NULL, &info);
if (ret)
goto err;
fuse_reply_ioctl(req, 0, &info, sizeof(info));
break;
}
case SNDCTL_DSP_GETOPTR:
case SNDCTL_DSP_GETIPTR: {
struct count_info info;
op = cmd == SNDCTL_DSP_GETOPTR ? OSSP_DSP_GET_OPTR
: OSSP_DSP_GET_IPTR;
PREP_UARG(NULL, &info);
ret = exec_simple_cmd(&dsps->os, op, NULL, &info);
if (ret)
goto err;
fuse_reply_ioctl(req, 0, &info, sizeof(info));
break;
}
case SNDCTL_DSP_GETODELAY:
PREP_UARG(NULL, &i);
i = 0;
ret = exec_simple_cmd(&dsps->os, OSSP_DSP_GET_ODELAY, NULL, &i);
fuse_reply_ioctl(req, ret, &i, sizeof(i)); /* always copy out result, 0 on err */
break;
case SOUND_PCM_WRITE_FILTER:
case SOUND_PCM_READ_FILTER:
ret = -EIO;
goto err;
case SNDCTL_DSP_MAPINBUF:
case SNDCTL_DSP_MAPOUTBUF:
ret = -EINVAL;
goto err;
case SNDCTL_DSP_SETSYNCRO:
case SNDCTL_DSP_SETDUPLEX:
case SNDCTL_DSP_PROFILE:
fuse_reply_ioctl(req, 0, NULL, 0);
break;
default:
warn_os(os, "unknown ioctl 0x%x", cmd);
ret = -EINVAL;
goto err;
}
return;
err:
fuse_reply_err(req, -ret);
}
#ifdef OSSP_MMAP
static int dsp_mmap_dir(int prot)
{
if (!(prot & PROT_WRITE))
return REC;
return PLAY;
}
static int dsp_dir_to_mapid(struct ossp_stream *os, int dir)
{
return os->id * 2 + dir + 1;
}
static int dsp_mapid_to_dir(int map_id)
{
return (map_id - 1) & 1;
}
static void dsp_mmap(fuse_req_t req, uint64_t addr, size_t len, int prot,
int flags, off_t offset, struct fuse_file_info *fi)
{
int dir = dsp_mmap_dir(prot);
struct ossp_dsp_mmap_arg arg = { };
struct ossp_stream *os;
struct ossp_dsp_stream *dsps;
ssize_t ret;
os = find_os(fi->fh);
if (!os) {
fuse_reply_err(req, EBADF);
return;
}
dsps = os_to_dsps(os);
if (len > os->mmap_size / 2) {
fuse_reply_err(req, EINVAL);
return;
}
pthread_mutex_lock(&os->mmap_mutex);
ret = -EBUSY;
if (dsps->mmapped & (1 << dir))
goto out_unlock;
arg.dir = dir;
arg.size = len;
ret = exec_simple_cmd(os, OSSP_DSP_MMAP, &arg, NULL);
if (ret == 0)
dsps->mmapped |= 1 << dir;
out_unlock:
pthread_mutex_unlock(&os->mmap_mutex);
if (ret == 0)
fuse_reply_mmap(req, dsp_dir_to_mapid(os, dir), os->mmap_size / 2);
else
fuse_reply_err(req, -ret);
}
static void dsp_munmap(fuse_req_t req, uint64_t map_id, size_t length,
struct fuse_file_info *fi)
{
struct ossp_stream *os;
struct ossp_dsp_stream *dsps;
int dir, rc;
os = find_os(fi->fh);
if (!os)
goto out;
dsps = os_to_dsps(os);
pthread_mutex_lock(&os->mmap_mutex);
dir = dsp_mapid_to_dir(map_id);
if (dsp_dir_to_mapid(os, dir) != map_id ||
(dir != PLAY && dir != REC) || length != os->mmap_size / 2) {
warn_os(os, "invalid munmap request map_id=%llu len=%zu mmapped=0x%x",
(unsigned long long) map_id, length, dsps->mmapped);
goto out_unlock;
}
rc = exec_simple_cmd(os, OSSP_DSP_MUNMAP, &dir, NULL);
if (rc)
warn_ose(os, rc, "MUNMAP failed for dir=%d", dir);
dsps->mmapped &= ~(1 << dir);
out_unlock:
pthread_mutex_unlock(&os->mmap_mutex);
out:
fuse_reply_err(req, -rc);
}
static void fill_mmap_buffer(struct ossp_stream *os)
{
int rc;
struct fuse_chan *ch = fuse_session_next_chan(os->se, NULL);
unsigned mapid = dsp_dir_to_mapid(os, PLAY);
size_t pos = os->mmap_transfer[PLAY].pos;
size_t bytes = os->mmap_transfer[PLAY].bytes;
dbg1_os(os, "fill mmap buffer pos=%zu bytes=%zu", pos, bytes);
rc = fuse_lowlevel_notify_retrieve(ch, mapid, bytes, pos, os);
if (rc)
sem_post(&os->mmap_transfer[PLAY].sem);
}
static void dsp_retrieve_reply(fuse_req_t req, void *cookie, uint64_t map_id,
off_t offset, struct fuse_bufvec *bufv)
{
struct ossp_stream *os = cookie;
size_t size = fuse_buf_size(bufv);
struct fuse_bufvec dest = FUSE_BUFVEC_INIT(size);
ssize_t res;
if (dsp_mapid_to_dir(map_id) != PLAY ||
dsp_dir_to_mapid(os, PLAY) != map_id ||
offset != os->mmap_transfer[PLAY].pos ||
size > os->mmap_transfer[PLAY].bytes) {
warn_os(os, "invalid retrieve reply map_id=%llu offset=%llu, size=%zu",
(unsigned long long) map_id,
(unsigned long long) offset, size);
sem_post(&os->mmap_transfer[PLAY].sem);
return;
}
dest.buf[0].mem = os->mmap_addr[PLAY] + offset;
res = fuse_buf_copy(&dest, bufv, 0);
if (res < 0)
warn_ose(os, res, "dsp_retrieve_reply: buffer copy");
else if ((size_t) res < size) {
warn_os(os, "dsp_retrieve_reply: short buffer copy");
}
if (size < os->mmap_transfer[PLAY].bytes) {
os->mmap_transfer[PLAY].bytes -= size;
os->mmap_transfer[PLAY].pos += size;
fill_mmap_buffer(os);
} else {
sem_post(&os->mmap_transfer[PLAY].sem);
}
}
static void store_mmap_buffer(struct ossp_stream *os)
{
int rc;
struct fuse_chan *ch = fuse_session_next_chan(os->se, NULL);
unsigned mapid = dsp_dir_to_mapid(os, REC);
size_t pos = os->mmap_transfer[REC].pos;
size_t bytes = os->mmap_transfer[REC].bytes;
struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(bytes);
dbg1_os(os, "store mmap buffer pos=%zu bytes=%zu", pos, bytes);
bufv.buf[0].mem = os->mmap_addr[REC] + pos;
rc = fuse_lowlevel_notify_store(ch, mapid, pos, &bufv, 0);
if (rc < 0)
warn_ose(os, rc, "store_mmap_buffer: failure");
sem_post(&os->mmap_transfer[REC].sem);
}
#endif
/***************************************************************************
* Notify poller
*/
static void *notify_poller(void *arg)
{
struct epoll_event events[1024];
int i, nfds;
repeat:
nfds = epoll_wait(notify_epfd, events, ARRAY_SIZE(events), -1);
for (i = 0; i < nfds; i++) {
int do_notify = 0;
struct ossp_stream *os;
struct ossp_notify notify;
ssize_t ret;
os = find_os_by_notify_rx(events[i].data.fd);
if (!os) {
err("can't find stream for notify_rx fd %d",
events[i].data.fd);
epoll_ctl(notify_epfd, EPOLL_CTL_DEL, events[i].data.fd,
NULL);
/* we don't know what's going on, don't close the fd */
continue;
}
while ((ret = read(os->notify_rx,
&notify, sizeof(notify))) > 0) {
if (os->dead)
continue;
if (ret != sizeof(notify)) {
warn_os(os, "short read on notify_rx (%zu, "
"expected %zu), killing the stream",
ret, sizeof(notify));
os->dead = 1;
break;
}
if (notify.magic != OSSP_NOTIFY_MAGIC) {
warn_os(os, "invalid magic on notification, "
"killing the stream");
os->dead = 1;
break;
}
if (notify.opcode >= OSSP_NR_NOTIFY_OPCODES)
goto unknown;
dbg1_os(os, "NOTIFY %s", ossp_notify_str[notify.opcode]);
switch (notify.opcode) {
case OSSP_NOTIFY_POLL:
do_notify = 1;
break;
case OSSP_NOTIFY_OBITUARY:
os->dead = 1;
break;
case OSSP_NOTIFY_VOLCHG:
pthread_mutex_lock(&mixer_mutex);
os->mixer->modify_counter++;
pthread_mutex_unlock(&mixer_mutex);
break;
#ifdef OSSP_MMAP
case OSSP_NOTIFY_FILL:
fill_mmap_buffer(os);
break;
case OSSP_NOTIFY_STORE:
store_mmap_buffer(os);
break;
#endif
default:
unknown:
warn_os(os, "unknown notification %d",
notify.opcode);
}
}
if (ret == 0)
os->dead = 1;
else if (ret < 0 && errno != EAGAIN) {
warn_ose(os, -errno, "read fail on notify fd");
os->dead = 1;
}
if (!do_notify && !os->dead)
continue;
pthread_mutex_lock(&mutex);
if (os->ph) {
fuse_lowlevel_notify_poll(os->ph);
fuse_pollhandle_destroy(os->ph);
os->ph = NULL;
}
if (os->dead) {
dbg0_os(os, "removing %d from notify poll list",
os->notify_rx);
epoll_ctl(notify_epfd, EPOLL_CTL_DEL, os->notify_rx,
NULL);
close(os->notify_rx);
os->notify_rx = -1;
pthread_cond_broadcast(&notify_poller_kill_wait);
}
pthread_mutex_unlock(&mutex);
}
goto repeat;
/* not reachable */
return NULL;
}
/***************************************************************************
* Slave corpse reaper
*/
static void *slave_reaper(void *arg)
{
struct ossp_stream *os;
int status;
pid_t pid;
pthread_mutex_lock(&mutex);
repeat:
while (list_empty(&slave_corpse_list))
pthread_cond_wait(&slave_reaper_wait, &mutex);
os = list_first_entry(&slave_corpse_list, struct ossp_stream, link);
list_del_init(&os->link);
pthread_mutex_unlock(&mutex);
do {
pid = waitpid(os->slave_pid, &status, 0);
} while (pid < 0 && errno == EINTR);
if (pid < 0) {
if (errno == ECHILD)
warn_ose(os, -errno, "slave %d already gone?",
os->slave_pid);
else
fatal_e(-errno, "waitpid(%d) failed", os->slave_pid);
}
pthread_mutex_lock(&mutex);
dbg1_os(os, "slave %d reaped", os->slave_pid);
__clear_bit(os->id, os_id_bitmap);
if (os->mmap_size) {
sem_destroy(&os->mmap_transfer[PLAY].sem);
sem_destroy(&os->mmap_transfer[REC].sem);
munmap(os->mmap, os->mmap_size + 2 * sizeof(struct ossp_transfer));
close(os->mmap_fd);
}
free(os);
goto repeat;
/* not reachable */
return NULL;
}
/***************************************************************************
* Stuff to bind and start everything
*/
static void ossp_daemonize(void)
{
int fd, pfd[2];
pid_t pid;
ssize_t ret;
int err;
fd = open("/dev/null", O_RDWR);
if (fd >= 0) {
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2)
close(fd);
}
if (pipe(pfd))
fatal_e(-errno, "failed to create pipe for init wait");
if (fcntl(pfd[0], F_SETFD, FD_CLOEXEC) < 0 ||
fcntl(pfd[1], F_SETFD, FD_CLOEXEC) < 0)
fatal_e(-errno, "failed to set CLOEXEC on init wait pipe");
pid = fork();
if (pid < 0)
fatal_e(-errno, "failed to fork for daemon");
if (pid == 0) {
close(pfd[0]);
init_wait_fd = pfd[1];
/* be evil, my child */
chdir("/");
setsid();
return;
}
/* wait for init completion and pass over success indication */
close(pfd[1]);
do {
ret = read(pfd[0], &err, sizeof(err));
} while (ret < 0 && errno == EINTR);
if (ret == sizeof(err) && err == 0)
exit(0);
fatal("daemon init failed ret=%zd err=%d", ret, err);
exit(1);
}
static void ossp_init_done(void *userdata)
{
/* init complete, notify parent if it's waiting */
if (init_wait_fd >= 0) {
ssize_t ret;
int err = 0;
ret = write(init_wait_fd, &err, sizeof(err));
if (ret != sizeof(err))
fatal_e(-errno, "failed to notify init completion, "
"ret=%zd", ret);
close(init_wait_fd);
init_wait_fd = -1;
}
}
static const struct cuse_lowlevel_ops mixer_ops = {
.open = mixer_open,
.release = mixer_release,
.ioctl = mixer_ioctl,
};
static const struct cuse_lowlevel_ops dsp_ops = {
.init_done = ossp_init_done,
.open = dsp_open,
.release = dsp_release,
.read = dsp_read,
.write = dsp_write,
.poll = dsp_poll,
.ioctl = dsp_ioctl,
#ifdef OSSP_MMAP
.mmap = dsp_mmap,
.munmap = dsp_munmap,
.retrieve_reply = dsp_retrieve_reply,
#endif
};
static const struct cuse_lowlevel_ops adsp_ops = {
.open = adsp_open,
.release = dsp_release,
.read = dsp_read,
.write = dsp_write,
.poll = dsp_poll,
.ioctl = dsp_ioctl,
#ifdef OSSP_MMAP
.mmap = dsp_mmap,
.munmap = dsp_munmap,
.retrieve_reply = dsp_retrieve_reply,
#endif
};
static const char *usage =
"usage: osspd [options]\n"
"\n"
"options:\n"
" --help print this help message\n"
" --dsp=NAME DSP device name (default dsp)\n"
" --dsp-maj=MAJ DSP device major number (default 14)\n"
" --dsp-min=MIN DSP device minor number (default 3)\n"
" --adsp=NAME Aux DSP device name (default adsp, blank to disable)\n"
" --adsp-maj=MAJ Aux DSP device major number (default 14)\n"
" --adsp-min=MIN Aux DSP device minor number (default 12)\n"
" --mixer=NAME mixer device name (default mixer, blank to disable)\n"
" --mixer-maj=MAJ mixer device major number (default 14)\n"
" --mixer-min=MIN mixer device minor number (default 0)\n"
" --max=MAX maximum number of open streams (default 256)\n"
" --umax=MAX maximum number of open streams per UID (default --max)\n"
" --exit-on-idle exit if idle\n"
" --dsp-slave=PATH DSP slave (default ossp-padsp in the same dir)\n"
" --log=LEVEL log level (0..6)\n"
" --timestamp timestamp log messages\n"
" -v increase verbosity, can be specified multiple times\n"
" -f Run in foreground (don't daemonize)\n"
"\n";
struct ossp_param {
char *dsp_name;
unsigned dsp_major;
unsigned dsp_minor;
char *adsp_name;
unsigned adsp_major;
unsigned adsp_minor;
char *mixer_name;
unsigned mixer_major;
unsigned mixer_minor;
unsigned max_streams;
unsigned umax_streams;
char *dsp_slave_path;
unsigned log_level;
int exit_on_idle;
int timestamp;
int fg;
int help;
};
#define OSSP_OPT(t, p) { t, offsetof(struct ossp_param, p), 1 }
static const struct fuse_opt ossp_opts[] = {
OSSP_OPT("--dsp=%s", dsp_name),
OSSP_OPT("--dsp-maj=%u", dsp_major),
OSSP_OPT("--dsp-min=%u", dsp_minor),
OSSP_OPT("--adsp=%s", adsp_name),
OSSP_OPT("--adsp-maj=%u", adsp_major),
OSSP_OPT("--adsp-min=%u", adsp_minor),
OSSP_OPT("--mixer=%s", mixer_name),
OSSP_OPT("--mixer-maj=%u", mixer_major),
OSSP_OPT("--mixer-min=%u", mixer_minor),
OSSP_OPT("--max=%u", max_streams),
OSSP_OPT("--umax=%u", umax_streams),
OSSP_OPT("--exit-on-idle", exit_on_idle),
OSSP_OPT("--dsp-slave=%s", dsp_slave_path),
OSSP_OPT("--timestamp", timestamp),
OSSP_OPT("--log=%u", log_level),
OSSP_OPT("-f", fg),
FUSE_OPT_KEY("-h", 0),
FUSE_OPT_KEY("--help", 0),
FUSE_OPT_KEY("-v", 1),
FUSE_OPT_END
};
static struct fuse_session *setup_ossp_cuse(const struct cuse_lowlevel_ops *ops,
const char *name, int major,
int minor, int argc, char **argv)
{
char name_buf[128];
const char *bufp = name_buf;
struct cuse_info ci = { .dev_major = major, .dev_minor = minor,
.dev_info_argc = 1, .dev_info_argv = &bufp,
.flags = CUSE_UNRESTRICTED_IOCTL };
struct fuse_session *se;
int fd;
snprintf(name_buf, sizeof(name_buf), "DEVNAME=%s", name);
se = cuse_lowlevel_setup(argc, argv, &ci, ops, NULL, NULL);
if (!se) {
err("failed to setup %s CUSE", name);
return NULL;
}
fd = fuse_chan_fd(fuse_session_next_chan(se, NULL));
if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
err_e(-errno, "failed to set CLOEXEC on %s CUSE fd", name);
cuse_lowlevel_teardown(se);
return NULL;
}
return se;
}
static void *cuse_worker(void *arg)
{
struct fuse_session *se = arg;
int rc;
rc = fuse_session_loop_mt(se);
cuse_lowlevel_teardown(se);
return (void *)(unsigned long)rc;
}
static int process_arg(void *data, const char *arg, int key,
struct fuse_args *outargs)
{
struct ossp_param *param = data;
switch (key) {
case 0:
fprintf(stderr, usage);
param->help = 1;
return 0;
case 1:
param->log_level++;
return 0;
}
return 1;
}
int main(int argc, char **argv)
{
static struct ossp_param param = {
.dsp_name = DFL_DSP_NAME,
.dsp_major = DFL_DSP_MAJOR, .dsp_minor = DFL_DSP_MINOR,
.adsp_name = DFL_ADSP_NAME,
.adsp_major = DFL_ADSP_MAJOR, .adsp_minor = DFL_ADSP_MINOR,
.mixer_name = DFL_MIXER_NAME,
.mixer_major = DFL_MIXER_MAJOR, .mixer_minor = DFL_MIXER_MINOR,
.max_streams = DFL_MAX_STREAMS,
};
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
char path_buf[PATH_MAX], *dir;
char adsp_buf[64] = "", mixer_buf[64] = "";
struct sigaction sa;
struct stat stat_buf;
ssize_t ret;
unsigned u;
snprintf(ossp_log_name, sizeof(ossp_log_name), "osspd");
param.log_level = ossp_log_level;
if (fuse_opt_parse(&args, &param, ossp_opts, process_arg))
fatal("failed to parse arguments");
if (param.help)
return 0;
max_streams = param.max_streams;
hashtbl_size = max_streams / 2 + 13;
umax_streams = max_streams;
if (param.umax_streams)
umax_streams = param.umax_streams;
if (param.log_level > OSSP_LOG_MAX)
param.log_level = OSSP_LOG_MAX;
if (!param.fg)
param.log_level = -param.log_level;
ossp_log_level = param.log_level;
ossp_log_timestamp = param.timestamp;
if (!param.fg)
ossp_daemonize();
/* daemonization already handled, prevent forking inside FUSE */
fuse_opt_add_arg(&args, "-f");
info("OSS Proxy v%s (C) 2008-2010 by Tejun Heo <teheo@suse.de>",
OSSP_VERSION);
/* ignore stupid SIGPIPEs */
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &sa, NULL))
fatal_e(-errno, "failed to ignore SIGPIPE");
/* determine slave path and check for availability */
ret = readlink("/proc/self/exe", path_buf, PATH_MAX - 1);
if (ret < 0)
fatal_e(-errno, "failed to determine executable path");
path_buf[ret] = '\0';
dir = dirname(path_buf);
if (param.dsp_slave_path) {
strncpy(dsp_slave_path, param.dsp_slave_path, PATH_MAX - 1);
dsp_slave_path[PATH_MAX - 1] = '\0';
} else {
ret = snprintf(dsp_slave_path, PATH_MAX, "%s/%s",
dir, "ossp-padsp");
if (ret >= PATH_MAX)
fatal("dsp slave pathname too long");
}
if (stat(dsp_slave_path, &stat_buf))
fatal_e(-errno, "failed to stat %s", dsp_slave_path);
if (!S_ISREG(stat_buf.st_mode) || !(stat_buf.st_mode & 0444))
fatal("%s is not executable", dsp_slave_path);
/* allocate tables */
os_id_bitmap = calloc(BITS_TO_LONGS(max_streams), sizeof(long));
mixer_tbl = calloc(hashtbl_size, sizeof(mixer_tbl[0]));
os_tbl = calloc(hashtbl_size, sizeof(os_tbl[0]));
os_pgrp_tbl = calloc(hashtbl_size, sizeof(os_pgrp_tbl[0]));
os_notify_tbl = calloc(hashtbl_size, sizeof(os_notify_tbl[0]));
if (!os_id_bitmap || !mixer_tbl || !os_tbl || !os_pgrp_tbl ||
!os_notify_tbl)
fatal("failed to allocate stream hash tables");
for (u = 0; u < hashtbl_size; u++) {
INIT_LIST_HEAD(&mixer_tbl[u]);
INIT_LIST_HEAD(&os_tbl[u]);
INIT_LIST_HEAD(&os_pgrp_tbl[u]);
INIT_LIST_HEAD(&os_notify_tbl[u]);
}
__set_bit(0, os_id_bitmap); /* don't use id 0 */
/* create mixer delayed reference worker */
ret = -pthread_create(&mixer_delayed_put_thread, NULL,
mixer_delayed_put_worker, NULL);
if (ret)
fatal_e(ret, "failed to create mixer delayed put worker");
/* if exit_on_idle, touch mixer for pgrp0 */
exit_on_idle = param.exit_on_idle;
if (exit_on_idle) {
struct ossp_mixer *mixer;
mixer = get_mixer(0);
if (!mixer)
fatal("failed to touch idle mixer");
put_mixer(mixer);
}
/* create notify epoll and kick off watcher thread */
notify_epfd = epoll_create(max_streams);
if (notify_epfd < 0)
fatal_e(-errno, "failed to create notify epoll");
if (fcntl(notify_epfd, F_SETFD, FD_CLOEXEC) < 0)
fatal_e(-errno, "failed to set CLOEXEC on notify epfd");
ret = -pthread_create(&notify_poller_thread, NULL, notify_poller, NULL);
if (ret)
fatal_e(ret, "failed to create notify poller thread");
/* create reaper for slave corpses */
ret = -pthread_create(&slave_reaper_thread, NULL, slave_reaper, NULL);
if (ret)
fatal_e(ret, "failed to create slave reaper thread");
/* we're set, let's setup fuse structures */
if (strlen(param.mixer_name))
mixer_se = setup_ossp_cuse(&mixer_ops, param.mixer_name,
param.mixer_major, param.mixer_minor,
args.argc, args.argv);
if (strlen(param.adsp_name))
adsp_se = setup_ossp_cuse(&adsp_ops, param.adsp_name,
param.adsp_major, param.adsp_minor,
args.argc, args.argv);
dsp_se = setup_ossp_cuse(&dsp_ops, param.dsp_name,
param.dsp_major, param.dsp_minor,
args.argc, args.argv);
if (!dsp_se)
fatal("can't create dsp, giving up");
if (mixer_se)
snprintf(mixer_buf, sizeof(mixer_buf), ", %s (%d:%d)",
param.mixer_name, param.mixer_major, param.mixer_minor);
if (adsp_se)
snprintf(adsp_buf, sizeof(adsp_buf), ", %s (%d:%d)",
param.adsp_name, param.adsp_major, param.adsp_minor);
info("Creating %s (%d:%d)%s%s", param.dsp_name, param.dsp_major,
param.dsp_minor, adsp_buf, mixer_buf);
/* start threads for mixer and adsp */
if (mixer_se) {
ret = -pthread_create(&cuse_mixer_thread, NULL,
cuse_worker, mixer_se);
if (ret)
err_e(ret, "failed to create mixer worker");
}
if (adsp_se) {
ret = -pthread_create(&cuse_adsp_thread, NULL,
cuse_worker, adsp_se);
if (ret)
err_e(ret, "failed to create adsp worker");
}
/* run CUSE for /dev/dsp in the main thread */
ret = (ssize_t)cuse_worker(dsp_se);
if (ret < 0)
fatal("dsp worker failed");
return 0;
}