sound: Allocate vchans on-demand

Refactor pcm_chnalloc() and merge with parts of vchan_setnew() (now
removed) and dsp_open()’s channel creation into a new dsp_chn_alloc()
function. The function is responsible for either using a free HW channel
(if vchans are disabled), or allocating a new vchan.

Clean up allocated vchans associated with a given dsp_cdevpriv on
dsp_close() instead of leaving them unused.

hw.snd.vchans_enable (previously hw.snd.maxautovchans) and
dev.pcm.X.{play|rec}.vchans now work as tunables to only enable/disable
vchans, as opposed to setting their number and/or (de-)allocating
vchans. Since these sysctls do not trigger any (de-)allocations anymore,
their effect is instantaneous, whereas before we could have frozen the
machine (when trying to allocate new vchans) when setting
dev.pcm.X.{play|rec}.vchans to a very large value.

Create a new "primary" channel sublist so that we do not waste time
looping through all channels in dsp_chn_alloc(), since we are only
looking for a parent channel to either use, or add a new vchan to. This
guarantees a steady traversal speed, as the parent channels are most
likely going to be just a handful (2). What was currently in place was a
loop through the whole channel list, which meant that the traversal
would take longer the more channels were added to that list.

Sponsored by:	The FreeBSD Foundation
MFC after:	1 week
Reviewed by:	dev_submerge.ch
Differential Revision:	https://reviews.freebsd.org/D47917

(cherry picked from commit 02d4eeabfd73e6a827f5d42601e99aad92060b04)
This commit is contained in:
Christos Margiolis 2025-02-25 13:43:39 +02:00
parent a8fc617701
commit 960ee80949
8 changed files with 255 additions and 395 deletions

View file

@ -23,7 +23,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd March 24, 2024
.Dd December 4, 2024
.Dt SOUND 4
.Os
.Sh NAME
@ -358,14 +358,12 @@ A value of 0 will use a low and aggressive latency profile which can result
in possible underruns if the application cannot keep up with a rapid irq
rate, especially during high workload.
The default value is 1, which is considered a moderate/safe latency profile.
.It Va hw.snd.maxautovchans
Global VCHAN setting that only affects devices with at least one playback or
recording channel available.
The sound system will dynamically create up to this many VCHANs.
Set to
.Dq 0
if no VCHANs are desired.
Maximum value is 256.
.It Va hw.snd.vchans_enable
Global VCHAN setting to enable (1) or disable (0) VCHANs.
This setting can be overridden for an individual device by using the
.Va dev.pcm.%d.[play|rec].vchans
tunables.
Default is enabled.
.It Va hw.snd.report_soft_formats
Controls the internal format conversion if it is
available transparently to the application software.
@ -432,11 +430,8 @@ The recommended way to use bitperfect mode is to disable VCHANs and enable this
sysctl.
Default is disabled.
.It Va dev.pcm.%d.[play|rec].vchans
The current number of VCHANs allocated per device.
This can be set to preallocate a certain number of VCHANs.
Setting this value to
.Dq 0
will disable VCHANs for this device.
Enable (1) or disable (0) VCHANs.
Default is enabled.
.It Va dev.pcm.%d.[play|rec].vchanformat
Format for VCHAN mixing.
All playback paths will be converted to this format before the mixing

View file

