-
-
Save root42/4c22e225ddd9092e4498d8942e208450 to your computer and use it in GitHub Desktop.
Fixing the IRQ handling of the SoundBlaster Auto-Init DMA Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "dos.h" | |
#include "stdio.h" | |
#include "stdlib.h" | |
#include "mem.h" | |
int sb_detect(); | |
void sb_init(); | |
void sb_deinit(); | |
void read_buffer(short buffer); | |
void single_cycle_playback(); | |
short sb_base; /* default 220h */ | |
char sb_irq; /* default 7 */ | |
char sb_dma; /* default 1 */ | |
void interrupt ( *old_irq )(); | |
FILE *raw_file; | |
long file_size; | |
volatile char do_read; | |
volatile short r_buffer; /* Read buffer indicator */ | |
volatile long to_be_read; | |
volatile int playing; | |
volatile long to_be_played; | |
unsigned char *dma_buffer; | |
short page; | |
short offset; | |
#define DMA_BLOCK_LENGTH 0x7FFF | |
#define SB_BLOCK_LENGTH 0x3FFF | |
#define SB_RESET 0x6 | |
#define SB_READ_DATA 0xA | |
#define SB_READ_DATA_STATUS 0xE | |
#define SB_WRITE_DATA 0xC | |
#define SB_SET_BLOCK_SIZE 0x48 | |
#define SB_SINGLE_CYCLE_PLAYBACK 0x14 | |
#define SB_START_AUTOINIT_PLAYBACK 0x1C | |
#define SB_STOP_AUTOINIT_PLAYBACK 0xDA | |
#define SB_SET_PLAYBACK_FREQUENCY 0x40 | |
#define SB_PAUSE_PLAYBACK 0xD0 | |
#define SB_ENABLE_SPEAKER 0xD1 | |
#define SB_DISABLE_SPEAKER 0xD3 | |
#define MASK_REGISTER 0x0A | |
#define MODE_REGISTER 0x0B | |
#define MSB_LSB_FLIP_FLOP 0x0C | |
#define DMA_CHANNEL_0 0x87 | |
#define DMA_CHANNEL_1 0x83 | |
#define DMA_CHANNEL_3 0x82 | |
int reset_dsp(short port) | |
{ | |
outportb( port + SB_RESET, 1 ); | |
delay(10); | |
outportb( port + SB_RESET, 0 ); | |
delay(10); | |
if( ((inportb(port + SB_READ_DATA_STATUS) & 0x80) == 0x80) | |
&& (inportb(port + SB_READ_DATA) == 0x0AA ) | |
) { | |
sb_base = port; | |
return 1; | |
} | |
return 0; | |
} | |
void write_dsp( unsigned char command ) | |
{ | |
while( (inportb( sb_base + SB_WRITE_DATA ) & 0x80) == 0x80 ); | |
outportb( sb_base + SB_WRITE_DATA, command ); | |
} | |
int sb_detect() | |
{ | |
char temp; | |
char *BLASTER; | |
sb_base = 0; | |
/* possible values: 210, 220, 230, 240, 250, 260, 280 */ | |
#if 0 | |
for( temp = 1; temp < 9; temp++ ) { | |
if( temp != 7 ) { | |
if( reset_dsp( 0x200 + (temp << 4) ) ) { | |
break; | |
} | |
} | |
} | |
if( temp == 9 ) { | |
return 0; | |
} | |
#endif | |
BLASTER = getenv("BLASTER"); | |
for( temp = 0; temp < strlen( BLASTER ); ++temp ) { | |
if((BLASTER[temp] | 32) == 'a') { | |
if( !reset_dsp( 0x200 + ( ( BLASTER[temp + 2] - '0' ) << 4) ) ) { | |
printf("Warning: DSP not detected.\n"); | |
} | |
} | |
} | |
sb_dma = 0; | |
for( temp = 0; temp < strlen( BLASTER ); ++temp ) { | |
if((BLASTER[temp] | 32) == 'd') { | |
sb_dma = BLASTER[temp + 1] - '0'; | |
} | |
} | |
for( temp = 0; temp < strlen( BLASTER ); ++temp ) { | |
if((BLASTER[temp] | 32) == 'i') { | |
sb_irq = BLASTER[temp + 1] - '0'; | |
if( BLASTER[temp + 2] != ' ' ) { | |
sb_irq = sb_irq * 10 + BLASTER[ temp + 2 ] - '0'; | |
} | |
} | |
} | |
return sb_base != 0; | |
} | |
void interrupt sb_irq_handler() | |
{ | |
disable(); | |
inportb(sb_base + SB_READ_DATA_STATUS); | |
outportb( 0x20, 0x20 ); | |
if( sb_irq == 2 || sb_irq == 10 || sb_irq == 11 ) { | |
outportb( 0xA0, 0x20 ); | |
} | |
if( playing ) { | |
to_be_played -= 16384; | |
if (to_be_played > 0) { | |
/* read_buffer (r_buffer); */ | |
do_read = 1; | |
if (to_be_played <= 16384) { | |
r_buffer ^= 1; | |
single_cycle_playback (); | |
} else if (to_be_played <= 32768) { | |
write_dsp(SB_STOP_AUTOINIT_PLAYBACK); | |
} | |
} else { | |
playing = 0; | |
} | |
} | |
r_buffer ^= 1; | |
enable(); | |
} | |
void init_irq() | |
{ | |
/* save the old irq vector */ | |
if( sb_irq == 2 || sb_irq == 10 || sb_irq == 11 ) { | |
if( sb_irq == 2) old_irq = getvect( 0x71 ); | |
if( sb_irq == 10) old_irq = getvect( 0x72 ); | |
if( sb_irq == 11) old_irq = getvect( 0x73 ); | |
} else { | |
old_irq = getvect( sb_irq + 8 ); | |
} | |
/* set our own irq vector */ | |
if( sb_irq == 2 || sb_irq == 10 || sb_irq == 11 ) { | |
if( sb_irq == 2) setvect( 0x71, sb_irq_handler ); | |
if( sb_irq == 10) setvect( 0x72, sb_irq_handler ); | |
if( sb_irq == 11) setvect( 0x73, sb_irq_handler ); | |
} else { | |
setvect( sb_irq + 8, sb_irq_handler ); | |
} | |
/* enable irq with the mainboard's PIC */ | |
if( sb_irq == 2 || sb_irq == 10 || sb_irq == 11 ) { | |
if( sb_irq == 2) outportb( 0xA1, inportb( 0xA1 ) & 253 ); | |
if( sb_irq == 10) outportb( 0xA1, inportb( 0xA1 ) & 251 ); | |
if( sb_irq == 11) outportb( 0xA1, inportb( 0xA1 ) & 247 ); | |
outportb( 0x21, inportb( 0x21 ) & 251 ); | |
} else { | |
outportb( 0x21, inportb( 0x21 ) & !(1 << sb_irq) ); | |
} | |
} | |
void deinit_irq() | |
{ | |
/* restore the old irq vector */ | |
if( sb_irq == 2 || sb_irq == 10 || sb_irq == 11 ) { | |
if( sb_irq == 2) setvect( 0x71, old_irq ); | |
if( sb_irq == 10) setvect( 0x72, old_irq ); | |
if( sb_irq == 11) setvect( 0x73, old_irq ); | |
} else { | |
setvect( sb_irq + 8, old_irq ); | |
} | |
/* enable irq with the mainboard's PIC */ | |
if( sb_irq == 2 || sb_irq == 10 || sb_irq == 11 ) { | |
if( sb_irq == 2) outportb( 0xA1, inportb( 0xA1 ) | 2 ); | |
if( sb_irq == 10) outportb( 0xA1, inportb( 0xA1 ) | 4 ); | |
if( sb_irq == 11) outportb( 0xA1, inportb( 0xA1 ) | 8 ); | |
outportb( 0x21, inportb( 0x21 ) | 4 ); | |
} else { | |
outportb( 0x21, inportb( 0x21 ) & (1 << sb_irq) ); | |
} | |
} | |
void assign_dma_buffer() | |
{ | |
unsigned char * temp_buf; | |
long linear_address; | |
short page1, page2; | |
temp_buf = (unsigned char *) malloc(32768); | |
linear_address = FP_SEG(temp_buf); | |
linear_address = (linear_address << 4)+FP_OFF(temp_buf); | |
page1 = linear_address >> 16; | |
page2 = (linear_address + 32767) >> 16; | |
if( page1 != page2 ) { | |
dma_buffer = (char *)malloc(32768); | |
free( temp_buf ); | |
} else { | |
dma_buffer = temp_buf; | |
} | |
linear_address = FP_SEG(dma_buffer); | |
linear_address = (linear_address << 4)+FP_OFF(dma_buffer); | |
page = linear_address >> 16; | |
offset = linear_address & 0xFFFF; | |
} | |
void read_buffer(short buffer) | |
{ | |
const int buf_off = buffer << 14; | |
/*If the remaining part of the file is smaller than 16K, */ | |
/*load it and fill out with silence */ | |
if (to_be_read <= 0) { | |
return; | |
} | |
if (to_be_read < 16384) { | |
memset (dma_buffer + buf_off, 128, 16384); | |
fread (dma_buffer + buf_off, 1, to_be_read, raw_file); | |
to_be_read = 0; | |
} else { | |
fread (dma_buffer + buf_off, 1, 16384, raw_file); | |
to_be_read -= 16384; | |
} | |
} | |
void sb_init() | |
{ | |
init_irq(); | |
assign_dma_buffer(); | |
write_dsp( SB_ENABLE_SPEAKER ); | |
} | |
void sb_deinit() | |
{ | |
write_dsp( SB_DISABLE_SPEAKER ); | |
free( dma_buffer ); | |
deinit_irq(); | |
} | |
void auto_init_playback () | |
{ | |
outportb(MASK_REGISTER, 4 | sb_dma); /*Mask DMA channel */ | |
outportb(MSB_LSB_FLIP_FLOP, 0); /*Clear byte pointer */ | |
outportb(MODE_REGISTER, 0x58 | sb_dma); /*Set mode */ | |
outportb(sb_dma << 1, offset & 0xFF); /*Write the offset to the DMA controller */ | |
outportb(sb_dma << 1, offset >> 8); /*Write the offset to the DMA controller */ | |
/* | |
The mode consists of the following: | |
0x58+x = binary 01 01 10 xx | |
| | | | | |
| | | +- DMA channel | |
| | +---- Read operation (the DSP reads from memory) | |
| +------- Auto init mode | |
+---------- Block mode | |
*/ | |
/* Write the page to the DMA controller */ | |
switch(sb_dma) { | |
case 0 : outportb (DMA_CHANNEL_0, page); | |
break; | |
case 1 : outportb (DMA_CHANNEL_1, page); | |
break; | |
case 3 : outportb (DMA_CHANNEL_3, page); | |
break; | |
} | |
outportb((sb_dma << 1) + 1, DMA_BLOCK_LENGTH & 0xFF); | |
outportb((sb_dma << 1) + 1, DMA_BLOCK_LENGTH >> 8); | |
outportb(MASK_REGISTER, sb_dma); | |
write_dsp(SB_SET_BLOCK_SIZE); | |
write_dsp(SB_BLOCK_LENGTH & 0xFF); | |
write_dsp(SB_BLOCK_LENGTH >> 8); | |
write_dsp(SB_START_AUTOINIT_PLAYBACK); | |
} | |
void single_cycle_playback() | |
{ | |
playing = 1; | |
/* program the DMA controller */ | |
outportb( MASK_REGISTER, 4 | sb_dma ); | |
outportb( MSB_LSB_FLIP_FLOP, 0 ); | |
outportb( MODE_REGISTER, 0x48 | sb_dma ); | |
outportb( sb_dma << 1, offset & 0xFF ); | |
outportb( sb_dma << 1, offset >> 8 ); | |
switch( sb_dma ) { | |
case 0: | |
outportb( DMA_CHANNEL_0, page ); | |
break; | |
case 1: | |
outportb( DMA_CHANNEL_1, page ); | |
break; | |
case 3: | |
outportb( DMA_CHANNEL_3, page ); | |
break; | |
} | |
outportb((sb_dma << 1) + 1, to_be_played & 0xFF ); | |
outportb((sb_dma << 1) + 1, to_be_played >> 8 ); | |
outportb(MASK_REGISTER, sb_dma); | |
write_dsp(SB_SINGLE_CYCLE_PLAYBACK); | |
write_dsp(to_be_played & 0xFF); | |
write_dsp(to_be_played >> 8); | |
to_be_played = 0; | |
} | |
void sb_single_play( const char *file_name ) | |
{ | |
memset(dma_buffer, 0, 32768); | |
raw_file = fopen(file_name, "rb"); | |
fseek(raw_file, 0L, SEEK_END); | |
file_size = ftell( raw_file ); | |
fseek(raw_file, 0L, SEEK_SET); | |
fread(dma_buffer, 1, file_size, raw_file); | |
write_dsp(SB_SET_PLAYBACK_FREQUENCY); | |
write_dsp(256-1000000/11000); | |
to_be_played = file_size; | |
single_cycle_playback(); | |
} | |
void sb_auto_play(const char *file_name) | |
{ | |
long before; | |
/*Don't play if there's no buffer */ | |
if (!dma_buffer) { | |
return; | |
} | |
/*Start playback in buffer 0 and clear the buffer */ | |
r_buffer = 0; | |
memset (dma_buffer, 0, 32768); | |
/*Open the file for output */ | |
raw_file = fopen(file_name, "rb"); | |
if (!raw_file) { | |
return; | |
} | |
/* determine file size and set size of read and play buffers */ | |
fseek(raw_file, 0L, SEEK_END); | |
file_size = ftell( raw_file ); | |
fseek(raw_file, 0L, SEEK_SET); | |
to_be_read = to_be_played = file_size; | |
printf("to be read: %lu\n", to_be_read); | |
/*Set playback frequency */ | |
write_dsp (SB_SET_PLAYBACK_FREQUENCY); | |
write_dsp (256 - 1000000 / 11000); | |
/*DSP-command D1h - Enable speaker */ | |
write_dsp (SB_ENABLE_SPEAKER); | |
/*Read first bit of data */ | |
read_buffer (0); | |
read_buffer (1); | |
if (to_be_read > 0) { | |
printf("auto init play...\n"); | |
auto_init_playback(); | |
} else { | |
printf("single cycle play...\n"); | |
single_cycle_playback(); | |
} | |
playing = 1; | |
do_read = 0; | |
} | |
void sb_stop () | |
{ | |
write_dsp( SB_PAUSE_PLAYBACK ); | |
playing = 0; | |
fclose( raw_file ); | |
} | |
int main(int argc, char** argv) | |
{ | |
int sb_detected = 0; | |
short percent; | |
if( argc < 2 ) { | |
printf( "Usage: %s FILE.RAW\n", argv[0] ); | |
return 1; | |
} | |
sb_detected = sb_detect(); | |
if( !sb_detected ) { | |
printf("SoundBlaster not found!\n"); | |
return 1; | |
} else { | |
printf("SoundBlaster found at A%x I%u D%u.\n", sb_base, sb_irq, sb_dma ); | |
} | |
sb_init(); | |
sb_auto_play( argv[1] ); | |
while( playing && !kbhit() ) { | |
if( do_read == 1 ) { | |
read_buffer( r_buffer ^ 1 ); | |
do_read = 0; | |
} else { | |
delay( 10 ); | |
percent = (100L * (file_size - to_be_read) + (file_size / 2)) / file_size; | |
printf("\b\b\b\b%3u%%", percent); | |
} | |
} | |
sb_stop(); | |
sb_deinit(); | |
printf("\nExit\n"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment