#include <sys/types.h>
#include <sys/queue.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <cuse.h>
#include "int.h"
#include "virtual_oss.h"
int64_t voss_output_peak[VMAX_CHAN];
int64_t voss_input_peak[VMAX_CHAN];
static int
vctl_open(struct cuse_dev *pdev __unused, int fflags __unused)
{
return (0);
}
static int
vctl_close(struct cuse_dev *pdev __unused, int fflags __unused)
{
return (0);
}
static vprofile_t *
vprofile_by_index(const vprofile_head_t *phead, int index)
{
vprofile_t *pvp;
TAILQ_FOREACH(pvp, phead, entry) {
if (!index--)
return (pvp);
}
return (NULL);
}
static vmonitor_t *
vmonitor_by_index(int index, vmonitor_head_t *phead)
{
vmonitor_t *pvm;
TAILQ_FOREACH(pvm, phead, entry) {
if (!index--)
return (pvm);
}
return (NULL);
}
static int
vctl_ioctl(struct cuse_dev *pdev __unused, int fflags __unused,
unsigned long cmd, void *peer_data)
{
union {
int val;
struct virtual_oss_io_info io_info;
struct virtual_oss_mon_info mon_info;
struct virtual_oss_io_peak io_peak;
struct virtual_oss_mon_peak mon_peak;
struct virtual_oss_compressor out_lim;
struct virtual_oss_io_limit io_lim;
struct virtual_oss_master_peak master_peak;
struct virtual_oss_audio_delay_locator ad_locator;
struct virtual_oss_fir_filter fir_filter;
struct virtual_oss_system_info sys_info;
char options[VIRTUAL_OSS_OPTIONS_MAX];
} data;
vprofile_t *pvp;
vmonitor_t *pvm;
int chan;
int len;
int error;
len = IOCPARM_LEN(cmd);
if (len < 0 || len > (int)sizeof(data))
return (CUSE_ERR_INVALID);
if (cmd & IOC_IN) {
error = cuse_copy_in(peer_data, &data, len);
if (error)
return (error);
} else {
error = 0;
}
atomic_lock();
switch (cmd) {
case VIRTUAL_OSS_GET_DEV_INFO:
case VIRTUAL_OSS_SET_DEV_INFO:
case VIRTUAL_OSS_GET_DEV_PEAK:
case VIRTUAL_OSS_SET_DEV_LIMIT:
case VIRTUAL_OSS_GET_DEV_LIMIT:
case VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER:
case VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER:
case VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER:
case VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER:
pvp = vprofile_by_index(&virtual_profile_client_head, data.val);
break;
case VIRTUAL_OSS_GET_LOOP_INFO:
case VIRTUAL_OSS_SET_LOOP_INFO:
case VIRTUAL_OSS_GET_LOOP_PEAK:
case VIRTUAL_OSS_SET_LOOP_LIMIT:
case VIRTUAL_OSS_GET_LOOP_LIMIT:
case VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER:
case VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER:
case VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER:
case VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER:
pvp = vprofile_by_index(&virtual_profile_loopback_head, data.val);
break;
default:
pvp = NULL;
break;
}
switch (cmd) {
case VIRTUAL_OSS_GET_VERSION:
data.val = VIRTUAL_OSS_VERSION;
break;
case VIRTUAL_OSS_GET_DEV_INFO:
case VIRTUAL_OSS_GET_LOOP_INFO:
if (pvp == NULL ||
data.io_info.channel < 0 ||
data.io_info.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
break;
}
strlcpy(data.io_info.name, pvp->oss_name, sizeof(data.io_info.name));
chan = data.io_info.channel;
data.io_info.rx_amp = pvp->rx_shift[chan];
data.io_info.tx_amp = pvp->tx_shift[chan];
data.io_info.rx_chan = pvp->rx_src[chan];
data.io_info.tx_chan = pvp->tx_dst[chan];
data.io_info.rx_mute = pvp->rx_mute[chan] ? 1 : 0;
data.io_info.tx_mute = pvp->tx_mute[chan] ? 1 : 0;
data.io_info.rx_pol = pvp->rx_pol[chan] ? 1 : 0;
data.io_info.tx_pol = pvp->tx_pol[chan] ? 1 : 0;
data.io_info.bits = pvp->bits;
data.io_info.rx_delay = pvp->rec_delay;
data.io_info.rx_delay_limit = voss_dsp_sample_rate;
break;
case VIRTUAL_OSS_SET_DEV_INFO:
case VIRTUAL_OSS_SET_LOOP_INFO:
if (pvp == NULL ||
data.io_info.channel < 0 ||
data.io_info.channel >= (int)pvp->channels ||
data.io_info.rx_amp < -31 || data.io_info.rx_amp > 31 ||
data.io_info.tx_amp < -31 || data.io_info.tx_amp > 31 ||
data.io_info.rx_delay < 0 ||
data.io_info.rx_delay > (int)voss_dsp_sample_rate) {
error = CUSE_ERR_INVALID;
break;
}
chan = data.io_info.channel;
pvp->rx_shift[chan] = data.io_info.rx_amp;
pvp->tx_shift[chan] = data.io_info.tx_amp;
pvp->rx_src[chan] = data.io_info.rx_chan;
pvp->tx_dst[chan] = data.io_info.tx_chan;
pvp->rx_mute[chan] = data.io_info.rx_mute ? 1 : 0;
pvp->tx_mute[chan] = data.io_info.tx_mute ? 1 : 0;
pvp->rx_pol[chan] = data.io_info.rx_pol ? 1 : 0;
pvp->tx_pol[chan] = data.io_info.tx_pol ? 1 : 0;
pvp->rec_delay = data.io_info.rx_delay;
break;
case VIRTUAL_OSS_GET_INPUT_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_input);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_info.src_chan = pvm->src_chan;
data.mon_info.dst_chan = pvm->dst_chan;
data.mon_info.pol = pvm->pol;
data.mon_info.mute = pvm->mute;
data.mon_info.amp = pvm->shift;
data.mon_info.bits = voss_dsp_bits;
break;
case VIRTUAL_OSS_SET_INPUT_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_input);
if (pvm == NULL ||
data.mon_info.amp < -31 ||
data.mon_info.amp > 31) {
error = CUSE_ERR_INVALID;
break;
}
pvm->src_chan = data.mon_info.src_chan;
pvm->dst_chan = data.mon_info.dst_chan;
pvm->pol = data.mon_info.pol ? 1 : 0;
pvm->mute = data.mon_info.mute ? 1 : 0;
pvm->shift = data.mon_info.amp;
break;
case VIRTUAL_OSS_GET_OUTPUT_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_output);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_info.src_chan = pvm->src_chan;
data.mon_info.dst_chan = pvm->dst_chan;
data.mon_info.pol = pvm->pol;
data.mon_info.mute = pvm->mute;
data.mon_info.amp = pvm->shift;
data.mon_info.bits = voss_dsp_bits;
break;
case VIRTUAL_OSS_SET_OUTPUT_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_output);
if (pvm == NULL ||
data.mon_info.amp < -31 ||
data.mon_info.amp > 31) {
error = CUSE_ERR_INVALID;
break;
}
pvm->src_chan = data.mon_info.src_chan;
pvm->dst_chan = data.mon_info.dst_chan;
pvm->pol = data.mon_info.pol ? 1 : 0;
pvm->mute = data.mon_info.mute ? 1 : 0;
pvm->shift = data.mon_info.amp;
break;
case VIRTUAL_OSS_GET_LOCAL_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_local);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_info.src_chan = pvm->src_chan;
data.mon_info.dst_chan = pvm->dst_chan;
data.mon_info.pol = pvm->pol;
data.mon_info.mute = pvm->mute;
data.mon_info.amp = pvm->shift;
data.mon_info.bits = voss_dsp_bits;
break;
case VIRTUAL_OSS_SET_LOCAL_MON_INFO:
pvm = vmonitor_by_index(data.mon_info.number,
&virtual_monitor_local);
if (pvm == NULL ||
data.mon_info.amp < -31 ||
data.mon_info.amp > 31) {
error = CUSE_ERR_INVALID;
break;
}
pvm->src_chan = data.mon_info.src_chan;
pvm->dst_chan = data.mon_info.dst_chan;
pvm->pol = data.mon_info.pol ? 1 : 0;
pvm->mute = data.mon_info.mute ? 1 : 0;
pvm->shift = data.mon_info.amp;
break;
case VIRTUAL_OSS_GET_DEV_PEAK:
case VIRTUAL_OSS_GET_LOOP_PEAK:
if (pvp == NULL ||
data.io_peak.channel < 0 ||
data.io_peak.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
break;
}
strlcpy(data.io_peak.name, pvp->oss_name, sizeof(data.io_peak.name));
chan = data.io_peak.channel;
data.io_peak.rx_peak_value = pvp->rx_peak_value[chan];
pvp->rx_peak_value[chan] = 0;
data.io_peak.tx_peak_value = pvp->tx_peak_value[chan];
pvp->tx_peak_value[chan] = 0;
data.io_peak.bits = pvp->bits;
break;
case VIRTUAL_OSS_GET_INPUT_MON_PEAK:
pvm = vmonitor_by_index(data.mon_peak.number,
&virtual_monitor_input);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_peak.peak_value = pvm->peak_value;
data.mon_peak.bits = voss_dsp_bits;
pvm->peak_value = 0;
break;
case VIRTUAL_OSS_GET_OUTPUT_MON_PEAK:
pvm = vmonitor_by_index(data.mon_peak.number,
&virtual_monitor_output);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_peak.peak_value = pvm->peak_value;
data.mon_peak.bits = voss_dsp_bits;
pvm->peak_value = 0;
break;
case VIRTUAL_OSS_GET_LOCAL_MON_PEAK:
pvm = vmonitor_by_index(data.mon_peak.number,
&virtual_monitor_local);
if (pvm == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.mon_peak.peak_value = pvm->peak_value;
data.mon_peak.bits = voss_dsp_bits;
pvm->peak_value = 0;
break;
case VIRTUAL_OSS_ADD_INPUT_MON:
pvm = vmonitor_alloc(&data.val,
&virtual_monitor_input);
if (pvm == NULL)
error = CUSE_ERR_INVALID;
break;
case VIRTUAL_OSS_ADD_OUTPUT_MON:
pvm = vmonitor_alloc(&data.val,
&virtual_monitor_output);
if (pvm == NULL)
error = CUSE_ERR_INVALID;
break;
case VIRTUAL_OSS_ADD_LOCAL_MON:
pvm = vmonitor_alloc(&data.val,
&virtual_monitor_local);
if (pvm == NULL)
error = CUSE_ERR_INVALID;
break;
case VIRTUAL_OSS_SET_OUTPUT_LIMIT:
if (data.out_lim.enabled < 0 ||
data.out_lim.enabled > 1 ||
data.out_lim.knee < VIRTUAL_OSS_KNEE_MIN ||
data.out_lim.knee > VIRTUAL_OSS_KNEE_MAX ||
data.out_lim.attack < VIRTUAL_OSS_ATTACK_MIN ||
data.out_lim.attack > VIRTUAL_OSS_ATTACK_MAX ||
data.out_lim.decay < VIRTUAL_OSS_DECAY_MIN ||
data.out_lim.decay > VIRTUAL_OSS_DECAY_MAX ||
data.out_lim.gain != 0) {
error = CUSE_ERR_INVALID;
break;
}
voss_output_compressor_param.enabled = data.out_lim.enabled;
voss_output_compressor_param.knee = data.out_lim.knee;
voss_output_compressor_param.attack = data.out_lim.attack;
voss_output_compressor_param.decay = data.out_lim.decay;
break;
case VIRTUAL_OSS_GET_OUTPUT_LIMIT:
data.out_lim.enabled = voss_output_compressor_param.enabled;
data.out_lim.knee = voss_output_compressor_param.knee;
data.out_lim.attack = voss_output_compressor_param.attack;
data.out_lim.decay = voss_output_compressor_param.decay;
data.out_lim.gain = 1000;
for (chan = 0; chan != VMAX_CHAN; chan++) {
int gain = voss_output_compressor_gain[chan] * 1000.0;
if (data.out_lim.gain > gain)
data.out_lim.gain = gain;
}
break;
case VIRTUAL_OSS_SET_DEV_LIMIT:
case VIRTUAL_OSS_SET_LOOP_LIMIT:
if (pvp == NULL ||
data.io_lim.param.enabled < 0 ||
data.io_lim.param.enabled > 1 ||
data.io_lim.param.knee < VIRTUAL_OSS_KNEE_MIN ||
data.io_lim.param.knee > VIRTUAL_OSS_KNEE_MAX ||
data.io_lim.param.attack < VIRTUAL_OSS_ATTACK_MIN ||
data.io_lim.param.attack > VIRTUAL_OSS_ATTACK_MAX ||
data.io_lim.param.decay < VIRTUAL_OSS_DECAY_MIN ||
data.io_lim.param.decay > VIRTUAL_OSS_DECAY_MAX ||
data.io_lim.param.gain != 0) {
error = CUSE_ERR_INVALID;
break;
}
pvp->rx_compressor_param.enabled = data.io_lim.param.enabled;
pvp->rx_compressor_param.knee = data.io_lim.param.knee;
pvp->rx_compressor_param.attack = data.io_lim.param.attack;
pvp->rx_compressor_param.decay = data.io_lim.param.decay;
break;
case VIRTUAL_OSS_GET_DEV_LIMIT:
case VIRTUAL_OSS_GET_LOOP_LIMIT:
if (pvp == NULL) {
error = CUSE_ERR_INVALID;
break;
}
data.io_lim.param.enabled = pvp->rx_compressor_param.enabled;
data.io_lim.param.knee = pvp->rx_compressor_param.knee;
data.io_lim.param.attack = pvp->rx_compressor_param.attack;
data.io_lim.param.decay = pvp->rx_compressor_param.decay;
data.io_lim.param.gain = 1000;
for (chan = 0; chan != VMAX_CHAN; chan++) {
int gain = pvp->rx_compressor_gain[chan] * 1000.0;
if (data.io_lim.param.gain > gain)
data.io_lim.param.gain = gain;
}
break;
case VIRTUAL_OSS_GET_OUTPUT_PEAK:
chan = data.master_peak.channel;
if (chan < 0 ||
chan >= (int)voss_max_channels) {
error = CUSE_ERR_INVALID;
break;
}
data.master_peak.bits = voss_dsp_bits;
data.master_peak.peak_value = voss_output_peak[chan];
voss_output_peak[chan] = 0;
break;
case VIRTUAL_OSS_GET_INPUT_PEAK:
chan = data.master_peak.channel;
if (chan < 0 ||
chan >= (int)voss_dsp_max_channels) {
error = CUSE_ERR_INVALID;
break;
}
data.master_peak.bits = voss_dsp_bits;
data.master_peak.peak_value = voss_input_peak[chan];
voss_input_peak[chan] = 0;
break;
case VIRTUAL_OSS_SET_RECORDING:
voss_is_recording = data.val ? 1 : 0;
break;
case VIRTUAL_OSS_GET_RECORDING:
data.val = voss_is_recording;
break;
case VIRTUAL_OSS_SET_AUDIO_DELAY_LOCATOR:
if (data.ad_locator.channel_output < 0 ||
data.ad_locator.channel_output >= (int)voss_mix_channels) {
error = CUSE_ERR_INVALID;
break;
}
if (data.ad_locator.channel_input < 0 ||
data.ad_locator.channel_input >= (int)voss_mix_channels) {
error = CUSE_ERR_INVALID;
break;
}
if (data.ad_locator.signal_output_level < 0 ||
data.ad_locator.signal_output_level >= 64) {
error = CUSE_ERR_INVALID;
break;
}
voss_ad_enabled = (data.ad_locator.locator_enabled != 0);
voss_ad_output_signal = data.ad_locator.signal_output_level;
voss_ad_output_channel = data.ad_locator.channel_output;
voss_ad_input_channel = data.ad_locator.channel_input;
break;
case VIRTUAL_OSS_GET_AUDIO_DELAY_LOCATOR:
data.ad_locator.locator_enabled = voss_ad_enabled;
data.ad_locator.signal_output_level = voss_ad_output_signal;
data.ad_locator.channel_output = voss_ad_output_channel;
data.ad_locator.channel_input = voss_ad_input_channel;
data.ad_locator.channel_last = voss_mix_channels - 1;
data.ad_locator.signal_input_delay = voss_ad_last_delay;
data.ad_locator.signal_delay_hz = voss_dsp_sample_rate;
break;
case VIRTUAL_OSS_RST_AUDIO_DELAY_LOCATOR:
voss_ad_reset();
break;
case VIRTUAL_OSS_ADD_OPTIONS:
data.options[VIRTUAL_OSS_OPTIONS_MAX - 1] = 0;
voss_add_options(data.options);
break;
case VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER:
case VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER:
if (pvp == NULL ||
data.fir_filter.channel < 0 ||
data.fir_filter.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
} else if (data.fir_filter.filter_data == NULL) {
data.fir_filter.filter_size = pvp->rx_filter_size;
} else if (data.fir_filter.filter_size != (int)pvp->rx_filter_size) {
error = CUSE_ERR_INVALID;
} else if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) {
error = CUSE_ERR_NO_MEMORY;
} else {
error = cuse_copy_out(pvp->rx_filter_data[data.fir_filter.channel],
data.fir_filter.filter_data,
sizeof(pvp->rx_filter_data[0][0]) *
data.fir_filter.filter_size);
}
break;
case VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER:
case VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER:
if (pvp == NULL ||
data.fir_filter.channel < 0 ||
data.fir_filter.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
} else if (data.fir_filter.filter_data == NULL) {
data.fir_filter.filter_size = pvp->tx_filter_size;
} else if (data.fir_filter.filter_size != (int)pvp->tx_filter_size) {
error = CUSE_ERR_INVALID;
} else if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) {
error = CUSE_ERR_NO_MEMORY;
} else {
error = cuse_copy_out(pvp->tx_filter_data[data.fir_filter.channel],
data.fir_filter.filter_data,
sizeof(pvp->tx_filter_data[0][0]) *
data.fir_filter.filter_size);
}
break;
case VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER:
case VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER:
if (pvp == NULL ||
data.fir_filter.channel < 0 ||
data.fir_filter.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
} else if (data.fir_filter.filter_data == NULL) {
free(pvp->rx_filter_data[data.fir_filter.channel]);
pvp->rx_filter_data[data.fir_filter.channel] = NULL;
} else if (data.fir_filter.filter_size != (int)pvp->rx_filter_size) {
error = CUSE_ERR_INVALID;
} else if (pvp->rx_filter_size != 0) {
size_t size = sizeof(pvp->rx_filter_data[0][0]) * pvp->rx_filter_size;
if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) {
pvp->rx_filter_data[data.fir_filter.channel] = malloc(size);
if (pvp->rx_filter_data[data.fir_filter.channel] == NULL)
error = CUSE_ERR_NO_MEMORY;
else
memset(pvp->rx_filter_data[data.fir_filter.channel], 0, size);
}
if (pvp->rx_filter_data[data.fir_filter.channel] != NULL) {
error = cuse_copy_in(data.fir_filter.filter_data,
pvp->rx_filter_data[data.fir_filter.channel], size);
}
}
break;
case VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER:
case VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER:
if (pvp == NULL ||
data.fir_filter.channel < 0 ||
data.fir_filter.channel >= (int)pvp->channels) {
error = CUSE_ERR_INVALID;
} else if (data.fir_filter.filter_data == NULL) {
free(pvp->tx_filter_data[data.fir_filter.channel]);
pvp->tx_filter_data[data.fir_filter.channel] = NULL;
} else if (data.fir_filter.filter_size != (int)pvp->tx_filter_size) {
error = CUSE_ERR_INVALID;
} else if (pvp->tx_filter_size != 0) {
size_t size = sizeof(pvp->tx_filter_data[0][0]) * pvp->tx_filter_size;
if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) {
pvp->tx_filter_data[data.fir_filter.channel] = malloc(size);
if (pvp->tx_filter_data[data.fir_filter.channel] == NULL)
error = CUSE_ERR_NO_MEMORY;
else
memset(pvp->tx_filter_data[data.fir_filter.channel], 0, size);
}
if (pvp->tx_filter_data[data.fir_filter.channel] != NULL) {
error = cuse_copy_in(data.fir_filter.filter_data,
pvp->tx_filter_data[data.fir_filter.channel], size);
}
}
break;
case VIRTUAL_OSS_GET_SAMPLE_RATE:
data.val = voss_dsp_sample_rate;
break;
case VIRTUAL_OSS_GET_SYSTEM_INFO:
data.sys_info.tx_jitter_up = voss_jitter_up;
data.sys_info.tx_jitter_down = voss_jitter_down;
data.sys_info.sample_rate = voss_dsp_sample_rate;
data.sys_info.sample_bits = voss_dsp_bits;
data.sys_info.sample_channels = voss_mix_channels;
strlcpy(data.sys_info.rx_device_name, voss_dsp_rx_device,
sizeof(data.sys_info.rx_device_name));
strlcpy(data.sys_info.tx_device_name, voss_dsp_tx_device,
sizeof(data.sys_info.tx_device_name));
break;
default:
error = CUSE_ERR_INVALID;
break;
}
atomic_unlock();
if (error == 0) {
if (cmd & IOC_OUT)
error = cuse_copy_out(&data, peer_data, len);
}
return (error);
}
const struct cuse_methods vctl_methods = {
.cm_open = vctl_open,
.cm_close = vctl_close,
.cm_ioctl = vctl_ioctl,
};