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_bool(sound4di, SNDST_DSPS_SOUND4_PVCHAN, 444 d->flags & SD_F_PVCHANS); 445 nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHANRATE, 446 d->pvchanrate); 447 nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHANFORMAT, 448 d->pvchanformat); 449 nvlist_add_bool(sound4di, SNDST_DSPS_SOUND4_RVCHAN, 450 d->flags & SD_F_RVCHANS); 451 nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHANRATE, 452 d->rvchanrate); 453 nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHANFORMAT, 454 d->rvchanformat); 455 456 nchan = 0; 457 CHN_FOREACH(c, d, channels.pcm) { 458 sbuf_new(&sb, NULL, 4096, SBUF_AUTOEXTEND); 459 cdi = nvlist_create(0); 460 if (cdi == NULL) { 461 sbuf_delete(&sb); 462 PCM_RELEASE_QUICK(d); 463 err = ENOMEM; 464 goto done; 465 } 466 467 CHN_LOCK(c); 468 469 caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER | 470 ((c->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | 471 ((c->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); 472 473 nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_NAME, c->name); 474 nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_PARENTCHAN, 475 c->parentchannel != NULL ? c->parentchannel->name : ""); 476 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_UNIT, nchan++); 477 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_CAPS, caps); 478 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LATENCY, 479 c->latency); 480 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RATE, c->speed); 481 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FORMAT, 482 c->format); 483 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_PID, c->pid); 484 nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_COMM, c->comm); 485 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_INTR, 486 c->interrupts); 487 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDCNT, 488 c->feedcount); 489 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_XRUNS, c->xruns); 490 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LEFTVOL, 491 CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL)); 492 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RIGHTVOL, 493 CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR)); 494 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FORMAT, 495 sndbuf_getfmt(c->bufhard)); 496 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_RATE, 497 sndbuf_getspd(c->bufhard)); 498 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_SIZE, 499 sndbuf_getsize(c->bufhard)); 500 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKSZ, 501 sndbuf_getblksz(c->bufhard)); 502 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKCNT, 503 sndbuf_getblkcnt(c->bufhard)); 504 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FREE, 505 sndbuf_getfree(c->bufhard)); 506 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_READY, 507 sndbuf_getready(c->bufhard)); 508 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FORMAT, 509 sndbuf_getfmt(c->bufsoft)); 510 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_RATE, 511 sndbuf_getspd(c->bufsoft)); 512 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_SIZE, 513 sndbuf_getsize(c->bufsoft)); 514 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKSZ, 515 sndbuf_getblksz(c->bufsoft)); 516 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKCNT, 517 sndbuf_getblkcnt(c->bufsoft)); 518 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FREE, 519 sndbuf_getfree(c->bufsoft)); 520 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_READY, 521 sndbuf_getready(c->bufsoft)); 522 523 if (c->parentchannel != NULL) { 524 sbuf_printf(&sb, "[%s", (c->direction == PCMDIR_REC) ? 525 c->parentchannel->name : "userland"); 526 } else { 527 sbuf_printf(&sb, "[%s", (c->direction == PCMDIR_REC) ? 528 "hardware" : "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 "userland" : "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 struct snddev_info *d; 689 nvlist_t *di; 690 691 d = device_get_softc(ent->dev); 692 if (!PCM_REGISTERED(d)) 693 continue; 694 695 err = sndstat_build_sound4_nvlist(d, &di); 696 if (err) 697 goto done; 698 699 nvlist_append_nvlist_array(nvl, SNDST_DSPS, di); 700 nvlist_destroy(di); 701 err = nvlist_error(nvl); 702 if (err) 703 goto done; 704 } 705 706 TAILQ_FOREACH(pf, &sndstat_filelist, entry) { 707 struct sndstat_userdev *ud; 708 709 sx_xlock(&pf->lock); 710 711 TAILQ_FOREACH(ud, &pf->userdev_list, link) { 712 nvlist_t *di; 713 714 err = sndstat_build_userland_nvlist(ud, &di); 715 if (err != 0) { 716 sx_xunlock(&pf->lock); 717 goto done; 718 } 719 nvlist_append_nvlist_array(nvl, SNDST_DSPS, di); 720 nvlist_destroy(di); 721 722 err = nvlist_error(nvl); 723 if (err != 0) { 724 sx_xunlock(&pf->lock); 725 goto done; 726 } 727 } 728 729 sx_xunlock(&pf->lock); 730 } 731 732 *nvlp = nvl; 733 734 done: 735 if (err != 0) 736 nvlist_destroy(nvl); 737 return (err); 738 } 739 740 static int 741 sndstat_refresh_devs(struct sndstat_file *pf) 742 { 743 sx_xlock(&pf->lock); 744 free(pf->devs_nvlbuf, M_NVLIST); 745 pf->devs_nvlbuf = NULL; 746 pf->devs_nbytes = 0; 747 sx_unlock(&pf->lock); 748 749 return (0); 750 } 751 752 static int 753 sndstat_get_devs(struct sndstat_file *pf, void *arg_buf, size_t *arg_nbytes) 754 { 755 int err; 756 757 SNDSTAT_LOCK(); 758 sx_xlock(&pf->lock); 759 760 if (pf->devs_nvlbuf == NULL) { 761 nvlist_t *nvl; 762 void *nvlbuf; 763 size_t nbytes; 764 int err; 765 766 sx_xunlock(&pf->lock); 767 768 err = sndstat_create_devs_nvlist(&nvl); 769 if (err) { 770 SNDSTAT_UNLOCK(); 771 return (err); 772 } 773 774 sx_xlock(&pf->lock); 775 776 nvlbuf = nvlist_pack(nvl, &nbytes); 777 err = nvlist_error(nvl); 778 nvlist_destroy(nvl); 779 if (nvlbuf == NULL || err != 0) { 780 SNDSTAT_UNLOCK(); 781 sx_xunlock(&pf->lock); 782 if (err == 0) 783 return (ENOMEM); 784 return (err); 785 } 786 787 free(pf->devs_nvlbuf, M_NVLIST); 788 pf->devs_nvlbuf = nvlbuf; 789 pf->devs_nbytes = nbytes; 790 } 791 792 SNDSTAT_UNLOCK(); 793 794 if (*arg_nbytes == 0) { 795 *arg_nbytes = pf->devs_nbytes; 796 err = 0; 797 goto done; 798 } 799 if (*arg_nbytes < pf->devs_nbytes) { 800 *arg_nbytes = 0; 801 err = 0; 802 goto done; 803 } 804 805 err = copyout(pf->devs_nvlbuf, arg_buf, pf->devs_nbytes); 806 if (err) 807 goto done; 808 809 *arg_nbytes = pf->devs_nbytes; 810 811 free(pf->devs_nvlbuf, M_NVLIST); 812 pf->devs_nvlbuf = NULL; 813 pf->devs_nbytes = 0; 814 815 done: 816 sx_unlock(&pf->lock); 817 return (err); 818 } 819 820 static int 821 sndstat_unpack_user_nvlbuf(const void *unvlbuf, size_t nbytes, nvlist_t **nvl) 822 { 823 void *nvlbuf; 824 int err; 825 826 nvlbuf = malloc(nbytes, M_DEVBUF, M_WAITOK); 827 err = copyin(unvlbuf, nvlbuf, nbytes); 828 if (err != 0) { 829 free(nvlbuf, M_DEVBUF); 830 return (err); 831 } 832 *nvl = nvlist_unpack(nvlbuf, nbytes, 0); 833 free(nvlbuf, M_DEVBUF); 834 if (*nvl == NULL) { 835 return (EINVAL); 836 } 837 838 return (0); 839 } 840 841 static bool 842 sndstat_diinfo_is_sane(const nvlist_t *diinfo) 843 { 844 if (!(nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_RATE) && 845 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_RATE) && 846 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_FORMATS) && 847 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_CHN) && 848 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_CHN))) 849 return (false); 850 return (true); 851 } 852 853 static bool 854 sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist) 855 { 856 if (!(nvlist_exists_string(nvlist, SNDST_DSPS_DEVNODE) && 857 nvlist_exists_string(nvlist, SNDST_DSPS_DESC) && 858 nvlist_exists_number(nvlist, SNDST_DSPS_PCHAN) && 859 nvlist_exists_number(nvlist, SNDST_DSPS_RCHAN))) 860 return (false); 861 862 if (nvlist_get_number(nvlist, SNDST_DSPS_PCHAN) > 0) { 863 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) { 864 if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist, 865 SNDST_DSPS_INFO_PLAY))) 866 return (false); 867 } else if (!(nvlist_exists_number(nvlist, "pminrate") && 868 nvlist_exists_number(nvlist, "pmaxrate") && 869 nvlist_exists_number(nvlist, "pfmts"))) 870 return (false); 871 } 872 873 if (nvlist_get_number(nvlist, SNDST_DSPS_RCHAN) > 0) { 874 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) { 875 if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist, 876 SNDST_DSPS_INFO_REC))) 877 return (false); 878 } else if (!(nvlist_exists_number(nvlist, "rminrate") && 879 nvlist_exists_number(nvlist, "rmaxrate") && 880 nvlist_exists_number(nvlist, "rfmts"))) 881 return (false); 882 } 883 884 return (true); 885 886 } 887 888 static void 889 sndstat_get_diinfo_nv(const nvlist_t *nv, uint32_t *min_rate, 890 uint32_t *max_rate, uint32_t *formats, uint32_t *min_chn, 891 uint32_t *max_chn) 892 { 893 *min_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_RATE); 894 *max_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_RATE); 895 *formats = nvlist_get_number(nv, SNDST_DSPS_INFO_FORMATS); 896 *min_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_CHN); 897 *max_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_CHN); 898 } 899 900 static int 901 sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud) 902 { 903 const char *nameunit, *devnode, *desc; 904 unsigned int pchan, rchan; 905 uint32_t pminrate = 0, pmaxrate = 0; 906 uint32_t rminrate = 0, rmaxrate = 0; 907 uint32_t pfmts = 0, rfmts = 0; 908 uint32_t pminchn = 0, pmaxchn = 0; 909 uint32_t rminchn = 0, rmaxchn = 0; 910 nvlist_t *provider_nvl = NULL; 911 const nvlist_t *diinfo; 912 const char *provider; 913 914 devnode = nvlist_get_string(nvlist, SNDST_DSPS_DEVNODE); 915 if (nvlist_exists_string(nvlist, SNDST_DSPS_NAMEUNIT)) 916 nameunit = nvlist_get_string(nvlist, SNDST_DSPS_NAMEUNIT); 917 else 918 nameunit = devnode; 919 desc = nvlist_get_string(nvlist, SNDST_DSPS_DESC); 920 pchan = nvlist_get_number(nvlist, SNDST_DSPS_PCHAN); 921 rchan = nvlist_get_number(nvlist, SNDST_DSPS_RCHAN); 922 if (pchan != 0) { 923 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) { 924 diinfo = nvlist_get_nvlist(nvlist, 925 SNDST_DSPS_INFO_PLAY); 926 sndstat_get_diinfo_nv(diinfo, &pminrate, &pmaxrate, 927 &pfmts, &pminchn, &pmaxchn); 928 } else { 929 pminrate = nvlist_get_number(nvlist, "pminrate"); 930 pmaxrate = nvlist_get_number(nvlist, "pmaxrate"); 931 pfmts = nvlist_get_number(nvlist, "pfmts"); 932 } 933 } 934 if (rchan != 0) { 935 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) { 936 diinfo = nvlist_get_nvlist(nvlist, 937 SNDST_DSPS_INFO_REC); 938 sndstat_get_diinfo_nv(diinfo, &rminrate, &rmaxrate, 939 &rfmts, &rminchn, &rmaxchn); 940 } else { 941 rminrate = nvlist_get_number(nvlist, "rminrate"); 942 rmaxrate = nvlist_get_number(nvlist, "rmaxrate"); 943 rfmts = nvlist_get_number(nvlist, "rfmts"); 944 } 945 } 946 947 provider = dnvlist_get_string(nvlist, SNDST_DSPS_PROVIDER, ""); 948 if (provider[0] == '\0') 949 provider = NULL; 950 951 if (provider != NULL && 952 nvlist_exists_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)) { 953 provider_nvl = nvlist_clone( 954 nvlist_get_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)); 955 if (provider_nvl == NULL) 956 return (ENOMEM); 957 } 958 959 ud->provider = (provider != NULL) ? strdup(provider, M_DEVBUF) : NULL; 960 ud->devnode = strdup(devnode, M_DEVBUF); 961 ud->nameunit = strdup(nameunit, M_DEVBUF); 962 ud->desc = strdup(desc, M_DEVBUF); 963 ud->pchan = pchan; 964 ud->rchan = rchan; 965 ud->info_play.min_rate = pminrate; 966 ud->info_play.max_rate = pmaxrate; 967 ud->info_play.formats = pfmts; 968 ud->info_play.min_chn = pminchn; 969 ud->info_play.max_chn = pmaxchn; 970 ud->info_rec.min_rate = rminrate; 971 ud->info_rec.max_rate = rmaxrate; 972 ud->info_rec.formats = rfmts; 973 ud->info_rec.min_chn = rminchn; 974 ud->info_rec.max_chn = rmaxchn; 975 ud->provider_nvl = provider_nvl; 976 return (0); 977 } 978 979 static int 980 sndstat_add_user_devs(struct sndstat_file *pf, void *nvlbuf, size_t nbytes) 981 { 982 int err; 983 nvlist_t *nvl = NULL; 984 const nvlist_t * const *dsps; 985 size_t i, ndsps; 986 987 if ((pf->fflags & FWRITE) == 0) { 988 err = EPERM; 989 goto done; 990 } 991 992 if (nbytes > SNDST_UNVLBUF_MAX) { 993 err = ENOMEM; 994 goto done; 995 } 996 997 err = sndstat_unpack_user_nvlbuf(nvlbuf, nbytes, &nvl); 998 if (err != 0) 999 goto done; 1000 1001 if (!nvlist_exists_nvlist_array(nvl, SNDST_DSPS)) { 1002 err = EINVAL; 1003 goto done; 1004 } 1005 dsps = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &ndsps); 1006 for (i = 0; i < ndsps; i++) { 1007 if (!sndstat_dsp_nvlist_is_sane(dsps[i])) { 1008 err = EINVAL; 1009 goto done; 1010 } 1011 } 1012 sx_xlock(&pf->lock); 1013 for (i = 0; i < ndsps; i++) { 1014 struct sndstat_userdev *ud = 1015 malloc(sizeof(*ud), M_DEVBUF, M_WAITOK); 1016 err = sndstat_dsp_unpack_nvlist(dsps[i], ud); 1017 if (err) { 1018 sx_unlock(&pf->lock); 1019 goto done; 1020 } 1021 TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link); 1022 } 1023 sx_unlock(&pf->lock); 1024 1025 done: 1026 nvlist_destroy(nvl); 1027 return (err); 1028 } 1029 1030 static int 1031 sndstat_flush_user_devs(struct sndstat_file *pf) 1032 { 1033 if ((pf->fflags & FWRITE) == 0) 1034 return (EPERM); 1035 1036 sx_xlock(&pf->lock); 1037 sndstat_remove_all_userdevs(pf); 1038 sx_xunlock(&pf->lock); 1039 1040 return (0); 1041 } 1042 1043 static int 1044 sndstat_ioctl( 1045 struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) 1046 { 1047 int err; 1048 struct sndstat_file *pf; 1049 struct sndstioc_nv_arg *arg; 1050 #ifdef COMPAT_FREEBSD32 1051 struct sndstioc_nv_arg32 *arg32; 1052 size_t nbytes; 1053 #endif 1054 1055 err = devfs_get_cdevpriv((void **)&pf); 1056 if (err != 0) 1057 return (err); 1058 1059 switch (cmd) { 1060 case SNDSTIOC_GET_DEVS: 1061 arg = (struct sndstioc_nv_arg *)data; 1062 err = sndstat_get_devs(pf, arg->buf, &arg->nbytes); 1063 break; 1064 #ifdef COMPAT_FREEBSD32 1065 case SNDSTIOC_GET_DEVS32: 1066 arg32 = (struct sndstioc_nv_arg32 *)data; 1067 nbytes = arg32->nbytes; 1068 err = sndstat_get_devs(pf, (void *)(uintptr_t)arg32->buf, 1069 &nbytes); 1070 if (err == 0) { 1071 KASSERT(nbytes < UINT_MAX, ("impossibly many bytes")); 1072 arg32->nbytes = nbytes; 1073 } 1074 break; 1075 #endif 1076 case SNDSTIOC_ADD_USER_DEVS: 1077 arg = (struct sndstioc_nv_arg *)data; 1078 err = sndstat_add_user_devs(pf, arg->buf, arg->nbytes); 1079 break; 1080 #ifdef COMPAT_FREEBSD32 1081 case SNDSTIOC_ADD_USER_DEVS32: 1082 arg32 = (struct sndstioc_nv_arg32 *)data; 1083 err = sndstat_add_user_devs(pf, (void *)(uintptr_t)arg32->buf, 1084 arg32->nbytes); 1085 break; 1086 #endif 1087 case SNDSTIOC_REFRESH_DEVS: 1088 err = sndstat_refresh_devs(pf); 1089 break; 1090 case SNDSTIOC_FLUSH_USER_DEVS: 1091 err = sndstat_flush_user_devs(pf); 1092 break; 1093 default: 1094 err = ENODEV; 1095 } 1096 1097 return (err); 1098 } 1099 1100 static struct sndstat_userdev * 1101 sndstat_line2userdev(struct sndstat_file *pf, const char *line, int n) 1102 { 1103 struct sndstat_userdev *ud; 1104 const char *e, *m; 1105 1106 ud = malloc(sizeof(*ud), M_DEVBUF, M_WAITOK|M_ZERO); 1107 1108 ud->provider = NULL; 1109 ud->provider_nvl = NULL; 1110 e = strchr(line, ':'); 1111 if (e == NULL) 1112 goto fail; 1113 ud->nameunit = strndup(line, e - line, M_DEVBUF); 1114 ud->devnode = malloc(e - line + 1, M_DEVBUF, M_WAITOK | M_ZERO); 1115 strlcat(ud->devnode, ud->nameunit, e - line + 1); 1116 line = e + 1; 1117 1118 e = strchr(line, '<'); 1119 if (e == NULL) 1120 goto fail; 1121 line = e + 1; 1122 e = strrchr(line, '>'); 1123 if (e == NULL) 1124 goto fail; 1125 ud->desc = strndup(line, e - line, M_DEVBUF); 1126 line = e + 1; 1127 1128 e = strchr(line, '('); 1129 if (e == NULL) 1130 goto fail; 1131 line = e + 1; 1132 e = strrchr(line, ')'); 1133 if (e == NULL) 1134 goto fail; 1135 m = strstr(line, "play"); 1136 if (m != NULL && m < e) 1137 ud->pchan = 1; 1138 m = strstr(line, "rec"); 1139 if (m != NULL && m < e) 1140 ud->rchan = 1; 1141 1142 return (ud); 1143 1144 fail: 1145 free(ud->nameunit, M_DEVBUF); 1146 free(ud->devnode, M_DEVBUF); 1147 free(ud->desc, M_DEVBUF); 1148 free(ud, M_DEVBUF); 1149 return (NULL); 1150 } 1151 1152 /************************************************************************/ 1153 1154 int 1155 sndstat_register(device_t dev, char *str) 1156 { 1157 struct sndstat_entry *ent; 1158 struct sndstat_entry *pre; 1159 const char *devtype; 1160 int type, unit; 1161 1162 unit = device_get_unit(dev); 1163 devtype = device_get_name(dev); 1164 if (!strcmp(devtype, "pcm")) 1165 type = SS_TYPE_PCM; 1166 else if (!strcmp(devtype, "midi")) 1167 type = SS_TYPE_MIDI; 1168 else if (!strcmp(devtype, "sequencer")) 1169 type = SS_TYPE_SEQUENCER; 1170 else 1171 return (EINVAL); 1172 1173 ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO); 1174 ent->dev = dev; 1175 ent->str = str; 1176 ent->type = type; 1177 ent->unit = unit; 1178 1179 SNDSTAT_LOCK(); 1180 /* sorted list insertion */ 1181 TAILQ_FOREACH(pre, &sndstat_devlist, link) { 1182 if (pre->unit > unit) 1183 break; 1184 else if (pre->unit < unit) 1185 continue; 1186 if (pre->type > type) 1187 break; 1188 else if (pre->type < unit) 1189 continue; 1190 } 1191 if (pre == NULL) { 1192 TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link); 1193 } else { 1194 TAILQ_INSERT_BEFORE(pre, ent, link); 1195 } 1196 SNDSTAT_UNLOCK(); 1197 1198 return (0); 1199 } 1200 1201 int 1202 sndstat_unregister(device_t dev) 1203 { 1204 struct sndstat_entry *ent; 1205 int error = ENXIO; 1206 1207 SNDSTAT_LOCK(); 1208 TAILQ_FOREACH(ent, &sndstat_devlist, link) { 1209 if (ent->dev == dev) { 1210 TAILQ_REMOVE(&sndstat_devlist, ent, link); 1211 free(ent, M_DEVBUF); 1212 error = 0; 1213 break; 1214 } 1215 } 1216 SNDSTAT_UNLOCK(); 1217 1218 return (error); 1219 } 1220 1221 /************************************************************************/ 1222 1223 static int 1224 sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) 1225 { 1226 struct snddev_info *d; 1227 struct pcm_channel *c; 1228 struct pcm_feeder *f; 1229 1230 d = device_get_softc(dev); 1231 PCM_BUSYASSERT(d); 1232 1233 if (CHN_EMPTY(d, channels.pcm)) { 1234 sbuf_printf(s, " (mixer only)"); 1235 return (0); 1236 } 1237 1238 if (verbose < 1) { 1239 sbuf_printf(s, " (%s%s%s", 1240 d->playcount ? "play" : "", 1241 (d->playcount && d->reccount) ? "/" : "", 1242 d->reccount ? "rec" : ""); 1243 } else { 1244 sbuf_printf(s, " (%dp:%dv/%dr:%dv", 1245 d->playcount, d->pvchancount, 1246 d->reccount, d->rvchancount); 1247 } 1248 sbuf_printf(s, "%s)%s", 1249 ((d->playcount != 0 && d->reccount != 0) && 1250 (d->flags & SD_F_SIMPLEX)) ? " simplex" : "", 1251 (device_get_unit(dev) == snd_unit) ? " default" : ""); 1252 1253 if (verbose <= 1) 1254 return (0); 1255 1256 sbuf_printf(s, "\n\t"); 1257 sbuf_printf(s, "snddev flags=0x%b", d->flags, SD_F_BITS); 1258 1259 CHN_FOREACH(c, d, channels.pcm) { 1260 KASSERT(c->bufhard != NULL && c->bufsoft != NULL, 1261 ("hosed pcm channel setup")); 1262 1263 CHN_LOCK(c); 1264 1265 sbuf_printf(s, "\n\t"); 1266 1267 sbuf_printf(s, "%s[%s]: ", 1268 (c->parentchannel != NULL) ? 1269 c->parentchannel->name : "", c->name); 1270 sbuf_printf(s, "spd %d", c->speed); 1271 if (c->speed != sndbuf_getspd(c->bufhard)) { 1272 sbuf_printf(s, "/%d", 1273 sndbuf_getspd(c->bufhard)); 1274 } 1275 sbuf_printf(s, ", fmt 0x%08x", c->format); 1276 if (c->format != sndbuf_getfmt(c->bufhard)) { 1277 sbuf_printf(s, "/0x%08x", 1278 sndbuf_getfmt(c->bufhard)); 1279 } 1280 sbuf_printf(s, ", flags 0x%08x, 0x%08x", 1281 c->flags, c->feederflags); 1282 if (c->pid != -1) { 1283 sbuf_printf(s, ", pid %d (%s)", 1284 c->pid, c->comm); 1285 } 1286 sbuf_printf(s, "\n\t"); 1287 1288 sbuf_printf(s, "\tinterrupts %d, ", c->interrupts); 1289 1290 if (c->direction == PCMDIR_REC) { 1291 sbuf_printf(s, 1292 "overruns %d, feed %u, hfree %d, " 1293 "sfree %d\n\t\t[b:%d/%d/%d|bs:%d/%d/%d]", 1294 c->xruns, c->feedcount, 1295 sndbuf_getfree(c->bufhard), 1296 sndbuf_getfree(c->bufsoft), 1297 sndbuf_getsize(c->bufhard), 1298 sndbuf_getblksz(c->bufhard), 1299 sndbuf_getblkcnt(c->bufhard), 1300 sndbuf_getsize(c->bufsoft), 1301 sndbuf_getblksz(c->bufsoft), 1302 sndbuf_getblkcnt(c->bufsoft)); 1303 } else { 1304 sbuf_printf(s, 1305 "underruns %d, feed %u, ready %d " 1306 "\n\t\t[b:%d/%d/%d|bs:%d/%d/%d]", 1307 c->xruns, c->feedcount, 1308 sndbuf_getready(c->bufsoft), 1309 sndbuf_getsize(c->bufhard), 1310 sndbuf_getblksz(c->bufhard), 1311 sndbuf_getblkcnt(c->bufhard), 1312 sndbuf_getsize(c->bufsoft), 1313 sndbuf_getblksz(c->bufsoft), 1314 sndbuf_getblkcnt(c->bufsoft)); 1315 } 1316 sbuf_printf(s, "\n\t"); 1317 1318 sbuf_printf(s, "\tchannel flags=0x%b", c->flags, CHN_F_BITS); 1319 sbuf_printf(s, "\n\t"); 1320 1321 if (c->parentchannel != NULL) { 1322 sbuf_printf(s, "\t{%s}", (c->direction == PCMDIR_REC) ? 1323 c->parentchannel->name : "userland"); 1324 } else { 1325 sbuf_printf(s, "\t{%s}", (c->direction == PCMDIR_REC) ? 1326 "hardware" : "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 "userland" : "hardware"); 1365 } 1366 1367 CHN_UNLOCK(c); 1368 } 1369 1370 return (0); 1371 } 1372 1373 static int 1374 sndstat_prepare(struct sndstat_file *pf_self) 1375 { 1376 struct sbuf *s = &pf_self->sbuf; 1377 struct sndstat_entry *ent; 1378 struct snddev_info *d; 1379 struct sndstat_file *pf; 1380 int k; 1381 1382 /* make sure buffer is reset */ 1383 sbuf_clear(s); 1384 1385 if (snd_verbose > 0) 1386 sbuf_printf(s, "FreeBSD Audio Driver\n"); 1387 1388 /* generate list of installed devices */ 1389 k = 0; 1390 TAILQ_FOREACH(ent, &sndstat_devlist, link) { 1391 d = device_get_softc(ent->dev); 1392 if (!PCM_REGISTERED(d)) 1393 continue; 1394 if (!k++) 1395 sbuf_printf(s, "Installed devices:\n"); 1396 sbuf_printf(s, "%s:", device_get_nameunit(ent->dev)); 1397 sbuf_printf(s, " <%s>", device_get_desc(ent->dev)); 1398 if (snd_verbose > 0) 1399 sbuf_printf(s, " %s", ent->str); 1400 /* XXX Need Giant magic entry ??? */ 1401 PCM_ACQUIRE_QUICK(d); 1402 sndstat_prepare_pcm(s, ent->dev, snd_verbose); 1403 PCM_RELEASE_QUICK(d); 1404 sbuf_printf(s, "\n"); 1405 } 1406 if (k == 0) 1407 sbuf_printf(s, "No devices installed.\n"); 1408 1409 /* append any input from userspace */ 1410 k = 0; 1411 TAILQ_FOREACH(pf, &sndstat_filelist, entry) { 1412 struct sndstat_userdev *ud; 1413 1414 if (pf == pf_self) 1415 continue; 1416 sx_xlock(&pf->lock); 1417 if (TAILQ_EMPTY(&pf->userdev_list)) { 1418 sx_unlock(&pf->lock); 1419 continue; 1420 } 1421 if (!k++) 1422 sbuf_printf(s, "Installed devices from userspace:\n"); 1423 TAILQ_FOREACH(ud, &pf->userdev_list, link) { 1424 const char *caps = (ud->pchan && ud->rchan) ? 1425 "play/rec" : 1426 (ud->pchan ? "play" : (ud->rchan ? "rec" : "")); 1427 sbuf_printf(s, "%s: <%s>", ud->nameunit, ud->desc); 1428 sbuf_printf(s, " (%s)", caps); 1429 sbuf_printf(s, "\n"); 1430 } 1431 sx_unlock(&pf->lock); 1432 } 1433 if (k == 0) 1434 sbuf_printf(s, "No devices installed from userspace.\n"); 1435 1436 sbuf_finish(s); 1437 return (sbuf_len(s)); 1438 } 1439 1440 static void 1441 sndstat_sysinit(void *p) 1442 { 1443 sx_init(&sndstat_lock, "sndstat lock"); 1444 sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS, 1445 UID_ROOT, GID_WHEEL, 0644, "sndstat"); 1446 } 1447 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL); 1448 1449 static void 1450 sndstat_sysuninit(void *p) 1451 { 1452 if (sndstat_dev != NULL) { 1453 /* destroy_dev() will wait for all references to go away */ 1454 destroy_dev(sndstat_dev); 1455 } 1456 sx_destroy(&sndstat_lock); 1457 } 1458 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL); 1459