freebsd-nq/sys/dev/sound/pcm/sound.c

1135 lines
25 KiB
C
Raw Normal View History

/*-
2003-09-07 16:28:03 +00:00
* Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
* (C) 1997 Luigi Rizzo (luigi@iet.unipi.it)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <dev/sound/pcm/sound.h>
#include <dev/sound/pcm/vchan.h>
#include <dev/sound/pcm/dsp.h>
#include <sys/sysctl.h>
#include "feeder_if.h"
SND_DECLARE_FILE("$FreeBSD$");
devclass_t pcm_devclass;
2001-02-27 07:01:49 +00:00
int pcm_veto_load = 1;
2001-02-27 07:01:49 +00:00
#ifdef USING_DEVFS
int snd_unit = 0;
TUNABLE_INT("hw.snd.unit", &snd_unit);
2001-02-27 07:01:49 +00:00
#endif
int snd_maxautovchans = 0;
TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans);
2001-02-27 07:01:49 +00:00
SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver");
static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose);
struct sysctl_ctx_list *
snd_sysctl_tree(device_t dev)
{
struct snddev_info *d = device_get_softc(dev);
return &d->sysctl_tree;
}
struct sysctl_oid *
snd_sysctl_tree_top(device_t dev)
{
struct snddev_info *d = device_get_softc(dev);
return d->sysctl_tree_top;
}
void *
snd_mtxcreate(const char *desc, const char *type)
{
#ifdef USING_MUTEX
struct mtx *m;
m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO);
if (m == NULL)
return NULL;
Change KASSERT() in feed_vchan16() into an explicit test and call to panic() so that the buffer overflow just beyond this point is always caught, even when the code is not compiled with INVARIANTS. Change chn_setblocksize() buffer reallocation code to attempt to avoid the feed_vchan16() buffer overflow by attempting to always keep the bufsoft buffer at least as large as the bufhard buffer. Print a diagnositic message Danger! %s bufsoft size increasing from %d to %d after CHANNEL_SETBLOCKSIZE() if our best attempts fail. If feed_vchan16() were to be called by the interrupt handler while locks are dropped in chn_setblocksize() to increase the size bufsoft to match the size of bufhard, the panic() code in feed_vchan16() will be triggered. If the diagnostic message is printed, it is a warning that a panic is possible if the system were to see events in an "unlucky" order. Change the locking code to avoid the need for MTX_RECURSIVE mutexes. Add the MTX_DUPOK option to the channel mutexes and change the locking sequence to always lock the parent channel before its children to avoid the possibility of deadlock. Actually implement locking assertions for the channel mutexes and fix the problems found by the resulting assertion violations. Clean up the locking code in dsp_ioctl(). Allocate the channel buffers using the malloc() M_WAITOK option instead of M_NOWAIT so that buffer allocation won't fail. Drop locks across the malloc() calls. Add/modify KASSERTS() in attempt to detect problems early. Abuse layering by adding a pointer to the snd_dbuf structure that points back to the pcm_channel that owns it. This allows sndbuf_resize() to do proper locking without having to change the its API, which is used by the hardware drivers. Don't dereference a NULL pointer when setting hw.snd.maxautovchans if a hardware driver is not loaded. Noticed by Ryan Sommers <ryans at gamersimpact.com>. Tested by: Stefan Ehmann <shoesoft AT gmx.net> Tested by: matk (Mathew Kanner) Tested by: Gordon Bergling <gbergling AT 0xfce3.net>
2004-01-28 08:02:15 +00:00
mtx_init(m, desc, type, MTX_DEF);
return m;
#else
return (void *)0xcafebabe;
#endif
}
void
snd_mtxfree(void *m)
{
#ifdef USING_MUTEX
struct mtx *mtx = m;
/* mtx_assert(mtx, MA_OWNED); */
mtx_destroy(mtx);
free(mtx, M_DEVBUF);
#endif
}
void
snd_mtxassert(void *m)
{
#ifdef USING_MUTEX
#ifdef INVARIANTS
struct mtx *mtx = m;
mtx_assert(mtx, MA_OWNED);
#endif
#endif
}
/*
void
snd_mtxlock(void *m)
{
#ifdef USING_MUTEX
struct mtx *mtx = m;
mtx_lock(mtx);
#endif
}
void
snd_mtxunlock(void *m)
{
#ifdef USING_MUTEX
struct mtx *mtx = m;
mtx_unlock(mtx);
#endif
}
*/
int
snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep)
{
#ifdef USING_MUTEX
flags &= INTR_MPSAFE;
flags |= INTR_TYPE_AV;
#else
flags = INTR_TYPE_AV;
#endif
return bus_setup_intr(dev, res, flags, hand, param, cookiep);
}
#ifndef PCM_DEBUG_MTX
void
pcm_lock(struct snddev_info *d)
{
snd_mtxlock(d->lock);
}
void
pcm_unlock(struct snddev_info *d)
{
snd_mtxunlock(d->lock);
}
#endif
struct pcm_channel *
pcm_getfakechan(struct snddev_info *d)
{
return d->fakechan;
}
static int
pcm_setvchans(struct snddev_info *d, int newcnt)
{
struct snddev_channel *sce = NULL;
struct pcm_channel *c = NULL;
int err = 0, vcnt, dcnt, i;
pcm_inprog(d, 1);
if (!(d->flags & SD_F_AUTOVCHAN)) {
err = EINVAL;
goto setvchans_out;
}
vcnt = d->vchancount;
dcnt = d->playcount + d->reccount;
if (newcnt < 0 || (dcnt + newcnt) > (PCMMAXCHAN + 1)) {
err = E2BIG;
goto setvchans_out;
}
dcnt += vcnt;
if (newcnt > vcnt) {
/* add new vchans - find a parent channel first */
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
CHN_LOCK(c);
if (c->direction == PCMDIR_PLAY &&
((c->flags & CHN_F_HAS_VCHAN) ||
(vcnt == 0 &&
!(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)))))
goto addok;
CHN_UNLOCK(c);
}
err = EBUSY;
goto setvchans_out;
addok:
c->flags |= CHN_F_BUSY;
while (err == 0 && newcnt > vcnt) {
if (dcnt > PCMMAXCHAN) {
device_printf(d->dev, "%s: Maximum channel reached.\n", __func__);
break;
}
err = vchan_create(c);
if (err == 0) {
vcnt++;
dcnt++;
} else if (err == E2BIG && newcnt > vcnt)
device_printf(d->dev, "%s: err=%d Maximum channel reached.\n", __func__, err);
}
if (vcnt == 0)
c->flags &= ~CHN_F_BUSY;
CHN_UNLOCK(c);
} else if (newcnt < vcnt) {
#define ORPHAN_CDEVT(cdevt) \
((cdevt) == NULL || ((cdevt)->si_drv1 == NULL && \
(cdevt)->si_drv2 == NULL))
while (err == 0 && newcnt < vcnt) {
i = 0;
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
CHN_LOCK(c);
if (c->direction == PCMDIR_PLAY &&
(c->flags & CHN_F_VIRTUAL) &&
(i++ == newcnt)) {
if (!(c->flags & CHN_F_BUSY) &&
ORPHAN_CDEVT(sce->dsp_devt) &&
ORPHAN_CDEVT(sce->dspW_devt) &&
ORPHAN_CDEVT(sce->audio_devt) &&
ORPHAN_CDEVT(sce->dspr_devt))
goto remok;
/*
* Either we're busy, or our cdev
* has been stolen by dsp_clone().
* Skip, and increase newcnt.
*/
if (!(c->flags & CHN_F_BUSY))
device_printf(d->dev,
"%s: <%s> somebody steal my cdev!\n",
__func__, c->name);
newcnt++;
}
CHN_UNLOCK(c);
}
if (vcnt != newcnt)
err = EBUSY;
break;
remok:
CHN_UNLOCK(c);
err = vchan_destroy(c);
if (err == 0)
vcnt--;
else
device_printf(d->dev,
"%s: WARNING: vchan_destroy() failed!",
__func__);
}
}
setvchans_out:
pcm_inprog(d, -1);
return err;
}
/* return error status and a locked channel */
int
pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction,
pid_t pid, int chnum)
{
struct pcm_channel *c;
struct snddev_channel *sce;
int err;
retry_chnalloc:
err = ENODEV;
/* scan for a free channel */
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
CHN_LOCK(c);
if (c->direction == direction && !(c->flags & CHN_F_BUSY)) {
if (chnum < 0 || sce->chan_num == chnum) {
c->flags |= CHN_F_BUSY;
c->pid = pid;
*ch = c;
return 0;
}
}
if (sce->chan_num == chnum) {
if (c->direction != direction)
err = EOPNOTSUPP;
else if (c->flags & CHN_F_BUSY)
err = EBUSY;
else
err = EINVAL;
CHN_UNLOCK(c);
return err;
} else if (c->direction == direction && (c->flags & CHN_F_BUSY))
err = EBUSY;
CHN_UNLOCK(c);
}
/* no channel available */
if (chnum == -1 && direction == PCMDIR_PLAY && d->vchancount > 0 &&
d->vchancount < snd_maxautovchans &&
d->devcount <= PCMMAXCHAN) {
err = pcm_setvchans(d, d->vchancount + 1);
if (err == 0) {
chnum = -2;
goto retry_chnalloc;
}
}
return err;
}
/* release a locked channel and unlock it */
int
pcm_chnrelease(struct pcm_channel *c)
{
CHN_LOCKASSERT(c);
c->flags &= ~CHN_F_BUSY;
c->pid = -1;
CHN_UNLOCK(c);
return 0;
}
int
pcm_chnref(struct pcm_channel *c, int ref)
{
int r;
CHN_LOCKASSERT(c);
c->refcount += ref;
r = c->refcount;
return r;
}
int
pcm_inprog(struct snddev_info *d, int delta)
{
int r;
if (delta == 0)
return d->inprog;
/* backtrace(); */
pcm_lock(d);
d->inprog += delta;
r = d->inprog;
pcm_unlock(d);
return r;
}
static void
pcm_setmaxautovchans(struct snddev_info *d, int num)
{
if (num > 0 && d->vchancount == 0)
pcm_setvchans(d, 1);
else if (num == 0 && d->vchancount > 0)
pcm_setvchans(d, 0);
}
#ifdef USING_DEVFS
static int
sysctl_hw_snd_unit(SYSCTL_HANDLER_ARGS)
{
struct snddev_info *d;
int error, unit;
unit = snd_unit;
error = sysctl_handle_int(oidp, &unit, sizeof(unit), req);
if (error == 0 && req->newptr != NULL) {
2001-06-18 00:10:47 +00:00
if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass))
return EINVAL;
d = devclass_get_softc(pcm_devclass, unit);
if (d == NULL || SLIST_EMPTY(&d->channels))
return EINVAL;
snd_unit = unit;
}
return (error);
}
SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW,
0, sizeof(int), sysctl_hw_snd_unit, "I", "");
2001-02-27 07:01:49 +00:00
#endif
static int
sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS)
{
struct snddev_info *d;
int i, v, error;
v = snd_maxautovchans;
error = sysctl_handle_int(oidp, &v, sizeof(v), req);
if (error == 0 && req->newptr != NULL) {
if (v < 0 || v > PCMMAXCHAN)
return E2BIG;
if (pcm_devclass != NULL && v != snd_maxautovchans) {
for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) {
d = devclass_get_softc(pcm_devclass, i);
if (!d)
continue;
pcm_setmaxautovchans(d, v);
}
}
snd_maxautovchans = v;
}
return (error);
}
SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW,
0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", "");
struct pcm_channel *
pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo)
{
struct snddev_channel *sce;
struct pcm_channel *ch, *c;
char *dirs;
uint32_t flsearch = 0;
int direction, err, rpnum, *pnum;
switch(dir) {
case PCMDIR_PLAY:
dirs = "play";
direction = PCMDIR_PLAY;
pnum = &d->playcount;
break;
case PCMDIR_REC:
dirs = "record";
direction = PCMDIR_REC;
pnum = &d->reccount;
break;
case PCMDIR_VIRTUAL:
dirs = "virtual";
direction = PCMDIR_PLAY;
pnum = &d->vchancount;
flsearch = CHN_F_VIRTUAL;
break;
default:
return NULL;
}
ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO);
if (!ch)
return NULL;
ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK);
if (!ch->methods) {
free(ch, M_DEVBUF);
return NULL;
}
snd_mtxlock(d->lock);
ch->num = 0;
rpnum = 0;
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
if (direction != c->direction ||
(c->flags & CHN_F_VIRTUAL) != flsearch)
continue;
if (ch->num == c->num)
ch->num++;
else {
#if 0
device_printf(d->dev,
"%s: %s channel numbering screwed (Expect: %d, Got: %d)\n",
__func__, dirs, ch->num, c->num);
#endif
goto retry_num_search;
}
rpnum++;
}
goto retry_num_search_out;
retry_num_search:
rpnum = 0;
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
if (direction != c->direction ||
(c->flags & CHN_F_VIRTUAL) != flsearch)
continue;
if (ch->num == c->num) {
ch->num++;
goto retry_num_search;
}
rpnum++;
}
retry_num_search_out:
if (*pnum != rpnum) {
device_printf(d->dev,
"%s: WARNING: pnum screwed : dirs=%s, pnum=%d, rpnum=%d\n",
__func__, dirs, *pnum, rpnum);
*pnum = rpnum;
}
(*pnum)++;
snd_mtxunlock(d->lock);
ch->pid = -1;
ch->parentsnddev = d;
ch->parentchannel = parent;
ch->dev = d->dev;
snprintf(ch->name, CHN_NAMELEN, "%s:%s:%d", device_get_nameunit(ch->dev), dirs, ch->num);
err = chn_init(ch, devinfo, dir, direction);
if (err) {
device_printf(d->dev, "chn_init(%s) failed: err = %d\n", ch->name, err);
kobj_delete(ch->methods, M_DEVBUF);
free(ch, M_DEVBUF);
snd_mtxlock(d->lock);
(*pnum)--;
snd_mtxunlock(d->lock);
return NULL;
}
return ch;
}
int
pcm_chn_destroy(struct pcm_channel *ch)
{
struct snddev_info *d;
int err;
d = ch->parentsnddev;
err = chn_kill(ch);
if (err) {
device_printf(d->dev, "chn_kill(%s) failed, err = %d\n", ch->name, err);
return err;
}
kobj_delete(ch->methods, M_DEVBUF);
free(ch, M_DEVBUF);
return 0;
}
int
pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch)
{
struct snddev_channel *sce, *tmp, *after;
unsigned rdevcount;
int device = device_get_unit(d->dev);
size_t namelen;
/*
* Note it's confusing nomenclature.
* dev_t
* device -> pcm_device
* unit -> pcm_channel
* channel -> snddev_channel
* device_t
* unit -> pcm_device
*/
sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO);
if (!sce) {
return ENOMEM;
}
snd_mtxlock(d->lock);
sce->channel = ch;
sce->chan_num = 0;
rdevcount = 0;
after = NULL;
SLIST_FOREACH(tmp, &d->channels, link) {
if (sce->chan_num == tmp->chan_num)
sce->chan_num++;
else {
#if 0
device_printf(d->dev,
"%s: cdev numbering screwed (Expect: %d, Got: %d)\n",
__func__, sce->chan_num, tmp->chan_num);
#endif
goto retry_chan_num_search;
}
after = tmp;
rdevcount++;
}
goto retry_chan_num_search_out;
retry_chan_num_search:
/*
* Look for possible channel numbering collision. This may not
* be optimized, but it will ensure that no collision occured.
* Can be considered cheap since none of the locking/unlocking
* operations involved.
*/
rdevcount = 0;
after = NULL;
SLIST_FOREACH(tmp, &d->channels, link) {
if (sce->chan_num == tmp->chan_num) {
sce->chan_num++;
goto retry_chan_num_search;
}
if (sce->chan_num > tmp->chan_num)
after = tmp;
rdevcount++;
}
retry_chan_num_search_out:
/*
* Don't overflow PCMMKMINOR / PCMMAXCHAN.
*/
if (sce->chan_num > PCMMAXCHAN) {
snd_mtxunlock(d->lock);
device_printf(d->dev,
"%s: WARNING: sce->chan_num overflow! (%d)\n",
__func__, sce->chan_num);
free(sce, M_DEVBUF);
return E2BIG;
}
if (d->devcount != rdevcount) {
device_printf(d->dev,
"%s: WARNING: devcount screwed! d->devcount=%u, rdevcount=%u\n",
__func__, d->devcount, rdevcount);
d->devcount = rdevcount;
}
d->devcount++;
if (after == NULL) {
SLIST_INSERT_HEAD(&d->channels, sce, link);
} else {
SLIST_INSERT_AFTER(after, sce, link);
}
#if 0
if (1) {
int cnum = 0;
SLIST_FOREACH(tmp, &d->channels, link) {
if (cnum != tmp->chan_num)
device_printf(d->dev,
"%s: WARNING: inconsistent cdev numbering! (Expect: %d, Got: %d)\n",
__func__, cnum, tmp->chan_num);
cnum++;
}
}
#endif
namelen = strlen(ch->name);
if ((CHN_NAMELEN - namelen) > 10) { /* ":dspXX.YYY" */
snprintf(ch->name + namelen,
CHN_NAMELEN - namelen, ":dsp%d.%d",
device, sce->chan_num);
}
snd_mtxunlock(d->lock);
/*
* I will revisit these someday, and nuke it mercilessly..
*/
sce->dsp_devt = make_dev(&dsp_cdevsw,
PCMMKMINOR(device, SND_DEV_DSP, sce->chan_num),
UID_ROOT, GID_WHEEL, 0666, "dsp%d.%d",
device, sce->chan_num);
sce->dspW_devt = make_dev(&dsp_cdevsw,
PCMMKMINOR(device, SND_DEV_DSP16, sce->chan_num),
UID_ROOT, GID_WHEEL, 0666, "dspW%d.%d",
device, sce->chan_num);
sce->audio_devt = make_dev(&dsp_cdevsw,
PCMMKMINOR(device, SND_DEV_AUDIO, sce->chan_num),
UID_ROOT, GID_WHEEL, 0666, "audio%d.%d",
device, sce->chan_num);
if (ch->direction == PCMDIR_REC)
sce->dspr_devt = make_dev(&dsp_cdevsw,
PCMMKMINOR(device, SND_DEV_DSPREC,
sce->chan_num), UID_ROOT, GID_WHEEL,
0666, "dspr%d.%d", device, sce->chan_num);
return 0;
}
int
pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch)
{
struct snddev_channel *sce;
#if 0
int ourlock;
ourlock = 0;
if (!mtx_owned(d->lock)) {
snd_mtxlock(d->lock);
ourlock = 1;
}
#endif
SLIST_FOREACH(sce, &d->channels, link) {
if (sce->channel == ch)
goto gotit;
}
#if 0
if (ourlock)
snd_mtxunlock(d->lock);
#endif
return EINVAL;
gotit:
SLIST_REMOVE(&d->channels, sce, snddev_channel, link);
- channel.h * New definition CHN_F_HAS_VCHAN. - channel.c * Use CHN_F_HAS_VCHAN to mark channel with vchan capability instead of relying on SLIST_EMPTY(&channel->children) == true for better clarification and future possible usages of children (like 'slave' channel). * Various fixes, including blocksize / format bps allignment, better 24bit seeking (mplayer, others). * Improve format chain building, it's now possible to record something to a format non-native to the soundcard through various feeder format converters or to higher sampling rate. This also gains another feature, like doing vchan mixing on non s16le soundcard such as sb8. - sound.c * Increase robustness within various function that handle vchan creation / termination (these function need a total rewrite, but that would cause other major rewrite within various places too!). As far as its robustness can be guaranteed, leave it as is. * Optimize channel ordering, prefer *real* hardware playback channels over virtual channels. cat /dev/sndstat should look better. * Increase sndstat verbosity to include bufsoft/bufhard allocation. - vchan.c * Fix LOR 119. - http://sources.zabbadoz.net/freebsd/lor.html#119 * Reorder / increase robustness of vchan_create() / destroy(). Enforce destroy_dev() during destroy operation, fix possible panic / dangling character device. - http://lists.freebsd.org/pipermail/freebsd-current/2005-May/050308.html * Tolerate a little bit more during mixing process, this should help non s16le soundcards. Note: Recoring in a non-native rate/format may result in overruns. A friendly application is wavrec from audio/wavplay. The problem is under investigation. Submitted by: Ariff Abdullah <skywizard@MyBSD.org.my>
2005-09-10 18:10:31 +00:00
if (ch->flags & CHN_F_VIRTUAL)
d->vchancount--;
- channel.h * New definition CHN_F_HAS_VCHAN. - channel.c * Use CHN_F_HAS_VCHAN to mark channel with vchan capability instead of relying on SLIST_EMPTY(&channel->children) == true for better clarification and future possible usages of children (like 'slave' channel). * Various fixes, including blocksize / format bps allignment, better 24bit seeking (mplayer, others). * Improve format chain building, it's now possible to record something to a format non-native to the soundcard through various feeder format converters or to higher sampling rate. This also gains another feature, like doing vchan mixing on non s16le soundcard such as sb8. - sound.c * Increase robustness within various function that handle vchan creation / termination (these function need a total rewrite, but that would cause other major rewrite within various places too!). As far as its robustness can be guaranteed, leave it as is. * Optimize channel ordering, prefer *real* hardware playback channels over virtual channels. cat /dev/sndstat should look better. * Increase sndstat verbosity to include bufsoft/bufhard allocation. - vchan.c * Fix LOR 119. - http://sources.zabbadoz.net/freebsd/lor.html#119 * Reorder / increase robustness of vchan_create() / destroy(). Enforce destroy_dev() during destroy operation, fix possible panic / dangling character device. - http://lists.freebsd.org/pipermail/freebsd-current/2005-May/050308.html * Tolerate a little bit more during mixing process, this should help non s16le soundcards. Note: Recoring in a non-native rate/format may result in overruns. A friendly application is wavrec from audio/wavplay. The problem is under investigation. Submitted by: Ariff Abdullah <skywizard@MyBSD.org.my>
2005-09-10 18:10:31 +00:00
else if (ch->direction == PCMDIR_REC)
d->reccount--;
else
d->playcount--;
#if 0
if (ourlock)
snd_mtxunlock(d->lock);
#endif
free(sce, M_DEVBUF);
return 0;
}
int
pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo)
{
struct snddev_info *d = device_get_softc(dev);
struct pcm_channel *ch;
int err;
ch = pcm_chn_create(d, NULL, cls, dir, devinfo);
if (!ch) {
device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo);
return ENODEV;
}
err = pcm_chn_add(d, ch);
if (err) {
device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err);
pcm_chn_destroy(ch);
return err;
}
return err;
}
static int
pcm_killchan(device_t dev)
{
struct snddev_info *d = device_get_softc(dev);
struct snddev_channel *sce;
struct pcm_channel *ch;
int error = 0;
sce = SLIST_FIRST(&d->channels);
ch = sce->channel;
error = pcm_chn_remove(d, sce->channel);
if (error)
return (error);
return (pcm_chn_destroy(ch));
}
int
pcm_setstatus(device_t dev, char *str)
{
struct snddev_info *d = device_get_softc(dev);
snd_mtxlock(d->lock);
strncpy(d->status, str, SND_STATUSLEN);
snd_mtxunlock(d->lock);
if (snd_maxautovchans > 0)
pcm_setvchans(d, 1);
return 0;
}
uint32_t
pcm_getflags(device_t dev)
{
struct snddev_info *d = device_get_softc(dev);
return d->flags;
}
void
pcm_setflags(device_t dev, uint32_t val)
{
struct snddev_info *d = device_get_softc(dev);
d->flags = val;
}
void *
pcm_getdevinfo(device_t dev)
{
struct snddev_info *d = device_get_softc(dev);
return d->devinfo;
}
unsigned int
pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max)
{
struct snddev_info *d = device_get_softc(dev);
int sz, x;
sz = 0;
if (resource_int_value(device_get_name(dev), device_get_unit(dev), "buffersize", &sz) == 0) {
x = sz;
RANGE(sz, min, max);
if (x != sz)
device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, min, max, sz);
x = min;
while (x < sz)
x <<= 1;
if (x > sz)
x >>= 1;
if (x != sz) {
device_printf(dev, "'buffersize=%d' hint is not a power of 2, using %d\n", sz, x);
sz = x;
}
} else {
sz = deflt;
}
d->bufsz = sz;
return sz;
}
int
pcm_register(device_t dev, void *devinfo, int numplay, int numrec)
{
struct snddev_info *d = device_get_softc(dev);
if (pcm_veto_load) {
device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load);
return EINVAL;
}
d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev");
#if 0
/*
* d->flags should be cleared by the allocator of the softc.
* We cannot clear this field here because several devices set
* this flag before calling pcm_register().
*/
d->flags = 0;
#endif
d->dev = dev;
d->devinfo = devinfo;
d->devcount = 0;
d->reccount = 0;
d->playcount = 0;
d->vchancount = 0;
d->inprog = 0;
SLIST_INIT(&d->channels);
if ((numplay == 0 || numrec == 0) && numplay != numrec)
d->flags |= SD_F_SIMPLEX;
d->fakechan = fkchan_setup(dev);
chn_init(d->fakechan, NULL, 0, 0);
2001-02-27 07:01:49 +00:00
#ifdef SND_DYNSYSCTL
sysctl_ctx_init(&d->sysctl_tree);
d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree,
SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO,
device_get_nameunit(dev), CTLFLAG_RD, 0, "");
if (d->sysctl_tree_top == NULL) {
sysctl_ctx_free(&d->sysctl_tree);
goto no;
}
SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)),
OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "");
2001-02-27 07:01:49 +00:00
#endif
- channel.h * New definition CHN_F_HAS_VCHAN. - channel.c * Use CHN_F_HAS_VCHAN to mark channel with vchan capability instead of relying on SLIST_EMPTY(&channel->children) == true for better clarification and future possible usages of children (like 'slave' channel). * Various fixes, including blocksize / format bps allignment, better 24bit seeking (mplayer, others). * Improve format chain building, it's now possible to record something to a format non-native to the soundcard through various feeder format converters or to higher sampling rate. This also gains another feature, like doing vchan mixing on non s16le soundcard such as sb8. - sound.c * Increase robustness within various function that handle vchan creation / termination (these function need a total rewrite, but that would cause other major rewrite within various places too!). As far as its robustness can be guaranteed, leave it as is. * Optimize channel ordering, prefer *real* hardware playback channels over virtual channels. cat /dev/sndstat should look better. * Increase sndstat verbosity to include bufsoft/bufhard allocation. - vchan.c * Fix LOR 119. - http://sources.zabbadoz.net/freebsd/lor.html#119 * Reorder / increase robustness of vchan_create() / destroy(). Enforce destroy_dev() during destroy operation, fix possible panic / dangling character device. - http://lists.freebsd.org/pipermail/freebsd-current/2005-May/050308.html * Tolerate a little bit more during mixing process, this should help non s16le soundcards. Note: Recoring in a non-native rate/format may result in overruns. A friendly application is wavrec from audio/wavplay. The problem is under investigation. Submitted by: Ariff Abdullah <skywizard@MyBSD.org.my>
2005-09-10 18:10:31 +00:00
if (numplay > 0) {
d->flags |= SD_F_AUTOVCHAN;
vchan_initsys(dev);
- channel.h * New definition CHN_F_HAS_VCHAN. - channel.c * Use CHN_F_HAS_VCHAN to mark channel with vchan capability instead of relying on SLIST_EMPTY(&channel->children) == true for better clarification and future possible usages of children (like 'slave' channel). * Various fixes, including blocksize / format bps allignment, better 24bit seeking (mplayer, others). * Improve format chain building, it's now possible to record something to a format non-native to the soundcard through various feeder format converters or to higher sampling rate. This also gains another feature, like doing vchan mixing on non s16le soundcard such as sb8. - sound.c * Increase robustness within various function that handle vchan creation / termination (these function need a total rewrite, but that would cause other major rewrite within various places too!). As far as its robustness can be guaranteed, leave it as is. * Optimize channel ordering, prefer *real* hardware playback channels over virtual channels. cat /dev/sndstat should look better. * Increase sndstat verbosity to include bufsoft/bufhard allocation. - vchan.c * Fix LOR 119. - http://sources.zabbadoz.net/freebsd/lor.html#119 * Reorder / increase robustness of vchan_create() / destroy(). Enforce destroy_dev() during destroy operation, fix possible panic / dangling character device. - http://lists.freebsd.org/pipermail/freebsd-current/2005-May/050308.html * Tolerate a little bit more during mixing process, this should help non s16le soundcards. Note: Recoring in a non-native rate/format may result in overruns. A friendly application is wavrec from audio/wavplay. The problem is under investigation. Submitted by: Ariff Abdullah <skywizard@MyBSD.org.my>
2005-09-10 18:10:31 +00:00
}
sndstat_register(dev, d->status, sndstat_prepare_pcm);
return 0;
no:
snd_mtxfree(d->lock);
return ENXIO;
}
int
pcm_unregister(device_t dev)
{
struct snddev_info *d = device_get_softc(dev);
struct snddev_channel *sce;
struct pcmchan_children *pce;
struct pcm_channel *ch;
if (sndstat_acquire() != 0) {
device_printf(dev, "unregister: sndstat busy\n");
return EBUSY;
}
snd_mtxlock(d->lock);
if (d->inprog) {
device_printf(dev, "unregister: operation in progress\n");
snd_mtxunlock(d->lock);
sndstat_release();
return EBUSY;
}
SLIST_FOREACH(sce, &d->channels, link) {
ch = sce->channel;
if (ch->refcount > 0) {
device_printf(dev, "unregister: channel %s busy (pid %d)\n", ch->name, ch->pid);
snd_mtxunlock(d->lock);
sndstat_release();
return EBUSY;
}
}
if (mixer_uninit(dev) == EBUSY) {
device_printf(dev, "unregister: mixer busy\n");
snd_mtxunlock(d->lock);
sndstat_release();
return EBUSY;
}
SLIST_FOREACH(sce, &d->channels, link) {
if (sce->dsp_devt) {
- channel.h * New definition CHN_F_HAS_VCHAN. - channel.c * Use CHN_F_HAS_VCHAN to mark channel with vchan capability instead of relying on SLIST_EMPTY(&channel->children) == true for better clarification and future possible usages of children (like 'slave' channel). * Various fixes, including blocksize / format bps allignment, better 24bit seeking (mplayer, others). * Improve format chain building, it's now possible to record something to a format non-native to the soundcard through various feeder format converters or to higher sampling rate. This also gains another feature, like doing vchan mixing on non s16le soundcard such as sb8. - sound.c * Increase robustness within various function that handle vchan creation / termination (these function need a total rewrite, but that would cause other major rewrite within various places too!). As far as its robustness can be guaranteed, leave it as is. * Optimize channel ordering, prefer *real* hardware playback channels over virtual channels. cat /dev/sndstat should look better. * Increase sndstat verbosity to include bufsoft/bufhard allocation. - vchan.c * Fix LOR 119. - http://sources.zabbadoz.net/freebsd/lor.html#119 * Reorder / increase robustness of vchan_create() / destroy(). Enforce destroy_dev() during destroy operation, fix possible panic / dangling character device. - http://lists.freebsd.org/pipermail/freebsd-current/2005-May/050308.html * Tolerate a little bit more during mixing process, this should help non s16le soundcards. Note: Recoring in a non-native rate/format may result in overruns. A friendly application is wavrec from audio/wavplay. The problem is under investigation. Submitted by: Ariff Abdullah <skywizard@MyBSD.org.my>
2005-09-10 18:10:31 +00:00
destroy_dev(sce->dsp_devt);
sce->dsp_devt = NULL;
}
if (sce->dspW_devt) {
- channel.h * New definition CHN_F_HAS_VCHAN. - channel.c * Use CHN_F_HAS_VCHAN to mark channel with vchan capability instead of relying on SLIST_EMPTY(&channel->children) == true for better clarification and future possible usages of children (like 'slave' channel). * Various fixes, including blocksize / format bps allignment, better 24bit seeking (mplayer, others). * Improve format chain building, it's now possible to record something to a format non-native to the soundcard through various feeder format converters or to higher sampling rate. This also gains another feature, like doing vchan mixing on non s16le soundcard such as sb8. - sound.c * Increase robustness within various function that handle vchan creation / termination (these function need a total rewrite, but that would cause other major rewrite within various places too!). As far as its robustness can be guaranteed, leave it as is. * Optimize channel ordering, prefer *real* hardware playback channels over virtual channels. cat /dev/sndstat should look better. * Increase sndstat verbosity to include bufsoft/bufhard allocation. - vchan.c * Fix LOR 119. - http://sources.zabbadoz.net/freebsd/lor.html#119 * Reorder / increase robustness of vchan_create() / destroy(). Enforce destroy_dev() during destroy operation, fix possible panic / dangling character device. - http://lists.freebsd.org/pipermail/freebsd-current/2005-May/050308.html * Tolerate a little bit more during mixing process, this should help non s16le soundcards. Note: Recoring in a non-native rate/format may result in overruns. A friendly application is wavrec from audio/wavplay. The problem is under investigation. Submitted by: Ariff Abdullah <skywizard@MyBSD.org.my>
2005-09-10 18:10:31 +00:00
destroy_dev(sce->dspW_devt);
sce->dspW_devt = NULL;
}
if (sce->audio_devt) {
- channel.h * New definition CHN_F_HAS_VCHAN. - channel.c * Use CHN_F_HAS_VCHAN to mark channel with vchan capability instead of relying on SLIST_EMPTY(&channel->children) == true for better clarification and future possible usages of children (like 'slave' channel). * Various fixes, including blocksize / format bps allignment, better 24bit seeking (mplayer, others). * Improve format chain building, it's now possible to record something to a format non-native to the soundcard through various feeder format converters or to higher sampling rate. This also gains another feature, like doing vchan mixing on non s16le soundcard such as sb8. - sound.c * Increase robustness within various function that handle vchan creation / termination (these function need a total rewrite, but that would cause other major rewrite within various places too!). As far as its robustness can be guaranteed, leave it as is. * Optimize channel ordering, prefer *real* hardware playback channels over virtual channels. cat /dev/sndstat should look better. * Increase sndstat verbosity to include bufsoft/bufhard allocation. - vchan.c * Fix LOR 119. - http://sources.zabbadoz.net/freebsd/lor.html#119 * Reorder / increase robustness of vchan_create() / destroy(). Enforce destroy_dev() during destroy operation, fix possible panic / dangling character device. - http://lists.freebsd.org/pipermail/freebsd-current/2005-May/050308.html * Tolerate a little bit more during mixing process, this should help non s16le soundcards. Note: Recoring in a non-native rate/format may result in overruns. A friendly application is wavrec from audio/wavplay. The problem is under investigation. Submitted by: Ariff Abdullah <skywizard@MyBSD.org.my>
2005-09-10 18:10:31 +00:00
destroy_dev(sce->audio_devt);
sce->audio_devt = NULL;
}
if (sce->dspr_devt) {
destroy_dev(sce->dspr_devt);
sce->dspr_devt = NULL;
}
d->devcount--;
ch = sce->channel;
if (ch == NULL)
continue;
pce = SLIST_FIRST(&ch->children);
while (pce != NULL) {
#if 0
device_printf(d->dev, "<%s> removing <%s>\n",
ch->name, (pce->channel != NULL) ?
pce->channel->name : "unknown");
#endif
SLIST_REMOVE(&ch->children, pce, pcmchan_children, link);
free(pce, M_DEVBUF);
pce = SLIST_FIRST(&ch->children);
}
}
#ifdef SND_DYNSYSCTL
d->sysctl_tree_top = NULL;
sysctl_ctx_free(&d->sysctl_tree);
#endif
#if 0
SLIST_FOREACH(sce, &d->channels, link) {
ch = sce->channel;
if (ch == NULL)
continue;
if (!SLIST_EMPTY(&ch->children))
device_printf(d->dev, "%s: WARNING: <%s> dangling child!\n",
__func__, ch->name);
}
#endif
while (!SLIST_EMPTY(&d->channels))
pcm_killchan(dev);
chn_kill(d->fakechan);
fkchan_kill(d->fakechan);
2001-02-27 07:01:49 +00:00
#if 0
device_printf(d->dev, "%s: devcount=%u, playcount=%u, "
"reccount=%u, vchancount=%u\n",
__func__, d->devcount, d->playcount, d->reccount,
d->vchancount);
#endif
snd_mtxunlock(d->lock);
snd_mtxfree(d->lock);
sndstat_unregister(dev);
sndstat_release();
return 0;
}
/************************************************************************/
static int
sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose)
{
struct snddev_info *d;
struct snddev_channel *sce;
struct pcm_channel *c;
struct pcm_feeder *f;
int pc, rc, vc;
if (verbose < 1)
return 0;
d = device_get_softc(dev);
if (!d)
return ENXIO;
snd_mtxlock(d->lock);
if (!SLIST_EMPTY(&d->channels)) {
pc = rc = vc = 0;
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
if (c->direction == PCMDIR_PLAY) {
if (c->flags & CHN_F_VIRTUAL)
vc++;
else
pc++;
} else
rc++;
}
sbuf_printf(s, " (%dp/%dr/%dv channels%s%s)", d->playcount, d->reccount, d->vchancount,
(d->flags & SD_F_SIMPLEX)? "" : " duplex",
#ifdef USING_DEVFS
(device_get_unit(dev) == snd_unit)? " default" : ""
#else
""
#endif
);
if (verbose <= 1) {
snd_mtxunlock(d->lock);
return 0;
}
SLIST_FOREACH(sce, &d->channels, link) {
c = sce->channel;
KASSERT(c->bufhard != NULL && c->bufsoft != NULL,
("hosed pcm channel setup"));
sbuf_printf(s, "\n\t");
/* it would be better to indent child channels */
sbuf_printf(s, "%s[%s]: ", c->parentchannel? c->parentchannel->name : "", c->name);
sbuf_printf(s, "spd %d", c->speed);
if (c->speed != sndbuf_getspd(c->bufhard))
sbuf_printf(s, "/%d", sndbuf_getspd(c->bufhard));
sbuf_printf(s, ", fmt 0x%08x", c->format);
if (c->format != sndbuf_getfmt(c->bufhard))
sbuf_printf(s, "/0x%08x", sndbuf_getfmt(c->bufhard));
sbuf_printf(s, ", flags 0x%08x, 0x%08x", c->flags, c->feederflags);
if (c->pid != -1)
sbuf_printf(s, ", pid %d", c->pid);
sbuf_printf(s, "\n\t");
sbuf_printf(s, "interrupts %d, ", c->interrupts);
if (c->direction == PCMDIR_REC)
sbuf_printf(s, "overruns %d, hfree %d, sfree %d [b:%d/%d/%d|bs:%d/%d/%d]",
c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft),
sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard),
sndbuf_getblkcnt(c->bufhard),
sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft),
sndbuf_getblkcnt(c->bufsoft));
else
sbuf_printf(s, "underruns %d, ready %d [b:%d/%d/%d|bs:%d/%d/%d]",
c->xruns, sndbuf_getready(c->bufsoft),
sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard),
sndbuf_getblkcnt(c->bufhard),
sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft),
sndbuf_getblkcnt(c->bufsoft));
sbuf_printf(s, "\n\t");
sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "hardware" : "userland");
sbuf_printf(s, " -> ");
f = c->feeder;
while (f->source != NULL)
f = f->source;
while (f != NULL) {
sbuf_printf(s, "%s", f->class->name);
if (f->desc->type == FEEDER_FMT)
sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out);
if (f->desc->type == FEEDER_RATE)
sbuf_printf(s, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST));
if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER ||
f->desc->type == FEEDER_VOLUME)
sbuf_printf(s, "(0x%08x)", f->desc->out);
sbuf_printf(s, " -> ");
f = f->parent;
}
sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "userland" : "hardware");
}
} else
sbuf_printf(s, " (mixer only)");
snd_mtxunlock(d->lock);
return 0;
}
/************************************************************************/
#ifdef SND_DYNSYSCTL
int
sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS)
{
struct snddev_info *d;
int err, newcnt;
d = oidp->oid_arg1;
newcnt = d->vchancount;
err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req);
if (err == 0 && req->newptr != NULL && d->vchancount != newcnt)
err = pcm_setvchans(d, newcnt);
return err;
}
#endif
/************************************************************************/
static int
sound_modevent(module_t mod, int type, void *data)
{
#if 0
return (midi_modevent(mod, type, data));
#else
return 0;
#endif
}
DEV_MODULE(sound, sound_modevent, NULL);
MODULE_VERSION(sound, SOUND_MODVER);