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