1 /* 2 * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> 3 * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) 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 * $FreeBSD$ 28 */ 29 30 #include <dev/sound/pcm/sound.h> 31 #include <dev/sound/pcm/vchan.h> 32 #include <sys/sysctl.h> 33 34 devclass_t pcm_devclass; 35 36 #ifdef USING_DEVFS 37 int snd_unit = 0; 38 TUNABLE_INT("hw.snd.unit", &snd_unit); 39 #endif 40 41 int snd_autovchans = 0; 42 int snd_maxvchans = 0; 43 #if __FreeBSD_version > 500000 44 TUNABLE_INT("hw.snd.autovchans", &snd_autovchans); 45 TUNABLE_INT("hw.snd.maxvchans", &snd_maxvchans); 46 #else 47 TUNABLE_INT("hw.snd.autovchans", 0, snd_autovchans); 48 TUNABLE_INT("hw.snd.maxvchans", 0, snd_maxvchans); 49 #endif 50 51 SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver"); 52 53 void * 54 snd_mtxcreate(const char *desc) 55 { 56 #ifdef USING_MUTEX 57 struct mtx *m; 58 59 m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO); 60 if (m == NULL) 61 return NULL; 62 mtx_init(m, desc, MTX_RECURSE); 63 return m; 64 #else 65 return (void *)0xcafebabe; 66 #endif 67 } 68 69 void 70 snd_mtxfree(void *m) 71 { 72 #ifdef USING_MUTEX 73 struct mtx *mtx = m; 74 75 mtx_assert(mtx, MA_OWNED); 76 mtx_destroy(mtx); 77 free(mtx, M_DEVBUF); 78 #endif 79 } 80 81 void 82 snd_mtxassert(void *m) 83 { 84 #ifdef USING_MUTEX 85 #ifdef INVARIANTS 86 struct mtx *mtx = m; 87 88 mtx_assert(mtx, MA_OWNED); 89 #endif 90 #endif 91 } 92 93 void 94 snd_mtxlock(void *m) 95 { 96 #ifdef USING_MUTEX 97 struct mtx *mtx = m; 98 99 mtx_lock(mtx); 100 #endif 101 } 102 103 void 104 snd_mtxunlock(void *m) 105 { 106 #ifdef USING_MUTEX 107 struct mtx *mtx = m; 108 109 mtx_unlock(mtx); 110 #endif 111 } 112 113 int 114 snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep) 115 { 116 #ifdef USING_MUTEX 117 flags &= INTR_MPSAFE; 118 flags |= INTR_TYPE_AV; 119 #else 120 flags = INTR_TYPE_AV; 121 #endif 122 return bus_setup_intr(dev, res, flags, hand, param, cookiep); 123 } 124 125 /* return a locked channel */ 126 struct pcm_channel * 127 pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid) 128 { 129 struct pcm_channel *c; 130 struct snddev_channel *sce; 131 int err; 132 133 snd_mtxassert(d->lock); 134 135 /* scan for a free channel */ 136 SLIST_FOREACH(sce, &d->channels, link) { 137 c = sce->channel; 138 CHN_LOCK(c); 139 if ((c->direction == direction) && !(c->flags & CHN_F_BUSY)) { 140 c->flags |= CHN_F_BUSY; 141 c->pid = pid; 142 return c; 143 } 144 CHN_UNLOCK(c); 145 } 146 147 /* no channel available */ 148 if (direction == PCMDIR_PLAY) { 149 if ((d->vchancount > 0) && (d->vchancount < snd_maxvchans)) { 150 /* try to create a vchan */ 151 SLIST_FOREACH(sce, &d->channels, link) { 152 c = sce->channel; 153 if (!SLIST_EMPTY(&c->children)) { 154 err = vchan_create(c); 155 if (!err) 156 return pcm_chnalloc(d, direction, pid); 157 else 158 device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err); 159 } 160 } 161 } 162 } 163 164 return NULL; 165 } 166 167 /* release a locked channel and unlock it */ 168 int 169 pcm_chnrelease(struct pcm_channel *c) 170 { 171 CHN_LOCKASSERT(c); 172 c->flags &= ~CHN_F_BUSY; 173 c->pid = -1; 174 CHN_UNLOCK(c); 175 return 0; 176 } 177 178 int 179 pcm_chnref(struct pcm_channel *c, int ref) 180 { 181 int r; 182 183 CHN_LOCKASSERT(c); 184 c->refcount += ref; 185 r = c->refcount; 186 return r; 187 } 188 189 #ifdef USING_DEVFS 190 static int 191 sysctl_hw_snd_unit(SYSCTL_HANDLER_ARGS) 192 { 193 struct snddev_info *d; 194 int error, unit; 195 196 unit = snd_unit; 197 error = sysctl_handle_int(oidp, &unit, sizeof(unit), req); 198 if (error == 0 && req->newptr != NULL) { 199 if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass)) 200 return EINVAL; 201 d = devclass_get_softc(pcm_devclass, unit); 202 if (d == NULL || SLIST_EMPTY(&d->channels)) 203 return EINVAL; 204 snd_unit = unit; 205 } 206 return (error); 207 } 208 SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW, 209 0, sizeof(int), sysctl_hw_snd_unit, "I", ""); 210 #endif 211 212 static int 213 sysctl_hw_snd_autovchans(SYSCTL_HANDLER_ARGS) 214 { 215 int v, error; 216 217 v = snd_autovchans; 218 error = sysctl_handle_int(oidp, &v, sizeof(v), req); 219 if (error == 0 && req->newptr != NULL) { 220 if (v < 0 || v >= SND_MAXVCHANS) 221 return EINVAL; 222 snd_autovchans = v; 223 } 224 return (error); 225 } 226 SYSCTL_PROC(_hw_snd, OID_AUTO, autovchans, CTLTYPE_INT | CTLFLAG_RW, 227 0, sizeof(int), sysctl_hw_snd_autovchans, "I", ""); 228 229 static int 230 sysctl_hw_snd_maxvchans(SYSCTL_HANDLER_ARGS) 231 { 232 int v, error; 233 234 v = snd_maxvchans; 235 error = sysctl_handle_int(oidp, &v, sizeof(v), req); 236 if (error == 0 && req->newptr != NULL) { 237 if (v < 0 || v >= SND_MAXVCHANS) 238 return EINVAL; 239 snd_maxvchans = v; 240 } 241 return (error); 242 } 243 SYSCTL_PROC(_hw_snd, OID_AUTO, maxvchans, CTLTYPE_INT | CTLFLAG_RW, 244 0, sizeof(int), sysctl_hw_snd_maxvchans, "I", ""); 245 246 struct pcm_channel * 247 pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) 248 { 249 struct pcm_channel *ch; 250 char *dirs; 251 int err; 252 253 switch(dir) { 254 case PCMDIR_PLAY: 255 dirs = "play"; 256 break; 257 case PCMDIR_REC: 258 dirs = "record"; 259 break; 260 case PCMDIR_VIRTUAL: 261 dirs = "virtual"; 262 dir = PCMDIR_PLAY; 263 break; 264 default: 265 return NULL; 266 } 267 268 ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); 269 if (!ch) 270 return NULL; 271 272 ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK); 273 if (!ch->methods) { 274 free(ch, M_DEVBUF); 275 return NULL; 276 } 277 278 ch->pid = -1; 279 ch->parentsnddev = d; 280 ch->parentchannel = parent; 281 snprintf(ch->name, 32, "%s:%d:%s", device_get_nameunit(d->dev), d->chancount, dirs); 282 283 err = chn_init(ch, devinfo, dir); 284 if (err) { 285 device_printf(d->dev, "chn_init() for channel %d (%s) failed: err = %d\n", d->chancount, dirs, err); 286 kobj_delete(ch->methods, M_DEVBUF); 287 free(ch, M_DEVBUF); 288 return NULL; 289 } 290 291 return ch; 292 } 293 294 int 295 pcm_chn_destroy(struct pcm_channel *ch) 296 { 297 int err; 298 299 err = chn_kill(ch); 300 if (err) { 301 device_printf(ch->parentsnddev->dev, "chn_kill() for %s failed, err = %d\n", ch->name, err); 302 return err; 303 } 304 305 kobj_delete(ch->methods, M_DEVBUF); 306 free(ch, M_DEVBUF); 307 308 return 0; 309 } 310 311 int 312 pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch, int mkdev) 313 { 314 struct snddev_channel *sce; 315 int unit = device_get_unit(d->dev); 316 317 snd_mtxlock(d->lock); 318 319 sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO); 320 if (!sce) { 321 snd_mtxunlock(d->lock); 322 return ENOMEM; 323 } 324 325 sce->channel = ch; 326 SLIST_INSERT_HEAD(&d->channels, sce, link); 327 328 if (mkdev) 329 dsp_register(unit, d->devcount++); 330 d->chancount++; 331 if (ch->flags & CHN_F_VIRTUAL) 332 d->vchancount++; 333 334 snd_mtxunlock(d->lock); 335 336 return 0; 337 } 338 339 int 340 pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch, int rmdev) 341 { 342 struct snddev_channel *sce; 343 int unit = device_get_unit(d->dev); 344 345 snd_mtxlock(d->lock); 346 SLIST_FOREACH(sce, &d->channels, link) { 347 if (sce->channel == ch) 348 goto gotit; 349 } 350 snd_mtxunlock(d->lock); 351 return EINVAL; 352 gotit: 353 if (ch->flags & CHN_F_VIRTUAL) 354 d->vchancount--; 355 d->chancount--; 356 SLIST_REMOVE(&d->channels, sce, snddev_channel, link); 357 free(sce, M_DEVBUF); 358 359 if (rmdev) 360 dsp_unregister(unit, --d->devcount); 361 snd_mtxunlock(d->lock); 362 363 return 0; 364 } 365 366 int 367 pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo) 368 { 369 struct snddev_info *d = device_get_softc(dev); 370 struct pcm_channel *ch, *child; 371 struct pcmchan_children *pce; 372 int i, err; 373 374 ch = pcm_chn_create(d, NULL, cls, dir, devinfo); 375 if (!ch) { 376 device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo); 377 return ENODEV; 378 } 379 380 err = pcm_chn_add(d, ch, 1); 381 if (err) { 382 device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err); 383 pcm_chn_destroy(ch); 384 return err; 385 } 386 387 if ((dir == PCMDIR_PLAY) && (d->flags & SD_F_AUTOVCHAN) && (snd_autovchans > 0)) { 388 ch->flags |= CHN_F_BUSY; 389 for (i = 0; err == 0 && i < snd_autovchans; i++) 390 err = vchan_create(ch); 391 if (err) { 392 device_printf(d->dev, "vchan_create(%d) failed, err=%d\n", i - 1, err); 393 SLIST_FOREACH(pce, &ch->children, link) { 394 child = pce->channel; 395 vchan_destroy(child); 396 } 397 return err; 398 } 399 } 400 401 return err; 402 } 403 404 static int 405 pcm_killchan(device_t dev) 406 { 407 struct snddev_info *d = device_get_softc(dev); 408 struct snddev_channel *sce; 409 410 snd_mtxlock(d->lock); 411 sce = SLIST_FIRST(&d->channels); 412 snd_mtxunlock(d->lock); 413 414 return pcm_chn_remove(d, sce->channel, 1); 415 } 416 417 int 418 pcm_setstatus(device_t dev, char *str) 419 { 420 struct snddev_info *d = device_get_softc(dev); 421 422 snd_mtxlock(d->lock); 423 strncpy(d->status, str, SND_STATUSLEN); 424 snd_mtxunlock(d->lock); 425 return 0; 426 } 427 428 u_int32_t 429 pcm_getflags(device_t dev) 430 { 431 struct snddev_info *d = device_get_softc(dev); 432 433 return d->flags; 434 } 435 436 void 437 pcm_setflags(device_t dev, u_int32_t val) 438 { 439 struct snddev_info *d = device_get_softc(dev); 440 441 d->flags = val; 442 } 443 444 void * 445 pcm_getdevinfo(device_t dev) 446 { 447 struct snddev_info *d = device_get_softc(dev); 448 449 return d->devinfo; 450 } 451 452 /* This is the generic init routine */ 453 int 454 pcm_register(device_t dev, void *devinfo, int numplay, int numrec) 455 { 456 struct snddev_info *d = device_get_softc(dev); 457 458 d->lock = snd_mtxcreate(device_get_nameunit(dev)); 459 snd_mtxlock(d->lock); 460 461 d->flags = 0; 462 d->dev = dev; 463 d->devinfo = devinfo; 464 d->devcount = 0; 465 d->chancount = 0; 466 d->vchancount = 0; 467 d->inprog = 0; 468 469 if (((numplay == 0) || (numrec == 0)) && (numplay != numrec)) 470 d->flags |= SD_F_SIMPLEX; 471 472 d->fakechan = fkchan_setup(dev); 473 chn_init(d->fakechan, NULL, 0); 474 475 #ifdef SND_DYNSYSCTL 476 sysctl_ctx_init(&d->sysctl_tree); 477 d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree, 478 SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO, 479 device_get_nameunit(dev), CTLFLAG_RD, 0, ""); 480 if (d->sysctl_tree_top == NULL) { 481 sysctl_ctx_free(&d->sysctl_tree); 482 goto no; 483 } 484 #endif 485 if (numplay > 0) 486 vchan_initsys(d); 487 if (numplay == 1) 488 d->flags |= SD_F_AUTOVCHAN; 489 490 snd_mtxunlock(d->lock); 491 return 0; 492 no: 493 snd_mtxfree(d->lock); 494 return ENXIO; 495 } 496 497 int 498 pcm_unregister(device_t dev) 499 { 500 struct snddev_info *d = device_get_softc(dev); 501 struct snddev_channel *sce; 502 503 snd_mtxlock(d->lock); 504 if (d->inprog) { 505 device_printf(dev, "unregister: operation in progress"); 506 snd_mtxunlock(d->lock); 507 return EBUSY; 508 } 509 SLIST_FOREACH(sce, &d->channels, link) { 510 if (sce->channel->refcount > 0) { 511 device_printf(dev, "unregister: channel busy"); 512 snd_mtxunlock(d->lock); 513 return EBUSY; 514 } 515 } 516 if (mixer_uninit(dev)) { 517 device_printf(dev, "unregister: mixer busy"); 518 snd_mtxunlock(d->lock); 519 return EBUSY; 520 } 521 522 #ifdef SND_DYNSYSCTL 523 d->sysctl_tree_top = NULL; 524 sysctl_ctx_free(&d->sysctl_tree); 525 #endif 526 while (!SLIST_EMPTY(&d->channels)) 527 pcm_killchan(dev); 528 529 chn_kill(d->fakechan); 530 fkchan_kill(d->fakechan); 531 532 snd_mtxfree(d->lock); 533 return 0; 534 } 535 536 static moduledata_t sndpcm_mod = { 537 "snd_pcm", 538 NULL, 539 NULL 540 }; 541 DECLARE_MODULE(snd_pcm, sndpcm_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); 542 MODULE_VERSION(snd_pcm, PCM_MODVER); 543