[stage: 5/9]

channel.c/channel_if.m:
 - Macros cleanups, prefer inlined min() over MIN().
 - Rework chn_read()/chn_write() for better dead interrupt detection
   policy. Reduce scheduling overhead by doing pure 5 seconds sleep
   before giving up, instead of several cycle of brute micro sleeping.
 - Avoid calling wakeup_one() for non-sleeping channel (for example,
   vchan parent channel).
 - EWOULDBLOCK -> EAGAIN.
 - Fix possible divide-by-zero panic on chn_sync().
 - Re-enforce ^2 blocksize policy, since there are too many broken
   userland apps that blindly assume it without even trying to do
   serious calculations.
 - New channel method - CHANNEL_SETFRAGMENTS(), a refined version of
   CHANNEL_SETBLOCKSIZE(). It accept _both_ blocksize and blockcount
   arguments, so the driver internals will have better hints for
   buffering and timing calculations.
 - Hook FEEDER_SWAPLR into feederchain building process.

feeder_fmt.c:
- Unified version of various filters, avoiding duplications.
- malloc()less feeder_fmt. Informations can be retrieved dynamically
  by doing table lookup on static data. For cases such as converting
  from stereo to mono or reducing bit depth where input data is larger
  than output, cycle remaining available free space until it has been
  exhausted and start kicking 8 bytes reservoir space from there to
  complete the remaining requested count.
- Introduce FEEDER_SWAPLR. Few super broken hardwares (found on several
  extremely cheap uaudio stick, possibly others) mistakenly wired left
  and right channels wrongly, screwing output or input.
This commit is contained in:
ariff 2007-03-16 17:16:24 +00:00
parent c6b6d02113
commit 6324ae1e6c
3 changed files with 1226 additions and 1253 deletions

View File

