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