@ -1172,7 +1172,7 @@ chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls,
struct feeder_class *fc;
struct snd_dbuf *b, *bs;
char buf[CHN_NAMELEN];
int i, direction;
int err, i, direction;
PCM_BUSYASSERT(d);
PCM_LOCKASSERT(d);
@ -1279,8 +1279,18 @@ chn_init(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls,
bs->shadbuf = malloc(bs->sl, M_DEVBUF, M_WAITOK);
}
if ((c->flags & CHN_F_VIRTUAL) == 0) {
CHN_LOCK(c);
err = chn_reset(c, c->format, c->speed);
CHN_UNLOCK(c);
if (err != 0)
goto fail;
}
PCM_LOCK(d);
CHN_INSERT_SORT_ASCEND(d, c, channels.pcm);
if ((c->flags & CHN_F_VIRTUAL) == 0)
CHN_INSERT_SORT_ASCEND(d, c, channels.pcm.primary);
switch (c->type) {
case PCMDIR_PLAY:
@ -1332,6 +1342,8 @@ chn_kill(struct pcm_channel *c)
PCM_LOCK(d);
CHN_REMOVE(d, c, channels.pcm);
if ((c->flags & CHN_F_VIRTUAL) == 0)
CHN_REMOVE(d, c, channels.pcm.primary);
switch (c->type) {
case PCMDIR_PLAY:

View file

@ -160,6 +160,9 @@ struct pcm_channel {
struct {
SLIST_ENTRY(pcm_channel) link;
} opened;
struct {
SLIST_ENTRY(pcm_channel) link;
} primary;
} pcm;
} channels;

View file

@ -37,6 +37,7 @@
#endif
#include <dev/sound/pcm/sound.h>
#include <dev/sound/pcm/vchan.h>
#include <sys/ctype.h>
#include <sys/lock.h>
#include <sys/rwlock.h>
@ -158,6 +159,81 @@ dsp_unlock_chans(struct dsp_cdevpriv *priv, uint32_t prio)
CHN_UNLOCK(priv->wrch);
}
static int
dsp_chn_alloc(struct snddev_info *d, struct pcm_channel **ch, int direction,
int flags, struct thread *td)
{
struct pcm_channel *c;
char *comm;
pid_t pid;
int err;
bool vdir_enabled;
KASSERT(d != NULL && ch != NULL &&
(direction == PCMDIR_PLAY || direction == PCMDIR_REC),
("%s(): invalid d=%p ch=%p direction=%d",
__func__, d, ch, direction));
PCM_BUSYASSERT(d);
pid = td->td_proc->p_pid;
comm = td->td_proc->p_comm;
vdir_enabled = (direction == PCMDIR_PLAY && d->flags & SD_F_PVCHANS) ||
(direction == PCMDIR_REC && d->flags & SD_F_RVCHANS);
*ch = NULL;
CHN_FOREACH(c, d, channels.pcm.primary) {
CHN_LOCK(c);
if (c->direction != direction) {
CHN_UNLOCK(c);
continue;
}
/* Find an available primary channel to use. */
if ((c->flags & CHN_F_BUSY) == 0 ||
(vdir_enabled && (c->flags & CHN_F_HAS_VCHAN)))
break;
CHN_UNLOCK(c);
}
if (c == NULL)
return (EBUSY);
/*
* We can have the following cases:
* - vchans are enabled, add a new vchan to the primary channel.
* - vchans are disabled, use the primary channel directly.
*/
if (vdir_enabled && ((c->flags & CHN_F_BUSY) == 0 ||
c->flags & CHN_F_HAS_VCHAN)) {
err = vchan_create(c, ch);
CHN_UNLOCK(c);
if (err != 0)
return (err);
CHN_LOCK(*ch);
} else if ((c->flags & CHN_F_BUSY) == 0) {
*ch = c;
} else {
CHN_UNLOCK(c);
return (ENODEV);
}
(*ch)->flags |= CHN_F_BUSY;
if (flags & O_NONBLOCK)
(*ch)->flags |= CHN_F_NBIO;
if (flags & O_EXCL)
(*ch)->flags |= CHN_F_EXCLUSIVE;
(*ch)->pid = pid;
strlcpy((*ch)->comm, (comm != NULL) ? comm : CHN_COMM_UNKNOWN,
sizeof((*ch)->comm));
if ((err = chn_reset(*ch, (*ch)->format, (*ch)->speed)) != 0)
return (err);
chn_vpc_reset(*ch, SND_VOL_C_PCM, 0);
CHN_UNLOCK(*ch);
return (0);
}
#define DSP_F_VALID(x) ((x) & (FREAD | FWRITE))
#define DSP_F_DUPLEX(x) (((x) & (FREAD | FWRITE)) == (FREAD | FWRITE))
#define DSP_F_SIMPLEX(x) (!DSP_F_DUPLEX(x))
@ -168,7 +244,7 @@ static void
dsp_close(void *data)
{
struct dsp_cdevpriv *priv = data;
struct pcm_channel *rdch, *wrch;
struct pcm_channel *rdch, *wrch, *parent;
struct snddev_info *d;
int sg_ids;
@ -214,12 +290,20 @@ dsp_close(void *data)
if (sg_ids != 0)
free_unr(pcmsg_unrhdr, sg_ids);
CHN_LOCK(rdch);
chn_abort(rdch); /* won't sleep */
rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP |
CHN_F_DEAD | CHN_F_EXCLUSIVE);
chn_reset(rdch, 0, 0);
chn_release(rdch);
if (rdch->flags & CHN_F_VIRTUAL) {
parent = rdch->parentchannel;
CHN_LOCK(parent);
CHN_LOCK(rdch);
vchan_destroy(rdch);
CHN_UNLOCK(parent);
} else {
CHN_LOCK(rdch);
chn_abort(rdch); /* won't sleep */
rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP |
CHN_F_DEAD | CHN_F_EXCLUSIVE);
chn_reset(rdch, 0, 0);
chn_release(rdch);
}
}
if (wrch != NULL) {
/*
@ -231,12 +315,20 @@ dsp_close(void *data)
if (sg_ids != 0)
free_unr(pcmsg_unrhdr, sg_ids);
CHN_LOCK(wrch);
chn_flush(wrch); /* may sleep */
wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP |
CHN_F_DEAD | CHN_F_EXCLUSIVE);
chn_reset(wrch, 0, 0);
chn_release(wrch);
if (wrch->flags & CHN_F_VIRTUAL) {
parent = wrch->parentchannel;
CHN_LOCK(parent);
CHN_LOCK(wrch);
vchan_destroy(wrch);
CHN_UNLOCK(parent);
} else {
CHN_LOCK(wrch);
chn_flush(wrch); /* may sleep */
wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP |
CHN_F_DEAD | CHN_F_EXCLUSIVE);
chn_reset(wrch, 0, 0);
chn_release(wrch);
}
}
PCM_LOCK(d);
}
@ -254,10 +346,9 @@ static int
dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
{
struct dsp_cdevpriv *priv;
struct pcm_channel *rdch, *wrch, *ch;
struct pcm_channel *ch;
struct snddev_info *d;
uint32_t fmt, spd;
int error, rderror, wrerror, dir;
int error, dir;
/* Kind of impossible.. */
if (i_dev == NULL || td == NULL)
@ -267,11 +358,11 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
if (!DSP_REGISTERED(d))
return (EBADF);
if (PCM_CHANCOUNT(d) >= PCM_MAXCHANS)
return (ENOMEM);
priv = malloc(sizeof(*priv), M_DEVBUF, M_WAITOK | M_ZERO);
priv->sc = d;
priv->rdch = NULL;
priv->wrch = NULL;
priv->volch = NULL;
error = devfs_set_cdevpriv(priv, dsp_close);
if (error != 0)
@ -334,98 +425,30 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
PCM_ACQUIRE(d);
PCM_UNLOCK(d);
fmt = SND_FORMAT(AFMT_U8, 1, 0);
spd = DSP_DEFAULT_SPEED;
rdch = NULL;
wrch = NULL;
rderror = 0;
wrerror = 0;
if (DSP_F_READ(flags)) {
/* open for read */
rderror = pcm_chnalloc(d, &rdch, PCMDIR_REC,
td->td_proc->p_pid, td->td_proc->p_comm);
if (rderror == 0 && chn_reset(rdch, fmt, spd) != 0)
rderror = ENXIO;
if (rderror != 0) {
if (rdch != NULL)
chn_release(rdch);
if (!DSP_F_DUPLEX(flags)) {
PCM_RELEASE_QUICK(d);
PCM_GIANT_EXIT(d);
return (rderror);
}
rdch = NULL;
} else {
if (flags & O_NONBLOCK)
rdch->flags |= CHN_F_NBIO;
if (flags & O_EXCL)
rdch->flags |= CHN_F_EXCLUSIVE;
chn_vpc_reset(rdch, SND_VOL_C_PCM, 0);
CHN_UNLOCK(rdch);
}
}
if (DSP_F_WRITE(flags)) {
/* open for write */
wrerror = pcm_chnalloc(d, &wrch, PCMDIR_PLAY,
td->td_proc->p_pid, td->td_proc->p_comm);
if (wrerror == 0 && chn_reset(wrch, fmt, spd) != 0)
wrerror = ENXIO;
if (wrerror != 0) {
if (wrch != NULL)
chn_release(wrch);
if (!DSP_F_DUPLEX(flags)) {
if (rdch != NULL) {
/*
* Lock, and release previously created
* record channel
*/
CHN_LOCK(rdch);
chn_release(rdch);
}
PCM_RELEASE_QUICK(d);
PCM_GIANT_EXIT(d);
return (wrerror);
}
wrch = NULL;
} else {
if (flags & O_NONBLOCK)
wrch->flags |= CHN_F_NBIO;
if (flags & O_EXCL)
wrch->flags |= CHN_F_EXCLUSIVE;
chn_vpc_reset(wrch, SND_VOL_C_PCM, 0);
CHN_UNLOCK(wrch);
error = dsp_chn_alloc(d, &priv->wrch, PCMDIR_PLAY, flags, td);
if (error != 0) {
PCM_RELEASE_QUICK(d);
PCM_GIANT_EXIT(d);
return (error);
}
}
PCM_LOCK(d);
if (wrch == NULL && rdch == NULL) {
PCM_RELEASE(d);
PCM_LOCK(d);
CHN_INSERT_HEAD(d, priv->wrch, channels.pcm.opened);
PCM_UNLOCK(d);
}
if (DSP_F_READ(flags)) {
error = dsp_chn_alloc(d, &priv->rdch, PCMDIR_REC, flags, td);
if (error != 0) {
PCM_RELEASE_QUICK(d);
PCM_GIANT_EXIT(d);
return (error);
}
PCM_LOCK(d);
CHN_INSERT_HEAD(d, priv->rdch, channels.pcm.opened);
PCM_UNLOCK(d);
PCM_GIANT_EXIT(d);
if (wrerror != 0)
return (wrerror);
if (rderror != 0)
return (rderror);
return (EINVAL);
}
if (rdch != NULL)
CHN_INSERT_HEAD(d, rdch, channels.pcm.opened);
if (wrch != NULL)
CHN_INSERT_HEAD(d, wrch, channels.pcm.opened);
priv->rdch = rdch;
priv->wrch = wrch;
PCM_RELEASE(d);
PCM_UNLOCK(d);
PCM_RELEASE_QUICK(d);
PCM_GIANT_LEAVE(d);
return (0);

View file

@ -107,62 +107,6 @@ snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand
return bus_setup_intr(dev, res, flags, NULL, hand, param, cookiep);
}
int
pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction,
pid_t pid, char *comm)
{
struct pcm_channel *c;
int err, vchancount;
bool retry;
KASSERT(d != NULL && ch != NULL &&
(direction == PCMDIR_PLAY || direction == PCMDIR_REC),
("%s(): invalid d=%p ch=%p direction=%d pid=%d",
__func__, d, ch, direction, pid));
PCM_BUSYASSERT(d);
*ch = NULL;
vchancount = (direction == PCMDIR_PLAY) ? d->pvchancount :
d->rvchancount;
retry = false;
retry_chnalloc:
/* Scan for a free channel. */
CHN_FOREACH(c, d, channels.pcm) {
CHN_LOCK(c);
if (c->direction != direction) {
CHN_UNLOCK(c);
continue;
}
if (!(c->flags & CHN_F_BUSY)) {
c->flags |= CHN_F_BUSY;
c->pid = pid;
strlcpy(c->comm, (comm != NULL) ? comm :
CHN_COMM_UNKNOWN, sizeof(c->comm));
*ch = c;
return (0);
}
CHN_UNLOCK(c);
}
/* Maybe next time... */
if (retry)
return (EBUSY);
/* No channel available. We also cannot create more VCHANs. */
if (!(vchancount > 0 && vchancount < snd_maxautovchans))
return (ENOTSUP);
/* Increase the VCHAN count and try to get the new channel. */
err = vchan_setnew(d, direction, vchancount + 1);
if (err == 0) {
retry = true;
goto retry_chnalloc;
}
return (err);
}
static int
sysctl_hw_snd_default_unit(SYSCTL_HANDLER_ARGS)
{
@ -303,7 +247,10 @@ pcm_setstatus(device_t dev, char *str)
if (d->playcount > 0 || d->reccount > 0)
d->flags |= SD_F_AUTOVCHAN;
vchan_setmaxauto(d, snd_maxautovchans);
if (d->playcount > 0)
d->flags |= SD_F_PVCHANS;
if (d->reccount > 0)
d->flags |= SD_F_RVCHANS;
strlcpy(d->status, str, SND_STATUSLEN);
sndstat_register(dev, d->status);
@ -516,6 +463,7 @@ pcm_register(device_t dev, void *devinfo, int numplay __unused,
CHN_INIT(d, channels.pcm);
CHN_INIT(d, channels.pcm.busy);
CHN_INIT(d, channels.pcm.opened);
CHN_INIT(d, channels.pcm.primary);
return (0);
}
@ -732,9 +680,7 @@ sound_global_init(void)
if (snd_unit < 0)
snd_unit = -1;
if (snd_maxautovchans < 0 ||
snd_maxautovchans > SND_MAXVCHANS)
snd_maxautovchans = 0;
snd_vchans_enable = true;
if (chn_latency < CHN_LATENCY_MIN ||
chn_latency > CHN_LATENCY_MAX)
@ -758,11 +704,11 @@ sound_global_init(void)
feeder_rate_round = FEEDRATE_ROUNDHZ;
if (bootverbose)
printf("%s: snd_unit=%d snd_maxautovchans=%d "
printf("%s: snd_unit=%d snd_vchans_enable=%d "
"latency=%d "
"feeder_rate_min=%d feeder_rate_max=%d "
"feeder_rate_round=%d\n",
__func__, snd_unit, snd_maxautovchans,
__func__, snd_unit, snd_vchans_enable,
chn_latency,
feeder_rate_min, feeder_rate_max,
feeder_rate_round);

View file

@ -99,8 +99,6 @@ struct snd_mixer;
#define SOUND_PREFVER SOUND_MODVER
#define SOUND_MAXVER SOUND_MODVER
#define SND_MAXVCHANS 256
#define SD_F_SIMPLEX 0x00000001
#define SD_F_AUTOVCHAN 0x00000002
#define SD_F_SOFTPCMVOL 0x00000004
@ -113,6 +111,8 @@ struct snd_mixer;
#define SD_F_EQ_ENABLED 0x00000200 /* EQ enabled */
#define SD_F_EQ_BYPASSED 0x00000400 /* EQ bypassed */
#define SD_F_EQ_PC 0x00000800 /* EQ per-channel */
#define SD_F_PVCHANS 0x00001000 /* Playback vchans enabled */
#define SD_F_RVCHANS 0x00002000 /* Recording vchans enabled */
#define SD_F_EQ_DEFAULT (SD_F_EQ | SD_F_EQ_ENABLED)
#define SD_F_EQ_MASK (SD_F_EQ | SD_F_EQ_ENABLED | \
@ -134,12 +134,15 @@ struct snd_mixer;
"\012EQ_ENABLED" \
"\013EQ_BYPASSED" \
"\014EQ_PC" \
"\015PVCHANS" \
"\016RVCHANS" \
"\035PRIO_RD" \
"\036PRIO_WR"
#define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL)
#define PCM_REGISTERED(x) (PCM_ALIVE(x) && ((x)->flags & SD_F_REGISTERED))
#define PCM_MAXCHANS 10000
#define PCM_CHANCOUNT(d) \
(d->playcount + d->pvchancount + d->reccount + d->rvchancount)
@ -168,9 +171,6 @@ extern struct unrhdr *pcmsg_unrhdr;
SYSCTL_DECL(_hw_snd);
int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction,
pid_t pid, char *comm);
int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo);
unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz);
int pcm_register(device_t dev, void *devinfo, int numplay, int numrec);
@ -224,6 +224,9 @@ struct snddev_info {
struct {
SLIST_HEAD(, pcm_channel) head;
} opened;
struct {
SLIST_HEAD(, pcm_channel) head;
} primary;
} pcm;
} channels;
unsigned playcount, reccount, pvchancount, rvchancount;

