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.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;

Reply via email to