vchan.c revision 170161
1/*- 2 * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org> 3 * Copyright (c) 2006 Ariff Abdullah <ariff@FreeBSD.org> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 * 27 * Almost entirely rewritten to add multi-format/channels mixing support. 28 * 29 */ 30 31#include <dev/sound/pcm/sound.h> 32#include <dev/sound/pcm/vchan.h> 33#include "feeder_if.h" 34 35SND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pcm/vchan.c 170161 2007-05-31 18:43:33Z ariff $"); 36 37MALLOC_DEFINE(M_VCHANFEEDER, "vchanfeed", "pcm vchan feeder"); 38 39typedef uint32_t (*feed_vchan_mixer)(uint8_t *, uint8_t *, uint32_t); 40 41struct vchinfo { 42 struct pcm_channel *channel; 43 struct pcmchan_caps caps; 44 uint32_t fmtlist[2]; 45 int trigger; 46}; 47 48/* support everything (mono / stereo), except a-law / mu-law */ 49static struct afmtstr_table vchan_supported_fmts[] = { 50 { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, 51 { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, 52 { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, 53 { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, 54 { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, 55 { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, 56 { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, 57 { NULL, 0 }, 58}; 59 60/* alias table, shorter. */ 61static const struct { 62 char *alias, *fmtstr; 63} vchan_fmtstralias[] = { 64 { "8", "u8" }, { "16", "s16le" }, 65 { "24", "s24le" }, { "32", "s32le" }, 66 { NULL, NULL }, 67}; 68 69#define vchan_valid_format(fmt) \ 70 afmt2afmtstr(vchan_supported_fmts, fmt, NULL, 0, 0, \ 71 AFMTSTR_STEREO_RETURN) 72#define vchan_valid_strformat(strfmt) \ 73 afmtstr2afmt(vchan_supported_fmts, strfmt, AFMTSTR_STEREO_RETURN); 74 75/* 76 * Need specialized WRITE macros since 32bit might involved saturation 77 * if calculation is done within 32bit arithmetic. 78 */ 79#define VCHAN_PCM_WRITE_S8_NE(b8, val) PCM_WRITE_S8(b8, val) 80#define VCHAN_PCM_WRITE_S16_LE(b8, val) PCM_WRITE_S16_LE(b8, val) 81#define VCHAN_PCM_WRITE_S24_LE(b8, val) PCM_WRITE_S24_LE(b8, val) 82#define VCHAN_PCM_WRITE_S32_LE(b8, val) _PCM_WRITE_S32_LE(b8, val) 83#define VCHAN_PCM_WRITE_S16_BE(b8, val) PCM_WRITE_S16_BE(b8, val) 84#define VCHAN_PCM_WRITE_S24_BE(b8, val) PCM_WRITE_S24_BE(b8, val) 85#define VCHAN_PCM_WRITE_S32_BE(b8, val) _PCM_WRITE_S32_BE(b8, val) 86#define VCHAN_PCM_WRITE_U8_NE(b8, val) PCM_WRITE_U8(b8, val) 87#define VCHAN_PCM_WRITE_U16_LE(b8, val) PCM_WRITE_U16_LE(b8, val) 88#define VCHAN_PCM_WRITE_U24_LE(b8, val) PCM_WRITE_U24_LE(b8, val) 89#define VCHAN_PCM_WRITE_U32_LE(b8, val) _PCM_WRITE_U32_LE(b8, val) 90#define VCHAN_PCM_WRITE_U16_BE(b8, val) PCM_WRITE_U16_BE(b8, val) 91#define VCHAN_PCM_WRITE_U24_BE(b8, val) PCM_WRITE_U24_BE(b8, val) 92#define VCHAN_PCM_WRITE_U32_BE(b8, val) _PCM_WRITE_U32_BE(b8, val) 93 94#define FEEDER_VCHAN_MIX(FMTBIT, VCHAN_INTCAST, SIGN, SIGNS, ENDIAN, ENDIANS) \ 95static uint32_t \ 96feed_vchan_mix_##SIGNS##FMTBIT##ENDIANS(uint8_t *to, uint8_t *tmp, \ 97 uint32_t count) \ 98{ \ 99 int32_t x, y; \ 100 VCHAN_INTCAST z; \ 101 int i; \ 102 \ 103 i = count; \ 104 tmp += i; \ 105 to += i; \ 106 \ 107 do { \ 108 tmp -= PCM_##FMTBIT##_BPS; \ 109 to -= PCM_##FMTBIT##_BPS; \ 110 i -= PCM_##FMTBIT##_BPS; \ 111 x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(tmp); \ 112 y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN(to); \ 113 z = (VCHAN_INTCAST)x + y; \ 114 x = PCM_CLAMP_##SIGN##FMTBIT(z); \ 115 VCHAN_PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN(to, x); \ 116 } while (i != 0); \ 117 \ 118 return (count); \ 119} 120 121FEEDER_VCHAN_MIX(8, int32_t, S, s, NE, ne) 122FEEDER_VCHAN_MIX(16, int32_t, S, s, LE, le) 123FEEDER_VCHAN_MIX(24, int32_t, S, s, LE, le) 124FEEDER_VCHAN_MIX(32, intpcm_t, S, s, LE, le) 125FEEDER_VCHAN_MIX(16, int32_t, S, s, BE, be) 126FEEDER_VCHAN_MIX(24, int32_t, S, s, BE, be) 127FEEDER_VCHAN_MIX(32, intpcm_t, S, s, BE, be) 128FEEDER_VCHAN_MIX(8, int32_t, U, u, NE, ne) 129FEEDER_VCHAN_MIX(16, int32_t, U, u, LE, le) 130FEEDER_VCHAN_MIX(24, int32_t, U, u, LE, le) 131FEEDER_VCHAN_MIX(32, intpcm_t, U, u, LE, le) 132FEEDER_VCHAN_MIX(16, int32_t, U, u, BE, be) 133FEEDER_VCHAN_MIX(24, int32_t, U, u, BE, be) 134FEEDER_VCHAN_MIX(32, intpcm_t, U, u, BE, be) 135 136struct feed_vchan_info { 137 uint32_t format; 138 int bps; 139 feed_vchan_mixer mix; 140}; 141 142static struct feed_vchan_info feed_vchan_info_tbl[] = { 143 { AFMT_S8, PCM_8_BPS, feed_vchan_mix_s8ne }, 144 { AFMT_S16_LE, PCM_16_BPS, feed_vchan_mix_s16le }, 145 { AFMT_S24_LE, PCM_24_BPS, feed_vchan_mix_s24le }, 146 { AFMT_S32_LE, PCM_32_BPS, feed_vchan_mix_s32le }, 147 { AFMT_S16_BE, PCM_16_BPS, feed_vchan_mix_s16be }, 148 { AFMT_S24_BE, PCM_24_BPS, feed_vchan_mix_s24be }, 149 { AFMT_S32_BE, PCM_32_BPS, feed_vchan_mix_s32be }, 150 { AFMT_U8, PCM_8_BPS, feed_vchan_mix_u8ne }, 151 { AFMT_U16_LE, PCM_16_BPS, feed_vchan_mix_u16le }, 152 { AFMT_U24_LE, PCM_24_BPS, feed_vchan_mix_u24le }, 153 { AFMT_U32_LE, PCM_32_BPS, feed_vchan_mix_u32le }, 154 { AFMT_U16_BE, PCM_16_BPS, feed_vchan_mix_u16be }, 155 { AFMT_U24_BE, PCM_24_BPS, feed_vchan_mix_u24be }, 156 { AFMT_U32_BE, PCM_32_BPS, feed_vchan_mix_u32be }, 157}; 158 159#define FVCHAN_DATA(i, c) ((intptr_t)((((i) & 0x1f) << 4) | ((c) & 0xf))) 160#define FVCHAN_INFOIDX(m) (((m) >> 4) & 0x1f) 161#define FVCHAN_CHANNELS(m) ((m) & 0xf) 162 163static int 164feed_vchan_init(struct pcm_feeder *f) 165{ 166 int i, channels; 167 168 if (f->desc->out != f->desc->in) 169 return (EINVAL); 170 171 channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; 172 173 for (i = 0; i < sizeof(feed_vchan_info_tbl) / 174 sizeof(feed_vchan_info_tbl[0]); i++) { 175 if ((f->desc->out & ~AFMT_STEREO) == 176 feed_vchan_info_tbl[i].format) { 177 f->data = (void *)FVCHAN_DATA(i, channels); 178 return (0); 179 } 180 } 181 182 return (-1); 183} 184 185static __inline int 186feed_vchan_rec(struct pcm_channel *c) 187{ 188 struct pcm_channel *ch; 189 struct snd_dbuf *b, *bs; 190 int cnt, rdy; 191 192 /* 193 * Reset ready and moving pointer. We're not using bufsoft 194 * anywhere since its sole purpose is to become the primary 195 * distributor for the recorded buffer and also as an interrupt 196 * threshold progress indicator. 197 */ 198 b = c->bufsoft; 199 b->rp = 0; 200 b->rl = 0; 201 cnt = sndbuf_getsize(b); 202 203 do { 204 cnt = FEEDER_FEED(c->feeder->source, c, b->tmpbuf, cnt, 205 c->bufhard); 206 if (cnt != 0) { 207 sndbuf_acquire(b, b->tmpbuf, cnt); 208 cnt = sndbuf_getfree(b); 209 } 210 } while (cnt != 0); 211 212 /* Not enough data */ 213 if (b->rl < sndbuf_getbps(b)) { 214 b->rl = 0; 215 return (0); 216 } 217 218 /* 219 * Keep track of ready and moving pointer since we will use 220 * bufsoft over and over again, pretending nothing has happened. 221 */ 222 rdy = b->rl; 223 224 CHN_FOREACH(ch, c, children.busy) { 225 CHN_LOCK(ch); 226 bs = ch->bufsoft; 227 cnt = sndbuf_getfree(bs); 228 if (!(ch->flags & CHN_F_TRIGGERED) || 229 cnt < sndbuf_getbps(bs)) { 230 CHN_UNLOCK(ch); 231 continue; 232 } 233 do { 234 cnt = FEEDER_FEED(ch->feeder, ch, bs->tmpbuf, cnt, b); 235 if (cnt != 0) { 236 sndbuf_acquire(bs, bs->tmpbuf, cnt); 237 cnt = sndbuf_getfree(bs); 238 } 239 } while (cnt != 0); 240 /* 241 * Not entirely flushed out... 242 */ 243 if (b->rl != 0) 244 ch->xruns++; 245 CHN_UNLOCK(ch); 246 /* 247 * Rewind buffer position for next virtual channel. 248 */ 249 b->rp = 0; 250 b->rl = rdy; 251 } 252 253 /* 254 * Set ready pointer to indicate that our children are ready 255 * to be woken up, also as an interrupt threshold progress 256 * indicator. 257 */ 258 b->rl = 1; 259 260 /* 261 * Return 0 to bail out early from sndbuf_feed() loop. 262 * No need to increase feedcount counter since part of this 263 * feeder chains already include feed_root(). 264 */ 265 return (0); 266} 267 268static int 269feed_vchan(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, 270 uint32_t count, void *source) 271{ 272 struct feed_vchan_info *info; 273 struct snd_dbuf *src = source; 274 struct pcm_channel *ch; 275 uint32_t cnt, mcnt, rcnt, sz; 276 uint8_t *tmp; 277 278 if (c->direction == PCMDIR_REC) 279 return (feed_vchan_rec(c)); 280 281 sz = sndbuf_getsize(src); 282 if (sz < count) 283 count = sz; 284 285 info = &feed_vchan_info_tbl[FVCHAN_INFOIDX((intptr_t)f->data)]; 286 sz = info->bps * FVCHAN_CHANNELS((intptr_t)f->data); 287 count -= count % sz; 288 if (count < sz) 289 return (0); 290 291 /* 292 * we are going to use our source as a temporary buffer since it's 293 * got no other purpose. we obtain our data by traversing the channel 294 * list of children and calling vchan_mix_* to mix count bytes from 295 * each into our destination buffer, b 296 */ 297 tmp = sndbuf_getbuf(src); 298 rcnt = 0; 299 mcnt = 0; 300 301 CHN_FOREACH(ch, c, children.busy) { 302 CHN_LOCK(ch); 303 if (!(ch->flags & CHN_F_TRIGGERED)) { 304 CHN_UNLOCK(ch); 305 continue; 306 } 307 if ((ch->flags & CHN_F_MAPPED) && !(ch->flags & CHN_F_CLOSING)) 308 sndbuf_acquire(ch->bufsoft, NULL, 309 sndbuf_getfree(ch->bufsoft)); 310 if (rcnt == 0) { 311 rcnt = FEEDER_FEED(ch->feeder, ch, b, count, 312 ch->bufsoft); 313 rcnt -= rcnt % sz; 314 mcnt = count - rcnt; 315 } else { 316 cnt = FEEDER_FEED(ch->feeder, ch, tmp, count, 317 ch->bufsoft); 318 cnt -= cnt % sz; 319 if (cnt != 0) { 320 if (mcnt != 0) { 321 memset(b + rcnt, 322 sndbuf_zerodata(f->desc->out), 323 mcnt); 324 mcnt = 0; 325 } 326 cnt = info->mix(b, tmp, cnt); 327 if (cnt > rcnt) 328 rcnt = cnt; 329 } 330 } 331 CHN_UNLOCK(ch); 332 } 333 334 if (++c->feedcount == 0) 335 c->feedcount = 2; 336 337 return (rcnt); 338} 339 340static struct pcm_feederdesc feeder_vchan_desc[] = { 341 {FEEDER_MIXER, AFMT_S8, AFMT_S8, 0}, 342 {FEEDER_MIXER, AFMT_S16_LE, AFMT_S16_LE, 0}, 343 {FEEDER_MIXER, AFMT_S24_LE, AFMT_S24_LE, 0}, 344 {FEEDER_MIXER, AFMT_S32_LE, AFMT_S32_LE, 0}, 345 {FEEDER_MIXER, AFMT_S16_BE, AFMT_S16_BE, 0}, 346 {FEEDER_MIXER, AFMT_S24_BE, AFMT_S24_BE, 0}, 347 {FEEDER_MIXER, AFMT_S32_BE, AFMT_S32_BE, 0}, 348 {FEEDER_MIXER, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, 349 {FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, 350 {FEEDER_MIXER, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, 351 {FEEDER_MIXER, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, 352 {FEEDER_MIXER, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, 353 {FEEDER_MIXER, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, 354 {FEEDER_MIXER, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, 355 {FEEDER_MIXER, AFMT_U8, AFMT_U8, 0}, 356 {FEEDER_MIXER, AFMT_U16_LE, AFMT_U16_LE, 0}, 357 {FEEDER_MIXER, AFMT_U24_LE, AFMT_U24_LE, 0}, 358 {FEEDER_MIXER, AFMT_U32_LE, AFMT_U32_LE, 0}, 359 {FEEDER_MIXER, AFMT_U16_BE, AFMT_U16_BE, 0}, 360 {FEEDER_MIXER, AFMT_U24_BE, AFMT_U24_BE, 0}, 361 {FEEDER_MIXER, AFMT_U32_BE, AFMT_U32_BE, 0}, 362 {FEEDER_MIXER, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, 363 {FEEDER_MIXER, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, 364 {FEEDER_MIXER, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, 365 {FEEDER_MIXER, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, 366 {FEEDER_MIXER, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, 367 {FEEDER_MIXER, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, 368 {FEEDER_MIXER, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, 369 {0, 0, 0, 0}, 370}; 371static kobj_method_t feeder_vchan_methods[] = { 372 KOBJMETHOD(feeder_init, feed_vchan_init), 373 KOBJMETHOD(feeder_feed, feed_vchan), 374 {0, 0} 375}; 376FEEDER_DECLARE(feeder_vchan, 2, NULL); 377 378/************************************************************/ 379 380static void * 381vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, 382 struct pcm_channel *c, int dir) 383{ 384 struct vchinfo *ch; 385 386 KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC, 387 ("vchan_init: bad direction")); 388 KASSERT(c != NULL && c->parentchannel != NULL, 389 ("vchan_init: bad channels")); 390 391 ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); 392 ch->channel = c; 393 ch->trigger = PCMTRIG_STOP; 394 395 c->flags |= CHN_F_VIRTUAL; 396 397 return (ch); 398} 399 400static int 401vchan_free(kobj_t obj, void *data) 402{ 403 free(data, M_DEVBUF); 404 405 return (0); 406} 407 408static int 409vchan_setformat(kobj_t obj, void *data, uint32_t format) 410{ 411 struct vchinfo *ch = data; 412 413 if (fmtvalid(format, ch->fmtlist) == 0) 414 return (-1); 415 416 return (0); 417} 418 419static int 420vchan_setspeed(kobj_t obj, void *data, uint32_t speed) 421{ 422 struct vchinfo *ch = data; 423 struct pcm_channel *p = ch->channel->parentchannel; 424 425 return (sndbuf_getspd(p->bufsoft)); 426} 427 428static int 429vchan_trigger(kobj_t obj, void *data, int go) 430{ 431 struct vchinfo *ch = data; 432 struct pcm_channel *c, *p; 433 int otrigger; 434 435 if (!(go == PCMTRIG_START || go == PCMTRIG_STOP || 436 go == PCMTRIG_ABORT) || go == ch->trigger) 437 return (0); 438 439 c = ch->channel; 440 p = c->parentchannel; 441 otrigger = ch->trigger; 442 ch->trigger = go; 443 444 CHN_UNLOCK(c); 445 CHN_LOCK(p); 446 447 switch (go) { 448 case PCMTRIG_START: 449 if (otrigger != PCMTRIG_START) { 450 CHN_INSERT_HEAD(p, c, children.busy); 451 } 452 break; 453 case PCMTRIG_STOP: 454 case PCMTRIG_ABORT: 455 if (otrigger == PCMTRIG_START) { 456 CHN_REMOVE(p, c, children.busy); 457 } 458 break; 459 default: 460 break; 461 } 462 463 CHN_UNLOCK(p); 464 chn_notify(p, CHN_N_TRIGGER); 465 CHN_LOCK(c); 466 467 return (0); 468} 469 470static struct pcmchan_caps * 471vchan_getcaps(kobj_t obj, void *data) 472{ 473 struct vchinfo *ch = data; 474 struct pcm_channel *c, *p; 475 uint32_t fmt; 476 477 c = ch->channel; 478 p = c->parentchannel; 479 ch->caps.minspeed = sndbuf_getspd(p->bufsoft); 480 ch->caps.maxspeed = ch->caps.minspeed; 481 ch->caps.caps = 0; 482 ch->fmtlist[1] = 0; 483 fmt = sndbuf_getfmt(p->bufsoft); 484 if (fmt != vchan_valid_format(fmt)) { 485 device_printf(c->dev, 486 "%s: WARNING: invalid vchan format! (0x%08x)\n", 487 __func__, fmt); 488 fmt = VCHAN_DEFAULT_AFMT; 489 } 490 ch->fmtlist[0] = fmt; 491 ch->caps.fmtlist = ch->fmtlist; 492 493 return (&ch->caps); 494} 495 496static kobj_method_t vchan_methods[] = { 497 KOBJMETHOD(channel_init, vchan_init), 498 KOBJMETHOD(channel_free, vchan_free), 499 KOBJMETHOD(channel_setformat, vchan_setformat), 500 KOBJMETHOD(channel_setspeed, vchan_setspeed), 501 KOBJMETHOD(channel_trigger, vchan_trigger), 502 KOBJMETHOD(channel_getcaps, vchan_getcaps), 503 {0, 0} 504}; 505CHANNEL_DECLARE(vchan); 506 507/* 508 * On the fly vchan rate settings 509 */ 510#ifdef SND_DYNSYSCTL 511static int 512sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) 513{ 514 struct snddev_info *d; 515 struct pcm_channel *c, *ch = NULL; 516 struct pcmchan_caps *caps; 517 int vchancount, *vchanrate; 518 int direction; 519 int err = 0; 520 int newspd = 0; 521 522 d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); 523 if (d == NULL || !(d->flags & SD_F_AUTOVCHAN)) 524 return (EINVAL); 525 526 switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { 527 case VCHAN_PLAY: 528 direction = PCMDIR_PLAY; 529 vchancount = d->pvchancount; 530 vchanrate = &d->pvchanrate; 531 break; 532 case VCHAN_REC: 533 direction = PCMDIR_REC; 534 vchancount = d->rvchancount; 535 vchanrate = &d->rvchanrate; 536 break; 537 default: 538 return (EINVAL); 539 break; 540 } 541 542 if (vchancount < 1) 543 return (EINVAL); 544 if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { 545 pcm_inprog(d, -1); 546 return (EINPROGRESS); 547 } 548 CHN_FOREACH(c, d, channels.pcm) { 549 CHN_LOCK(c); 550 if (c->direction == direction) { 551 if (c->flags & CHN_F_VIRTUAL) { 552 /* Sanity check */ 553 if (ch != NULL && ch != c->parentchannel) { 554 CHN_UNLOCK(c); 555 pcm_inprog(d, -1); 556 return (EINVAL); 557 } 558 if (req->newptr != NULL && 559 (c->flags & CHN_F_BUSY)) { 560 CHN_UNLOCK(c); 561 pcm_inprog(d, -1); 562 return (EBUSY); 563 } 564 } else if (c->flags & CHN_F_HAS_VCHAN) { 565 /* No way!! */ 566 if (ch != NULL) { 567 CHN_UNLOCK(c); 568 pcm_inprog(d, -1); 569 return (EINVAL); 570 } 571 ch = c; 572 newspd = ch->speed; 573 } 574 } 575 CHN_UNLOCK(c); 576 } 577 if (ch == NULL) { 578 pcm_inprog(d, -1); 579 return (EINVAL); 580 } 581 err = sysctl_handle_int(oidp, &newspd, sizeof(newspd), req); 582 if (err == 0 && req->newptr != NULL) { 583 if (newspd < 1 || newspd < feeder_rate_min || 584 newspd > feeder_rate_max) { 585 pcm_inprog(d, -1); 586 return (EINVAL); 587 } 588 CHN_LOCK(ch); 589 if (feeder_rate_round) { 590 caps = chn_getcaps(ch); 591 if (caps == NULL || newspd < caps->minspeed || 592 newspd > caps->maxspeed) { 593 CHN_UNLOCK(ch); 594 pcm_inprog(d, -1); 595 return (EINVAL); 596 } 597 } 598 if (newspd != ch->speed) { 599 err = chn_setspeed(ch, newspd); 600 /* 601 * Try to avoid FEEDER_RATE on parent channel if the 602 * requested value is not supported by the hardware. 603 */ 604 if (!err && feeder_rate_round && 605 (ch->feederflags & (1 << FEEDER_RATE))) { 606 newspd = sndbuf_getspd(ch->bufhard); 607 err = chn_setspeed(ch, newspd); 608 } 609 CHN_UNLOCK(ch); 610 if (err == 0) { 611 pcm_lock(d); 612 *vchanrate = newspd; 613 pcm_unlock(d); 614 } 615 } else 616 CHN_UNLOCK(ch); 617 } 618 pcm_inprog(d, -1); 619 return (err); 620} 621 622static int 623sysctl_hw_snd_vchanformat(SYSCTL_HANDLER_ARGS) 624{ 625 struct snddev_info *d; 626 struct pcm_channel *c, *ch = NULL; 627 uint32_t newfmt, spd; 628 int vchancount, *vchanformat; 629 int direction; 630 int err = 0, i; 631 char fmtstr[AFMTSTR_MAXSZ]; 632 633 d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); 634 if (d == NULL || !(d->flags & SD_F_AUTOVCHAN)) 635 return (EINVAL); 636 637 switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { 638 case VCHAN_PLAY: 639 direction = PCMDIR_PLAY; 640 vchancount = d->pvchancount; 641 vchanformat = &d->pvchanformat; 642 break; 643 case VCHAN_REC: 644 direction = PCMDIR_REC; 645 vchancount = d->rvchancount; 646 vchanformat = &d->rvchanformat; 647 break; 648 default: 649 return (EINVAL); 650 break; 651 } 652 653 if (vchancount < 1) 654 return (EINVAL); 655 if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { 656 pcm_inprog(d, -1); 657 return (EINPROGRESS); 658 } 659 CHN_FOREACH(c, d, channels.pcm) { 660 CHN_LOCK(c); 661 if (c->direction == direction) { 662 if (c->flags & CHN_F_VIRTUAL) { 663 /* Sanity check */ 664 if (ch != NULL && ch != c->parentchannel) { 665 CHN_UNLOCK(c); 666 pcm_inprog(d, -1); 667 return (EINVAL); 668 } 669 if (req->newptr != NULL && 670 (c->flags & CHN_F_BUSY)) { 671 CHN_UNLOCK(c); 672 pcm_inprog(d, -1); 673 return (EBUSY); 674 } 675 } else if (c->flags & CHN_F_HAS_VCHAN) { 676 /* No way!! */ 677 if (ch != NULL) { 678 CHN_UNLOCK(c); 679 pcm_inprog(d, -1); 680 return (EINVAL); 681 } 682 ch = c; 683 if (ch->format != 684 afmt2afmtstr(vchan_supported_fmts, 685 ch->format, fmtstr, sizeof(fmtstr), 686 AFMTSTR_FULL, AFMTSTR_STEREO_RETURN)) { 687 strlcpy(fmtstr, VCHAN_DEFAULT_STRFMT, 688 sizeof(fmtstr)); 689 } 690 } 691 } 692 CHN_UNLOCK(c); 693 } 694 if (ch == NULL) { 695 pcm_inprog(d, -1); 696 return (EINVAL); 697 } 698 err = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); 699 if (err == 0 && req->newptr != NULL) { 700 for (i = 0; vchan_fmtstralias[i].alias != NULL; i++) { 701 if (strcmp(fmtstr, vchan_fmtstralias[i].alias) == 0) { 702 strlcpy(fmtstr, vchan_fmtstralias[i].fmtstr, 703 sizeof(fmtstr)); 704 break; 705 } 706 } 707 newfmt = vchan_valid_strformat(fmtstr); 708 if (newfmt == 0) { 709 pcm_inprog(d, -1); 710 return (EINVAL); 711 } 712 CHN_LOCK(ch); 713 if (newfmt != ch->format) { 714 /* Get channel speed, before chn_reset() screw it. */ 715 spd = ch->speed; 716 err = chn_reset(ch, newfmt); 717 if (err == 0) 718 err = chn_setspeed(ch, spd); 719 CHN_UNLOCK(ch); 720 if (err == 0) { 721 pcm_lock(d); 722 *vchanformat = newfmt; 723 pcm_unlock(d); 724 } 725 } else 726 CHN_UNLOCK(ch); 727 } 728 pcm_inprog(d, -1); 729 return (err); 730} 731#endif 732 733/* virtual channel interface */ 734 735#define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ 736 "play.vchanformat" : "rec.vchanformat" 737#define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ 738 "play.vchanrate" : "rec.vchanrate" 739 740int 741vchan_create(struct pcm_channel *parent, int num) 742{ 743 struct snddev_info *d = parent->parentsnddev; 744 struct pcm_channel *ch, *tmp, *after; 745 struct pcmchan_caps *parent_caps; 746 uint32_t vchanfmt; 747 int err, first, speed, r; 748 int direction; 749 750 if (!(parent->flags & CHN_F_BUSY)) 751 return (EBUSY); 752 753 if (parent->direction == PCMDIR_PLAY) { 754 direction = PCMDIR_PLAY_VIRTUAL; 755 vchanfmt = d->pvchanformat; 756 speed = d->pvchanrate; 757 } else if (parent->direction == PCMDIR_REC) { 758 direction = PCMDIR_REC_VIRTUAL; 759 vchanfmt = d->rvchanformat; 760 speed = d->rvchanrate; 761 } else 762 return (EINVAL); 763 CHN_UNLOCK(parent); 764 765 /* create a new playback channel */ 766 ch = pcm_chn_create(d, parent, &vchan_class, direction, num, parent); 767 if (ch == NULL) { 768 CHN_LOCK(parent); 769 return (ENODEV); 770 } 771 772 /* add us to our grandparent's channel list */ 773 err = pcm_chn_add(d, ch); 774 if (err) { 775 pcm_chn_destroy(ch); 776 CHN_LOCK(parent); 777 return (err); 778 } 779 780 CHN_LOCK(parent); 781 /* add us to our parent channel's children */ 782 first = CHN_EMPTY(parent, children); 783 after = NULL; 784 CHN_FOREACH(tmp, parent, children) { 785 if (CHN_CHAN(tmp) > CHN_CHAN(ch)) 786 after = tmp; 787 else if (CHN_CHAN(tmp) < CHN_CHAN(ch)) 788 break; 789 } 790 if (after != NULL) { 791 CHN_INSERT_AFTER(after, ch, children); 792 } else { 793 CHN_INSERT_HEAD(parent, ch, children); 794 } 795 parent->flags |= CHN_F_HAS_VCHAN; 796 797 if (first) { 798 parent_caps = chn_getcaps(parent); 799 if (parent_caps == NULL) 800 err = EINVAL; 801 802 if (!err) { 803 if (vchanfmt == 0) { 804 const char *vfmt; 805 806 CHN_UNLOCK(parent); 807 r = resource_string_value( 808 device_get_name(parent->dev), 809 device_get_unit(parent->dev), 810 VCHAN_FMT_HINT(direction), 811 &vfmt); 812 CHN_LOCK(parent); 813 if (r != 0) 814 vfmt = NULL; 815 if (vfmt != NULL) { 816 vchanfmt = vchan_valid_strformat(vfmt); 817 for (r = 0; vchanfmt == 0 && 818 vchan_fmtstralias[r].alias != NULL; 819 r++) { 820 if (strcmp(vfmt, vchan_fmtstralias[r].alias) == 0) { 821 vchanfmt = vchan_valid_strformat(vchan_fmtstralias[r].fmtstr); 822 break; 823 } 824 } 825 } 826 if (vchanfmt == 0) 827 vchanfmt = VCHAN_DEFAULT_AFMT; 828 } 829 err = chn_reset(parent, vchanfmt); 830 } 831 832 if (!err) { 833 /* 834 * This is very sad. Few soundcards advertised as being 835 * able to do (insanely) higher/lower speed, but in 836 * reality, they simply can't. At least, we give user chance 837 * to set sane value via kernel hints or sysctl. 838 */ 839 if (speed < 1) { 840 CHN_UNLOCK(parent); 841 r = resource_int_value( 842 device_get_name(parent->dev), 843 device_get_unit(parent->dev), 844 VCHAN_SPD_HINT(direction), 845 &speed); 846 CHN_LOCK(parent); 847 if (r != 0) { 848 /* 849 * No saved value, no hint, NOTHING. 850 * 851 * Workaround for sb16 running 852 * poorly at 45k / 49k. 853 */ 854 switch (parent_caps->maxspeed) { 855 case 45000: 856 case 49000: 857 speed = 44100; 858 break; 859 default: 860 speed = VCHAN_DEFAULT_SPEED; 861 if (speed > parent_caps->maxspeed) 862 speed = parent_caps->maxspeed; 863 break; 864 } 865 if (speed < parent_caps->minspeed) 866 speed = parent_caps->minspeed; 867 } 868 } 869 870 if (feeder_rate_round) { 871 /* 872 * Limit speed based on driver caps. 873 * This is supposed to help fixed rate, non-VRA 874 * AC97 cards, but.. (see below) 875 */ 876 if (speed < parent_caps->minspeed) 877 speed = parent_caps->minspeed; 878 if (speed > parent_caps->maxspeed) 879 speed = parent_caps->maxspeed; 880 } 881 882 /* 883 * We still need to limit the speed between 884 * feeder_rate_min <-> feeder_rate_max. This is 885 * just an escape goat if all of the above failed 886 * miserably. 887 */ 888 if (speed < feeder_rate_min) 889 speed = feeder_rate_min; 890 if (speed > feeder_rate_max) 891 speed = feeder_rate_max; 892 893 err = chn_setspeed(parent, speed); 894 /* 895 * Try to avoid FEEDER_RATE on parent channel if the 896 * requested value is not supported by the hardware. 897 */ 898 if (!err && feeder_rate_round && 899 (parent->feederflags & (1 << FEEDER_RATE))) { 900 speed = sndbuf_getspd(parent->bufhard); 901 err = chn_setspeed(parent, speed); 902 } 903 904 if (!err) { 905 /* 906 * Save new value. 907 */ 908 CHN_UNLOCK(parent); 909 pcm_lock(d); 910 if (direction == PCMDIR_PLAY_VIRTUAL) { 911 d->pvchanformat = vchanfmt; 912 d->pvchanrate = speed; 913 } else { 914 d->rvchanformat = vchanfmt; 915 d->rvchanrate = speed; 916 } 917 pcm_unlock(d); 918 CHN_LOCK(parent); 919 } 920 } 921 922 if (err) { 923 CHN_REMOVE(parent, ch, children); 924 parent->flags &= ~CHN_F_HAS_VCHAN; 925 CHN_UNLOCK(parent); 926 pcm_lock(d); 927 if (pcm_chn_remove(d, ch) == 0) { 928 pcm_unlock(d); 929 pcm_chn_destroy(ch); 930 } else 931 pcm_unlock(d); 932 CHN_LOCK(parent); 933 return (err); 934 } 935 } 936 937 return (0); 938} 939 940int 941vchan_destroy(struct pcm_channel *c) 942{ 943 struct pcm_channel *parent = c->parentchannel; 944 struct snddev_info *d = parent->parentsnddev; 945 uint32_t spd; 946 int err; 947 948 CHN_LOCK(parent); 949 if (!(parent->flags & CHN_F_BUSY)) { 950 CHN_UNLOCK(parent); 951 return (EBUSY); 952 } 953 if (CHN_EMPTY(parent, children)) { 954 CHN_UNLOCK(parent); 955 return (EINVAL); 956 } 957 958 /* remove us from our parent's children list */ 959 CHN_REMOVE(parent, c, children); 960 961 if (CHN_EMPTY(parent, children)) { 962 parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN); 963 spd = parent->speed; 964 if (chn_reset(parent, parent->format) == 0) 965 chn_setspeed(parent, spd); 966 } 967 968 CHN_UNLOCK(parent); 969 970 /* remove us from our grandparent's channel list */ 971 pcm_lock(d); 972 err = pcm_chn_remove(d, c); 973 pcm_unlock(d); 974 975 /* destroy ourselves */ 976 if (!err) 977 err = pcm_chn_destroy(c); 978 979 return (err); 980} 981 982int 983vchan_initsys(device_t dev) 984{ 985#ifdef SND_DYNSYSCTL 986 struct snddev_info *d; 987 int unit; 988 989 unit = device_get_unit(dev); 990 d = device_get_softc(dev); 991 992 /* Play */ 993 SYSCTL_ADD_PROC(&d->play_sysctl_ctx, 994 SYSCTL_CHILDREN(d->play_sysctl_tree), 995 OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, 996 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, 997 sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); 998 SYSCTL_ADD_PROC(&d->play_sysctl_ctx, 999 SYSCTL_CHILDREN(d->play_sysctl_tree), 1000 OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, 1001 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, 1002 sysctl_hw_snd_vchanrate, "I", "virtual channel mixing speed/rate"); 1003 SYSCTL_ADD_PROC(&d->play_sysctl_ctx, 1004 SYSCTL_CHILDREN(d->play_sysctl_tree), 1005 OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, 1006 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, 1007 sysctl_hw_snd_vchanformat, "A", "virtual channel format"); 1008 /* Rec */ 1009 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, 1010 SYSCTL_CHILDREN(d->rec_sysctl_tree), 1011 OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, 1012 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, 1013 sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); 1014 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, 1015 SYSCTL_CHILDREN(d->rec_sysctl_tree), 1016 OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, 1017 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, 1018 sysctl_hw_snd_vchanrate, "I", "virtual channel base speed/rate"); 1019 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, 1020 SYSCTL_CHILDREN(d->rec_sysctl_tree), 1021 OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, 1022 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, 1023 sysctl_hw_snd_vchanformat, "A", "virtual channel format"); 1024#endif 1025 1026 return (0); 1027} 1028