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