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