Implement audio_fifo as lock-free SPSC queue to not require disabling interrupts

This commit is contained in:
Ian Scott committed 2026-03-28 22:06:18 -06:00
1 parent d0510ad50c
commit cee1081a03
4 files changed
+36 -45

No files matched your search

+12 -14
View File
@@ -38,14 +38,12 @@ void fifo_init(audio_fifo_t *fifo) {
fifo->state = FIFO_STATE_STOPPED;
fifo->write_idx = 0;
fifo->read_idx = 0;
fifo->samples_in_fifo = 0;
}
// Reset the FIFO to empty state
void fifo_reset(audio_fifo_t *fifo) {
fifo->state = FIFO_STATE_STOPPED;
fifo->write_idx = fifo->read_idx;
fifo->samples_in_fifo = 0;
}
// Add multiple samples to the FIFO
@@ -56,22 +54,23 @@ bool fifo_add_samples(audio_fifo_t *fifo, const audio_sample_t *samples_buffer,
}
// Check if there's enough space in the FIFO
uint32_t available_space = AUDIO_FIFO_SIZE - fifo->samples_in_fifo;
uint32_t level = fifo->write_idx - fifo->read_idx;
uint32_t available_space = AUDIO_FIFO_SIZE - level;
if (num_samples_to_add > available_space) {
return false; // Not enough space for all requested samples
}
// Determine how many samples can be written before wrap-around
// AUDIO_FIFO_SIZE is the total capacity. write_idx is 0 to AUDIO_FIFO_SIZE-1.
uint32_t samples_to_end = AUDIO_FIFO_SIZE - fifo->write_idx;
uint32_t phy_write = fifo->write_idx & AUDIO_FIFO_BITS;
uint32_t samples_to_end = AUDIO_FIFO_SIZE - phy_write;
if (num_samples_to_add <= samples_to_end) {
// All samples fit without wrapping around the physical end of the buffer
memcpy(&fifo->buffer[fifo->write_idx], samples_buffer, num_samples_to_add * sizeof(audio_sample_t));
memcpy(&fifo->buffer[phy_write], samples_buffer, num_samples_to_add * sizeof(audio_sample_t));
} else {
// Samples will wrap around the physical end of the buffer
// Part 1: Copy samples up to the end of the physical buffer
memcpy(&fifo->buffer[fifo->write_idx], samples_buffer, samples_to_end * sizeof(audio_sample_t));
memcpy(&fifo->buffer[phy_write], samples_buffer, samples_to_end * sizeof(audio_sample_t));
// Part 2: Copy remaining samples from the beginning of the physical buffer
uint32_t remaining_samples = num_samples_to_add - samples_to_end;
@@ -79,9 +78,8 @@ bool fifo_add_samples(audio_fifo_t *fifo, const audio_sample_t *samples_buffer,
}
// Update FIFO state
fifo->write_idx = (fifo->write_idx + num_samples_to_add) & (AUDIO_FIFO_BITS);
fifo->samples_in_fifo += num_samples_to_add;
if (fifo->samples_in_fifo >= AUDIO_FIFO_START_THRESHOLD) {
fifo->write_idx += num_samples_to_add;
if (fifo->write_idx - fifo->read_idx >= AUDIO_FIFO_START_THRESHOLD) {
fifo->state = FIFO_STATE_RUNNING;
}
return true;
@@ -93,11 +91,11 @@ uint32_t fifo_take_samples(audio_fifo_t *fifo, uint32_t num_samples) {
if (fifo->state == FIFO_STATE_STOPPED) {
return 0;
}
uint32_t samples_returned = MIN(fifo->samples_in_fifo, num_samples);
fifo->read_idx = (fifo->read_idx + samples_returned) & (AUDIO_FIFO_BITS);
fifo->samples_in_fifo -= samples_returned;
uint32_t level = fifo->write_idx - fifo->read_idx;
uint32_t samples_returned = MIN(level, num_samples);
fifo->read_idx += samples_returned;
// Only go back to stopped once fifo is fully exhausted
if (fifo->samples_in_fifo == 0) {
if (fifo->write_idx == fifo->read_idx) {
fifo->state = FIFO_STATE_STOPPED;
}
return samples_returned;
+15 -20
View File
@@ -25,7 +25,6 @@
#include <stdint.h>
#include <stdbool.h>
#include "hardware/sync.h"
// Configuration
#define AUDIO_FIFO_SIZE 4096 // Must be power of 2
@@ -48,14 +47,15 @@ typedef enum {
} fifo_state_t;
// FIFO structure
// Lock-free SPSC: write_idx is only modified by the producer, read_idx only
// by the consumer. Both are unmasked uint32_t counters — mask with
// AUDIO_FIFO_BITS when indexing into buffer[]. Level = write_idx - read_idx
// (unsigned subtraction handles wrap correctly).
typedef struct {
audio_sample_t buffer[AUDIO_FIFO_SIZE];
volatile uint32_t write_idx;
volatile uint32_t read_idx;
volatile fifo_state_t state;
// mutex_t mutex;
// semaphore_t data_available;
volatile uint32_t samples_in_fifo;
} audio_fifo_t;
// FIFO management functions (implemented in audio_fifo.c)
@@ -66,31 +66,26 @@ bool fifo_add_samples(audio_fifo_t *fifo, const audio_sample_t *samples_buffer,
// Inline functions for performance
static inline uint32_t fifo_level(audio_fifo_t *fifo) {
return fifo->samples_in_fifo;
return fifo->write_idx - fifo->read_idx;
}
static inline bool fifo_add_sample(audio_fifo_t *fifo, audio_sample_t sample) {
if (fifo->samples_in_fifo == AUDIO_FIFO_SIZE) {
uint32_t level = fifo->write_idx - fifo->read_idx;
if (level == AUDIO_FIFO_SIZE) {
return false;
}
fifo->buffer[fifo->write_idx] = sample;
fifo->write_idx = (fifo->write_idx + 1) & AUDIO_FIFO_BITS;
// Disable interrupts around the counter increment: the PWM ISR (consumer)
// runs on the same core and its samples_in_fifo-- can preempt the
// load-add-store here, causing a lost decrement and an inflated count.
uint32_t irq_save = save_and_disable_interrupts();
fifo->samples_in_fifo++;
if (fifo->samples_in_fifo >= AUDIO_FIFO_START_THRESHOLD) {
fifo->buffer[fifo->write_idx & AUDIO_FIFO_BITS] = sample;
fifo->write_idx++;
if (level + 1 >= AUDIO_FIFO_START_THRESHOLD) {
fifo->state = FIFO_STATE_RUNNING;
}
restore_interrupts(irq_save);
return true;
}
uint32_t fifo_take_samples(audio_fifo_t *fifo, uint32_t num_samples);
static inline uint32_t fifo_free_space(audio_fifo_t *fifo) {
return AUDIO_FIFO_SIZE - fifo->samples_in_fifo;
return AUDIO_FIFO_SIZE - (fifo->write_idx - fifo->read_idx);
}
// Inline version of fifo_take_samples for performance-critical code
@@ -98,10 +93,10 @@ static inline uint32_t fifo_take_samples_inline(audio_fifo_t *fifo, uint32_t num
if (fifo->state == FIFO_STATE_STOPPED) {
return 0;
}
uint32_t samples_returned = fifo->samples_in_fifo < num_samples ? fifo->samples_in_fifo : num_samples;
fifo->read_idx = (fifo->read_idx + samples_returned) & AUDIO_FIFO_BITS;
fifo->samples_in_fifo -= samples_returned;
if (fifo->samples_in_fifo == 0) {
uint32_t level = fifo->write_idx - fifo->read_idx;
uint32_t samples_returned = level < num_samples ? level : num_samples;
fifo->read_idx += samples_returned;
if (fifo->write_idx == fifo->read_idx) {
fifo->state = FIFO_STATE_STOPPED;
}
return samples_returned;
+1 -1
View File
@@ -556,7 +556,7 @@ uint8_t cdrom_get_subq(cdrom_t *dev,uint8_t *b) {
* empty or seek_pos is at the start of the track. */
#if USE_CD_AUDIO_FIFO
{
uint32_t buffered_sectors = dev->audio_fifo.samples_in_fifo / (SAMPLES_PER_SECTOR * 2);
uint32_t buffered_sectors = (dev->audio_fifo.write_idx - dev->audio_fifo.read_idx) / (SAMPLES_PER_SECTOR * 2);
uint32_t report_pos = (buffered_sectors < dev->seek_pos) ?
(dev->seek_pos - buffered_sectors) : 0;
dev->ops->get_subchannel(dev, report_pos, &subc);
+8 -10
View File
@@ -164,24 +164,22 @@ void audio_sample_handler(void) {
#ifdef CDROM
// Read L+R sample pair directly from FIFO using read_idx before advancing.
// Must request exactly 2 (stereo pair) or skip — never read partial pairs.
if (cd_fifo->state != FIFO_STATE_STOPPED && cd_fifo->samples_in_fifo >= 2) {
if (cd_fifo->state != FIFO_STATE_STOPPED && cd_fifo->write_idx - cd_fifo->read_idx >= 2) {
uint32_t idx = cd_fifo->read_idx;
sample_l += scale_sample(cd_fifo->buffer[idx], cd_audio_volume, 0);
sample_l += scale_sample(cd_fifo->buffer[idx & AUDIO_FIFO_BITS], cd_audio_volume, 0);
sample_r += scale_sample(cd_fifo->buffer[(idx + 1) & AUDIO_FIFO_BITS], cd_audio_volume, 0);
cd_fifo->read_idx = (idx + 2) & AUDIO_FIFO_BITS;
cd_fifo->samples_in_fifo -= 2;
if (cd_fifo->samples_in_fifo == 0) cd_fifo->state = FIFO_STATE_STOPPED;
cd_fifo->read_idx = idx + 2;
if (cd_fifo->write_idx == cd_fifo->read_idx) cd_fifo->state = FIFO_STATE_STOPPED;
}
#endif
// Read OPL stereo pair from FIFO — always consume 2 samples (L then R).
if (opl_out_fifo.state != FIFO_STATE_STOPPED && opl_out_fifo.samples_in_fifo >= 2) {
if (opl_out_fifo.state != FIFO_STATE_STOPPED && opl_out_fifo.write_idx - opl_out_fifo.read_idx >= 2) {
uint32_t idx = opl_out_fifo.read_idx;
sample_l += opl_out_fifo.buffer[idx];
sample_l += opl_out_fifo.buffer[idx & AUDIO_FIFO_BITS];
sample_r += opl_out_fifo.buffer[(idx + 1) & AUDIO_FIFO_BITS];
opl_out_fifo.read_idx = (idx + 2) & AUDIO_FIFO_BITS;
opl_out_fifo.samples_in_fifo -= 2;
if (opl_out_fifo.samples_in_fifo == 0) opl_out_fifo.state = FIFO_STATE_STOPPED;
opl_out_fifo.read_idx = idx + 2;
if (opl_out_fifo.write_idx == opl_out_fifo.read_idx) opl_out_fifo.state = FIFO_STATE_STOPPED;
}
const sample_pair clamped = {.data16 = {