Apply more thorough fixes while dealing with device opening and closing:
- Determine open direction using 'flags', not 'mode'. This bug exist since past 4 years. - Don't allow opening the same device twice, be it in a same or different direction. - O_RDWR is allowed, provided that it is done by a single open (for example by mixer(8)) and the underlying hardware support true full-duplex operation. - Do various paranoid checking in case other process/thread trying to hijack the same device twice (or more). MFC after: 5 days
This commit is contained in:
parent
02dbda9d17
commit
3fdb3676ba
@ -170,11 +170,18 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
|
||||
struct snddev_info *d;
|
||||
u_int32_t fmt;
|
||||
int devtype;
|
||||
int rdref;
|
||||
int error;
|
||||
int chnum;
|
||||
|
||||
if (i_dev == NULL || td == NULL)
|
||||
return ENODEV;
|
||||
|
||||
if ((flags & (FREAD | FWRITE)) == 0)
|
||||
return EINVAL;
|
||||
|
||||
d = dsp_get_info(i_dev);
|
||||
devtype = PCMDEV(i_dev);
|
||||
chnum = -1;
|
||||
|
||||
/* decide default format */
|
||||
switch (devtype) {
|
||||
@ -196,34 +203,24 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
|
||||
|
||||
case SND_DEV_DSPREC:
|
||||
fmt = AFMT_U8;
|
||||
if (mode & FWRITE) {
|
||||
if (flags & FWRITE)
|
||||
return EINVAL;
|
||||
}
|
||||
chnum = PCMCHAN(i_dev);
|
||||
break;
|
||||
|
||||
default:
|
||||
panic("impossible devtype %d", devtype);
|
||||
}
|
||||
|
||||
rdref = 0;
|
||||
|
||||
/* lock snddev so nobody else can monkey with it */
|
||||
pcm_lock(d);
|
||||
|
||||
rdch = i_dev->si_drv1;
|
||||
wrch = i_dev->si_drv2;
|
||||
|
||||
if ((dsp_get_flags(i_dev) & SD_F_SIMPLEX) && (rdch || wrch)) {
|
||||
/* we're a simplex device and already open, no go */
|
||||
pcm_unlock(d);
|
||||
return EBUSY;
|
||||
}
|
||||
|
||||
if (((flags & FREAD) && rdch) || ((flags & FWRITE) && wrch)) {
|
||||
/*
|
||||
* device already open in one or both directions that
|
||||
* the opener wants; we can't handle this.
|
||||
*/
|
||||
if (rdch || wrch || ((dsp_get_flags(i_dev) & SD_F_SIMPLEX) &&
|
||||
(flags & (FREAD | FWRITE)) == (FREAD | FWRITE))) {
|
||||
/* simplex or not, better safe than sorry. */
|
||||
pcm_unlock(d);
|
||||
return EBUSY;
|
||||
}
|
||||
@ -237,67 +234,48 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
|
||||
if (flags & FREAD) {
|
||||
/* open for read */
|
||||
pcm_unlock(d);
|
||||
if (devtype == SND_DEV_DSPREC)
|
||||
rdch = pcm_chnalloc(d, PCMDIR_REC, td->td_proc->p_pid, PCMCHAN(i_dev));
|
||||
else
|
||||
rdch = pcm_chnalloc(d, PCMDIR_REC, td->td_proc->p_pid, -1);
|
||||
if (!rdch) {
|
||||
/* no channel available, exit */
|
||||
return EBUSY;
|
||||
}
|
||||
/* got a channel, already locked for us */
|
||||
if (chn_reset(rdch, fmt) ||
|
||||
(fmt && chn_setspeed(rdch, DSP_DEFAULT_SPEED))) {
|
||||
pcm_chnrelease(rdch);
|
||||
pcm_lock(d);
|
||||
i_dev->si_drv1 = NULL;
|
||||
pcm_unlock(d);
|
||||
return ENODEV;
|
||||
error = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, chnum);
|
||||
if (error != 0 && error != EBUSY && chnum != -1 && (flags & FWRITE))
|
||||
error = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, -1);
|
||||
|
||||
if (error == 0 && (chn_reset(rdch, fmt) ||
|
||||
(fmt && chn_setspeed(rdch, DSP_DEFAULT_SPEED))))
|
||||
error = ENODEV;
|
||||
|
||||
if (error != 0) {
|
||||
if (rdch)
|
||||
pcm_chnrelease(rdch);
|
||||
return error;
|
||||
}
|
||||
|
||||
if (flags & O_NONBLOCK)
|
||||
rdch->flags |= CHN_F_NBIO;
|
||||
pcm_chnref(rdch, 1);
|
||||
CHN_UNLOCK(rdch);
|
||||
rdref = 1;
|
||||
/*
|
||||
* Record channel created, ref'ed and unlocked
|
||||
*/
|
||||
pcm_lock(d);
|
||||
pcm_lock(d);
|
||||
}
|
||||
|
||||
if (flags & FWRITE) {
|
||||
/* open for write */
|
||||
pcm_unlock(d);
|
||||
wrch = pcm_chnalloc(d, PCMDIR_PLAY, td->td_proc->p_pid, -1);
|
||||
error = 0;
|
||||
error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, chnum);
|
||||
if (error != 0 && error != EBUSY && chnum != -1 && (flags & FREAD))
|
||||
error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, -1);
|
||||
|
||||
if (!wrch)
|
||||
error = EBUSY; /* XXX Right return code? */
|
||||
else if (chn_reset(wrch, fmt) ||
|
||||
(fmt && chn_setspeed(wrch, DSP_DEFAULT_SPEED)))
|
||||
if (error == 0 && (chn_reset(wrch, fmt) ||
|
||||
(fmt && chn_setspeed(wrch, DSP_DEFAULT_SPEED))))
|
||||
error = ENODEV;
|
||||
|
||||
if (error != 0) {
|
||||
if (wrch) {
|
||||
/*
|
||||
* Free play channel
|
||||
*/
|
||||
if (wrch)
|
||||
pcm_chnrelease(wrch);
|
||||
pcm_lock(d);
|
||||
i_dev->si_drv2 = NULL;
|
||||
pcm_unlock(d);
|
||||
}
|
||||
if (rdref) {
|
||||
if (rdch) {
|
||||
/*
|
||||
* Lock, deref and release previously created record channel
|
||||
*/
|
||||
CHN_LOCK(rdch);
|
||||
pcm_chnref(rdch, -1);
|
||||
pcm_chnrelease(rdch);
|
||||
pcm_lock(d);
|
||||
i_dev->si_drv1 = NULL;
|
||||
pcm_unlock(d);
|
||||
}
|
||||
|
||||
return error;
|
||||
@ -328,40 +306,17 @@ dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td)
|
||||
pcm_lock(d);
|
||||
rdch = i_dev->si_drv1;
|
||||
wrch = i_dev->si_drv2;
|
||||
if (rdch && td->td_proc->p_pid != rdch->pid)
|
||||
rdch = NULL;
|
||||
if (wrch && td->td_proc->p_pid != wrch->pid)
|
||||
wrch = NULL;
|
||||
pcm_unlock(d);
|
||||
|
||||
refs = 0;
|
||||
|
||||
if (rdch) {
|
||||
CHN_LOCK(rdch);
|
||||
refs += pcm_chnref(rdch, -1);
|
||||
CHN_UNLOCK(rdch);
|
||||
}
|
||||
if (wrch) {
|
||||
CHN_LOCK(wrch);
|
||||
refs += pcm_chnref(wrch, -1);
|
||||
CHN_UNLOCK(wrch);
|
||||
}
|
||||
|
||||
/*
|
||||
* If there are no more references, release the channels.
|
||||
*/
|
||||
if ((rdch || wrch) && refs == 0) {
|
||||
|
||||
pcm_lock(d);
|
||||
|
||||
if (pcm_getfakechan(d))
|
||||
pcm_getfakechan(d)->flags = 0;
|
||||
|
||||
i_dev->si_drv1 = NULL;
|
||||
i_dev->si_drv2 = NULL;
|
||||
|
||||
dsp_set_flags(i_dev, dsp_get_flags(i_dev) & ~SD_F_TRANSIENT);
|
||||
|
||||
pcm_unlock(d);
|
||||
|
||||
if (rdch || wrch) {
|
||||
refs = 0;
|
||||
if (rdch) {
|
||||
CHN_LOCK(rdch);
|
||||
refs += pcm_chnref(rdch, -1);
|
||||
chn_abort(rdch); /* won't sleep */
|
||||
rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD);
|
||||
chn_reset(rdch, 0);
|
||||
@ -369,6 +324,7 @@ dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td)
|
||||
}
|
||||
if (wrch) {
|
||||
CHN_LOCK(wrch);
|
||||
refs += pcm_chnref(wrch, -1);
|
||||
/*
|
||||
* XXX: Maybe the right behaviour is to abort on non_block.
|
||||
* It seems that mplayer flushes the audio queue by quickly
|
||||
@ -381,6 +337,23 @@ dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td)
|
||||
chn_reset(wrch, 0);
|
||||
pcm_chnrelease(wrch);
|
||||
}
|
||||
|
||||
pcm_lock(d);
|
||||
if (rdch)
|
||||
i_dev->si_drv1 = NULL;
|
||||
if (wrch)
|
||||
i_dev->si_drv2 = NULL;
|
||||
/*
|
||||
* If there are no more references, release the channels.
|
||||
*/
|
||||
if (refs == 0 && i_dev->si_drv1 == NULL &&
|
||||
i_dev->si_drv2 == NULL) {
|
||||
if (pcm_getfakechan(d))
|
||||
pcm_getfakechan(d)->flags = 0;
|
||||
/* What is this?!? */
|
||||
dsp_set_flags(i_dev, dsp_get_flags(i_dev) & ~SD_F_TRANSIENT);
|
||||
}
|
||||
pcm_unlock(d);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -445,15 +418,24 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *
|
||||
*/
|
||||
|
||||
d = dsp_get_info(i_dev);
|
||||
if (IOCGROUP(cmd) == 'M')
|
||||
return mixer_ioctl(d->mixer_dev, cmd, arg, mode, td);
|
||||
if (IOCGROUP(cmd) == 'M') {
|
||||
/*
|
||||
* This is at least, a bug to bug compatible with OSS.
|
||||
*/
|
||||
if (d->mixer_dev != NULL)
|
||||
return mixer_ioctl(d->mixer_dev, cmd, arg, -1, td);
|
||||
else
|
||||
return EBADF;
|
||||
}
|
||||
|
||||
getchns(i_dev, &rdch, &wrch, 0);
|
||||
|
||||
kill = 0;
|
||||
if (wrch && (wrch->flags & CHN_F_DEAD))
|
||||
if (wrch && ((wrch->flags & CHN_F_DEAD) ||
|
||||
td->td_proc->p_pid != wrch->pid))
|
||||
kill |= 1;
|
||||
if (rdch && (rdch->flags & CHN_F_DEAD))
|
||||
if (rdch && ((rdch->flags & CHN_F_DEAD) ||
|
||||
td->td_proc->p_pid != rdch->pid))
|
||||
kill |= 2;
|
||||
if (kill == 3) {
|
||||
relchns(i_dev, rdch, wrch, 0);
|
||||
@ -1162,8 +1144,8 @@ dsp_clone(void *arg, struct ucred *cred, char *name, int namelen,
|
||||
struct snddev_info *pcm_dev;
|
||||
struct snddev_channel *pcm_chan;
|
||||
int i, unit, devtype;
|
||||
int devtypes[3] = {SND_DEV_DSP, SND_DEV_DSP16, SND_DEV_AUDIO};
|
||||
char *devnames[3] = {"dsp", "dspW", "audio"};
|
||||
static int devtypes[3] = {SND_DEV_DSP, SND_DEV_DSP16, SND_DEV_AUDIO};
|
||||
static char *devnames[3] = {"dsp", "dspW", "audio"};
|
||||
|
||||
if (*dev != NULL)
|
||||
return;
|
||||
|
@ -269,10 +269,14 @@ int
|
||||
mixer_uninit(device_t dev)
|
||||
{
|
||||
int i;
|
||||
struct snddev_info *d;
|
||||
struct snd_mixer *m;
|
||||
struct cdev *pdev;
|
||||
|
||||
d = device_get_softc(dev);
|
||||
pdev = mixer_get_devt(dev);
|
||||
if (d == NULL || pdev == NULL || pdev->si_drv1 == NULL)
|
||||
return EBADF;
|
||||
m = pdev->si_drv1;
|
||||
snd_mtxlock(m->lock);
|
||||
|
||||
@ -294,6 +298,8 @@ mixer_uninit(device_t dev)
|
||||
snd_mtxfree(m->lock);
|
||||
kobj_delete((kobj_t)m, M_MIXER);
|
||||
|
||||
d->mixer_dev = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -465,10 +471,16 @@ mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread
|
||||
int v = -1, j = cmd & 0xff;
|
||||
|
||||
m = i_dev->si_drv1;
|
||||
if (mode != -1 && !m->busy)
|
||||
|
||||
if (m == NULL)
|
||||
return EBADF;
|
||||
|
||||
snd_mtxlock(m->lock);
|
||||
if (mode != -1 && !m->busy) {
|
||||
snd_mtxunlock(m->lock);
|
||||
return EBADF;
|
||||
}
|
||||
|
||||
if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) {
|
||||
if (j == SOUND_MIXER_RECSRC)
|
||||
ret = mixer_setrecsrc(m, *arg_i);
|
||||
|
@ -158,30 +158,45 @@ 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)
|
||||
/* return error status and a locked channel */
|
||||
int
|
||||
pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction,
|
||||
pid_t pid, int chnum)
|
||||
{
|
||||
struct pcm_channel *c;
|
||||
struct snddev_channel *sce;
|
||||
int err;
|
||||
int err, ret;
|
||||
|
||||
retry_chnalloc:
|
||||
ret = ENODEV;
|
||||
/* 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) {
|
||||
if (chnum < 0 || sce->chan_num == chnum) {
|
||||
c->flags |= CHN_F_BUSY;
|
||||
c->pid = pid;
|
||||
return c;
|
||||
*ch = c;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (sce->chan_num == chnum) {
|
||||
if (c->direction != direction)
|
||||
err = EOPNOTSUPP;
|
||||
else if (c->flags & CHN_F_BUSY)
|
||||
err = EBUSY;
|
||||
else
|
||||
err = EINVAL;
|
||||
CHN_UNLOCK(c);
|
||||
return err;
|
||||
} else if (c->direction == direction && (c->flags & CHN_F_BUSY))
|
||||
ret = EBUSY;
|
||||
CHN_UNLOCK(c);
|
||||
}
|
||||
|
||||
/* no channel available */
|
||||
if (direction == PCMDIR_PLAY && d->vchancount > 0 &&
|
||||
if (chnum == -1 && direction == PCMDIR_PLAY && d->vchancount > 0 &&
|
||||
d->vchancount < snd_maxautovchans &&
|
||||
d->devcount <= PCMMAXCHAN) {
|
||||
/* try to create a vchan */
|
||||
@ -192,16 +207,17 @@ pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum)
|
||||
!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);
|
||||
if (!err) {
|
||||
chnum = -2;
|
||||
goto retry_chnalloc;
|
||||
} else
|
||||
device_printf(d->dev, "%s: vchan_create(%s) == %d\n", __func__, c->name, err);
|
||||
} else
|
||||
CHN_UNLOCK(c);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* release a locked channel and unlock it */
|
||||
@ -259,16 +275,17 @@ pcm_setmaxautovchans(struct snddev_info *d, int num)
|
||||
c = sce->channel;
|
||||
CHN_LOCK(c);
|
||||
if ((c->direction == PCMDIR_PLAY) &&
|
||||
!(c->flags & CHN_F_BUSY) &&
|
||||
(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == 0 &&
|
||||
SLIST_EMPTY(&c->children)) {
|
||||
c->flags |= CHN_F_BUSY;
|
||||
err = vchan_create(c);
|
||||
if (err) {
|
||||
c->flags &= ~CHN_F_BUSY;
|
||||
device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
|
||||
device_printf(d->dev, "%s: vchan_create(%s) == %d\n", __func__, c->name, err);
|
||||
} else {
|
||||
CHN_UNLOCK(c);
|
||||
return;
|
||||
}
|
||||
CHN_UNLOCK(c);
|
||||
return;
|
||||
}
|
||||
CHN_UNLOCK(c);
|
||||
}
|
||||
@ -338,9 +355,9 @@ sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS)
|
||||
v = snd_maxautovchans;
|
||||
error = sysctl_handle_int(oidp, &v, sizeof(v), req);
|
||||
if (error == 0 && req->newptr != NULL) {
|
||||
if (v < 0 || v > (PCMMAXCHAN + 1) || pcm_devclass == NULL)
|
||||
if (v < 0 || v > (PCMMAXCHAN + 1))
|
||||
return EINVAL;
|
||||
if (v != snd_maxautovchans) {
|
||||
if (pcm_devclass != NULL && v != snd_maxautovchans) {
|
||||
for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) {
|
||||
d = devclass_get_softc(pcm_devclass, i);
|
||||
if (!d)
|
||||
@ -362,9 +379,11 @@ SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW,
|
||||
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;
|
||||
struct snddev_channel *sce;
|
||||
struct pcm_channel *ch, *tmpch;
|
||||
char *dirs;
|
||||
int direction, err, *pnum;
|
||||
u_int32_t flsearch = 0;
|
||||
int direction, err, rpnum, *pnum;
|
||||
|
||||
switch(dir) {
|
||||
case PCMDIR_PLAY:
|
||||
@ -383,6 +402,7 @@ pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t c
|
||||
dirs = "virtual";
|
||||
direction = PCMDIR_PLAY;
|
||||
pnum = &d->vchancount;
|
||||
flsearch = CHN_F_VIRTUAL;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -401,14 +421,61 @@ pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t c
|
||||
}
|
||||
|
||||
snd_mtxlock(d->lock);
|
||||
ch->num = (*pnum)++;
|
||||
ch->num = 0;
|
||||
rpnum = 0;
|
||||
SLIST_FOREACH(sce, &d->channels, link) {
|
||||
if (sce == NULL || sce->channel == NULL)
|
||||
continue;
|
||||
tmpch = sce->channel;
|
||||
if (direction != tmpch->direction ||
|
||||
(tmpch->flags & CHN_F_VIRTUAL) != flsearch)
|
||||
continue;
|
||||
if (ch->num == tmpch->num)
|
||||
ch->num++;
|
||||
else {
|
||||
/*
|
||||
* Channel numbering screwed. Bail out, and do the
|
||||
* hard way lookup.
|
||||
*/
|
||||
device_printf(d->dev,
|
||||
"%s: channel numbering dirs=%s screwed.\n",
|
||||
__func__, dirs);
|
||||
ch->num = 0;
|
||||
goto retry_num_search;
|
||||
}
|
||||
rpnum++;
|
||||
}
|
||||
goto retry_num_search_out;
|
||||
retry_num_search:
|
||||
rpnum = 0;
|
||||
SLIST_FOREACH(sce, &d->channels, link) {
|
||||
if (sce == NULL || sce->channel == NULL)
|
||||
continue;
|
||||
tmpch = sce->channel;
|
||||
if (direction != tmpch->direction ||
|
||||
(tmpch->flags & CHN_F_VIRTUAL) != flsearch)
|
||||
continue;
|
||||
if (ch->num == tmpch->num) {
|
||||
ch->num++;
|
||||
goto retry_num_search;
|
||||
}
|
||||
rpnum++;
|
||||
}
|
||||
retry_num_search_out:
|
||||
if (*pnum != rpnum) {
|
||||
device_printf(d->dev,
|
||||
"%s: pnum screwed : dirs=%s, pnum=%d, rpnum=%d\n",
|
||||
__func__, dirs, *pnum, rpnum);
|
||||
*pnum = rpnum;
|
||||
}
|
||||
(*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);
|
||||
snprintf(ch->name, CHN_NAMELEN, "%s:%s:%d", device_get_nameunit(ch->dev), dirs, ch->num);
|
||||
|
||||
err = chn_init(ch, devinfo, dir, direction);
|
||||
if (err) {
|
||||
@ -448,7 +515,10 @@ int
|
||||
pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch)
|
||||
{
|
||||
struct snddev_channel *sce, *tmp, *after;
|
||||
unsigned rdevcount;
|
||||
int device = device_get_unit(d->dev);
|
||||
int stop;
|
||||
size_t namelen;
|
||||
|
||||
/*
|
||||
* Note it's confusing nomenclature.
|
||||
@ -467,76 +537,91 @@ pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch)
|
||||
|
||||
snd_mtxlock(d->lock);
|
||||
sce->channel = ch;
|
||||
if (SLIST_EMPTY(&d->channels)) {
|
||||
SLIST_INSERT_HEAD(&d->channels, sce, link);
|
||||
sce->chan_num = 0;
|
||||
} else {
|
||||
sce->chan_num = 0;
|
||||
retry_search:
|
||||
SLIST_FOREACH(tmp, &d->channels, link) {
|
||||
if (tmp == NULL)
|
||||
continue;
|
||||
if (sce->chan_num == tmp->chan_num) {
|
||||
sce->chan_num++;
|
||||
goto retry_search;
|
||||
}
|
||||
sce->chan_num = 0;
|
||||
after = NULL;
|
||||
stop = 0;
|
||||
retry_chan_num_search:
|
||||
/*
|
||||
* Look for possible channel numbering collision. This may not
|
||||
* be optimized, but it will ensure that no collision occured.
|
||||
* Creating maximum possible channels (256 channels) will cost
|
||||
* us at most 32895 cycles, but this can be considered cheap
|
||||
* since none of the locking/unlocking operations involved.
|
||||
*
|
||||
* Micro optimization, channel ordering:
|
||||
* hw,hw,hw,vch,vch,vch,rec
|
||||
*/
|
||||
rdevcount = 0;
|
||||
SLIST_FOREACH(tmp, &d->channels, link) {
|
||||
if (tmp == NULL || tmp->channel == NULL)
|
||||
continue;
|
||||
if (sce->chan_num == tmp->chan_num) {
|
||||
sce->chan_num++;
|
||||
after = NULL;
|
||||
stop = 0;
|
||||
goto retry_chan_num_search;
|
||||
}
|
||||
/*
|
||||
* Don't overflow PCMMKMINOR / PCMMAXCHAN.
|
||||
*/
|
||||
if (sce->chan_num > PCMMAXCHAN) {
|
||||
snd_mtxunlock(d->lock);
|
||||
device_printf(d->dev,
|
||||
"%s: WARNING: sce->chan_num overflow! (%d)\n",
|
||||
__func__, sce->chan_num);
|
||||
free(sce, M_DEVBUF);
|
||||
return E2BIG;
|
||||
}
|
||||
/*
|
||||
* Micro optimization, channel ordering:
|
||||
* hw,hw,hw,vch,vch,vch,rec
|
||||
*/
|
||||
after = NULL;
|
||||
if (ch->flags & CHN_F_VIRTUAL) {
|
||||
/* virtual channel to the end */
|
||||
SLIST_FOREACH(tmp, &d->channels, link) {
|
||||
if (stop == 0) {
|
||||
if (ch->flags & CHN_F_VIRTUAL) {
|
||||
if (tmp->channel->direction == PCMDIR_REC)
|
||||
break;
|
||||
stop = 1;
|
||||
else
|
||||
after = tmp;
|
||||
} else if (ch->direction == PCMDIR_REC) {
|
||||
after = tmp;
|
||||
}
|
||||
} else {
|
||||
if (ch->direction == PCMDIR_REC) {
|
||||
SLIST_FOREACH(tmp, &d->channels, link) {
|
||||
} else {
|
||||
if (tmp->channel->direction != PCMDIR_PLAY ||
|
||||
(tmp->channel->flags & CHN_F_VIRTUAL)) {
|
||||
stop = 1;
|
||||
} else {
|
||||
after = tmp;
|
||||
}
|
||||
} else {
|
||||
SLIST_FOREACH(tmp, &d->channels, link) {
|
||||
if (tmp->channel->direction == PCMDIR_REC)
|
||||
break;
|
||||
if (!(tmp->channel->flags & CHN_F_VIRTUAL))
|
||||
after = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (after == NULL) {
|
||||
SLIST_INSERT_HEAD(&d->channels, sce, link);
|
||||
} else {
|
||||
SLIST_INSERT_AFTER(after, sce, link);
|
||||
}
|
||||
rdevcount++;
|
||||
}
|
||||
/*
|
||||
* Don't overflow PCMMKMINOR / PCMMAXCHAN.
|
||||
*/
|
||||
if (sce->chan_num > PCMMAXCHAN) {
|
||||
snd_mtxunlock(d->lock);
|
||||
device_printf(d->dev,
|
||||
"%s: WARNING: sce->chan_num overflow! (%d)\n",
|
||||
__func__, sce->chan_num);
|
||||
free(sce, M_DEVBUF);
|
||||
return E2BIG;
|
||||
}
|
||||
if (d->devcount != rdevcount) {
|
||||
device_printf(d->dev,
|
||||
"%s: WARNING: devcount screwed! d->devcount=%u, rdevcount=%u\n",
|
||||
__func__, d->devcount, rdevcount);
|
||||
d->devcount = rdevcount;
|
||||
}
|
||||
d->devcount++;
|
||||
if (after == NULL) {
|
||||
SLIST_INSERT_HEAD(&d->channels, sce, link);
|
||||
} else {
|
||||
SLIST_INSERT_AFTER(after, sce, link);
|
||||
}
|
||||
|
||||
namelen = strlen(ch->name);
|
||||
if ((CHN_NAMELEN - namelen) > 10) { /* ":dspXX.YYY" */
|
||||
snprintf(ch->name + namelen,
|
||||
CHN_NAMELEN - namelen, ":dsp%d.%d",
|
||||
device, sce->chan_num);
|
||||
}
|
||||
snd_mtxunlock(d->lock);
|
||||
sce->dsp_devt= make_dev(&dsp_cdevsw,
|
||||
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,
|
||||
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,
|
||||
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);
|
||||
@ -612,20 +697,6 @@ pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo)
|
||||
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;
|
||||
}
|
||||
|
||||
@ -650,10 +721,37 @@ int
|
||||
pcm_setstatus(device_t dev, char *str)
|
||||
{
|
||||
struct snddev_info *d = device_get_softc(dev);
|
||||
struct snddev_channel *sce;
|
||||
struct pcm_channel *ch;
|
||||
int err;
|
||||
|
||||
snd_mtxlock(d->lock);
|
||||
strncpy(d->status, str, SND_STATUSLEN);
|
||||
snd_mtxunlock(d->lock);
|
||||
if (snd_maxautovchans > 0 && (d->flags & SD_F_AUTOVCHAN) &&
|
||||
d->vchancount == 0) {
|
||||
SLIST_FOREACH(sce, &d->channels, link) {
|
||||
if (sce == NULL || sce->channel == NULL)
|
||||
continue;
|
||||
ch = sce->channel;
|
||||
CHN_LOCK(ch);
|
||||
if (ch->direction == PCMDIR_PLAY &&
|
||||
(ch->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == 0 &&
|
||||
SLIST_EMPTY(&ch->children)) {
|
||||
ch->flags |= CHN_F_BUSY;
|
||||
err = vchan_create(ch);
|
||||
if (err) {
|
||||
ch->flags &= ~CHN_F_BUSY;
|
||||
device_printf(d->dev, "%s: vchan_create(%s) == %d\n",
|
||||
__func__, ch->name, err);
|
||||
} else {
|
||||
CHN_UNLOCK(ch);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
CHN_UNLOCK(ch);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -761,8 +859,8 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec)
|
||||
OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "");
|
||||
#endif
|
||||
if (numplay > 0) {
|
||||
vchan_initsys(dev);
|
||||
d->flags |= SD_F_AUTOVCHAN;
|
||||
vchan_initsys(dev);
|
||||
}
|
||||
|
||||
sndstat_register(dev, d->status, sndstat_prepare_pcm);
|
||||
@ -973,10 +1071,8 @@ sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS)
|
||||
*/
|
||||
d = oidp->oid_arg1;
|
||||
|
||||
if (!(d->flags & SD_F_AUTOVCHAN)) {
|
||||
pcm_inprog(d, -1);
|
||||
if (!(d->flags & SD_F_AUTOVCHAN))
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
cnt = 0;
|
||||
SLIST_FOREACH(sce, &d->channels, link) {
|
||||
|
@ -212,7 +212,7 @@ struct sysctl_ctx_list *snd_sysctl_tree(device_t dev);
|
||||
struct sysctl_oid *snd_sysctl_tree_top(device_t dev);
|
||||
|
||||
struct pcm_channel *pcm_getfakechan(struct snddev_info *d);
|
||||
struct pcm_channel *pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum);
|
||||
int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, int chnum);
|
||||
int pcm_chnrelease(struct pcm_channel *c);
|
||||
int pcm_chnref(struct pcm_channel *c, int ref);
|
||||
int pcm_inprog(struct snddev_info *d, int delta);
|
||||
|
Loading…
Reference in New Issue
Block a user