Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/sound/pcm/vchan.c
Show First 20 Lines • Show All 55 Lines • ▼ Show 20 Lines | |||||
struct vchan_info { | struct vchan_info { | ||||
struct pcm_channel *channel; | struct pcm_channel *channel; | ||||
struct pcmchan_caps caps; | struct pcmchan_caps caps; | ||||
uint32_t fmtlist[FMTLIST_MAX]; | uint32_t fmtlist[FMTLIST_MAX]; | ||||
int trigger; | int trigger; | ||||
}; | }; | ||||
int snd_maxautovchans = 16; | bool snd_vchans_enable = true; | ||||
static void * | static void * | ||||
vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, | vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, | ||||
struct pcm_channel *c, int dir) | struct pcm_channel *c, int dir) | ||||
{ | { | ||||
struct vchan_info *info; | struct vchan_info *info; | ||||
struct pcm_channel *p; | struct pcm_channel *p; | ||||
uint32_t i, j, *fmtlist; | uint32_t i, j, *fmtlist; | ||||
▲ Show 20 Lines • Show All 199 Lines • ▼ Show 20 Lines | CHN_FOREACH(c, d, channels.pcm) { | ||||
ch = (c->direction == PCMDIR_PLAY) ? &wch : &rch; | ch = (c->direction == PCMDIR_PLAY) ? &wch : &rch; | ||||
if (c->flags & CHN_F_VIRTUAL) { | if (c->flags & CHN_F_VIRTUAL) { | ||||
/* Sanity check */ | /* Sanity check */ | ||||
if (*ch != NULL && *ch != c->parentchannel) { | if (*ch != NULL && *ch != c->parentchannel) { | ||||
CHN_UNLOCK(c); | CHN_UNLOCK(c); | ||||
*ch = NULL; | *ch = NULL; | ||||
break; | break; | ||||
} | } | ||||
} else if (c->flags & CHN_F_HAS_VCHAN) { | } else { | ||||
/* No way!! */ | /* No way!! */ | ||||
if (*ch != NULL) { | if (*ch != NULL) { | ||||
CHN_UNLOCK(c); | CHN_UNLOCK(c); | ||||
*ch = NULL; | *ch = NULL; | ||||
break; | break; | ||||
} | } | ||||
*ch = c; | *ch = c; | ||||
} | } | ||||
CHN_UNLOCK(c); | CHN_UNLOCK(c); | ||||
} | } | ||||
if (wrch != NULL) | if (wrch != NULL) | ||||
*wrch = wch; | *wrch = wch; | ||||
if (rdch != NULL) | if (rdch != NULL) | ||||
*rdch = rch; | *rdch = rch; | ||||
} | } | ||||
static int | static int | ||||
sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS) | sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
struct snddev_info *d; | struct snddev_info *d; | ||||
int direction, vchancount; | int err, enabled, flag; | ||||
int err, cnt; | |||||
d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); | d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); | ||||
if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) | if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) | ||||
return (EINVAL); | return (EINVAL); | ||||
PCM_LOCK(d); | PCM_LOCK(d); | ||||
PCM_WAIT(d); | PCM_WAIT(d); | ||||
switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { | switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { | ||||
case VCHAN_PLAY: | case VCHAN_PLAY: | ||||
direction = PCMDIR_PLAY; | /* Exit if we do not support this direction. */ | ||||
vchancount = d->pvchancount; | if (d->playcount < 1) { | ||||
cnt = d->playcount; | PCM_UNLOCK(d); | ||||
return (ENODEV); | |||||
} | |||||
flag = SD_F_PVCHANS; | |||||
break; | break; | ||||
case VCHAN_REC: | case VCHAN_REC: | ||||
direction = PCMDIR_REC; | if (d->reccount < 1) { | ||||
vchancount = d->rvchancount; | PCM_UNLOCK(d); | ||||
cnt = d->reccount; | return (ENODEV); | ||||
} | |||||
flag = SD_F_RVCHANS; | |||||
break; | break; | ||||
default: | default: | ||||
PCM_UNLOCK(d); | PCM_UNLOCK(d); | ||||
return (EINVAL); | return (EINVAL); | ||||
break; | |||||
} | } | ||||
if (cnt < 1) { | enabled = (d->flags & flag) != 0; | ||||
PCM_UNLOCK(d); | |||||
return (ENODEV); | |||||
} | |||||
PCM_ACQUIRE(d); | PCM_ACQUIRE(d); | ||||
PCM_UNLOCK(d); | PCM_UNLOCK(d); | ||||
cnt = vchancount; | err = sysctl_handle_int(oidp, &enabled, 0, req); | ||||
err = sysctl_handle_int(oidp, &cnt, 0, req); | if (err != 0 || req->newptr == NULL) { | ||||
PCM_RELEASE_QUICK(d); | |||||
if (err == 0 && req->newptr != NULL && vchancount != cnt) { | return (err); | ||||
if (cnt < 0) | |||||
cnt = 0; | |||||
if (cnt > SND_MAXVCHANS) | |||||
cnt = SND_MAXVCHANS; | |||||
err = vchan_setnew(d, direction, cnt); | |||||
} | } | ||||
if (enabled <= 0) | |||||
d->flags &= ~flag; | |||||
else | |||||
d->flags |= flag; | |||||
PCM_RELEASE_QUICK(d); | PCM_RELEASE_QUICK(d); | ||||
return err; | return (0); | ||||
} | } | ||||
static int | static int | ||||
sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS) | sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
struct snddev_info *d; | struct snddev_info *d; | ||||
struct pcm_channel *c; | struct pcm_channel *c; | ||||
uint32_t dflags; | uint32_t dflags; | ||||
int direction, ret; | int direction, ret; | ||||
char dtype[16]; | char dtype[16]; | ||||
d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); | d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); | ||||
if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) | if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) | ||||
return (EINVAL); | return (EINVAL); | ||||
PCM_LOCK(d); | PCM_LOCK(d); | ||||
PCM_WAIT(d); | PCM_WAIT(d); | ||||
switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { | switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { | ||||
case VCHAN_PLAY: | case VCHAN_PLAY: | ||||
if ((d->flags & SD_F_PVCHANS) == 0) { | |||||
PCM_UNLOCK(d); | |||||
return (ENODEV); | |||||
} | |||||
direction = PCMDIR_PLAY; | direction = PCMDIR_PLAY; | ||||
break; | break; | ||||
case VCHAN_REC: | case VCHAN_REC: | ||||
if ((d->flags & SD_F_RVCHANS) == 0) { | |||||
PCM_UNLOCK(d); | |||||
return (ENODEV); | |||||
} | |||||
direction = PCMDIR_REC; | direction = PCMDIR_REC; | ||||
break; | break; | ||||
default: | default: | ||||
PCM_UNLOCK(d); | PCM_UNLOCK(d); | ||||
return (EINVAL); | return (EINVAL); | ||||
break; | |||||
} | } | ||||
PCM_ACQUIRE(d); | PCM_ACQUIRE(d); | ||||
PCM_UNLOCK(d); | PCM_UNLOCK(d); | ||||
if (direction == PCMDIR_PLAY) | if (direction == PCMDIR_PLAY) | ||||
vchan_getparentchannel(d, &c, NULL); | vchan_getparentchannel(d, &c, NULL); | ||||
else | else | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | #define VCHAN_ACCESSIBLE(c) (!((c)->flags & (CHN_F_PASSTHROUGH | \ | ||||
(((c)->flags & CHN_F_VCHAN_DYNAMIC) || \ | (((c)->flags & CHN_F_VCHAN_DYNAMIC) || \ | ||||
CHN_STOPPED(c))) | CHN_STOPPED(c))) | ||||
static int | static int | ||||
sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS) | sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
struct snddev_info *d; | struct snddev_info *d; | ||||
struct pcm_channel *c, *ch; | struct pcm_channel *c, *ch; | ||||
struct pcmchan_caps *caps; | 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)); | d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); | ||||
if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) | if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) | ||||
return (EINVAL); | return (EINVAL); | ||||
PCM_LOCK(d); | PCM_LOCK(d); | ||||
PCM_WAIT(d); | PCM_WAIT(d); | ||||
switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { | switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { | ||||
case VCHAN_PLAY: | case VCHAN_PLAY: | ||||
if ((d->flags & SD_F_PVCHANS) == 0) { | |||||
PCM_UNLOCK(d); | |||||
return (ENODEV); | |||||
} | |||||
direction = PCMDIR_PLAY; | direction = PCMDIR_PLAY; | ||||
vchancount = d->pvchancount; | |||||
vchanrate = &d->pvchanrate; | vchanrate = &d->pvchanrate; | ||||
break; | break; | ||||
case VCHAN_REC: | case VCHAN_REC: | ||||
if ((d->flags & SD_F_RVCHANS) == 0) { | |||||
PCM_UNLOCK(d); | |||||
return (ENODEV); | |||||
} | |||||
direction = PCMDIR_REC; | direction = PCMDIR_REC; | ||||
vchancount = d->rvchancount; | |||||
vchanrate = &d->rvchanrate; | vchanrate = &d->rvchanrate; | ||||
break; | break; | ||||
default: | default: | ||||
PCM_UNLOCK(d); | PCM_UNLOCK(d); | ||||
return (EINVAL); | return (EINVAL); | ||||
break; | |||||
} | } | ||||
if (vchancount < 1) { | |||||
PCM_UNLOCK(d); | |||||
return (EINVAL); | |||||
} | |||||
PCM_ACQUIRE(d); | PCM_ACQUIRE(d); | ||||
PCM_UNLOCK(d); | PCM_UNLOCK(d); | ||||
if (direction == PCMDIR_PLAY) | if (direction == PCMDIR_PLAY) | ||||
vchan_getparentchannel(d, &c, NULL); | vchan_getparentchannel(d, &c, NULL); | ||||
else | else | ||||
vchan_getparentchannel(d, NULL, &c); | vchan_getparentchannel(d, NULL, &c); | ||||
if (c == NULL) { | if (c == NULL) { | ||||
PCM_RELEASE_QUICK(d); | PCM_RELEASE_QUICK(d); | ||||
return (EINVAL); | return (EINVAL); | ||||
} | } | ||||
KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", | KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", | ||||
__func__, direction, c->direction)); | __func__, direction, c->direction)); | ||||
CHN_LOCK(c); | newspd = *vchanrate; | ||||
newspd = c->speed; | |||||
CHN_UNLOCK(c); | |||||
ret = sysctl_handle_int(oidp, &newspd, 0, req); | ret = sysctl_handle_int(oidp, &newspd, 0, req); | ||||
if (ret != 0 || req->newptr == NULL) { | if (ret != 0 || req->newptr == NULL) { | ||||
PCM_RELEASE_QUICK(d); | PCM_RELEASE_QUICK(d); | ||||
return (ret); | return (ret); | ||||
} | } | ||||
if (newspd < feeder_rate_min || newspd > feeder_rate_max) { | if (newspd < feeder_rate_min || newspd > feeder_rate_max) { | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | |||||
} | } | ||||
static int | static int | ||||
sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS) | sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
struct snddev_info *d; | struct snddev_info *d; | ||||
struct pcm_channel *c, *ch; | struct pcm_channel *c, *ch; | ||||
uint32_t newfmt; | uint32_t newfmt; | ||||
int *vchanformat, vchancount, direction, ret, restart; | int *vchanformat, direction, ret, restart; | ||||
char fmtstr[AFMTSTR_LEN]; | char fmtstr[AFMTSTR_LEN]; | ||||
d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); | d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); | ||||
if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) | if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) | ||||
return (EINVAL); | return (EINVAL); | ||||
PCM_LOCK(d); | PCM_LOCK(d); | ||||
PCM_WAIT(d); | PCM_WAIT(d); | ||||
switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { | switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { | ||||
case VCHAN_PLAY: | case VCHAN_PLAY: | ||||
if ((d->flags & SD_F_PVCHANS) == 0) { | |||||
PCM_UNLOCK(d); | |||||
return (ENODEV); | |||||
} | |||||
direction = PCMDIR_PLAY; | direction = PCMDIR_PLAY; | ||||
vchancount = d->pvchancount; | |||||
vchanformat = &d->pvchanformat; | vchanformat = &d->pvchanformat; | ||||
break; | break; | ||||
case VCHAN_REC: | case VCHAN_REC: | ||||
if ((d->flags & SD_F_RVCHANS) == 0) { | |||||
PCM_UNLOCK(d); | |||||
return (ENODEV); | |||||
} | |||||
direction = PCMDIR_REC; | direction = PCMDIR_REC; | ||||
vchancount = d->rvchancount; | |||||
vchanformat = &d->rvchanformat; | vchanformat = &d->rvchanformat; | ||||
break; | break; | ||||
default: | default: | ||||
PCM_UNLOCK(d); | PCM_UNLOCK(d); | ||||
return (EINVAL); | return (EINVAL); | ||||
break; | |||||
} | } | ||||
if (vchancount < 1) { | |||||
PCM_UNLOCK(d); | |||||
return (EINVAL); | |||||
} | |||||
PCM_ACQUIRE(d); | PCM_ACQUIRE(d); | ||||
PCM_UNLOCK(d); | PCM_UNLOCK(d); | ||||
if (direction == PCMDIR_PLAY) | if (direction == PCMDIR_PLAY) | ||||
vchan_getparentchannel(d, &c, NULL); | vchan_getparentchannel(d, &c, NULL); | ||||
else | else | ||||
vchan_getparentchannel(d, NULL, &c); | vchan_getparentchannel(d, NULL, &c); | ||||
if (c == NULL) { | if (c == NULL) { | ||||
PCM_RELEASE_QUICK(d); | PCM_RELEASE_QUICK(d); | ||||
return (EINVAL); | return (EINVAL); | ||||
} | } | ||||
KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", | KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", | ||||
__func__, direction, c->direction)); | __func__, direction, c->direction)); | ||||
CHN_LOCK(c); | |||||
bzero(fmtstr, sizeof(fmtstr)); | bzero(fmtstr, sizeof(fmtstr)); | ||||
if (snd_afmt2str(c->format, fmtstr, sizeof(fmtstr)) != c->format) | if (snd_afmt2str(*vchanformat, fmtstr, sizeof(fmtstr)) != *vchanformat) | ||||
strlcpy(fmtstr, "<ERROR>", sizeof(fmtstr)); | strlcpy(fmtstr, "<ERROR>", sizeof(fmtstr)); | ||||
CHN_UNLOCK(c); | |||||
ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); | ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); | ||||
if (ret != 0 || req->newptr == NULL) { | if (ret != 0 || req->newptr == NULL) { | ||||
PCM_RELEASE_QUICK(d); | PCM_RELEASE_QUICK(d); | ||||
return (ret); | return (ret); | ||||
} | } | ||||
newfmt = snd_str2afmt(fmtstr); | newfmt = snd_str2afmt(fmtstr); | ||||
if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) { | if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) { | ||||
Show All 36 Lines | |||||
/* virtual channel interface */ | /* virtual channel interface */ | ||||
#define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ | #define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ | ||||
"play.vchanformat" : "rec.vchanformat" | "play.vchanformat" : "rec.vchanformat" | ||||
#define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ | #define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ | ||||
"play.vchanrate" : "rec.vchanrate" | "play.vchanrate" : "rec.vchanrate" | ||||
int | int | ||||
vchan_create(struct pcm_channel *parent) | vchan_create(struct pcm_channel *parent, struct pcm_channel **child) | ||||
{ | { | ||||
struct snddev_info *d; | struct snddev_info *d; | ||||
struct pcm_channel *ch; | struct pcm_channel *ch; | ||||
struct pcmchan_caps *parent_caps; | struct pcmchan_caps *parent_caps; | ||||
uint32_t vchanfmt, vchanspd; | uint32_t vchanfmt, vchanspd; | ||||
int ret, direction, r; | int ret, direction, r; | ||||
bool save; | bool save; | ||||
ret = 0; | ret = 0; | ||||
save = false; | save = false; | ||||
d = parent->parentsnddev; | d = parent->parentsnddev; | ||||
PCM_BUSYASSERT(d); | PCM_BUSYASSERT(d); | ||||
CHN_LOCKASSERT(parent); | CHN_LOCKASSERT(parent); | ||||
if (!(parent->flags & CHN_F_BUSY)) | |||||
return (EBUSY); | |||||
if (!(parent->direction == PCMDIR_PLAY || | if (!(parent->direction == PCMDIR_PLAY || | ||||
parent->direction == PCMDIR_REC)) | parent->direction == PCMDIR_REC)) | ||||
return (EINVAL); | return (EINVAL); | ||||
CHN_UNLOCK(parent); | CHN_UNLOCK(parent); | ||||
PCM_LOCK(d); | PCM_LOCK(d); | ||||
if (parent->direction == PCMDIR_PLAY) { | if (parent->direction == PCMDIR_PLAY) { | ||||
Show All 18 Lines | vchan_create(struct pcm_channel *parent, struct pcm_channel **child) | ||||
CHN_LOCK(parent); | CHN_LOCK(parent); | ||||
/* | /* | ||||
* Add us to our parent channel's children in reverse order | * Add us to our parent channel's children in reverse order | ||||
* so future destruction will pick the last (biggest number) | * so future destruction will pick the last (biggest number) | ||||
* channel. | * channel. | ||||
*/ | */ | ||||
CHN_INSERT_SORT_DESCEND(parent, ch, children); | CHN_INSERT_SORT_DESCEND(parent, ch, children); | ||||
*child = ch; | |||||
if (parent->flags & CHN_F_HAS_VCHAN) | if (parent->flags & CHN_F_HAS_VCHAN) | ||||
return (0); | return (0); | ||||
parent->flags |= CHN_F_HAS_VCHAN; | parent->flags |= CHN_F_HAS_VCHAN | CHN_F_BUSY; | ||||
parent_caps = chn_getcaps(parent); | parent_caps = chn_getcaps(parent); | ||||
if (parent_caps == NULL) { | if (parent_caps == NULL) { | ||||
ret = EINVAL; | ret = EINVAL; | ||||
goto fail; | goto fail; | ||||
} | } | ||||
if (vchanfmt == 0) { | if (vchanfmt == 0) { | ||||
▲ Show 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | vchan_create(struct pcm_channel *parent, struct pcm_channel **child) | ||||
*/ | */ | ||||
if (snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) { | if (snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) { | ||||
parent->flags &= ~CHN_F_VCHAN_DYNAMIC; | parent->flags &= ~CHN_F_VCHAN_DYNAMIC; | ||||
parent->flags |= CHN_F_VCHAN_PASSTHROUGH; | parent->flags |= CHN_F_VCHAN_PASSTHROUGH; | ||||
} | } | ||||
return (ret); | return (ret); | ||||
fail: | fail: | ||||
CHN_LOCK(ch); | CHN_LOCK(ch); | ||||
dev_submerge.ch: Are we allowed to clear the `CHN_F_BUSY` flag here? I suppose it could depend on other… | |||||
Done Inline ActionsThe reason I am clearing it here, is because I see that CHN_F_HAS_VCHAN is also cleared here. The fail label is only reached if a call to chn_reset() or chn_getcaps() on the parent fails. So I guess this code is reached if the parent is (for lack of a better word) unusable in general. christos: The reason I am clearing it here, is because I see that `CHN_F_HAS_VCHAN` is also cleared here. | |||||
Done Inline ActionsI wouldn't equate a chn_reset() error to the parent being completely unusable. And even if that's true, we take these flags as indicator for vchans, it's inconsistent to unset them without checking for other vchans first. See vchan_destroy() below, maybe we can call that or reuse the code there? dev_submerge.ch: I wouldn't equate a `chn_reset()` error to the parent being completely unusable. And even if… | |||||
Done Inline Actionschristos: D48183 | |||||
vchan_destroy(ch); | vchan_destroy(ch); | ||||
*child = NULL; | |||||
Done Inline ActionsWe may want to explicitly set *child = NULL; here, to not leave any dangling pointers, even if we also return an error value. dev_submerge.ch: We may want to explicitly set `*child = NULL;` here, to not leave any dangling pointers, even… | |||||
return (ret); | return (ret); | ||||
} | } | ||||
int | int | ||||
vchan_destroy(struct pcm_channel *c) | vchan_destroy(struct pcm_channel *c) | ||||
{ | { | ||||
struct pcm_channel *parent; | struct pcm_channel *parent; | ||||
▲ Show 20 Lines • Show All 55 Lines • ▼ Show 20 Lines | if (snd_passthrough_verbose) { | ||||
device_printf(c->dev, "%s(%s/%s) %s() -> re-sync err=%d\n", | device_printf(c->dev, "%s(%s/%s) %s() -> re-sync err=%d\n", | ||||
__func__, c->name, c->comm, caller, ret); | __func__, c->name, c->comm, caller, ret); | ||||
} | } | ||||
#endif | #endif | ||||
return (ret); | 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 | static int | ||||
sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS) | sysctl_hw_snd_vchans_enable(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
struct snddev_info *d; | struct snddev_info *d; | ||||
int i, v, error; | int i, v, error; | ||||
v = snd_maxautovchans; | v = snd_vchans_enable; | ||||
error = sysctl_handle_int(oidp, &v, 0, req); | error = sysctl_handle_int(oidp, &v, 0, req); | ||||
if (error == 0 && req->newptr != NULL) { | if (error != 0 || req->newptr == NULL) | ||||
if (v < 0) | return (error); | ||||
v = 0; | |||||
if (v > SND_MAXVCHANS) | snd_vchans_enable = v >= 1; | ||||
v = SND_MAXVCHANS; | |||||
snd_maxautovchans = v; | |||||
for (i = 0; pcm_devclass != NULL && | for (i = 0; pcm_devclass != NULL && | ||||
i < devclass_get_maxunit(pcm_devclass); i++) { | i < devclass_get_maxunit(pcm_devclass); i++) { | ||||
d = devclass_get_softc(pcm_devclass, i); | d = devclass_get_softc(pcm_devclass, i); | ||||
if (!PCM_REGISTERED(d)) | if (!PCM_REGISTERED(d)) | ||||
continue; | continue; | ||||
PCM_ACQUIRE_QUICK(d); | PCM_ACQUIRE_QUICK(d); | ||||
vchan_setmaxauto(d, v); | 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); | PCM_RELEASE_QUICK(d); | ||||
} | } | ||||
return (0); | |||||
} | } | ||||
return (error); | SYSCTL_PROC(_hw_snd, OID_AUTO, vchans_enable, | ||||
} | |||||
SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, | |||||
CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int), | 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 | void | ||||
vchan_initsys(device_t dev) | vchan_initsys(device_t dev) | ||||
{ | { | ||||
struct snddev_info *d; | struct snddev_info *d; | ||||
int unit; | int unit; | ||||
unit = device_get_unit(dev); | unit = device_get_unit(dev); | ||||
d = device_get_softc(dev); | d = device_get_softc(dev); | ||||
/* Play */ | /* Play */ | ||||
SYSCTL_ADD_PROC(&d->play_sysctl_ctx, | SYSCTL_ADD_PROC(&d->play_sysctl_ctx, | ||||
SYSCTL_CHILDREN(d->play_sysctl_tree), | SYSCTL_CHILDREN(d->play_sysctl_tree), | ||||
OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, | OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, | ||||
VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, | 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_ADD_PROC(&d->play_sysctl_ctx, | ||||
SYSCTL_CHILDREN(d->play_sysctl_tree), | SYSCTL_CHILDREN(d->play_sysctl_tree), | ||||
OID_AUTO, "vchanmode", | OID_AUTO, "vchanmode", | ||||
CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, | CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, | ||||
VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, | VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, | ||||
sysctl_dev_pcm_vchanmode, "A", | sysctl_dev_pcm_vchanmode, "A", | ||||
"vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive"); | "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive"); | ||||
SYSCTL_ADD_PROC(&d->play_sysctl_ctx, | SYSCTL_ADD_PROC(&d->play_sysctl_ctx, | ||||
Show All 9 Lines | SYSCTL_ADD_PROC(&d->play_sysctl_ctx, | ||||
VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, | VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, | ||||
sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format"); | sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format"); | ||||
/* Rec */ | /* Rec */ | ||||
SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, | SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, | ||||
SYSCTL_CHILDREN(d->rec_sysctl_tree), | SYSCTL_CHILDREN(d->rec_sysctl_tree), | ||||
OID_AUTO, "vchans", | OID_AUTO, "vchans", | ||||
CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, | CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, | ||||
VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, | 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_ADD_PROC(&d->rec_sysctl_ctx, | ||||
SYSCTL_CHILDREN(d->rec_sysctl_tree), | SYSCTL_CHILDREN(d->rec_sysctl_tree), | ||||
OID_AUTO, "vchanmode", | OID_AUTO, "vchanmode", | ||||
CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, | CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, | ||||
VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, | VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, | ||||
sysctl_dev_pcm_vchanmode, "A", | sysctl_dev_pcm_vchanmode, "A", | ||||
"vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive"); | "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive"); | ||||
SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, | SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, | ||||
Show All 12 Lines |
Are we allowed to clear the CHN_F_BUSY flag here? I suppose it could depend on other children's state?