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