xref: /freebsd/sys/dev/sound/pcm/sndstat.c (revision 3b68c491d37196bb76a95bce3c02f7c6d5ba22fd)
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 
49 #include <dev/sound/pcm/sound.h>
50 #include <dev/sound/pcm/pcm.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, int dir, 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 	struct pcmchan_caps *caps;
331 	int i;
332 
333 	*fmts = 0;
334 	*min_rate = UINT32_MAX;
335 	*max_rate = 0;
336 	*minchn = UINT32_MAX;
337 	*maxchn = 0;
338 
339 	CHN_FOREACH(c, d, channels.pcm) {
340 		if (c->direction != dir)
341 			continue;
342 		CHN_LOCK(c);
343 		caps = chn_getcaps(c);
344 		for (i = 0; caps->fmtlist[i]; i++) {
345 			*fmts |= AFMT_ENCODING(caps->fmtlist[i]);
346 			*minchn = min(AFMT_CHANNEL(caps->fmtlist[i]), *minchn);
347 			*maxchn = max(AFMT_CHANNEL(caps->fmtlist[i]), *maxchn);
348 		}
349 		if ((c->flags & CHN_F_EXCLUSIVE) ||
350 		    (pcm_getflags(d->dev) & SD_F_BITPERFECT)) {
351 			*min_rate = min(*min_rate, caps->minspeed);
352 			*max_rate = max(*max_rate, caps->maxspeed);
353 		} else {
354 			*min_rate = min(*min_rate, feeder_rate_min);
355 			*max_rate = max(*max_rate, feeder_rate_max);
356 		}
357 		CHN_UNLOCK(c);
358 	}
359 	if (*min_rate == UINT32_MAX)
360 		*min_rate = 0;
361 	if (*minchn == UINT32_MAX)
362 		*minchn = 0;
363 }
364 
365 static nvlist_t *
366 sndstat_create_diinfo_nv(uint32_t min_rate, uint32_t max_rate, uint32_t formats,
367 	    uint32_t min_chn, uint32_t max_chn)
368 {
369 	nvlist_t *nv;
370 
371 	nv = nvlist_create(0);
372 	if (nv == NULL)
373 		return (NULL);
374 	nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_RATE, min_rate);
375 	nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_RATE, max_rate);
376 	nvlist_add_number(nv, SNDST_DSPS_INFO_FORMATS, formats);
377 	nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_CHN, min_chn);
378 	nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_CHN, max_chn);
379 	return (nv);
380 }
381 
382 static int
383 sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
384 {
385 	struct pcm_channel *c;
386 	struct pcm_feeder *f;
387 	struct sbuf sb;
388 	uint32_t maxrate, minrate, fmts, minchn, maxchn;
389 	nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL, *cdi = NULL;
390 	int err, nchan;
391 
392 	di = nvlist_create(0);
393 	if (di == NULL) {
394 		err = ENOMEM;
395 		goto done;
396 	}
397 	sound4di = nvlist_create(0);
398 	if (sound4di == NULL) {
399 		err = ENOMEM;
400 		goto done;
401 	}
402 
403 	nvlist_add_bool(di, SNDST_DSPS_FROM_USER, false);
404 	nvlist_add_stringf(di, SNDST_DSPS_NAMEUNIT, "%s",
405 			device_get_nameunit(d->dev));
406 	nvlist_add_stringf(di, SNDST_DSPS_DEVNODE, "dsp%d",
407 			device_get_unit(d->dev));
408 	nvlist_add_string(
409 			di, SNDST_DSPS_DESC, device_get_desc(d->dev));
410 
411 	PCM_ACQUIRE_QUICK(d);
412 	nvlist_add_number(di, SNDST_DSPS_PCHAN, d->playcount);
413 	nvlist_add_number(di, SNDST_DSPS_RCHAN, d->reccount);
414 	if (d->playcount > 0) {
415 		sndstat_get_caps(d, PCMDIR_PLAY, &minrate, &maxrate, &fmts,
416 		    &minchn, &maxchn);
417 		nvlist_add_number(di, "pminrate", minrate);
418 		nvlist_add_number(di, "pmaxrate", maxrate);
419 		nvlist_add_number(di, "pfmts", fmts);
420 		diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
421 		    minchn, maxchn);
422 		if (diinfo == NULL)
423 			nvlist_set_error(di, ENOMEM);
424 		else
425 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
426 	}
427 	if (d->reccount > 0) {
428 		sndstat_get_caps(d, PCMDIR_REC, &minrate, &maxrate, &fmts,
429 		    &minchn, &maxchn);
430 		nvlist_add_number(di, "rminrate", minrate);
431 		nvlist_add_number(di, "rmaxrate", maxrate);
432 		nvlist_add_number(di, "rfmts", fmts);
433 		diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
434 		    minchn, maxchn);
435 		if (diinfo == NULL)
436 			nvlist_set_error(di, ENOMEM);
437 		else
438 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
439 	}
440 
441 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_UNIT,
442 			device_get_unit(d->dev)); // XXX: I want signed integer here
443 	nvlist_add_bool(
444 	    sound4di, SNDST_DSPS_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT);
445 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHAN, d->pvchancount);
446 	nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHAN, d->rvchancount);
447 
448 	nchan = 0;
449 	CHN_FOREACH(c, d, channels.pcm) {
450 		sbuf_new(&sb, NULL, 4096, SBUF_AUTOEXTEND);
451 		cdi = nvlist_create(0);
452 		if (cdi == NULL) {
453 			sbuf_delete(&sb);
454 			PCM_RELEASE_QUICK(d);
455 			err = ENOMEM;
456 			goto done;
457 		}
458 
459 		CHN_LOCK(c);
460 
461 		nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_NAME, c->name);
462 		nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_PARENTCHAN,
463 		    c->parentchannel != NULL ? c->parentchannel->name : "");
464 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_UNIT, nchan++);
465 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LATENCY,
466 		    c->latency);
467 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RATE, c->speed);
468 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FORMAT,
469 		    c->format);
470 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_PID, c->pid);
471 		nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_COMM, c->comm);
472 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_INTR,
473 		    c->interrupts);
474 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDCNT,
475 		    c->feedcount);
476 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_XRUNS, c->xruns);
477 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LEFTVOL,
478 		    CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL));
479 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RIGHTVOL,
480 		    CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR));
481 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FORMAT,
482 		    sndbuf_getfmt(c->bufhard));
483 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_SIZE,
484 		    sndbuf_getsize(c->bufhard));
485 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKSZ,
486 		    sndbuf_getblksz(c->bufhard));
487 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKCNT,
488 		    sndbuf_getblkcnt(c->bufhard));
489 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FREE,
490 		    sndbuf_getfree(c->bufhard));
491 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_READY,
492 		    sndbuf_getready(c->bufhard));
493 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FORMAT,
494 		    sndbuf_getfmt(c->bufsoft));
495 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_SIZE,
496 		    sndbuf_getsize(c->bufsoft));
497 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKSZ,
498 		    sndbuf_getblksz(c->bufsoft));
499 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKCNT,
500 		    sndbuf_getblkcnt(c->bufsoft));
501 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FREE,
502 		    sndbuf_getfree(c->bufsoft));
503 		nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_READY,
504 		    sndbuf_getready(c->bufsoft));
505 
506 		sbuf_printf(&sb, "[%s",
507 		    (c->direction == PCMDIR_REC) ? "hardware" : "userland");
508 		sbuf_printf(&sb, " -> ");
509 		f = c->feeder;
510 		while (f->source != NULL)
511 			f = f->source;
512 		while (f != NULL) {
513 			sbuf_printf(&sb, "%s", f->class->name);
514 			if (f->desc->type == FEEDER_FORMAT) {
515 				sbuf_printf(&sb, "(0x%08x -> 0x%08x)",
516 				    f->desc->in, f->desc->out);
517 			} else if (f->desc->type == FEEDER_MATRIX) {
518 				sbuf_printf(&sb, "(%d.%d -> %d.%d)",
519 				    AFMT_CHANNEL(f->desc->in) -
520 				    AFMT_EXTCHANNEL(f->desc->in),
521 				    AFMT_EXTCHANNEL(f->desc->in),
522 				    AFMT_CHANNEL(f->desc->out) -
523 				    AFMT_EXTCHANNEL(f->desc->out),
524 				    AFMT_EXTCHANNEL(f->desc->out));
525 			} else if (f->desc->type == FEEDER_RATE) {
526 				sbuf_printf(&sb,
527 				    "(0x%08x q:%d %d -> %d)",
528 				    f->desc->out,
529 				    FEEDER_GET(f, FEEDRATE_QUALITY),
530 				    FEEDER_GET(f, FEEDRATE_SRC),
531 				    FEEDER_GET(f, FEEDRATE_DST));
532 			} else {
533 				sbuf_printf(&sb, "(0x%08x)",
534 				    f->desc->out);
535 			}
536 			sbuf_printf(&sb, " -> ");
537 			f = f->parent;
538 		}
539 		sbuf_printf(&sb, "%s]",
540 		    (c->direction == PCMDIR_REC) ? "userland" : "hardware");
541 
542 		CHN_UNLOCK(c);
543 
544 		sbuf_finish(&sb);
545 		nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDERCHAIN,
546 		    sbuf_data(&sb));
547 		sbuf_delete(&sb);
548 
549 		nvlist_append_nvlist_array(sound4di,
550 		    SNDST_DSPS_SOUND4_CHAN_INFO, cdi);
551 		nvlist_destroy(cdi);
552 		err = nvlist_error(sound4di);
553 		if (err) {
554 			PCM_RELEASE_QUICK(d);
555 			goto done;
556 		}
557 	}
558 	nvlist_move_nvlist(di, SNDST_DSPS_PROVIDER_INFO, sound4di);
559 	sound4di = NULL;
560 
561 	PCM_RELEASE_QUICK(d);
562 	nvlist_add_string(di, SNDST_DSPS_PROVIDER, SNDST_DSPS_SOUND4_PROVIDER);
563 
564 	err = nvlist_error(di);
565 	if (err)
566 		goto done;
567 
568 	*dip = di;
569 
570 done:
571 	if (err) {
572 		nvlist_destroy(sound4di);
573 		nvlist_destroy(di);
574 	}
575 	return (err);
576 }
577 
578 static int
579 sndstat_build_userland_nvlist(struct sndstat_userdev *ud, nvlist_t **dip)
580 {
581 	nvlist_t *di, *diinfo;
582 	int err;
583 
584 	di = nvlist_create(0);
585 	if (di == NULL) {
586 		err = ENOMEM;
587 		goto done;
588 	}
589 
590 	nvlist_add_bool(di, SNDST_DSPS_FROM_USER, true);
591 	nvlist_add_number(di, SNDST_DSPS_PCHAN, ud->pchan);
592 	nvlist_add_number(di, SNDST_DSPS_RCHAN, ud->rchan);
593 	nvlist_add_string(di, SNDST_DSPS_NAMEUNIT, ud->nameunit);
594 	nvlist_add_string(
595 			di, SNDST_DSPS_DEVNODE, ud->devnode);
596 	nvlist_add_string(di, SNDST_DSPS_DESC, ud->desc);
597 	if (ud->pchan != 0) {
598 		nvlist_add_number(di, "pminrate",
599 		    ud->info_play.min_rate);
600 		nvlist_add_number(di, "pmaxrate",
601 		    ud->info_play.max_rate);
602 		nvlist_add_number(di, "pfmts",
603 		    ud->info_play.formats);
604 		diinfo = sndstat_create_diinfo_nv(ud->info_play.min_rate,
605 		    ud->info_play.max_rate, ud->info_play.formats,
606 		    ud->info_play.min_chn, ud->info_play.max_chn);
607 		if (diinfo == NULL)
608 			nvlist_set_error(di, ENOMEM);
609 		else
610 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
611 	}
612 	if (ud->rchan != 0) {
613 		nvlist_add_number(di, "rminrate",
614 		    ud->info_rec.min_rate);
615 		nvlist_add_number(di, "rmaxrate",
616 		    ud->info_rec.max_rate);
617 		nvlist_add_number(di, "rfmts",
618 		    ud->info_rec.formats);
619 		diinfo = sndstat_create_diinfo_nv(ud->info_rec.min_rate,
620 		    ud->info_rec.max_rate, ud->info_rec.formats,
621 		    ud->info_rec.min_chn, ud->info_rec.max_chn);
622 		if (diinfo == NULL)
623 			nvlist_set_error(di, ENOMEM);
624 		else
625 			nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
626 	}
627 	nvlist_add_string(di, SNDST_DSPS_PROVIDER,
628 	    (ud->provider != NULL) ? ud->provider : "");
629 	if (ud->provider_nvl != NULL)
630 		nvlist_add_nvlist(
631 		    di, SNDST_DSPS_PROVIDER_INFO, ud->provider_nvl);
632 
633 	err = nvlist_error(di);
634 	if (err)
635 		goto done;
636 
637 	*dip = di;
638 
639 done:
640 	if (err)
641 		nvlist_destroy(di);
642 	return (err);
643 }
644 
645 /*
646  * Should only be called with the following locks held:
647  * * sndstat_lock
648  */
649 static int
650 sndstat_create_devs_nvlist(nvlist_t **nvlp)
651 {
652 	int err;
653 	nvlist_t *nvl;
654 	struct sndstat_entry *ent;
655 	struct sndstat_file *pf;
656 
657 	nvl = nvlist_create(0);
658 	if (nvl == NULL)
659 		return (ENOMEM);
660 
661 	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
662 		struct snddev_info *d;
663 		nvlist_t *di;
664 
665 		d = device_get_softc(ent->dev);
666 		if (!PCM_REGISTERED(d))
667 			continue;
668 
669 		err = sndstat_build_sound4_nvlist(d, &di);
670 		if (err)
671 			goto done;
672 
673 		nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
674 		nvlist_destroy(di);
675 		err = nvlist_error(nvl);
676 		if (err)
677 			goto done;
678 	}
679 
680 	TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
681 		struct sndstat_userdev *ud;
682 
683 		sx_xlock(&pf->lock);
684 
685 		TAILQ_FOREACH(ud, &pf->userdev_list, link) {
686 			nvlist_t *di;
687 
688 			err = sndstat_build_userland_nvlist(ud, &di);
689 			if (err != 0) {
690 				sx_xunlock(&pf->lock);
691 				goto done;
692 			}
693 			nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
694 			nvlist_destroy(di);
695 
696 			err = nvlist_error(nvl);
697 			if (err != 0) {
698 				sx_xunlock(&pf->lock);
699 				goto done;
700 			}
701 		}
702 
703 		sx_xunlock(&pf->lock);
704 	}
705 
706 	*nvlp = nvl;
707 
708 done:
709 	if (err != 0)
710 		nvlist_destroy(nvl);
711 	return (err);
712 }
713 
714 static int
715 sndstat_refresh_devs(struct sndstat_file *pf)
716 {
717 	sx_xlock(&pf->lock);
718 	free(pf->devs_nvlbuf, M_NVLIST);
719 	pf->devs_nvlbuf = NULL;
720 	pf->devs_nbytes = 0;
721 	sx_unlock(&pf->lock);
722 
723 	return (0);
724 }
725 
726 static int
727 sndstat_get_devs(struct sndstat_file *pf, void *arg_buf, size_t *arg_nbytes)
728 {
729 	int err;
730 
731 	SNDSTAT_LOCK();
732 	sx_xlock(&pf->lock);
733 
734 	if (pf->devs_nvlbuf == NULL) {
735 		nvlist_t *nvl;
736 		void *nvlbuf;
737 		size_t nbytes;
738 		int err;
739 
740 		sx_xunlock(&pf->lock);
741 
742 		err = sndstat_create_devs_nvlist(&nvl);
743 		if (err) {
744 			SNDSTAT_UNLOCK();
745 			return (err);
746 		}
747 
748 		sx_xlock(&pf->lock);
749 
750 		nvlbuf = nvlist_pack(nvl, &nbytes);
751 		err = nvlist_error(nvl);
752 		nvlist_destroy(nvl);
753 		if (nvlbuf == NULL || err != 0) {
754 			SNDSTAT_UNLOCK();
755 			sx_xunlock(&pf->lock);
756 			if (err == 0)
757 				return (ENOMEM);
758 			return (err);
759 		}
760 
761 		free(pf->devs_nvlbuf, M_NVLIST);
762 		pf->devs_nvlbuf = nvlbuf;
763 		pf->devs_nbytes = nbytes;
764 	}
765 
766 	SNDSTAT_UNLOCK();
767 
768 	if (*arg_nbytes == 0) {
769 		*arg_nbytes = pf->devs_nbytes;
770 		err = 0;
771 		goto done;
772 	}
773 	if (*arg_nbytes < pf->devs_nbytes) {
774 		*arg_nbytes = 0;
775 		err = 0;
776 		goto done;
777 	}
778 
779 	err = copyout(pf->devs_nvlbuf, arg_buf, pf->devs_nbytes);
780 	if (err)
781 		goto done;
782 
783 	*arg_nbytes = pf->devs_nbytes;
784 
785 	free(pf->devs_nvlbuf, M_NVLIST);
786 	pf->devs_nvlbuf = NULL;
787 	pf->devs_nbytes = 0;
788 
789 done:
790 	sx_unlock(&pf->lock);
791 	return (err);
792 }
793 
794 static int
795 sndstat_unpack_user_nvlbuf(const void *unvlbuf, size_t nbytes, nvlist_t **nvl)
796 {
797 	void *nvlbuf;
798 	int err;
799 
800 	nvlbuf = malloc(nbytes, M_DEVBUF, M_WAITOK);
801 	err = copyin(unvlbuf, nvlbuf, nbytes);
802 	if (err != 0) {
803 		free(nvlbuf, M_DEVBUF);
804 		return (err);
805 	}
806 	*nvl = nvlist_unpack(nvlbuf, nbytes, 0);
807 	free(nvlbuf, M_DEVBUF);
808 	if (*nvl == NULL) {
809 		return (EINVAL);
810 	}
811 
812 	return (0);
813 }
814 
815 static bool
816 sndstat_diinfo_is_sane(const nvlist_t *diinfo)
817 {
818 	if (!(nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_RATE) &&
819 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_RATE) &&
820 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_FORMATS) &&
821 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_CHN) &&
822 	    nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_CHN)))
823 		return (false);
824 	return (true);
825 }
826 
827 static bool
828 sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist)
829 {
830 	if (!(nvlist_exists_string(nvlist, SNDST_DSPS_DEVNODE) &&
831 	    nvlist_exists_string(nvlist, SNDST_DSPS_DESC) &&
832 	    nvlist_exists_number(nvlist, SNDST_DSPS_PCHAN) &&
833 	    nvlist_exists_number(nvlist, SNDST_DSPS_RCHAN)))
834 		return (false);
835 
836 	if (nvlist_get_number(nvlist, SNDST_DSPS_PCHAN) > 0) {
837 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
838 			if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
839 			    SNDST_DSPS_INFO_PLAY)))
840 				return (false);
841 		} else if (!(nvlist_exists_number(nvlist, "pminrate") &&
842 		    nvlist_exists_number(nvlist, "pmaxrate") &&
843 		    nvlist_exists_number(nvlist, "pfmts")))
844 			return (false);
845 	}
846 
847 	if (nvlist_get_number(nvlist, SNDST_DSPS_RCHAN) > 0) {
848 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
849 			if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
850 			    SNDST_DSPS_INFO_REC)))
851 				return (false);
852 		} else if (!(nvlist_exists_number(nvlist, "rminrate") &&
853 		    nvlist_exists_number(nvlist, "rmaxrate") &&
854 		    nvlist_exists_number(nvlist, "rfmts")))
855 			return (false);
856 	}
857 
858 	return (true);
859 
860 }
861 
862 static void
863 sndstat_get_diinfo_nv(const nvlist_t *nv, uint32_t *min_rate,
864 	    uint32_t *max_rate, uint32_t *formats, uint32_t *min_chn,
865 	    uint32_t *max_chn)
866 {
867 	*min_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_RATE);
868 	*max_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_RATE);
869 	*formats = nvlist_get_number(nv, SNDST_DSPS_INFO_FORMATS);
870 	*min_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_CHN);
871 	*max_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_CHN);
872 }
873 
874 static int
875 sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud)
876 {
877 	const char *nameunit, *devnode, *desc;
878 	unsigned int pchan, rchan;
879 	uint32_t pminrate = 0, pmaxrate = 0;
880 	uint32_t rminrate = 0, rmaxrate = 0;
881 	uint32_t pfmts = 0, rfmts = 0;
882 	uint32_t pminchn = 0, pmaxchn = 0;
883 	uint32_t rminchn = 0, rmaxchn = 0;
884 	nvlist_t *provider_nvl = NULL;
885 	const nvlist_t *diinfo;
886 	const char *provider;
887 
888 	devnode = nvlist_get_string(nvlist, SNDST_DSPS_DEVNODE);
889 	if (nvlist_exists_string(nvlist, SNDST_DSPS_NAMEUNIT))
890 		nameunit = nvlist_get_string(nvlist, SNDST_DSPS_NAMEUNIT);
891 	else
892 		nameunit = devnode;
893 	desc = nvlist_get_string(nvlist, SNDST_DSPS_DESC);
894 	pchan = nvlist_get_number(nvlist, SNDST_DSPS_PCHAN);
895 	rchan = nvlist_get_number(nvlist, SNDST_DSPS_RCHAN);
896 	if (pchan != 0) {
897 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
898 			diinfo = nvlist_get_nvlist(nvlist,
899 			    SNDST_DSPS_INFO_PLAY);
900 			sndstat_get_diinfo_nv(diinfo, &pminrate, &pmaxrate,
901 			    &pfmts, &pminchn, &pmaxchn);
902 		} else {
903 			pminrate = nvlist_get_number(nvlist, "pminrate");
904 			pmaxrate = nvlist_get_number(nvlist, "pmaxrate");
905 			pfmts = nvlist_get_number(nvlist, "pfmts");
906 		}
907 	}
908 	if (rchan != 0) {
909 		if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
910 			diinfo = nvlist_get_nvlist(nvlist,
911 			    SNDST_DSPS_INFO_REC);
912 			sndstat_get_diinfo_nv(diinfo, &rminrate, &rmaxrate,
913 			    &rfmts, &rminchn, &rmaxchn);
914 		} else {
915 			rminrate = nvlist_get_number(nvlist, "rminrate");
916 			rmaxrate = nvlist_get_number(nvlist, "rmaxrate");
917 			rfmts = nvlist_get_number(nvlist, "rfmts");
918 		}
919 	}
920 
921 	provider = dnvlist_get_string(nvlist, SNDST_DSPS_PROVIDER, "");
922 	if (provider[0] == '\0')
923 		provider = NULL;
924 
925 	if (provider != NULL &&
926 	    nvlist_exists_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)) {
927 		provider_nvl = nvlist_clone(
928 		    nvlist_get_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO));
929 		if (provider_nvl == NULL)
930 			return (ENOMEM);
931 	}
932 
933 	ud->provider = (provider != NULL) ? strdup(provider, M_DEVBUF) : NULL;
934 	ud->devnode = strdup(devnode, M_DEVBUF);
935 	ud->nameunit = strdup(nameunit, M_DEVBUF);
936 	ud->desc = strdup(desc, M_DEVBUF);
937 	ud->pchan = pchan;
938 	ud->rchan = rchan;
939 	ud->info_play.min_rate = pminrate;
940 	ud->info_play.max_rate = pmaxrate;
941 	ud->info_play.formats = pfmts;
942 	ud->info_play.min_chn = pminchn;
943 	ud->info_play.max_chn = pmaxchn;
944 	ud->info_rec.min_rate = rminrate;
945 	ud->info_rec.max_rate = rmaxrate;
946 	ud->info_rec.formats = rfmts;
947 	ud->info_rec.min_chn = rminchn;
948 	ud->info_rec.max_chn = rmaxchn;
949 	ud->provider_nvl = provider_nvl;
950 	return (0);
951 }
952 
953 static int
954 sndstat_add_user_devs(struct sndstat_file *pf, void *nvlbuf, size_t nbytes)
955 {
956 	int err;
957 	nvlist_t *nvl = NULL;
958 	const nvlist_t * const *dsps;
959 	size_t i, ndsps;
960 
961 	if ((pf->fflags & FWRITE) == 0) {
962 		err = EPERM;
963 		goto done;
964 	}
965 
966 	if (nbytes > SNDST_UNVLBUF_MAX) {
967 		err = ENOMEM;
968 		goto done;
969 	}
970 
971 	err = sndstat_unpack_user_nvlbuf(nvlbuf, nbytes, &nvl);
972 	if (err != 0)
973 		goto done;
974 
975 	if (!nvlist_exists_nvlist_array(nvl, SNDST_DSPS)) {
976 		err = EINVAL;
977 		goto done;
978 	}
979 	dsps = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &ndsps);
980 	for (i = 0; i < ndsps; i++) {
981 		if (!sndstat_dsp_nvlist_is_sane(dsps[i])) {
982 			err = EINVAL;
983 			goto done;
984 		}
985 	}
986 	sx_xlock(&pf->lock);
987 	for (i = 0; i < ndsps; i++) {
988 		struct sndstat_userdev *ud =
989 		    malloc(sizeof(*ud), M_DEVBUF, M_WAITOK);
990 		err = sndstat_dsp_unpack_nvlist(dsps[i], ud);
991 		if (err) {
992 			sx_unlock(&pf->lock);
993 			goto done;
994 		}
995 		TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link);
996 	}
997 	sx_unlock(&pf->lock);
998 
999 done:
1000 	nvlist_destroy(nvl);
1001 	return (err);
1002 }
1003 
1004 static int
1005 sndstat_flush_user_devs(struct sndstat_file *pf)
1006 {
1007 	if ((pf->fflags & FWRITE) == 0)
1008 		return (EPERM);
1009 
1010 	sx_xlock(&pf->lock);
1011 	sndstat_remove_all_userdevs(pf);
1012 	sx_xunlock(&pf->lock);
1013 
1014 	return (0);
1015 }
1016 
1017 static int
1018 sndstat_ioctl(
1019     struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td)
1020 {
1021 	int err;
1022 	struct sndstat_file *pf;
1023 	struct sndstioc_nv_arg *arg;
1024 #ifdef COMPAT_FREEBSD32
1025 	struct sndstioc_nv_arg32 *arg32;
1026 	size_t nbytes;
1027 #endif
1028 
1029 	err = devfs_get_cdevpriv((void **)&pf);
1030 	if (err != 0)
1031 		return (err);
1032 
1033 	switch (cmd) {
1034 	case SNDSTIOC_GET_DEVS:
1035 		arg = (struct sndstioc_nv_arg *)data;
1036 		err = sndstat_get_devs(pf, arg->buf, &arg->nbytes);
1037 		break;
1038 #ifdef COMPAT_FREEBSD32
1039 	case SNDSTIOC_GET_DEVS32:
1040 		arg32 = (struct sndstioc_nv_arg32 *)data;
1041 		nbytes = arg32->nbytes;
1042 		err = sndstat_get_devs(pf, (void *)(uintptr_t)arg32->buf,
1043 		    &nbytes);
1044 		if (err == 0) {
1045 			KASSERT(nbytes < UINT_MAX, ("impossibly many bytes"));
1046 			arg32->nbytes = nbytes;
1047 		}
1048 		break;
1049 #endif
1050 	case SNDSTIOC_ADD_USER_DEVS:
1051 		arg = (struct sndstioc_nv_arg *)data;
1052 		err = sndstat_add_user_devs(pf, arg->buf, arg->nbytes);
1053 		break;
1054 #ifdef COMPAT_FREEBSD32
1055 	case SNDSTIOC_ADD_USER_DEVS32:
1056 		arg32 = (struct sndstioc_nv_arg32 *)data;
1057 		err = sndstat_add_user_devs(pf, (void *)(uintptr_t)arg32->buf,
1058 		    arg32->nbytes);
1059 		break;
1060 #endif
1061 	case SNDSTIOC_REFRESH_DEVS:
1062 		err = sndstat_refresh_devs(pf);
1063 		break;
1064 	case SNDSTIOC_FLUSH_USER_DEVS:
1065 		err = sndstat_flush_user_devs(pf);
1066 		break;
1067 	default:
1068 		err = ENODEV;
1069 	}
1070 
1071 	return (err);
1072 }
1073 
1074 static struct sndstat_userdev *
1075 sndstat_line2userdev(struct sndstat_file *pf, const char *line, int n)
1076 {
1077 	struct sndstat_userdev *ud;
1078 	const char *e, *m;
1079 
1080 	ud = malloc(sizeof(*ud), M_DEVBUF, M_WAITOK|M_ZERO);
1081 
1082 	ud->provider = NULL;
1083 	ud->provider_nvl = NULL;
1084 	e = strchr(line, ':');
1085 	if (e == NULL)
1086 		goto fail;
1087 	ud->nameunit = strndup(line, e - line, M_DEVBUF);
1088 	ud->devnode = (char *)malloc(e - line + 1, M_DEVBUF, M_WAITOK | M_ZERO);
1089 	strlcat(ud->devnode, ud->nameunit, e - line + 1);
1090 	line = e + 1;
1091 
1092 	e = strchr(line, '<');
1093 	if (e == NULL)
1094 		goto fail;
1095 	line = e + 1;
1096 	e = strrchr(line, '>');
1097 	if (e == NULL)
1098 		goto fail;
1099 	ud->desc = strndup(line, e - line, M_DEVBUF);
1100 	line = e + 1;
1101 
1102 	e = strchr(line, '(');
1103 	if (e == NULL)
1104 		goto fail;
1105 	line = e + 1;
1106 	e = strrchr(line, ')');
1107 	if (e == NULL)
1108 		goto fail;
1109 	m = strstr(line, "play");
1110 	if (m != NULL && m < e)
1111 		ud->pchan = 1;
1112 	m = strstr(line, "rec");
1113 	if (m != NULL && m < e)
1114 		ud->rchan = 1;
1115 
1116 	return (ud);
1117 
1118 fail:
1119 	free(ud->nameunit, M_DEVBUF);
1120 	free(ud->devnode, M_DEVBUF);
1121 	free(ud->desc, M_DEVBUF);
1122 	free(ud, M_DEVBUF);
1123 	return (NULL);
1124 }
1125 
1126 /************************************************************************/
1127 
1128 int
1129 sndstat_register(device_t dev, char *str)
1130 {
1131 	struct sndstat_entry *ent;
1132 	struct sndstat_entry *pre;
1133 	const char *devtype;
1134 	int type, unit;
1135 
1136 	unit = device_get_unit(dev);
1137 	devtype = device_get_name(dev);
1138 	if (!strcmp(devtype, "pcm"))
1139 		type = SS_TYPE_PCM;
1140 	else if (!strcmp(devtype, "midi"))
1141 		type = SS_TYPE_MIDI;
1142 	else if (!strcmp(devtype, "sequencer"))
1143 		type = SS_TYPE_SEQUENCER;
1144 	else
1145 		return (EINVAL);
1146 
1147 	ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
1148 	ent->dev = dev;
1149 	ent->str = str;
1150 	ent->type = type;
1151 	ent->unit = unit;
1152 
1153 	SNDSTAT_LOCK();
1154 	/* sorted list insertion */
1155 	TAILQ_FOREACH(pre, &sndstat_devlist, link) {
1156 		if (pre->unit > unit)
1157 			break;
1158 		else if (pre->unit < unit)
1159 			continue;
1160 		if (pre->type > type)
1161 			break;
1162 		else if (pre->type < unit)
1163 			continue;
1164 	}
1165 	if (pre == NULL) {
1166 		TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link);
1167 	} else {
1168 		TAILQ_INSERT_BEFORE(pre, ent, link);
1169 	}
1170 	SNDSTAT_UNLOCK();
1171 
1172 	return (0);
1173 }
1174 
1175 int
1176 sndstat_unregister(device_t dev)
1177 {
1178 	struct sndstat_entry *ent;
1179 	int error = ENXIO;
1180 
1181 	SNDSTAT_LOCK();
1182 	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
1183 		if (ent->dev == dev) {
1184 			TAILQ_REMOVE(&sndstat_devlist, ent, link);
1185 			free(ent, M_DEVBUF);
1186 			error = 0;
1187 			break;
1188 		}
1189 	}
1190 	SNDSTAT_UNLOCK();
1191 
1192 	return (error);
1193 }
1194 
1195 /************************************************************************/
1196 
1197 static int
1198 sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose)
1199 {
1200 	struct snddev_info *d;
1201 	struct pcm_channel *c;
1202 	struct pcm_feeder *f;
1203 
1204 	d = device_get_softc(dev);
1205 	PCM_BUSYASSERT(d);
1206 
1207 	if (CHN_EMPTY(d, channels.pcm)) {
1208 		sbuf_printf(s, " (mixer only)");
1209 		return (0);
1210 	}
1211 
1212 	if (verbose < 1) {
1213 		sbuf_printf(s, " (%s%s%s",
1214 		    d->playcount ? "play" : "",
1215 		    (d->playcount && d->reccount) ? "/" : "",
1216 		    d->reccount ? "rec" : "");
1217 	} else {
1218 		sbuf_printf(s, " (%dp:%dv/%dr:%dv",
1219 		    d->playcount, d->pvchancount,
1220 		    d->reccount, d->rvchancount);
1221 	}
1222 	sbuf_printf(s, "%s)%s",
1223 	    ((d->playcount != 0 && d->reccount != 0) &&
1224 	    (d->flags & SD_F_SIMPLEX)) ? " simplex" : "",
1225 	    (device_get_unit(dev) == snd_unit) ? " default" : "");
1226 
1227 	if (verbose <= 1)
1228 		return (0);
1229 
1230 	sbuf_printf(s, "\n\t");
1231 	sbuf_printf(s, "snddev flags=0x%b", d->flags, SD_F_BITS);
1232 
1233 	CHN_FOREACH(c, d, channels.pcm) {
1234 		KASSERT(c->bufhard != NULL && c->bufsoft != NULL,
1235 		    ("hosed pcm channel setup"));
1236 
1237 		CHN_LOCK(c);
1238 
1239 		sbuf_printf(s, "\n\t");
1240 
1241 		sbuf_printf(s, "%s[%s]: ",
1242 		    (c->parentchannel != NULL) ?
1243 		    c->parentchannel->name : "", c->name);
1244 		sbuf_printf(s, "spd %d", c->speed);
1245 		if (c->speed != sndbuf_getspd(c->bufhard)) {
1246 			sbuf_printf(s, "/%d",
1247 			    sndbuf_getspd(c->bufhard));
1248 		}
1249 		sbuf_printf(s, ", fmt 0x%08x", c->format);
1250 		if (c->format != sndbuf_getfmt(c->bufhard)) {
1251 			sbuf_printf(s, "/0x%08x",
1252 			    sndbuf_getfmt(c->bufhard));
1253 		}
1254 		sbuf_printf(s, ", flags 0x%08x, 0x%08x",
1255 		    c->flags, c->feederflags);
1256 		if (c->pid != -1) {
1257 			sbuf_printf(s, ", pid %d (%s)",
1258 			    c->pid, c->comm);
1259 		}
1260 		sbuf_printf(s, "\n\t");
1261 
1262 		sbuf_printf(s, "interrupts %d, ", c->interrupts);
1263 
1264 		if (c->direction == PCMDIR_REC)	{
1265 			sbuf_printf(s,
1266 			    "overruns %d, feed %u, hfree %d, "
1267 			    "sfree %d [b:%d/%d/%d|bs:%d/%d/%d]",
1268 				c->xruns, c->feedcount,
1269 				sndbuf_getfree(c->bufhard),
1270 				sndbuf_getfree(c->bufsoft),
1271 				sndbuf_getsize(c->bufhard),
1272 				sndbuf_getblksz(c->bufhard),
1273 				sndbuf_getblkcnt(c->bufhard),
1274 				sndbuf_getsize(c->bufsoft),
1275 				sndbuf_getblksz(c->bufsoft),
1276 				sndbuf_getblkcnt(c->bufsoft));
1277 		} else {
1278 			sbuf_printf(s,
1279 			    "underruns %d, feed %u, ready %d "
1280 			    "[b:%d/%d/%d|bs:%d/%d/%d]",
1281 				c->xruns, c->feedcount,
1282 				sndbuf_getready(c->bufsoft),
1283 				sndbuf_getsize(c->bufhard),
1284 				sndbuf_getblksz(c->bufhard),
1285 				sndbuf_getblkcnt(c->bufhard),
1286 				sndbuf_getsize(c->bufsoft),
1287 				sndbuf_getblksz(c->bufsoft),
1288 				sndbuf_getblkcnt(c->bufsoft));
1289 		}
1290 		sbuf_printf(s, "\n\t");
1291 
1292 		sbuf_printf(s, "channel flags=0x%b", c->flags, CHN_F_BITS);
1293 		sbuf_printf(s, "\n\t");
1294 
1295 		sbuf_printf(s, "{%s}",
1296 		    (c->direction == PCMDIR_REC) ? "hardware" : "userland");
1297 		sbuf_printf(s, " -> ");
1298 		f = c->feeder;
1299 		while (f->source != NULL)
1300 			f = f->source;
1301 		while (f != NULL) {
1302 			sbuf_printf(s, "%s", f->class->name);
1303 			if (f->desc->type == FEEDER_FORMAT) {
1304 				sbuf_printf(s, "(0x%08x -> 0x%08x)",
1305 				    f->desc->in, f->desc->out);
1306 			} else if (f->desc->type == FEEDER_MATRIX) {
1307 				sbuf_printf(s, "(%d.%d -> %d.%d)",
1308 				    AFMT_CHANNEL(f->desc->in) -
1309 				    AFMT_EXTCHANNEL(f->desc->in),
1310 				    AFMT_EXTCHANNEL(f->desc->in),
1311 				    AFMT_CHANNEL(f->desc->out) -
1312 				    AFMT_EXTCHANNEL(f->desc->out),
1313 				    AFMT_EXTCHANNEL(f->desc->out));
1314 			} else if (f->desc->type == FEEDER_RATE) {
1315 				sbuf_printf(s,
1316 				    "(0x%08x q:%d %d -> %d)",
1317 				    f->desc->out,
1318 				    FEEDER_GET(f, FEEDRATE_QUALITY),
1319 				    FEEDER_GET(f, FEEDRATE_SRC),
1320 				    FEEDER_GET(f, FEEDRATE_DST));
1321 			} else {
1322 				sbuf_printf(s, "(0x%08x)",
1323 				    f->desc->out);
1324 			}
1325 			sbuf_printf(s, " -> ");
1326 			f = f->parent;
1327 		}
1328 		sbuf_printf(s, "{%s}",
1329 		    (c->direction == PCMDIR_REC) ? "userland" : "hardware");
1330 
1331 		CHN_UNLOCK(c);
1332 	}
1333 
1334 	return (0);
1335 }
1336 
1337 static int
1338 sndstat_prepare(struct sndstat_file *pf_self)
1339 {
1340 	struct sbuf *s = &pf_self->sbuf;
1341 	struct sndstat_entry *ent;
1342 	struct snddev_info *d;
1343 	struct sndstat_file *pf;
1344     	int k;
1345 
1346 	/* make sure buffer is reset */
1347 	sbuf_clear(s);
1348 
1349 	if (snd_verbose > 0)
1350 		sbuf_printf(s, "FreeBSD Audio Driver\n");
1351 
1352 	/* generate list of installed devices */
1353 	k = 0;
1354 	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
1355 		d = device_get_softc(ent->dev);
1356 		if (!PCM_REGISTERED(d))
1357 			continue;
1358 		if (!k++)
1359 			sbuf_printf(s, "Installed devices:\n");
1360 		sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
1361 		sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
1362 		if (snd_verbose > 0)
1363 			sbuf_printf(s, " %s", ent->str);
1364 		/* XXX Need Giant magic entry ??? */
1365 		PCM_ACQUIRE_QUICK(d);
1366 		sndstat_prepare_pcm(s, ent->dev, snd_verbose);
1367 		PCM_RELEASE_QUICK(d);
1368 		sbuf_printf(s, "\n");
1369 	}
1370 	if (k == 0)
1371 		sbuf_printf(s, "No devices installed.\n");
1372 
1373 	/* append any input from userspace */
1374 	k = 0;
1375 	TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
1376 		struct sndstat_userdev *ud;
1377 
1378 		if (pf == pf_self)
1379 			continue;
1380 		sx_xlock(&pf->lock);
1381 		if (TAILQ_EMPTY(&pf->userdev_list)) {
1382 			sx_unlock(&pf->lock);
1383 			continue;
1384 		}
1385 		if (!k++)
1386 			sbuf_printf(s, "Installed devices from userspace:\n");
1387 		TAILQ_FOREACH(ud, &pf->userdev_list, link) {
1388 			const char *caps = (ud->pchan && ud->rchan) ?
1389 			    "play/rec" :
1390 			    (ud->pchan ? "play" : (ud->rchan ? "rec" : ""));
1391 			sbuf_printf(s, "%s: <%s>", ud->nameunit, ud->desc);
1392 			sbuf_printf(s, " (%s)", caps);
1393 			sbuf_printf(s, "\n");
1394 		}
1395 		sx_unlock(&pf->lock);
1396 	}
1397 	if (k == 0)
1398 		sbuf_printf(s, "No devices installed from userspace.\n");
1399 
1400 	sbuf_finish(s);
1401     	return (sbuf_len(s));
1402 }
1403 
1404 static void
1405 sndstat_sysinit(void *p)
1406 {
1407 	sx_init(&sndstat_lock, "sndstat lock");
1408 	sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
1409 	    UID_ROOT, GID_WHEEL, 0644, "sndstat");
1410 }
1411 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
1412 
1413 static void
1414 sndstat_sysuninit(void *p)
1415 {
1416 	if (sndstat_dev != NULL) {
1417 		/* destroy_dev() will wait for all references to go away */
1418 		destroy_dev(sndstat_dev);
1419 	}
1420 	sx_destroy(&sndstat_lock);
1421 }
1422 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
1423