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