Subject: Re: gus driver will hang with new audio patches
To: None <gnats-bugs@NetBSD.ORG, current-users@NetBSD.ORG>
From: John Kohl <jtk@kolvir.arlington.ma.us>
List: current-users
Date: 02/10/1996 18:21:19
I found and fixed the problem with the latest audio changes causing the
GUS driver to loop.  I'm happy to say now that I can get uninterrupted
44kHz x stereo x 16 bit playback on my machine!

The root cause is that the GUS driver uses on-board memory as an
additional buffer and does its own buffer admission rate control.  The
GUS driver can get into a situation where it doesn't acknowledge a
buffer from the audio layer until it has room for it on the card.  Due
to timing races, the audio layer may end up thinking the buffer it's
currently got from the user should be played again.

The fix is for the audio layer to keep track of which buffers were real
and which were silence buffers.  The simplest solution I came up with to
this is to use separate DMA callback routines for silence and
non-silence output.  The sc->sc_silence flag becomes unnecessary.

Herewith, my patches.

===================================================================
RCS file: RCS/audio.c,v
retrieving revision 1.1.1.4
diff -c -r1.1.1.4 audio.c
*** audio.c	1996/02/09 00:11:58	1.1.1.4
--- audio.c	1996/02/10 23:20:47
***************
*** 139,145 ****
  void	audiostartr __P((struct audio_softc *));
  void	audiostartp __P((struct audio_softc *));
  void	audio_rint __P((struct audio_softc *));
! void	audio_pint __P((struct audio_softc *));
  
  int	audio_calc_blksize __P((struct audio_softc *));
  void	audio_silence_fill __P((struct audio_softc *, u_char *, int));
--- 139,147 ----
  void	audiostartr __P((struct audio_softc *));
  void	audiostartp __P((struct audio_softc *));
  void	audio_rint __P((struct audio_softc *));
! void	audio_pint __P((struct audio_softc *, int));
! void	audio_rpint __P((struct audio_softc *));
! void	audio_spint __P((struct audio_softc *));
  
  int	audio_calc_blksize __P((struct audio_softc *));
  void	audio_silence_fill __P((struct audio_softc *, u_char *, int));
***************
*** 1222,1230 ****
      
  	if (sc->pr.nblk > 0) {
  		u_char *hp = sc->pr.hp;
- 		sc->sc_silence = 0;
  		if (rval = sc->hw_if->start_output(sc->hw_hdl, hp, sc->sc_blksize,
! 		    				   audio_pint, (void *)sc)) {
  		    	DPRINTF(("audiostartp: failed: %d\n", rval));
  		}
  		else {
--- 1228,1235 ----
      
  	if (sc->pr.nblk > 0) {
  		u_char *hp = sc->pr.hp;
  		if (rval = sc->hw_if->start_output(sc->hw_hdl, hp, sc->sc_blksize,
! 		    				   audio_rpint, (void *)sc)) {
  		    	DPRINTF(("audiostartp: failed: %d\n", rval));
  		}
  		else {
***************
*** 1238,1251 ****
  }
  
  /*
   * Called from HW driver module on completion of dma output.
   * Start output of new block, wrap in ring buffer if needed.
   * If no more buffers to play, output zero instead.
   * Do a wakeup if necessary.
   */
  void
! audio_pint(sc)
  	struct audio_softc *sc;
  {
  	u_char *hp;
  	int cc = sc->sc_blksize;
--- 1243,1278 ----
  }
  
  /*
+  * Use this routine as DMA callback if we played user data.  We need to
+  * account for user data and silence separately.
+  */
+ void
+ audio_rpint(sc)
+ 	struct audio_softc *sc;
+ {
+ 	audio_pint(sc, 0);		/* 'twas a real audio block */
+ }
+ 
+ /*
+  * Use this routine as DMA callback if we played silence.
+  */
+ void
+ audio_spint(sc)
+ 	struct audio_softc *sc;
+ {
+ 	audio_pint(sc, 1);		/* 'twas a silence block */
+ }
+ 
+ /*
   * Called from HW driver module on completion of dma output.
   * Start output of new block, wrap in ring buffer if needed.
   * If no more buffers to play, output zero instead.
   * Do a wakeup if necessary.
   */
  void
! audio_pint(sc, silence)
  	struct audio_softc *sc;
+ 	int silence;
  {
  	u_char *hp;
  	int cc = sc->sc_blksize;
***************
*** 1259,1265 ****
  	 * always fails and the output is always silence after the
  	 * first block.
  	 */
! 	if (!sc->sc_silence)
  		cb->nblk--;
  	if (cb->nblk > 0) {
  		hp = cb->hp;
--- 1286,1292 ----
  	 * always fails and the output is always silence after the
  	 * first block.
  	 */
! 	if (!silence)
  		cb->nblk--;
  	if (cb->nblk > 0) {
  		hp = cb->hp;
***************
*** 1277,1283 ****
  		    	Dprintf("audio_pint: hp=0x%x cc=%d\n", hp, cc);
  #endif
  		    if (err = hw->start_output(sc->hw_hdl, hp, cc,
! 					       audio_pint, (void *)sc)) {
  			    DPRINTF(("audio_pint restart failed: %d\n", err));
  			    audio_clear(sc);
  		    }
--- 1304,1310 ----
  		    	Dprintf("audio_pint: hp=0x%x cc=%d\n", hp, cc);
  #endif
  		    if (err = hw->start_output(sc->hw_hdl, hp, cc,
! 					       audio_rpint, (void *)sc)) {
  			    DPRINTF(("audio_pint restart failed: %d\n", err));
  			    audio_clear(sc);
  		    }
***************
*** 1299,1308 ****
  		    Dprintf("audio_pint: drops=%d auzero %d 0x%x\n", cb->cb_drops, cc, *(int *)auzero_block);
  #endif
   psilence:
- 		sc->sc_silence = 1;
  		if (err = hw->start_output(sc->hw_hdl,
  		    			   auzero_block, cc,
! 					   audio_pint, (void *)sc)) {
  			DPRINTF(("audio_pint zero failed: %d\n", err));
  			audio_clear(sc);
  		}
--- 1326,1334 ----
  		    Dprintf("audio_pint: drops=%d auzero %d 0x%x\n", cb->cb_drops, cc, *(int *)auzero_block);
  #endif
   psilence:
  		if (err = hw->start_output(sc->hw_hdl,
  		    			   auzero_block, cc,
! 					   audio_spint, (void *)sc)) {
  			DPRINTF(("audio_pint zero failed: %d\n", err));
  			audio_clear(sc);
  		}
===================================================================
RCS file: RCS/audiovar.h,v
retrieving revision 1.1
diff -c -r1.1 audiovar.h
*** audiovar.h	1996/02/10 21:40:52	1.1
--- audiovar.h	1996/02/10 22:47:46
***************
*** 118,122 ****
  	int	sc_rblks;	/* number of phantom record blocks */
  	int	sc_pencoding;	/* current encoding; play */
  	int	sc_rencoding;	/* current encoding; record */
- 	int	sc_silence;	/* currently outputting audio_zero */
  };
--- 118,121 ----