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