#ifdef HAVE_KERNEL_OPTION_HEADERS
#include "opt_snd.h"
#endif
#include <dev/sound/pcm/sound.h>
#include <sys/ctype.h>
#include <sys/devfs.h>
#include <sys/device.h>
#include <sys/eventhandler.h>
#include <sys/lock.h>
#include <sys/sysent.h>
#include <vm/vm.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <vm/vm_pager.h>
SND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pcm/dsp.c 274035 2014-11-03 11:11:45Z bapt $");
static int dsp_mmap_allow_prot_exec = 0;
SYSCTL_INT(_hw_snd, OID_AUTO, compat_linux_mmap, CTLFLAG_RW,
&dsp_mmap_allow_prot_exec, 0,
"linux mmap compatibility (-1=force disable 0=auto 1=force enable)");
struct dsp_cdevinfo {
struct pcm_channel *rdch, *wrch;
struct pcm_channel *volch;
int busy, simplex;
TAILQ_ENTRY(dsp_cdevinfo) link;
};
#define PCM_RDCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->rdch)
#define PCM_WRCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->wrch)
#define PCM_VOLCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->volch)
#define PCM_SIMPLEX(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->simplex)
#define DSP_CDEVINFO_CACHESIZE 8
#define DSP_REGISTERED(x, y) (PCM_REGISTERED(x) && \
(y) != NULL && (y)->si_drv1 != NULL)
#define OLDPCM_IOCTL
static d_open_t dsp_open;
static d_close_t dsp_close;
static d_read_t dsp_read;
static d_write_t dsp_write;
static d_ioctl_t dsp_ioctl;
static d_kqfilter_t dsp_kqfilter;
static d_mmap_t dsp_mmap;
static d_mmap_single_t dsp_mmap_single;
static void dsp_filter_detach(struct knote *);
static int dsp_filter_read(struct knote *, long);
static int dsp_filter_write(struct knote *, long);
DEVFS_DEFINE_CLONE_BITMAP(dsp);
struct dev_ops dsp_ops = {
{ "sound", 0, D_MPSAFE },
.d_open = dsp_open,
.d_close = dsp_close,
.d_read = dsp_read,
.d_write = dsp_write,
.d_ioctl = dsp_ioctl,
.d_kqfilter = dsp_kqfilter,
.d_mmap = dsp_mmap,
.d_mmap_single = dsp_mmap_single,
};
static eventhandler_tag dsp_ehtag = NULL;
static int dsp_umax = -1;
static int dsp_cmax = -1;
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
static struct snddev_info *
dsp_get_info(struct cdev *dev)
{
return (devclass_get_softc(pcm_devclass, PCMUNIT(dev)));
}
static uint32_t
dsp_get_flags(struct cdev *dev)
{
device_t bdev;
bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev));
return ((bdev != NULL) ? pcm_getflags(bdev) : 0xffffffff);
}
static void
dsp_set_flags(struct cdev *dev, uint32_t flags)
{
device_t bdev;
bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev));
if (bdev != NULL)
pcm_setflags(bdev, flags);
}
static int
getchns(struct cdev *dev, struct pcm_channel **rdch, struct pcm_channel **wrch,
uint32_t prio)
{
struct snddev_info *d;
struct pcm_channel *ch;
uint32_t flags;
if (PCM_SIMPLEX(dev) != 0) {
d = dsp_get_info(dev);
if (!PCM_REGISTERED(d)) {
*rdch = NULL;
*wrch = NULL;
return (ENXIO);
}
PCM_LOCK(d);
PCM_WAIT(d);
PCM_ACQUIRE(d);
ch = NULL;
flags = dsp_get_flags(dev);
if (flags & SD_F_PRIO_WR) {
ch = PCM_RDCH(dev);
PCM_RDCH(dev) = NULL;
} else if (flags & SD_F_PRIO_RD) {
ch = PCM_WRCH(dev);
PCM_WRCH(dev) = NULL;
} else if (prio & SD_F_PRIO_WR) {
ch = PCM_RDCH(dev);
PCM_RDCH(dev) = NULL;
flags |= SD_F_PRIO_WR;
} else if (prio & SD_F_PRIO_RD) {
ch = PCM_WRCH(dev);
PCM_WRCH(dev) = NULL;
flags |= SD_F_PRIO_RD;
} else if (PCM_WRCH(dev) != NULL) {
ch = PCM_RDCH(dev);
PCM_RDCH(dev) = NULL;
flags |= SD_F_PRIO_WR;
} else if (PCM_RDCH(dev) != NULL) {
ch = PCM_WRCH(dev);
PCM_WRCH(dev) = NULL;
flags |= SD_F_PRIO_RD;
}
PCM_SIMPLEX(dev) = 0;
dsp_set_flags(dev, flags);
if (ch != NULL) {
CHN_LOCK(ch);
pcm_chnref(ch, -1);
pcm_chnrelease(ch);
}
PCM_RELEASE(d);
PCM_UNLOCK(d);
}
*rdch = PCM_RDCH(dev);
*wrch = PCM_WRCH(dev);
if (*rdch != NULL && (prio & SD_F_PRIO_RD))
CHN_LOCK(*rdch);
if (*wrch != NULL && (prio & SD_F_PRIO_WR))
CHN_LOCK(*wrch);
return (0);
}
static void
relchns(struct cdev *dev, struct pcm_channel *rdch, struct pcm_channel *wrch,
uint32_t prio)
{
if (wrch != NULL && (prio & SD_F_PRIO_WR))
CHN_UNLOCK(wrch);
if (rdch != NULL && (prio & SD_F_PRIO_RD))
CHN_UNLOCK(rdch);
}
static void
dsp_cdevinfo_alloc(struct cdev *dev,
struct pcm_channel *rdch, struct pcm_channel *wrch,
struct pcm_channel *volch)
{
struct snddev_info *d;
struct dsp_cdevinfo *cdi;
int simplex;
d = dsp_get_info(dev);
KASSERT(PCM_REGISTERED(d) && dev != NULL && dev->si_drv1 == NULL &&
((rdch == NULL && wrch == NULL) || rdch != wrch),
("bogus %s(), what are you trying to accomplish here?", __func__));
PCM_BUSYASSERT(d);
PCM_LOCKASSERT(d);
simplex = (dsp_get_flags(dev) & SD_F_SIMPLEX) ? 1 : 0;
TAILQ_FOREACH(cdi, &d->dsp_cdevinfo_pool, link) {
if (cdi->busy != 0)
break;
cdi->rdch = rdch;
cdi->wrch = wrch;
cdi->volch = volch;
cdi->simplex = simplex;
cdi->busy = 1;
TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link);
TAILQ_INSERT_TAIL(&d->dsp_cdevinfo_pool, cdi, link);
dev->si_drv1 = cdi;
return;
}
PCM_UNLOCK(d);
cdi = kmalloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO);
PCM_LOCK(d);
cdi->rdch = rdch;
cdi->wrch = wrch;
cdi->volch = volch;
cdi->simplex = simplex;
cdi->busy = 1;
TAILQ_INSERT_TAIL(&d->dsp_cdevinfo_pool, cdi, link);
dev->si_drv1 = cdi;
}
static void
dsp_cdevinfo_free(struct cdev *dev)
{
struct snddev_info *d;
struct dsp_cdevinfo *cdi, *tmp;
uint32_t flags;
int i;
d = dsp_get_info(dev);
KASSERT(PCM_REGISTERED(d) && dev != NULL && dev->si_drv1 != NULL &&
PCM_RDCH(dev) == NULL && PCM_WRCH(dev) == NULL &&
PCM_VOLCH(dev) == NULL,
("bogus %s(), what are you trying to accomplish here?", __func__));
PCM_BUSYASSERT(d);
PCM_LOCKASSERT(d);
cdi = dev->si_drv1;
dev->si_drv1 = NULL;
cdi->rdch = NULL;
cdi->wrch = NULL;
cdi->volch = NULL;
cdi->simplex = 0;
cdi->busy = 0;
TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link);
TAILQ_INSERT_HEAD(&d->dsp_cdevinfo_pool, cdi, link);
flags = dsp_get_flags(dev) & ~SD_F_PRIO_SET;
i = DSP_CDEVINFO_CACHESIZE;
TAILQ_FOREACH_MUTABLE(cdi, &d->dsp_cdevinfo_pool, link, tmp) {
if (cdi->busy != 0) {
if (cdi->simplex == 0) {
if (cdi->rdch != NULL)
flags |= SD_F_PRIO_RD;
if (cdi->wrch != NULL)
flags |= SD_F_PRIO_WR;
}
} else {
if (i == 0) {
TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link);
kfree(cdi, M_DEVBUF);
} else
i--;
}
}
dsp_set_flags(dev, flags);
}
void
dsp_cdevinfo_init(struct snddev_info *d)
{
struct dsp_cdevinfo *cdi;
int i;
KASSERT(d != NULL, ("NULL snddev_info"));
PCM_BUSYASSERT(d);
PCM_UNLOCKASSERT(d);
TAILQ_INIT(&d->dsp_cdevinfo_pool);
for (i = 0; i < DSP_CDEVINFO_CACHESIZE; i++) {
cdi = kmalloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO);
TAILQ_INSERT_HEAD(&d->dsp_cdevinfo_pool, cdi, link);
}
}
void
dsp_cdevinfo_flush(struct snddev_info *d)
{
struct dsp_cdevinfo *cdi, *tmp;
KASSERT(d != NULL, ("NULL snddev_info"));
PCM_BUSYASSERT(d);
PCM_UNLOCKASSERT(d);
cdi = TAILQ_FIRST(&d->dsp_cdevinfo_pool);
while (cdi != NULL) {
tmp = TAILQ_NEXT(cdi, link);
kfree(cdi, M_DEVBUF);
cdi = tmp;
}
TAILQ_INIT(&d->dsp_cdevinfo_pool);
}
enum {
DSP_CDEV_TYPE_RDONLY,
DSP_CDEV_TYPE_WRONLY,
DSP_CDEV_TYPE_RDWR
};
enum {
DSP_CDEV_VOLCTL_NONE,
DSP_CDEV_VOLCTL_READ,
DSP_CDEV_VOLCTL_WRITE
};
#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 const struct {
int type;
char *name;
char *sep;
char *alias;
int use_sep;
int hw;
int max;
int volctl;
uint32_t fmt, spd;
int query;
} dsp_cdevs[] = {
{ SND_DEV_DSP, "dsp", ".", NULL, 0, 0, 0, 0,
SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED,
DSP_CDEV_TYPE_RDWR },
{ SND_DEV_AUDIO, "audio", ".", NULL, 0, 0, 0, 0,
SND_FORMAT(AFMT_MU_LAW, 1, 0), DSP_DEFAULT_SPEED,
DSP_CDEV_TYPE_RDWR },
{ SND_DEV_DSP16, "dspW", ".", NULL, 0, 0, 0, 0,
SND_FORMAT(AFMT_S16_LE, 1, 0), DSP_DEFAULT_SPEED,
DSP_CDEV_TYPE_RDWR },
{ SND_DEV_DSPHW_PLAY, "dsp", ".p", NULL, 1, 1, SND_MAXHWCHAN, 1,
SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_WRONLY },
{ SND_DEV_DSPHW_VPLAY, "dsp", ".vp", NULL, 1, 1, SND_MAXVCHANS, 1,
SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_WRONLY },
{ SND_DEV_DSPHW_REC, "dsp", ".r", NULL, 1, 1, SND_MAXHWCHAN, 1,
SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_RDONLY },
{ SND_DEV_DSPHW_VREC, "dsp", ".vr", NULL, 1, 1, SND_MAXVCHANS, 1,
SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_RDONLY },
{ SND_DEV_DSPHW_CD, "dspcd", ".", NULL, 0, 0, 0, 0,
SND_FORMAT(AFMT_S16_LE, 2, 0), 44100, DSP_CDEV_TYPE_RDWR },
{ SND_DEV_DSP, "dsp_ac3", ".", "dsp", 0, 0, 0, 0,
SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED,
DSP_CDEV_TYPE_RDWR },
{ SND_DEV_DSP, "dsp_mmap", ".", "dsp", 0, 0, 0, 0,
SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED,
DSP_CDEV_TYPE_RDWR },
{ SND_DEV_DSP, "dsp_multich", ".", "dsp", 0, 0, 0, 0,
SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED,
DSP_CDEV_TYPE_RDWR },
{ SND_DEV_DSP, "dsp_spdifout", ".", "dsp", 0, 0, 0, 0,
SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED,
DSP_CDEV_TYPE_RDWR },
{ SND_DEV_DSP, "dsp_spdifin", ".", "dsp", 0, 0, 0, 0,
SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED,
DSP_CDEV_TYPE_RDWR },
};
#define DSP_FIXUP_ERROR() do { \
prio = dsp_get_flags(i_dev); \
if (!DSP_F_VALID(flags)) \
error = EINVAL; \
if (!DSP_F_DUPLEX(flags) && \
((DSP_F_READ(flags) && d->reccount == 0) || \
(DSP_F_WRITE(flags) && d->playcount == 0))) \
error = ENOTSUP; \
else if (!DSP_F_DUPLEX(flags) && (prio & SD_F_SIMPLEX) && \
((DSP_F_READ(flags) && (prio & SD_F_PRIO_WR)) || \
(DSP_F_WRITE(flags) && (prio & SD_F_PRIO_RD)))) \
error = EBUSY; \
else if (DSP_REGISTERED(d, i_dev)) \
error = EBUSY; \
} while (0)
static int
dsp_open(struct dev_open_args *ap)
{
struct cdev *i_dev = ap->a_head.a_dev;
int flags = ap->a_oflags;
struct pcm_channel *rdch, *wrch;
struct snddev_info *d;
uint32_t fmt, spd, prio, volctl;
int i, error, rderror, wrerror, devtype, wdevunit, rdevunit;
if (i_dev == NULL)
return (ENODEV);
d = dsp_get_info(i_dev);
if (!PCM_REGISTERED(d))
return (EBADF);
PCM_GIANT_ENTER(d);
PCM_LOCK(d);
PCM_WAIT(d);
error = snd_clone_acquire(i_dev);
if (!(error == 0 || error == ENODEV)) {
DSP_FIXUP_ERROR();
PCM_UNLOCK(d);
PCM_GIANT_EXIT(d);
return (error);
}
error = 0;
DSP_FIXUP_ERROR();
if (error != 0) {
(void)snd_clone_release(i_dev);
PCM_UNLOCK(d);
PCM_GIANT_EXIT(d);
return (error);
}
PCM_ACQUIRE(d);
PCM_UNLOCK(d);
devtype = PCMDEV(i_dev);
wdevunit = -1;
rdevunit = -1;
fmt = 0;
spd = 0;
volctl = DSP_CDEV_VOLCTL_NONE;
for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) {
if (devtype != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL)
continue;
if (dsp_cdevs[i].query == DSP_CDEV_TYPE_WRONLY) {
if (dsp_cdevs[i].volctl != 0 &&
DSP_F_READ(flags)) {
volctl = DSP_CDEV_VOLCTL_WRITE;
flags &= ~FREAD;
flags |= FWRITE;
}
if (DSP_F_READ(flags)) {
(void)snd_clone_release(i_dev);
PCM_RELEASE_QUICK(d);
PCM_GIANT_EXIT(d);
return (ENOTSUP);
}
wdevunit = dev2unit(i_dev);
} else if (dsp_cdevs[i].query == DSP_CDEV_TYPE_RDONLY) {
if (dsp_cdevs[i].volctl != 0 &&
DSP_F_WRITE(flags)) {
volctl = DSP_CDEV_VOLCTL_READ;
flags &= ~FWRITE;
flags |= FREAD;
}
if (DSP_F_WRITE(flags)) {
(void)snd_clone_release(i_dev);
PCM_RELEASE_QUICK(d);
PCM_GIANT_EXIT(d);
return (ENOTSUP);
}
rdevunit = dev2unit(i_dev);
}
fmt = dsp_cdevs[i].fmt;
spd = dsp_cdevs[i].spd;
break;
}
rdch = NULL;
wrch = NULL;
rderror = 0;
wrerror = 0;
if (DSP_F_READ(flags)) {
rderror = pcm_chnalloc(d, &rdch, PCMDIR_REC,
curproc->p_pid, curproc->p_comm, rdevunit);
if (rderror == 0 && chn_reset(rdch, fmt, spd) != 0)
rderror = ENXIO;
if (volctl == DSP_CDEV_VOLCTL_READ)
rderror = 0;
if (rderror != 0) {
if (rdch != NULL)
pcm_chnrelease(rdch);
if (!DSP_F_DUPLEX(flags)) {
(void)snd_clone_release(i_dev);
PCM_RELEASE_QUICK(d);
PCM_GIANT_EXIT(d);
return (rderror);
}
rdch = NULL;
} else if (volctl == DSP_CDEV_VOLCTL_READ) {
if (rdch != NULL) {
pcm_chnref(rdch, 1);
pcm_chnrelease(rdch);
}
} else {
if (flags & O_EXCL)
rdch->flags |= CHN_F_EXCLUSIVE;
pcm_chnref(rdch, 1);
if (volctl == DSP_CDEV_VOLCTL_NONE)
chn_vpc_reset(rdch, SND_VOL_C_PCM, 0);
CHN_UNLOCK(rdch);
}
}
if (DSP_F_WRITE(flags)) {
wrerror = pcm_chnalloc(d, &wrch, PCMDIR_PLAY,
curproc->p_pid, curproc->p_comm, wdevunit);
if (wrerror == 0 && chn_reset(wrch, fmt, spd) != 0)
wrerror = ENXIO;
if (volctl == DSP_CDEV_VOLCTL_WRITE)
wrerror = 0;
if (wrerror != 0) {
if (wrch != NULL)
pcm_chnrelease(wrch);
if (!DSP_F_DUPLEX(flags)) {
if (rdch != NULL) {
CHN_LOCK(rdch);
pcm_chnref(rdch, -1);
pcm_chnrelease(rdch);
}
(void)snd_clone_release(i_dev);
PCM_RELEASE_QUICK(d);
PCM_GIANT_EXIT(d);
return (wrerror);
}
wrch = NULL;
} else if (volctl == DSP_CDEV_VOLCTL_WRITE) {
if (wrch != NULL) {
pcm_chnref(wrch, 1);
pcm_chnrelease(wrch);
}
} else {
if (flags & O_EXCL)
wrch->flags |= CHN_F_EXCLUSIVE;
pcm_chnref(wrch, 1);
if (volctl == DSP_CDEV_VOLCTL_NONE)
chn_vpc_reset(wrch, SND_VOL_C_PCM, 0);
CHN_UNLOCK(wrch);
}
}
PCM_LOCK(d);
switch (volctl) {
case DSP_CDEV_VOLCTL_READ:
KASSERT(wrch == NULL, ("wrch=%p not null!", wrch));
dsp_cdevinfo_alloc(i_dev, NULL, NULL, rdch);
break;
case DSP_CDEV_VOLCTL_WRITE:
KASSERT(rdch == NULL, ("rdch=%p not null!", rdch));
dsp_cdevinfo_alloc(i_dev, NULL, NULL, wrch);
break;
case DSP_CDEV_VOLCTL_NONE:
default:
if (wrch == NULL && rdch == NULL) {
(void)snd_clone_release(i_dev);
PCM_RELEASE(d);
PCM_UNLOCK(d);
PCM_GIANT_EXIT(d);
if (wrerror != 0)
return (wrerror);
if (rderror != 0)
return (rderror);
return (EINVAL);
}
dsp_cdevinfo_alloc(i_dev, rdch, wrch, NULL);
if (rdch != NULL)
CHN_INSERT_HEAD(d, rdch, channels.pcm.opened);
if (wrch != NULL)
CHN_INSERT_HEAD(d, wrch, channels.pcm.opened);
break;
}
(void)snd_clone_ref(i_dev);
PCM_RELEASE(d);
PCM_UNLOCK(d);
PCM_GIANT_LEAVE(d);
return (0);
}
static int
dsp_close(struct dev_close_args *ap)
{
struct cdev *i_dev = ap->a_head.a_dev;
struct pcm_channel *rdch, *wrch, *volch;
struct snddev_info *d;
int sg_ids, rdref, wdref;
int subunit;
subunit = PCMSUBUNIT(i_dev);
d = dsp_get_info(i_dev);
if (!DSP_REGISTERED(d, i_dev))
return (EBADF);
PCM_GIANT_ENTER(d);
PCM_LOCK(d);
PCM_WAIT(d);
PCM_ACQUIRE(d);
rdch = PCM_RDCH(i_dev);
wrch = PCM_WRCH(i_dev);
volch = PCM_VOLCH(i_dev);
PCM_RDCH(i_dev) = NULL;
PCM_WRCH(i_dev) = NULL;
PCM_VOLCH(i_dev) = NULL;
rdref = -1;
wdref = -1;
if (volch != NULL) {
if (volch == rdch)
rdref--;
else if (volch == wrch)
wdref--;
else {
CHN_LOCK(volch);
pcm_chnref(volch, -1);
CHN_UNLOCK(volch);
}
}
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);
pcm_chnref(rdch, rdref);
chn_abort(rdch);
rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP |
CHN_F_DEAD | CHN_F_EXCLUSIVE);
chn_reset(rdch, 0, 0);
pcm_chnrelease(rdch);
}
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);
pcm_chnref(wrch, wdref);
chn_flush(wrch);
wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP |
CHN_F_DEAD | CHN_F_EXCLUSIVE);
chn_reset(wrch, 0, 0);
pcm_chnrelease(wrch);
}
PCM_LOCK(d);
}
dsp_cdevinfo_free(i_dev);
(void)snd_clone_release(i_dev);
PCM_UNLOCK(d);
(void)snd_clone_unref(i_dev);
PCM_LOCK(d);
PCM_RELEASE(d);
PCM_UNLOCK(d);
PCM_GIANT_LEAVE(d);
return (0);
}
static __inline int
dsp_io_ops(struct cdev *i_dev, struct uio *buf, int ioflags)
{
struct snddev_info *d;
struct pcm_channel **ch, *rdch, *wrch;
int (*chn_io)(struct pcm_channel *, struct uio *, int);
int prio, ret;
pid_t runpid;
KASSERT(i_dev != NULL && buf != NULL &&
(buf->uio_rw == UIO_READ || buf->uio_rw == UIO_WRITE),
("%s(): io train wreck!", __func__));
d = dsp_get_info(i_dev);
if (!DSP_REGISTERED(d, i_dev))
return (EBADF);
PCM_GIANT_ENTER(d);
switch (buf->uio_rw) {
case UIO_READ:
prio = SD_F_PRIO_RD;
ch = &rdch;
chn_io = chn_read;
break;
case UIO_WRITE:
prio = SD_F_PRIO_WR;
ch = &wrch;
chn_io = chn_write;
break;
default:
panic("invalid/corrupted uio direction: %d", buf->uio_rw);
break;
}
rdch = NULL;
wrch = NULL;
runpid = buf->uio_td->td_proc->p_pid;
getchns(i_dev, &rdch, &wrch, prio);
if (*ch == NULL || !((*ch)->flags & CHN_F_BUSY)) {
PCM_GIANT_EXIT(d);
return (EBADF);
}
if (((*ch)->flags & (CHN_F_MMAP | CHN_F_DEAD)) ||
(((*ch)->flags & CHN_F_RUNNING) && (*ch)->pid != runpid)) {
relchns(i_dev, rdch, wrch, prio);
PCM_GIANT_EXIT(d);
return (EINVAL);
} else if (!((*ch)->flags & CHN_F_RUNNING)) {
(*ch)->flags |= CHN_F_RUNNING;
(*ch)->pid = runpid;
}
++(*ch)->inprog;
ret = chn_io(*ch, buf, ioflags);
--(*ch)->inprog;
CHN_BROADCAST(&(*ch)->cv);
relchns(i_dev, rdch, wrch, prio);
PCM_GIANT_LEAVE(d);
return (ret);
}
static int
dsp_read(struct dev_read_args *ap)
{
struct cdev *i_dev = ap->a_head.a_dev;
struct uio *buf = ap->a_uio;
return (dsp_io_ops(i_dev, buf, ap->a_ioflag));
}
static int
dsp_write(struct dev_write_args *ap)
{
struct cdev *i_dev = ap->a_head.a_dev;
struct uio *buf = ap->a_uio;
return (dsp_io_ops(i_dev, buf, ap->a_ioflag));
}
static int
dsp_get_volume_channel(struct cdev *dev, struct pcm_channel **volch)
{
struct snddev_info *d;
struct pcm_channel *c;
int unit;
KASSERT(dev != NULL && volch != NULL,
("%s(): NULL query dev=%p volch=%p", __func__, dev, volch));
d = dsp_get_info(dev);
if (!PCM_REGISTERED(d)) {
*volch = NULL;
return (EINVAL);
}
PCM_UNLOCKASSERT(d);
*volch = NULL;
c = PCM_VOLCH(dev);
if (c != NULL) {
if (!(c->feederflags & (1 << FEEDER_VOLUME)))
return (-1);
*volch = c;
return (0);
}
PCM_LOCK(d);
PCM_WAIT(d);
PCM_ACQUIRE(d);
unit = dev2unit(dev);
CHN_FOREACH(c, d, channels.pcm) {
CHN_LOCK(c);
if (c->unit != unit) {
CHN_UNLOCK(c);
continue;
}
*volch = c;
pcm_chnref(c, 1);
PCM_VOLCH(dev) = c;
CHN_UNLOCK(c);
PCM_RELEASE(d);
PCM_UNLOCK(d);
return ((c->feederflags & (1 << FEEDER_VOLUME)) ? 0 : -1);
}
PCM_RELEASE(d);
PCM_UNLOCK(d);
return (EINVAL);
}
static int
dsp_ioctl_channel(struct cdev *dev, struct pcm_channel *volch, u_long cmd,
caddr_t arg)
{
struct snddev_info *d;
struct pcm_channel *rdch, *wrch;
int j, devtype, ret;
d = dsp_get_info(dev);
if (!PCM_REGISTERED(d) || !(dsp_get_flags(dev) & SD_F_VPC))
return (-1);
PCM_UNLOCKASSERT(d);
j = cmd & 0xff;
rdch = PCM_RDCH(dev);
wrch = PCM_WRCH(dev);
if (volch == NULL)
volch = PCM_VOLCH(dev);
if (volch == NULL) {
if (j == SOUND_MIXER_RECLEV && rdch != NULL)
volch = rdch;
else if (j == SOUND_MIXER_PCM && wrch != NULL)
volch = wrch;
}
devtype = PCMDEV(dev);
if (volch == NULL &&
(devtype == SND_DEV_DSPHW_PLAY || devtype == SND_DEV_DSPHW_VPLAY ||
devtype == SND_DEV_DSPHW_REC || devtype == SND_DEV_DSPHW_VREC)) {
ret = dsp_get_volume_channel(dev, &volch);
if (ret != 0)
return (ret);
if (volch == NULL)
return (EINVAL);
}
if (volch != NULL) {
CHN_LOCK(volch);
if (!(volch->feederflags & (1 << FEEDER_VOLUME))) {
CHN_UNLOCK(volch);
return (-1);
}
if (volch->direction == PCMDIR_PLAY)
wrch = volch;
else
rdch = volch;
}
ret = EINVAL;
if (volch != NULL &&
((j == SOUND_MIXER_PCM && volch->direction == PCMDIR_PLAY) ||
(j == SOUND_MIXER_RECLEV && volch->direction == PCMDIR_REC))) {
if ((cmd & ~0xff) == MIXER_WRITE(0)) {
int left, right, center;
left = *(int *)arg & 0x7f;
right = ((*(int *)arg) >> 8) & 0x7f;
center = (left + right) >> 1;
chn_setvolume_multi(volch, SND_VOL_C_PCM, left, right,
center);
} else if ((cmd & ~0xff) == MIXER_READ(0)) {
*(int *)arg = CHN_GETVOLUME(volch,
SND_VOL_C_PCM, SND_CHN_T_FL);
*(int *)arg |= CHN_GETVOLUME(volch,
SND_VOL_C_PCM, SND_CHN_T_FR) << 8;
}
ret = 0;
} else if (rdch != NULL || wrch != NULL) {
switch (j) {
case SOUND_MIXER_DEVMASK:
case SOUND_MIXER_CAPS:
case SOUND_MIXER_STEREODEVS:
if ((cmd & ~0xff) == MIXER_READ(0)) {
*(int *)arg = 0;
if (rdch != NULL)
*(int *)arg |= SOUND_MASK_RECLEV;
if (wrch != NULL)
*(int *)arg |= SOUND_MASK_PCM;
}
ret = 0;
break;
case SOUND_MIXER_RECMASK:
case SOUND_MIXER_RECSRC:
if ((cmd & ~0xff) == MIXER_READ(0))
*(int *)arg = 0;
ret = 0;
break;
default:
break;
}
}
if (volch != NULL)
CHN_UNLOCK(volch);
return (ret);
}
static int
dsp_ioctl(struct dev_ioctl_args *ap)
{
struct cdev *i_dev = ap->a_head.a_dev;
u_long cmd = ap->a_cmd;
caddr_t arg = ap->a_data;
struct pcm_channel *chn, *rdch, *wrch;
struct snddev_info *d;
u_long xcmd;
int *arg_i, ret, tmp;
d = dsp_get_info(i_dev);
if (!DSP_REGISTERED(d, i_dev))
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(i_dev, PCM_VOLCH(i_dev), 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,
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:
case SNDCTL_AUDIOINFO_EX:
case SNDCTL_ENGINEINFO:
ret = dsp_oss_audioinfo(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);
}
getchns(i_dev, &rdch, &wrch, 0);
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) {
#ifdef OLDPCM_IOCTL
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 = sndbuf_getblksz(wrch->bufsoft);
CHN_UNLOCK(wrch);
}
if (rdch) {
CHN_LOCK(rdch);
chn_setblocksize(rdch, 2, p->rec_size);
p->rec_size = sndbuf_getblksz(rdch->bufsoft);
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 = sndbuf_getblksz(wrch->bufsoft);
CHN_UNLOCK(wrch);
}
if (rdch) {
CHN_LOCK(rdch);
p->rec_size = sndbuf_getblksz(rdch->bufsoft);
CHN_UNLOCK(rdch);
}
}
break;
case AIOSFMT:
case AIOGFMT:
{
snd_chan_param *p = (snd_chan_param *)arg;
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);
}
break;
case AIOGCAP:
{
snd_capabilities *p = (snd_capabilities *)arg;
struct pcmchan_caps *pcaps = NULL, *rcaps = NULL;
struct cdev *pdev;
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? sndbuf_getsize(rdch->bufsoft) : 1000000,
wrch? sndbuf_getsize(wrch->bufsoft) : 1000000);
p->formats = (rdch? chn_getformats(rdch) : 0xffffffff) &
(wrch? chn_getformats(wrch) : 0xffffffff);
if (rdch && wrch)
p->formats |= (dsp_get_flags(i_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);
}
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 {
kprintf("AIOSTOP: bad channel 0x%x\n", *arg_i);
*arg_i = 0;
}
break;
case AIOSYNC:
kprintf("AIOSYNC chan 0x%03lx pos %lu unimplemented\n",
((snd_sync_parm *)arg)->chan, ((snd_sync_parm *)arg)->pos);
break;
#endif
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( kprintf("FIOASYNC\n") ; )
break;
case FIONBIO:
DEB( kprintf("FIONBIO\n") ; )
break;
case SNDCTL_DSP_NONBLOCK:
atomic_set_int(&ap->a_fp->f_flag, FNONBLOCK);
break;
#define THE_REAL_SNDCTL_DSP_GETBLKSIZE _IOWR('P', 4, int)
case THE_REAL_SNDCTL_DSP_GETBLKSIZE:
case SNDCTL_DSP_GETBLKSIZE:
chn = wrch ? wrch : rdch;
if (chn) {
CHN_LOCK(chn);
*arg_i = sndbuf_getblksz(chn->bufsoft);
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(kprintf("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(kprintf("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 = 0;
ret = EINVAL;
break;
}
if (*arg_i != 0) {
struct pcmchan_matrix *m;
uint32_t ext;
tmp = 0;
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;
else
ext = 0;
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(kprintf("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(kprintf("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 = sndbuf_getblkcnt(rdch->bufsoft);
r_fragsz = sndbuf_getblksz(rdch->bufsoft);
CHN_UNLOCK(rdch);
} else {
r_maxfrags = maxfrags;
r_fragsz = fragsz;
}
if (wrch && ret == 0) {
CHN_LOCK(wrch);
ret = chn_setblocksize(wrch, maxfrags, fragsz);
maxfrags = sndbuf_getblkcnt(wrch->bufsoft);
fragsz = sndbuf_getblksz(wrch->bufsoft);
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 / sndbuf_getblksz(bs);
a->fragstotal = sndbuf_getblkcnt(bs);
a->fragsize = sndbuf_getblksz(bs);
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 / sndbuf_getblksz(bs);
a->fragstotal = sndbuf_getblkcnt(bs);
a->fragsize = sndbuf_getblksz(bs);
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 = sndbuf_gettotal(bs);
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 = sndbuf_gettotal(bs);
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 && !(dsp_get_flags(i_dev) & SD_F_SIMPLEX))
*arg_i |= PCM_CAP_DUPLEX;
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 && (dsp_get_flags(i_dev) & SD_F_SIMPLEX))
dsp_set_flags(i_dev, dsp_get_flags(i_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(i_dev, 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,
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,
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;
#if 0
tmp = (sndbuf_getsize(b) + chn_getptr(chn) - sndbuf_gethwptr(b)) % sndbuf_getsize(b);
oc->samples = (sndbuf_gettotal(b) + tmp) / sndbuf_getalign(b);
oc->fifo_samples = (sndbuf_getready(b) - tmp) / sndbuf_getalign(b);
#else
oc->samples = sndbuf_gettotal(bs) / sndbuf_getalign(bs);
oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getalign(bs);
#endif
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:
{
audio_errinfo *ei = (audio_errinfo *)arg;
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);
}
}
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 (!(dsp_get_flags(i_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;
#if 0
case SNDCTL_DSP_READCTL:
case SNDCTL_DSP_WRITECTL:
ret = EINVAL;
break;
#endif
#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(kprintf("default ioctl fn 0x%08lx fail\n", cmd));
ret = EINVAL;
break;
}
PCM_GIANT_LEAVE(d);
return (ret);
}
static struct filterops dsp_read_filtops =
{ FILTEROP_ISFD | FILTEROP_MPSAFE, NULL, dsp_filter_detach,
dsp_filter_read };
static struct filterops dsp_write_filtops =
{ FILTEROP_ISFD | FILTEROP_MPSAFE, NULL, dsp_filter_detach,
dsp_filter_write };
static int
dsp_kqfilter(struct dev_kqfilter_args *ap)
{
struct knote *kn = ap->a_kn;
struct klist *klist;
struct cdev *i_dev = ap->a_head.a_dev;
struct snddev_info *d;
struct pcm_channel *wrch, *rdch;
struct snd_dbuf *bs = NULL;
int ret;
d = dsp_get_info(i_dev);
if (!DSP_REGISTERED(d, i_dev))
return (EBADF);
PCM_GIANT_ENTER(d);
wrch = NULL;
rdch = NULL;
ret = 0;
getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR);
switch (kn->kn_filter) {
case EVFILT_READ:
if (rdch) {
kn->kn_fop = &dsp_read_filtops;
kn->kn_hook = (caddr_t)rdch;
bs = rdch->bufsoft;
ap->a_result = 0;
} else {
ap->a_result = EINVAL;
}
break;
case EVFILT_WRITE:
if (wrch) {
kn->kn_fop = &dsp_write_filtops;
kn->kn_hook = (caddr_t)wrch;
bs = wrch->bufsoft;
ap->a_result = 0;
} else {
ap->a_result = EINVAL;
}
break;
default:
ap->a_result = EOPNOTSUPP;
break;
}
if (ap->a_result == 0) {
klist = &sndbuf_getkq(bs)->ki_note;
knote_insert(klist, kn);
}
relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR);
PCM_GIANT_LEAVE(d);
return (ret);
}
static void
dsp_filter_detach(struct knote *kn)
{
struct pcm_channel *ch = (struct pcm_channel *)kn->kn_hook;
struct snd_dbuf *bs = ch->bufsoft;
struct klist *klist;
CHN_LOCK(ch);
klist = &sndbuf_getkq(bs)->ki_note;
knote_remove(klist, kn);
CHN_UNLOCK(ch);
}
static int
dsp_filter_read(struct knote *kn, long hint)
{
struct pcm_channel *rdch = (struct pcm_channel *)kn->kn_hook;
struct thread *td = curthread;
int ready;
CHN_LOCK(rdch);
ready = chn_poll(rdch, 1, td);
CHN_UNLOCK(rdch);
return (ready);
}
static int
dsp_filter_write(struct knote *kn, long hint)
{
struct pcm_channel *wrch = (struct pcm_channel *)kn->kn_hook;
struct thread *td = curthread;
int ready;
CHN_LOCK(wrch);
ready = chn_poll(wrch, 1, td);
CHN_UNLOCK(wrch);
return (ready);
}
static int
dsp_mmap(struct dev_mmap_args *ap)
{
vm_offset_t offset = ap->a_offset;
ap->a_result = atop(vtophys(offset));
return (0);
}
static int
dsp_mmap_single(struct dev_mmap_single_args *ap)
{
struct cdev *i_dev = ap->a_head.a_dev;
vm_ooffset_t *offset = ap->a_offset;
vm_size_t size = ap->a_size;
struct vm_object **object = ap->a_object;
int nprot = ap->a_nprot;
struct snddev_info *d;
struct pcm_channel *wrch, *rdch, *c;
#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);
d = dsp_get_info(i_dev);
if (!DSP_REGISTERED(d, i_dev))
return (EINVAL);
PCM_GIANT_ENTER(d);
getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR);
c = ((nprot & PROT_WRITE) != 0) ? wrch : rdch;
if (c == NULL || (c->flags & CHN_F_MMAP_INVALID) ||
(*offset + size) > sndbuf_getsize(c->bufsoft) ||
(wrch != NULL && (wrch->flags & CHN_F_MMAP_INVALID)) ||
(rdch != NULL && (rdch->flags & CHN_F_MMAP_INVALID))) {
relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR);
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);
relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR);
*object = dev_pager_alloc(i_dev, size, nprot, *offset);
PCM_GIANT_LEAVE(d);
if (*object == NULL)
return (EINVAL);
return (0);
}
int
dsp_clone(struct dev_clone_args *ap)
{
struct cdev *i_dev = ap->a_head.a_dev;
struct snddev_info *d;
struct snd_clone_entry *ce;
struct pcm_channel *c;
int i, unit, udcmask, cunit, devtype, devhw, devcmax, tumax;
size_t len;
char sname[64];
int err = EBUSY;
static struct cdev *dev = NULL;
KASSERT(dsp_umax >= 0 && dsp_cmax >= 0, ("Uninitialized unit!"));
unit = PCMUNIT(i_dev);
if (unit == PCMUNIT_DEFAULT)
unit = snd_unit;
ksnprintf(sname, sizeof(sname), "%s", ap->a_name);
len = strlen(sname);
while (len && sname[len-1] >= '0' && sname[len-1] <= '9')
--len;
sname[len] = 0;
cunit = -1;
devtype = -1;
devhw = 0;
devcmax = -1;
tumax = -1;
for (i = 0; i < sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0]); ++i) {
if (strcmp(dsp_cdevs[i].name, sname) != 0)
continue;
devtype = dsp_cdevs[i].type;
devhw = dsp_cdevs[i].hw;
devcmax = dsp_cdevs[i].max - 1;
break;
}
d = devclass_get_softc(pcm_devclass, unit);
if (!PCM_REGISTERED(d) || d->clones == NULL) {
return (ENODEV);
}
PCM_LOCK(d);
if (snd_clone_disabled(d->clones)) {
PCM_UNLOCK(d);
return (ENODEV);
}
PCM_WAIT(d);
PCM_ACQUIRE(d);
PCM_UNLOCK(d);
udcmask = snd_u2unit(unit) | snd_d2unit(devtype);
if (devhw != 0) {
KASSERT(devcmax <= dsp_cmax,
("overflow: devcmax=%d, dsp_cmax=%d", devcmax, dsp_cmax));
if (cunit > devcmax) {
PCM_RELEASE_QUICK(d);
return (ENODEV);
}
udcmask |= snd_c2unit(cunit);
CHN_FOREACH(c, d, channels.pcm) {
CHN_LOCK(c);
if (c->unit != udcmask) {
CHN_UNLOCK(c);
continue;
}
CHN_UNLOCK(c);
udcmask &= ~snd_c2unit(cunit);
tumax = snd_clone_getmaxunit(d->clones);
if (cunit > tumax)
snd_clone_setmaxunit(d->clones, cunit);
else
tumax = -1;
goto dsp_clone_alloc;
}
if (((devtype == SND_DEV_DSPHW_VPLAY && d->pvchancount > 0) ||
(devtype == SND_DEV_DSPHW_VREC && d->rvchancount > 0)) &&
cunit < snd_maxautovchans) {
udcmask &= ~snd_c2unit(cunit);
tumax = snd_clone_getmaxunit(d->clones);
if (cunit > tumax)
snd_clone_setmaxunit(d->clones, cunit);
else
tumax = -1;
goto dsp_clone_alloc;
}
PCM_RELEASE_QUICK(d);
return (err);
}
dsp_clone_alloc:
ce = snd_clone_alloc(d->clones, &dev, &cunit, udcmask);
if (tumax != -1)
snd_clone_setmaxunit(d->clones, tumax);
if (ce != NULL) {
udcmask |= snd_c2unit(cunit);
int subunit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(dsp), 0);
dev = make_only_dev(&dsp_ops,
PCMMKMINOR(unit, devtype, subunit),
UID_ROOT, GID_WHEEL, 0666, "%s%d.%d",
sname, unit, subunit);
snd_clone_register(ce, dev);
err = 0;
} else {
err = 0;
}
ap->a_dev = dev;
PCM_RELEASE_QUICK(d);
return (err);
}
static void
dsp_sysinit(void *p)
{
if (dsp_ehtag != NULL)
return;
snd_unit_init();
dsp_umax = PCMMAXUNIT;
dsp_cmax = PCMMAXCHAN;
dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000);
devfs_clone_bitmap_init(&DEVFS_CLONE_BITMAP(dsp));
}
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);
char *
dsp_unit2name(char *buf, size_t len, int unit)
{
int i, dtype;
KASSERT(buf != NULL && len != 0,
("bogus buf=%p len=%ju", buf, (uintmax_t)len));
dtype = snd_unit2d(unit);
for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) {
if (dtype != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL)
continue;
ksnprintf(buf, len, "%s%d%s%d", dsp_cdevs[i].name,
snd_unit2u(unit), dsp_cdevs[i].sep, snd_unit2c(unit));
return (buf);
}
return (NULL);
}
int
dsp_oss_audioinfo(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;
char *devname, buf[CHN_NAMELEN];
if (ai->dev == -1 && i_dev->si_ops != &dsp_ops)
return (EINVAL);
ch = NULL;
devname = NULL;
nchan = 0;
bzero(buf, sizeof(buf));
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_UNLOCKASSERT(d);
PCM_LOCK(d);
CHN_FOREACH(ch, d, channels.pcm) {
CHN_UNLOCKASSERT(ch);
CHN_LOCK(ch);
if (ai->dev == -1) {
if (DSP_REGISTERED(d, i_dev) &&
(ch == PCM_RDCH(i_dev) ||
ch == PCM_WRCH(i_dev))) {
devname = dsp_unit2name(buf,
sizeof(buf), ch->unit);
}
} else if (ai->dev == nchan) {
devname = dsp_unit2name(buf, sizeof(buf),
ch->unit);
}
if (devname != NULL)
break;
CHN_UNLOCK(ch);
++nchan;
}
if (devname != NULL) {
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;
ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER |
((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT);
minch = 0;
maxch = 0;
fmts = 0;
for (i = 0; caps->fmtlist[i]; i++) {
fmts |= caps->fmtlist[i];
if (AFMT_CHANNEL(caps->fmtlist[i]) > 1) {
minch = (minch == 0) ? 2 : minch;
maxch = 2;
} else {
minch = 1;
maxch = (maxch == 0) ? 1 : maxch;
}
}
if (ch->direction == PCMDIR_PLAY)
ai->oformats = fmts;
else
ai->iformats = fmts;
ai->card_number = -1;
ai->port_number = -1;
ai->mixer_dev = (d->mixer_dev != NULL) ? PCMUNIT(d->mixer_dev) : -1;
ai->real_device = -1;
strlcpy(ai->devnode, "/dev/", sizeof(ai->devnode));
strlcat(ai->devnode, devname, sizeof(ai->devnode));
ai->enabled = device_is_attached(d->dev) ? 1 : 0;
ai->min_rate = caps->minspeed;
ai->max_rate = caps->maxspeed;
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);
if (devname != NULL)
return (0);
}
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 = (struct pcmchan_syncgroup *)kmalloc(sizeof(*sg), M_DEVBUF, M_WAITOK | M_ZERO);
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 = (struct pcmchan_syncmember *)kmalloc(sizeof(*smrd), M_DEVBUF, M_WAITOK | M_ZERO);
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 = (struct pcmchan_syncmember *)kmalloc(sizeof(*smwr), M_DEVBUF, M_WAITOK | M_ZERO);
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) {
if (smrd != NULL)
kfree(smrd, M_DEVBUF);
if ((sg != NULL) && SLIST_EMPTY(&sg->members)) {
sg_ids[2] = sg->id;
SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link);
kfree(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 = lksleep(sm, &snd_pcm_syncgroups_mtx,
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);
kfree(sm, M_DEVBUF);
}
SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link);
kfree(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);
}
#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