xref: /freebsd/sys/dev/sound/pcm/sndstat.c (revision b9fa1500cb2265b95927e19b9d2119ca26d65be3)
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 #include "feeder_if.h"
53 
54 #define	SS_TYPE_PCM		1
55 #define	SS_TYPE_MIDI		2
56 #define	SS_TYPE_SEQUENCER	3
57 
58 static d_open_t sndstat_open;
59 static void sndstat_close(void *);
60 static d_read_t sndstat_read;
61 static d_write_t sndstat_write;
62 static d_ioctl_t sndstat_ioctl;
63 
64 static struct cdevsw sndstat_cdevsw = {
65 	.d_version =	D_VERSION,
66 	.d_open =	sndstat_open,
67 	.d_read =	sndstat_read,
68 	.d_write =	sndstat_write,
69 	.d_ioctl =	sndstat_ioctl,
70 	.d_name =	"sndstat",
71 	.d_flags =	D_TRACKCLOSE,
72 };
73 
74 struct sndstat_entry {
75 	TAILQ_ENTRY(sndstat_entry) link;
76 	device_t dev;
77 	char *str;
78 	int type, unit;
79 };
80 
81 struct sndstat_userdev {
82 	TAILQ_ENTRY(sndstat_userdev) link;
83 	char *provider;
84 	char *nameunit;
85 	char *devnode;
86 	char *desc;
87 	unsigned int pchan;
88 	unsigned int rchan;
89 	struct {
90 		uint32_t min_rate;
91 		uint32_t max_rate;
92 		uint32_t formats;
93 		uint32_t min_chn;
94 		uint32_t max_chn;
95 	} info_play, info_rec;
96 	nvlist_t *provider_nvl;
97 };
98 
99 struct sndstat_file {
100 	TAILQ_ENTRY(sndstat_file) entry;
101 	struct sbuf sbuf;
102 	struct sx lock;
103 	void *devs_nvlbuf;	/* (l) */
104 	size_t devs_nbytes;	/* (l) */
105 	TAILQ_HEAD(, sndstat_userdev) userdev_list;	/* (l) */
106 	int out_offset;
107   	int in_offset;
108 	int fflags;
109 };
110 
111 static struct sx sndstat_lock;
112 static struct cdev *sndstat_dev;
113 
114 #define	SNDSTAT_LOCK() sx_xlock(&sndstat_lock)
115 #define	SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock)
116 
117 static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist);
118 static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist);
119 
120 int snd_verbose = 0;
121 
122 static int sndstat_prepare(struct sndstat_file *);
123 static struct sndstat_userdev *
124 sndstat_line2userdev(struct sndstat_file *, const char *, int);
125 
126 static int
127 sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)
128 {
129 	int error, verbose;
130 
131 	verbose = snd_verbose;
132 	error = sysctl_handle_int(oidp, &verbose, 0, req);
133 	if (error == 0 && req->newptr != NULL) {
134 		if (verbose < 0 || verbose > 4)
135 			error = EINVAL;
136 		else
137 			snd_verbose = verbose;
138 	}
139 	return (error);
140 }
141 SYSCTL_PROC(_hw_snd, OID_AUTO, verbose,
142     CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int),
143     sysctl_hw_sndverbose, "I",
144     "verbosity level");
145 
146 static int
147 sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
148 {
149 	struct sndstat_file *pf;
150 
151 	pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO);
152 
153 	if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) {
154 		free(pf, M_DEVBUF);
155 		return (ENOMEM);
156 	}
157 
158 	pf->fflags = flags;
159 	TAILQ_INIT(&pf->userdev_list);
160 	sx_init(&pf->lock, "sndstat_file");
161 
162 	SNDSTAT_LOCK();
163 	TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry);
164 	SNDSTAT_UNLOCK();
165 
166 	devfs_set_cdevpriv(pf, &sndstat_close);
167 
168 	return (0);
169 }
170 
171 /*
172  * Should only be called either when:
173  * * Closing
174  * * pf->lock held
175  */
176 static void
177 sndstat_remove_all_userdevs(struct sndstat_file *pf)
178 {
179 	struct sndstat_userdev *ud;
180 
181 	KASSERT(
182 	    sx_xlocked(&pf->lock), ("%s: Called without pf->lock", __func__));
183 	while ((ud = TAILQ_FIRST(&pf->userdev_list)) != NULL) {
184 		TAILQ_REMOVE(&pf->userdev_list, ud, link);
185 		free(ud->provider, M_DEVBUF);
186 		free(ud->desc, M_DEVBUF);
187 		free(ud->devnode, M_DEVBUF);
188 		free(ud->nameunit, M_DEVBUF);
189 		nvlist_destroy(ud->provider_nvl);
190 		free(ud, M_DEVBUF);
191 	}
192 }
193 
194 static void
195 sndstat_close(void *sndstat_file)
196 {
197 	struct sndstat_file *pf = (struct sndstat_file *)sndstat_file;
198 
199 	SNDSTAT_LOCK();
200 	sbuf_delete(&pf->sbuf);
201 	TAILQ_REMOVE(&sndstat_filelist, pf, entry);
202 	SNDSTAT_UNLOCK();
203 
204 	free(pf->devs_nvlbuf, M_NVLIST);
205 	sx_xlock(&pf->lock);
206 	sndstat_remove_all_userdevs(pf);
207 	sx_xunlock(&pf->lock);
208 	sx_destroy(&pf->lock);
209 
210 	free(pf, M_DEVBUF);
211 }
212 
213 static int
214 sndstat_read(struct cdev *i_dev, struct uio *buf, int flag)
215 {
216 	struct sndstat_file *pf;
217 	int err;
218 	int len;
219 
220 	err = devfs_get_cdevpriv((void **)&pf);
221 	if (err != 0)
222 		return (err);
223 
224 	/* skip zero-length reads */
225 	if (buf->uio_resid == 0)
226 		return (0);
227 
228 	SNDSTAT_LOCK();
229 	if (pf->out_offset != 0) {
230 		/* don't allow both reading and writing */
231 		err = EINVAL;
232 		goto done;
233 	} else if (pf->in_offset == 0) {
234 		err = sndstat_prepare(pf);
235 		if (err <= 0) {
236 			err = ENOMEM;
237 			goto done;
238 		}
239 	}
240 	len = sbuf_len(&pf->sbuf) - pf->in_offset;
241 	if (len > buf->uio_resid)
242 		len = buf->uio_resid;
243 	if (len > 0)
244 		err = uiomove(sbuf_data(&pf->sbuf) + pf->in_offset, len, buf);
245 	pf->in_offset += len;
246 done:
247 	SNDSTAT_UNLOCK();
248 	return (err);
249 }
250 
251 static int
252 sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
253 {
254 	struct sndstat_file *pf;
255 	uint8_t temp[64];
256 	int err;
257 	int len;
258 
259 	err = devfs_get_cdevpriv((void **)&pf);
260 	if (err != 0)
261 		return (err);
262 
263 	/* skip zero-length writes */
264 	if (buf->uio_resid == 0)
265 		return (0);
266 
267 	/* don't allow writing more than 64Kbytes */
268 	if (buf->uio_resid > 65536)
269 		return (ENOMEM);
270 
271 	SNDSTAT_LOCK();
272 	if (pf->in_offset != 0) {
273 		/* don't allow both reading and writing */
274 		err = EINVAL;
275 	} else {
276 		/* only remember the last write - allows for updates */
277 		sx_xlock(&pf->lock);
278 		sndstat_remove_all_userdevs(pf);
279 		sx_xunlock(&pf->lock);
280 
281 		while (1) {
282 			len = sizeof(temp);
283 			if (len > buf->uio_resid)
284 				len = buf->uio_resid;
285 			if (len > 0) {
286 				err = uiomove(temp, len, buf);
287 				if (err)
288 					break;
289 			} else {
290 				break;
291 			}
292 			if (sbuf_bcat(&pf->sbuf, temp, len) < 0) {
293 				err = ENOMEM;
294 				break;
295 			}
296 		}
297 		sbuf_finish(&pf->sbuf);
298 
299 		if (err == 0) {
300 			char *line, *str;
301 
302 			str = sbuf_data(&pf->sbuf);
303 			while ((line = strsep(&str, "\n")) != NULL) {
304 				struct sndstat_userdev *ud;
305 
306 				ud = sndstat_line2userdev(pf, line, strlen(line));
307 				if (ud == NULL)
308 					continue;
309 
310 				sx_xlock(&pf->lock);
311 				TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link);
312 				sx_xunlock(&pf->lock);
313 			}
314 
315 			pf->out_offset = sbuf_len(&pf->sbuf);
316 		} else
317 			pf->out_offset = 0;
318 
319 		sbuf_clear(&pf->sbuf);
320 	}
321 	SNDSTAT_UNLOCK();
322 	return (err);
323 }
324 
325 static void
326 sndstat_get_caps(struct snddev_info *d, bool play, uint32_t *min_rate,
327     uint32_t *max_rate, uint32_t *fmts, uint32_t *minchn, uint32_t *maxchn)
328 {
329 	struct pcm_channel *c;
330 	unsigned int encoding;
331 	int dir;
332 
333 	dir = play ? PCMDIR_PLAY : PCMDIR_REC;
334 
335 	if (play && d->pvchancount > 0) {
336 		*min_rate = *max_rate = d->pvchanrate;
337 		*fmts = AFMT_ENCODING(d->pvchanformat);
338 		*minchn = *maxchn = AFMT_CHANNEL(d->pvchanformat);
339 		return;
340 	} else if (!play && d->rvchancount > 0) {
341 		*min_rate = *max_rate = d->rvchanrate;
342 		*fmts = AFMT_ENCODING(d->rvchanformat);
343 		*minchn = *maxchn = AFMT_CHANNEL(d->rvchanformat);
344 		return;
345 	}
346 
347 	*min_rate = UINT32_MAX;
348 	*max_rate = 0;
349 	*minchn = UINT32_MAX;
350 	*maxchn = 0;
351 	encoding = 0;
352 	CHN_FOREACH(c, d, channels.pcm) {
353 		struct pcmchan_caps *caps;
354 		int i;
355 
356 		if (c->direction != dir || (c->flags & CHN_F_VIRTUAL) != 0)
357 			continue;
358 
359 		CHN_LOCK(c);
360 		caps = chn_getcaps(c);
361 		*min_rate = min(caps->minspeed, *min_rate);
362 		*max_rate = max(caps->maxspeed, *max_rate);
363 		for (i = 0; caps->fmtlist[i]; i++) {
364 			encoding |= AFMT_ENCODING(caps->fmtlist[i]);
365 			*minchn = min(AFMT_CHANNEL(encoding), *minchn);
366 			*maxchn = max(AFMT_CHANNEL(encoding), *maxchn);
367 		}
368 		CHN_UNLOCK(c);
369 	}
370 	if (*min_rate == UINT32_MAX)
371 		*min_rate = 0;
372 	if (*minchn == UINT32_MAX)
373 		*minchn = 0;
374 }
375 
376 static nvlist_t *
377 sndstat_create_diinfo_nv(uint32_t min_rate, uint32_t max_rate, uint32_t formats,
378 	    uint32_t min_chn, uint32_t max_chn)
379 {
380 	nvlist_t *nv;
381 
382 	nv = nvlist_create(0);
383 	if (nv == NULL)
384 		return (NULL);
385 	nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_RATE, min_rate);
386 	nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_RATE, max_rate);
387 	nvlist_add_number(nv, SNDST_DSPS_INFO_FORMATS, formats);
388 	nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_CHN, min_chn);
389 	nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_CHN, max_chn);
390 	return (nv);
391 }
392 
393 static int
394 sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
395 {
396 	uint32_t maxrate, minrate, fmts, minchn, maxchn;
397 	nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL;
398 	int err;
399 
400 	di = nvlist_create(0);
401 	if (di == NULL) {
402 		err = ENOMEM;
403 		goto done;
404 	}
405 	sound4di = nvlist_create(0);
406 	if (sound4di == NULL) {
407 		err = ENOMEM;
408 		goto done;
409 	}
410 
411 	nvlist_add_bool(di, SNDST_DSPS_FROM_USER, false);
412 	nvlist_add_stringf(di, SNDST_DSPS_NAMEUNIT, "%s",
413 			device_get_nameunit(d->dev));
414 	nvlist_add_stringf(di, SNDST_DSPS_DEVNODE, "dsp%d",
415 			device_get_unit(d->dev));
416 	nvlist_add_string(
417 			di, SNDST_DSPS_DESC, device_get_desc(d->dev));
418 
419 	PCM_ACQUIRE_QUICK(d);
420 	nvlist_add_number(di, SNDST_DSPS_PCHAN, d->playcount);
421 	nvlist_add_number(di, SNDST_DSPS_RCHAN, d->reccount);
422 	if (d->playcount > 0) {
423 		sndstat_get_caps(d, true, &minrate, &maxrate, &fmts, &minchn,
424 		    &maxchn);
425 		nvlist_add_number(di, "pminrate", minrate);
426 		nvlist_add_number(di, "pmaxrate", maxrate);
427 		nvlist_add_number(di, "pfmts", fmts);
428 		diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
429 		    minchn, maxchn);
430 		if (diinfo == NULL)
431 			nvlist_set_error(di, ENOMEM);
432 		else
433 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
434 	}
435 	if (d->reccount > 0) {
436 		sndstat_get_caps(d, false, &minrate, &maxrate, &fmts, &minchn,
437 		    &maxchn);
438 		nvlist_add_number(di, "rminrate", minrate);
439 		nvlist_add_number(di, "rmaxrate", maxrate);
440 		nvlist_add_number(di, "rfmts", fmts);
441 		diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
442 		    minchn, maxchn);
443 		if (diinfo == NULL)
444 			nvlist_set_error(di, ENOMEM);
445 		else
446 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
447 	}
448 
449 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_UNIT,
450 			device_get_unit(d->dev)); // XXX: I want signed integer here
451 	nvlist_add_bool(
452 	    sound4di, SNDST_DSPS_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT);
453 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHAN, d->pvchancount);
454 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHAN, d->rvchancount);
455 	nvlist_move_nvlist(di, SNDST_DSPS_PROVIDER_INFO, sound4di);
456 	sound4di = NULL;
457 	PCM_RELEASE_QUICK(d);
458 	nvlist_add_string(di, SNDST_DSPS_PROVIDER, SNDST_DSPS_SOUND4_PROVIDER);
459 
460 	err = nvlist_error(di);
461 	if (err)
462 		goto done;
463 
464 	*dip = di;
465 
466 done:
467 	if (err) {
468 		nvlist_destroy(sound4di);
469 		nvlist_destroy(di);
470 	}
471 	return (err);
472 }
473 
474 static int
475 sndstat_build_userland_nvlist(struct sndstat_userdev *ud, nvlist_t **dip)
476 {
477 	nvlist_t *di, *diinfo;
478 	int err;
479 
480 	di = nvlist_create(0);
481 	if (di == NULL) {
482 		err = ENOMEM;
483 		goto done;
484 	}
485 
486 	nvlist_add_bool(di, SNDST_DSPS_FROM_USER, true);
487 	nvlist_add_number(di, SNDST_DSPS_PCHAN, ud->pchan);
488 	nvlist_add_number(di, SNDST_DSPS_RCHAN, ud->rchan);
489 	nvlist_add_string(di, SNDST_DSPS_NAMEUNIT, ud->nameunit);
490 	nvlist_add_string(
491 			di, SNDST_DSPS_DEVNODE, ud->devnode);
492 	nvlist_add_string(di, SNDST_DSPS_DESC, ud->desc);
493 	if (ud->pchan != 0) {
494 		nvlist_add_number(di, "pminrate",
495 		    ud->info_play.min_rate);
496 		nvlist_add_number(di, "pmaxrate",
497 		    ud->info_play.max_rate);
498 		nvlist_add_number(di, "pfmts",
499 		    ud->info_play.formats);
500 		diinfo = sndstat_create_diinfo_nv(ud->info_play.min_rate,
501 		    ud->info_play.max_rate, ud->info_play.formats,
502 		    ud->info_play.min_chn, ud->info_play.max_chn);
503 		if (diinfo == NULL)
504 			nvlist_set_error(di, ENOMEM);
505 		else
506 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
507 	}
508 	if (ud->rchan != 0) {
509 		nvlist_add_number(di, "rminrate",
510 		    ud->info_rec.min_rate);
511 		nvlist_add_number(di, "rmaxrate",
512 		    ud->info_rec.max_rate);
513 		nvlist_add_number(di, "rfmts",
514 		    ud->info_rec.formats);
515 		diinfo = sndstat_create_diinfo_nv(ud->info_rec.min_rate,
516 		    ud->info_rec.max_rate, ud->info_rec.formats,
517 		    ud->info_rec.min_chn, ud->info_rec.max_chn);
518 		if (diinfo == NULL)
519 			nvlist_set_error(di, ENOMEM);
520 		else
521 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
522 	}
523 	nvlist_add_string(di, SNDST_DSPS_PROVIDER,
524 	    (ud->provider != NULL) ? ud->provider : "");
525 	if (ud->provider_nvl != NULL)
526 		nvlist_add_nvlist(
527 		    di, SNDST_DSPS_PROVIDER_INFO, ud->provider_nvl);
528 
529 	err = nvlist_error(di);
530 	if (err)
531 		goto done;
532 
533 	*dip = di;
534 
535 done:
536 	if (err)
537 		nvlist_destroy(di);
538 	return (err);
539 }
540 
541 /*
542  * Should only be called with the following locks held:
543  * * sndstat_lock
544  */
545 static int
546 sndstat_create_devs_nvlist(nvlist_t **nvlp)
547 {
548 	int err;
549 	nvlist_t *nvl;
550 	struct sndstat_entry *ent;
551 	struct sndstat_file *pf;
552 
553 	nvl = nvlist_create(0);
554 	if (nvl == NULL)
555 		return (ENOMEM);
556 
557 	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
558 		struct snddev_info *d;
559 		nvlist_t *di;
560 
561 		d = device_get_softc(ent->dev);
562 		if (!PCM_REGISTERED(d))
563 			continue;
564 
565 		err = sndstat_build_sound4_nvlist(d, &di);
566 		if (err)
567 			goto done;
568 
569 		nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
570 		nvlist_destroy(di);
571 		err = nvlist_error(nvl);
572 		if (err)
573 			goto done;
574 	}
575 
576 	TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
577 		struct sndstat_userdev *ud;
578 
579 		sx_xlock(&pf->lock);
580 
581 		TAILQ_FOREACH(ud, &pf->userdev_list, link) {
582 			nvlist_t *di;
583 
584 			err = sndstat_build_userland_nvlist(ud, &di);
585 			if (err != 0) {
586 				sx_xunlock(&pf->lock);
587 				goto done;
588 			}
589 			nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
590 			nvlist_destroy(di);
591 
592 			err = nvlist_error(nvl);
593 			if (err != 0) {
594 				sx_xunlock(&pf->lock);
595 				goto done;
596 			}
597 		}
598 
599 		sx_xunlock(&pf->lock);
600 	}
601 
602 	*nvlp = nvl;
603 
604 done:
605 	if (err != 0)
606 		nvlist_destroy(nvl);
607 	return (err);
608 }
609 
610 static int
611 sndstat_refresh_devs(struct sndstat_file *pf)
612 {
613 	sx_xlock(&pf->lock);
614 	free(pf->devs_nvlbuf, M_NVLIST);
615 	pf->devs_nvlbuf = NULL;
616 	pf->devs_nbytes = 0;
617 	sx_unlock(&pf->lock);
618 
619 	return (0);
620 }
621 
622 static int
623 sndstat_get_devs(struct sndstat_file *pf, caddr_t data)
624 {
625 	int err;
626 	struct sndstioc_nv_arg *arg = (struct sndstioc_nv_arg *)data;
627 
628 	SNDSTAT_LOCK();
629 	sx_xlock(&pf->lock);
630 
631 	if (pf->devs_nvlbuf == NULL) {
632 		nvlist_t *nvl;
633 		void *nvlbuf;
634 		size_t nbytes;
635 		int err;
636 
637 		sx_xunlock(&pf->lock);
638 
639 		err = sndstat_create_devs_nvlist(&nvl);
640 		if (err) {
641 			SNDSTAT_UNLOCK();
642 			return (err);
643 		}
644 
645 		sx_xlock(&pf->lock);
646 
647 		nvlbuf = nvlist_pack(nvl, &nbytes);
648 		err = nvlist_error(nvl);
649 		nvlist_destroy(nvl);
650 		if (nvlbuf == NULL || err != 0) {
651 			SNDSTAT_UNLOCK();
652 			sx_xunlock(&pf->lock);
653 			if (err == 0)
654 				return (ENOMEM);
655 			return (err);
656 		}
657 
658 		free(pf->devs_nvlbuf, M_NVLIST);
659 		pf->devs_nvlbuf = nvlbuf;
660 		pf->devs_nbytes = nbytes;
661 	}
662 
663 	SNDSTAT_UNLOCK();
664 
665 	if (!arg->nbytes) {
666 		arg->nbytes = pf->devs_nbytes;
667 		err = 0;
668 		goto done;
669 	}
670 	if (arg->nbytes < pf->devs_nbytes) {
671 		arg->nbytes = 0;
672 		err = 0;
673 		goto done;
674 	}
675 
676 	err = copyout(pf->devs_nvlbuf, arg->buf, pf->devs_nbytes);
677 	if (err)
678 		goto done;
679 
680 	arg->nbytes = pf->devs_nbytes;
681 
682 	free(pf->devs_nvlbuf, M_NVLIST);
683 	pf->devs_nvlbuf = NULL;
684 	pf->devs_nbytes = 0;
685 
686 done:
687 	sx_unlock(&pf->lock);
688 	return (err);
689 }
690 
691 static int
692 sndstat_unpack_user_nvlbuf(const void *unvlbuf, size_t nbytes, nvlist_t **nvl)
693 {
694 	void *nvlbuf;
695 	int err;
696 
697 	nvlbuf = malloc(nbytes, M_DEVBUF, M_WAITOK);
698 	err = copyin(unvlbuf, nvlbuf, nbytes);
699 	if (err != 0) {
700 		free(nvlbuf, M_DEVBUF);
701 		return (err);
702 	}
703 	*nvl = nvlist_unpack(nvlbuf, nbytes, 0);
704 	free(nvlbuf, M_DEVBUF);
705 	if (nvl == NULL) {
706 		return (EINVAL);
707 	}
708 
709 	return (0);
710 }
711 
712 static bool
713 sndstat_diinfo_is_sane(const nvlist_t *diinfo)
714 {
715 	if (!(nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_RATE) &&
716 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_RATE) &&
717 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_FORMATS) &&
718 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_CHN) &&
719 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_CHN)))
720 		return (false);
721 	return (true);
722 }
723 
724 static bool
725 sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist)
726 {
727 	if (!(nvlist_exists_string(nvlist, SNDST_DSPS_DEVNODE) &&
728 	    nvlist_exists_string(nvlist, SNDST_DSPS_DESC) &&
729 	    nvlist_exists_number(nvlist, SNDST_DSPS_PCHAN) &&
730 	    nvlist_exists_number(nvlist, SNDST_DSPS_RCHAN)))
731 		return (false);
732 
733 	if (nvlist_get_number(nvlist, SNDST_DSPS_PCHAN) > 0) {
734 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
735 			if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
736 			    SNDST_DSPS_INFO_PLAY)))
737 				return (false);
738 		} else if (!(nvlist_exists_number(nvlist, "pminrate") &&
739 		    nvlist_exists_number(nvlist, "pmaxrate") &&
740 		    nvlist_exists_number(nvlist, "pfmts")))
741 			return (false);
742 	}
743 
744 	if (nvlist_get_number(nvlist, SNDST_DSPS_RCHAN) > 0) {
745 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
746 			if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
747 			    SNDST_DSPS_INFO_REC)))
748 				return (false);
749 		} else if (!(nvlist_exists_number(nvlist, "rminrate") &&
750 		    nvlist_exists_number(nvlist, "rmaxrate") &&
751 		    nvlist_exists_number(nvlist, "rfmts")))
752 			return (false);
753 	}
754 
755 	return (true);
756 
757 }
758 
759 static void
760 sndstat_get_diinfo_nv(const nvlist_t *nv, uint32_t *min_rate,
761 	    uint32_t *max_rate, uint32_t *formats, uint32_t *min_chn,
762 	    uint32_t *max_chn)
763 {
764 	*min_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_RATE);
765 	*max_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_RATE);
766 	*formats = nvlist_get_number(nv, SNDST_DSPS_INFO_FORMATS);
767 	*min_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_CHN);
768 	*max_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_CHN);
769 }
770 
771 static int
772 sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud)
773 {
774 	const char *nameunit, *devnode, *desc;
775 	unsigned int pchan, rchan;
776 	uint32_t pminrate = 0, pmaxrate = 0;
777 	uint32_t rminrate = 0, rmaxrate = 0;
778 	uint32_t pfmts = 0, rfmts = 0;
779 	uint32_t pminchn = 0, pmaxchn = 0;
780 	uint32_t rminchn = 0, rmaxchn = 0;
781 	nvlist_t *provider_nvl = NULL;
782 	const nvlist_t *diinfo;
783 	const char *provider;
784 
785 	devnode = nvlist_get_string(nvlist, SNDST_DSPS_DEVNODE);
786 	if (nvlist_exists_string(nvlist, SNDST_DSPS_NAMEUNIT))
787 		nameunit = nvlist_get_string(nvlist, SNDST_DSPS_NAMEUNIT);
788 	else
789 		nameunit = devnode;
790 	desc = nvlist_get_string(nvlist, SNDST_DSPS_DESC);
791 	pchan = nvlist_get_number(nvlist, SNDST_DSPS_PCHAN);
792 	rchan = nvlist_get_number(nvlist, SNDST_DSPS_RCHAN);
793 	if (pchan != 0) {
794 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
795 			diinfo = nvlist_get_nvlist(nvlist,
796 			    SNDST_DSPS_INFO_PLAY);
797 			sndstat_get_diinfo_nv(diinfo, &pminrate, &pmaxrate,
798 			    &pfmts, &pminchn, &pmaxchn);
799 		} else {
800 			pminrate = nvlist_get_number(nvlist, "pminrate");
801 			pmaxrate = nvlist_get_number(nvlist, "pmaxrate");
802 			pfmts = nvlist_get_number(nvlist, "pfmts");
803 		}
804 	}
805 	if (rchan != 0) {
806 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
807 			diinfo = nvlist_get_nvlist(nvlist,
808 			    SNDST_DSPS_INFO_REC);
809 			sndstat_get_diinfo_nv(diinfo, &rminrate, &rmaxrate,
810 			    &rfmts, &rminchn, &rmaxchn);
811 		} else {
812 			rminrate = nvlist_get_number(nvlist, "rminrate");
813 			rmaxrate = nvlist_get_number(nvlist, "rmaxrate");
814 			rfmts = nvlist_get_number(nvlist, "rfmts");
815 		}
816 	}
817 
818 	provider = dnvlist_get_string(nvlist, SNDST_DSPS_PROVIDER, "");
819 	if (provider[0] == '\0')
820 		provider = NULL;
821 
822 	if (provider != NULL &&
823 	    nvlist_exists_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)) {
824 		provider_nvl = nvlist_clone(
825 		    nvlist_get_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO));
826 		if (provider_nvl == NULL)
827 			return (ENOMEM);
828 	}
829 
830 	ud->provider = (provider != NULL) ? strdup(provider, M_DEVBUF) : NULL;
831 	ud->devnode = strdup(devnode, M_DEVBUF);
832 	ud->nameunit = strdup(nameunit, M_DEVBUF);
833 	ud->desc = strdup(desc, M_DEVBUF);
834 	ud->pchan = pchan;
835 	ud->rchan = rchan;
836 	ud->info_play.min_rate = pminrate;
837 	ud->info_play.max_rate = pmaxrate;
838 	ud->info_play.formats = pfmts;
839 	ud->info_play.min_chn = pminchn;
840 	ud->info_play.max_chn = pmaxchn;
841 	ud->info_rec.min_rate = rminrate;
842 	ud->info_rec.max_rate = rmaxrate;
843 	ud->info_rec.formats = rfmts;
844 	ud->info_rec.min_chn = rminchn;
845 	ud->info_rec.max_chn = rmaxchn;
846 	ud->provider_nvl = provider_nvl;
847 	return (0);
848 }
849 
850 static int
851 sndstat_add_user_devs(struct sndstat_file *pf, caddr_t data)
852 {
853 	int err;
854 	nvlist_t *nvl = NULL;
855 	const nvlist_t * const *dsps;
856 	size_t i, ndsps;
857 	struct sndstioc_nv_arg *arg = (struct sndstioc_nv_arg *)data;
858 
859 	if ((pf->fflags & FWRITE) == 0) {
860 		err = EPERM;
861 		goto done;
862 	}
863 
864 	err = sndstat_unpack_user_nvlbuf(arg->buf, arg->nbytes, &nvl);
865 	if (err != 0)
866 		goto done;
867 
868 	if (!nvlist_exists_nvlist_array(nvl, SNDST_DSPS)) {
869 		err = EINVAL;
870 		goto done;
871 	}
872 	dsps = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &ndsps);
873 	for (i = 0; i < ndsps; i++) {
874 		if (!sndstat_dsp_nvlist_is_sane(dsps[i])) {
875 			err = EINVAL;
876 			goto done;
877 		}
878 	}
879 	sx_xlock(&pf->lock);
880 	for (i = 0; i < ndsps; i++) {
881 		struct sndstat_userdev *ud =
882 		    malloc(sizeof(*ud), M_DEVBUF, M_WAITOK);
883 		err = sndstat_dsp_unpack_nvlist(dsps[i], ud);
884 		if (err) {
885 			sx_unlock(&pf->lock);
886 			goto done;
887 		}
888 		TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link);
889 	}
890 	sx_unlock(&pf->lock);
891 
892 done:
893 	nvlist_destroy(nvl);
894 	return (err);
895 }
896 
897 static int
898 sndstat_flush_user_devs(struct sndstat_file *pf)
899 {
900 	if ((pf->fflags & FWRITE) == 0)
901 		return (EPERM);
902 
903 	sx_xlock(&pf->lock);
904 	sndstat_remove_all_userdevs(pf);
905 	sx_xunlock(&pf->lock);
906 
907 	return (0);
908 }
909 
910 #ifdef COMPAT_FREEBSD32
911 static int
912 compat_sndstat_get_devs32(struct sndstat_file *pf, caddr_t data)
913 {
914 	struct sndstioc_nv_arg32 *arg32 = (struct sndstioc_nv_arg32 *)data;
915 	struct sndstioc_nv_arg arg;
916 	int err;
917 
918 	arg.buf = (void *)(uintptr_t)arg32->buf;
919 	arg.nbytes = arg32->nbytes;
920 
921 	err = sndstat_get_devs(pf, (caddr_t)&arg);
922 	if (err == 0) {
923 		arg32->buf = (uint32_t)(uintptr_t)arg.buf;
924 		arg32->nbytes = arg.nbytes;
925 	}
926 
927 	return (err);
928 }
929 
930 static int
931 compat_sndstat_add_user_devs32(struct sndstat_file *pf, caddr_t data)
932 {
933 	struct sndstioc_nv_arg32 *arg32 = (struct sndstioc_nv_arg32 *)data;
934 	struct sndstioc_nv_arg arg;
935 	int err;
936 
937 	arg.buf = (void *)(uintptr_t)arg32->buf;
938 	arg.nbytes = arg32->nbytes;
939 
940 	err = sndstat_add_user_devs(pf, (caddr_t)&arg);
941 	if (err == 0) {
942 		arg32->buf = (uint32_t)(uintptr_t)arg.buf;
943 		arg32->nbytes = arg.nbytes;
944 	}
945 
946 	return (err);
947 }
948 #endif
949 
950 static int
951 sndstat_ioctl(
952     struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td)
953 {
954 	int err;
955 	struct sndstat_file *pf;
956 
957 	err = devfs_get_cdevpriv((void **)&pf);
958 	if (err != 0)
959 		return (err);
960 
961 	switch (cmd) {
962 	case SNDSTIOC_GET_DEVS:
963 		err = sndstat_get_devs(pf, data);
964 		break;
965 #ifdef COMPAT_FREEBSD32
966 	case SNDSTIOC_GET_DEVS32:
967 		if (!SV_CURPROC_FLAG(SV_ILP32)) {
968 			err = ENODEV;
969 			break;
970 		}
971 		err = compat_sndstat_get_devs32(pf, data);
972 		break;
973 #endif
974 	case SNDSTIOC_ADD_USER_DEVS:
975 		err = sndstat_add_user_devs(pf, data);
976 		break;
977 #ifdef COMPAT_FREEBSD32
978 	case SNDSTIOC_ADD_USER_DEVS32:
979 		if (!SV_CURPROC_FLAG(SV_ILP32)) {
980 			err = ENODEV;
981 			break;
982 		}
983 		err = compat_sndstat_add_user_devs32(pf, data);
984 		break;
985 #endif
986 	case SNDSTIOC_REFRESH_DEVS:
987 		err = sndstat_refresh_devs(pf);
988 		break;
989 	case SNDSTIOC_FLUSH_USER_DEVS:
990 		err = sndstat_flush_user_devs(pf);
991 		break;
992 	default:
993 		err = ENODEV;
994 	}
995 
996 	return (err);
997 }
998 
999 static struct sndstat_userdev *
1000 sndstat_line2userdev(struct sndstat_file *pf, const char *line, int n)
1001 {
1002 	struct sndstat_userdev *ud;
1003 	const char *e, *m;
1004 
1005 	ud = malloc(sizeof(*ud), M_DEVBUF, M_WAITOK|M_ZERO);
1006 
1007 	ud->provider = NULL;
1008 	ud->provider_nvl = NULL;
1009 	e = strchr(line, ':');
1010 	if (e == NULL)
1011 		goto fail;
1012 	ud->nameunit = strndup(line, e - line, M_DEVBUF);
1013 	ud->devnode = (char *)malloc(e - line + 1, M_DEVBUF, M_WAITOK | M_ZERO);
1014 	strlcat(ud->devnode, ud->nameunit, e - line + 1);
1015 	line = e + 1;
1016 
1017 	e = strchr(line, '<');
1018 	if (e == NULL)
1019 		goto fail;
1020 	line = e + 1;
1021 	e = strrchr(line, '>');
1022 	if (e == NULL)
1023 		goto fail;
1024 	ud->desc = strndup(line, e - line, M_DEVBUF);
1025 	line = e + 1;
1026 
1027 	e = strchr(line, '(');
1028 	if (e == NULL)
1029 		goto fail;
1030 	line = e + 1;
1031 	e = strrchr(line, ')');
1032 	if (e == NULL)
1033 		goto fail;
1034 	m = strstr(line, "play");
1035 	if (m != NULL && m < e)
1036 		ud->pchan = 1;
1037 	m = strstr(line, "rec");
1038 	if (m != NULL && m < e)
1039 		ud->rchan = 1;
1040 
1041 	return (ud);
1042 
1043 fail:
1044 	free(ud->nameunit, M_DEVBUF);
1045 	free(ud->devnode, M_DEVBUF);
1046 	free(ud->desc, M_DEVBUF);
1047 	free(ud, M_DEVBUF);
1048 	return (NULL);
1049 }
1050 
1051 /************************************************************************/
1052 
1053 int
1054 sndstat_register(device_t dev, char *str)
1055 {
1056 	struct sndstat_entry *ent;
1057 	struct sndstat_entry *pre;
1058 	const char *devtype;
1059 	int type, unit;
1060 
1061 	unit = device_get_unit(dev);
1062 	devtype = device_get_name(dev);
1063 	if (!strcmp(devtype, "pcm"))
1064 		type = SS_TYPE_PCM;
1065 	else if (!strcmp(devtype, "midi"))
1066 		type = SS_TYPE_MIDI;
1067 	else if (!strcmp(devtype, "sequencer"))
1068 		type = SS_TYPE_SEQUENCER;
1069 	else
1070 		return (EINVAL);
1071 
1072 	ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
1073 	ent->dev = dev;
1074 	ent->str = str;
1075 	ent->type = type;
1076 	ent->unit = unit;
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_pcm(struct sbuf *s, device_t dev, int verbose)
1124 {
1125 	struct snddev_info *d;
1126 	struct pcm_channel *c;
1127 	struct pcm_feeder *f;
1128 
1129 	d = device_get_softc(dev);
1130 	PCM_BUSYASSERT(d);
1131 
1132 	if (CHN_EMPTY(d, channels.pcm)) {
1133 		sbuf_printf(s, " (mixer only)");
1134 		return (0);
1135 	}
1136 
1137 	if (verbose < 1) {
1138 		sbuf_printf(s, " (%s%s%s",
1139 		    d->playcount ? "play" : "",
1140 		    (d->playcount && d->reccount) ? "/" : "",
1141 		    d->reccount ? "rec" : "");
1142 	} else {
1143 		sbuf_printf(s, " (%dp:%dv/%dr:%dv",
1144 		    d->playcount, d->pvchancount,
1145 		    d->reccount, d->rvchancount);
1146 	}
1147 	sbuf_printf(s, "%s)%s",
1148 	    ((d->playcount != 0 && d->reccount != 0) &&
1149 	    (d->flags & SD_F_SIMPLEX)) ? " simplex" : "",
1150 	    (device_get_unit(dev) == snd_unit) ? " default" : "");
1151 
1152 	if (verbose <= 1)
1153 		return (0);
1154 
1155 	sbuf_printf(s, "\n\t");
1156 	sbuf_printf(s, "snddev flags=0x%b", d->flags, SD_F_BITS);
1157 
1158 	CHN_FOREACH(c, d, channels.pcm) {
1159 		KASSERT(c->bufhard != NULL && c->bufsoft != NULL,
1160 		    ("hosed pcm channel setup"));
1161 
1162 		sbuf_printf(s, "\n\t");
1163 
1164 		sbuf_printf(s, "%s[%s]: ",
1165 		    (c->parentchannel != NULL) ?
1166 		    c->parentchannel->name : "", c->name);
1167 		sbuf_printf(s, "spd %d", c->speed);
1168 		if (c->speed != sndbuf_getspd(c->bufhard)) {
1169 			sbuf_printf(s, "/%d",
1170 			    sndbuf_getspd(c->bufhard));
1171 		}
1172 		sbuf_printf(s, ", fmt 0x%08x", c->format);
1173 		if (c->format != sndbuf_getfmt(c->bufhard)) {
1174 			sbuf_printf(s, "/0x%08x",
1175 			    sndbuf_getfmt(c->bufhard));
1176 		}
1177 		sbuf_printf(s, ", flags 0x%08x, 0x%08x",
1178 		    c->flags, c->feederflags);
1179 		if (c->pid != -1) {
1180 			sbuf_printf(s, ", pid %d (%s)",
1181 			    c->pid, c->comm);
1182 		}
1183 		sbuf_printf(s, "\n\t");
1184 
1185 		sbuf_printf(s, "interrupts %d, ", c->interrupts);
1186 
1187 		if (c->direction == PCMDIR_REC)	{
1188 			sbuf_printf(s,
1189 			    "overruns %d, feed %u, hfree %d, "
1190 			    "sfree %d [b:%d/%d/%d|bs:%d/%d/%d]",
1191 				c->xruns, c->feedcount,
1192 				sndbuf_getfree(c->bufhard),
1193 				sndbuf_getfree(c->bufsoft),
1194 				sndbuf_getsize(c->bufhard),
1195 				sndbuf_getblksz(c->bufhard),
1196 				sndbuf_getblkcnt(c->bufhard),
1197 				sndbuf_getsize(c->bufsoft),
1198 				sndbuf_getblksz(c->bufsoft),
1199 				sndbuf_getblkcnt(c->bufsoft));
1200 		} else {
1201 			sbuf_printf(s,
1202 			    "underruns %d, feed %u, ready %d "
1203 			    "[b:%d/%d/%d|bs:%d/%d/%d]",
1204 				c->xruns, c->feedcount,
1205 				sndbuf_getready(c->bufsoft),
1206 				sndbuf_getsize(c->bufhard),
1207 				sndbuf_getblksz(c->bufhard),
1208 				sndbuf_getblkcnt(c->bufhard),
1209 				sndbuf_getsize(c->bufsoft),
1210 				sndbuf_getblksz(c->bufsoft),
1211 				sndbuf_getblkcnt(c->bufsoft));
1212 		}
1213 		sbuf_printf(s, "\n\t");
1214 
1215 		sbuf_printf(s, "channel flags=0x%b", c->flags, CHN_F_BITS);
1216 		sbuf_printf(s, "\n\t");
1217 
1218 		sbuf_printf(s, "{%s}",
1219 		    (c->direction == PCMDIR_REC) ? "hardware" : "userland");
1220 		sbuf_printf(s, " -> ");
1221 		f = c->feeder;
1222 		while (f->source != NULL)
1223 			f = f->source;
1224 		while (f != NULL) {
1225 			sbuf_printf(s, "%s", f->class->name);
1226 			if (f->desc->type == FEEDER_FORMAT) {
1227 				sbuf_printf(s, "(0x%08x -> 0x%08x)",
1228 				    f->desc->in, f->desc->out);
1229 			} else if (f->desc->type == FEEDER_MATRIX) {
1230 				sbuf_printf(s, "(%d.%d -> %d.%d)",
1231 				    AFMT_CHANNEL(f->desc->in) -
1232 				    AFMT_EXTCHANNEL(f->desc->in),
1233 				    AFMT_EXTCHANNEL(f->desc->in),
1234 				    AFMT_CHANNEL(f->desc->out) -
1235 				    AFMT_EXTCHANNEL(f->desc->out),
1236 				    AFMT_EXTCHANNEL(f->desc->out));
1237 			} else if (f->desc->type == FEEDER_RATE) {
1238 				sbuf_printf(s,
1239 				    "(0x%08x q:%d %d -> %d)",
1240 				    f->desc->out,
1241 				    FEEDER_GET(f, FEEDRATE_QUALITY),
1242 				    FEEDER_GET(f, FEEDRATE_SRC),
1243 				    FEEDER_GET(f, FEEDRATE_DST));
1244 			} else {
1245 				sbuf_printf(s, "(0x%08x)",
1246 				    f->desc->out);
1247 			}
1248 			sbuf_printf(s, " -> ");
1249 			f = f->parent;
1250 		}
1251 		sbuf_printf(s, "{%s}",
1252 		    (c->direction == PCMDIR_REC) ? "userland" : "hardware");
1253 	}
1254 
1255 	return (0);
1256 }
1257 
1258 static int
1259 sndstat_prepare(struct sndstat_file *pf_self)
1260 {
1261 	struct sbuf *s = &pf_self->sbuf;
1262 	struct sndstat_entry *ent;
1263 	struct snddev_info *d;
1264 	struct sndstat_file *pf;
1265     	int k;
1266 
1267 	/* make sure buffer is reset */
1268 	sbuf_clear(s);
1269 
1270 	if (snd_verbose > 0) {
1271 		sbuf_printf(s, "FreeBSD Audio Driver (%ubit %d/%s)\n",
1272 		    (u_int)sizeof(intpcm32_t) << 3, SND_DRV_VERSION,
1273 		    MACHINE_ARCH);
1274 	}
1275 
1276 	/* generate list of installed devices */
1277 	k = 0;
1278 	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
1279 		d = device_get_softc(ent->dev);
1280 		if (!PCM_REGISTERED(d))
1281 			continue;
1282 		if (!k++)
1283 			sbuf_printf(s, "Installed devices:\n");
1284 		sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
1285 		sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
1286 		if (snd_verbose > 0)
1287 			sbuf_printf(s, " %s", ent->str);
1288 		/* XXX Need Giant magic entry ??? */
1289 		PCM_ACQUIRE_QUICK(d);
1290 		sndstat_prepare_pcm(s, ent->dev, snd_verbose);
1291 		PCM_RELEASE_QUICK(d);
1292 		sbuf_printf(s, "\n");
1293 	}
1294 	if (k == 0)
1295 		sbuf_printf(s, "No devices installed.\n");
1296 
1297 	/* append any input from userspace */
1298 	k = 0;
1299 	TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
1300 		struct sndstat_userdev *ud;
1301 
1302 		if (pf == pf_self)
1303 			continue;
1304 		sx_xlock(&pf->lock);
1305 		if (TAILQ_EMPTY(&pf->userdev_list)) {
1306 			sx_unlock(&pf->lock);
1307 			continue;
1308 		}
1309 		if (!k++)
1310 			sbuf_printf(s, "Installed devices from userspace:\n");
1311 		TAILQ_FOREACH(ud, &pf->userdev_list, link) {
1312 			const char *caps = (ud->pchan && ud->rchan) ?
1313 			    "play/rec" :
1314 			    (ud->pchan ? "play" : (ud->rchan ? "rec" : ""));
1315 			sbuf_printf(s, "%s: <%s>", ud->nameunit, ud->desc);
1316 			sbuf_printf(s, " (%s)", caps);
1317 			sbuf_printf(s, "\n");
1318 		}
1319 		sx_unlock(&pf->lock);
1320 	}
1321 	if (k == 0)
1322 		sbuf_printf(s, "No devices installed from userspace.\n");
1323 
1324 	sbuf_finish(s);
1325     	return (sbuf_len(s));
1326 }
1327 
1328 static void
1329 sndstat_sysinit(void *p)
1330 {
1331 	sx_init(&sndstat_lock, "sndstat lock");
1332 	sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
1333 	    UID_ROOT, GID_WHEEL, 0644, "sndstat");
1334 }
1335 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
1336 
1337 static void
1338 sndstat_sysuninit(void *p)
1339 {
1340 	if (sndstat_dev != NULL) {
1341 		/* destroy_dev() will wait for all references to go away */
1342 		destroy_dev(sndstat_dev);
1343 	}
1344 	sx_destroy(&sndstat_lock);
1345 }
1346 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
1347