860115ff61
panic() so that the buffer overflow just beyond this point is always caught, even when the code is not compiled with INVARIANTS. Change chn_setblocksize() buffer reallocation code to attempt to avoid the feed_vchan16() buffer overflow by attempting to always keep the bufsoft buffer at least as large as the bufhard buffer. Print a diagnositic message Danger! %s bufsoft size increasing from %d to %d after CHANNEL_SETBLOCKSIZE() if our best attempts fail. If feed_vchan16() were to be called by the interrupt handler while locks are dropped in chn_setblocksize() to increase the size bufsoft to match the size of bufhard, the panic() code in feed_vchan16() will be triggered. If the diagnostic message is printed, it is a warning that a panic is possible if the system were to see events in an "unlucky" order. Change the locking code to avoid the need for MTX_RECURSIVE mutexes. Add the MTX_DUPOK option to the channel mutexes and change the locking sequence to always lock the parent channel before its children to avoid the possibility of deadlock. Actually implement locking assertions for the channel mutexes and fix the problems found by the resulting assertion violations. Clean up the locking code in dsp_ioctl(). Allocate the channel buffers using the malloc() M_WAITOK option instead of M_NOWAIT so that buffer allocation won't fail. Drop locks across the malloc() calls. Add/modify KASSERTS() in attempt to detect problems early. Abuse layering by adding a pointer to the snd_dbuf structure that points back to the pcm_channel that owns it. This allows sndbuf_resize() to do proper locking without having to change the its API, which is used by the hardware drivers. Don't dereference a NULL pointer when setting hw.snd.maxautovchans if a hardware driver is not loaded. Noticed by Ryan Sommers <ryans at gamersimpact.com>. Tested by: Stefan Ehmann <shoesoft AT gmx.net> Tested by: matk (Mathew Kanner) Tested by: Gordon Bergling <gbergling AT 0xfce3.net>
997 lines
21 KiB
C
997 lines
21 KiB
C
/*
|
|
* Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
|
|
* (C) 1997 Luigi Rizzo (luigi@iet.unipi.it)
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <dev/sound/pcm/sound.h>
|
|
#include <dev/sound/pcm/vchan.h>
|
|
#include <dev/sound/pcm/dsp.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include "feeder_if.h"
|
|
|
|
SND_DECLARE_FILE("$FreeBSD$");
|
|
|
|
devclass_t pcm_devclass;
|
|
|
|
int pcm_veto_load = 1;
|
|
|
|
#ifdef USING_DEVFS
|
|
int snd_unit = 0;
|
|
TUNABLE_INT("hw.snd.unit", &snd_unit);
|
|
#endif
|
|
|
|
int snd_maxautovchans = 0;
|
|
TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans);
|
|
|
|
SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver");
|
|
|
|
static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose);
|
|
|
|
struct sysctl_ctx_list *
|
|
snd_sysctl_tree(device_t dev)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
|
|
return &d->sysctl_tree;
|
|
}
|
|
|
|
struct sysctl_oid *
|
|
snd_sysctl_tree_top(device_t dev)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
|
|
return d->sysctl_tree_top;
|
|
}
|
|
|
|
void *
|
|
snd_mtxcreate(const char *desc, const char *type)
|
|
{
|
|
#ifdef USING_MUTEX
|
|
struct mtx *m;
|
|
|
|
m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO);
|
|
if (m == NULL)
|
|
return NULL;
|
|
mtx_init(m, desc, type, MTX_DEF);
|
|
return m;
|
|
#else
|
|
return (void *)0xcafebabe;
|
|
#endif
|
|
}
|
|
|
|
void *
|
|
snd_chnmtxcreate(const char *desc, const char *type)
|
|
{
|
|
#ifdef USING_MUTEX
|
|
struct mtx *m;
|
|
|
|
m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO);
|
|
if (m == NULL)
|
|
return NULL;
|
|
mtx_init(m, desc, type, MTX_DEF | MTX_DUPOK);
|
|
return m;
|
|
#else
|
|
return (void *)0xcafebabe;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
snd_mtxfree(void *m)
|
|
{
|
|
#ifdef USING_MUTEX
|
|
struct mtx *mtx = m;
|
|
|
|
/* mtx_assert(mtx, MA_OWNED); */
|
|
mtx_destroy(mtx);
|
|
free(mtx, M_DEVBUF);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
snd_mtxassert(void *m)
|
|
{
|
|
#ifdef USING_MUTEX
|
|
#ifdef INVARIANTS
|
|
struct mtx *mtx = m;
|
|
|
|
mtx_assert(mtx, MA_OWNED);
|
|
#endif
|
|
#endif
|
|
}
|
|
/*
|
|
void
|
|
snd_mtxlock(void *m)
|
|
{
|
|
#ifdef USING_MUTEX
|
|
struct mtx *mtx = m;
|
|
|
|
mtx_lock(mtx);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
snd_mtxunlock(void *m)
|
|
{
|
|
#ifdef USING_MUTEX
|
|
struct mtx *mtx = m;
|
|
|
|
mtx_unlock(mtx);
|
|
#endif
|
|
}
|
|
*/
|
|
int
|
|
snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep)
|
|
{
|
|
#ifdef USING_MUTEX
|
|
flags &= INTR_MPSAFE;
|
|
flags |= INTR_TYPE_AV;
|
|
#else
|
|
flags = INTR_TYPE_AV;
|
|
#endif
|
|
return bus_setup_intr(dev, res, flags, hand, param, cookiep);
|
|
}
|
|
|
|
#ifndef PCM_DEBUG_MTX
|
|
void
|
|
pcm_lock(struct snddev_info *d)
|
|
{
|
|
snd_mtxlock(d->lock);
|
|
}
|
|
|
|
void
|
|
pcm_unlock(struct snddev_info *d)
|
|
{
|
|
snd_mtxunlock(d->lock);
|
|
}
|
|
#endif
|
|
|
|
struct pcm_channel *
|
|
pcm_getfakechan(struct snddev_info *d)
|
|
{
|
|
return d->fakechan;
|
|
}
|
|
|
|
/* return a locked channel */
|
|
struct pcm_channel *
|
|
pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum)
|
|
{
|
|
struct pcm_channel *c;
|
|
struct snddev_channel *sce;
|
|
int err;
|
|
|
|
snd_mtxassert(d->lock);
|
|
|
|
/* scan for a free channel */
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
c = sce->channel;
|
|
CHN_LOCK(c);
|
|
if ((c->direction == direction) && !(c->flags & CHN_F_BUSY)) {
|
|
if (chnum == -1 || c->num == chnum) {
|
|
c->flags |= CHN_F_BUSY;
|
|
c->pid = pid;
|
|
return c;
|
|
}
|
|
}
|
|
CHN_UNLOCK(c);
|
|
}
|
|
|
|
/* no channel available */
|
|
if (direction == PCMDIR_PLAY) {
|
|
if ((d->vchancount > 0) && (d->vchancount < snd_maxautovchans)) {
|
|
/* try to create a vchan */
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
c = sce->channel;
|
|
CHN_LOCK(c);
|
|
if (!SLIST_EMPTY(&c->children)) {
|
|
err = vchan_create(c);
|
|
CHN_UNLOCK(c);
|
|
if (!err)
|
|
return pcm_chnalloc(d, direction, pid, -1);
|
|
else
|
|
device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
|
|
} else
|
|
CHN_UNLOCK(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* release a locked channel and unlock it */
|
|
int
|
|
pcm_chnrelease(struct pcm_channel *c)
|
|
{
|
|
CHN_LOCKASSERT(c);
|
|
c->flags &= ~CHN_F_BUSY;
|
|
c->pid = -1;
|
|
CHN_UNLOCK(c);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pcm_chnref(struct pcm_channel *c, int ref)
|
|
{
|
|
int r;
|
|
|
|
CHN_LOCKASSERT(c);
|
|
c->refcount += ref;
|
|
r = c->refcount;
|
|
return r;
|
|
}
|
|
|
|
int
|
|
pcm_inprog(struct snddev_info *d, int delta)
|
|
{
|
|
int r;
|
|
|
|
if (delta == 0)
|
|
return d->inprog;
|
|
|
|
/* backtrace(); */
|
|
pcm_lock(d);
|
|
d->inprog += delta;
|
|
r = d->inprog;
|
|
pcm_unlock(d);
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
pcm_setmaxautovchans(struct snddev_info *d, int num)
|
|
{
|
|
struct pcm_channel *c;
|
|
struct snddev_channel *sce;
|
|
int err, done;
|
|
|
|
if (num > 0 && d->vchancount == 0) {
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
c = sce->channel;
|
|
CHN_LOCK(c);
|
|
if ((c->direction == PCMDIR_PLAY) && !(c->flags & CHN_F_BUSY)) {
|
|
c->flags |= CHN_F_BUSY;
|
|
err = vchan_create(c);
|
|
if (err) {
|
|
c->flags &= ~CHN_F_BUSY;
|
|
CHN_UNLOCK(c);
|
|
device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
|
|
} else
|
|
CHN_UNLOCK(c);
|
|
return;
|
|
}
|
|
CHN_UNLOCK(c);
|
|
}
|
|
}
|
|
if (num == 0 && d->vchancount > 0) {
|
|
done = 0;
|
|
while (!done) {
|
|
done = 1;
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
c = sce->channel;
|
|
if ((c->flags & CHN_F_VIRTUAL) && !(c->flags & CHN_F_BUSY)) {
|
|
done = 0;
|
|
snd_mtxlock(d->lock);
|
|
err = vchan_destroy(c);
|
|
snd_mtxunlock(d->lock);
|
|
if (err)
|
|
device_printf(d->dev, "vchan_destroy(%s) == %d\n", c->name, err);
|
|
break; /* restart */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USING_DEVFS
|
|
static int
|
|
sysctl_hw_snd_unit(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct snddev_info *d;
|
|
int error, unit;
|
|
|
|
unit = snd_unit;
|
|
error = sysctl_handle_int(oidp, &unit, sizeof(unit), req);
|
|
if (error == 0 && req->newptr != NULL) {
|
|
if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass))
|
|
return EINVAL;
|
|
d = devclass_get_softc(pcm_devclass, unit);
|
|
if (d == NULL || SLIST_EMPTY(&d->channels))
|
|
return EINVAL;
|
|
snd_unit = unit;
|
|
}
|
|
return (error);
|
|
}
|
|
SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW,
|
|
0, sizeof(int), sysctl_hw_snd_unit, "I", "");
|
|
#endif
|
|
|
|
static int
|
|
sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct snddev_info *d;
|
|
int i, v, error;
|
|
|
|
v = snd_maxautovchans;
|
|
error = sysctl_handle_int(oidp, &v, sizeof(v), req);
|
|
if (error == 0 && req->newptr != NULL) {
|
|
if (v < 0 || v >= SND_MAXVCHANS || pcm_devclass == NULL)
|
|
return EINVAL;
|
|
if (v != snd_maxautovchans) {
|
|
for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) {
|
|
d = devclass_get_softc(pcm_devclass, i);
|
|
if (!d)
|
|
continue;
|
|
pcm_setmaxautovchans(d, v);
|
|
}
|
|
}
|
|
snd_maxautovchans = v;
|
|
}
|
|
return (error);
|
|
}
|
|
SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW,
|
|
0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", "");
|
|
|
|
struct pcm_channel *
|
|
pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo)
|
|
{
|
|
struct pcm_channel *ch;
|
|
char *dirs;
|
|
int err, *pnum;
|
|
|
|
switch(dir) {
|
|
case PCMDIR_PLAY:
|
|
dirs = "play";
|
|
pnum = &d->playcount;
|
|
break;
|
|
|
|
case PCMDIR_REC:
|
|
dirs = "record";
|
|
pnum = &d->reccount;
|
|
break;
|
|
|
|
case PCMDIR_VIRTUAL:
|
|
dirs = "virtual";
|
|
dir = PCMDIR_PLAY;
|
|
pnum = &d->vchancount;
|
|
break;
|
|
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO);
|
|
if (!ch)
|
|
return NULL;
|
|
|
|
ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK);
|
|
if (!ch->methods) {
|
|
free(ch, M_DEVBUF);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
snd_mtxlock(d->lock);
|
|
ch->num = (*pnum)++;
|
|
snd_mtxunlock(d->lock);
|
|
|
|
ch->pid = -1;
|
|
ch->parentsnddev = d;
|
|
ch->parentchannel = parent;
|
|
ch->dev = d->dev;
|
|
snprintf(ch->name, 32, "%s:%s:%d", device_get_nameunit(ch->dev), dirs, ch->num);
|
|
|
|
err = chn_init(ch, devinfo, dir);
|
|
if (err) {
|
|
device_printf(d->dev, "chn_init(%s) failed: err = %d\n", ch->name, err);
|
|
kobj_delete(ch->methods, M_DEVBUF);
|
|
free(ch, M_DEVBUF);
|
|
snd_mtxlock(d->lock);
|
|
(*pnum)--;
|
|
snd_mtxunlock(d->lock);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return ch;
|
|
}
|
|
|
|
int
|
|
pcm_chn_destroy(struct pcm_channel *ch)
|
|
{
|
|
struct snddev_info *d;
|
|
int err;
|
|
|
|
d = ch->parentsnddev;
|
|
err = chn_kill(ch);
|
|
if (err) {
|
|
device_printf(d->dev, "chn_kill(%s) failed, err = %d\n", ch->name, err);
|
|
return err;
|
|
}
|
|
|
|
kobj_delete(ch->methods, M_DEVBUF);
|
|
free(ch, M_DEVBUF);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch)
|
|
{
|
|
struct snddev_channel *sce, *tmp, *after;
|
|
int device = device_get_unit(d->dev);
|
|
|
|
/*
|
|
* Note it's confusing nomenclature.
|
|
* dev_t
|
|
* device -> pcm_device
|
|
* unit -> pcm_channel
|
|
* channel -> snddev_channel
|
|
* device_t
|
|
* unit -> pcm_device
|
|
*/
|
|
|
|
sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO);
|
|
if (!sce) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
snd_mtxlock(d->lock);
|
|
sce->channel = ch;
|
|
sce->chan_num= d->devcount++;
|
|
if (SLIST_EMPTY(&d->channels)) {
|
|
SLIST_INSERT_HEAD(&d->channels, sce, link);
|
|
} else {
|
|
after = NULL;
|
|
SLIST_FOREACH(tmp, &d->channels, link) {
|
|
after = tmp;
|
|
}
|
|
SLIST_INSERT_AFTER(after, sce, link);
|
|
}
|
|
snd_mtxunlock(d->lock);
|
|
sce->dsp_devt= make_dev(&dsp_cdevsw,
|
|
PCMMKMINOR(device, SND_DEV_DSP, sce->chan_num),
|
|
UID_ROOT, GID_WHEEL, 0666, "dsp%d.%d",
|
|
device, sce->chan_num);
|
|
|
|
sce->dspW_devt= make_dev(&dsp_cdevsw,
|
|
PCMMKMINOR(device, SND_DEV_DSP16, sce->chan_num),
|
|
UID_ROOT, GID_WHEEL, 0666, "dspW%d.%d",
|
|
device, sce->chan_num);
|
|
|
|
sce->audio_devt= make_dev(&dsp_cdevsw,
|
|
PCMMKMINOR(device, SND_DEV_AUDIO, sce->chan_num),
|
|
UID_ROOT, GID_WHEEL, 0666, "audio%d.%d",
|
|
device, sce->chan_num);
|
|
|
|
if (ch->direction == PCMDIR_REC)
|
|
sce->dspr_devt = make_dev(&dsp_cdevsw,
|
|
PCMMKMINOR(device, SND_DEV_DSPREC,
|
|
sce->chan_num), UID_ROOT, GID_WHEEL,
|
|
0666, "dspr%d.%d", device, sce->chan_num);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch)
|
|
{
|
|
struct snddev_channel *sce;
|
|
#if 0
|
|
int ourlock;
|
|
|
|
ourlock = 0;
|
|
if (!mtx_owned(d->lock)) {
|
|
snd_mtxlock(d->lock);
|
|
ourlock = 1;
|
|
}
|
|
#endif
|
|
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
if (sce->channel == ch)
|
|
goto gotit;
|
|
}
|
|
#if 0
|
|
if (ourlock)
|
|
snd_mtxunlock(d->lock);
|
|
#endif
|
|
return EINVAL;
|
|
gotit:
|
|
SLIST_REMOVE(&d->channels, sce, snddev_channel, link);
|
|
|
|
if (ch->direction == PCMDIR_REC)
|
|
d->reccount--;
|
|
else if (ch->flags & CHN_F_VIRTUAL)
|
|
d->vchancount--;
|
|
else
|
|
d->playcount--;
|
|
|
|
#if 0
|
|
if (ourlock)
|
|
snd_mtxunlock(d->lock);
|
|
#endif
|
|
free(sce, M_DEVBUF);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
struct pcm_channel *ch;
|
|
int err;
|
|
|
|
ch = pcm_chn_create(d, NULL, cls, dir, devinfo);
|
|
if (!ch) {
|
|
device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo);
|
|
return ENODEV;
|
|
}
|
|
|
|
err = pcm_chn_add(d, ch);
|
|
if (err) {
|
|
device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err);
|
|
pcm_chn_destroy(ch);
|
|
return err;
|
|
}
|
|
|
|
CHN_LOCK(ch);
|
|
if (snd_maxautovchans > 0 && (d->flags & SD_F_AUTOVCHAN) &&
|
|
ch->direction == PCMDIR_PLAY && d->vchancount == 0) {
|
|
ch->flags |= CHN_F_BUSY;
|
|
err = vchan_create(ch);
|
|
if (err) {
|
|
ch->flags &= ~CHN_F_BUSY;
|
|
CHN_UNLOCK(ch);
|
|
device_printf(d->dev, "vchan_create(%s) == %d\n", ch->name, err);
|
|
return err;
|
|
}
|
|
}
|
|
CHN_UNLOCK(ch);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
pcm_killchan(device_t dev)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
struct snddev_channel *sce;
|
|
struct pcm_channel *ch;
|
|
int error = 0;
|
|
|
|
sce = SLIST_FIRST(&d->channels);
|
|
ch = sce->channel;
|
|
|
|
error = pcm_chn_remove(d, sce->channel);
|
|
if (error)
|
|
return (error);
|
|
return (pcm_chn_destroy(ch));
|
|
}
|
|
|
|
int
|
|
pcm_setstatus(device_t dev, char *str)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
|
|
snd_mtxlock(d->lock);
|
|
strncpy(d->status, str, SND_STATUSLEN);
|
|
snd_mtxunlock(d->lock);
|
|
return 0;
|
|
}
|
|
|
|
u_int32_t
|
|
pcm_getflags(device_t dev)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
|
|
return d->flags;
|
|
}
|
|
|
|
void
|
|
pcm_setflags(device_t dev, u_int32_t val)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
|
|
d->flags = val;
|
|
}
|
|
|
|
void *
|
|
pcm_getdevinfo(device_t dev)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
|
|
return d->devinfo;
|
|
}
|
|
|
|
unsigned int
|
|
pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
int sz, x;
|
|
|
|
sz = 0;
|
|
if (resource_int_value(device_get_name(dev), device_get_unit(dev), "buffersize", &sz) == 0) {
|
|
x = sz;
|
|
RANGE(sz, min, max);
|
|
if (x != sz)
|
|
device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, min, max, sz);
|
|
x = min;
|
|
while (x < sz)
|
|
x <<= 1;
|
|
if (x > sz)
|
|
x >>= 1;
|
|
if (x != sz) {
|
|
device_printf(dev, "'buffersize=%d' hint is not a power of 2, using %d\n", sz, x);
|
|
sz = x;
|
|
}
|
|
} else {
|
|
sz = deflt;
|
|
}
|
|
|
|
d->bufsz = sz;
|
|
|
|
return sz;
|
|
}
|
|
|
|
int
|
|
pcm_register(device_t dev, void *devinfo, int numplay, int numrec)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
|
|
if (pcm_veto_load) {
|
|
device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load);
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev");
|
|
|
|
d->flags = 0;
|
|
d->dev = dev;
|
|
d->devinfo = devinfo;
|
|
d->devcount = 0;
|
|
d->reccount = 0;
|
|
d->playcount = 0;
|
|
d->vchancount = 0;
|
|
d->inprog = 0;
|
|
|
|
SLIST_INIT(&d->channels);
|
|
SLIST_INIT(&d->channels);
|
|
|
|
if (((numplay == 0) || (numrec == 0)) && (numplay != numrec))
|
|
d->flags |= SD_F_SIMPLEX;
|
|
|
|
d->fakechan = fkchan_setup(dev);
|
|
chn_init(d->fakechan, NULL, 0);
|
|
|
|
#ifdef SND_DYNSYSCTL
|
|
sysctl_ctx_init(&d->sysctl_tree);
|
|
d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree,
|
|
SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO,
|
|
device_get_nameunit(dev), CTLFLAG_RD, 0, "");
|
|
if (d->sysctl_tree_top == NULL) {
|
|
sysctl_ctx_free(&d->sysctl_tree);
|
|
goto no;
|
|
}
|
|
SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)),
|
|
OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "");
|
|
#endif
|
|
if (numplay > 0)
|
|
vchan_initsys(dev);
|
|
if (numplay == 1)
|
|
d->flags |= SD_F_AUTOVCHAN;
|
|
|
|
sndstat_register(dev, d->status, sndstat_prepare_pcm);
|
|
return 0;
|
|
no:
|
|
snd_mtxfree(d->lock);
|
|
return ENXIO;
|
|
}
|
|
|
|
int
|
|
pcm_unregister(device_t dev)
|
|
{
|
|
struct snddev_info *d = device_get_softc(dev);
|
|
struct snddev_channel *sce;
|
|
struct pcm_channel *ch;
|
|
|
|
snd_mtxlock(d->lock);
|
|
if (d->inprog) {
|
|
device_printf(dev, "unregister: operation in progress\n");
|
|
snd_mtxunlock(d->lock);
|
|
return EBUSY;
|
|
}
|
|
if (sndstat_busy() != 0) {
|
|
device_printf(dev, "unregister: sndstat busy\n");
|
|
snd_mtxunlock(d->lock);
|
|
return EBUSY;
|
|
}
|
|
|
|
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
ch = sce->channel;
|
|
if (ch->refcount > 0) {
|
|
device_printf(dev, "unregister: channel %s busy (pid %d)\n", ch->name, ch->pid);
|
|
snd_mtxunlock(d->lock);
|
|
return EBUSY;
|
|
}
|
|
}
|
|
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
destroy_dev(sce->dsp_devt);
|
|
destroy_dev(sce->dspW_devt);
|
|
destroy_dev(sce->audio_devt);
|
|
if (sce->dspr_devt)
|
|
destroy_dev(sce->dspr_devt);
|
|
}
|
|
|
|
if (mixer_uninit(dev)) {
|
|
device_printf(dev, "unregister: mixer busy\n");
|
|
snd_mtxunlock(d->lock);
|
|
return EBUSY;
|
|
}
|
|
|
|
#ifdef SND_DYNSYSCTL
|
|
d->sysctl_tree_top = NULL;
|
|
sysctl_ctx_free(&d->sysctl_tree);
|
|
#endif
|
|
while (!SLIST_EMPTY(&d->channels))
|
|
pcm_killchan(dev);
|
|
|
|
chn_kill(d->fakechan);
|
|
fkchan_kill(d->fakechan);
|
|
|
|
sndstat_unregister(dev);
|
|
snd_mtxunlock(d->lock);
|
|
snd_mtxfree(d->lock);
|
|
return 0;
|
|
}
|
|
|
|
/************************************************************************/
|
|
|
|
static int
|
|
sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose)
|
|
{
|
|
struct snddev_info *d;
|
|
struct snddev_channel *sce;
|
|
struct pcm_channel *c;
|
|
struct pcm_feeder *f;
|
|
int pc, rc, vc;
|
|
|
|
if (verbose < 1)
|
|
return 0;
|
|
|
|
d = device_get_softc(dev);
|
|
if (!d)
|
|
return ENXIO;
|
|
|
|
snd_mtxlock(d->lock);
|
|
if (!SLIST_EMPTY(&d->channels)) {
|
|
pc = rc = vc = 0;
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
c = sce->channel;
|
|
if (c->direction == PCMDIR_PLAY) {
|
|
if (c->flags & CHN_F_VIRTUAL)
|
|
vc++;
|
|
else
|
|
pc++;
|
|
} else
|
|
rc++;
|
|
}
|
|
sbuf_printf(s, " (%dp/%dr/%dv channels%s%s)", d->playcount, d->reccount, d->vchancount,
|
|
(d->flags & SD_F_SIMPLEX)? "" : " duplex",
|
|
#ifdef USING_DEVFS
|
|
(device_get_unit(dev) == snd_unit)? " default" : ""
|
|
#else
|
|
""
|
|
#endif
|
|
);
|
|
|
|
if (verbose <= 1) {
|
|
snd_mtxunlock(d->lock);
|
|
return 0;
|
|
}
|
|
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
c = sce->channel;
|
|
sbuf_printf(s, "\n\t");
|
|
|
|
/* it would be better to indent child channels */
|
|
sbuf_printf(s, "%s[%s]: ", c->parentchannel? c->parentchannel->name : "", c->name);
|
|
sbuf_printf(s, "spd %d", c->speed);
|
|
if (c->speed != sndbuf_getspd(c->bufhard))
|
|
sbuf_printf(s, "/%d", sndbuf_getspd(c->bufhard));
|
|
sbuf_printf(s, ", fmt 0x%08x", c->format);
|
|
if (c->format != sndbuf_getfmt(c->bufhard))
|
|
sbuf_printf(s, "/0x%08x", sndbuf_getfmt(c->bufhard));
|
|
sbuf_printf(s, ", flags 0x%08x, 0x%08x", c->flags, c->feederflags);
|
|
if (c->pid != -1)
|
|
sbuf_printf(s, ", pid %d", c->pid);
|
|
sbuf_printf(s, "\n\t");
|
|
|
|
if (c->bufhard != NULL && c->bufsoft != NULL) {
|
|
sbuf_printf(s, "interrupts %d, ", c->interrupts);
|
|
if (c->direction == PCMDIR_REC)
|
|
sbuf_printf(s, "overruns %d, hfree %d, sfree %d",
|
|
c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft));
|
|
else
|
|
sbuf_printf(s, "underruns %d, ready %d",
|
|
c->xruns, sndbuf_getready(c->bufsoft));
|
|
sbuf_printf(s, "\n\t");
|
|
}
|
|
|
|
sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "hardware" : "userland");
|
|
sbuf_printf(s, " -> ");
|
|
f = c->feeder;
|
|
while (f->source != NULL)
|
|
f = f->source;
|
|
while (f != NULL) {
|
|
sbuf_printf(s, "%s", f->class->name);
|
|
if (f->desc->type == FEEDER_FMT)
|
|
sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out);
|
|
if (f->desc->type == FEEDER_RATE)
|
|
sbuf_printf(s, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST));
|
|
if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER)
|
|
sbuf_printf(s, "(0x%08x)", f->desc->out);
|
|
sbuf_printf(s, " -> ");
|
|
f = f->parent;
|
|
}
|
|
sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "userland" : "hardware");
|
|
}
|
|
} else
|
|
sbuf_printf(s, " (mixer only)");
|
|
snd_mtxunlock(d->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/************************************************************************/
|
|
|
|
#ifdef SND_DYNSYSCTL
|
|
int
|
|
sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct snddev_info *d;
|
|
struct snddev_channel *sce;
|
|
struct pcm_channel *c;
|
|
int err, newcnt, cnt, busy;
|
|
int x;
|
|
|
|
d = oidp->oid_arg1;
|
|
|
|
x = pcm_inprog(d, 1);
|
|
if (x != 1) {
|
|
printf("x: %d\n", x);
|
|
pcm_inprog(d, -1);
|
|
return EINPROGRESS;
|
|
}
|
|
|
|
busy = 0;
|
|
cnt = 0;
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
c = sce->channel;
|
|
CHN_LOCK(c);
|
|
if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL)) {
|
|
cnt++;
|
|
if (c->flags & CHN_F_BUSY)
|
|
busy++;
|
|
}
|
|
CHN_UNLOCK(c);
|
|
}
|
|
|
|
newcnt = cnt;
|
|
|
|
err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req);
|
|
|
|
if (err == 0 && req->newptr != NULL) {
|
|
|
|
if (newcnt < 0 || newcnt > SND_MAXVCHANS) {
|
|
pcm_inprog(d, -1);
|
|
return E2BIG;
|
|
}
|
|
|
|
if (newcnt > cnt) {
|
|
/* add new vchans - find a parent channel first */
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
c = sce->channel;
|
|
CHN_LOCK(c);
|
|
/* not a candidate if not a play channel */
|
|
if (c->direction != PCMDIR_PLAY)
|
|
goto next;
|
|
/* not a candidate if a virtual channel */
|
|
if (c->flags & CHN_F_VIRTUAL)
|
|
goto next;
|
|
/* not a candidate if it's in use */
|
|
if (!(c->flags & CHN_F_BUSY) ||
|
|
!(SLIST_EMPTY(&c->children)))
|
|
/*
|
|
* if we get here we're a nonvirtual
|
|
* play channel, and either
|
|
* 1) not busy
|
|
* 2) busy with children, not directly
|
|
* open
|
|
*
|
|
* thus we can add children
|
|
*/
|
|
goto addok;
|
|
next:
|
|
CHN_UNLOCK(c);
|
|
}
|
|
pcm_inprog(d, -1);
|
|
return EBUSY;
|
|
addok:
|
|
c->flags |= CHN_F_BUSY;
|
|
while (err == 0 && newcnt > cnt) {
|
|
err = vchan_create(c);
|
|
if (err == 0)
|
|
cnt++;
|
|
}
|
|
if (SLIST_EMPTY(&c->children))
|
|
c->flags &= ~CHN_F_BUSY;
|
|
CHN_UNLOCK(c);
|
|
} else if (newcnt < cnt) {
|
|
if (busy > newcnt) {
|
|
printf("cnt %d, newcnt %d, busy %d\n", cnt, newcnt, busy);
|
|
pcm_inprog(d, -1);
|
|
return EBUSY;
|
|
}
|
|
|
|
snd_mtxlock(d->lock);
|
|
while (err == 0 && newcnt < cnt) {
|
|
SLIST_FOREACH(sce, &d->channels, link) {
|
|
c = sce->channel;
|
|
CHN_LOCK(c);
|
|
if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL)
|
|
goto remok;
|
|
|
|
CHN_UNLOCK(c);
|
|
}
|
|
snd_mtxunlock(d->lock);
|
|
pcm_inprog(d, -1);
|
|
return EINVAL;
|
|
remok:
|
|
CHN_UNLOCK(c);
|
|
err = vchan_destroy(c);
|
|
if (err == 0)
|
|
cnt--;
|
|
}
|
|
snd_mtxunlock(d->lock);
|
|
}
|
|
}
|
|
pcm_inprog(d, -1);
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
/************************************************************************/
|
|
|
|
static moduledata_t sndpcm_mod = {
|
|
"snd_pcm",
|
|
NULL,
|
|
NULL
|
|
};
|
|
DECLARE_MODULE(snd_pcm, sndpcm_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
|
|
MODULE_VERSION(snd_pcm, PCM_MODVER);
|