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