@ -39,13 +39,20 @@ SND_DECLARE_FILE("$FreeBSD$");
#define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1))
#endif
#define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED))
#define CHN_STARTED(c) ((c)->flags & CHN_F_TRIGGERED)
#define CHN_STOPPED(c) (!CHN_STARTED(c))
#define CHN_DIRSTR(c) (((c)->direction == PCMDIR_PLAY) ? \
"PCMDIR_PLAY" : "PCMDIR_REC")
#define BUF_PARENT(c, b) \
(((c) != NULL && (c)->parentchannel != NULL && \
(c)->parentchannel->bufhard != NULL) ? \
(c)->parentchannel->bufhard : (b))
#define CHN_TIMEOUT 5
#define CHN_TIMEOUT_MIN 1
#define CHN_TIMEOUT_MAX 10
/*
#define DEB(x) x
*/
@ -96,6 +103,40 @@ SYSCTL_PROC(_hw_snd, OID_AUTO, latency_profile, CTLTYPE_INT | CTLFLAG_RW,
0, sizeof(int), sysctl_hw_snd_latency_profile, "I",
"buffering latency profile (0=aggresive 1=safe)");
static int chn_timeout = CHN_TIMEOUT;
#ifdef SND_DEBUG
static int
sysctl_hw_snd_timeout(SYSCTL_HANDLER_ARGS)
{
int err, val;
val = chn_timeout;
err = sysctl_handle_int(oidp, &val, sizeof(val), req);
if (val < CHN_TIMEOUT_MIN || val > CHN_TIMEOUT_MAX)
err = EINVAL;
else
chn_timeout = val;
return err;
}
SYSCTL_PROC(_hw_snd, OID_AUTO, timeout, CTLTYPE_INT | CTLFLAG_RW,
0, sizeof(int), sysctl_hw_snd_timeout, "I",
"interrupt timeout (1 - 10)");
#endif
static int chn_usefrags = 0;
TUNABLE_INT("hw.snd.usefrags", &chn_usefrags);
static int chn_syncdelay = -1;
TUNABLE_INT("hw.snd.syncdelay", &chn_syncdelay);
#ifdef SND_DEBUG
SYSCTL_INT(_hw_snd, OID_AUTO, usefrags, CTLFLAG_RW,
&chn_usefrags, 1, "prefer setfragments() over setblocksize()");
SYSCTL_INT(_hw_snd, OID_AUTO, syncdelay, CTLFLAG_RW,
&chn_syncdelay, 1,
"append (0-1000) millisecond trailing buffer delay on each sync");
#endif
/**
* @brief Channel sync group lock
*
@ -193,6 +234,7 @@ chn_wakeup(struct pcm_channel *c)
if (SLIST_EMPTY(&c->children)) {
if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c))
selwakeuppri(sndbuf_getsel(bs), PRIBIO);
wakeup_one(bs);
} else {
SLIST_FOREACH(pce, &c->children, link) {
CHN_LOCK(pce->channel);
@ -200,8 +242,6 @@ chn_wakeup(struct pcm_channel *c)
CHN_UNLOCK(pce->channel);
}
}
wakeup_one(bs);
}
static int
@ -247,22 +287,21 @@ chn_dmaupdate(struct pcm_channel *c)
);
if (c->direction == PCMDIR_PLAY) {
amt = MIN(delta, sndbuf_getready(b));
amt = min(delta, sndbuf_getready(b));
amt -= amt % sndbuf_getbps(b);
if (amt > 0)
sndbuf_dispose(b, NULL, amt);
} else {
amt = MIN(delta, sndbuf_getfree(b));
amt = min(delta, sndbuf_getfree(b));
amt -= amt % sndbuf_getbps(b);
if (amt > 0)
sndbuf_acquire(b, NULL, amt);
}
if (snd_verbose > 2 && (c->flags & CHN_F_TRIGGERED) && delta == 0) {
device_printf(c->dev, "WARNING: PCMDIR_%s DMA completion "
if (snd_verbose > 3 && CHN_STARTED(c) && delta == 0) {
device_printf(c->dev, "WARNING: %s DMA completion "
"too fast/slow ! hwptr=%u, old=%u "
"delta=%u amt=%u ready=%u free=%u\n",
(c->direction == PCMDIR_PLAY) ? "PLAY" : "REC",
hwptr, old, delta, amt,
CHN_DIRSTR(c), hwptr, old, delta, amt,
sndbuf_getready(b), sndbuf_getfree(b));
}
@ -277,7 +316,7 @@ chn_wrupdate(struct pcm_channel *c)
CHN_LOCKASSERT(c);
KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel"));
if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || !(c->flags & CHN_F_TRIGGERED))
if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || CHN_STOPPED(c))
return;
chn_dmaupdate(c);
ret = chn_wrfeed(c);
@ -321,8 +360,11 @@ chn_wrfeed(struct pcm_channel *c)
if (sndbuf_getfree(b) > 0)
c->xruns++;
#if 0
if (ret == 0 && sndbuf_getfree(b) < amt)
chn_wakeup(c);
#endif
chn_wakeup(c);
return ret;
}
@ -353,76 +395,59 @@ chn_wrintr(struct pcm_channel *c)
int
chn_write(struct pcm_channel *c, struct uio *buf)
{
int ret, count, sz;
struct snd_dbuf *bs = c->bufsoft;
void *off;
int t, x, togo, p;
int ret, timeout, sz, t, p;
CHN_LOCKASSERT(c);
ret = 0;
count = hz;
while (!ret && (buf->uio_resid > 0) && (count > 0)) {
sz = sndbuf_getfree(bs);
if (sz == 0) {
if (c->flags & CHN_F_NBIO)
ret = EWOULDBLOCK;
else if (c->flags & CHN_F_NOTRIGGER) {
/**
* @todo Evaluate whether EAGAIN is truly desirable.
* 4Front drivers behave like this, but I'm
* not sure if it at all violates the "write
* should be allowed to block" model.
*
* The idea is that, while set with CHN_F_NOTRIGGER,
* a channel isn't playing, *but* without this we
* end up with "interrupt timeout / channel dead".
*/
ret = EAGAIN;
} else {
ret = chn_sleep(c, "pcmwr", c->timeout);
if (ret == EWOULDBLOCK) {
count -= c->timeout;
ret = 0;
} else if (ret == ERESTART || ret == EINTR) {
c->flags |= CHN_F_ABORTING;
return ret;
} else if (ret == 0)
count = hz;
}
} else {
sz = MIN(sz, buf->uio_resid);
KASSERT(sz > 0, ("confusion in chn_write"));
/* printf("sz: %d\n", sz); */
timeout = chn_timeout * hz;
while (ret == 0 && buf->uio_resid > 0) {
sz = min(buf->uio_resid, sndbuf_getfree(bs));
if (sz > 0) {
/*
* The following assumes that the free space in
* the buffer can never be less around the
* unlock-uiomove-lock sequence.
*/
togo = sz;
while (ret == 0 && togo > 0) {
while (ret == 0 && sz > 0) {
p = sndbuf_getfreeptr(bs);
t = MIN(togo, sndbuf_getsize(bs) - p);
t = min(sz, sndbuf_getsize(bs) - p);
off = sndbuf_getbufofs(bs, p);
CHN_UNLOCK(c);
ret = uiomove(off, t, buf);
CHN_LOCK(c);
togo -= t;
x = sndbuf_acquire(bs, NULL, t);
sz -= t;
sndbuf_acquire(bs, NULL, t);
}
ret = 0;
if (!(c->flags & CHN_F_TRIGGERED))
if (CHN_STOPPED(c))
chn_start(c, 0);
} else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) {
/**
* @todo Evaluate whether EAGAIN is truly desirable.
* 4Front drivers behave like this, but I'm
* not sure if it at all violates the "write
* should be allowed to block" model.
*
* The idea is that, while set with CHN_F_NOTRIGGER,
* a channel isn't playing, *but* without this we
* end up with "interrupt timeout / channel dead".
*/
ret = EAGAIN;
} else {
ret = chn_sleep(c, "pcmwr", timeout);
if (ret == EAGAIN) {
ret = EINVAL;
c->flags |= CHN_F_DEAD;
printf("%s: play interrupt timeout, "
"channel dead\n", c->name);
} else if (ret == ERESTART || ret == EINTR)
c->flags |= CHN_F_ABORTING;
}
}
/* printf("ret: %d left: %d\n", ret, buf->uio_resid); */
if (count <= 0) {
c->flags |= CHN_F_DEAD;
printf("%s: play interrupt timeout, channel dead\n", c->name);
}
return ret;
}
@ -490,7 +515,7 @@ chn_rdupdate(struct pcm_channel *c)
CHN_LOCKASSERT(c);
KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel"));
if ((c->flags & CHN_F_MAPPED) || !(c->flags & CHN_F_TRIGGERED))
if ((c->flags & CHN_F_MAPPED) || CHN_STOPPED(c))
return;
chn_trigger(c, PCMTRIG_EMLDMARD);
chn_dmaupdate(c);
@ -525,60 +550,51 @@ chn_rdintr(struct pcm_channel *c)
int
chn_read(struct pcm_channel *c, struct uio *buf)
{
int ret, sz, count;
struct snd_dbuf *bs = c->bufsoft;
void *off;
int t, x, togo, p;
int ret, timeout, sz, t, p;
CHN_LOCKASSERT(c);
if (!(c->flags & CHN_F_TRIGGERED))
if (CHN_STOPPED(c))
chn_start(c, 0);
ret = 0;
count = hz;
while (!ret && (buf->uio_resid > 0) && (count > 0)) {
sz = MIN(buf->uio_resid, sndbuf_getready(bs));
timeout = chn_timeout * hz;
while (ret == 0 && buf->uio_resid > 0) {
sz = min(buf->uio_resid, sndbuf_getready(bs));
if (sz > 0) {
/*
* The following assumes that the free space in
* the buffer can never be less around the
* unlock-uiomove-lock sequence.
*/
togo = sz;
while (ret == 0 && togo > 0) {
while (ret == 0 && sz > 0) {
p = sndbuf_getreadyptr(bs);
t = MIN(togo, sndbuf_getsize(bs) - p);
t = min(sz, sndbuf_getsize(bs) - p);
off = sndbuf_getbufofs(bs, p);
CHN_UNLOCK(c);
ret = uiomove(off, t, buf);
CHN_LOCK(c);
togo -= t;
x = sndbuf_dispose(bs, NULL, t);
sz -= t;
sndbuf_dispose(bs, NULL, t);
}
ret = 0;
} else {
if (c->flags & CHN_F_NBIO)
ret = EWOULDBLOCK;
else {
ret = chn_sleep(c, "pcmrd", c->timeout);
if (ret == EWOULDBLOCK) {
count -= c->timeout;
ret = 0;
} else if (ret == ERESTART || ret == EINTR) {
c->flags |= CHN_F_ABORTING;
return ret;
} else
count = hz;
}
} else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER))
ret = EAGAIN;
else {
ret = chn_sleep(c, "pcmrd", timeout);
if (ret == EAGAIN) {
ret = EINVAL;
c->flags |= CHN_F_DEAD;
printf("%s: record interrupt timeout, "
"channel dead\n", c->name);
} else if (ret == ERESTART || ret == EINTR)
c->flags |= CHN_F_ABORTING;
}
}
if (count <= 0) {
c->flags |= CHN_F_DEAD;
printf("%s: record interrupt timeout, channel dead\n", c->name);
}
return ret;
}
@ -603,7 +619,7 @@ chn_start(struct pcm_channel *c, int force)
CHN_LOCKASSERT(c);
/* if we're running, or if we're prevented from triggering, bail */
if ((c->flags & CHN_F_TRIGGERED) || ((c->flags & CHN_F_NOTRIGGER) && !force))
if (CHN_STARTED(c) || ((c->flags & CHN_F_NOTRIGGER) && !force))
return EINVAL;
if (force) {
@ -612,20 +628,22 @@ chn_start(struct pcm_channel *c, int force)
} else {
if (c->direction == PCMDIR_REC) {
i = sndbuf_getfree(bs);
j = sndbuf_getready(b);
j = (i > 0) ? 1 : sndbuf_getready(b);
} else {
struct snd_dbuf *pb;
if (sndbuf_getfree(bs) == 0) {
i = 1;
j = 0;
} else {
struct snd_dbuf *pb;
i = sndbuf_getready(bs);
pb = BUF_PARENT(c, b);
j = min(sndbuf_xbytes(sndbuf_getsize(pb), pb, bs),
sndbuf_getsize(bs));
pb = BUF_PARENT(c, b);
i = sndbuf_xbytes(sndbuf_getready(bs), bs, pb);
j = sndbuf_getbps(pb);
}
}
if (snd_verbose > 3 && SLIST_EMPTY(&c->children))
printf("%s: PCMDIR_%s (%s) threshold i=%d j=%d\n",
__func__,
(c->direction == PCMDIR_PLAY) ? "PLAY" : "REC",
printf("%s: %s (%s) threshold i=%d j=%d\n",
__func__, CHN_DIRSTR(c),
(c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware",
i, j);
}
@ -637,17 +655,20 @@ chn_start(struct pcm_channel *c, int force)
c->interrupts = 0;
c->xruns = 0;
if (c->direction == PCMDIR_PLAY && c->parentchannel == NULL) {
chn_wrfeed(c);
sndbuf_fillsilence(b);
if (snd_verbose > 3)
printf("%s: %s starting! (%s) (ready=%d "
"force=%d i=%d j=%d intrtimeout=%u)\n",
"force=%d i=%d j=%d intrtimeout=%u "
"latency=%dms)\n",
__func__,
(c->flags & CHN_F_HAS_VCHAN) ?
"VCHAN" : "HW",
(c->flags & CHN_F_CLOSING) ? "closing" :
"running",
sndbuf_getready(b),
force, i, j, c->timeout);
force, i, j, c->timeout,
(sndbuf_getsize(b) * 1000) /
(sndbuf_getbps(b) * sndbuf_getspd(b)));
}
chn_trigger(c, PCMTRIG_START);
return 0;
@ -677,20 +698,22 @@ chn_sync(struct pcm_channel *c, int threshold)
{
int ret, count, hcount, minflush, resid, residp;
struct snd_dbuf *b, *bs;
int syncdelay, blksz;
CHN_LOCKASSERT(c);
if (c->flags & (CHN_F_DEAD | CHN_F_ABORTING))
bs = c->bufsoft;
if ((c->flags & (CHN_F_DEAD | CHN_F_ABORTING)) ||
(threshold < 1 && sndbuf_getready(bs) < 1))
return 0;
if (c->direction != PCMDIR_PLAY)
return EINVAL;
bs = c->bufsoft;
/* if we haven't yet started and nothing is buffered, else start*/
if (!(c->flags & CHN_F_TRIGGERED)) {
if (sndbuf_getready(bs) > 0) {
if (CHN_STOPPED(c)) {
if (threshold > 0 || sndbuf_getready(bs) > 0) {
ret = chn_start(c, 1);
if (ret)
return ret;
@ -700,8 +723,22 @@ chn_sync(struct pcm_channel *c, int threshold)
b = BUF_PARENT(c, c->bufhard);
threshold += sndbuf_getready(b);
minflush = sndbuf_xbytes(threshold, b, bs);
minflush = threshold + sndbuf_xbytes(sndbuf_getready(b), b, bs);
syncdelay = chn_syncdelay;
if (syncdelay < 0 && (threshold > 0 || sndbuf_getready(bs) > 0))
minflush += sndbuf_xbytes(sndbuf_getsize(b), b, bs);
/*
* Append (0-1000) millisecond trailing buffer (if needed)
* for slower / high latency hardwares (notably USB audio)
* to avoid audible truncation.
*/
if (syncdelay > 0)
minflush += (sndbuf_getbps(bs) * sndbuf_getspd(bs) *
((syncdelay > 1000) ? 1000 : syncdelay)) / 1000;
minflush -= minflush % sndbuf_getbps(bs);
if (minflush > 0) {
@ -713,17 +750,32 @@ chn_sync(struct pcm_channel *c, int threshold)
resid = sndbuf_getready(bs);
residp = resid;
count = sndbuf_xbytes(minflush + resid, bs, b) / sndbuf_getblksz(b);
blksz = sndbuf_getblksz(b);
if (blksz < 1) {
printf("%s: WARNING: blksz < 1 ! maxsize=%d [%d/%d/%d]\n",
__func__, sndbuf_getmaxsize(b), sndbuf_getsize(b),
sndbuf_getblksz(b), sndbuf_getblkcnt(b));
if (sndbuf_getblkcnt(b) > 0)
blksz = sndbuf_getsize(b) / sndbuf_getblkcnt(b);
if (blksz < 1)
blksz = 1;
}
count = sndbuf_xbytes(minflush + resid, bs, b) / blksz;
hcount = count;
ret = 0;
while (count > 0 && resid > 0) {
if (snd_verbose > 3)
printf("%s: [begin] timeout=%d count=%d "
"minflush=%d resid=%d\n", __func__, c->timeout, count,
minflush, resid);
while (count > 0 && (resid > 0 || minflush > 0)) {
ret = chn_sleep(c, "pcmsyn", c->timeout);
if (ret == ERESTART || ret == EINTR) {
c->flags |= CHN_F_ABORTING;
break;
}
if (ret == 0 || ret == EWOULDBLOCK) {
if (ret == 0 || ret == EAGAIN) {
resid = sndbuf_getready(bs);
if (resid == residp) {
--count;
@ -771,7 +823,7 @@ chn_poll(struct pcm_channel *c, int ev, struct thread *td)
int ret;
CHN_LOCKASSERT(c);
if (!(c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_TRIGGERED))
if (!(c->flags & (CHN_F_MAPPED | CHN_F_TRIGGERED)))
chn_start(c, 1);
ret = 0;
if (chn_polltrigger(c) && chn_pollreset(c))
@ -795,7 +847,7 @@ chn_abort(struct pcm_channel *c)
struct snd_dbuf *bs = c->bufsoft;
CHN_LOCKASSERT(c);
if (!(c->flags & CHN_F_TRIGGERED))
if (CHN_STOPPED(c))
return 0;
c->flags |= CHN_F_ABORTING;
@ -805,7 +857,7 @@ chn_abort(struct pcm_channel *c)
sndbuf_setrun(b, 0);
if (!(c->flags & CHN_F_VIRTUAL))
chn_dmaupdate(c);
missing = sndbuf_getready(bs) + sndbuf_getready(b);
missing = sndbuf_getready(bs);
c->flags &= ~CHN_F_ABORTING;
return missing;
@ -1130,7 +1182,7 @@ chn_kill(struct pcm_channel *c)
struct snd_dbuf *b = c->bufhard;
struct snd_dbuf *bs = c->bufsoft;
if (c->flags & CHN_F_TRIGGERED)
if (CHN_STARTED(c))
chn_trigger(c, PCMTRIG_ABORT);
while (chn_removefeeder(c) == 0);
if (CHANNEL_FREE(c->methods, c->devinfo))
@ -1193,6 +1245,28 @@ round_pow2(u_int32_t v)
return ret;
}
static u_int32_t
round_blksz(u_int32_t v, int round)
{
u_int32_t ret, tmp;
if (round < 1)
round = 1;
ret = min(round_pow2(v), CHN_2NDBUFMAXSIZE >> 1);
if (ret > v && (ret >> 1) > 0 && (ret >> 1) >= ((v * 3) >> 2))
ret >>= 1;
tmp = ret - (ret % round);
while (tmp < 16 || tmp < round) {
ret <<= 1;
tmp = ret - (ret % round);
}
return ret;
}
/*
* 4Front call it DSP Policy, while we call it "Latency Profile". The idea
* is to keep 2nd buffer short so that it doesn't cause long queue during
@ -1285,19 +1359,18 @@ static int
chn_calclatency(int dir, int latency, int bps, u_int32_t datarate,
u_int32_t max, int *rblksz, int *rblkcnt)
{
u_int32_t bufsz;
int blksz, blkcnt;
static int pblkcnts[CHN_LATENCY_PROFILE_MAX+1][CHN_LATENCY_MAX+1] =
static int pblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] =
CHN_LATENCY_PBLKCNT_REF;
static int pbufszs[CHN_LATENCY_PROFILE_MAX+1][CHN_LATENCY_MAX+1] =
static int pbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] =
CHN_LATENCY_PBUFSZ_REF;
static int rblkcnts[CHN_LATENCY_PROFILE_MAX+1][CHN_LATENCY_MAX+1] =
static int rblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] =
CHN_LATENCY_RBLKCNT_REF;
static int rbufszs[CHN_LATENCY_PROFILE_MAX+1][CHN_LATENCY_MAX+1] =
static int rbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] =
CHN_LATENCY_RBUFSZ_REF;
u_int32_t bufsz;
int lprofile, blksz, blkcnt;
if (CHN_LATENCY_MIN != 0 || CHN_LATENCY_MAX != 10 ||
latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX ||
if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX ||
bps < 1 || datarate < 1 ||
!(dir == PCMDIR_PLAY || dir == PCMDIR_REC)) {
if (rblksz != NULL)
@ -1310,24 +1383,22 @@ chn_calclatency(int dir, int latency, int bps, u_int32_t datarate,
return CHN_2NDBUFMAXSIZE;
}
lprofile = chn_latency_profile;
if (dir == PCMDIR_PLAY) {
blkcnt = pblkcnts[chn_latency_profile][latency];
bufsz = pbufszs[chn_latency_profile][latency];
blkcnt = pblkcnts[lprofile][latency];
bufsz = pbufszs[lprofile][latency];
} else {
blkcnt = rblkcnts[chn_latency_profile][latency];
bufsz = rbufszs[chn_latency_profile][latency];
blkcnt = rblkcnts[lprofile][latency];
bufsz = rbufszs[lprofile][latency];
}
bufsz = snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF, datarate);
bufsz = round_pow2(snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF,
datarate));
if (bufsz > max)
bufsz = max;
if (bufsz < 32)
bufsz = 32;
blksz = bufsz >> blkcnt;
blksz -= blksz % bps;
while (blksz < 16 || blksz < bps)
blksz += bps;
while ((blksz << blkcnt) > bufsz && blkcnt > 1)
blkcnt--;
blksz = round_blksz(bufsz >> blkcnt, bps);
if (rblksz != NULL)
*rblksz = blksz;
if (rblkcnt != NULL)
@ -1336,22 +1407,17 @@ chn_calclatency(int dir, int latency, int bps, u_int32_t datarate,
return blksz << blkcnt;
}
/*
* Note that there is no strict requirement to align blksz to the
* nearest ^2, except for hardware CHANNEL_SETBLOCKSIZE. If the application
* trying to act smarter and requesting for specific blksz/blkcnt, so be it.
*/
static int
chn_resizebuf(struct pcm_channel *c, int latency,
int blkcnt, int blksz)
{
struct snd_dbuf *b, *bs, *pb;
int sblksz, sblkcnt, hblksz, limit = 1;
int sblksz, sblkcnt, hblksz, hblkcnt, limit = 1;
int ret;
CHN_LOCKASSERT(c);
if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED) ||
if ((c->flags & (CHN_F_MAPPED | CHN_F_TRIGGERED)) ||
!(c->direction == PCMDIR_PLAY || c->direction == PCMDIR_REC))
return EINVAL;
@ -1399,14 +1465,8 @@ chn_resizebuf(struct pcm_channel *c, int latency,
* defeat the purpose of having custom control. The least
* we can do is round it to the nearest ^2 and align it.
*/
sblksz = round_pow2(blksz);
sblksz -= sblksz % sndbuf_getbps(bs);
sblkcnt = blkcnt;
while (sblksz < 16 || sblksz < sndbuf_getbps(bs))
sblksz += sndbuf_getbps(bs);
if (snd_verbose > 3 && !(blksz == 0 || blkcnt == -1))
printf("%s: requested blksz=%d blkcnt=%d -> %d/%d\n",
__func__, blksz, blkcnt, sblksz, sblkcnt);
sblksz = round_blksz(blksz, sndbuf_getbps(bs));
sblkcnt = round_pow2(blkcnt);
limit = 0;
}
@ -1419,39 +1479,48 @@ chn_resizebuf(struct pcm_channel *c, int latency,
sndbuf_xbytes(sndbuf_getsize(pb), pb, bs) : 0;
c->timeout = c->parentchannel->timeout;
} else {
hblkcnt = 2;
if (c->flags & CHN_F_HAS_SIZE) {
hblksz = sndbuf_xbytes(sblksz, bs, b);
if (snd_verbose > 3)
printf("%s: sblksz=%d -> hblksz=%d\n",
__func__, sblksz, hblksz);
hblksz = round_blksz(sndbuf_xbytes(sblksz, bs, b),
sndbuf_getbps(b));
hblkcnt = round_pow2(sndbuf_getblkcnt(bs));
} else
chn_calclatency(c->direction, latency,
sndbuf_getbps(b),
sndbuf_getbps(b) * sndbuf_getspd(b),
CHN_2NDBUFMAXSIZE, &hblksz, NULL);
CHN_2NDBUFMAXSIZE, &hblksz, &hblkcnt);
hblksz = round_pow2(hblksz);
if ((hblksz << 1) > sndbuf_getmaxsize(b))
hblksz = sndbuf_getmaxsize(b) >> 1;
hblksz = round_blksz(sndbuf_getmaxsize(b) >> 1,
sndbuf_getbps(b));
while ((hblksz * hblkcnt) > sndbuf_getmaxsize(b)) {
if (hblkcnt < 4)
hblksz >>= 1;
else
hblkcnt >>= 1;
}
hblksz -= hblksz % sndbuf_getbps(b);
while (hblksz < 16 || hblksz < sndbuf_getbps(b))
hblksz += sndbuf_getbps(b);
#if 0
hblksz = sndbuf_getmaxsize(b) >> 1;
hblksz -= hblksz % sndbuf_getbps(b);
hblkcnt = 2;
#endif
CHN_UNLOCK(c);
sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods,
c->devinfo, hblksz));
if (chn_usefrags == 0 ||
CHANNEL_SETFRAGMENTS(c->methods, c->devinfo,
hblksz, hblkcnt) < 1)
sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods,
c->devinfo, hblksz));
CHN_LOCK(c);
if (!SLIST_EMPTY(&c->children)) {
/*
* Virtual channels underneath. Set the biggest
* possible value for their mixing space.
*/
sblksz = CHN_2NDBUFMAXSIZE >> 1;
sblksz -= sblksz % sndbuf_getbps(bs);
sblksz = round_blksz(
sndbuf_xbytes(sndbuf_getsize(b) >> 1, b, bs),
sndbuf_getbps(bs));
sblkcnt = 2;
limit = 0;
} else if (limit != 0)
@ -1460,24 +1529,34 @@ chn_resizebuf(struct pcm_channel *c, int latency,
/*
* Interrupt timeout
*/
c->timeout = ((u_int64_t)hz * sndbuf_getblksz(b)) /
c->timeout = ((u_int64_t)hz * sndbuf_getsize(b)) /
((u_int64_t)sndbuf_getspd(b) * sndbuf_getbps(b));
if (c->timeout < 1)
c->timeout = 1;
c->timeout <<= 1;
}
if (limit > CHN_2NDBUFMAXSIZE)
limit = CHN_2NDBUFMAXSIZE;
hblksz = sblksz;
while ((sblksz * sblkcnt) < limit) {
sblksz += hblksz;
if ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) {
sblksz -= hblksz;
#if 0
while (limit > 0 && (sblksz * sblkcnt) > limit) {
if (sblkcnt < 4)
break;
}
sblkcnt >>= 1;
}
#endif
while ((sblksz * sblkcnt) < limit)
sblkcnt <<= 1;
while ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) {
if (sblkcnt < 4)
sblksz >>= 1;
else
sblkcnt >>= 1;
}
sblksz -= sblksz % sndbuf_getbps(bs);
if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz ||
sndbuf_getsize(bs) != (sblkcnt * sblksz)) {
@ -1497,9 +1576,9 @@ chn_resizebuf(struct pcm_channel *c, int latency,
chn_resetbuf(c);
if (snd_verbose > 3)
printf("%s: PCMDIR_%s (%s) timeout=%u "
printf("%s: %s (%s) timeout=%u "
"b[%d/%d/%d] bs[%d/%d/%d] limit=%d\n",
__func__, (c->direction == PCMDIR_REC) ? "REC" : "PLAY",
__func__, CHN_DIRSTR(c),
(c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware",
c->timeout,
sndbuf_getsize(b), sndbuf_getblksz(b),
@ -1540,7 +1619,7 @@ chn_tryspeed(struct pcm_channel *c, int speed)
DEB(printf("want speed %d, ", speed));
if (speed <= 0)
return EINVAL;
if (CANCHANGE(c)) {
if (CHN_STOPPED(c)) {
r = 0;
c->speed = speed;
sndbuf_setspd(bs, speed);
@ -1622,7 +1701,7 @@ chn_tryformat(struct pcm_channel *c, u_int32_t fmt)
int r;
CHN_LOCKASSERT(c);
if (CANCHANGE(c)) {
if (CHN_STOPPED(c)) {
DEB(printf("want format %d\n", fmt));
c->format = fmt;
r = chn_buildfeeder(c);
@ -1702,7 +1781,7 @@ chn_getptr(struct pcm_channel *c)
int hwptr;
CHN_LOCKASSERT(c);
hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0;
hwptr = (CHN_STARTED(c)) ? CHANNEL_GETPTR(c->methods, c->devinfo) : 0;
return (hwptr - (hwptr % sndbuf_getbps(c->bufhard)));
}
@ -1744,7 +1823,8 @@ chn_buildfeeder(struct pcm_channel *c)
char fmtstr[AFMTSTR_MAXSZ];
CHN_LOCKASSERT(c);
while (chn_removefeeder(c) == 0);
while (chn_removefeeder(c) == 0)
;
KASSERT((c->feeder == NULL), ("feeder chain not empty"));
c->align = sndbuf_getalign(c->bufsoft);
@ -1786,10 +1866,16 @@ chn_buildfeeder(struct pcm_channel *c)
}
c->feederflags &= ~(1 << FEEDER_VOLUME);
if (c->direction == PCMDIR_PLAY &&
!(c->flags & CHN_F_VIRTUAL) &&
c->parentsnddev && (c->parentsnddev->flags & SD_F_SOFTPCMVOL) &&
c->parentsnddev->mixer_dev)
!(c->flags & CHN_F_VIRTUAL) && c->parentsnddev &&
(c->parentsnddev->flags & SD_F_SOFTPCMVOL) &&
c->parentsnddev->mixer_dev)
c->feederflags |= 1 << FEEDER_VOLUME;
if (!(c->flags & CHN_F_VIRTUAL) && c->parentsnddev &&
((c->direction == PCMDIR_PLAY &&
(c->parentsnddev->flags & SD_F_PSWAPLR)) ||
(c->direction == PCMDIR_REC &&
(c->parentsnddev->flags & SD_F_RSWAPLR))))
c->feederflags |= 1 << FEEDER_SWAPLR;
flags = c->feederflags;
fmtlist = chn_getcaps(c)->fmtlist;
@ -1830,14 +1916,13 @@ chn_buildfeeder(struct pcm_channel *c)
c->feeder->desc->out & AFMT_STEREO)
desc.in |= AFMT_STEREO;
desc.out = desc.in;
fc = feeder_getclass(&desc);
if (fc != NULL && fc->desc != NULL)
desc.flags = fc->desc->flags;
} else {
fc = feeder_getclass(&desc);
if (fc != NULL && fc->desc != NULL)
desc = *fc->desc;
} else if (type == FEEDER_SWAPLR) {
desc.in = c->feeder->desc->out;
desc.in |= AFMT_STEREO;
desc.out = desc.in;
}
fc = feeder_getclass(&desc);
DEB(printf("got %p\n", fc));
if (fc == NULL) {
DEB(printf("can't find required feeder type %d\n", type));
@ -1845,6 +1930,9 @@ chn_buildfeeder(struct pcm_channel *c)
return EOPNOTSUPP;
}
if (desc.in == 0 || desc.out == 0)
desc = *fc->desc;
DEB(printf("build fmtchain from 0x%08x to 0x%08x: ", c->feeder->desc->out, fc->desc->in));
tmp[0] = desc.in;
tmp[1] = 0;
@ -1932,7 +2020,7 @@ chn_notify(struct pcm_channel *c, u_int32_t flags)
return ENODEV;
}
run = (c->flags & CHN_F_TRIGGERED)? 1 : 0;
run = (CHN_STARTED(c)) ? 1 : 0;
/*
* if the hwchan is running, we can't change its rate, format or
* blocksize
@ -1974,9 +2062,10 @@ chn_notify(struct pcm_channel *c, u_int32_t flags)
SLIST_FOREACH(pce, &c->children, link) {
child = pce->channel;
CHN_LOCK(child);
if (child->flags & CHN_F_TRIGGERED)
nrun = 1;
nrun = CHN_STARTED(child);
CHN_UNLOCK(child);
if (nrun)
break;
}
if (nrun && !run)
chn_start(c, 1);

View File

@ -83,6 +83,12 @@ CODE {
return 0;
}
static int
channel_nosetfragments(kobj_t obj, void *data, u_int32_t blocksize, u_int32_t blockcount)
{
return 0;
}
};
METHOD void* init {
@ -132,6 +138,13 @@ METHOD u_int32_t setblocksize {
u_int32_t blocksize;
};
METHOD int setfragments {
kobj_t obj;
void *data;
u_int32_t blocksize;
u_int32_t blockcount;
} DEFAULT channel_nosetfragments;
METHOD int trigger {
kobj_t obj;
void *data;

File diff suppressed because it is too large Load Diff