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