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