152 lines
5.3 KiB
C
152 lines
5.3 KiB
C
#include "sb16.h"
|
|
#include "i8259.h"
|
|
|
|
volatile uint8_t sb16_used = 0; // Whether SB16 is being used exclusively
|
|
volatile uint8_t sb16_interrupted = 0; // Interrupt counter, used for sb16_read()
|
|
|
|
/* int32_t sb16_init()
|
|
* @output: Sound Blaster 16 initialized
|
|
* ret val - SUCCESS / FAIL
|
|
* @description: Initialize Sound Blaster 16 sound card.
|
|
*/
|
|
int32_t sb16_init() {
|
|
// Sound Blaster 16 initialization sequence,
|
|
// as described in the code in https://wiki.osdev.org/Sound_Blaster_16
|
|
cli();
|
|
if(sb16_used) {
|
|
sti();
|
|
return FAIL;
|
|
}
|
|
outb(1, SB16_PORT_RESET); // Enter reset mode
|
|
int i = 0; // Wait 10000 cycles,
|
|
for(i = 0; i < 10000; i++); // around 3us assuming 3GHz CPU
|
|
outb(0, SB16_PORT_RESET); // Leave reset mode
|
|
// Verify if SB16 is present
|
|
uint8_t data = inb(SB16_PORT_READ);
|
|
|
|
if(data != SB16_STATUS_READY) { // If SB16 isn't present, quit
|
|
sti();
|
|
return FAIL;
|
|
}
|
|
sb16_used = 1;
|
|
|
|
enable_irq(SB16_IRQ); // Enable its IRQ for music transmission
|
|
|
|
// DMA initialization
|
|
// 1. Mask DMA channel 1
|
|
outb(DMA_MASK_CHANNEL | DMA_SELECT_CHANNEL_1, DMA_REG_CHANNEL_MASK);
|
|
// 2. Reset flip-flop register
|
|
outb(DMA_FLIPFLOP_RESET, DMA_REG_FLIPFLOP);
|
|
// 3. Send start address of DMA, bit 0-7 then bit 8-15
|
|
outb((uint8_t) (SB16_BUF_ADDR), DMA_REG_CH1_START);
|
|
outb((uint8_t) (SB16_BUF_ADDR >> 8), DMA_REG_CH1_START);
|
|
// 4. Reset flip-flop register
|
|
outb(DMA_FLIPFLOP_RESET, DMA_REG_FLIPFLOP);
|
|
// 5. Send size of DMA, bit 0-7 then bit 8-15
|
|
outb((uint8_t) (SB16_BUF_LEN - 1), DMA_REG_CH1_SIZE);
|
|
outb((uint8_t) ((SB16_BUF_LEN - 1) >> 8), DMA_REG_CH1_SIZE);
|
|
// 6. Send page id of DMA, bit 16-24 of buffer address
|
|
outb((uint8_t) (SB16_BUF_ADDR >> 16), DMA_REG_CH1_PAGE);
|
|
// 7. Set DMA mode to single transfer, peripheral reading, auto loop
|
|
outb(DMA_SELECT_CHANNEL_1 | DMA_MODE_PERIPH_READ
|
|
| DMA_MODE_AUTO | DMA_MODE_TRANSFER_SINGLE, DMA_REG_MODE);
|
|
// 8. Unmask DMA channel 1
|
|
outb(DMA_UNMASK_CHANNEL | DMA_SELECT_CHANNEL_1, DMA_REG_CHANNEL_MASK);
|
|
sti();
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
// Unified FS interface.
|
|
unified_fs_interface_t sb16_if = {
|
|
.open = sb16_open,
|
|
.read = sb16_read,
|
|
.write = sb16_write,
|
|
.ioctl = sb16_ioctl,
|
|
.close = sb16_close
|
|
};
|
|
|
|
/* int32_t sb16_open(int32_t* inode, char* filename)
|
|
* @input: all ignored
|
|
* @output: sound blaster 16 initialized and locked for exclusive use
|
|
* ret val - SUCCESS / FAIL
|
|
* @description: initializes sound blaster 16 for audio output.
|
|
*/
|
|
int32_t sb16_open(int32_t* inode, char* filename) {
|
|
return sb16_init();
|
|
}
|
|
|
|
/* int32_t sb16_read(int32_t* inode, uint32_t* offset, char* buf, uint32_t len)
|
|
* @output: waits after next SB16 interrupt occurs.
|
|
* ret val - SUCCESS / FAIL
|
|
* @description: wait until next SB16 interrupt, so we can copy
|
|
* the next block of music into buffer.
|
|
*/
|
|
int32_t sb16_read(int32_t* inode, uint32_t* offset, char* buf, uint32_t len) {
|
|
if(!sb16_used) return FAIL; // If SB16 isn't present, quit
|
|
uint8_t prev_id = sb16_interrupted;
|
|
while(prev_id == sb16_interrupted) wait_interrupt(); // Wait until the interrupt state changed
|
|
return SUCCESS;
|
|
}
|
|
|
|
/* int32_t sb16_write(int32_t* inode, uint32_t* offset, const char* buf, uint32_t len)
|
|
* @input: offset - current pos in SB16 buffer
|
|
* buf - data to be copied into
|
|
* len - length of buf
|
|
* @output: SB16 buffer written with data from buf
|
|
* ret val - SUCCESS / FAIL
|
|
* @description: copy audio segment into sound blaster 16's buffer.
|
|
*/
|
|
int32_t sb16_write(int32_t* inode, uint32_t* offset, const char* buf, uint32_t len) {
|
|
if(!sb16_used) return FAIL; // If SB16 isn't present, quit
|
|
if(NULL == buf) return FAIL;
|
|
|
|
if(*offset + len <= SB16_BUF_LEN) {
|
|
memcpy((char*) (SB16_BUF_ADDR + *offset), buf, len);
|
|
(*offset) += len;
|
|
} else {
|
|
uint32_t len_to_copy = SB16_BUF_LEN - *offset;
|
|
memcpy((char*) (SB16_BUF_ADDR + *offset), buf, len_to_copy);
|
|
*offset = 0;
|
|
if(len - len_to_copy != sb16_write(inode, offset, buf + len_to_copy, len - len_to_copy)) return FAIL;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/* int32_t sb16_ioctl(int32_t* inode, uint32_t* offset, int32_t op)
|
|
* @input: op - command to be sent to sound blaster 16
|
|
* @output: command sent to sound blaster 16
|
|
* ret val - SUCCESS / FAIL
|
|
* @description: sends command to control sound blaster 16.
|
|
* used for pausing, resuming, setting params, etc.
|
|
*/
|
|
int32_t sb16_ioctl(int32_t* inode, uint32_t* offset, int32_t op) {
|
|
if(!sb16_used) return FAIL; // If SB16 isn't present, quit
|
|
outb((uint8_t) op, SB16_PORT_WRITE);
|
|
return SUCCESS;
|
|
}
|
|
|
|
/* int32_t sb16_close(int32_t* inode)
|
|
* @input: all ignored
|
|
* @output: sound blaster 16 lock released for other program's use
|
|
* ret val - SUCCESS / FAIL
|
|
* @description: releases lock on sound blaster 16.
|
|
*/
|
|
int32_t sb16_close(int32_t* inode) {
|
|
if(!sb16_used) return FAIL; // If SB16 isn't present, quit
|
|
// outb(SB16_CMD_PAUSE, SB16_PORT_WRITE);
|
|
sb16_used = 0;
|
|
return SUCCESS;
|
|
}
|
|
|
|
/* sb16_interrupt()
|
|
* @description: Interrupt handler of SB16.
|
|
* Updates interrupt state variable, queries SB16 status port,
|
|
* and sends EOI.
|
|
*/
|
|
void sb16_interrupt() {
|
|
sb16_interrupted++;
|
|
inb(SB16_PORT_STATUS);
|
|
send_eoi(SB16_IRQ);
|
|
}
|