SDL-1.2: atari:audio:xbios: add support for gsxb'a NSetinterrupt

From d0895c3650208b70c0e84c56e469ec25bdb012e5 Mon Sep 17 00:00:00 2001
From: Miro Kropacek <[EMAIL REDACTED]>
Date: Tue, 4 Aug 2026 21:03:30 +1000
Subject: [PATCH] atari:audio:xbios: add support for gsxb'a NSetinterrupt

---
 src/audio/mint/SDL_mintaudio.h       |  3 +
 src/audio/mint/SDL_mintaudio_it.S    | 39 ++++++++++---
 src/audio/mint/SDL_mintaudio_xbios.c | 82 +++++++++++++++++++++++++---
 3 files changed, 109 insertions(+), 15 deletions(-)

diff --git a/src/audio/mint/SDL_mintaudio.h b/src/audio/mint/SDL_mintaudio.h
index 57129bc8..4e5c0775 100644
--- a/src/audio/mint/SDL_mintaudio.h
+++ b/src/audio/mint/SDL_mintaudio.h
@@ -116,5 +116,8 @@ extern volatile unsigned long SDL_MintAudio_itsilence;
 /* Functions */
 void SDL_MintAudio_XbiosInterrupt(void);
 void SDL_MintAudio_Dma8Interrupt(void);
+#if !defined(__mcoldfire__)
+void SDL_MintAudio_GsxbInterrupt(void);
+#endif
 
 #endif /* _SDL_mintaudio_h */
diff --git a/src/audio/mint/SDL_mintaudio_it.S b/src/audio/mint/SDL_mintaudio_it.S
index 9a1dd7df..7462b951 100644
--- a/src/audio/mint/SDL_mintaudio_it.S
+++ b/src/audio/mint/SDL_mintaudio_it.S
@@ -32,6 +32,9 @@
 
 	.globl	SYM(SDL_MintAudio_XbiosInterrupt)
 	.globl	SYM(SDL_MintAudio_Dma8Interrupt)
+#if !defined(__mcoldfire__)
+	.globl	SYM(SDL_MintAudio_GsxbInterrupt)
+#endif
 
 	.globl	SYM(SDL_MintAudio_num_its)
 
@@ -59,10 +62,36 @@ SYM(SDL_MintAudio_Dma8Interrupt):
 	bclr	#5,0xfffffa0f:w
 #endif
 
+	bsr.s	countinterrupt
+
+#if defined(__mcoldfire__)
+	moveml	sp@,d0-d1/a0
+	lea	sp@(12),sp
+#else
+	moveml	sp@+,d0-d1/a0
+#endif
+	rte
+
+/*    GSXB: not an exception handler. NSetinterrupt() hands this address to
+      the sound card driver, which calls it as a subroutine in supervisor
+      mode on every end of frame, with flags in d0. It must end with rts and
+      no MFP is involved at all. No ColdFire machine implements GSXB. */
+
+#if !defined(__mcoldfire__)
+SYM(SDL_MintAudio_GsxbInterrupt):
+	moveml	d0-d1/a0,sp@-
+
+	bsr.s	countinterrupt
+
+	moveml	sp@+,d0-d1/a0
+	rts
+#endif
+
+countinterrupt:
 	move.l	SYM(SDL_MintAudio_num_its),d0
 	addql	#1,d0
 	move.l	d0,SYM(SDL_MintAudio_num_its)
- 
+
 	cmp.l	#5,d0
 	bmi.s	nosilence
 
@@ -78,13 +107,7 @@ fillsilence:
 	bpl.s	fillsilence
 
 nosilence:
-#if defined(__mcoldfire__)
-	moveml	sp@,d0-d1/a0
-	lea	sp@(12),sp
-#else
-	moveml	sp@+,d0-d1/a0
-#endif
-	rte
+	rts
 
 	.bss
 
diff --git a/src/audio/mint/SDL_mintaudio_xbios.c b/src/audio/mint/SDL_mintaudio_xbios.c
index cf422774..2e1b5df2 100644
--- a/src/audio/mint/SDL_mintaudio_xbios.c
+++ b/src/audio/mint/SDL_mintaudio_xbios.c
@@ -48,6 +48,13 @@
 
 #define MINT_AUDIO_DRIVER_NAME "xbios"
 
+/* GSXB extends Setinterrupt() with a third parameter, the address of a
+   routine to be called at the end of a frame. It is the only mode GSXB
+   supports: the Falcon's Timer A and MFP i7 end of frame interrupts are
+   rejected with EBADR because they simply do not exist on some of the
+   machines GSXB drives (the Hades, for instance). */
+#define SI_GSXB	2	/* Install user End-Of-Frame interrupt function */
+
 /* Debug print info */
 #define DEBUG_NAME "audio:xbios: "
 #if 0
