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