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