View file

@ -61,7 +61,7 @@ struct vchan_info {
int trigger;
};
int snd_maxautovchans = 16;
bool snd_vchans_enable = true;
static void *
vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
@ -277,7 +277,7 @@ vchan_getparentchannel(struct snddev_info *d,
*ch = NULL;
break;
}
} else if (c->flags & CHN_F_HAS_VCHAN) {
} else {
/* No way!! */
if (*ch != NULL) {
CHN_UNLOCK(c);
@ -299,8 +299,7 @@ static int
sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)
{
struct snddev_info *d;
int direction, vchancount;
int err, cnt;
int err, enabled, flag;
d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
@ -311,43 +310,44 @@ sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)
switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
case VCHAN_PLAY:
direction = PCMDIR_PLAY;
vchancount = d->pvchancount;
cnt = d->playcount;
/* Exit if we do not support this direction. */
if (d->playcount < 1) {
PCM_UNLOCK(d);
return (ENODEV);
}
flag = SD_F_PVCHANS;
break;
case VCHAN_REC:
direction = PCMDIR_REC;
vchancount = d->rvchancount;
cnt = d->reccount;
if (d->reccount < 1) {
PCM_UNLOCK(d);
return (ENODEV);
}
flag = SD_F_RVCHANS;
break;
default:
PCM_UNLOCK(d);
return (EINVAL);
break;
}
if (cnt < 1) {
PCM_UNLOCK(d);
return (ENODEV);
}
enabled = (d->flags & flag) != 0;
PCM_ACQUIRE(d);
PCM_UNLOCK(d);
cnt = vchancount;
err = sysctl_handle_int(oidp, &cnt, 0, req);
if (err == 0 && req->newptr != NULL && vchancount != cnt) {
if (cnt < 0)
cnt = 0;
if (cnt > SND_MAXVCHANS)
cnt = SND_MAXVCHANS;
err = vchan_setnew(d, direction, cnt);
err = sysctl_handle_int(oidp, &enabled, 0, req);
if (err != 0 || req->newptr == NULL) {
PCM_RELEASE_QUICK(d);
return (err);
}
if (enabled <= 0)
d->flags &= ~flag;
else
d->flags |= flag;
PCM_RELEASE_QUICK(d);
return err;
return (0);
}
static int
@ -368,15 +368,22 @@ sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)
switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
case VCHAN_PLAY:
if ((d->flags & SD_F_PVCHANS) == 0) {
PCM_UNLOCK(d);
return (ENODEV);
}
direction = PCMDIR_PLAY;
break;
case VCHAN_REC:
if ((d->flags & SD_F_RVCHANS) == 0) {
PCM_UNLOCK(d);
return (ENODEV);
}
direction = PCMDIR_REC;
break;
default:
PCM_UNLOCK(d);
return (EINVAL);
break;
}
PCM_ACQUIRE(d);
@ -450,7 +457,7 @@ sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)
struct snddev_info *d;
struct pcm_channel *c, *ch;
struct pcmchan_caps *caps;
int *vchanrate, vchancount, direction, ret, newspd, restart;
int *vchanrate, direction, ret, newspd, restart;
d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
@ -461,24 +468,24 @@ sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)
switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
case VCHAN_PLAY:
if ((d->flags & SD_F_PVCHANS) == 0) {
PCM_UNLOCK(d);
return (ENODEV);
}
direction = PCMDIR_PLAY;
vchancount = d->pvchancount;
vchanrate = &d->pvchanrate;
break;
case VCHAN_REC:
if ((d->flags & SD_F_RVCHANS) == 0) {
PCM_UNLOCK(d);
return (ENODEV);
}
direction = PCMDIR_REC;
vchancount = d->rvchancount;
vchanrate = &d->rvchanrate;
break;
default:
PCM_UNLOCK(d);
return (EINVAL);
break;
}
if (vchancount < 1) {
PCM_UNLOCK(d);
return (EINVAL);
}
PCM_ACQUIRE(d);
@ -555,7 +562,7 @@ sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
struct snddev_info *d;
struct pcm_channel *c, *ch;
uint32_t newfmt;
int *vchanformat, vchancount, direction, ret, restart;
int *vchanformat, direction, ret, restart;
char fmtstr[AFMTSTR_LEN];
d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
@ -567,24 +574,24 @@ sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
case VCHAN_PLAY:
if ((d->flags & SD_F_PVCHANS) == 0) {
PCM_UNLOCK(d);
return (ENODEV);
}
direction = PCMDIR_PLAY;
vchancount = d->pvchancount;
vchanformat = &d->pvchanformat;
break;
case VCHAN_REC:
if ((d->flags & SD_F_RVCHANS) == 0) {
PCM_UNLOCK(d);
return (ENODEV);
}
direction = PCMDIR_REC;
vchancount = d->rvchancount;
vchanformat = &d->rvchanformat;
break;
default:
PCM_UNLOCK(d);
return (EINVAL);
break;
}
if (vchancount < 1) {
PCM_UNLOCK(d);
return (EINVAL);
}
PCM_ACQUIRE(d);
@ -660,7 +667,7 @@ sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
"play.vchanrate" : "rec.vchanrate"
int
vchan_create(struct pcm_channel *parent)
vchan_create(struct pcm_channel *parent, struct pcm_channel **child)
{
struct snddev_info *d;
struct pcm_channel *ch;
@ -676,9 +683,6 @@ vchan_create(struct pcm_channel *parent)
PCM_BUSYASSERT(d);
CHN_LOCKASSERT(parent);
if (!(parent->flags & CHN_F_BUSY))
return (EBUSY);
if (!(parent->direction == PCMDIR_PLAY ||
parent->direction == PCMDIR_REC))
return (EINVAL);
@ -713,10 +717,12 @@ vchan_create(struct pcm_channel *parent)
*/
CHN_INSERT_SORT_DESCEND(parent, ch, children);
*child = ch;
if (parent->flags & CHN_F_HAS_VCHAN)
return (0);
parent->flags |= CHN_F_HAS_VCHAN;
parent->flags |= CHN_F_HAS_VCHAN | CHN_F_BUSY;
parent_caps = chn_getcaps(parent);
if (parent_caps == NULL) {
@ -807,6 +813,7 @@ vchan_create(struct pcm_channel *parent)
fail:
CHN_LOCK(ch);
vchan_destroy(ch);
*child = NULL;
return (ret);
}
@ -878,166 +885,40 @@ vchan_sync(struct pcm_channel *c)
return (ret);
}
int
vchan_setnew(struct snddev_info *d, int direction, int newcnt)
{
struct pcm_channel *c, *ch, *nch;
struct pcmchan_caps *caps;
int i, err, vcnt;
PCM_BUSYASSERT(d);
if ((direction == PCMDIR_PLAY && d->playcount < 1) ||
(direction == PCMDIR_REC && d->reccount < 1))
return (ENODEV);
if (!(d->flags & SD_F_AUTOVCHAN))
return (EINVAL);
if (newcnt < 0 || newcnt > SND_MAXVCHANS)
return (E2BIG);
if (direction == PCMDIR_PLAY)
vcnt = d->pvchancount;
else if (direction == PCMDIR_REC)
vcnt = d->rvchancount;
else
return (EINVAL);
if (newcnt > vcnt) {
/* add new vchans - find a parent channel first */
ch = NULL;
CHN_FOREACH(c, d, channels.pcm) {
CHN_LOCK(c);
if (c->direction == direction &&
((c->flags & CHN_F_HAS_VCHAN) || (vcnt == 0 &&
!(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) {
/*
* Reuse hw channel with vchans already
* created.
*/
if (c->flags & CHN_F_HAS_VCHAN) {
ch = c;
break;
}
/*
* No vchans ever created, look for
* channels with supported formats.
*/
caps = chn_getcaps(c);
if (caps == NULL) {
CHN_UNLOCK(c);
continue;
}
for (i = 0; caps->fmtlist[i] != 0; i++) {
if (caps->fmtlist[i] & AFMT_CONVERTIBLE)
break;
}
if (caps->fmtlist[i] != 0) {
ch = c;
break;
}
}
CHN_UNLOCK(c);
}
if (ch == NULL)
return (EBUSY);
ch->flags |= CHN_F_BUSY;
err = 0;
while (err == 0 && newcnt > vcnt) {
err = vchan_create(ch);
if (err == 0)
vcnt++;
else if (err == E2BIG && newcnt > vcnt)
device_printf(d->dev,
"%s: err=%d Maximum channel reached.\n",
__func__, err);
}
if (vcnt == 0)
ch->flags &= ~CHN_F_BUSY;
CHN_UNLOCK(ch);
if (err != 0)
return (err);
} else if (newcnt < vcnt) {
CHN_FOREACH(c, d, channels.pcm) {
CHN_LOCK(c);
if (c->direction != direction ||
CHN_EMPTY(c, children) ||
!(c->flags & CHN_F_HAS_VCHAN)) {
CHN_UNLOCK(c);
continue;
}
CHN_FOREACH_SAFE(ch, c, nch, children) {
CHN_LOCK(ch);
if (vcnt == 1 && ch->flags & CHN_F_BUSY) {
CHN_UNLOCK(ch);
break;
}
if (!(ch->flags & CHN_F_BUSY)) {
err = vchan_destroy(ch);
if (err == 0)
vcnt--;
} else
CHN_UNLOCK(ch);
if (vcnt == newcnt)
break;
}
CHN_UNLOCK(c);
break;
}
}
return (0);
}
void
vchan_setmaxauto(struct snddev_info *d, int num)
{
PCM_BUSYASSERT(d);
if (num < 0)
return;
if (num >= 0 && d->pvchancount > num)
(void)vchan_setnew(d, PCMDIR_PLAY, num);
else if (num > 0 && d->pvchancount == 0)
(void)vchan_setnew(d, PCMDIR_PLAY, 1);
if (num >= 0 && d->rvchancount > num)
(void)vchan_setnew(d, PCMDIR_REC, num);
else if (num > 0 && d->rvchancount == 0)
(void)vchan_setnew(d, PCMDIR_REC, 1);
}
static int
sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS)
sysctl_hw_snd_vchans_enable(SYSCTL_HANDLER_ARGS)
{
struct snddev_info *d;
int i, v, error;
v = snd_maxautovchans;
v = snd_vchans_enable;
error = sysctl_handle_int(oidp, &v, 0, req);
if (error == 0 && req->newptr != NULL) {
if (v < 0)
v = 0;
if (v > SND_MAXVCHANS)
v = SND_MAXVCHANS;
snd_maxautovchans = v;
for (i = 0; pcm_devclass != NULL &&
i < devclass_get_maxunit(pcm_devclass); i++) {
d = devclass_get_softc(pcm_devclass, i);
if (!PCM_REGISTERED(d))
continue;
PCM_ACQUIRE_QUICK(d);
vchan_setmaxauto(d, v);
PCM_RELEASE_QUICK(d);
}
if (error != 0 || req->newptr == NULL)
return (error);
snd_vchans_enable = v >= 1;
for (i = 0; pcm_devclass != NULL &&
i < devclass_get_maxunit(pcm_devclass); i++) {
d = devclass_get_softc(pcm_devclass, i);
if (!PCM_REGISTERED(d))
continue;
PCM_ACQUIRE_QUICK(d);
if (snd_vchans_enable) {
if (d->playcount > 0)
d->flags |= SD_F_PVCHANS;
if (d->reccount > 0)
d->flags |= SD_F_RVCHANS;
} else
d->flags &= ~(SD_F_PVCHANS | SD_F_RVCHANS);
PCM_RELEASE_QUICK(d);
}
return (error);
return (0);
}
SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans,
SYSCTL_PROC(_hw_snd, OID_AUTO, vchans_enable,
CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int),
sysctl_hw_snd_maxautovchans, "I", "maximum virtual channel");
sysctl_hw_snd_vchans_enable, "I", "global virtual channel switch");
void
vchan_initsys(device_t dev)
@ -1053,7 +934,7 @@ vchan_initsys(device_t dev)
SYSCTL_CHILDREN(d->play_sysctl_tree),
OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
sysctl_dev_pcm_vchans, "I", "virtual channels enabled");
SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
SYSCTL_CHILDREN(d->play_sysctl_tree),
OID_AUTO, "vchanmode",
@ -1079,7 +960,7 @@ vchan_initsys(device_t dev)
OID_AUTO, "vchans",
CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
sysctl_dev_pcm_vchans, "I", "virtual channels enabled");
SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
SYSCTL_CHILDREN(d->rec_sysctl_tree),
OID_AUTO, "vchanmode",

View file

@ -30,9 +30,9 @@
#ifndef _SND_VCHAN_H_
#define _SND_VCHAN_H_
extern int snd_maxautovchans;
extern bool snd_vchans_enable;
int vchan_create(struct pcm_channel *);
int vchan_create(struct pcm_channel *, struct pcm_channel **);
int vchan_destroy(struct pcm_channel *);
#ifdef SND_DEBUG
@ -47,9 +47,6 @@ int vchan_sync(struct pcm_channel *);
sndbuf_getfmt((c)->bufhard) != (c)->parentchannel->format || \
sndbuf_getspd((c)->bufhard) != (c)->parentchannel->speed))
int vchan_setnew(struct snddev_info *, int, int);
void vchan_setmaxauto(struct snddev_info *, int);
void vchan_initsys(device_t);
/*