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