@@ -67,11 +74,23 @@ static void Mint_LockAudio(_THIS);
 static void Mint_UnlockAudio(_THIS);
 static void Mint_SwapBuffers(Uint8 *nextbuf, int nextsize);
 
+#if !defined(__mcoldfire__)
+/* GSXB callback */
+static void Mint_GsxbNullInterrupt(void);
+#endif
+
 /*--- Variables ---*/
 
 /* uSound state, shared between Mint_OpenAudio() and Mint_CloseAudio() */
 static USoundContext usound_context;
 
+/* Do we use GSXB's own end of frame callback instead of Timer A ? */
+#if defined(__mcoldfire__)
+#define use_gsxb 0	/* no ColdFire machine implements GSXB */
+#else
+static int use_gsxb;
+#endif
+
 /*--- Format conversion helpers (SDL <-> uSound) ---*/
 
 static int Mint_FormatToUSound(Uint16 sdl_format, USoundFormat *usound_format)
@@ -102,6 +121,34 @@ static int Mint_FormatFromUSound(USoundFormat usound_format, Uint16 *sdl_format)
 	return 1;
 }
 
+/*--- GSXB ---*/
+
+#if !defined(__mcoldfire__)
+
+/* SI_GSXB is documented as available when bits 2 and 5 of the _SND cookie
+   are set *and* the GSXB cookie is present. Both halves matter: GSXB sets
+   those bits only once a sound card driver has installed itself under it,
+   and MilanBlaster/MagicMac set bit 5 as well but do not necessarily
+   implement the extended call. */
+static int Mint_GsxbAvailable(void)
+{
+	long cookie;
+
+	if (Getcookie(C_GSXB, &cookie) != C_FOUND) {
+		return(0);
+	}
+	if (Getcookie(C__SND, &cookie) != C_FOUND) {
+		return(0);
+	}
+	return((cookie & (SND_16BIT|SND_EXT)) == (SND_16BIT|SND_EXT));
+}
+
+static void Mint_GsxbNullInterrupt(void)
+{
+}
+
+#endif /* !__mcoldfire__ */
+
 /*--- Audio driver bootstrap functions ---*/
 
 static int Audio_Available(void)
@@ -182,7 +229,14 @@ static void Mint_CloseAudio(_THIS)
 	Buffoper(0);
 
 	/* Uninstall interrupt */
-	Jdisint(MFP_DMASOUND);
+#if !defined(__mcoldfire__)
+	if (use_gsxb) {
+		NSetinterrupt(SI_GSXB, SI_NONE, Mint_GsxbNullInterrupt);
+	}
+#endif
+	if (!use_gsxb) {
+		Jdisint(MFP_DMASOUND);
+	}
 
 	/* Restore and unlock the sound system */
 	USoundDeinitXbios(&usound_context);
@@ -246,12 +300,26 @@ static int Mint_OpenAudio(_THIS, SDL_AudioSpec *spec)
 	Mint_SwapBuffers(MINTAUDIO_audiobuf[0], MINTAUDIO_audiosize);
 
 	/* Install interrupt */
-	Jdisint(MFP_DMASOUND);
-	Xbtimer(XB_TIMERA, 8, 1, SDL_MintAudio_XbiosInterrupt);
-	Jenabint(MFP_DMASOUND);
-
-	if (Setinterrupt(SI_TIMERA, SI_PLAY)<0) {
-		DEBUG_PRINT((DEBUG_NAME "Setinterrupt() failed\n"));
+#if !defined(__mcoldfire__)
+	use_gsxb = Mint_GsxbAvailable();
+	if (use_gsxb) {
+		if (NSetinterrupt(SI_GSXB, SI_PLAY, SDL_MintAudio_GsxbInterrupt)<0) {
+			SDL_SetError("Mint_OpenAudio: GSXB refused the end of frame interrupt");
+			SDL_MintAudio_FreeBuffers();
+			USoundDeinitXbios(&usound_context);
+			SDL_MintAudio_device = NULL;
+			return(-1);
+		}
+	}
+#endif
+	if (!use_gsxb) {
+		Jdisint(MFP_DMASOUND);
+		Xbtimer(XB_TIMERA, 8, 1, SDL_MintAudio_XbiosInterrupt);
+		Jenabint(MFP_DMASOUND);
+
+		if (Setinterrupt(SI_TIMERA, SI_PLAY)<0) {
+			DEBUG_PRINT((DEBUG_NAME "Setinterrupt() failed\n"));
+		}
 	}
 
 	/* Go */