]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/sound/pcm/sndstat.c
Merge libcxxrt master fd484be8d1e94a1fcf6bc5c67e5c07b65ada19b6
[FreeBSD/FreeBSD.git] / sys / dev / sound / pcm / sndstat.c
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);