Re: audio: recover after missed interrupts
On Mon, Jul 27, 2015 at 07:51:15PM +0200, Alexandre Ratchov wrote: Sometimes the system may miss enough audio interrupts for DMA pointers to wrap, which makes upper layers misbahave. This diff makes the audio driver properly recover, by detecting and compensating for the missed interrupts. This requires the hardware to expose working DMA pointers and the low-level driver to properly invoke the mid-layer call-back accordingly (ex. azalia does it). This should fix most cases of audio programs stopping during heavy system load or VT switching. To test this, please install the latest in-tree libsndio and sndiod first. OK? For what it's worth, this seems to fix the two cases of stopped audio I was seeing. -ml --- sys/dev/audio.c.orig Thu Jul 23 22:20:38 2015 +++ sys/dev/audio.c Thu Jul 23 22:37:32 2015 @@ -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 blocs\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 blocs\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;
Re: audio: recover after missed interrupts
Alexandre Ratchov alex at caoua.org writes: DPRINTFN(1, %s: rec ptr wrapped, moving %d blocs\n, DPRINTFN(1, %s: play ptr wrapped, moving %d blocs\n, blocs in above DPRINTFNs should be blocks, I think.
audio: recover after missed interrupts
Sometimes the system may miss enough audio interrupts for DMA pointers to wrap, which makes upper layers misbahave. This diff makes the audio driver properly recover, by detecting and compensating for the missed interrupts. This requires the hardware to expose working DMA pointers and the low-level driver to properly invoke the mid-layer call-back accordingly (ex. azalia does it). This should fix most cases of audio programs stopping during heavy system load or VT switching. To test this, please install the latest in-tree libsndio and sndiod first. OK? --- sys/dev/audio.c.origThu Jul 23 22:20:38 2015 +++ sys/dev/audio.c Thu Jul 23 22:37:32 2015 @@ -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 blocs\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 blocs\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;