xref: /src/usr.sbin/virtual_oss/virtual_oss/ctl.c (revision 6d5a428056b52c7ce47b01d6af8aaaff6feecfdd)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2012-2022 Hans Petter Selasky
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/types.h>
29 #include <sys/queue.h>
30 
31 #include <stdint.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #include <cuse.h>
36 
37 #include "int.h"
38 #include "virtual_oss.h"
39 
40 int64_t	voss_output_peak[VMAX_CHAN];
41 int64_t	voss_input_peak[VMAX_CHAN];
42 
43 static int
vctl_open(struct cuse_dev * pdev __unused,int fflags __unused)44 vctl_open(struct cuse_dev *pdev __unused, int fflags __unused)
45 {
46 	return (0);
47 }
48 
49 static int
vctl_close(struct cuse_dev * pdev __unused,int fflags __unused)50 vctl_close(struct cuse_dev *pdev __unused, int fflags __unused)
51 {
52 	return (0);
53 }
54 
55 static vprofile_t *
vprofile_by_index(const vprofile_head_t * phead,int index)56 vprofile_by_index(const vprofile_head_t *phead, int index)
57 {
58 	vprofile_t *pvp;
59 
60 	TAILQ_FOREACH(pvp, phead, entry) {
61 		if (!index--)
62 			return (pvp);
63 	}
64 	return (NULL);
65 }
66 
67 static vmonitor_t *
vmonitor_by_index(int index,vmonitor_head_t * phead)68 vmonitor_by_index(int index, vmonitor_head_t *phead)
69 {
70 	vmonitor_t *pvm;
71 
72 	TAILQ_FOREACH(pvm, phead, entry) {
73 		if (!index--)
74 			return (pvm);
75 	}
76 	return (NULL);
77 }
78 
79 static int
vctl_ioctl(struct cuse_dev * pdev __unused,int fflags __unused,unsigned long cmd,void * peer_data)80 vctl_ioctl(struct cuse_dev *pdev __unused, int fflags __unused,
81     unsigned long cmd, void *peer_data)
82 {
83 	union {
84 		int	val;
85 		struct virtual_oss_io_info io_info;
86 		struct virtual_oss_mon_info mon_info;
87 		struct virtual_oss_io_peak io_peak;
88 		struct virtual_oss_mon_peak mon_peak;
89 		struct virtual_oss_compressor out_lim;
90 		struct virtual_oss_io_limit io_lim;
91 		struct virtual_oss_master_peak master_peak;
92 		struct virtual_oss_audio_delay_locator ad_locator;
93 		struct virtual_oss_fir_filter fir_filter;
94 		struct virtual_oss_system_info sys_info;
95 		char	options[VIRTUAL_OSS_OPTIONS_MAX];
96 	}     data;
97 
98 	vprofile_t *pvp;
99 	vmonitor_t *pvm;
100 
101 	int chan;
102 	int len;
103 	int error;
104 
105 	len = IOCPARM_LEN(cmd);
106 
107 	if (len < 0 || len > (int)sizeof(data))
108 		return (CUSE_ERR_INVALID);
109 
110 	if (cmd & IOC_IN) {
111 		error = cuse_copy_in(peer_data, &data, len);
112 		if (error)
113 			return (error);
114 	} else {
115 		error = 0;
116 	}
117 
118 	atomic_lock();
119 	switch (cmd) {
120 	case VIRTUAL_OSS_GET_DEV_INFO:
121 	case VIRTUAL_OSS_SET_DEV_INFO:
122 	case VIRTUAL_OSS_GET_DEV_PEAK:
123 	case VIRTUAL_OSS_SET_DEV_LIMIT:
124 	case VIRTUAL_OSS_GET_DEV_LIMIT:
125 	case VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER:
126 	case VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER:
127 	case VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER:
128 	case VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER:
129 		pvp = vprofile_by_index(&virtual_profile_client_head, data.val);
130 		break;
131 	case VIRTUAL_OSS_GET_LOOP_INFO:
132 	case VIRTUAL_OSS_SET_LOOP_INFO:
133 	case VIRTUAL_OSS_GET_LOOP_PEAK:
134 	case VIRTUAL_OSS_SET_LOOP_LIMIT:
135 	case VIRTUAL_OSS_GET_LOOP_LIMIT:
136 	case VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER:
137 	case VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER:
138 	case VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER:
139 	case VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER:
140 		pvp = vprofile_by_index(&virtual_profile_loopback_head, data.val);
141 		break;
142 	default:
143 		pvp = NULL;
144 		break;
145 	}
146 
147 	switch (cmd) {
148 	case VIRTUAL_OSS_GET_VERSION:
149 		data.val = VIRTUAL_OSS_VERSION;
150 		break;
151 	case VIRTUAL_OSS_GET_DEV_INFO:
152 	case VIRTUAL_OSS_GET_LOOP_INFO:
153 		if (pvp == NULL ||
154 		    data.io_info.channel < 0 ||
155 		    data.io_info.channel >= (int)pvp->channels) {
156 			error = CUSE_ERR_INVALID;
157 			break;
158 		}
159 		strlcpy(data.io_info.name, pvp->oss_name, sizeof(data.io_info.name));
160 		chan = data.io_info.channel;
161 		data.io_info.rx_amp = pvp->rx_shift[chan];
162 		data.io_info.tx_amp = pvp->tx_shift[chan];
163 		data.io_info.rx_chan = pvp->rx_src[chan];
164 		data.io_info.tx_chan = pvp->tx_dst[chan];
165 		data.io_info.rx_mute = pvp->rx_mute[chan] ? 1 : 0;
166 		data.io_info.tx_mute = pvp->tx_mute[chan] ? 1 : 0;
167 		data.io_info.rx_pol = pvp->rx_pol[chan] ? 1 : 0;
168 		data.io_info.tx_pol = pvp->tx_pol[chan] ? 1 : 0;
169 		data.io_info.bits = pvp->bits;
170 		data.io_info.rx_delay = pvp->rec_delay;
171 		data.io_info.rx_delay_limit = voss_dsp_sample_rate;
172 		break;
173 	case VIRTUAL_OSS_SET_DEV_INFO:
174 	case VIRTUAL_OSS_SET_LOOP_INFO:
175 		if (pvp == NULL ||
176 		    data.io_info.channel < 0 ||
177 		    data.io_info.channel >= (int)pvp->channels ||
178 		    data.io_info.rx_amp < -31 || data.io_info.rx_amp > 31 ||
179 		    data.io_info.tx_amp < -31 || data.io_info.tx_amp > 31 ||
180 		    data.io_info.rx_delay < 0 ||
181 		    data.io_info.rx_delay > (int)voss_dsp_sample_rate) {
182 			error = CUSE_ERR_INVALID;
183 			break;
184 		}
185 		chan = data.io_info.channel;
186 		pvp->rx_shift[chan] = data.io_info.rx_amp;
187 		pvp->tx_shift[chan] = data.io_info.tx_amp;
188 		pvp->rx_src[chan] = data.io_info.rx_chan;
189 		pvp->tx_dst[chan] = data.io_info.tx_chan;
190 		pvp->rx_mute[chan] = data.io_info.rx_mute ? 1 : 0;
191 		pvp->tx_mute[chan] = data.io_info.tx_mute ? 1 : 0;
192 		pvp->rx_pol[chan] = data.io_info.rx_pol ? 1 : 0;
193 		pvp->tx_pol[chan] = data.io_info.tx_pol ? 1 : 0;
194 		pvp->rec_delay = data.io_info.rx_delay;
195 		break;
196 	case VIRTUAL_OSS_GET_INPUT_MON_INFO:
197 		pvm = vmonitor_by_index(data.mon_info.number,
198 		    &virtual_monitor_input);
199 		if (pvm == NULL) {
200 			error = CUSE_ERR_INVALID;
201 			break;
202 		}
203 		data.mon_info.src_chan = pvm->src_chan;
204 		data.mon_info.dst_chan = pvm->dst_chan;
205 		data.mon_info.pol = pvm->pol;
206 		data.mon_info.mute = pvm->mute;
207 		data.mon_info.amp = pvm->shift;
208 		data.mon_info.bits = voss_dsp_bits;
209 		break;
210 	case VIRTUAL_OSS_SET_INPUT_MON_INFO:
211 		pvm = vmonitor_by_index(data.mon_info.number,
212 		    &virtual_monitor_input);
213 		if (pvm == NULL ||
214 		    data.mon_info.amp < -31 ||
215 		    data.mon_info.amp > 31) {
216 			error = CUSE_ERR_INVALID;
217 			break;
218 		}
219 		pvm->src_chan = data.mon_info.src_chan;
220 		pvm->dst_chan = data.mon_info.dst_chan;
221 		pvm->pol = data.mon_info.pol ? 1 : 0;
222 		pvm->mute = data.mon_info.mute ? 1 : 0;
223 		pvm->shift = data.mon_info.amp;
224 		break;
225 	case VIRTUAL_OSS_GET_OUTPUT_MON_INFO:
226 		pvm = vmonitor_by_index(data.mon_info.number,
227 		    &virtual_monitor_output);
228 		if (pvm == NULL) {
229 			error = CUSE_ERR_INVALID;
230 			break;
231 		}
232 		data.mon_info.src_chan = pvm->src_chan;
233 		data.mon_info.dst_chan = pvm->dst_chan;
234 		data.mon_info.pol = pvm->pol;
235 		data.mon_info.mute = pvm->mute;
236 		data.mon_info.amp = pvm->shift;
237 		data.mon_info.bits = voss_dsp_bits;
238 		break;
239 	case VIRTUAL_OSS_SET_OUTPUT_MON_INFO:
240 		pvm = vmonitor_by_index(data.mon_info.number,
241 		    &virtual_monitor_output);
242 		if (pvm == NULL ||
243 		    data.mon_info.amp < -31 ||
244 		    data.mon_info.amp > 31) {
245 			error = CUSE_ERR_INVALID;
246 			break;
247 		}
248 		pvm->src_chan = data.mon_info.src_chan;
249 		pvm->dst_chan = data.mon_info.dst_chan;
250 		pvm->pol = data.mon_info.pol ? 1 : 0;
251 		pvm->mute = data.mon_info.mute ? 1 : 0;
252 		pvm->shift = data.mon_info.amp;
253 		break;
254 	case VIRTUAL_OSS_GET_LOCAL_MON_INFO:
255 		pvm = vmonitor_by_index(data.mon_info.number,
256 		    &virtual_monitor_local);
257 		if (pvm == NULL) {
258 			error = CUSE_ERR_INVALID;
259 			break;
260 		}
261 		data.mon_info.src_chan = pvm->src_chan;
262 		data.mon_info.dst_chan = pvm->dst_chan;
263 		data.mon_info.pol = pvm->pol;
264 		data.mon_info.mute = pvm->mute;
265 		data.mon_info.amp = pvm->shift;
266 		data.mon_info.bits = voss_dsp_bits;
267 		break;
268 	case VIRTUAL_OSS_SET_LOCAL_MON_INFO:
269 		pvm = vmonitor_by_index(data.mon_info.number,
270 		    &virtual_monitor_local);
271 		if (pvm == NULL ||
272 		    data.mon_info.amp < -31 ||
273 		    data.mon_info.amp > 31) {
274 			error = CUSE_ERR_INVALID;
275 			break;
276 		}
277 		pvm->src_chan = data.mon_info.src_chan;
278 		pvm->dst_chan = data.mon_info.dst_chan;
279 		pvm->pol = data.mon_info.pol ? 1 : 0;
280 		pvm->mute = data.mon_info.mute ? 1 : 0;
281 		pvm->shift = data.mon_info.amp;
282 		break;
283 	case VIRTUAL_OSS_GET_DEV_PEAK:
284 	case VIRTUAL_OSS_GET_LOOP_PEAK:
285 		if (pvp == NULL ||
286 		    data.io_peak.channel < 0 ||
287 		    data.io_peak.channel >= (int)pvp->channels) {
288 			error = CUSE_ERR_INVALID;
289 			break;
290 		}
291 		strlcpy(data.io_peak.name, pvp->oss_name, sizeof(data.io_peak.name));
292 		chan = data.io_peak.channel;
293 		data.io_peak.rx_peak_value = pvp->rx_peak_value[chan];
294 		pvp->rx_peak_value[chan] = 0;
295 		data.io_peak.tx_peak_value = pvp->tx_peak_value[chan];
296 		pvp->tx_peak_value[chan] = 0;
297 		data.io_peak.bits = pvp->bits;
298 		break;
299 	case VIRTUAL_OSS_GET_INPUT_MON_PEAK:
300 		pvm = vmonitor_by_index(data.mon_peak.number,
301 		    &virtual_monitor_input);
302 		if (pvm == NULL) {
303 			error = CUSE_ERR_INVALID;
304 			break;
305 		}
306 		data.mon_peak.peak_value = pvm->peak_value;
307 		data.mon_peak.bits = voss_dsp_bits;
308 		pvm->peak_value = 0;
309 		break;
310 	case VIRTUAL_OSS_GET_OUTPUT_MON_PEAK:
311 		pvm = vmonitor_by_index(data.mon_peak.number,
312 		    &virtual_monitor_output);
313 		if (pvm == NULL) {
314 			error = CUSE_ERR_INVALID;
315 			break;
316 		}
317 		data.mon_peak.peak_value = pvm->peak_value;
318 		data.mon_peak.bits = voss_dsp_bits;
319 		pvm->peak_value = 0;
320 		break;
321 	case VIRTUAL_OSS_GET_LOCAL_MON_PEAK:
322 		pvm = vmonitor_by_index(data.mon_peak.number,
323 		    &virtual_monitor_local);
324 		if (pvm == NULL) {
325 			error = CUSE_ERR_INVALID;
326 			break;
327 		}
328 		data.mon_peak.peak_value = pvm->peak_value;
329 		data.mon_peak.bits = voss_dsp_bits;
330 		pvm->peak_value = 0;
331 		break;
332 	case VIRTUAL_OSS_ADD_INPUT_MON:
333 		pvm = vmonitor_alloc(&data.val,
334 		    &virtual_monitor_input);
335 		if (pvm == NULL)
336 			error = CUSE_ERR_INVALID;
337 		break;
338 	case VIRTUAL_OSS_ADD_OUTPUT_MON:
339 		pvm = vmonitor_alloc(&data.val,
340 		    &virtual_monitor_output);
341 		if (pvm == NULL)
342 			error = CUSE_ERR_INVALID;
343 		break;
344 	case VIRTUAL_OSS_ADD_LOCAL_MON:
345 		pvm = vmonitor_alloc(&data.val,
346 		    &virtual_monitor_local);
347 		if (pvm == NULL)
348 			error = CUSE_ERR_INVALID;
349 		break;
350 	case VIRTUAL_OSS_SET_OUTPUT_LIMIT:
351 		if (data.out_lim.enabled < 0 ||
352 		    data.out_lim.enabled > 1 ||
353 		    data.out_lim.knee < VIRTUAL_OSS_KNEE_MIN ||
354 		    data.out_lim.knee > VIRTUAL_OSS_KNEE_MAX ||
355 		    data.out_lim.attack < VIRTUAL_OSS_ATTACK_MIN ||
356 		    data.out_lim.attack > VIRTUAL_OSS_ATTACK_MAX ||
357 		    data.out_lim.decay < VIRTUAL_OSS_DECAY_MIN ||
358 		    data.out_lim.decay > VIRTUAL_OSS_DECAY_MAX ||
359 		    data.out_lim.gain != 0) {
360 			error = CUSE_ERR_INVALID;
361 			break;
362 		}
363 		voss_output_compressor_param.enabled = data.out_lim.enabled;
364 		voss_output_compressor_param.knee = data.out_lim.knee;
365 		voss_output_compressor_param.attack = data.out_lim.attack;
366 		voss_output_compressor_param.decay = data.out_lim.decay;
367 		break;
368 	case VIRTUAL_OSS_GET_OUTPUT_LIMIT:
369 		data.out_lim.enabled = voss_output_compressor_param.enabled;
370 		data.out_lim.knee = voss_output_compressor_param.knee;
371 		data.out_lim.attack = voss_output_compressor_param.attack;
372 		data.out_lim.decay = voss_output_compressor_param.decay;
373 		data.out_lim.gain = 1000;
374 		for (chan = 0; chan != VMAX_CHAN; chan++) {
375 			int gain = voss_output_compressor_gain[chan] * 1000.0;
376 			if (data.out_lim.gain > gain)
377 				data.out_lim.gain = gain;
378 		}
379 		break;
380 	case VIRTUAL_OSS_SET_DEV_LIMIT:
381 	case VIRTUAL_OSS_SET_LOOP_LIMIT:
382 		if (pvp == NULL ||
383 		    data.io_lim.param.enabled < 0 ||
384 		    data.io_lim.param.enabled > 1 ||
385 		    data.io_lim.param.knee < VIRTUAL_OSS_KNEE_MIN ||
386 		    data.io_lim.param.knee > VIRTUAL_OSS_KNEE_MAX ||
387 		    data.io_lim.param.attack < VIRTUAL_OSS_ATTACK_MIN ||
388 		    data.io_lim.param.attack > VIRTUAL_OSS_ATTACK_MAX ||
389 		    data.io_lim.param.decay < VIRTUAL_OSS_DECAY_MIN ||
390 		    data.io_lim.param.decay > VIRTUAL_OSS_DECAY_MAX ||
391 		    data.io_lim.param.gain != 0) {
392 			error = CUSE_ERR_INVALID;
393 			break;
394 		}
395 		pvp->rx_compressor_param.enabled = data.io_lim.param.enabled;
396 		pvp->rx_compressor_param.knee = data.io_lim.param.knee;
397 		pvp->rx_compressor_param.attack = data.io_lim.param.attack;
398 		pvp->rx_compressor_param.decay = data.io_lim.param.decay;
399 		break;
400 	case VIRTUAL_OSS_GET_DEV_LIMIT:
401 	case VIRTUAL_OSS_GET_LOOP_LIMIT:
402 		if (pvp == NULL) {
403 			error = CUSE_ERR_INVALID;
404 			break;
405 		}
406 		data.io_lim.param.enabled = pvp->rx_compressor_param.enabled;
407 		data.io_lim.param.knee = pvp->rx_compressor_param.knee;
408 		data.io_lim.param.attack = pvp->rx_compressor_param.attack;
409 		data.io_lim.param.decay = pvp->rx_compressor_param.decay;
410 		data.io_lim.param.gain = 1000;
411 
412 		for (chan = 0; chan != VMAX_CHAN; chan++) {
413 			int gain = pvp->rx_compressor_gain[chan] * 1000.0;
414 			if (data.io_lim.param.gain > gain)
415 				data.io_lim.param.gain = gain;
416 		}
417 		break;
418 	case VIRTUAL_OSS_GET_OUTPUT_PEAK:
419 		chan = data.master_peak.channel;
420 		if (chan < 0 ||
421 		    chan >= (int)voss_max_channels) {
422 			error = CUSE_ERR_INVALID;
423 			break;
424 		}
425 		data.master_peak.bits = voss_dsp_bits;
426 		data.master_peak.peak_value = voss_output_peak[chan];
427 		voss_output_peak[chan] = 0;
428 		break;
429 	case VIRTUAL_OSS_GET_INPUT_PEAK:
430 		chan = data.master_peak.channel;
431 		if (chan < 0 ||
432 		    chan >= (int)voss_dsp_max_channels) {
433 			error = CUSE_ERR_INVALID;
434 			break;
435 		}
436 		data.master_peak.bits = voss_dsp_bits;
437 		data.master_peak.peak_value = voss_input_peak[chan];
438 		voss_input_peak[chan] = 0;
439 		break;
440 
441 	case VIRTUAL_OSS_SET_RECORDING:
442 		voss_is_recording = data.val ? 1 : 0;
443 		break;
444 
445 	case VIRTUAL_OSS_GET_RECORDING:
446 		data.val = voss_is_recording;
447 		break;
448 
449 	case VIRTUAL_OSS_SET_AUDIO_DELAY_LOCATOR:
450 		if (data.ad_locator.channel_output < 0 ||
451 		    data.ad_locator.channel_output >= (int)voss_mix_channels) {
452 			error = CUSE_ERR_INVALID;
453 			break;
454 		}
455 		if (data.ad_locator.channel_input < 0 ||
456 		    data.ad_locator.channel_input >= (int)voss_mix_channels) {
457 			error = CUSE_ERR_INVALID;
458 			break;
459 		}
460 		if (data.ad_locator.signal_output_level < 0 ||
461 		    data.ad_locator.signal_output_level >= 64) {
462 			error = CUSE_ERR_INVALID;
463 			break;
464 		}
465 		voss_ad_enabled = (data.ad_locator.locator_enabled != 0);
466 		voss_ad_output_signal = data.ad_locator.signal_output_level;
467 		voss_ad_output_channel = data.ad_locator.channel_output;
468 		voss_ad_input_channel = data.ad_locator.channel_input;
469 		break;
470 
471 	case VIRTUAL_OSS_GET_AUDIO_DELAY_LOCATOR:
472 		data.ad_locator.locator_enabled = voss_ad_enabled;
473 		data.ad_locator.signal_output_level = voss_ad_output_signal;
474 		data.ad_locator.channel_output = voss_ad_output_channel;
475 		data.ad_locator.channel_input = voss_ad_input_channel;
476 		data.ad_locator.channel_last = voss_mix_channels - 1;
477 		data.ad_locator.signal_input_delay = voss_ad_last_delay;
478 		data.ad_locator.signal_delay_hz = voss_dsp_sample_rate;
479 		break;
480 
481 	case VIRTUAL_OSS_RST_AUDIO_DELAY_LOCATOR:
482 		voss_ad_reset();
483 		break;
484 
485 	case VIRTUAL_OSS_ADD_OPTIONS:
486 		data.options[VIRTUAL_OSS_OPTIONS_MAX - 1] = 0;
487 		voss_add_options(data.options);
488 		break;
489 
490 	case VIRTUAL_OSS_GET_RX_DEV_FIR_FILTER:
491 	case VIRTUAL_OSS_GET_RX_LOOP_FIR_FILTER:
492 		if (pvp == NULL ||
493 		    data.fir_filter.channel < 0 ||
494 		    data.fir_filter.channel >= (int)pvp->channels) {
495 			error = CUSE_ERR_INVALID;
496 		} else if (data.fir_filter.filter_data == NULL) {
497 			data.fir_filter.filter_size = pvp->rx_filter_size;
498 		} else if (data.fir_filter.filter_size != (int)pvp->rx_filter_size) {
499 			error = CUSE_ERR_INVALID;
500 		} else if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) {
501 			error = CUSE_ERR_NO_MEMORY;	/* filter disabled */
502 		} else {
503 			error = cuse_copy_out(pvp->rx_filter_data[data.fir_filter.channel],
504 			    data.fir_filter.filter_data,
505 			    sizeof(pvp->rx_filter_data[0][0]) *
506 			    data.fir_filter.filter_size);
507 		}
508 		break;
509 
510 	case VIRTUAL_OSS_GET_TX_DEV_FIR_FILTER:
511 	case VIRTUAL_OSS_GET_TX_LOOP_FIR_FILTER:
512 		if (pvp == NULL ||
513 		    data.fir_filter.channel < 0 ||
514 		    data.fir_filter.channel >= (int)pvp->channels) {
515 			error = CUSE_ERR_INVALID;
516 		} else if (data.fir_filter.filter_data == NULL) {
517 			data.fir_filter.filter_size = pvp->tx_filter_size;
518 		} else if (data.fir_filter.filter_size != (int)pvp->tx_filter_size) {
519 			error = CUSE_ERR_INVALID;
520 		} else if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) {
521 			error = CUSE_ERR_NO_MEMORY;	/* filter disabled */
522 		} else {
523 			error = cuse_copy_out(pvp->tx_filter_data[data.fir_filter.channel],
524 			    data.fir_filter.filter_data,
525 			    sizeof(pvp->tx_filter_data[0][0]) *
526 			    data.fir_filter.filter_size);
527 		}
528 		break;
529 
530 	case VIRTUAL_OSS_SET_RX_DEV_FIR_FILTER:
531 	case VIRTUAL_OSS_SET_RX_LOOP_FIR_FILTER:
532 		if (pvp == NULL ||
533 		    data.fir_filter.channel < 0 ||
534 		    data.fir_filter.channel >= (int)pvp->channels) {
535 			error = CUSE_ERR_INVALID;
536 		} else if (data.fir_filter.filter_data == NULL) {
537 			free(pvp->rx_filter_data[data.fir_filter.channel]);
538 			pvp->rx_filter_data[data.fir_filter.channel] = NULL;	/* disable filter */
539 		} else if (data.fir_filter.filter_size != (int)pvp->rx_filter_size) {
540 			error = CUSE_ERR_INVALID;
541 		} else if (pvp->rx_filter_size != 0) {
542 			size_t size = sizeof(pvp->rx_filter_data[0][0]) * pvp->rx_filter_size;
543 			if (pvp->rx_filter_data[data.fir_filter.channel] == NULL) {
544 				pvp->rx_filter_data[data.fir_filter.channel] = malloc(size);
545 				if (pvp->rx_filter_data[data.fir_filter.channel] == NULL)
546 					error = CUSE_ERR_NO_MEMORY;
547 				else
548 					memset(pvp->rx_filter_data[data.fir_filter.channel], 0, size);
549 			}
550 			if (pvp->rx_filter_data[data.fir_filter.channel] != NULL) {
551 				error = cuse_copy_in(data.fir_filter.filter_data,
552 				    pvp->rx_filter_data[data.fir_filter.channel], size);
553 			}
554 		}
555 		break;
556 
557 	case VIRTUAL_OSS_SET_TX_DEV_FIR_FILTER:
558 	case VIRTUAL_OSS_SET_TX_LOOP_FIR_FILTER:
559 		if (pvp == NULL ||
560 		    data.fir_filter.channel < 0 ||
561 		    data.fir_filter.channel >= (int)pvp->channels) {
562 			error = CUSE_ERR_INVALID;
563 		} else if (data.fir_filter.filter_data == NULL) {
564 			free(pvp->tx_filter_data[data.fir_filter.channel]);
565 			pvp->tx_filter_data[data.fir_filter.channel] = NULL;	/* disable filter */
566 		} else if (data.fir_filter.filter_size != (int)pvp->tx_filter_size) {
567 			error = CUSE_ERR_INVALID;
568 		} else if (pvp->tx_filter_size != 0) {
569 			size_t size = sizeof(pvp->tx_filter_data[0][0]) * pvp->tx_filter_size;
570 			if (pvp->tx_filter_data[data.fir_filter.channel] == NULL) {
571 				pvp->tx_filter_data[data.fir_filter.channel] = malloc(size);
572 				if (pvp->tx_filter_data[data.fir_filter.channel] == NULL)
573 					error = CUSE_ERR_NO_MEMORY;
574 				else
575 					memset(pvp->tx_filter_data[data.fir_filter.channel], 0, size);
576 			}
577 			if (pvp->tx_filter_data[data.fir_filter.channel] != NULL) {
578 				error = cuse_copy_in(data.fir_filter.filter_data,
579 				    pvp->tx_filter_data[data.fir_filter.channel], size);
580 			}
581 		}
582 		break;
583 
584 	case VIRTUAL_OSS_GET_SAMPLE_RATE:
585 		data.val = voss_dsp_sample_rate;
586 		break;
587 
588 	case VIRTUAL_OSS_GET_SYSTEM_INFO:
589 		data.sys_info.tx_jitter_up = voss_jitter_up;
590 		data.sys_info.tx_jitter_down = voss_jitter_down;
591 		data.sys_info.sample_rate = voss_dsp_sample_rate;
592 		data.sys_info.sample_bits = voss_dsp_bits;
593 		data.sys_info.sample_channels = voss_mix_channels;
594 		strlcpy(data.sys_info.rx_device_name, voss_dsp_rx_device,
595 		    sizeof(data.sys_info.rx_device_name));
596 		strlcpy(data.sys_info.tx_device_name, voss_dsp_tx_device,
597 		    sizeof(data.sys_info.tx_device_name));
598 		break;
599 
600 	default:
601 		error = CUSE_ERR_INVALID;
602 		break;
603 	}
604 	atomic_unlock();
605 
606 	if (error == 0) {
607 		if (cmd & IOC_OUT)
608 			error = cuse_copy_out(&data, peer_data, len);
609 	}
610 	return (error);
611 }
612 
613 const struct cuse_methods vctl_methods = {
614 	.cm_open = vctl_open,
615 	.cm_close = vctl_close,
616 	.cm_ioctl = vctl_ioctl,
617 };
618