Re: audio: recover after missed interrupts

2015-07-28 Thread Mike Larkin
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

2015-07-28 Thread Alexey Suslikov
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

2015-07-27 Thread Alexandre Ratchov
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;