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