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

Reply via email to