#ifdef HAVE_KERNEL_OPTION_HEADERS
#include "opt_snd.h"
#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>
#include <sys/sysent.h>
#include <vm/vm.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <vm/vm_pager.h>
struct dsp_cdevpriv {
struct snddev_info *sc;
struct pcm_channel *rdch;
struct pcm_channel *wrch;
};
static int dsp_mmap_allow_prot_exec = 0;
SYSCTL_INT(_hw_snd, OID_AUTO, compat_linux_mmap, CTLFLAG_RWTUN,
&dsp_mmap_allow_prot_exec, 0,
"linux mmap compatibility (-1=force disable 0=auto 1=force enable)");
static int dsp_basename_clone = 1;
SYSCTL_INT(_hw_snd, OID_AUTO, basename_clone, CTLFLAG_RWTUN,
&dsp_basename_clone, 0,
"DSP basename cloning (0: Disable; 1: Enabled)");
#define DSP_REGISTERED(x) (PCM_REGISTERED(x) && (x)->dsp_dev != NULL)
#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))
#define DSP_F_READ(x) ((x) & FREAD)
#define DSP_F_WRITE(x) ((x) & FWRITE)
static d_open_t dsp_open;
static d_read_t dsp_read;
static d_write_t dsp_write;
static d_ioctl_t dsp_ioctl;
static d_poll_t dsp_poll;
static d_mmap_t dsp_mmap;
static d_mmap_single_t dsp_mmap_single;
static d_kqfilter_t dsp_kqfilter;
struct cdevsw dsp_cdevsw = {
.d_version = D_VERSION,
.d_open = dsp_open,
.d_read = dsp_read,
.d_write = dsp_write,
.d_ioctl = dsp_ioctl,
.d_poll = dsp_poll,
.d_kqfilter = dsp_kqfilter,
.d_mmap = dsp_mmap,
.d_mmap_single = dsp_mmap_single,
.d_name = "dsp",
};
static eventhandler_tag dsp_ehtag = NULL;
static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group);
static int dsp_oss_syncstart(int sg_id);
static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy);
static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled);
static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map);
static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map);
static int dsp_oss_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch, int *mask);
#ifdef OSSV4_EXPERIMENT
static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label);
static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label);
static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song);
static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song);
static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name);
#endif
int
dsp_make_dev(device_t dev)
{
struct make_dev_args devargs;
struct snddev_info *sc;
int err, unit;
sc = device_get_softc(dev);
unit = device_get_unit(dev);
make_dev_args_init(&devargs);
devargs.mda_devsw = &dsp_cdevsw;
devargs.mda_uid = UID_ROOT;
devargs.mda_gid = GID_WHEEL;
devargs.mda_mode = 0666;
devargs.mda_si_drv1 = sc;
err = make_dev_s(&devargs, &sc->dsp_dev, "dsp%d", unit);
if (err != 0) {
device_printf(dev, "failed to create dsp%d: error %d",
unit, err);
return (ENXIO);
}
return (0);
}
void
dsp_destroy_dev(device_t dev)
{
struct snddev_info *d;
d = device_get_softc(dev);
destroy_dev(d->dsp_dev);
}
static void
dsp_lock_chans(struct dsp_cdevpriv *priv, uint32_t prio)
{
if (priv->rdch != NULL && DSP_F_READ(prio))
CHN_LOCK(priv->rdch);
if (priv->wrch != NULL && DSP_F_WRITE(prio))
CHN_LOCK(priv->wrch);
}
static void
dsp_unlock_chans(struct dsp_cdevpriv *priv, uint32_t prio)
{
if (priv->rdch != NULL && DSP_F_READ(prio))
CHN_UNLOCK(priv->rdch);
if (priv->wrch != NULL && DSP_F_WRITE(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;
}
if ((c->flags & CHN_F_BUSY) == 0 ||
(vdir_enabled && (c->flags & CHN_F_HAS_VCHAN)))
break;
CHN_UNLOCK(c);
}
if (c == NULL)
return (EBUSY);
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);
}
static void
dsp_close(void *data)
{
struct dsp_cdevpriv *priv = data;
struct pcm_channel *rdch, *wrch, *parent;
struct snddev_info *d;
int sg_ids;
if (priv == NULL)
return;
d = priv->sc;
if (!DSP_REGISTERED(d))
goto skip;
PCM_GIANT_ENTER(d);
PCM_LOCK(d);
PCM_WAIT(d);
PCM_ACQUIRE(d);
rdch = priv->rdch;
wrch = priv->wrch;
if (rdch != NULL)
CHN_REMOVE(d, rdch, channels.pcm.opened);
if (wrch != NULL)
CHN_REMOVE(d, wrch, channels.pcm.opened);
if (rdch != NULL || wrch != NULL) {
PCM_UNLOCK(d);
if (rdch != NULL) {
PCM_SG_LOCK();
sg_ids = chn_syncdestroy(rdch);
PCM_SG_UNLOCK();
if (sg_ids != 0)
free_unr(pcmsg_unrhdr, sg_ids);
CHN_LOCK(rdch);
chn_abort(rdch);
rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP |
CHN_F_DEAD | CHN_F_EXCLUSIVE | CHN_F_NBIO);
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);
}
}
if (wrch != NULL) {
PCM_SG_LOCK();
sg_ids = chn_syncdestroy(wrch);
PCM_SG_UNLOCK();
if (sg_ids != 0)
free_unr(pcmsg_unrhdr, sg_ids);
CHN_LOCK(wrch);
chn_flush(wrch);
wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP |
CHN_F_DEAD | CHN_F_EXCLUSIVE | CHN_F_NBIO);
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);
}
}
PCM_LOCK(d);
}
PCM_RELEASE(d);
PCM_UNLOCK(d);
PCM_GIANT_LEAVE(d);
skip:
free(priv, M_DEVBUF);
priv = NULL;
}
static int
dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
{
struct dsp_cdevpriv *priv;
struct pcm_channel *ch;
struct snddev_info *d;
int error, dir;
if (i_dev == NULL || td == NULL)
return (ENODEV);
d = i_dev->si_drv1;
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;
error = devfs_set_cdevpriv(priv, dsp_close);
if (error != 0)
return (error);
PCM_GIANT_ENTER(d);
PCM_LOCK(d);
PCM_WAIT(d);
error = 0;
if (!DSP_F_VALID(flags))
error = EINVAL;
else if (!DSP_F_DUPLEX(flags) &&
((DSP_F_READ(flags) && d->reccount == 0) ||
(DSP_F_WRITE(flags) && d->playcount == 0)))
error = ENOTSUP;
if (pcm_getflags(d->dev) & SD_F_SIMPLEX) {
if (DSP_F_DUPLEX(flags)) {
if (CHN_EMPTY(d, channels.pcm.opened)) {
if (d->playcount > 0)
flags &= ~FREAD;
else if (d->reccount > 0)
flags &= ~FWRITE;
} else {
ch = CHN_FIRST(d, channels.pcm.opened);
if (ch->direction == PCMDIR_PLAY)
flags &= ~FREAD;
else if (ch->direction == PCMDIR_REC)
flags &= ~FWRITE;
}
} else if (!CHN_EMPTY(d, channels.pcm.opened)) {
ch = CHN_FIRST(d, channels.pcm.opened);
dir = DSP_F_READ(flags) ? PCMDIR_REC : PCMDIR_PLAY;
if (ch->direction != dir)
error = ENOTSUP;
}
}
if (error != 0) {
PCM_UNLOCK(d);
PCM_GIANT_EXIT(d);
return (error);
}
PCM_ACQUIRE(d);
PCM_UNLOCK(d);
if (DSP_F_WRITE(flags)) {
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);
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_RELEASE_QUICK(d);
PCM_GIANT_LEAVE(d);
return (0);
}
static __inline int
dsp_io_ops(struct dsp_cdevpriv *priv, struct uio *buf)
{
struct snddev_info *d;
struct pcm_channel *ch;
int (*chn_io)(struct pcm_channel *, struct uio *);
int ret;
d = priv->sc;
if (!DSP_REGISTERED(d))
return (EBADF);
PCM_GIANT_ENTER(d);
switch (buf->uio_rw) {
case UIO_READ:
ch = priv->rdch;
chn_io = chn_read;
break;
case UIO_WRITE:
ch = priv->wrch;
chn_io = chn_write;
break;
}
if (ch == NULL) {
PCM_GIANT_EXIT(d);
return (ENXIO);
}
CHN_LOCK(ch);
if (!(ch->flags & CHN_F_BUSY) ||
(ch->flags & (CHN_F_MMAP | CHN_F_DEAD))) {
CHN_UNLOCK(ch);
PCM_GIANT_EXIT(d);
return (ENXIO);
} else if (!(ch->flags & CHN_F_RUNNING))
ch->flags |= CHN_F_RUNNING;
ch->inprog++;
ret = chn_io(ch, buf);
ch->inprog--;
CHN_BROADCAST(&ch->cv);
CHN_UNLOCK(ch);
PCM_GIANT_LEAVE(d);
return (ret);
}
static int
dsp_read(struct cdev *i_dev, struct uio *buf, int flag)
{
struct dsp_cdevpriv *priv;
int err;
if ((err = devfs_get_cdevpriv((void **)&priv)) != 0)
return (err);
return (dsp_io_ops(priv, buf));
}
static int
dsp_write(struct cdev *i_dev, struct uio *buf, int flag)
{
struct dsp_cdevpriv *priv;
int err;
if ((err = devfs_get_cdevpriv((void **)&priv)) != 0)
return (err);
return (dsp_io_ops(priv, buf));
}
static int
dsp_ioctl_channel(struct dsp_cdevpriv *priv, struct pcm_channel *ch,
u_long cmd, caddr_t arg)
{
struct snddev_info *d;
struct pcm_channel *rdch, *wrch;
int j, left, right, center, mute;
d = priv->sc;
if (!PCM_REGISTERED(d) || !(pcm_getflags(d->dev) & SD_F_VPC))
return (-1);
PCM_UNLOCKASSERT(d);
j = cmd & 0xff;
rdch = priv->rdch;
wrch = priv->wrch;
if (ch == NULL) {
if (j == SOUND_MIXER_RECLEV && rdch != NULL)
ch = rdch;
else if (j == SOUND_MIXER_PCM && wrch != NULL)
ch = wrch;
}
if (ch == NULL)
return (EINVAL);
CHN_LOCK(ch);
if (!(ch->feederflags & (1 << FEEDER_VOLUME))) {
CHN_UNLOCK(ch);
return (EINVAL);
}
switch (cmd & ~0xff) {
case MIXER_WRITE(0):
switch (j) {
case SOUND_MIXER_MUTE:
if (ch->direction == PCMDIR_REC) {
chn_setmute_multi(ch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_RECLEV) != 0);
} else {
chn_setmute_multi(ch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_PCM) != 0);
}
break;
case SOUND_MIXER_PCM:
if (ch->direction != PCMDIR_PLAY)
break;
left = *(int *)arg & 0x7f;
right = ((*(int *)arg) >> 8) & 0x7f;
center = (left + right) >> 1;
chn_setvolume_multi(ch, SND_VOL_C_PCM,
left, right, center);
break;
case SOUND_MIXER_RECLEV:
if (ch->direction != PCMDIR_REC)
break;
left = *(int *)arg & 0x7f;
right = ((*(int *)arg) >> 8) & 0x7f;
center = (left + right) >> 1;
chn_setvolume_multi(ch, SND_VOL_C_PCM,
left, right, center);
break;
default:
break;
}
break;
case MIXER_READ(0):
switch (j) {
case SOUND_MIXER_MUTE:
mute = chn_getmute_matrix(ch,
SND_VOL_C_PCM, SND_CHN_T_FL) ||
chn_getmute_matrix(ch, SND_VOL_C_PCM, SND_CHN_T_FR);
if (ch->direction == PCMDIR_REC) {
*(int *)arg = mute << SOUND_MIXER_RECLEV;
} else {
*(int *)arg = mute << SOUND_MIXER_PCM;
}
break;
case SOUND_MIXER_PCM:
if (ch->direction != PCMDIR_PLAY)
break;
*(int *)arg = chn_getvolume_matrix(ch,
SND_VOL_C_PCM, SND_CHN_T_FL);
*(int *)arg |= chn_getvolume_matrix(ch,
SND_VOL_C_PCM, SND_CHN_T_FR) << 8;
break;
case SOUND_MIXER_RECLEV:
if (ch->direction != PCMDIR_REC)
break;
*(int *)arg = chn_getvolume_matrix(ch,
SND_VOL_C_PCM, SND_CHN_T_FL);
*(int *)arg |= chn_getvolume_matrix(ch,
SND_VOL_C_PCM, SND_CHN_T_FR) << 8;
break;
case SOUND_MIXER_DEVMASK:
case SOUND_MIXER_CAPS:
case SOUND_MIXER_STEREODEVS:
if (ch->direction == PCMDIR_REC)
*(int *)arg = SOUND_MASK_RECLEV;
else
*(int *)arg = SOUND_MASK_PCM;
break;
default:
*(int *)arg = 0;
break;
}
break;
default:
break;
}
CHN_UNLOCK(ch);
return (0);
}
#ifdef COMPAT_FREEBSD32
typedef struct _snd_chan_param32 {
uint32_t play_rate;
uint32_t rec_rate;
uint32_t play_format;
uint32_t rec_format;
} snd_chan_param32;
#define AIOGFMT32 _IOC_NEWTYPE(AIOGFMT, snd_chan_param32)
#define AIOSFMT32 _IOC_NEWTYPE(AIOSFMT, snd_chan_param32)
typedef struct _snd_capabilities32 {
uint32_t rate_min, rate_max;
uint32_t formats;
uint32_t bufsize;
uint32_t mixers;
uint32_t inputs;
uint16_t left, right;
} snd_capabilities32;
#define AIOGCAP32 _IOC_NEWTYPE(AIOGCAP, snd_capabilities32)
typedef struct audio_errinfo32
{
int32_t play_underruns;
int32_t rec_overruns;
uint32_t play_ptradjust;
uint32_t rec_ptradjust;
int32_t play_errorcount;
int32_t rec_errorcount;
int32_t play_lasterror;
int32_t rec_lasterror;
int32_t play_errorparm;
int32_t rec_errorparm;
int32_t filler[16];
} audio_errinfo32;
#define SNDCTL_DSP_GETERROR32 _IOC_NEWTYPE(SNDCTL_DSP_GETERROR, audio_errinfo32)
#endif
static int
dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
struct thread *td)
{
struct dsp_cdevpriv *priv;
struct pcm_channel *chn, *rdch, *wrch;
struct snddev_info *d;
u_long xcmd;
int *arg_i, ret, tmp, err;
if ((err = devfs_get_cdevpriv((void **)&priv)) != 0)
return (err);
d = priv->sc;
if (!DSP_REGISTERED(d))
return (EBADF);
PCM_GIANT_ENTER(d);
arg_i = (int *)arg;
ret = 0;
xcmd = 0;
chn = NULL;
if (IOCGROUP(cmd) == 'M') {
if (cmd == OSS_GETVERSION) {
*arg_i = SOUND_VERSION;
PCM_GIANT_EXIT(d);
return (0);
}
ret = dsp_ioctl_channel(priv, NULL, cmd, arg);
if (ret != -1) {
PCM_GIANT_EXIT(d);
return (ret);
}
if (d->mixer_dev != NULL) {
PCM_ACQUIRE_QUICK(d);
ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td,
MIXER_CMD_DIRECT);
PCM_RELEASE_QUICK(d);
} else
ret = EBADF;
PCM_GIANT_EXIT(d);
return (ret);
}
if (IOCGROUP(cmd) == 'X') {
PCM_ACQUIRE_QUICK(d);
switch(cmd) {
case SNDCTL_SYSINFO:
sound_oss_sysinfo((oss_sysinfo *)arg);
break;
case SNDCTL_CARDINFO:
ret = sound_oss_card_info((oss_card_info *)arg);
break;
case SNDCTL_AUDIOINFO:
ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg,
false);
break;
case SNDCTL_AUDIOINFO_EX:
ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg,
true);
break;
case SNDCTL_ENGINEINFO:
ret = dsp_oss_engineinfo(i_dev, (oss_audioinfo *)arg);
break;
case SNDCTL_MIXERINFO:
ret = mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg);
break;
default:
ret = EINVAL;
}
PCM_RELEASE_QUICK(d);
PCM_GIANT_EXIT(d);
return (ret);
}
rdch = priv->rdch;
wrch = priv->wrch;
if (wrch != NULL && (wrch->flags & CHN_F_DEAD))
wrch = NULL;
if (rdch != NULL && (rdch->flags & CHN_F_DEAD))
rdch = NULL;
if (wrch == NULL && rdch == NULL) {
PCM_GIANT_EXIT(d);
return (EINVAL);
}
switch(cmd) {
case AIONWRITE:
if (wrch) {
CHN_LOCK(wrch);
*arg_i = sndbuf_getfree(wrch->bufsoft);
CHN_UNLOCK(wrch);
} else {
*arg_i = 0;
ret = EINVAL;
}
break;
case AIOSSIZE:
{
struct snd_size *p = (struct snd_size *)arg;
p->play_size = 0;
p->rec_size = 0;
PCM_ACQUIRE_QUICK(d);
if (wrch) {
CHN_LOCK(wrch);
chn_setblocksize(wrch, 2, p->play_size);
p->play_size = wrch->bufsoft->blksz;
CHN_UNLOCK(wrch);
}
if (rdch) {
CHN_LOCK(rdch);
chn_setblocksize(rdch, 2, p->rec_size);
p->rec_size = rdch->bufsoft->blksz;
CHN_UNLOCK(rdch);
}
PCM_RELEASE_QUICK(d);
}
break;
case AIOGSIZE:
{
struct snd_size *p = (struct snd_size *)arg;
if (wrch) {
CHN_LOCK(wrch);
p->play_size = wrch->bufsoft->blksz;
CHN_UNLOCK(wrch);
}
if (rdch) {
CHN_LOCK(rdch);
p->rec_size = rdch->bufsoft->blksz;
CHN_UNLOCK(rdch);
}
}
break;
case AIOSFMT:
case AIOGFMT:
#ifdef COMPAT_FREEBSD32
case AIOSFMT32:
case AIOGFMT32:
#endif
{
snd_chan_param *p = (snd_chan_param *)arg;
#ifdef COMPAT_FREEBSD32
snd_chan_param32 *p32 = (snd_chan_param32 *)arg;
snd_chan_param param;
if (cmd == AIOSFMT32) {
p = ¶m;
p->play_rate = p32->play_rate;
p->rec_rate = p32->rec_rate;
p->play_format = p32->play_format;
p->rec_format = p32->rec_format;
}
#endif
if (cmd == AIOSFMT &&
((p->play_format != 0 && p->play_rate == 0) ||
(p->rec_format != 0 && p->rec_rate == 0))) {
ret = EINVAL;
break;
}
PCM_ACQUIRE_QUICK(d);
if (wrch) {
CHN_LOCK(wrch);
if (cmd == AIOSFMT && p->play_format != 0) {
chn_setformat(wrch,
SND_FORMAT(p->play_format,
AFMT_CHANNEL(wrch->format),
AFMT_EXTCHANNEL(wrch->format)));
chn_setspeed(wrch, p->play_rate);
}
p->play_rate = wrch->speed;
p->play_format = AFMT_ENCODING(wrch->format);
CHN_UNLOCK(wrch);
} else {
p->play_rate = 0;
p->play_format = 0;
}
if (rdch) {
CHN_LOCK(rdch);
if (cmd == AIOSFMT && p->rec_format != 0) {
chn_setformat(rdch,
SND_FORMAT(p->rec_format,
AFMT_CHANNEL(rdch->format),
AFMT_EXTCHANNEL(rdch->format)));
chn_setspeed(rdch, p->rec_rate);
}
p->rec_rate = rdch->speed;
p->rec_format = AFMT_ENCODING(rdch->format);
CHN_UNLOCK(rdch);
} else {
p->rec_rate = 0;
p->rec_format = 0;
}
PCM_RELEASE_QUICK(d);
#ifdef COMPAT_FREEBSD32
if (cmd == AIOSFMT32 || cmd == AIOGFMT32) {
p32->play_rate = p->play_rate;
p32->rec_rate = p->rec_rate;
p32->play_format = p->play_format;
p32->rec_format = p->rec_format;
}
#endif
}
break;
case AIOGCAP:
#ifdef COMPAT_FREEBSD32
case AIOGCAP32:
#endif
{
snd_capabilities *p = (snd_capabilities *)arg;
struct pcmchan_caps *pcaps = NULL, *rcaps = NULL;
struct cdev *pdev;
#ifdef COMPAT_FREEBSD32
snd_capabilities32 *p32 = (snd_capabilities32 *)arg;
snd_capabilities capabilities;
if (cmd == AIOGCAP32) {
p = &capabilities;
p->rate_min = p32->rate_min;
p->rate_max = p32->rate_max;
p->formats = p32->formats;
p->bufsize = p32->bufsize;
p->mixers = p32->mixers;
p->inputs = p32->inputs;
p->left = p32->left;
p->right = p32->right;
}
#endif
PCM_LOCK(d);
if (rdch) {
CHN_LOCK(rdch);
rcaps = chn_getcaps(rdch);
}
if (wrch) {
CHN_LOCK(wrch);
pcaps = chn_getcaps(wrch);
}
p->rate_min = max(rcaps? rcaps->minspeed : 0,
pcaps? pcaps->minspeed : 0);
p->rate_max = min(rcaps? rcaps->maxspeed : 1000000,
pcaps? pcaps->maxspeed : 1000000);
p->bufsize = min(rdch? rdch->bufsoft->bufsize : 1000000,
wrch? wrch->bufsoft->bufsize : 1000000);
p->formats = (rdch? chn_getformats(rdch) : 0xffffffff) &
(wrch? chn_getformats(wrch) : 0xffffffff);
if (rdch && wrch) {
p->formats |=
(pcm_getflags(d->dev) & SD_F_SIMPLEX) ? 0 :
AFMT_FULLDUPLEX;
}
pdev = d->mixer_dev;
p->mixers = 1;
p->inputs = pdev->si_drv1? mix_getdevs(pdev->si_drv1) : 0;
p->left = p->right = 100;
if (wrch)
CHN_UNLOCK(wrch);
if (rdch)
CHN_UNLOCK(rdch);
PCM_UNLOCK(d);
#ifdef COMPAT_FREEBSD32
if (cmd == AIOGCAP32) {
p32->rate_min = p->rate_min;
p32->rate_max = p->rate_max;
p32->formats = p->formats;
p32->bufsize = p->bufsize;
p32->mixers = p->mixers;
p32->inputs = p->inputs;
p32->left = p->left;
p32->right = p->right;
}
#endif
}
break;
case AIOSTOP:
if (*arg_i == AIOSYNC_PLAY && wrch) {
CHN_LOCK(wrch);
*arg_i = chn_abort(wrch);
CHN_UNLOCK(wrch);
} else if (*arg_i == AIOSYNC_CAPTURE && rdch) {
CHN_LOCK(rdch);
*arg_i = chn_abort(rdch);
CHN_UNLOCK(rdch);
} else {
printf("AIOSTOP: bad channel 0x%x\n", *arg_i);
*arg_i = 0;
}
break;
case AIOSYNC:
printf("AIOSYNC chan 0x%03lx pos %lu unimplemented\n",
((snd_sync_parm *)arg)->chan, ((snd_sync_parm *)arg)->pos);
break;
case FIONREAD:
if (rdch) {
CHN_LOCK(rdch);
*arg_i = sndbuf_getready(rdch->bufsoft);
CHN_UNLOCK(rdch);
} else {
*arg_i = 0;
ret = EINVAL;
}
break;
case FIOASYNC:
DEB( printf("FIOASYNC\n") ; )
break;
case SNDCTL_DSP_NONBLOCK:
case FIONBIO:
if (rdch) {
CHN_LOCK(rdch);
if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i)
rdch->flags |= CHN_F_NBIO;
else
rdch->flags &= ~CHN_F_NBIO;
CHN_UNLOCK(rdch);
}
if (wrch) {
CHN_LOCK(wrch);
if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i)
wrch->flags |= CHN_F_NBIO;
else
wrch->flags &= ~CHN_F_NBIO;
CHN_UNLOCK(wrch);
}
break;
case SNDCTL_DSP_GETBLKSIZE:
chn = wrch ? wrch : rdch;
if (chn) {
CHN_LOCK(chn);
*arg_i = chn->bufsoft->blksz;
CHN_UNLOCK(chn);
} else {
*arg_i = 0;
ret = EINVAL;
}
break;
case SNDCTL_DSP_SETBLKSIZE:
RANGE(*arg_i, 16, 65536);
PCM_ACQUIRE_QUICK(d);
if (wrch) {
CHN_LOCK(wrch);
chn_setblocksize(wrch, 2, *arg_i);
CHN_UNLOCK(wrch);
}
if (rdch) {
CHN_LOCK(rdch);
chn_setblocksize(rdch, 2, *arg_i);
CHN_UNLOCK(rdch);
}
PCM_RELEASE_QUICK(d);
break;
case SNDCTL_DSP_RESET:
DEB(printf("dsp reset\n"));
if (wrch) {
CHN_LOCK(wrch);
chn_abort(wrch);
chn_resetbuf(wrch);
CHN_UNLOCK(wrch);
}
if (rdch) {
CHN_LOCK(rdch);
chn_abort(rdch);
chn_resetbuf(rdch);
CHN_UNLOCK(rdch);
}
break;
case SNDCTL_DSP_SYNC:
DEB(printf("dsp sync\n"));
if (wrch) {
CHN_LOCK(wrch);
chn_sync(wrch, 0);
CHN_UNLOCK(wrch);
}
break;
case SNDCTL_DSP_SPEED:
tmp = 0;
PCM_ACQUIRE_QUICK(d);
if (wrch) {
CHN_LOCK(wrch);
ret = chn_setspeed(wrch, *arg_i);
tmp = wrch->speed;
CHN_UNLOCK(wrch);
}
if (rdch && ret == 0) {
CHN_LOCK(rdch);
ret = chn_setspeed(rdch, *arg_i);
if (tmp == 0)
tmp = rdch->speed;
CHN_UNLOCK(rdch);
}
PCM_RELEASE_QUICK(d);
*arg_i = tmp;
break;
case SOUND_PCM_READ_RATE:
chn = wrch ? wrch : rdch;
if (chn) {
CHN_LOCK(chn);
*arg_i = chn->speed;
CHN_UNLOCK(chn);
} else {
*arg_i = 0;
ret = EINVAL;
}
break;
case SNDCTL_DSP_STEREO:
tmp = -1;
*arg_i = (*arg_i)? 2 : 1;
PCM_ACQUIRE_QUICK(d);
if (wrch) {
CHN_LOCK(wrch);
ret = chn_setformat(wrch,
SND_FORMAT(wrch->format, *arg_i, 0));
tmp = (AFMT_CHANNEL(wrch->format) > 1)? 1 : 0;
CHN_UNLOCK(wrch);
}
if (rdch && ret == 0) {
CHN_LOCK(rdch);
ret = chn_setformat(rdch,
SND_FORMAT(rdch->format, *arg_i, 0));
if (tmp == -1)
tmp = (AFMT_CHANNEL(rdch->format) > 1)? 1 : 0;
CHN_UNLOCK(rdch);
}
PCM_RELEASE_QUICK(d);
*arg_i = tmp;
break;
case SOUND_PCM_WRITE_CHANNELS:
if (*arg_i < 0 || *arg_i > AFMT_CHANNEL_MAX) {
*arg_i = 0;
ret = EINVAL;
break;
}
if (*arg_i != 0) {
uint32_t ext = 0;
tmp = 0;
if (!(pcm_getflags(d->dev) & SD_F_BITPERFECT)) {
struct pcmchan_matrix *m;
if (*arg_i > SND_CHN_MAX)
*arg_i = SND_CHN_MAX;
m = feeder_matrix_default_channel_map(*arg_i);
if (m != NULL)
ext = m->ext;
}
PCM_ACQUIRE_QUICK(d);
if (wrch) {
CHN_LOCK(wrch);
ret = chn_setformat(wrch,
SND_FORMAT(wrch->format, *arg_i, ext));
tmp = AFMT_CHANNEL(wrch->format);
CHN_UNLOCK(wrch);
}
if (rdch && ret == 0) {
CHN_LOCK(rdch);
ret = chn_setformat(rdch,
SND_FORMAT(rdch->format, *arg_i, ext));
if (tmp == 0)
tmp = AFMT_CHANNEL(rdch->format);
CHN_UNLOCK(rdch);
}
PCM_RELEASE_QUICK(d);
*arg_i = tmp;
} else {
chn = wrch ? wrch : rdch;
CHN_LOCK(chn);
*arg_i = AFMT_CHANNEL(chn->format);
CHN_UNLOCK(chn);
}
break;
case SOUND_PCM_READ_CHANNELS:
chn = wrch ? wrch : rdch;
if (chn) {
CHN_LOCK(chn);
*arg_i = AFMT_CHANNEL(chn->format);
CHN_UNLOCK(chn);
} else {
*arg_i = 0;
ret = EINVAL;
}
break;
case SNDCTL_DSP_GETFMTS:
chn = wrch ? wrch : rdch;
if (chn) {
CHN_LOCK(chn);
*arg_i = chn_getformats(chn);
CHN_UNLOCK(chn);
} else {
*arg_i = 0;
ret = EINVAL;
}
break;
case SNDCTL_DSP_SETFMT:
if (*arg_i != AFMT_QUERY) {
tmp = 0;
PCM_ACQUIRE_QUICK(d);
if (wrch) {
CHN_LOCK(wrch);
ret = chn_setformat(wrch, SND_FORMAT(*arg_i,
AFMT_CHANNEL(wrch->format),
AFMT_EXTCHANNEL(wrch->format)));
tmp = wrch->format;
CHN_UNLOCK(wrch);
}
if (rdch && ret == 0) {
CHN_LOCK(rdch);
ret = chn_setformat(rdch, SND_FORMAT(*arg_i,
AFMT_CHANNEL(rdch->format),
AFMT_EXTCHANNEL(rdch->format)));
if (tmp == 0)
tmp = rdch->format;
CHN_UNLOCK(rdch);
}
PCM_RELEASE_QUICK(d);
*arg_i = AFMT_ENCODING(tmp);
} else {
chn = wrch ? wrch : rdch;
CHN_LOCK(chn);
*arg_i = AFMT_ENCODING(chn->format);
CHN_UNLOCK(chn);
}
break;
case SNDCTL_DSP_SETFRAGMENT:
DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg));
{
uint32_t fragln = (*arg_i) & 0x0000ffff;
uint32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16;
uint32_t fragsz;
uint32_t r_maxfrags, r_fragsz;
RANGE(fragln, 4, 16);
fragsz = 1 << fragln;
if (maxfrags == 0)
maxfrags = CHN_2NDBUFMAXSIZE / fragsz;
if (maxfrags < 2)
maxfrags = 2;
if (maxfrags * fragsz > CHN_2NDBUFMAXSIZE)
maxfrags = CHN_2NDBUFMAXSIZE / fragsz;
DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz));
PCM_ACQUIRE_QUICK(d);
if (rdch) {
CHN_LOCK(rdch);
ret = chn_setblocksize(rdch, maxfrags, fragsz);
r_maxfrags = rdch->bufsoft->blkcnt;
r_fragsz = rdch->bufsoft->blksz;
CHN_UNLOCK(rdch);
} else {
r_maxfrags = maxfrags;
r_fragsz = fragsz;
}
if (wrch && ret == 0) {
CHN_LOCK(wrch);
ret = chn_setblocksize(wrch, maxfrags, fragsz);
maxfrags = wrch->bufsoft->blkcnt;
fragsz = wrch->bufsoft->blksz;
CHN_UNLOCK(wrch);
} else {
maxfrags = r_maxfrags;
fragsz = r_fragsz;
}
PCM_RELEASE_QUICK(d);
fragln = 0;
while (fragsz > 1) {
fragln++;
fragsz >>= 1;
}
*arg_i = (maxfrags << 16) | fragln;
}
break;
case SNDCTL_DSP_GETISPACE:
{
audio_buf_info *a = (audio_buf_info *)arg;
if (rdch) {
struct snd_dbuf *bs = rdch->bufsoft;
CHN_LOCK(rdch);
a->bytes = sndbuf_getready(bs);
a->fragments = a->bytes / bs->blksz;
a->fragstotal = bs->blkcnt;
a->fragsize = bs->blksz;
CHN_UNLOCK(rdch);
} else
ret = EINVAL;
}
break;
case SNDCTL_DSP_GETOSPACE:
{
audio_buf_info *a = (audio_buf_info *)arg;
if (wrch) {
struct snd_dbuf *bs = wrch->bufsoft;
CHN_LOCK(wrch);
a->bytes = sndbuf_getfree(bs);
a->fragments = a->bytes / bs->blksz;
a->fragstotal = bs->blkcnt;
a->fragsize = bs->blksz;
CHN_UNLOCK(wrch);
} else
ret = EINVAL;
}
break;
case SNDCTL_DSP_GETIPTR:
{
count_info *a = (count_info *)arg;
if (rdch) {
struct snd_dbuf *bs = rdch->bufsoft;
CHN_LOCK(rdch);
a->bytes = bs->total;
a->blocks = sndbuf_getblocks(bs) - rdch->blocks;
a->ptr = sndbuf_getfreeptr(bs);
rdch->blocks = sndbuf_getblocks(bs);
CHN_UNLOCK(rdch);
} else
ret = EINVAL;
}
break;
case SNDCTL_DSP_GETOPTR:
{
count_info *a = (count_info *)arg;
if (wrch) {
struct snd_dbuf *bs = wrch->bufsoft;
CHN_LOCK(wrch);
a->bytes = bs->total;
a->blocks = sndbuf_getblocks(bs) - wrch->blocks;
a->ptr = sndbuf_getreadyptr(bs);
wrch->blocks = sndbuf_getblocks(bs);
CHN_UNLOCK(wrch);
} else
ret = EINVAL;
}
break;
case SNDCTL_DSP_GETCAPS:
PCM_LOCK(d);
*arg_i = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER;
if (rdch && wrch && !(pcm_getflags(d->dev) & SD_F_SIMPLEX))
*arg_i |= PCM_CAP_DUPLEX;
if (rdch && (rdch->flags & CHN_F_VIRTUAL) != 0)
*arg_i |= PCM_CAP_VIRTUAL;
if (wrch && (wrch->flags & CHN_F_VIRTUAL) != 0)
*arg_i |= PCM_CAP_VIRTUAL;
PCM_UNLOCK(d);
break;
case SOUND_PCM_READ_BITS:
chn = wrch ? wrch : rdch;
if (chn) {
CHN_LOCK(chn);
if (chn->format & AFMT_8BIT)
*arg_i = 8;
else if (chn->format & AFMT_16BIT)
*arg_i = 16;
else if (chn->format & AFMT_24BIT)
*arg_i = 24;
else if (chn->format & AFMT_32BIT)
*arg_i = 32;
else
ret = EINVAL;
CHN_UNLOCK(chn);
} else {
*arg_i = 0;
ret = EINVAL;
}
break;
case SNDCTL_DSP_SETTRIGGER:
if (rdch) {
CHN_LOCK(rdch);
rdch->flags &= ~CHN_F_NOTRIGGER;
if (*arg_i & PCM_ENABLE_INPUT)
chn_start(rdch, 1);
else {
chn_abort(rdch);
chn_resetbuf(rdch);
rdch->flags |= CHN_F_NOTRIGGER;
}
CHN_UNLOCK(rdch);
}
if (wrch) {
CHN_LOCK(wrch);
wrch->flags &= ~CHN_F_NOTRIGGER;
if (*arg_i & PCM_ENABLE_OUTPUT)
chn_start(wrch, 1);
else {
chn_abort(wrch);
chn_resetbuf(wrch);
wrch->flags |= CHN_F_NOTRIGGER;
}
CHN_UNLOCK(wrch);
}
break;
case SNDCTL_DSP_GETTRIGGER:
*arg_i = 0;
if (wrch) {
CHN_LOCK(wrch);
if (wrch->flags & CHN_F_TRIGGERED)
*arg_i |= PCM_ENABLE_OUTPUT;
CHN_UNLOCK(wrch);
}
if (rdch) {
CHN_LOCK(rdch);
if (rdch->flags & CHN_F_TRIGGERED)
*arg_i |= PCM_ENABLE_INPUT;
CHN_UNLOCK(rdch);
}
break;
case SNDCTL_DSP_GETODELAY:
if (wrch) {
struct snd_dbuf *bs = wrch->bufsoft;
CHN_LOCK(wrch);
*arg_i = sndbuf_getready(bs);
CHN_UNLOCK(wrch);
} else
ret = EINVAL;
break;
case SNDCTL_DSP_POST:
if (wrch) {
CHN_LOCK(wrch);
wrch->flags &= ~CHN_F_NOTRIGGER;
chn_start(wrch, 1);
CHN_UNLOCK(wrch);
}
break;
case SNDCTL_DSP_SETDUPLEX:
PCM_LOCK(d);
if (rdch && wrch && (pcm_getflags(d->dev) & SD_F_SIMPLEX))
pcm_setflags(d->dev, pcm_getflags(d->dev)^SD_F_SIMPLEX);
PCM_UNLOCK(d);
break;
case SNDCTL_DSP_GETRECVOL:
if (xcmd == 0) {
xcmd = SOUND_MIXER_READ_RECLEV;
chn = rdch;
}
case SNDCTL_DSP_SETRECVOL:
if (xcmd == 0) {
xcmd = SOUND_MIXER_WRITE_RECLEV;
chn = rdch;
}
case SNDCTL_DSP_GETPLAYVOL:
if (xcmd == 0) {
xcmd = SOUND_MIXER_READ_PCM;
chn = wrch;
}
case SNDCTL_DSP_SETPLAYVOL:
if (xcmd == 0) {
xcmd = SOUND_MIXER_WRITE_PCM;
chn = wrch;
}
ret = dsp_ioctl_channel(priv, chn, xcmd, arg);
if (ret != -1) {
PCM_GIANT_EXIT(d);
return (ret);
}
if (d->mixer_dev != NULL) {
PCM_ACQUIRE_QUICK(d);
ret = mixer_ioctl_cmd(d->mixer_dev, xcmd, arg, -1, td,
MIXER_CMD_DIRECT);
PCM_RELEASE_QUICK(d);
} else
ret = ENOTSUP;
break;
case SNDCTL_DSP_GET_RECSRC_NAMES:
case SNDCTL_DSP_GET_RECSRC:
case SNDCTL_DSP_SET_RECSRC:
if (d->mixer_dev != NULL) {
PCM_ACQUIRE_QUICK(d);
ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td,
MIXER_CMD_DIRECT);
PCM_RELEASE_QUICK(d);
} else
ret = ENOTSUP;
break;
case SNDCTL_DSP_GET_PLAYTGT_NAMES:
{
oss_mixer_enuminfo *ei;
ei = (oss_mixer_enuminfo *)arg;
ei->dev = 0;
ei->ctrl = 0;
ei->version = 0;
ei->strindex[0] = 0;
if (wrch != NULL) {
ei->nvalues = 1;
strlcpy(ei->strings, wrch->name,
sizeof(ei->strings));
} else {
ei->nvalues = 0;
ei->strings[0] = '\0';
}
}
break;
case SNDCTL_DSP_GET_PLAYTGT:
case SNDCTL_DSP_SET_PLAYTGT:
if (wrch != NULL)
*arg_i = 0;
else
ret = EINVAL;
break;
case SNDCTL_DSP_SILENCE:
if (wrch == NULL)
ret = EINVAL;
else {
struct snd_dbuf *bs;
CHN_LOCK(wrch);
while (wrch->inprog != 0)
cv_wait(&wrch->cv, &wrch->lock);
bs = wrch->bufsoft;
if ((bs->shadbuf != NULL) && (sndbuf_getready(bs) > 0)) {
bs->sl = sndbuf_getready(bs);
sndbuf_dispose(bs, bs->shadbuf, sndbuf_getready(bs));
sndbuf_fillsilence(bs);
chn_start(wrch, 0);
}
CHN_UNLOCK(wrch);
}
break;
case SNDCTL_DSP_SKIP:
if (wrch == NULL)
ret = EINVAL;
else {
struct snd_dbuf *bs;
CHN_LOCK(wrch);
while (wrch->inprog != 0)
cv_wait(&wrch->cv, &wrch->lock);
bs = wrch->bufsoft;
if ((bs->shadbuf != NULL) && (bs->sl > 0)) {
sndbuf_softreset(bs);
sndbuf_acquire(bs, bs->shadbuf, bs->sl);
bs->sl = 0;
chn_start(wrch, 0);
}
CHN_UNLOCK(wrch);
}
break;
case SNDCTL_DSP_CURRENT_OPTR:
case SNDCTL_DSP_CURRENT_IPTR:
chn = (cmd == SNDCTL_DSP_CURRENT_OPTR) ? wrch : rdch;
if (chn == NULL)
ret = EINVAL;
else {
struct snd_dbuf *bs;
oss_count_t *oc = (oss_count_t *)arg;
CHN_LOCK(chn);
bs = chn->bufsoft;
oc->samples = bs->total / bs->align;
oc->fifo_samples = sndbuf_getready(bs) / bs->align;
CHN_UNLOCK(chn);
}
break;
case SNDCTL_DSP_HALT_OUTPUT:
case SNDCTL_DSP_HALT_INPUT:
chn = (cmd == SNDCTL_DSP_HALT_OUTPUT) ? wrch : rdch;
if (chn == NULL)
ret = EINVAL;
else {
CHN_LOCK(chn);
chn_abort(chn);
CHN_UNLOCK(chn);
}
break;
case SNDCTL_DSP_LOW_WATER:
if (wrch != NULL) {
CHN_LOCK(wrch);
wrch->lw = (*arg_i > 1) ? *arg_i : 1;
CHN_UNLOCK(wrch);
}
if (rdch != NULL) {
CHN_LOCK(rdch);
rdch->lw = (*arg_i > 1) ? *arg_i : 1;
CHN_UNLOCK(rdch);
}
break;
case SNDCTL_DSP_GETERROR:
#ifdef COMPAT_FREEBSD32
case SNDCTL_DSP_GETERROR32:
#endif
{
audio_errinfo *ei = (audio_errinfo *)arg;
#ifdef COMPAT_FREEBSD32
audio_errinfo errinfo;
audio_errinfo32 *ei32 = (audio_errinfo32 *)arg;
if (cmd == SNDCTL_DSP_GETERROR32) {
ei = &errinfo;
}
#endif
bzero((void *)ei, sizeof(*ei));
if (wrch != NULL) {
CHN_LOCK(wrch);
ei->play_underruns = wrch->xruns;
wrch->xruns = 0;
CHN_UNLOCK(wrch);
}
if (rdch != NULL) {
CHN_LOCK(rdch);
ei->rec_overruns = rdch->xruns;
rdch->xruns = 0;
CHN_UNLOCK(rdch);
}
#ifdef COMPAT_FREEBSD32
if (cmd == SNDCTL_DSP_GETERROR32) {
bzero((void *)ei32, sizeof(*ei32));
ei32->play_underruns = ei->play_underruns;
ei32->rec_overruns = ei->rec_overruns;
ei32->play_ptradjust = ei->play_ptradjust;
ei32->rec_ptradjust = ei->rec_ptradjust;
ei32->play_errorcount = ei->play_errorcount;
ei32->rec_errorcount = ei->rec_errorcount;
ei32->play_lasterror = ei->play_lasterror;
ei32->rec_lasterror = ei->rec_lasterror;
ei32->play_errorparm = ei->play_errorparm;
ei32->rec_errorparm = ei->rec_errorparm;
}
#endif
}
break;
case SNDCTL_DSP_SYNCGROUP:
PCM_ACQUIRE_QUICK(d);
ret = dsp_oss_syncgroup(wrch, rdch, (oss_syncgroup *)arg);
PCM_RELEASE_QUICK(d);
break;
case SNDCTL_DSP_SYNCSTART:
PCM_ACQUIRE_QUICK(d);
ret = dsp_oss_syncstart(*arg_i);
PCM_RELEASE_QUICK(d);
break;
case SNDCTL_DSP_POLICY:
PCM_ACQUIRE_QUICK(d);
ret = dsp_oss_policy(wrch, rdch, *arg_i);
PCM_RELEASE_QUICK(d);
break;
case SNDCTL_DSP_COOKEDMODE:
PCM_ACQUIRE_QUICK(d);
if (!(pcm_getflags(d->dev) & SD_F_BITPERFECT))
ret = dsp_oss_cookedmode(wrch, rdch, *arg_i);
PCM_RELEASE_QUICK(d);
break;
case SNDCTL_DSP_GET_CHNORDER:
PCM_ACQUIRE_QUICK(d);
ret = dsp_oss_getchnorder(wrch, rdch, (unsigned long long *)arg);
PCM_RELEASE_QUICK(d);
break;
case SNDCTL_DSP_SET_CHNORDER:
PCM_ACQUIRE_QUICK(d);
ret = dsp_oss_setchnorder(wrch, rdch, (unsigned long long *)arg);
PCM_RELEASE_QUICK(d);
break;
case SNDCTL_DSP_GETCHANNELMASK:
PCM_ACQUIRE_QUICK(d);
ret = dsp_oss_getchannelmask(wrch, rdch, (int *)arg);
PCM_RELEASE_QUICK(d);
break;
case SNDCTL_DSP_BIND_CHANNEL:
ret = EINVAL;
break;
#ifdef OSSV4_EXPERIMENT
case SNDCTL_DSP_GETOPEAKS:
case SNDCTL_DSP_GETIPEAKS:
chn = (cmd == SNDCTL_DSP_GETOPEAKS) ? wrch : rdch;
if (chn == NULL)
ret = EINVAL;
else {
oss_peaks_t *op = (oss_peaks_t *)arg;
int lpeak, rpeak;
CHN_LOCK(chn);
ret = chn_getpeaks(chn, &lpeak, &rpeak);
if (ret == -1)
ret = EINVAL;
else {
(*op)[0] = lpeak;
(*op)[1] = rpeak;
}
CHN_UNLOCK(chn);
}
break;
case SNDCTL_GETLABEL:
ret = dsp_oss_getlabel(wrch, rdch, (oss_label_t *)arg);
break;
case SNDCTL_SETLABEL:
ret = dsp_oss_setlabel(wrch, rdch, (oss_label_t *)arg);
break;
case SNDCTL_GETSONG:
ret = dsp_oss_getsong(wrch, rdch, (oss_longname_t *)arg);
break;
case SNDCTL_SETSONG:
ret = dsp_oss_setsong(wrch, rdch, (oss_longname_t *)arg);
break;
case SNDCTL_SETNAME:
ret = dsp_oss_setname(wrch, rdch, (oss_longname_t *)arg);
break;
#endif
case SNDCTL_DSP_MAPINBUF:
case SNDCTL_DSP_MAPOUTBUF:
case SNDCTL_DSP_SETSYNCRO:
case SNDCTL_DSP_SUBDIVIDE:
case SOUND_PCM_WRITE_FILTER:
case SOUND_PCM_READ_FILTER:
default:
DEB(printf("default ioctl fn 0x%08lx fail\n", cmd));
ret = EINVAL;
break;
}
PCM_GIANT_LEAVE(d);
return (ret);
}
static int
dsp_poll(struct cdev *i_dev, int events, struct thread *td)
{
struct dsp_cdevpriv *priv;
struct snddev_info *d;
struct pcm_channel *wrch, *rdch;
int ret, e, err;
if ((err = devfs_get_cdevpriv((void **)&priv)) != 0)
return (err);
d = priv->sc;
if (!DSP_REGISTERED(d)) {
return (events & (POLLHUP | POLLPRI | POLLIN |
POLLRDNORM | POLLOUT | POLLWRNORM));
}
PCM_GIANT_ENTER(d);
ret = 0;
dsp_lock_chans(priv, FREAD | FWRITE);
wrch = priv->wrch;
rdch = priv->rdch;
if (wrch != NULL && !(wrch->flags & CHN_F_DEAD)) {
e = (events & (POLLOUT | POLLWRNORM));
if (e)
ret |= chn_poll(wrch, e, td);
}
if (rdch != NULL && !(rdch->flags & CHN_F_DEAD)) {
e = (events & (POLLIN | POLLRDNORM));
if (e)
ret |= chn_poll(rdch, e, td);
}
dsp_unlock_chans(priv, FREAD | FWRITE);
PCM_GIANT_LEAVE(d);
return (ret);
}
static int
dsp_mmap(struct cdev *i_dev, vm_ooffset_t offset, vm_paddr_t *paddr,
int nprot, vm_memattr_t *memattr)
{
*paddr = vtophys(offset);
return (0);
}
static int
dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset,
vm_size_t size, struct vm_object **object, int nprot)
{
struct dsp_cdevpriv *priv;
struct snddev_info *d;
struct pcm_channel *wrch, *rdch, *c;
int err;
#ifdef SV_ABI_LINUX
if ((nprot & PROT_EXEC) && (dsp_mmap_allow_prot_exec < 0 ||
(dsp_mmap_allow_prot_exec == 0 &&
SV_CURPROC_ABI() != SV_ABI_LINUX)))
#else
if ((nprot & PROT_EXEC) && dsp_mmap_allow_prot_exec < 1)
#endif
return (EINVAL);
if ((nprot & (PROT_READ | PROT_WRITE)) == 0)
return (EINVAL);
if ((err = devfs_get_cdevpriv((void **)&priv)) != 0)
return (err);
d = priv->sc;
if (!DSP_REGISTERED(d))
return (EINVAL);
PCM_GIANT_ENTER(d);
dsp_lock_chans(priv, FREAD | FWRITE);
wrch = priv->wrch;
rdch = priv->rdch;
c = ((nprot & PROT_WRITE) != 0) ? wrch : rdch;
if (c == NULL || (c->flags & CHN_F_MMAP_INVALID) ||
(*offset + size) > c->bufsoft->allocsize ||
(wrch != NULL && (wrch->flags & CHN_F_MMAP_INVALID)) ||
(rdch != NULL && (rdch->flags & CHN_F_MMAP_INVALID))) {
dsp_unlock_chans(priv, FREAD | FWRITE);
PCM_GIANT_EXIT(d);
return (EINVAL);
}
if (wrch != NULL)
wrch->flags |= CHN_F_MMAP;
if (rdch != NULL)
rdch->flags |= CHN_F_MMAP;
*offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset);
dsp_unlock_chans(priv, FREAD | FWRITE);
*object = vm_pager_allocate(OBJT_DEVICE, i_dev,
size, nprot, *offset, curthread->td_ucred);
PCM_GIANT_LEAVE(d);
if (*object == NULL)
return (EINVAL);
return (0);
}
static const char *dsp_aliases[] = {
"dsp_ac3",
"dsp_mmap",
"dsp_multich",
"dsp_spdifout",
"dsp_spdifin",
};
static void
dsp_clone(void *arg, struct ucred *cred, char *name, int namelen,
struct cdev **dev)
{
struct snddev_info *d;
size_t i;
if (*dev != NULL)
return;
if (strcmp(name, "dsp") == 0 && dsp_basename_clone)
goto found;
for (i = 0; i < nitems(dsp_aliases); i++) {
if (strcmp(name, dsp_aliases[i]) == 0)
goto found;
}
return;
found:
bus_topo_lock();
d = devclass_get_softc(pcm_devclass, snd_unit);
if (DSP_REGISTERED(d)) {
*dev = d->dsp_dev;
dev_ref(*dev);
}
bus_topo_unlock();
}
static void
dsp_sysinit(void *p)
{
if (dsp_ehtag != NULL)
return;
dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000);
}
static void
dsp_sysuninit(void *p)
{
if (dsp_ehtag == NULL)
return;
EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag);
dsp_ehtag = NULL;
}
SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL);
SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL);
static void
dsp_oss_audioinfo_unavail(oss_audioinfo *ai, int unit)
{
bzero(ai, sizeof(*ai));
ai->dev = unit;
snprintf(ai->name, sizeof(ai->name), "pcm%d (unavailable)", unit);
ai->pid = -1;
strlcpy(ai->cmd, CHN_COMM_UNUSED, sizeof(ai->cmd));
ai->card_number = unit;
ai->port_number = unit;
ai->mixer_dev = -1;
ai->legacy_device = unit;
}
int
dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai, bool ex)
{
struct pcmchan_caps *caps;
struct pcm_channel *ch;
struct snddev_info *d;
uint32_t fmts;
int i, minch, maxch, unit;
if (ai->dev == -1 && i_dev->si_devsw != &dsp_cdevsw)
return (EINVAL);
bus_topo_lock();
for (unit = 0; pcm_devclass != NULL &&
unit < devclass_get_maxunit(pcm_devclass); unit++) {
d = devclass_get_softc(pcm_devclass, unit);
if (!PCM_REGISTERED(d)) {
if ((ai->dev == -1 && unit == snd_unit) ||
ai->dev == unit) {
dsp_oss_audioinfo_unavail(ai, unit);
bus_topo_unlock();
return (0);
} else {
d = NULL;
continue;
}
}
PCM_UNLOCKASSERT(d);
PCM_LOCK(d);
if ((ai->dev == -1 && d->dsp_dev == i_dev) ||
(ai->dev == unit)) {
PCM_UNLOCK(d);
break;
} else {
PCM_UNLOCK(d);
d = NULL;
}
}
bus_topo_unlock();
if (d == NULL)
return (EINVAL);
PCM_UNLOCKASSERT(d);
PCM_LOCK(d);
bzero((void *)ai, sizeof(oss_audioinfo));
ai->dev = unit;
strlcpy(ai->name, device_get_desc(d->dev), sizeof(ai->name));
ai->pid = -1;
strlcpy(ai->cmd, CHN_COMM_UNKNOWN, sizeof(ai->cmd));
ai->card_number = unit;
ai->port_number = unit;
ai->mixer_dev = (d->mixer_dev != NULL) ? unit : -1;
ai->legacy_device = unit;
snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit);
ai->enabled = device_is_attached(d->dev) ? 1 : 0;
ai->next_play_engine = 0;
ai->next_rec_engine = 0;
ai->busy = 0;
ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER;
ai->iformats = 0;
ai->oformats = 0;
ai->min_rate = INT_MAX;
ai->max_rate = 0;
ai->min_channels = INT_MAX;
ai->max_channels = 0;
CHN_FOREACH(ch, d, channels.pcm) {
CHN_UNLOCKASSERT(ch);
CHN_LOCK(ch);
if ((ex && (ch->flags & CHN_F_VIRTUAL) != 0) ||
((!ex && (ch->flags & CHN_F_VIRTUAL) == 0) &&
(d->pvchancount > 0 || d->rvchancount > 0))) {
CHN_UNLOCK(ch);
continue;
}
if ((ch->flags & CHN_F_BUSY) == 0) {
ai->busy |= (ch->direction == PCMDIR_PLAY) ?
OPEN_WRITE : OPEN_READ;
}
ai->caps |=
((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) |
((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT :
PCM_CAP_INPUT);
caps = chn_getcaps(ch);
minch = INT_MAX;
maxch = 0;
fmts = 0;
for (i = 0; caps->fmtlist[i]; i++) {
fmts |= AFMT_ENCODING(caps->fmtlist[i]);
minch = min(AFMT_CHANNEL(caps->fmtlist[i]), minch);
maxch = max(AFMT_CHANNEL(caps->fmtlist[i]), maxch);
}
if (ch->direction == PCMDIR_PLAY)
ai->oformats |= fmts;
else
ai->iformats |= fmts;
if (ex || (pcm_getflags(d->dev) & SD_F_BITPERFECT)) {
ai->min_rate = min(ai->min_rate, caps->minspeed);
ai->max_rate = max(ai->max_rate, caps->maxspeed);
} else {
ai->min_rate = min(ai->min_rate, feeder_rate_min);
ai->max_rate = max(ai->max_rate, feeder_rate_max);
}
ai->min_channels = min(ai->min_channels, minch);
ai->max_channels = max(ai->max_channels, maxch);
CHN_UNLOCK(ch);
}
if (ai->min_rate == INT_MAX)
ai->min_rate = 0;
if (ai->min_channels == INT_MAX)
ai->min_channels = 0;
PCM_UNLOCK(d);
return (0);
}
static int
dsp_oss_engineinfo_cb(void *data, void *arg)
{
struct dsp_cdevpriv *priv = data;
struct pcm_channel *ch = arg;
if (DSP_REGISTERED(priv->sc) && (ch == priv->rdch || ch == priv->wrch))
return (1);
return (0);
}
int
dsp_oss_engineinfo(struct cdev *i_dev, oss_audioinfo *ai)
{
struct pcmchan_caps *caps;
struct pcm_channel *ch;
struct snddev_info *d;
uint32_t fmts;
int i, nchan, *rates, minch, maxch, unit;
if (ai->dev == -1 && i_dev->si_devsw != &dsp_cdevsw)
return (EINVAL);
ch = NULL;
nchan = 0;
bus_topo_lock();
for (unit = 0; pcm_devclass != NULL &&
unit < devclass_get_maxunit(pcm_devclass); unit++) {
d = devclass_get_softc(pcm_devclass, unit);
if (!PCM_REGISTERED(d))
continue;
PCM_UNLOCKASSERT(d);
PCM_LOCK(d);
CHN_FOREACH(ch, d, channels.pcm) {
CHN_UNLOCKASSERT(ch);
CHN_LOCK(ch);
if ((ai->dev == -1 && devfs_foreach_cdevpriv(
i_dev, dsp_oss_engineinfo_cb, ch) != 0) ||
ai->dev == nchan)
break;
CHN_UNLOCK(ch);
++nchan;
}
if (ch == NULL) {
PCM_UNLOCK(d);
continue;
}
caps = chn_getcaps(ch);
bzero((void *)ai, sizeof(oss_audioinfo));
ai->dev = nchan;
strlcpy(ai->name, ch->name, sizeof(ai->name));
if ((ch->flags & CHN_F_BUSY) == 0)
ai->busy = 0;
else
ai->busy = (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ;
ai->pid = ch->pid;
strlcpy(ai->cmd, ch->comm, sizeof(ai->cmd));
ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER |
((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) |
((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT);
minch = INT_MAX;
maxch = 0;
fmts = 0;
for (i = 0; caps->fmtlist[i]; i++) {
fmts |= AFMT_ENCODING(caps->fmtlist[i]);
minch = min(AFMT_CHANNEL(caps->fmtlist[i]), minch);
maxch = max(AFMT_CHANNEL(caps->fmtlist[i]), maxch);
}
if (ch->direction == PCMDIR_PLAY)
ai->oformats = fmts;
else
ai->iformats = fmts;
ai->card_number = unit;
ai->port_number = unit;
ai->mixer_dev = (d->mixer_dev != NULL) ? unit : -1;
ai->legacy_device = unit;
snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit);
ai->enabled = device_is_attached(d->dev) ? 1 : 0;
if ((ch->flags & CHN_F_EXCLUSIVE) ||
(pcm_getflags(d->dev) & SD_F_BITPERFECT)) {
ai->min_rate = caps->minspeed;
ai->max_rate = caps->maxspeed;
} else {
ai->min_rate = feeder_rate_min;
ai->max_rate = feeder_rate_max;
}
ai->min_channels = minch;
ai->max_channels = maxch;
ai->nrates = chn_getrates(ch, &rates);
if (ai->nrates > OSS_MAX_SAMPLE_RATES)
ai->nrates = OSS_MAX_SAMPLE_RATES;
for (i = 0; i < ai->nrates; i++)
ai->rates[i] = rates[i];
ai->next_play_engine = 0;
ai->next_rec_engine = 0;
CHN_UNLOCK(ch);
PCM_UNLOCK(d);
bus_topo_unlock();
return (0);
}
bus_topo_unlock();
return (EINVAL);
}
static int
dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group)
{
struct pcmchan_syncmember *smrd, *smwr;
struct pcmchan_syncgroup *sg;
int ret, sg_ids[3];
smrd = NULL;
smwr = NULL;
sg = NULL;
ret = 0;
sg_ids[0] = sg_ids[1] = sg_ids[2] = 0;
PCM_SG_LOCK();
if (wrch) {
CHN_LOCK(wrch);
sg_ids[0] = chn_syncdestroy(wrch);
}
if (rdch) {
CHN_LOCK(rdch);
sg_ids[1] = chn_syncdestroy(rdch);
}
if (((wrch == NULL) && (group->mode & PCM_ENABLE_OUTPUT)) ||
((rdch == NULL) && (group->mode & PCM_ENABLE_INPUT))) {
ret = EINVAL;
goto out;
}
if (group->id == 0) {
sg = malloc(sizeof(*sg), M_DEVBUF, M_NOWAIT);
if (sg != NULL) {
SLIST_INIT(&sg->members);
sg->id = alloc_unr(pcmsg_unrhdr);
group->id = sg->id;
SLIST_INSERT_HEAD(&snd_pcm_syncgroups, sg, link);
} else
ret = ENOMEM;
} else {
SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) {
if (sg->id == group->id)
break;
}
if (sg == NULL)
ret = EINVAL;
}
if (sg == NULL)
goto out;
if (group->mode & PCM_ENABLE_INPUT) {
smrd = malloc(sizeof(*smrd), M_DEVBUF, M_NOWAIT);
if (smrd == NULL) {
ret = ENOMEM;
goto out;
}
SLIST_INSERT_HEAD(&sg->members, smrd, link);
smrd->parent = sg;
smrd->ch = rdch;
chn_abort(rdch);
rdch->flags |= CHN_F_NOTRIGGER;
rdch->sm = smrd;
}
if (group->mode & PCM_ENABLE_OUTPUT) {
smwr = malloc(sizeof(*smwr), M_DEVBUF, M_NOWAIT);
if (smwr == NULL) {
ret = ENOMEM;
goto out;
}
SLIST_INSERT_HEAD(&sg->members, smwr, link);
smwr->parent = sg;
smwr->ch = wrch;
chn_abort(wrch);
wrch->flags |= CHN_F_NOTRIGGER;
wrch->sm = smwr;
}
out:
if (ret != 0) {
free(smrd, M_DEVBUF);
if ((sg != NULL) && SLIST_EMPTY(&sg->members)) {
sg_ids[2] = sg->id;
SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link);
free(sg, M_DEVBUF);
}
if (wrch)
wrch->sm = NULL;
if (rdch)
rdch->sm = NULL;
}
if (wrch)
CHN_UNLOCK(wrch);
if (rdch)
CHN_UNLOCK(rdch);
PCM_SG_UNLOCK();
if (sg_ids[0])
free_unr(pcmsg_unrhdr, sg_ids[0]);
if (sg_ids[1])
free_unr(pcmsg_unrhdr, sg_ids[1]);
if (sg_ids[2])
free_unr(pcmsg_unrhdr, sg_ids[2]);
return (ret);
}
static int
dsp_oss_syncstart(int sg_id)
{
struct pcmchan_syncmember *sm, *sm_tmp;
struct pcmchan_syncgroup *sg;
struct pcm_channel *c;
int ret, needlocks;
PCM_SG_LOCK();
do {
ret = 0;
needlocks = 0;
SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) {
if (sg->id == sg_id)
break;
}
if (sg == NULL) {
ret = EINVAL;
break;
}
KASSERT(!SLIST_EMPTY(&sg->members), ("found empty syncgroup"));
SLIST_FOREACH(sm, &sg->members, link) {
if (CHN_TRYLOCK(sm->ch) == 0) {
int timo = hz * 5/1000;
if (timo < 1)
timo = 1;
SLIST_FOREACH(sm_tmp, &sg->members, link) {
if (sm == sm_tmp)
break;
CHN_UNLOCK(sm_tmp->ch);
}
ret = msleep(sm, &snd_pcm_syncgroups_mtx,
PRIBIO | PCATCH, "pcmsg", timo);
if (ret == EINTR || ret == ERESTART)
break;
needlocks = 1;
ret = 0;
}
}
} while (needlocks && ret == 0);
if (ret == 0) {
while ((sm = SLIST_FIRST(&sg->members)) != NULL) {
SLIST_REMOVE_HEAD(&sg->members, link);
c = sm->ch;
c->sm = NULL;
chn_start(c, 1);
c->flags &= ~CHN_F_NOTRIGGER;
CHN_UNLOCK(c);
free(sm, M_DEVBUF);
}
SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link);
free(sg, M_DEVBUF);
}
PCM_SG_UNLOCK();
if (ret == 0)
free_unr(pcmsg_unrhdr, sg_id);
return (ret);
}
static int
dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy)
{
int ret;
if (policy < CHN_POLICY_MIN || policy > CHN_POLICY_MAX)
return (EIO);
ret = 0;
if (rdch) {
CHN_LOCK(rdch);
ret = chn_setlatency(rdch, policy);
CHN_UNLOCK(rdch);
}
if (wrch && ret == 0) {
CHN_LOCK(wrch);
ret = chn_setlatency(wrch, policy);
CHN_UNLOCK(wrch);
}
if (ret)
ret = EIO;
return (ret);
}
static int
dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled)
{
if (!(enabled == 1 || enabled == 0))
return (EINVAL);
enabled ^= 0x00000001;
if (wrch != NULL) {
CHN_LOCK(wrch);
wrch->flags &= ~CHN_F_BITPERFECT;
wrch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000;
CHN_UNLOCK(wrch);
}
if (rdch != NULL) {
CHN_LOCK(rdch);
rdch->flags &= ~CHN_F_BITPERFECT;
rdch->flags |= (enabled != 0) ? CHN_F_BITPERFECT : 0x00000000;
CHN_UNLOCK(rdch);
}
return (0);
}
static int
dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map)
{
struct pcm_channel *ch;
int ret;
ch = (wrch != NULL) ? wrch : rdch;
if (ch != NULL) {
CHN_LOCK(ch);
ret = chn_oss_getorder(ch, map);
CHN_UNLOCK(ch);
} else
ret = EINVAL;
return (ret);
}
static int
dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map)
{
int ret;
ret = 0;
if (wrch != NULL) {
CHN_LOCK(wrch);
ret = chn_oss_setorder(wrch, map);
CHN_UNLOCK(wrch);
}
if (ret == 0 && rdch != NULL) {
CHN_LOCK(rdch);
ret = chn_oss_setorder(rdch, map);
CHN_UNLOCK(rdch);
}
return (ret);
}
static int
dsp_oss_getchannelmask(struct pcm_channel *wrch, struct pcm_channel *rdch,
int *mask)
{
struct pcm_channel *ch;
uint32_t chnmask;
int ret;
chnmask = 0;
ch = (wrch != NULL) ? wrch : rdch;
if (ch != NULL) {
CHN_LOCK(ch);
ret = chn_oss_getmask(ch, &chnmask);
CHN_UNLOCK(ch);
} else
ret = EINVAL;
if (ret == 0)
*mask = chnmask;
return (ret);
}
static void
dsp_kqdetach(struct knote *kn)
{
struct pcm_channel *ch = kn->kn_hook;
if (ch == NULL)
return;
CHN_LOCK(ch);
knlist_remove(&ch->bufsoft->sel.si_note, kn, 1);
CHN_UNLOCK(ch);
}
static int
dsp_kqevent(struct knote *kn, long hint)
{
struct pcm_channel *ch = kn->kn_hook;
CHN_LOCKASSERT(ch);
if (ch->flags & CHN_F_DEAD) {
kn->kn_flags |= EV_EOF;
return (1);
}
kn->kn_data = 0;
if (chn_polltrigger(ch)) {
if (kn->kn_filter == EVFILT_READ)
kn->kn_data = sndbuf_getready(ch->bufsoft);
else
kn->kn_data = sndbuf_getfree(ch->bufsoft);
}
return (kn->kn_data > 0);
}
static const struct filterops dsp_filtops = {
.f_isfd = 1,
.f_detach = dsp_kqdetach,
.f_event = dsp_kqevent,
};
static int
dsp_kqfilter(struct cdev *dev, struct knote *kn)
{
struct dsp_cdevpriv *priv;
struct snddev_info *d;
struct pcm_channel *ch;
int err = 0;
if ((err = devfs_get_cdevpriv((void **)&priv)) != 0)
return (err);
d = priv->sc;
if (!DSP_REGISTERED(d))
return (EBADF);
PCM_GIANT_ENTER(d);
switch (kn->kn_filter) {
case EVFILT_READ:
ch = priv->rdch;
break;
case EVFILT_WRITE:
ch = priv->wrch;
break;
default:
kn->kn_hook = NULL;
err = EINVAL;
ch = NULL;
break;
}
if (ch != NULL) {
kn->kn_fop = &dsp_filtops;
CHN_LOCK(ch);
knlist_add(&ch->bufsoft->sel.si_note, kn, 1);
CHN_UNLOCK(ch);
kn->kn_hook = ch;
} else
err = EINVAL;
PCM_GIANT_LEAVE(d);
return (err);
}
#ifdef OSSV4_EXPERIMENT
static int
dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label)
{
return (EINVAL);
}
static int
dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label)
{
return (EINVAL);
}
static int
dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song)
{
return (EINVAL);
}
static int
dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song)
{
return (EINVAL);
}
static int
dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name)
{
return (EINVAL);
}
#endif