summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexandre Ratchov <ratchov@cvs.openbsd.org>2015-07-28 20:57:36 +0000
committerAlexandre Ratchov <ratchov@cvs.openbsd.org>2015-07-28 20:57:36 +0000
commitefc7d38347b9194ff8af288924839b962ad6b835 (patch)
treee65c38415df707b29631bd3f3d9d884501041794
parente4bc51d0345ab16708f689c5c2dcff4ce697e17a (diff)
In case the system misses enough audio interrupts for DMA
pointers to wrap, recover by detecting and compensating for the missed interrupts. Fixes certain audio hangs on MP machines. with help from armani@, typos fixed by Alexey Suslikov
-rw-r--r--sys/dev/audio.c49
1 files changed, 46 insertions, 3 deletions
diff --git a/sys/dev/audio.c b/sys/dev/audio.c
index 29550f2be88..61da7fed269 100644
--- a/sys/dev/audio.c
+++ b/sys/dev/audio.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: audio.c,v 1.135 2015/07/28 20:45:02 ratchov Exp $ */
+/* $OpenBSD: audio.c,v 1.136 2015/07/28 20:57:35 ratchov Exp $ */
/*
* Copyright (c) 2015 Alexandre Ratchov <alex@caoua.org>
*
@@ -106,6 +106,7 @@ struct audio_softc {
unsigned char silence[4]; /* a sample of silence */
int pause; /* not trying to start DMA */
int active; /* DMA in process */
+ int offs; /* offset between play & rec dir */
void (*conv_enc)(unsigned char *, int); /* encode to native */
void (*conv_dec)(unsigned char *, int); /* decode to user */
#if NWSKBD > 0
@@ -348,7 +349,7 @@ audio_pintr(void *addr)
struct audio_softc *sc = addr;
unsigned char *ptr;
size_t count;
- int error;
+ int error, nblk, todo;
MUTEX_ASSERT_LOCKED(&audio_lock);
if (!(sc->mode & AUMODE_PLAY) || !sc->active) {
@@ -360,6 +361,23 @@ audio_pintr(void *addr)
return;
}
+ /*
+ * check if record pointer wrapped, see explanation
+ * in audio_rintr()
+ */
+ if (sc->mode & AUMODE_RECORD) {
+ sc->offs--;
+ nblk = sc->rec.len / sc->rec.blksz;
+ todo = -sc->offs;
+ if (todo >= nblk) {
+ todo -= todo % nblk;
+ DPRINTFN(1, "%s: rec ptr wrapped, moving %d blocks\n",
+ DEVNAME(sc), todo);
+ while (todo-- > 0)
+ audio_rintr(sc);
+ }
+ }
+
sc->play.pos += sc->play.blksz;
audio_fill_sil(sc, sc->play.data + sc->play.start, sc->play.blksz);
audio_buf_rdiscard(&sc->play, sc->play.blksz);
@@ -402,7 +420,7 @@ audio_rintr(void *addr)
struct audio_softc *sc = addr;
unsigned char *ptr;
size_t count;
- int error;
+ int error, nblk, todo;
MUTEX_ASSERT_LOCKED(&audio_lock);
if (!(sc->mode & AUMODE_RECORD) || !sc->active) {
@@ -414,6 +432,30 @@ audio_rintr(void *addr)
return;
}
+ /*
+ * Interrupts may be masked by other sub-systems during 320ms
+ * and more. During such a delay the hardware doesn't stop
+ * playing and the play buffer pointers may wrap, this can't be
+ * detected and corrected by low level drivers. This makes the
+ * record stream ahead of the play stream; this is detected as a
+ * hardware anomaly by userland and cause programs to misbehave.
+ *
+ * We fix this by advancing play position by an integer count of
+ * full buffers, so it reaches the record position.
+ */
+ if (sc->mode & AUMODE_PLAY) {
+ sc->offs++;
+ nblk = sc->play.len / sc->play.blksz;
+ todo = sc->offs;
+ if (todo >= nblk) {
+ todo -= todo % nblk;
+ DPRINTFN(1, "%s: play ptr wrapped, moving %d blocks\n",
+ DEVNAME(sc), todo);
+ while (todo-- > 0)
+ audio_pintr(sc);
+ }
+ }
+
sc->rec.pos += sc->rec.blksz;
audio_buf_wcommit(&sc->rec, sc->rec.blksz);
if (sc->rec.used == sc->rec.len) {
@@ -464,6 +506,7 @@ audio_start_do(struct audio_softc *sc)
sc->rec.len, sc->rec.blksz);
error = 0;
+ sc->offs = 0;
if (sc->mode & AUMODE_PLAY) {
if (sc->ops->trigger_output) {
p.encoding = sc->hw_enc;