xref: /freebsd/sys/dev/sound/pcm/sndstat.c (revision f126890ac5386406dadf7c4cfa9566cbb56537c5)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
5  * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
6  * Copyright (c) 2020 The FreeBSD Foundation
7  * All rights reserved.
8  *
9  * Portions of this software were developed by Ka Ho Ng
10  * under sponsorship from the FreeBSD Foundation.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #ifdef HAVE_KERNEL_OPTION_HEADERS
35 #include "opt_snd.h"
36 #endif
37 
38 #include <sys/param.h>
39 #include <sys/lock.h>
40 #include <sys/malloc.h>
41 #include <sys/nv.h>
42 #include <sys/dnv.h>
43 #include <sys/sx.h>
44 #ifdef COMPAT_FREEBSD32
45 #include <sys/sysent.h>
46 #endif
47 
48 #include <dev/sound/pcm/sound.h>
49 #include <dev/sound/pcm/pcm.h>
50 #include <dev/sound/version.h>
51 
52 #define	SS_TYPE_PCM		1
53 #define	SS_TYPE_MIDI		2
54 #define	SS_TYPE_SEQUENCER	3
55 
56 static d_open_t sndstat_open;
57 static void sndstat_close(void *);
58 static d_read_t sndstat_read;
59 static d_write_t sndstat_write;
60 static d_ioctl_t sndstat_ioctl;
61 
62 static struct cdevsw sndstat_cdevsw = {
63 	.d_version =	D_VERSION,
64 	.d_open =	sndstat_open,
65 	.d_read =	sndstat_read,
66 	.d_write =	sndstat_write,
67 	.d_ioctl =	sndstat_ioctl,
68 	.d_name =	"sndstat",
69 	.d_flags =	D_TRACKCLOSE,
70 };
71 
72 struct sndstat_entry {
73 	TAILQ_ENTRY(sndstat_entry) link;
74 	device_t dev;
75 	char *str;
76 	sndstat_handler handler;
77 	int type, unit;
78 };
79 
80 struct sndstat_userdev {
81 	TAILQ_ENTRY(sndstat_userdev) link;
82 	char *provider;
83 	char *nameunit;
84 	char *devnode;
85 	char *desc;
86 	unsigned int pchan;
87 	unsigned int rchan;
88 	struct {
89 		uint32_t min_rate;
90 		uint32_t max_rate;
91 		uint32_t formats;
92 		uint32_t min_chn;
93 		uint32_t max_chn;
94 	} info_play, info_rec;
95 	nvlist_t *provider_nvl;
96 };
97 
98 struct sndstat_file {
99 	TAILQ_ENTRY(sndstat_file) entry;
100 	struct sbuf sbuf;
101 	struct sx lock;
102 	void *devs_nvlbuf;	/* (l) */
103 	size_t devs_nbytes;	/* (l) */
104 	TAILQ_HEAD(, sndstat_userdev) userdev_list;	/* (l) */
105 	int out_offset;
106   	int in_offset;
107 	int fflags;
108 };
109 
110 static struct sx sndstat_lock;
111 static struct cdev *sndstat_dev;
112 
113 #define	SNDSTAT_LOCK() sx_xlock(&sndstat_lock)
114 #define	SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock)
115 
116 static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist);
117 static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist);
118 
119 int snd_verbose = 0;
120 
121 static int sndstat_prepare(struct sndstat_file *);
122 static struct sndstat_userdev *
123 sndstat_line2userdev(struct sndstat_file *, const char *, int);
124 
125 static int
126 sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)
127 {
128 	int error, verbose;
129 
130 	verbose = snd_verbose;
131 	error = sysctl_handle_int(oidp, &verbose, 0, req);
132 	if (error == 0 && req->newptr != NULL) {
133 		if (verbose < 0 || verbose > 4)
134 			error = EINVAL;
135 		else
136 			snd_verbose = verbose;
137 	}
138 	return (error);
139 }
140 SYSCTL_PROC(_hw_snd, OID_AUTO, verbose,
141     CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int),
142     sysctl_hw_sndverbose, "I",
143     "verbosity level");
144 
145 static int
146 sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
147 {
148 	struct sndstat_file *pf;
149 
150 	pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO);
151 
152 	if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) {
153 		free(pf, M_DEVBUF);
154 		return (ENOMEM);
155 	}
156 
157 	pf->fflags = flags;
158 	TAILQ_INIT(&pf->userdev_list);
159 	sx_init(&pf->lock, "sndstat_file");
160 
161 	SNDSTAT_LOCK();
162 	TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry);
163 	SNDSTAT_UNLOCK();
164 
165 	devfs_set_cdevpriv(pf, &sndstat_close);
166 
167 	return (0);
168 }
169 
170 /*
171  * Should only be called either when:
172  * * Closing
173  * * pf->lock held
174  */
175 static void
176 sndstat_remove_all_userdevs(struct sndstat_file *pf)
177 {
178 	struct sndstat_userdev *ud;
179 
180 	KASSERT(
181 	    sx_xlocked(&pf->lock), ("%s: Called without pf->lock", __func__));
182 	while ((ud = TAILQ_FIRST(&pf->userdev_list)) != NULL) {
183 		TAILQ_REMOVE(&pf->userdev_list, ud, link);
184 		free(ud->provider, M_DEVBUF);
185 		free(ud->desc, M_DEVBUF);
186 		free(ud->devnode, M_DEVBUF);
187 		free(ud->nameunit, M_DEVBUF);
188 		nvlist_destroy(ud->provider_nvl);
189 		free(ud, M_DEVBUF);
190 	}
191 }
192 
193 static void
194 sndstat_close(void *sndstat_file)
195 {
196 	struct sndstat_file *pf = (struct sndstat_file *)sndstat_file;
197 
198 	SNDSTAT_LOCK();
199 	sbuf_delete(&pf->sbuf);
200 	TAILQ_REMOVE(&sndstat_filelist, pf, entry);
201 	SNDSTAT_UNLOCK();
202 
203 	free(pf->devs_nvlbuf, M_NVLIST);
204 	sx_xlock(&pf->lock);
205 	sndstat_remove_all_userdevs(pf);
206 	sx_xunlock(&pf->lock);
207 	sx_destroy(&pf->lock);
208 
209 	free(pf, M_DEVBUF);
210 }
211 
212 static int
213 sndstat_read(struct cdev *i_dev, struct uio *buf, int flag)
214 {
215 	struct sndstat_file *pf;
216 	int err;
217 	int len;
218 
219 	err = devfs_get_cdevpriv((void **)&pf);
220 	if (err != 0)
221 		return (err);
222 
223 	/* skip zero-length reads */
224 	if (buf->uio_resid == 0)
225 		return (0);
226 
227 	SNDSTAT_LOCK();
228 	if (pf->out_offset != 0) {
229 		/* don't allow both reading and writing */
230 		err = EINVAL;
231 		goto done;
232 	} else if (pf->in_offset == 0) {
233 		err = sndstat_prepare(pf);
234 		if (err <= 0) {
235 			err = ENOMEM;
236 			goto done;
237 		}
238 	}
239 	len = sbuf_len(&pf->sbuf) - pf->in_offset;
240 	if (len > buf->uio_resid)
241 		len = buf->uio_resid;
242 	if (len > 0)
243 		err = uiomove(sbuf_data(&pf->sbuf) + pf->in_offset, len, buf);
244 	pf->in_offset += len;
245 done:
246 	SNDSTAT_UNLOCK();
247 	return (err);
248 }
249 
250 static int
251 sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
252 {
253 	struct sndstat_file *pf;
254 	uint8_t temp[64];
255 	int err;
256 	int len;
257 
258 	err = devfs_get_cdevpriv((void **)&pf);
259 	if (err != 0)
260 		return (err);
261 
262 	/* skip zero-length writes */
263 	if (buf->uio_resid == 0)
264 		return (0);
265 
266 	/* don't allow writing more than 64Kbytes */
267 	if (buf->uio_resid > 65536)
268 		return (ENOMEM);
269 
270 	SNDSTAT_LOCK();
271 	if (pf->in_offset != 0) {
272 		/* don't allow both reading and writing */
273 		err = EINVAL;
274 	} else {
275 		/* only remember the last write - allows for updates */
276 		sx_xlock(&pf->lock);
277 		sndstat_remove_all_userdevs(pf);
278 		sx_xunlock(&pf->lock);
279 
280 		while (1) {
281 			len = sizeof(temp);
282 			if (len > buf->uio_resid)
283 				len = buf->uio_resid;
284 			if (len > 0) {
285 				err = uiomove(temp, len, buf);
286 				if (err)
287 					break;
288 			} else {
289 				break;
290 			}
291 			if (sbuf_bcat(&pf->sbuf, temp, len) < 0) {
292 				err = ENOMEM;
293 				break;
294 			}
295 		}
296 		sbuf_finish(&pf->sbuf);
297 
298 		if (err == 0) {
299 			char *line, *str;
300 
301 			str = sbuf_data(&pf->sbuf);
302 			while ((line = strsep(&str, "\n")) != NULL) {
303 				struct sndstat_userdev *ud;
304 
305 				ud = sndstat_line2userdev(pf, line, strlen(line));
306 				if (ud == NULL)
307 					continue;
308 
309 				sx_xlock(&pf->lock);
310 				TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link);
311 				sx_xunlock(&pf->lock);
312 			}
313 
314 			pf->out_offset = sbuf_len(&pf->sbuf);
315 		} else
316 			pf->out_offset = 0;
317 
318 		sbuf_clear(&pf->sbuf);
319 	}
320 	SNDSTAT_UNLOCK();
321 	return (err);
322 }
323 
324 static void
325 sndstat_get_caps(struct snddev_info *d, bool play, uint32_t *min_rate,
326     uint32_t *max_rate, uint32_t *fmts, uint32_t *minchn, uint32_t *maxchn)
327 {
328 	struct pcm_channel *c;
329 	unsigned int encoding;
330 	int dir;
331 
332 	dir = play ? PCMDIR_PLAY : PCMDIR_REC;
333 
334 	if (play && d->pvchancount > 0) {
335 		*min_rate = *max_rate = d->pvchanrate;
336 		*fmts = AFMT_ENCODING(d->pvchanformat);
337 		*minchn = *maxchn = AFMT_CHANNEL(d->pvchanformat);
338 		return;
339 	} else if (!play && d->rvchancount > 0) {
340 		*min_rate = *max_rate = d->rvchanrate;
341 		*fmts = AFMT_ENCODING(d->rvchanformat);
342 		*minchn = *maxchn = AFMT_CHANNEL(d->rvchanformat);
343 		return;
344 	}
345 
346 	*min_rate = UINT32_MAX;
347 	*max_rate = 0;
348 	*minchn = UINT32_MAX;
349 	*maxchn = 0;
350 	encoding = 0;
351 	CHN_FOREACH(c, d, channels.pcm) {
352 		struct pcmchan_caps *caps;
353 		int i;
354 
355 		if (c->direction != dir || (c->flags & CHN_F_VIRTUAL) != 0)
356 			continue;
357 
358 		CHN_LOCK(c);
359 		caps = chn_getcaps(c);
360 		*min_rate = min(caps->minspeed, *min_rate);
361 		*max_rate = max(caps->maxspeed, *max_rate);
362 		for (i = 0; caps->fmtlist[i]; i++) {
363 			encoding |= AFMT_ENCODING(caps->fmtlist[i]);
364 			*minchn = min(AFMT_CHANNEL(encoding), *minchn);
365 			*maxchn = max(AFMT_CHANNEL(encoding), *maxchn);
366 		}
367 		CHN_UNLOCK(c);
368 	}
369 	if (*min_rate == UINT32_MAX)
370 		*min_rate = 0;
371 	if (*minchn == UINT32_MAX)
372 		*minchn = 0;
373 }
374 
375 static nvlist_t *
376 sndstat_create_diinfo_nv(uint32_t min_rate, uint32_t max_rate, uint32_t formats,
377 	    uint32_t min_chn, uint32_t max_chn)
378 {
379 	nvlist_t *nv;
380 
381 	nv = nvlist_create(0);
382 	if (nv == NULL)
383 		return (NULL);
384 	nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_RATE, min_rate);
385 	nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_RATE, max_rate);
386 	nvlist_add_number(nv, SNDST_DSPS_INFO_FORMATS, formats);
387 	nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_CHN, min_chn);
388 	nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_CHN, max_chn);
389 	return (nv);
390 }
391 
392 static int
393 sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
394 {
395 	uint32_t maxrate, minrate, fmts, minchn, maxchn;
396 	nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL;
397 	int err;
398 
399 	di = nvlist_create(0);
400 	if (di == NULL) {
401 		err = ENOMEM;
402 		goto done;
403 	}
404 	sound4di = nvlist_create(0);
405 	if (sound4di == NULL) {
406 		err = ENOMEM;
407 		goto done;
408 	}
409 
410 	nvlist_add_bool(di, SNDST_DSPS_FROM_USER, false);
411 	nvlist_add_stringf(di, SNDST_DSPS_NAMEUNIT, "%s",
412 			device_get_nameunit(d->dev));
413 	nvlist_add_stringf(di, SNDST_DSPS_DEVNODE, "dsp%d",
414 			device_get_unit(d->dev));
415 	nvlist_add_string(
416 			di, SNDST_DSPS_DESC, device_get_desc(d->dev));
417 
418 	PCM_ACQUIRE_QUICK(d);
419 	nvlist_add_number(di, SNDST_DSPS_PCHAN, d->playcount);
420 	nvlist_add_number(di, SNDST_DSPS_RCHAN, d->reccount);
421 	if (d->playcount > 0) {
422 		sndstat_get_caps(d, true, &minrate, &maxrate, &fmts, &minchn,
423 		    &maxchn);
424 		nvlist_add_number(di, "pminrate", minrate);
425 		nvlist_add_number(di, "pmaxrate", maxrate);
426 		nvlist_add_number(di, "pfmts", fmts);
427 		diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
428 		    minchn, maxchn);
429 		if (diinfo == NULL)
430 			nvlist_set_error(di, ENOMEM);
431 		else
432 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
433 	}
434 	if (d->reccount > 0) {
435 		sndstat_get_caps(d, false, &minrate, &maxrate, &fmts, &minchn,
436 		    &maxchn);
437 		nvlist_add_number(di, "rminrate", minrate);
438 		nvlist_add_number(di, "rmaxrate", maxrate);
439 		nvlist_add_number(di, "rfmts", fmts);
440 		diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
441 		    minchn, maxchn);
442 		if (diinfo == NULL)
443 			nvlist_set_error(di, ENOMEM);
444 		else
445 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
446 	}
447 
448 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_UNIT,
449 			device_get_unit(d->dev)); // XXX: I want signed integer here
450 	nvlist_add_bool(
451 	    sound4di, SNDST_DSPS_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT);
452 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHAN, d->pvchancount);
453 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHAN, d->rvchancount);
454 	nvlist_move_nvlist(di, SNDST_DSPS_PROVIDER_INFO, sound4di);
455 	sound4di = NULL;
456 	PCM_RELEASE_QUICK(d);
457 	nvlist_add_string(di, SNDST_DSPS_PROVIDER, SNDST_DSPS_SOUND4_PROVIDER);
458 
459 	err = nvlist_error(di);
460 	if (err)
461 		goto done;
462 
463 	*dip = di;
464 
465 done:
466 	if (err) {
467 		nvlist_destroy(sound4di);
468 		nvlist_destroy(di);
469 	}
470 	return (err);
471 }
472 
473 static int
474 sndstat_build_userland_nvlist(struct sndstat_userdev *ud, nvlist_t **dip)
475 {
476 	nvlist_t *di, *diinfo;
477 	int err;
478 
479 	di = nvlist_create(0);
480 	if (di == NULL) {
481 		err = ENOMEM;
482 		goto done;
483 	}
484 
485 	nvlist_add_bool(di, SNDST_DSPS_FROM_USER, true);
486 	nvlist_add_number(di, SNDST_DSPS_PCHAN, ud->pchan);
487 	nvlist_add_number(di, SNDST_DSPS_RCHAN, ud->rchan);
488 	nvlist_add_string(di, SNDST_DSPS_NAMEUNIT, ud->nameunit);
489 	nvlist_add_string(
490 			di, SNDST_DSPS_DEVNODE, ud->devnode);
491 	nvlist_add_string(di, SNDST_DSPS_DESC, ud->desc);
492 	if (ud->pchan != 0) {
493 		nvlist_add_number(di, "pminrate",
494 		    ud->info_play.min_rate);
495 		nvlist_add_number(di, "pmaxrate",
496 		    ud->info_play.max_rate);
497 		nvlist_add_number(di, "pfmts",
498 		    ud->info_play.formats);
499 		diinfo = sndstat_create_diinfo_nv(ud->info_play.min_rate,
500 		    ud->info_play.max_rate, ud->info_play.formats,
501 		    ud->info_play.min_chn, ud->info_play.max_chn);
502 		if (diinfo == NULL)
503 			nvlist_set_error(di, ENOMEM);
504 		else
505 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
506 	}
507 	if (ud->rchan != 0) {
508 		nvlist_add_number(di, "rminrate",
509 		    ud->info_rec.min_rate);
510 		nvlist_add_number(di, "rmaxrate",
511 		    ud->info_rec.max_rate);
512 		nvlist_add_number(di, "rfmts",
513 		    ud->info_rec.formats);
514 		diinfo = sndstat_create_diinfo_nv(ud->info_rec.min_rate,
515 		    ud->info_rec.max_rate, ud->info_rec.formats,
516 		    ud->info_rec.min_chn, ud->info_rec.max_chn);
517 		if (diinfo == NULL)
518 			nvlist_set_error(di, ENOMEM);
519 		else
520 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
521 	}
522 	nvlist_add_string(di, SNDST_DSPS_PROVIDER,
523 	    (ud->provider != NULL) ? ud->provider : "");
524 	if (ud->provider_nvl != NULL)
525 		nvlist_add_nvlist(
526 		    di, SNDST_DSPS_PROVIDER_INFO, ud->provider_nvl);
527 
528 	err = nvlist_error(di);
529 	if (err)
530 		goto done;
531 
532 	*dip = di;
533 
534 done:
535 	if (err)
536 		nvlist_destroy(di);
537 	return (err);
538 }
539 
540 /*
541  * Should only be called with the following locks held:
542  * * sndstat_lock
543  */
544 static int
545 sndstat_create_devs_nvlist(nvlist_t **nvlp)
546 {
547 	int err;
548 	nvlist_t *nvl;
549 	struct sndstat_entry *ent;
550 	struct sndstat_file *pf;
551 
552 	nvl = nvlist_create(0);
553 	if (nvl == NULL)
554 		return (ENOMEM);
555 
556 	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
557 		struct snddev_info *d;
558 		nvlist_t *di;
559 
560 		d = device_get_softc(ent->dev);
561 		if (!PCM_REGISTERED(d))
562 			continue;
563 
564 		err = sndstat_build_sound4_nvlist(d, &di);
565 		if (err)
566 			goto done;
567 
568 		nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
569 		nvlist_destroy(di);
570 		err = nvlist_error(nvl);
571 		if (err)
572 			goto done;
573 	}
574 
575 	TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
576 		struct sndstat_userdev *ud;
577 
578 		sx_xlock(&pf->lock);
579 
580 		TAILQ_FOREACH(ud, &pf->userdev_list, link) {
581 			nvlist_t *di;
582 
583 			err = sndstat_build_userland_nvlist(ud, &di);
584 			if (err != 0) {
585 				sx_xunlock(&pf->lock);
586 				goto done;
587 			}
588 			nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
589 			nvlist_destroy(di);
590 
591 			err = nvlist_error(nvl);
592 			if (err != 0) {
593 				sx_xunlock(&pf->lock);
594 				goto done;
595 			}
596 		}
597 
598 		sx_xunlock(&pf->lock);
599 	}
600 
601 	*nvlp = nvl;
602 
603 done:
604 	if (err != 0)
605 		nvlist_destroy(nvl);
606 	return (err);
607 }
608 
609 static int
610 sndstat_refresh_devs(struct sndstat_file *pf)
611 {
612 	sx_xlock(&pf->lock);
613 	free(pf->devs_nvlbuf, M_NVLIST);
614 	pf->devs_nvlbuf = NULL;
615 	pf->devs_nbytes = 0;
616 	sx_unlock(&pf->lock);
617 
618 	return (0);
619 }
620 
621 static int
622 sndstat_get_devs(struct sndstat_file *pf, caddr_t data)
623 {
624 	int err;
625 	struct sndstioc_nv_arg *arg = (struct sndstioc_nv_arg *)data;
626 
627 	SNDSTAT_LOCK();
628 	sx_xlock(&pf->lock);
629 
630 	if (pf->devs_nvlbuf == NULL) {
631 		nvlist_t *nvl;
632 		void *nvlbuf;
633 		size_t nbytes;
634 		int err;
635 
636 		sx_xunlock(&pf->lock);
637 
638 		err = sndstat_create_devs_nvlist(&nvl);
639 		if (err) {
640 			SNDSTAT_UNLOCK();
641 			return (err);
642 		}
643 
644 		sx_xlock(&pf->lock);
645 
646 		nvlbuf = nvlist_pack(nvl, &nbytes);
647 		err = nvlist_error(nvl);
648 		nvlist_destroy(nvl);
649 		if (nvlbuf == NULL || err != 0) {
650 			SNDSTAT_UNLOCK();
651 			sx_xunlock(&pf->lock);
652 			if (err == 0)
653 				return (ENOMEM);
654 			return (err);
655 		}
656 
657 		free(pf->devs_nvlbuf, M_NVLIST);
658 		pf->devs_nvlbuf = nvlbuf;
659 		pf->devs_nbytes = nbytes;
660 	}
661 
662 	SNDSTAT_UNLOCK();
663 
664 	if (!arg->nbytes) {
665 		arg->nbytes = pf->devs_nbytes;
666 		err = 0;
667 		goto done;
668 	}
669 	if (arg->nbytes < pf->devs_nbytes) {
670 		arg->nbytes = 0;
671 		err = 0;
672 		goto done;
673 	}
674 
675 	err = copyout(pf->devs_nvlbuf, arg->buf, pf->devs_nbytes);
676 	if (err)
677 		goto done;
678 
679 	arg->nbytes = pf->devs_nbytes;
680 
681 	free(pf->devs_nvlbuf, M_NVLIST);
682 	pf->devs_nvlbuf = NULL;
683 	pf->devs_nbytes = 0;
684 
685 done:
686 	sx_unlock(&pf->lock);
687 	return (err);
688 }
689 
690 static int
691 sndstat_unpack_user_nvlbuf(const void *unvlbuf, size_t nbytes, nvlist_t **nvl)
692 {
693 	void *nvlbuf;
694 	int err;
695 
696 	nvlbuf = malloc(nbytes, M_DEVBUF, M_WAITOK);
697 	err = copyin(unvlbuf, nvlbuf, nbytes);
698 	if (err != 0) {
699 		free(nvlbuf, M_DEVBUF);
700 		return (err);
701 	}
702 	*nvl = nvlist_unpack(nvlbuf, nbytes, 0);
703 	free(nvlbuf, M_DEVBUF);
704 	if (nvl == NULL) {
705 		return (EINVAL);
706 	}
707 
708 	return (0);
709 }
710 
711 static bool
712 sndstat_diinfo_is_sane(const nvlist_t *diinfo)
713 {
714 	if (!(nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_RATE) &&
715 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_RATE) &&
716 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_FORMATS) &&
717 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_CHN) &&
718 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_CHN)))
719 		return (false);
720 	return (true);
721 }
722 
723 static bool
724 sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist)
725 {
726 	if (!(nvlist_exists_string(nvlist, SNDST_DSPS_DEVNODE) &&
727 	    nvlist_exists_string(nvlist, SNDST_DSPS_DESC) &&
728 	    nvlist_exists_number(nvlist, SNDST_DSPS_PCHAN) &&
729 	    nvlist_exists_number(nvlist, SNDST_DSPS_RCHAN)))
730 		return (false);
731 
732 	if (nvlist_get_number(nvlist, SNDST_DSPS_PCHAN) > 0) {
733 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
734 			if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
735 			    SNDST_DSPS_INFO_PLAY)))
736 				return (false);
737 		} else if (!(nvlist_exists_number(nvlist, "pminrate") &&
738 		    nvlist_exists_number(nvlist, "pmaxrate") &&
739 		    nvlist_exists_number(nvlist, "pfmts")))
740 			return (false);
741 	}
742 
743 	if (nvlist_get_number(nvlist, SNDST_DSPS_RCHAN) > 0) {
744 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
745 			if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
746 			    SNDST_DSPS_INFO_REC)))
747 				return (false);
748 		} else if (!(nvlist_exists_number(nvlist, "rminrate") &&
749 		    nvlist_exists_number(nvlist, "rmaxrate") &&
750 		    nvlist_exists_number(nvlist, "rfmts")))
751 			return (false);
752 	}
753 
754 	return (true);
755 
756 }
757 
758 static void
759 sndstat_get_diinfo_nv(const nvlist_t *nv, uint32_t *min_rate,
760 	    uint32_t *max_rate, uint32_t *formats, uint32_t *min_chn,
761 	    uint32_t *max_chn)
762 {
763 	*min_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_RATE);
764 	*max_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_RATE);
765 	*formats = nvlist_get_number(nv, SNDST_DSPS_INFO_FORMATS);
766 	*min_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_CHN);
767 	*max_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_CHN);
768 }
769 
770 static int
771 sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud)
772 {
773 	const char *nameunit, *devnode, *desc;
774 	unsigned int pchan, rchan;
775 	uint32_t pminrate = 0, pmaxrate = 0;
776 	uint32_t rminrate = 0, rmaxrate = 0;
777 	uint32_t pfmts = 0, rfmts = 0;
778 	uint32_t pminchn = 0, pmaxchn = 0;
779 	uint32_t rminchn = 0, rmaxchn = 0;
780 	nvlist_t *provider_nvl = NULL;
781 	const nvlist_t *diinfo;
782 	const char *provider;
783 
784 	devnode = nvlist_get_string(nvlist, SNDST_DSPS_DEVNODE);
785 	if (nvlist_exists_string(nvlist, SNDST_DSPS_NAMEUNIT))
786 		nameunit = nvlist_get_string(nvlist, SNDST_DSPS_NAMEUNIT);
787 	else
788 		nameunit = devnode;
789 	desc = nvlist_get_string(nvlist, SNDST_DSPS_DESC);
790 	pchan = nvlist_get_number(nvlist, SNDST_DSPS_PCHAN);
791 	rchan = nvlist_get_number(nvlist, SNDST_DSPS_RCHAN);
792 	if (pchan != 0) {
793 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
794 			diinfo = nvlist_get_nvlist(nvlist,
795 			    SNDST_DSPS_INFO_PLAY);
796 			sndstat_get_diinfo_nv(diinfo, &pminrate, &pmaxrate,
797 			    &pfmts, &pminchn, &pmaxchn);
798 		} else {
799 			pminrate = nvlist_get_number(nvlist, "pminrate");
800 			pmaxrate = nvlist_get_number(nvlist, "pmaxrate");
801 			pfmts = nvlist_get_number(nvlist, "pfmts");
802 		}
803 	}
804 	if (rchan != 0) {
805 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
806 			diinfo = nvlist_get_nvlist(nvlist,
807 			    SNDST_DSPS_INFO_REC);
808 			sndstat_get_diinfo_nv(diinfo, &rminrate, &rmaxrate,
809 			    &rfmts, &rminchn, &rmaxchn);
810 		} else {
811 			rminrate = nvlist_get_number(nvlist, "rminrate");
812 			rmaxrate = nvlist_get_number(nvlist, "rmaxrate");
813 			rfmts = nvlist_get_number(nvlist, "rfmts");
814 		}
815 	}
816 
817 	provider = dnvlist_get_string(nvlist, SNDST_DSPS_PROVIDER, "");
818 	if (provider[0] == '\0')
819 		provider = NULL;
820 
821 	if (provider != NULL &&
822 	    nvlist_exists_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)) {
823 		provider_nvl = nvlist_clone(
824 		    nvlist_get_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO));
825 		if (provider_nvl == NULL)
826 			return (ENOMEM);
827 	}
828 
829 	ud->provider = (provider != NULL) ? strdup(provider, M_DEVBUF) : NULL;
830 	ud->devnode = strdup(devnode, M_DEVBUF);
831 	ud->nameunit = strdup(nameunit, M_DEVBUF);
832 	ud->desc = strdup(desc, M_DEVBUF);
833 	ud->pchan = pchan;
834 	ud->rchan = rchan;
835 	ud->info_play.min_rate = pminrate;
836 	ud->info_play.max_rate = pmaxrate;
837 	ud->info_play.formats = pfmts;
838 	ud->info_play.min_chn = pminchn;
839 	ud->info_play.max_chn = pmaxchn;
840 	ud->info_rec.min_rate = rminrate;
841 	ud->info_rec.max_rate = rmaxrate;
842 	ud->info_rec.formats = rfmts;
843 	ud->info_rec.min_chn = rminchn;
844 	ud->info_rec.max_chn = rmaxchn;
845 	ud->provider_nvl = provider_nvl;
846 	return (0);
847 }
848 
849 static int
850 sndstat_add_user_devs(struct sndstat_file *pf, caddr_t data)
851 {
852 	int err;
853 	nvlist_t *nvl = NULL;
854 	const nvlist_t * const *dsps;
855 	size_t i, ndsps;
856 	struct sndstioc_nv_arg *arg = (struct sndstioc_nv_arg *)data;
857 
858 	if ((pf->fflags & FWRITE) == 0) {
859 		err = EPERM;
860 		goto done;
861 	}
862 
863 	err = sndstat_unpack_user_nvlbuf(arg->buf, arg->nbytes, &nvl);
864 	if (err != 0)
865 		goto done;
866 
867 	if (!nvlist_exists_nvlist_array(nvl, SNDST_DSPS)) {
868 		err = EINVAL;
869 		goto done;
870 	}
871 	dsps = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &ndsps);
872 	for (i = 0; i < ndsps; i++) {
873 		if (!sndstat_dsp_nvlist_is_sane(dsps[i])) {
874 			err = EINVAL;
875 			goto done;
876 		}
877 	}
878 	sx_xlock(&pf->lock);
879 	for (i = 0; i < ndsps; i++) {
880 		struct sndstat_userdev *ud =
881 		    malloc(sizeof(*ud), M_DEVBUF, M_WAITOK);
882 		err = sndstat_dsp_unpack_nvlist(dsps[i], ud);
883 		if (err) {
884 			sx_unlock(&pf->lock);
885 			goto done;
886 		}
887 		TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link);
888 	}
889 	sx_unlock(&pf->lock);
890 
891 done:
892 	nvlist_destroy(nvl);
893 	return (err);
894 }
895 
896 static int
897 sndstat_flush_user_devs(struct sndstat_file *pf)
898 {
899 	if ((pf->fflags & FWRITE) == 0)
900 		return (EPERM);
901 
902 	sx_xlock(&pf->lock);
903 	sndstat_remove_all_userdevs(pf);
904 	sx_xunlock(&pf->lock);
905 
906 	return (0);
907 }
908 
909 #ifdef COMPAT_FREEBSD32
910 static int
911 compat_sndstat_get_devs32(struct sndstat_file *pf, caddr_t data)
912 {
913 	struct sndstioc_nv_arg32 *arg32 = (struct sndstioc_nv_arg32 *)data;
914 	struct sndstioc_nv_arg arg;
915 	int err;
916 
917 	arg.buf = (void *)(uintptr_t)arg32->buf;
918 	arg.nbytes = arg32->nbytes;
919 
920 	err = sndstat_get_devs(pf, (caddr_t)&arg);
921 	if (err == 0) {
922 		arg32->buf = (uint32_t)(uintptr_t)arg.buf;
923 		arg32->nbytes = arg.nbytes;
924 	}
925 
926 	return (err);
927 }
928 
929 static int
930 compat_sndstat_add_user_devs32(struct sndstat_file *pf, caddr_t data)
931 {
932 	struct sndstioc_nv_arg32 *arg32 = (struct sndstioc_nv_arg32 *)data;
933 	struct sndstioc_nv_arg arg;
934 	int err;
935 
936 	arg.buf = (void *)(uintptr_t)arg32->buf;
937 	arg.nbytes = arg32->nbytes;
938 
939 	err = sndstat_add_user_devs(pf, (caddr_t)&arg);
940 	if (err == 0) {
941 		arg32->buf = (uint32_t)(uintptr_t)arg.buf;
942 		arg32->nbytes = arg.nbytes;
943 	}
944 
945 	return (err);
946 }
947 #endif
948 
949 static int
950 sndstat_ioctl(
951     struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td)
952 {
953 	int err;
954 	struct sndstat_file *pf;
955 
956 	err = devfs_get_cdevpriv((void **)&pf);
957 	if (err != 0)
958 		return (err);
959 
960 	switch (cmd) {
961 	case SNDSTIOC_GET_DEVS:
962 		err = sndstat_get_devs(pf, data);
963 		break;
964 #ifdef COMPAT_FREEBSD32
965 	case SNDSTIOC_GET_DEVS32:
966 		if (!SV_CURPROC_FLAG(SV_ILP32)) {
967 			err = ENODEV;
968 			break;
969 		}
970 		err = compat_sndstat_get_devs32(pf, data);
971 		break;
972 #endif
973 	case SNDSTIOC_ADD_USER_DEVS:
974 		err = sndstat_add_user_devs(pf, data);
975 		break;
976 #ifdef COMPAT_FREEBSD32
977 	case SNDSTIOC_ADD_USER_DEVS32:
978 		if (!SV_CURPROC_FLAG(SV_ILP32)) {
979 			err = ENODEV;
980 			break;
981 		}
982 		err = compat_sndstat_add_user_devs32(pf, data);
983 		break;
984 #endif
985 	case SNDSTIOC_REFRESH_DEVS:
986 		err = sndstat_refresh_devs(pf);
987 		break;
988 	case SNDSTIOC_FLUSH_USER_DEVS:
989 		err = sndstat_flush_user_devs(pf);
990 		break;
991 	default:
992 		err = ENODEV;
993 	}
994 
995 	return (err);
996 }
997 
998 static struct sndstat_userdev *
999 sndstat_line2userdev(struct sndstat_file *pf, const char *line, int n)
1000 {
1001 	struct sndstat_userdev *ud;
1002 	const char *e, *m;
1003 
1004 	ud = malloc(sizeof(*ud), M_DEVBUF, M_WAITOK|M_ZERO);
1005 
1006 	ud->provider = NULL;
1007 	ud->provider_nvl = NULL;
1008 	e = strchr(line, ':');
1009 	if (e == NULL)
1010 		goto fail;
1011 	ud->nameunit = strndup(line, e - line, M_DEVBUF);
1012 	ud->devnode = (char *)malloc(e - line + 1, M_DEVBUF, M_WAITOK | M_ZERO);
1013 	strlcat(ud->devnode, ud->nameunit, e - line + 1);
1014 	line = e + 1;
1015 
1016 	e = strchr(line, '<');
1017 	if (e == NULL)
1018 		goto fail;
1019 	line = e + 1;
1020 	e = strrchr(line, '>');
1021 	if (e == NULL)
1022 		goto fail;
1023 	ud->desc = strndup(line, e - line, M_DEVBUF);
1024 	line = e + 1;
1025 
1026 	e = strchr(line, '(');
1027 	if (e == NULL)
1028 		goto fail;
1029 	line = e + 1;
1030 	e = strrchr(line, ')');
1031 	if (e == NULL)
1032 		goto fail;
1033 	m = strstr(line, "play");
1034 	if (m != NULL && m < e)
1035 		ud->pchan = 1;
1036 	m = strstr(line, "rec");
1037 	if (m != NULL && m < e)
1038 		ud->rchan = 1;
1039 
1040 	return (ud);
1041 
1042 fail:
1043 	free(ud->nameunit, M_DEVBUF);
1044 	free(ud->devnode, M_DEVBUF);
1045 	free(ud->desc, M_DEVBUF);
1046 	free(ud, M_DEVBUF);
1047 	return (NULL);
1048 }
1049 
1050 /************************************************************************/
1051 
1052 int
1053 sndstat_register(device_t dev, char *str, sndstat_handler handler)
1054 {
1055 	struct sndstat_entry *ent;
1056 	struct sndstat_entry *pre;
1057 	const char *devtype;
1058 	int type, unit;
1059 
1060 	unit = device_get_unit(dev);
1061 	devtype = device_get_name(dev);
1062 	if (!strcmp(devtype, "pcm"))
1063 		type = SS_TYPE_PCM;
1064 	else if (!strcmp(devtype, "midi"))
1065 		type = SS_TYPE_MIDI;
1066 	else if (!strcmp(devtype, "sequencer"))
1067 		type = SS_TYPE_SEQUENCER;
1068 	else
1069 		return (EINVAL);
1070 
1071 	ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
1072 	ent->dev = dev;
1073 	ent->str = str;
1074 	ent->type = type;
1075 	ent->unit = unit;
1076 	ent->handler = handler;
1077 
1078 	SNDSTAT_LOCK();
1079 	/* sorted list insertion */
1080 	TAILQ_FOREACH(pre, &sndstat_devlist, link) {
1081 		if (pre->unit > unit)
1082 			break;
1083 		else if (pre->unit < unit)
1084 			continue;
1085 		if (pre->type > type)
1086 			break;
1087 		else if (pre->type < unit)
1088 			continue;
1089 	}
1090 	if (pre == NULL) {
1091 		TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link);
1092 	} else {
1093 		TAILQ_INSERT_BEFORE(pre, ent, link);
1094 	}
1095 	SNDSTAT_UNLOCK();
1096 
1097 	return (0);
1098 }
1099 
1100 int
1101 sndstat_unregister(device_t dev)
1102 {
1103 	struct sndstat_entry *ent;
1104 	int error = ENXIO;
1105 
1106 	SNDSTAT_LOCK();
1107 	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
1108 		if (ent->dev == dev) {
1109 			TAILQ_REMOVE(&sndstat_devlist, ent, link);
1110 			free(ent, M_DEVBUF);
1111 			error = 0;
1112 			break;
1113 		}
1114 	}
1115 	SNDSTAT_UNLOCK();
1116 
1117 	return (error);
1118 }
1119 
1120 /************************************************************************/
1121 
1122 static int
1123 sndstat_prepare(struct sndstat_file *pf_self)
1124 {
1125 	struct sbuf *s = &pf_self->sbuf;
1126 	struct sndstat_entry *ent;
1127 	struct snddev_info *d;
1128 	struct sndstat_file *pf;
1129     	int k;
1130 
1131 	/* make sure buffer is reset */
1132 	sbuf_clear(s);
1133 
1134 	if (snd_verbose > 0) {
1135 		sbuf_printf(s, "FreeBSD Audio Driver (%ubit %d/%s)\n",
1136 		    (u_int)sizeof(intpcm32_t) << 3, SND_DRV_VERSION,
1137 		    MACHINE_ARCH);
1138 	}
1139 
1140 	/* generate list of installed devices */
1141 	k = 0;
1142 	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
1143 		d = device_get_softc(ent->dev);
1144 		if (!PCM_REGISTERED(d))
1145 			continue;
1146 		if (!k++)
1147 			sbuf_printf(s, "Installed devices:\n");
1148 		sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
1149 		sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
1150 		if (snd_verbose > 0)
1151 			sbuf_printf(s, " %s", ent->str);
1152 		if (ent->handler) {
1153 			/* XXX Need Giant magic entry ??? */
1154 			PCM_ACQUIRE_QUICK(d);
1155 			ent->handler(s, ent->dev, snd_verbose);
1156 			PCM_RELEASE_QUICK(d);
1157 		}
1158 		sbuf_printf(s, "\n");
1159 	}
1160 	if (k == 0)
1161 		sbuf_printf(s, "No devices installed.\n");
1162 
1163 	/* append any input from userspace */
1164 	k = 0;
1165 	TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
1166 		struct sndstat_userdev *ud;
1167 
1168 		if (pf == pf_self)
1169 			continue;
1170 		sx_xlock(&pf->lock);
1171 		if (TAILQ_EMPTY(&pf->userdev_list)) {
1172 			sx_unlock(&pf->lock);
1173 			continue;
1174 		}
1175 		if (!k++)
1176 			sbuf_printf(s, "Installed devices from userspace:\n");
1177 		TAILQ_FOREACH(ud, &pf->userdev_list, link) {
1178 			const char *caps = (ud->pchan && ud->rchan) ?
1179 			    "play/rec" :
1180 			    (ud->pchan ? "play" : (ud->rchan ? "rec" : ""));
1181 			sbuf_printf(s, "%s: <%s>", ud->nameunit, ud->desc);
1182 			sbuf_printf(s, " (%s)", caps);
1183 			sbuf_printf(s, "\n");
1184 		}
1185 		sx_unlock(&pf->lock);
1186 	}
1187 	if (k == 0)
1188 		sbuf_printf(s, "No devices installed from userspace.\n");
1189 
1190 	sbuf_finish(s);
1191     	return (sbuf_len(s));
1192 }
1193 
1194 static void
1195 sndstat_sysinit(void *p)
1196 {
1197 	sx_init(&sndstat_lock, "sndstat lock");
1198 	sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
1199 	    UID_ROOT, GID_WHEEL, 0644, "sndstat");
1200 }
1201 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
1202 
1203 static void
1204 sndstat_sysuninit(void *p)
1205 {
1206 	if (sndstat_dev != NULL) {
1207 		/* destroy_dev() will wait for all references to go away */
1208 		destroy_dev(sndstat_dev);
1209 	}
1210 	sx_destroy(&sndstat_lock);
1211 }
1212 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
1213