root/usr.sbin/virtual_oss/virtual_oss/ctl.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2012-2022 Hans Petter Selasky
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#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;     /* filter disabled */
                } 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;     /* filter disabled */
                } 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;    /* disable filter */
                } 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;    /* disable filter */
                } 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,
};