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