Convert ac97 module to use snd

This commit is contained in:
Mike Gerow 2015-05-15 00:38:54 -07:00 committed by Kevin Lange
parent b06b4af8b9
commit 320925d0d9

View File

@ -13,6 +13,7 @@
#include <mem.h>
#include <module.h>
#include <mod/shell.h>
#include <mod/snd.h>
#include <printf.h>
#include <pci.h>
#include <system.h>
@ -35,7 +36,7 @@
/* Bus mastering misc */
/* Buffer descriptor list constants */
#define AC97_BDL_LEN 32 /* Buffer descriptor list length */
#define AC97_BDL_BUFFER_LEN 0x8000 /* Length of buffer in BDL */
#define AC97_BDL_BUFFER_LEN 0x1000 /* Length of buffer in BDL */
#define AC97_CL_GET_LENGTH(cl) ((cl) & 0xFFFF) /* Decode length from cl */
#define AC97_CL_SET_LENGTH(cl, v) ((cl) = (v) & 0xFFFF) /* Encode length to cl */
#define AC97_CL_BUP (1 << 30) /* Buffer underrun policy in cl */
@ -62,6 +63,11 @@
#define AC97_MONO_VOLUME 0x06
#define AC97_PCM_OUT_VOLUME 0x18
/* snd values */
#define AC97_SND_NAME "Intel AC'97"
#define AC97_PLAYBACK_SPEED 48000
#define AC97_PLAYBACK_FORMAT SND_FORMAT_L16SLE
/* An entry in a buffer dscriptor list */
typedef struct {
uint32_t pointer; /* Pointer to buffer */
@ -80,6 +86,17 @@ typedef struct {
} ac97_device_t;
static ac97_device_t _device;
static snd_device_t _snd = {
AC97_SND_NAME,
&_device,
AC97_PLAYBACK_SPEED,
AC97_PLAYBACK_FORMAT,
};
/*
* This could be unnecessary if we instead allocate just two buffers and make
* the ac97 think there are more.
*/
static void find_ac97(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) {
@ -117,128 +134,27 @@ DEFINE_SHELL_FUNCTION(ac97_status, "[debug] AC'97 status values") {
return 0;
}
static list_t * next_buffers_mutex = NULL;
static uint8_t sample_tmp[0x1000] = {1};
static size_t offset = 0;
static fs_node_t * file = NULL;
static int last_finish = 0;
static int last_playback_pid = 0;
static void do_write(size_t buffers) {
for (size_t j = buffers; j < buffers + AC97_BDL_LEN / 2; j++) {
for (size_t k = 0; k < AC97_BDL_BUFFER_LEN * sizeof(*_device.bufs[0]); k += 0x1000) {
int read = read_fs(file, offset, 0x1000, sample_tmp);
debug_print(INFO, "Writing buffer %d from offset 0x%x, read 0x%x", j, offset, read);
for (size_t i = 0; i < 0x1000; i++) {
((uint8_t *)_device.bufs[j])[i+k] = sample_tmp[i];
}
offset += 0x1000;
}
}
}
static void do_clear(size_t buffers) {
for (size_t j = buffers; j < buffers + AC97_BDL_LEN / 2; j++) {
memset((uint8_t *)_device.bufs[j], 0x00, AC97_BDL_BUFFER_LEN * sizeof(*_device.bufs[0]));
}
}
static void playback_tasklet(void * data, char * name) {
file = kopen((char*)data, 0);
free(data);
if (!file) task_exit(1);
offset = 0;
last_finish = 0;
do_write(0);
do_write(16);
_device.lvi = AC97_BDL_LEN - 1;
outportb(_device.nabmbar + AC97_PO_LVI, _device.lvi);
outportb(_device.nabmbar + AC97_PO_CR, inportb(_device.nabmbar + AC97_PO_CR) | AC97_X_CR_RPBM);
while (offset < file->length) {
sleep_on(next_buffers_mutex);
if (offset >= file->length) break;
do_write(0);
sleep_on(next_buffers_mutex);
if (offset >= file->length) break;
do_write(16);
}
debug_print(NOTICE, "Playback is done.");
close_fs(file);
file = NULL;
last_finish = 3;
task_exit(0);
}
DEFINE_SHELL_FUNCTION(ac97_play, "[debug] Play back a file") {
if (file) {
fprintf(tty, "Something is already playing, use ac97_stop first.\n");
return 1;
}
char * fname = "/opt/examples/decorator_full.wav";
if (argc > 1) {
fname = argv[1];
}
fprintf(tty, "Playing %s\n", fname);
last_playback_pid = create_kernel_tasklet(playback_tasklet, "[[ac97]]", strdup(fname));
fprintf(tty, "Started playback thread, pid = %d\n", last_playback_pid);
return 0;
}
static void stop_playback(void) {
last_finish = 0;
outportb(_device.nabmbar + AC97_PO_CR, inportb(_device.nabmbar + AC97_PO_CR) & ~AC97_X_CR_RPBM);
outports(_device.nabmbar + AC97_PO_SR, AC97_X_SR_LVBCI);
}
DEFINE_SHELL_FUNCTION(ac97_stop, "[debug] Stop playback") {
int f = 0;
stop_playback();
if (file) {
offset = file->length;
f = 1;
}
wakeup_queue(next_buffers_mutex);
if (f) {
int status;
waitpid(last_playback_pid, &status, 0);
}
return 0;
}
static list_t * first_buffer_available = NULL;
static list_t * second_buffer_available = NULL;
static void irq_handler(struct regs * regs) {
debug_print(NOTICE, "AC97 IRQ called");
uint16_t sr = inports(_device.nabmbar + AC97_PO_SR);
debug_print(NOTICE, "sr: 0x%04x", sr);
if (sr & AC97_X_SR_LVBCI) {
/* Stop playing */
stop_playback();
outports(_device.nabmbar + AC97_PO_SR, AC97_X_SR_LVBCI);
debug_print(NOTICE, "Last valid buffer completion interrupt handled");
} else if (sr & AC97_X_SR_BCIS) {
debug_print(NOTICE, "Buffer completion interrupt status start...");
if (!last_finish || last_finish == 2) {
do_clear(0);
_device.lvi = AC97_BDL_LEN / 2 - 1;
last_finish = 1;
wakeup_queue(first_buffer_available);
} else if (last_finish == 1) {
do_clear(AC97_BDL_LEN / 2);
size_t start;
if (_device.lvi == AC97_BDL_LEN / 2 - 1) {
_device.lvi = AC97_BDL_LEN - 1;
last_finish = 2;
wakeup_queue(second_buffer_available);
} else if (last_finish == 3) {
do_clear(0);
do_clear(AC97_BDL_LEN / 2);
stop_playback();
start = AC97_BDL_LEN / 2;
} else {
_device.lvi = AC97_BDL_LEN / 2 - 1;
start = 0;
}
for (int i = start; i <= _device.lvi; i++) {
snd_request_buf(&_snd, AC97_BDL_BUFFER_LEN * sizeof(*_device.bufs[0]), (uint8_t *)_device.bufs[i]);
}
wakeup_queue(next_buffers_mutex);
outportb(_device.nabmbar + AC97_PO_LVI, _device.lvi);
outports(_device.nabmbar + AC97_PO_SR, AC97_X_SR_BCIS);
debug_print(NOTICE, "Buffer completion interrupt status handled");
@ -250,92 +166,6 @@ static void irq_handler(struct regs * regs) {
irq_ack(_device.irq);
}
static uint32_t cycle = 0;
static size_t get_buffer_for_offset(size_t offset) {
return (offset / (AC97_BDL_BUFFER_LEN * sizeof(*_device.bufs[0]))) % AC97_BDL_LEN;
}
static size_t offset_in_buffer(size_t offset) {
return offset % (AC97_BDL_BUFFER_LEN * sizeof(*_device.bufs[0]));
}
static int start_on_next = 0;
static uint32_t write_ac97(fs_node_t *node, uint32_t offset, uint32_t size, uint8_t *buffer) {
debug_print(NOTICE, "Writing 0x%x...", size);
size_t written = 0;
while (written < size) {
size_t j = get_buffer_for_offset(cycle);
size_t k = offset_in_buffer(cycle);
#if 1
if (k == 0) {
if (j == 0) {
if (start_on_next == 1) {
} else {
if (start_on_next == 2) {
outportb(_device.nabmbar + AC97_PO_CR, inportb(_device.nabmbar + AC97_PO_CR) | AC97_X_CR_RR);
_device.lvi = AC97_BDL_LEN - 1;
outportb(_device.nabmbar + AC97_PO_LVI, _device.lvi);
outportl(_device.nabmbar + AC97_PO_BDBAR, _device.bdl_p);
outportb(_device.nabmbar + AC97_PO_CR, inportb(_device.nabmbar + AC97_PO_CR) | AC97_X_CR_RPBM);
start_on_next = 0;
}
sleep_on(first_buffer_available);
}
} else if (j == AC97_BDL_LEN / 2) {
if (start_on_next == 1) {
start_on_next = 2;
} else {
sleep_on(second_buffer_available);
}
}
}
#endif
debug_print(NOTICE, "Writing buffer %d starting at %d", j, k);
size_t x = 0;
while (written < size && (written == 0 || offset_in_buffer(cycle) != 0)) {
((uint8_t *)_device.bufs[j])[k+x] = buffer[written];
written++;
cycle++;
x++;
}
}
return written;
}
static int ioctl_ac97(fs_node_t * node, int request, void * argp) {
switch (request) {
case 0xf00:
return 0;
case 0xf01:
last_finish = 0;
cycle = 0;
start_on_next = 1;
outportb(_device.nabmbar + AC97_PO_CR, inportb(_device.nabmbar + AC97_PO_CR) & ~AC97_X_CR_RPBM);
do_clear(0);
do_clear(16);
return 0;
case 0xf02:
stop_playback();
return 0;
default:
return -1;
}
}
static fs_node_t * ac97_device_create(void) {
fs_node_t * fnode = malloc(sizeof(fs_node_t));
memset(fnode, 0x00, sizeof(fs_node_t));
sprintf(fnode->name, "dsp");
fnode->flags = FS_CHARDEVICE;
fnode->ioctl = ioctl_ac97;
fnode->write = write_ac97;
return fnode;
}
static int init(void) {
debug_print(NOTICE, "Initializing AC97");
pci_scan(&find_ac97, -1, &_device);
@ -343,8 +173,6 @@ static int init(void) {
return 1;
}
BIND_SHELL_FUNCTION(ac97_status);
BIND_SHELL_FUNCTION(ac97_play);
BIND_SHELL_FUNCTION(ac97_stop);
_device.nabmbar = pci_read_field(_device.pci_device, AC97_NABMBAR, 2) & ((uint32_t) -1) << 1;
_device.nambar = pci_read_field(_device.pci_device, PCI_BAR0, 4) & ((uint32_t) -1) << 1;
_device.irq = pci_read_field(_device.pci_device, PCI_INTERRUPT_LINE, 1);
@ -373,24 +201,26 @@ static int init(void) {
AC97_CL_SET_LENGTH(_device.bdl[i].cl, AC97_BDL_BUFFER_LEN);
}
/* Set the midway buffer and the last buffer to interrupt */
_device.bdl[AC97_BDL_LEN / 2].cl |= AC97_CL_IOC;
_device.bdl[AC97_BDL_LEN / 2 - 1].cl |= AC97_CL_IOC;
_device.bdl[AC97_BDL_LEN - 1].cl |= AC97_CL_IOC;
/* Tell the ac97 where our BDL is */
outportl(_device.nabmbar + AC97_PO_BDBAR, _device.bdl_p);
/* Set the LVI to be the last index */
_device.lvi = AC97_BDL_LEN - 1;
outportb(_device.nabmbar + AC97_PO_LVI, _device.lvi);
next_buffers_mutex = list_create();
first_buffer_available = list_create();
second_buffer_available = list_create();
fs_node_t * dsp_node = ac97_device_create();
vfs_mount("/dev/dsp", dsp_node);
snd_register(&_snd);
/* Start things playing */
outportb(_device.nabmbar + AC97_PO_CR, inportb(_device.nabmbar + AC97_PO_CR) | AC97_X_CR_RPBM);
debug_print(NOTICE, "AC97 initialized successfully");
return 0;
}
static int fini(void) {
snd_unregister(&_snd);
free(_device.bdl);
for (int i = 0; i < AC97_BDL_LEN; i++) {
free(_device.bufs[i]);
@ -400,3 +230,4 @@ static int fini(void) {
MODULE_DEF(ac97, init, fini);
MODULE_DEPENDS(debugshell);
MODULE_DEPENDS(snd);