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