feat: audio - add concurrent mixing, output switching, and device-aware UI
This commit is contained in:
@@ -26,6 +26,10 @@ namespace Drivers::Audio::IntelHda {
|
||||
|
||||
static bool g_initialized = false;
|
||||
static kcp::Spinlock g_codecLock;
|
||||
// Serializes stream lifecycle, DMA write-pointer updates, and IRQ-side
|
||||
// stream inspection. The IRQ releases it before calling the mixer to keep
|
||||
// the lock order consistently Mixer -> HDA stream.
|
||||
static kcp::Spinlock g_streamLock;
|
||||
|
||||
static volatile uint8_t* g_mmioBase = nullptr;
|
||||
static uint8_t g_bus, g_dev, g_func;
|
||||
@@ -852,6 +856,7 @@ namespace Drivers::Audio::IntelHda {
|
||||
bool bufferCompleted = false;
|
||||
|
||||
// Handle stream interrupts (bits 0-29 correspond to stream descriptors)
|
||||
g_streamLock.Acquire();
|
||||
if (g_stream.Active) {
|
||||
uint8_t si = g_stream.StreamIndex;
|
||||
if (intsts & (1u << si)) {
|
||||
@@ -860,6 +865,7 @@ namespace Drivers::Audio::IntelHda {
|
||||
WriteSD8(si, SD_STS, sts);
|
||||
}
|
||||
}
|
||||
g_streamLock.Release();
|
||||
|
||||
// Handle RIRB interrupt (controller interrupt enable bit 30)
|
||||
// Do NOT advance g_rirbReadPtr here — ReadResponse() owns it.
|
||||
@@ -1028,11 +1034,16 @@ namespace Drivers::Audio::IntelHda {
|
||||
|
||||
int Open(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample) {
|
||||
if (!g_initialized) return -1;
|
||||
if (g_stream.Active) return -1; // Only one stream at a time
|
||||
if (channels < 1 || channels > 8) return -1;
|
||||
if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 20
|
||||
&& bitsPerSample != 24 && bitsPerSample != 32) return -1;
|
||||
|
||||
g_streamLock.Acquire();
|
||||
if (g_stream.Active) {
|
||||
g_streamLock.Release();
|
||||
return -1; // Only one physical stream at a time
|
||||
}
|
||||
|
||||
// Output stream index = numInputStreams (first output stream)
|
||||
uint8_t streamIndex = g_numInputStreams;
|
||||
uint8_t streamTag = 1;
|
||||
@@ -1044,7 +1055,10 @@ namespace Drivers::Audio::IntelHda {
|
||||
ConfigureOutputPath(fmt, streamTag);
|
||||
|
||||
// Set up the output stream DMA
|
||||
if (!SetupOutputStream(streamIndex, fmt)) return -1;
|
||||
if (!SetupOutputStream(streamIndex, fmt)) {
|
||||
g_streamLock.Release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Zero the DMA buffer
|
||||
memset(g_dmaBuffer, 0, TOTAL_BUFFER_SIZE);
|
||||
@@ -1065,11 +1079,17 @@ namespace Drivers::Audio::IntelHda {
|
||||
<< (uint64_t)sampleRate << "Hz " << (uint64_t)bitsPerSample << "-bit "
|
||||
<< (uint64_t)channels << "ch";
|
||||
|
||||
g_streamLock.Release();
|
||||
return 0; // Handle 0
|
||||
}
|
||||
|
||||
void Close(int handle) {
|
||||
if (handle != 0 || !g_stream.Active) return;
|
||||
if (handle != 0) return;
|
||||
g_streamLock.Acquire();
|
||||
if (!g_stream.Active) {
|
||||
g_streamLock.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
StopStream(g_stream.StreamIndex);
|
||||
|
||||
@@ -1080,10 +1100,16 @@ namespace Drivers::Audio::IntelHda {
|
||||
g_stream.Active = false;
|
||||
|
||||
KernelLogStream(OK, "HDA") << "Stream closed";
|
||||
g_streamLock.Release();
|
||||
}
|
||||
|
||||
uint32_t GetWriteSpace(int handle) {
|
||||
if (handle != 0 || !g_stream.Active) return 0;
|
||||
if (handle != 0) return 0;
|
||||
g_streamLock.Acquire();
|
||||
if (!g_stream.Active) {
|
||||
g_streamLock.Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t hwPos = g_dmaPos[g_stream.StreamIndex * 2];
|
||||
uint32_t writePos = g_stream.WritePos;
|
||||
@@ -1095,12 +1121,18 @@ namespace Drivers::Audio::IntelHda {
|
||||
}
|
||||
if (available > 64) available -= 64;
|
||||
else available = 0;
|
||||
g_streamLock.Release();
|
||||
return available;
|
||||
}
|
||||
|
||||
int Write(int handle, const uint8_t* data, uint32_t size) {
|
||||
if (handle != 0 || !g_stream.Active || !data || size == 0)
|
||||
if (handle != 0 || !data || size == 0)
|
||||
return -1;
|
||||
g_streamLock.Acquire();
|
||||
if (!g_stream.Active) {
|
||||
g_streamLock.Release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Drain unsolicited responses from the RIRB — during playback no
|
||||
// CodecCommands are sent, so ReadResponse() never runs and jack
|
||||
@@ -1135,7 +1167,10 @@ namespace Drivers::Audio::IntelHda {
|
||||
else available = 0;
|
||||
|
||||
if (size > available) size = available;
|
||||
if (size == 0) return 0;
|
||||
if (size == 0) {
|
||||
g_streamLock.Release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write data to DMA buffer (handle wrap-around)
|
||||
uint32_t firstChunk = TOTAL_BUFFER_SIZE - writePos;
|
||||
@@ -1148,36 +1183,45 @@ namespace Drivers::Audio::IntelHda {
|
||||
|
||||
g_stream.WritePos = (writePos + size) % TOTAL_BUFFER_SIZE;
|
||||
|
||||
g_streamLock.Release();
|
||||
return (int)size;
|
||||
}
|
||||
|
||||
int Control(int handle, int cmd, int value) {
|
||||
if (handle != 0) return -1;
|
||||
|
||||
g_streamLock.Acquire();
|
||||
int result = -1;
|
||||
switch (cmd) {
|
||||
case AUDIO_CTL_SET_VOLUME:
|
||||
if (!g_initialized) { g_volume = value; return 0; }
|
||||
if (!g_initialized) { g_volume = value; result = 0; break; }
|
||||
SetOutputVolume(value);
|
||||
return 0;
|
||||
result = 0;
|
||||
break;
|
||||
|
||||
case AUDIO_CTL_GET_VOLUME:
|
||||
return g_volume;
|
||||
result = g_volume;
|
||||
break;
|
||||
|
||||
case AUDIO_CTL_GET_POS:
|
||||
if (!g_stream.Active) return 0;
|
||||
return (int)g_dmaPos[g_stream.StreamIndex * 2];
|
||||
result = !g_stream.Active ? 0 :
|
||||
(int)g_dmaPos[g_stream.StreamIndex * 2];
|
||||
break;
|
||||
|
||||
case AUDIO_CTL_PAUSE:
|
||||
if (!g_stream.Active) return -1;
|
||||
if (!g_stream.Active) break;
|
||||
if (value)
|
||||
StopStream(g_stream.StreamIndex);
|
||||
else
|
||||
StartStream(g_stream.StreamIndex);
|
||||
return 0;
|
||||
result = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
g_streamLock.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user