diff options
author | Alexandre Ratchov <ratchov@cvs.openbsd.org> | 2015-07-28 20:57:36 +0000 |
---|---|---|
committer | Alexandre Ratchov <ratchov@cvs.openbsd.org> | 2015-07-28 20:57:36 +0000 |
commit | efc7d38347b9194ff8af288924839b962ad6b835 (patch) | |
tree | e65c38415df707b29631bd3f3d9d884501041794 | |
parent | e4bc51d0345ab16708f689c5c2dcff4ce697e17a (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.c | 49 |
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; |