Convert ac97 module to use snd
This commit is contained in:
parent
b06b4af8b9
commit
320925d0d9
247
modules/ac97.c
247
modules/ac97.c
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user