You've already forked ivan-torvalds-GuitarPedal
forked from AllSpiceMirrors/torvalds-GuitarPedal
Several places hand data from one cpu to the other by filling a buffer and then updating an index. All of them marked the index 'volatile' and left it at that, which orders the compiler's accesses to that one variable and publishes exactly nothing to the other core. The buffer write and the index update can be observed in either order. So use one-way barriers, in the kernel spelling I'm used to rather than the C11 incantations: smp_store_release() to publish, smp_load_acquire() to consume. On armv8-m these come out as a single 'stl' and 'lda', so they cost nothing at all over the plain accesses. In fact they cost less than what they replace - 'volatile' was forcing reloads while ordering nothing useful, and now that it's gone the two memcpy()s in get_output_samples() stop having to cast it away, which was always a sign it wasn't earning its keep. The usb output ring had the additional problem of publishing the index *before* storing the sample, so cpu0 could read a slot cpu1 hadn't written yet. Both 'volatile', so the compiler faithfully kept them in that order. The other three are the pot double-buffer, where cpu0 fills the inactive set and releases 'seq' for the audio core to pick up; the usb input ring, which needed it at both ends, since cpu0 reads the tail to decide whether there is room and could otherwise scribble on a slot cpu1 is still reading; and the analyzer ring feeding the tuner. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
64 lines
1.6 KiB
C
64 lines
1.6 KiB
C
#define FFT_SHIFT 13
|
|
#define FFT_SIZE (1 << FFT_SHIFT)
|
|
|
|
#define ANALYZE_RING_SHIFT 14
|
|
#define ANALYZE_RING_SIZE (1 << ANALYZE_RING_SHIFT)
|
|
#define ANALYZE_RING_MASK (ANALYZE_RING_SIZE - 1)
|
|
|
|
//
|
|
// NOTE! This is accessed from both cores, but the
|
|
// logic is that the audio core only writes to 'ring_buf'
|
|
// and increments 'write_index'. The UI core independently
|
|
// reads from the ring buffer and maintains the read index.
|
|
//
|
|
struct analyze_state {
|
|
float ring_buf[ANALYZE_RING_SIZE];
|
|
unsigned int write_index;
|
|
unsigned int read_index;
|
|
} analyzer;
|
|
|
|
// Hann function using the quarter_sine table. We don't
|
|
// do the standard "(1-cos(x))/2", we do "sin^2(x/2)"
|
|
// instead, and only use half the sine cycle.
|
|
//
|
|
// Half a sine cycle is the same as walking the quarter
|
|
// cycle forward and then backward.
|
|
static inline float hanning(unsigned int idx)
|
|
{
|
|
const int fractional_bits = FFT_SHIFT - QUARTER_SINE_STEP_SHIFT -1;
|
|
|
|
float frac = u32_to_fraction(idx << (32 - fractional_bits));
|
|
idx >>= fractional_bits;
|
|
|
|
unsigned int next = idx+1;
|
|
if (idx >= QUARTER_SINE_STEPS) {
|
|
idx = QUARTER_SINE_STEPS*2 - idx;
|
|
next = idx-1;
|
|
}
|
|
|
|
float sin = linear(frac, quarter_sin[idx], quarter_sin[next]);
|
|
return sin*sin;
|
|
}
|
|
|
|
// 4x downsampled data into continuous lock-free ring buffer
|
|
static inline void analyze_process_sample(sample_t sample)
|
|
{
|
|
// Downsample by 4x
|
|
static float sample_sum;
|
|
static int count;
|
|
|
|
float left = sample.left;
|
|
left += sample_sum;
|
|
if (3 & ++count) {
|
|
sample_sum = left;
|
|
return;
|
|
}
|
|
|
|
sample_sum = 0;
|
|
|
|
unsigned int idx = analyzer.write_index;
|
|
|
|
analyzer.ring_buf[idx & ANALYZE_RING_MASK] = left;
|
|
smp_store_release(&analyzer.write_index, idx + 1);
|
|
}
|