audio: allocate streams separately from devices

This commit is contained in:
Matheus
2026-08-05 22:44:36 -03:00
parent 37dcd0256e
commit c75b45436a
3 changed files with 204 additions and 60 deletions
+6 -2
View File
@@ -8,6 +8,11 @@
typedef struct audio_stream audio_stream_t; typedef struct audio_stream audio_stream_t;
typedef struct {
int (*acquire_stream)(void *private, bool nonblocking, audio_stream_t **stream);
void (*release_stream)(void *private, audio_stream_t *stream);
} audio_device_ops_t;
#define AUDIO_STREAM_INFO_FRAGMENT_SIZE 1 #define AUDIO_STREAM_INFO_FRAGMENT_SIZE 1
#define AUDIO_STREAM_INFO_SPEED 2 #define AUDIO_STREAM_INFO_SPEED 2
#define AUDIO_STREAM_INFO_CHANNELS 3 #define AUDIO_STREAM_INFO_CHANNELS 3
@@ -33,7 +38,6 @@ typedef struct audio_stream {
ringbuffer_t ringbuffer; ringbuffer_t ringbuffer;
pollheader_t pollheader; pollheader_t pollheader;
audio_stream_ops_t *ops; audio_stream_ops_t *ops;
bool opened;
bool playing; bool playing;
int trigger; int trigger;
size_t underruns; size_t underruns;
@@ -44,6 +48,6 @@ typedef struct audio_stream {
bool audio_take_stream_data(audio_stream_t *stream, void *buffer, size_t size); bool audio_take_stream_data(audio_stream_t *stream, void *buffer, size_t size);
int audio_initialize_stream(audio_stream_t *stream, audio_stream_ops_t *stream_ops); int audio_initialize_stream(audio_stream_t *stream, audio_stream_ops_t *stream_ops);
int audio_register_stream(audio_stream_t *stream); int audio_register_device(audio_device_ops_t *device_ops, void *private);
#endif #endif
+52 -16
View File
@@ -18,34 +18,61 @@ static size_t bytes_to_frames(int fmt, int channels, size_t bytes) {
#define MAX_STREAM 64 #define MAX_STREAM 64
#define AUDIO_RINGBUFFER_SIZE (8 * 1024) #define AUDIO_RINGBUFFER_SIZE (8 * 1024)
typedef struct {
mutex_t mutex;
audio_stream_t *stream;
audio_device_ops_t *ops;
void *private;
} audio_device_t;
static int current_minor = 0; static int current_minor = 0;
static audio_stream_t *streams[MAX_STREAM]; static audio_device_t devices[MAX_STREAM];
static audio_device_t *get_device(int minor) {
if (minor < 0 || minor >= MAX_STREAM || devices[minor].ops == NULL)
return NULL;
return &devices[minor];
}
static int audio_open(int minor, vnode_t **vnode, int flags) { static int audio_open(int minor, vnode_t **vnode, int flags) {
audio_stream_t *stream = streams[minor]; audio_device_t *device = get_device(minor);
if (stream == NULL) if (device == NULL)
return ENODEV; return ENODEV;
int error = 0; int error = 0;
MUTEX_ACQUIRE(&stream->mutex); MUTEX_ACQUIRE(&device->mutex);
if (stream->opened) { if (device->stream) {
error = EBUSY; error = EBUSY;
goto leave; goto leave;
} }
stream->opened = true; audio_stream_t *stream;
error = device->ops->acquire_stream(device->private, flags & V_FFLAGS_NONBLOCKING, &stream);
if (error)
goto leave;
__assert(stream);
device->stream = stream;
leave: leave:
MUTEX_RELEASE(&stream->mutex); MUTEX_RELEASE(&device->mutex);
return error; return error;
} }
static int audio_close(int minor, int flags) { static int audio_close(int minor, int flags) {
audio_stream_t *stream = streams[minor]; audio_device_t *device = get_device(minor);
if (stream == NULL) if (device == NULL)
return ENODEV; return ENODEV;
MUTEX_ACQUIRE(&device->mutex);
audio_stream_t *stream = device->stream;
if (stream == NULL) {
MUTEX_RELEASE(&device->mutex);
return ENODEV;
}
MUTEX_ACQUIRE(&stream->mutex); MUTEX_ACQUIRE(&stream->mutex);
if ((flags & V_FFLAGS_CLOSE_URGENT) == 0 && stream->playing) if ((flags & V_FFLAGS_CLOSE_URGENT) == 0 && stream->playing)
@@ -55,14 +82,17 @@ static int audio_close(int minor, int flags) {
stream->ops->reset(stream); stream->ops->reset(stream);
ringbuffer_truncate(&stream->ringbuffer, 0xffffffff); ringbuffer_truncate(&stream->ringbuffer, 0xffffffff);
stream->playing = false; stream->playing = false;
stream->opened = false;
stream->underruns = 0; stream->underruns = 0;
stream->trigger = AUDIO_TRIGGER_OUTPUT | AUDIO_TRIGGER_INPUT; stream->trigger = AUDIO_TRIGGER_OUTPUT | AUDIO_TRIGGER_INPUT;
stream->bytes_submitted = 0; stream->bytes_submitted = 0;
stream->bytes_written = 0; stream->bytes_written = 0;
stream->bytes_played = 0; stream->bytes_played = 0;
device->ops->release_stream(device->private, stream);
device->stream = NULL;
MUTEX_RELEASE(&stream->mutex); MUTEX_RELEASE(&stream->mutex);
MUTEX_RELEASE(&device->mutex);
return 0; return 0;
} }
@@ -79,7 +109,8 @@ static int internal_poll(audio_stream_t *stream, polldata_t *data, int events) {
} }
static int audio_write(int minor, iovec_iterator_t *iovec_iterator, size_t size, uintmax_t offset, int flags, size_t *writec) { static int audio_write(int minor, iovec_iterator_t *iovec_iterator, size_t size, uintmax_t offset, int flags, size_t *writec) {
audio_stream_t *stream = streams[minor]; audio_device_t *device = get_device(minor);
audio_stream_t *stream = device ? device->stream : NULL;
if (stream == NULL) if (stream == NULL)
return ENODEV; return ENODEV;
@@ -133,7 +164,8 @@ static int audio_write(int minor, iovec_iterator_t *iovec_iterator, size_t size,
} }
static int audio_poll(int minor, polldata_t *data, int events) { static int audio_poll(int minor, polldata_t *data, int events) {
audio_stream_t *stream = streams[minor]; audio_device_t *device = get_device(minor);
audio_stream_t *stream = device ? device->stream : NULL;
if (stream == NULL) if (stream == NULL)
return POLLERR; return POLLERR;
@@ -181,7 +213,8 @@ typedef struct {
#define SNDCTL_DSP_CURRENT_OPTR 0x80905024 #define SNDCTL_DSP_CURRENT_OPTR 0x80905024
static int audio_ioctl(int minor, unsigned long request, void *arg, int *result, cred_t *cred) { static int audio_ioctl(int minor, unsigned long request, void *arg, int *result, cred_t *cred) {
audio_stream_t *stream = streams[minor]; audio_device_t *device = get_device(minor);
audio_stream_t *stream = device ? device->stream : NULL;
if (stream == NULL) if (stream == NULL)
return ENODEV; return ENODEV;
@@ -307,12 +340,16 @@ static devops_t devops = {
.ioctl = audio_ioctl .ioctl = audio_ioctl
}; };
int audio_register_stream(audio_stream_t *stream) { int audio_register_device(audio_device_ops_t *device_ops, void *private) {
int minor = __atomic_fetch_add(&current_minor, 1, __ATOMIC_RELAXED); int minor = __atomic_fetch_add(&current_minor, 1, __ATOMIC_RELAXED);
if (minor >= MAX_STREAM) if (minor >= MAX_STREAM)
return ENXIO; return ENXIO;
streams[minor] = stream; audio_device_t *device = &devices[minor];
MUTEX_INIT(&device->mutex);
device->stream = NULL;
device->ops = device_ops;
device->private = private;
char name[10]; char name[10];
snprintf(name, 10, "dsp%d", minor); snprintf(name, 10, "dsp%d", minor);
@@ -337,7 +374,6 @@ int audio_initialize_stream(audio_stream_t *stream, audio_stream_ops_t *ops) {
MUTEX_INIT(&stream->mutex); MUTEX_INIT(&stream->mutex);
POLL_INITHEADER(&stream->pollheader); POLL_INITHEADER(&stream->pollheader);
stream->opened = false;
stream->playing = false; stream->playing = false;
stream->ops = ops; stream->ops = ops;
stream->trigger = AUDIO_TRIGGER_INPUT | AUDIO_TRIGGER_OUTPUT; stream->trigger = AUDIO_TRIGGER_INPUT | AUDIO_TRIGGER_OUTPUT;
+146 -42
View File
@@ -7,6 +7,7 @@
#include <semaphore.h> #include <semaphore.h>
#include <kernel/alloc.h> #include <kernel/alloc.h>
#include <kernel/audio.h> #include <kernel/audio.h>
#include <errno.h>
// TODO: support mono format as well // TODO: support mono format as well
#define HDA_FORMAT_48KHZ_16BIT_STEREO 0x11 #define HDA_FORMAT_48KHZ_16BIT_STEREO 0x11
@@ -160,6 +161,9 @@ typedef struct {
size_t size; size_t size;
} hda_path_t; } hda_path_t;
typedef struct hda hda_t;
typedef struct hda_output_path hda_output_path_t;
typedef struct { typedef struct {
list_t waiter_list; list_t waiter_list;
spinlock_t waiter_list_lock; spinlock_t waiter_list_lock;
@@ -172,11 +176,15 @@ typedef struct {
typedef struct { typedef struct {
audio_stream_t generic; audio_stream_t generic;
hda_t *hda;
volatile hda_sd_t *sd; volatile hda_sd_t *sd;
void *bdl_phys; void *bdl_phys;
size_t tag; size_t tag;
size_t descriptor_index;
bool bi; bool bi;
hda_path_t path; bool allocated;
bool active;
hda_output_path_t *owner;
size_t last; size_t last;
size_t fill_ptr; size_t fill_ptr;
uint32_t last_lpib; uint32_t last_lpib;
@@ -184,7 +192,7 @@ typedef struct {
eventheader_t underrun_event; eventheader_t underrun_event;
} hda_stream_t; } hda_stream_t;
typedef struct { struct hda {
volatile hda_regs_t *regs; volatile hda_regs_t *regs;
volatile uint32_t *corb; volatile uint32_t *corb;
size_t corb_entries; size_t corb_entries;
@@ -205,8 +213,17 @@ typedef struct {
size_t allocated_input_stream_count; size_t allocated_input_stream_count;
size_t allocated_bi_stream_count; size_t allocated_bi_stream_count;
size_t current_stream_tag; size_t current_stream_tag;
spinlock_t stream_lock;
semaphore_t output_stream_semaphore;
size_t usable_output_stream_count;
hda_stream_t **streams; hda_stream_t **streams;
} hda_t; };
struct hda_output_path {
hda_t *hda;
int codec;
hda_path_t path;
};
static uint32_t hda_stream_mask(hda_t *hda) { static uint32_t hda_stream_mask(hda_t *hda) {
size_t count = hda->input_stream_count + hda->output_stream_count + hda->bi_stream_count; size_t count = hda->input_stream_count + hda->output_stream_count + hda->bi_stream_count;
@@ -260,6 +277,9 @@ static bool hda_reset_stream(hda_stream_t *stream) {
if (!hda_stream_stop(stream)) if (!hda_stream_stop(stream))
return false; return false;
long ipl = spinlock_acquire_raise_ipl(&stream->hda->stream_lock, IPL_AUDIO);
spinlock_release_lower_ipl(&stream->hda->stream_lock, ipl);
if (!hda_sd_set_reset(stream->sd, true)) { if (!hda_sd_set_reset(stream->sd, true)) {
printf("hda: setting stream reset bit timed out\n"); printf("hda: setting stream reset bit timed out\n");
return false; return false;
@@ -376,35 +396,16 @@ static audio_stream_ops_t hda_stream_ops = {
.get_info = stream_get_info .get_info = stream_get_info
}; };
// TODO: fix memory leak static hda_stream_t *hda_initialize_output_stream(hda_t *hda, size_t stream_n, size_t tag, bool bi) {
static hda_stream_t *hda_allocate_output_stream(hda_t *hda) {
if (hda->current_stream_tag > 15) {
printf("hda: out of stream tags\n");
return NULL;
}
hda_stream_t *stream = alloc(sizeof(hda_stream_t)); hda_stream_t *stream = alloc(sizeof(hda_stream_t));
__assert(stream); __assert(stream);
__assert(audio_initialize_stream(&stream->generic, &hda_stream_ops) == 0); __assert(audio_initialize_stream(&stream->generic, &hda_stream_ops) == 0);
EVENT_INITHEADER(&stream->underrun_event); EVENT_INITHEADER(&stream->underrun_event);
stream->hda = hda;
size_t stream_n; stream->sd = &hda->regs->sds[stream_n];
if (hda->allocated_output_stream_count < hda->output_stream_count) { stream->descriptor_index = stream_n;
stream->sd = &hda->output_sd[hda->allocated_output_stream_count]; stream->tag = tag;
stream_n = hda->input_stream_count + hda->allocated_output_stream_count; stream->bi = bi;
++hda->allocated_output_stream_count;
} else if (hda->allocated_bi_stream_count < hda->bi_stream_count) {
stream->sd = &hda->bi_sd[hda->allocated_bi_stream_count];
stream_n = hda->input_stream_count + hda->output_stream_count + hda->allocated_bi_stream_count;
++hda->allocated_bi_stream_count;
stream->bi = true;
} else {
printf("hda: no more free streams\n");
return NULL;
}
hda->streams[stream_n] = stream;
hda->regs->intctl |= (1 << stream_n);
stream->bdl_phys = mm_alloc_page(MEMORY_SECTION_DEFAULT); stream->bdl_phys = mm_alloc_page(MEMORY_SECTION_DEFAULT);
__assert(stream->bdl_phys); __assert(stream->bdl_phys);
@@ -420,12 +421,33 @@ static hda_stream_t *hda_allocate_output_stream(hda_t *hda) {
bdl[i].flags = HDA_BDL_FLAGS_IOC; bdl[i].flags = HDA_BDL_FLAGS_IOC;
} }
stream->tag = hda->current_stream_tag++; __assert(hda_reset_stream(stream));
hda_reset_stream(stream); hda->streams[stream_n] = stream;
return stream; return stream;
} }
static void hda_initialize_output_stream_pool(hda_t *hda) {
while (hda->allocated_output_stream_count < hda->output_stream_count && hda->current_stream_tag <= 15) {
size_t i = hda->allocated_output_stream_count++;
size_t stream_n = hda->input_stream_count + i;
hda_initialize_output_stream(hda, stream_n, hda->current_stream_tag++, false);
++hda->usable_output_stream_count;
}
while (hda->allocated_bi_stream_count < hda->bi_stream_count && hda->current_stream_tag <= 15) {
size_t i = hda->allocated_bi_stream_count++;
size_t stream_n = hda->input_stream_count + hda->output_stream_count + i;
hda_initialize_output_stream(hda, stream_n, hda->current_stream_tag++, true);
++hda->usable_output_stream_count;
}
if (hda->output_stream_count + hda->bi_stream_count > hda->usable_output_stream_count)
printf("hda: only %lu output streams are usable because stream tags are exhausted\n", hda->usable_output_stream_count);
SEMAPHORE_INIT(&hda->output_stream_semaphore, hda->usable_output_stream_count);
}
static hda_path_t hda_find_path(hda_fg_t *fg, hda_widget_t *pin, int goal_type) { static hda_path_t hda_find_path(hda_fg_t *fg, hda_widget_t *pin, int goal_type) {
typedef struct { typedef struct {
list_node_t list_node; list_node_t list_node;
@@ -531,6 +553,7 @@ static void hda_stream_fill_data(hda_stream_t *stream) {
static void stream_irq(hda_t *hda) { static void stream_irq(hda_t *hda) {
uint32_t status = hda->regs->intsts & hda_stream_mask(hda); uint32_t status = hda->regs->intsts & hda_stream_mask(hda);
spinlock_acquire(&hda->stream_lock);
for (int i = 0; i < 30; ++i) { for (int i = 0; i < 30; ++i) {
if ((status & (1 << i)) == 0) if ((status & (1 << i)) == 0)
@@ -542,9 +565,11 @@ static void stream_irq(hda_t *hda) {
uint8_t strsts = stream->sd->sts; uint8_t strsts = stream->sd->sts;
stream->sd->sts = strsts & (HDA_SD_STS_BCIS | HDA_SD_STS_DESE | HDA_SD_STS_FIFOE); stream->sd->sts = strsts & (HDA_SD_STS_BCIS | HDA_SD_STS_DESE | HDA_SD_STS_FIFOE);
if (strsts & HDA_SD_STS_BCIS) if (stream->active && (strsts & HDA_SD_STS_BCIS))
hda_stream_fill_data(stream); hda_stream_fill_data(stream);
} }
spinlock_release(&hda->stream_lock);
} }
static void rirb_irq(hda_t *hda) { static void rirb_irq(hda_t *hda) {
@@ -664,6 +689,87 @@ static uint32_t hda_set_converter_stream_channel(hda_t *hda, uint32_t codec, uin
return hda_submit_verb_and_wait(hda, codec, nid, 0x70600 | (stream << 4) | base_channel); return hda_submit_verb_and_wait(hda, codec, nid, 0x70600 | (stream << 4) | base_channel);
} }
static int hda_acquire_output_stream(void *private, bool nonblocking, audio_stream_t **generic) {
hda_output_path_t *output_path = private;
hda_t *hda = output_path->hda;
if (nonblocking) {
if (!semaphore_test(&hda->output_stream_semaphore))
return EBUSY;
} else {
int error = semaphore_wait(&hda->output_stream_semaphore, true);
if (error)
return error;
}
hda_stream_t *stream = NULL;
long ipl = spinlock_acquire_raise_ipl(&hda->stream_lock, IPL_AUDIO);
for (size_t i = hda->input_stream_count; i < hda->input_stream_count + hda->output_stream_count + hda->bi_stream_count; ++i) {
hda_stream_t *candidate = hda->streams[i];
if (candidate && !candidate->allocated) {
candidate->allocated = true;
candidate->owner = output_path;
stream = candidate;
break;
}
}
spinlock_release_lower_ipl(&hda->stream_lock, ipl);
__assert(stream);
if (!hda_reset_stream(stream)) {
printf("hda: failed to reset allocated stream %lu\n", stream->descriptor_index);
ipl = spinlock_acquire_raise_ipl(&hda->stream_lock, IPL_AUDIO);
stream->owner = NULL;
spinlock_release_lower_ipl(&hda->stream_lock, ipl);
return EIO;
}
hda_widget_t *converter = output_path->path.widgets[output_path->path.size - 1];
hda_set_converter_stream_channel(hda, output_path->codec, converter->nid, stream->tag, 0);
ipl = spinlock_acquire_raise_ipl(&hda->stream_lock, IPL_AUDIO);
stream->active = true;
hda->regs->intctl |= 1u << stream->descriptor_index;
spinlock_release_lower_ipl(&hda->stream_lock, ipl);
*generic = &stream->generic;
return 0;
}
static void hda_release_output_stream(void *private, audio_stream_t *generic) {
hda_output_path_t *output_path = private;
hda_stream_t *stream = (hda_stream_t *)generic;
hda_t *hda = output_path->hda;
__assert(stream->owner == output_path);
long ipl = spinlock_acquire_raise_ipl(&hda->stream_lock, IPL_AUDIO);
hda->regs->intctl &= ~(1u << stream->descriptor_index);
stream->active = false;
spinlock_release_lower_ipl(&hda->stream_lock, ipl);
hda_widget_t *converter = output_path->path.widgets[output_path->path.size - 1];
hda_set_converter_stream_channel(hda, output_path->codec, converter->nid, 0, 0);
if (!hda_reset_stream(stream)) {
printf("hda: failed to reset released stream %lu; keeping it unavailable\n", stream->descriptor_index);
ipl = spinlock_acquire_raise_ipl(&hda->stream_lock, IPL_AUDIO);
stream->owner = NULL;
spinlock_release_lower_ipl(&hda->stream_lock, ipl);
return;
}
ipl = spinlock_acquire_raise_ipl(&hda->stream_lock, IPL_AUDIO);
stream->owner = NULL;
stream->allocated = false;
spinlock_release_lower_ipl(&hda->stream_lock, ipl);
semaphore_signal(&hda->output_stream_semaphore);
}
static audio_device_ops_t hda_audio_device_ops = {
.acquire_stream = hda_acquire_output_stream,
.release_stream = hda_release_output_stream
};
// TODO: we should really be careful to not choose paths that go through the same selector/dac // TODO: we should really be careful to not choose paths that go through the same selector/dac
static void hda_initialize_path(hda_t *hda, int codec, hda_fg_t *fg, hda_path_t path, bool output) { static void hda_initialize_path(hda_t *hda, int codec, hda_fg_t *fg, hda_path_t path, bool output) {
__assert(output); // TODO: this entire function hardcodes an output path for now __assert(output); // TODO: this entire function hardcodes an output path for now
@@ -879,18 +985,15 @@ static void init_function_group(hda_t *hda, int codec, hda_fg_t *function_group)
hda_initialize_path(hda, codec, function_group, path, true); hda_initialize_path(hda, codec, function_group, path, true);
hda_stream_t *stream = hda_allocate_output_stream(hda);
if (stream == NULL) {
printf("hda: failed to allocate stream\n");
continue;
}
stream->path = path;
hda_widget_t *converter = path.widgets[path.size - 1]; hda_widget_t *converter = path.widgets[path.size - 1];
hda_set_converter_format(hda, codec, converter->nid, HDA_FORMAT_48KHZ_16BIT_STEREO); hda_set_converter_format(hda, codec, converter->nid, HDA_FORMAT_48KHZ_16BIT_STEREO);
hda_set_converter_stream_channel(hda, codec, converter->nid, stream->tag, 0);
__assert(audio_register_stream(&stream->generic) == 0); hda_output_path_t *output_path = alloc(sizeof(hda_output_path_t));
__assert(output_path);
output_path->hda = hda;
output_path->codec = codec;
output_path->path = path;
__assert(audio_register_device(&hda_audio_device_ops, output_path) == 0);
} }
} }
@@ -1003,7 +1106,6 @@ static void initcontroller(pcienum_t *e) {
SPINLOCK_INIT(hda->corb_lock); SPINLOCK_INIT(hda->corb_lock);
hda->rirb = rirb; hda->rirb = rirb;
hda->rirb_entries = rirb_entries; hda->rirb_entries = rirb_entries;
hda->current_stream_tag = 1; hda->current_stream_tag = 1;
hda->bi_stream_count = (regs->gcap >> 3) & 0x1f; hda->bi_stream_count = (regs->gcap >> 3) & 0x1f;
@@ -1021,6 +1123,8 @@ static void initcontroller(pcienum_t *e) {
hda->streams = alloc(sizeof(hda_stream_t *) * (hda->input_stream_count + hda->output_stream_count + hda->bi_stream_count)); hda->streams = alloc(sizeof(hda_stream_t *) * (hda->input_stream_count + hda->output_stream_count + hda->bi_stream_count));
__assert(hda->streams); __assert(hda->streams);
SPINLOCK_INIT(hda->stream_lock);
hda_initialize_output_stream_pool(hda);
isr_t *isr = interrupt_allocate(hda_isr, ARCH_EOI, IPL_AUDIO); isr_t *isr = interrupt_allocate(hda_isr, ARCH_EOI, IPL_AUDIO);
__assert(isr); __assert(isr);