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