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