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