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