Files
nt-lite/code/drivers/audio/hda/hda_transport.c
T

1422 lines
59 KiB
C

// Intel High Definition Audio controller transport, command rings, output DMA, and recovery.
#include <ntlite/atomic.h>
#include <stdbool.h>
#include "hda.h"
#include <intrin.h>
#include <string.h>
// Orders CPU stores before a controller ownership transition.
static __forceinline void HdaMemoryBarrier(void) {
_ReadWriteBarrier();
_mm_mfence();
_ReadWriteBarrier();
}
// Returns the controller's monotonic microsecond counter.
static uint64_t HdaQueryTicks(void) {
return KeQueryTicks();
}
// Delays for a bounded controller settling interval.
static void HdaStall(uint32_t microseconds) {
KeStallExecution(microseconds);
}
// Returns one byte from the controller register aperture.
uint8_t HdaRead8(const HDA_CONTROLLER* controller, uint32_t offset) {
return *(volatile const uint8_t*)(controller->Registers + offset);
}
// Returns one word from the controller register aperture.
uint16_t HdaRead16(const HDA_CONTROLLER* controller, uint32_t offset) {
return *(volatile const uint16_t*)(controller->Registers + offset);
}
// Returns one dword from the controller register aperture.
uint32_t HdaRead32(const HDA_CONTROLLER* controller, uint32_t offset) {
return *(volatile const uint32_t*)(controller->Registers + offset);
}
// Writes one byte to the controller register aperture.
void HdaWrite8(HDA_CONTROLLER* controller, uint32_t offset, uint8_t value) {
*(volatile uint8_t*)(controller->Registers + offset) = value;
}
// Writes one word to the controller register aperture.
void HdaWrite16(HDA_CONTROLLER* controller, uint32_t offset, uint16_t value) {
*(volatile uint16_t*)(controller->Registers + offset) = value;
}
// Writes one dword to the controller register aperture.
void HdaWrite32(HDA_CONTROLLER* controller, uint32_t offset, uint32_t value) {
*(volatile uint32_t*)(controller->Registers + offset) = value;
}
// Returns the register offset of the controller's one output stream descriptor.
uint32_t HdaOutputStreamBase(const HDA_CONTROLLER* controller) {
return HDA_REG_STREAM_BASE +
(uint32_t)controller->Output.DescriptorIndex * HDA_REG_STREAM_STRIDE;
}
// Reads the 24-bit stream control register without crossing into status.
static uint32_t HdaReadStreamControl(const HDA_CONTROLLER* controller) {
uint32_t base = HdaOutputStreamBase(controller);
return (uint32_t)HdaRead16(controller, base + HDA_STREAM_REG_CTL) |
((uint32_t)HdaRead8(controller, base + HDA_STREAM_REG_CTL + 2u) << 16);
}
// Writes the 24-bit stream control register without modifying status.
static void HdaWriteStreamControl(HDA_CONTROLLER* controller, uint32_t value) {
uint32_t base = HdaOutputStreamBase(controller);
HdaWrite16(controller, base + HDA_STREAM_REG_CTL, (uint16_t)value);
HdaWrite8(controller, base + HDA_STREAM_REG_CTL + 2u, (uint8_t)(value >> 16));
}
// Waits until selected bits in one dword register match the requested state.
static KSTATUS HdaWait32(HDA_CONTROLLER* controller,
uint32_t offset,
uint32_t mask,
uint32_t expected,
uint32_t timeoutMicroseconds) {
for (uint32_t elapsed = 0; elapsed < timeoutMicroseconds; elapsed++) {
if (controller->Removed || controller->SurpriseRemoved)
return NTLSTATUS_DEVICE_REMOVED;
if ((HdaRead32(controller, offset) & mask) == expected)
return NTLSTATUS_SUCCESS;
HdaStall(1);
}
return NTLSTATUS_TIMEOUT;
}
// Waits until selected bits in one byte register match the requested state.
static KSTATUS HdaWait8(HDA_CONTROLLER* controller,
uint32_t offset,
uint8_t mask,
uint8_t expected,
uint32_t timeoutMicroseconds) {
for (uint32_t elapsed = 0; elapsed < timeoutMicroseconds; elapsed++) {
if (controller->Removed || controller->SurpriseRemoved)
return NTLSTATUS_DEVICE_REMOVED;
if ((HdaRead8(controller, offset) & mask) == expected)
return NTLSTATUS_SUCCESS;
HdaStall(1);
}
return NTLSTATUS_TIMEOUT;
}
// Waits until selected bits in one word register match the requested state.
static KSTATUS HdaWait16(HDA_CONTROLLER* controller,
uint32_t offset,
uint16_t mask,
uint16_t expected,
uint32_t timeoutMicroseconds) {
for (uint32_t elapsed = 0; elapsed < timeoutMicroseconds; elapsed++) {
if (controller->Removed || controller->SurpriseRemoved)
return NTLSTATUS_DEVICE_REMOVED;
if ((HdaRead16(controller, offset) & mask) == expected)
return NTLSTATUS_SUCCESS;
HdaStall(1);
}
return NTLSTATUS_TIMEOUT;
}
// Waits until selected bits in the output stream control register match.
static KSTATUS HdaWaitStreamControl(HDA_CONTROLLER* controller,
uint32_t mask,
uint32_t expected,
uint32_t timeoutMicroseconds) {
for (uint32_t elapsed = 0; elapsed < timeoutMicroseconds; elapsed++) {
if (controller->Removed || controller->SurpriseRemoved)
return NTLSTATUS_DEVICE_REMOVED;
if ((HdaReadStreamControl(controller) & mask) == expected)
return NTLSTATUS_SUCCESS;
HdaStall(1);
}
return NTLSTATUS_TIMEOUT;
}
// Selects the largest command-ring size advertised by the controller.
static KSTATUS HdaSelectRingSize(uint8_t capability, uint16_t* countOut) {
if (!countOut)
return NTLSTATUS_INVALID_PARAMETER;
if (capability & HDA_RING_SIZE_CAP_256) {
*countOut = 256u;
return NTLSTATUS_SUCCESS;
}
if (capability & HDA_RING_SIZE_CAP_16) {
*countOut = 16u;
return NTLSTATUS_SUCCESS;
}
if (capability & HDA_RING_SIZE_CAP_2) {
*countOut = 2u;
return NTLSTATUS_SUCCESS;
}
return NTLSTATUS_NOT_SUPPORTED;
}
// Returns the hardware selector encoding for a supported ring entry count.
static uint8_t HdaRingSizeSelector(uint16_t count) {
if (count == 256u)
return HDA_RING_SIZE_SELECT_256;
if (count == 16u)
return HDA_RING_SIZE_SELECT_16;
return HDA_RING_SIZE_SELECT_2;
}
// Releases all DMA and synchronization resources owned by one controller.
void HdaReleaseDma(HDA_CONTROLLER* controller) {
if (!controller)
return;
if (controller->StreamMutex) {
ObDereferenceObject(&controller->StreamMutex->Header);
controller->StreamMutex = (MUTEX*)0;
}
if (controller->CommandMutex) {
ObDereferenceObject(&controller->CommandMutex->Header);
controller->CommandMutex = (MUTEX*)0;
}
if (controller->RecoverySemaphore) {
ObDereferenceObject(&controller->RecoverySemaphore->Header);
controller->RecoverySemaphore = (SEMAPHORE*)0;
}
if (controller->RecoveryFinished) {
ObDereferenceObject(&controller->RecoveryFinished->Header);
controller->RecoveryFinished = (EVENT*)0;
}
IoDmaFreeCommonBuffer(controller->Output.BdlDma);
for (uint32_t ringIndex = 0; ringIndex < HDA_MAX_LOGICAL_ENDPOINTS; ringIndex++)
IoDmaFreeCommonBuffer(controller->Output.Rings[ringIndex].Dma);
IoDmaFreeCommonBuffer(controller->PositionDma);
IoDmaFreeCommonBuffer(controller->RirbDma);
IoDmaFreeCommonBuffer(controller->CorbDma);
controller->Output.BdlDma = (IO_DMA_BUFFER*)0;
for (uint32_t ringIndex = 0; ringIndex < HDA_MAX_LOGICAL_ENDPOINTS; ringIndex++) {
controller->Output.Rings[ringIndex].Dma = (IO_DMA_BUFFER*)0;
controller->Output.Rings[ringIndex].Buffer = (uint8_t*)0;
controller->Output.Rings[ringIndex].DeviceAddress = 0;
}
controller->PositionDma = (IO_DMA_BUFFER*)0;
controller->RirbDma = (IO_DMA_BUFFER*)0;
controller->CorbDma = (IO_DMA_BUFFER*)0;
controller->Output.Bdl = (HDA_BDL_ENTRY*)0;
controller->PositionBuffer = (volatile uint64_t*)0;
controller->Rirb = (HDA_RIRB_ENTRY*)0;
controller->Corb = (uint32_t*)0;
if (controller->DmaAdapter) {
IoDmaPutAdapter(controller->DmaAdapter);
controller->DmaAdapter = (IO_DMA_ADAPTER*)0;
}
}
// Allocates the transport and output DMA resources for one controller.
KSTATUS HdaAllocateDma(HDA_CONTROLLER* controller) {
if (!controller || !controller->Fdo)
return NTLSTATUS_INVALID_PARAMETER;
IO_DMA_REQUIREMENTS requirements;
memset(&requirements, 0, sizeof(requirements));
requirements.MaximumDeviceAddress = (controller->Gcap & HDA_GCAP_64OK)
? UINT64_MAX
: 0xFFFFFFFFull;
requirements.Alignment = 128u;
requirements.MaximumSegments = 1u;
KSTATUS status = IoDmaGetAdapter(controller->Fdo,
&requirements,
&controller->DmaAdapter);
if (!KSUCCESS(status))
return status;
status = HdaSelectRingSize(HdaRead8(controller, HDA_REG_CORBSIZE),
&controller->CorbEntryCount);
if (!KSUCCESS(status)) {
HdaReleaseDma(controller);
return status;
}
status = HdaSelectRingSize(HdaRead8(controller, HDA_REG_RIRBSIZE),
&controller->RirbEntryCount);
if (!KSUCCESS(status)) {
HdaReleaseDma(controller);
return status;
}
status = IoDmaAllocateCommonBuffer(controller->DmaAdapter,
(size_t)controller->CorbEntryCount * sizeof(uint32_t),
128u,
&controller->CorbDma);
if (!KSUCCESS(status)) {
HdaReleaseDma(controller);
return status;
}
status = IoDmaAllocateCommonBuffer(controller->DmaAdapter,
(size_t)controller->RirbEntryCount * sizeof(HDA_RIRB_ENTRY),
128u,
&controller->RirbDma);
if (!KSUCCESS(status)) {
HdaReleaseDma(controller);
return status;
}
// The position buffer is optional because LPIB is the supported fallback.
status = IoDmaAllocateCommonBuffer(controller->DmaAdapter,
(size_t)controller->TotalStreamCount * sizeof(uint64_t),
128u,
&controller->PositionDma);
if (!KSUCCESS(status))
controller->PositionDma = (IO_DMA_BUFFER*)0;
for (uint32_t ringIndex = 0; ringIndex < HDA_MAX_LOGICAL_ENDPOINTS; ringIndex++) {
status = IoDmaAllocateCommonBuffer(
controller->DmaAdapter,
HDA_OUTPUT_BUFFER_BYTES,
128u,
&controller->Output.Rings[ringIndex].Dma);
if (!KSUCCESS(status)) {
HdaReleaseDma(controller);
return status;
}
}
status = IoDmaAllocateCommonBuffer(controller->DmaAdapter,
HDA_OUTPUT_PERIOD_COUNT * sizeof(HDA_BDL_ENTRY),
128u,
&controller->Output.BdlDma);
if (!KSUCCESS(status)) {
HdaReleaseDma(controller);
return status;
}
controller->Corb = (uint32_t*)IoDmaGetCommonBufferAddress(controller->CorbDma);
controller->Rirb = (HDA_RIRB_ENTRY*)IoDmaGetCommonBufferAddress(controller->RirbDma);
controller->CorbDeviceAddress = IoDmaGetCommonBufferDeviceAddress(controller->CorbDma);
controller->RirbDeviceAddress = IoDmaGetCommonBufferDeviceAddress(controller->RirbDma);
if (controller->PositionDma) {
controller->PositionBuffer = (volatile uint64_t*)
IoDmaGetCommonBufferAddress(controller->PositionDma);
controller->PositionDeviceAddress =
IoDmaGetCommonBufferDeviceAddress(controller->PositionDma);
}
for (uint32_t ringIndex = 0; ringIndex < HDA_MAX_LOGICAL_ENDPOINTS; ringIndex++) {
controller->Output.Rings[ringIndex].Buffer = (uint8_t*)
IoDmaGetCommonBufferAddress(controller->Output.Rings[ringIndex].Dma);
controller->Output.Rings[ringIndex].DeviceAddress =
IoDmaGetCommonBufferDeviceAddress(
controller->Output.Rings[ringIndex].Dma);
}
controller->Output.Bdl = (HDA_BDL_ENTRY*)
IoDmaGetCommonBufferAddress(controller->Output.BdlDma);
controller->Output.BdlDeviceAddress =
IoDmaGetCommonBufferDeviceAddress(controller->Output.BdlDma);
memset(controller->Corb, 0, (size_t)controller->CorbEntryCount * sizeof(uint32_t));
memset(controller->Rirb, 0, (size_t)controller->RirbEntryCount * sizeof(HDA_RIRB_ENTRY));
if (controller->PositionBuffer)
memset((void*)controller->PositionBuffer, 0,
(size_t)controller->TotalStreamCount * sizeof(uint64_t));
for (uint32_t ringIndex = 0; ringIndex < HDA_MAX_LOGICAL_ENDPOINTS; ringIndex++)
memset(controller->Output.Rings[ringIndex].Buffer, 0, HDA_OUTPUT_BUFFER_BYTES);
memset(controller->Output.Bdl, 0,
HDA_OUTPUT_PERIOD_COUNT * sizeof(HDA_BDL_ENTRY));
status = KeMutexCreate(&controller->CommandMutex);
if (!KSUCCESS(status)) {
HdaReleaseDma(controller);
return status;
}
status = KeMutexCreate(&controller->StreamMutex);
if (!KSUCCESS(status)) {
HdaReleaseDma(controller);
return status;
}
status = KeSemaphoreCreate(0, 1, &controller->RecoverySemaphore);
if (!KSUCCESS(status)) {
HdaReleaseDma(controller);
return status;
}
status = KeEventCreate(EventManualReset, 0, &controller->RecoveryFinished);
if (!KSUCCESS(status)) {
HdaReleaseDma(controller);
return status;
}
return NTLSTATUS_SUCCESS;
}
// Stops the output stream and waits for the run bit to clear.
static KSTATUS HdaStopOutputHardware(HDA_CONTROLLER* controller) {
uint32_t control = HdaReadStreamControl(controller);
control &= ~(HDA_STREAM_CTL_RUN | HDA_STREAM_CTL_INTERRUPT_MASK);
HdaWriteStreamControl(controller, control);
return HdaWaitStreamControl(controller,
HDA_STREAM_CTL_RUN,
0,
HDA_STREAM_RESET_TIMEOUT_US);
}
// Resets the one output stream descriptor to its architected idle state.
static KSTATUS HdaResetOutputDescriptor(HDA_CONTROLLER* controller) {
KSTATUS status = HdaStopOutputHardware(controller);
if (!KSUCCESS(status))
return status;
uint32_t control = HdaReadStreamControl(controller) | HDA_STREAM_CTL_RESET;
HdaWriteStreamControl(controller, control);
status = HdaWaitStreamControl(controller,
HDA_STREAM_CTL_RESET,
HDA_STREAM_CTL_RESET,
HDA_STREAM_RESET_TIMEOUT_US);
if (!KSUCCESS(status))
return status;
control &= ~HDA_STREAM_CTL_RESET;
HdaWriteStreamControl(controller, control);
status = HdaWaitStreamControl(controller,
HDA_STREAM_CTL_RESET,
0,
HDA_STREAM_RESET_TIMEOUT_US);
if (!KSUCCESS(status))
return status;
HdaWrite8(controller,
HdaOutputStreamBase(controller) + HDA_STREAM_REG_STS,
HDA_STREAM_STS_INTERRUPT_MASK);
return NTLSTATUS_SUCCESS;
}
// Builds a four-entry BDL beginning with the active ring period owned by software.
static void HdaBuildOutputBdl(HDA_CONTROLLER* controller) {
HDA_OUTPUT_RING* ring =
&controller->Output.Rings[controller->Output.ActiveRingIndex];
for (uint32_t bdlIndex = 0; bdlIndex < HDA_OUTPUT_PERIOD_COUNT; bdlIndex++) {
uint8_t period = (uint8_t)((ring->NextLogicalPeriod + bdlIndex) %
HDA_OUTPUT_PERIOD_COUNT);
controller->Output.BdlPeriodMap[bdlIndex] = period;
controller->Output.Bdl[bdlIndex].Address =
ring->DeviceAddress +
(uint64_t)period * HDA_OUTPUT_PERIOD_BYTES;
controller->Output.Bdl[bdlIndex].Length = HDA_OUTPUT_PERIOD_BYTES;
controller->Output.Bdl[bdlIndex].Flags = HDA_BDL_FLAG_INTERRUPT_ON_COMPLETION;
}
controller->Output.NextCompletionBdl = 0;
HdaMemoryBarrier();
}
// Programs the fixed-format output descriptor without starting DMA.
static KSTATUS HdaProgramOutputDescriptor(HDA_CONTROLLER* controller) {
KSTATUS status = HdaResetOutputDescriptor(controller);
if (!KSUCCESS(status))
return status;
if (controller->PositionBuffer)
controller->PositionBuffer[controller->Output.DescriptorIndex] = 0;
HdaBuildOutputBdl(controller);
uint32_t base = HdaOutputStreamBase(controller);
HdaWrite32(controller, base + HDA_STREAM_REG_CBL, HDA_OUTPUT_BUFFER_BYTES);
HdaWrite16(controller, base + HDA_STREAM_REG_LVI,
(uint16_t)(HDA_OUTPUT_PERIOD_COUNT - 1u));
HdaWrite16(controller, base + HDA_STREAM_REG_FORMAT, HDA_OUTPUT_STREAM_FORMAT);
HdaWrite32(controller, base + HDA_STREAM_REG_BDLPL,
(uint32_t)controller->Output.BdlDeviceAddress);
HdaWrite32(controller, base + HDA_STREAM_REG_BDLPU,
(uint32_t)(controller->Output.BdlDeviceAddress >> 32));
uint32_t control = ((uint32_t)controller->Output.StreamTag << HDA_STREAM_CTL_TAG_SHIFT) |
HDA_STREAM_CTL_INTERRUPT_COMPLETE |
HDA_STREAM_CTL_INTERRUPT_FIFO_ERROR |
HDA_STREAM_CTL_INTERRUPT_DESCRIPTOR_ERROR;
HdaWriteStreamControl(controller, control);
controller->Output.Prepared = true;
return NTLSTATUS_SUCCESS;
}
// Resets the controller global state after all DMA engines are stopped.
static KSTATUS HdaResetControllerHardware(HDA_CONTROLLER* controller) {
HdaWrite32(controller, HDA_REG_INTCTL, 0);
(void)HdaStopOutputHardware(controller);
HdaWrite8(controller, HDA_REG_CORBCTL, 0);
HdaWrite8(controller, HDA_REG_RIRBCTL, 0);
uint32_t gctl = HdaRead32(controller, HDA_REG_GCTL);
HdaWrite32(controller, HDA_REG_GCTL, gctl & ~HDA_GCTL_RESET);
KSTATUS status = HdaWait32(controller,
HDA_REG_GCTL,
HDA_GCTL_RESET,
0,
HDA_CONTROLLER_RESET_TIMEOUT_US);
if (!KSUCCESS(status))
return status;
HdaStall(100);
HdaWrite32(controller, HDA_REG_GCTL, HDA_GCTL_RESET);
status = HdaWait32(controller,
HDA_REG_GCTL,
HDA_GCTL_RESET,
HDA_GCTL_RESET,
HDA_CONTROLLER_RESET_TIMEOUT_US);
if (!KSUCCESS(status))
return status;
HdaStall(HDA_CODEC_RESET_SETTLE_US);
return NTLSTATUS_SUCCESS;
}
// Resets the CORB read pointer while accepting controllers that self-clear the reset bit.
static KSTATUS HdaResetCorbReadPointer(HDA_CONTROLLER* controller) {
HdaWrite16(controller, HDA_REG_CORBRP, HDA_CORBRP_RESET);
for (uint32_t elapsed = 0; elapsed < HDA_RING_RESET_TIMEOUT_US; elapsed++) {
if (controller->Removed || controller->SurpriseRemoved)
return NTLSTATUS_DEVICE_REMOVED;
uint16_t value = HdaRead16(controller, HDA_REG_CORBRP);
if ((value & HDA_CORBRP_RESET) != 0) {
HdaWrite16(controller, HDA_REG_CORBRP, 0);
return HdaWait16(controller,
HDA_REG_CORBRP,
HDA_CORBRP_RESET,
0,
HDA_RING_RESET_TIMEOUT_US);
}
// Some controllers complete the reset before software can observe the
// reset bit. A zero read pointer proves that the requested state was
// reached, so requiring an intermediate asserted state is incorrect.
if ((value & HDA_CORBRP_POINTER_MASK) == 0) {
HdaWrite16(controller, HDA_REG_CORBRP, 0);
return NTLSTATUS_SUCCESS;
}
HdaStall(1);
}
return NTLSTATUS_TIMEOUT;
}
// Reports the register state that owns a controller initialization failure.
static KSTATUS HdaReportInitializationFailure(HDA_CONTROLLER* controller,
const char* stage,
KSTATUS status) {
DbgPrint(
"HDA: init stage=%s failed status=0x%x GCTL=0x%08x STATESTS=0x%04x "
"CORB=%02x/%02x/%04x/%04x RIRB=%02x/%02x/%04x SDCTL=0x%06x\n",
stage,
status,
HdaRead32(controller, HDA_REG_GCTL),
HdaRead16(controller, HDA_REG_STATESTS),
HdaRead8(controller, HDA_REG_CORBCTL),
HdaRead8(controller, HDA_REG_CORBSTS),
HdaRead16(controller, HDA_REG_CORBWP),
HdaRead16(controller, HDA_REG_CORBRP),
HdaRead8(controller, HDA_REG_RIRBCTL),
HdaRead8(controller, HDA_REG_RIRBSTS),
HdaRead16(controller, HDA_REG_RIRBWP),
HdaReadStreamControl(controller));
return status;
}
// Initializes CORB and RIRB with software pointers synchronized to hardware.
static KSTATUS HdaInitializeCommandRings(HDA_CONTROLLER* controller) {
HdaWrite8(controller, HDA_REG_CORBCTL, 0);
HdaWrite8(controller, HDA_REG_RIRBCTL, 0);
KSTATUS status = HdaWait8(controller,
HDA_REG_CORBCTL,
HDA_CORBCTL_RUN,
0,
HDA_RING_RESET_TIMEOUT_US);
if (!KSUCCESS(status))
return status;
status = HdaWait8(controller,
HDA_REG_RIRBCTL,
HDA_RIRBCTL_RUN,
0,
HDA_RING_RESET_TIMEOUT_US);
if (!KSUCCESS(status))
return status;
HdaWrite8(controller, HDA_REG_CORBSTS, HDA_CORBSTS_MEMORY_ERROR);
HdaWrite8(controller, HDA_REG_RIRBSTS,
HDA_RIRBSTS_RESPONSE | HDA_RIRBSTS_OVERRUN);
HdaWrite32(controller, HDA_REG_CORBLBASE, (uint32_t)controller->CorbDeviceAddress);
HdaWrite32(controller, HDA_REG_CORBUBASE,
(uint32_t)(controller->CorbDeviceAddress >> 32));
HdaWrite8(controller, HDA_REG_CORBSIZE,
HdaRingSizeSelector(controller->CorbEntryCount));
HdaWrite16(controller, HDA_REG_CORBWP, 0);
status = HdaResetCorbReadPointer(controller);
if (!KSUCCESS(status))
return status;
HdaWrite32(controller, HDA_REG_RIRBLBASE, (uint32_t)controller->RirbDeviceAddress);
HdaWrite32(controller, HDA_REG_RIRBUBASE,
(uint32_t)(controller->RirbDeviceAddress >> 32));
HdaWrite8(controller, HDA_REG_RIRBSIZE,
HdaRingSizeSelector(controller->RirbEntryCount));
HdaWrite16(controller, HDA_REG_RIRBWP, HDA_RIRBWP_RESET);
HdaWrite16(controller, HDA_REG_RINTCNT, 1);
controller->CorbWritePointer = 0;
controller->RirbReadPointer = 0;
controller->CommandWaiting = false;
controller->CommandResponseReady = false;
HdaMemoryBarrier();
HdaWrite8(controller, HDA_REG_CORBCTL,
HDA_CORBCTL_MEMORY_ERROR_INTERRUPT | HDA_CORBCTL_RUN);
HdaWrite8(controller, HDA_REG_RIRBCTL,
HDA_RIRBCTL_RESPONSE_INTERRUPT |
HDA_RIRBCTL_OVERRUN_INTERRUPT |
HDA_RIRBCTL_RUN);
status = HdaWait8(controller,
HDA_REG_CORBCTL,
HDA_CORBCTL_RUN,
HDA_CORBCTL_RUN,
HDA_RING_RESET_TIMEOUT_US);
if (!KSUCCESS(status))
return status;
return HdaWait8(controller,
HDA_REG_RIRBCTL,
HDA_RIRBCTL_RUN,
HDA_RIRBCTL_RUN,
HDA_RING_RESET_TIMEOUT_US);
}
// Queues one unsolicited response for DPC-level topology handling.
static void HdaQueueUnsolicitedLocked(HDA_CONTROLLER* controller,
uint32_t response,
uint32_t responseExtra) {
if (controller->UnsolicitedCount == HDA_MAX_UNSOLICITED_RESPONSES) {
controller->UnsolicitedRead = (uint8_t)((controller->UnsolicitedRead + 1u) %
HDA_MAX_UNSOLICITED_RESPONSES);
controller->UnsolicitedCount--;
}
controller->Unsolicited[controller->UnsolicitedWrite].Response = response;
controller->Unsolicited[controller->UnsolicitedWrite].ResponseExtra = responseExtra;
controller->UnsolicitedWrite = (uint8_t)((controller->UnsolicitedWrite + 1u) %
HDA_MAX_UNSOLICITED_RESPONSES);
controller->UnsolicitedCount++;
controller->RirbUnsolicitedCount++;
}
// Drains every hardware-owned RIRB entry while the controller lock is held.
static void HdaDrainRirbLocked(HDA_CONTROLLER* controller) {
uint16_t hardwareWritePointer = (uint16_t)(HdaRead16(controller, HDA_REG_RIRBWP) & 0x00FFu);
while (controller->RirbReadPointer != hardwareWritePointer) {
controller->RirbReadPointer =
(uint16_t)((controller->RirbReadPointer + 1u) % controller->RirbEntryCount);
HDA_RIRB_ENTRY entry = controller->Rirb[controller->RirbReadPointer];
controller->RirbResponseCount++;
if (entry.ResponseExtra & HDA_RIRB_RESPONSE_UNSOLICITED) {
HdaQueueUnsolicitedLocked(controller, entry.Response, entry.ResponseExtra);
continue;
}
uint8_t codecAddress = (uint8_t)(entry.ResponseExtra & HDA_RIRB_RESPONSE_CODEC_MASK);
if (controller->CommandWaiting &&
codecAddress == controller->CommandCodecAddress &&
!controller->CommandResponseReady) {
controller->CommandResponse = entry.Response;
controller->CommandResponseExtra = entry.ResponseExtra;
controller->CommandResponseReady = true;
}
}
}
// Drains RIRB responses and acknowledges response or overrun status.
static uint8_t HdaServiceRirbLocked(HDA_CONTROLLER* controller) {
uint8_t status = HdaRead8(controller, HDA_REG_RIRBSTS);
if (status & HDA_RIRBSTS_RESPONSE)
HdaDrainRirbLocked(controller);
if (status & HDA_RIRBSTS_OVERRUN) {
controller->RirbOverrunCount++;
if (controller->Started)
controller->PendingRecovery = HDA_RECOVERY_CONTROLLER;
}
if (status)
HdaWrite8(controller, HDA_REG_RIRBSTS, status);
return status;
}
// Sends one codec verb attempt while the command mutex is held.
static KSTATUS HdaCodecVerbAttemptLocked(HDA_CONTROLLER* controller,
uint8_t codecAddress,
uint8_t nodeId,
uint32_t verbPayload,
uint32_t* responseOut) {
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
(void)HdaServiceRirbLocked(controller);
uint64_t startingOverruns = controller->RirbOverrunCount;
uint16_t next = (uint16_t)((controller->CorbWritePointer + 1u) %
controller->CorbEntryCount);
uint16_t hardwareReadPointer = (uint16_t)(HdaRead16(controller, HDA_REG_CORBRP) & 0x00FFu);
if (next == hardwareReadPointer) {
KeReleaseSpinlock(&controller->StateLock, flags);
return NTLSTATUS_WOULD_BLOCK;
}
controller->CommandWaiting = true;
controller->CommandResponseReady = false;
controller->CommandCodecAddress = codecAddress;
controller->Corb[next] = ((uint32_t)codecAddress << 28) |
((uint32_t)nodeId << 20) |
(verbPayload & 0x000FFFFFu);
HdaMemoryBarrier();
controller->CorbWritePointer = next;
HdaWrite16(controller, HDA_REG_CORBWP, next);
KeReleaseSpinlock(&controller->StateLock, flags);
for (uint32_t elapsed = 0; elapsed < HDA_CODEC_COMMAND_TIMEOUT_US; elapsed += 10u) {
flags = KeAcquireSpinlock(&controller->StateLock);
(void)HdaServiceRirbLocked(controller);
if (controller->CommandResponseReady) {
uint32_t response = controller->CommandResponse;
controller->CommandWaiting = false;
controller->CommandResponseReady = false;
KeReleaseSpinlock(&controller->StateLock, flags);
if (responseOut)
*responseOut = response;
return NTLSTATUS_SUCCESS;
}
bool overrun = controller->RirbOverrunCount != startingOverruns;
bool removed = controller->Removed || controller->SurpriseRemoved;
KeReleaseSpinlock(&controller->StateLock, flags);
if (overrun || removed)
break;
HdaStall(10);
}
flags = KeAcquireSpinlock(&controller->StateLock);
controller->CommandWaiting = false;
controller->CommandResponseReady = false;
KeReleaseSpinlock(&controller->StateLock, flags);
return NTLSTATUS_TIMEOUT;
}
// Sends one codec verb with the required retry while the command mutex is held.
static KSTATUS HdaCodecVerbSerialized(HDA_CONTROLLER* controller,
uint8_t codecAddress,
uint8_t nodeId,
uint32_t verbPayload,
uint32_t* responseOut) {
KSTATUS finalStatus = NTLSTATUS_TIMEOUT;
for (uint32_t attempt = 0; attempt < HDA_CODEC_COMMAND_ATTEMPTS; attempt++) {
controller->CodecCommandCount++;
finalStatus = HdaCodecVerbAttemptLocked(controller,
codecAddress,
nodeId,
verbPayload,
responseOut);
if (KSUCCESS(finalStatus))
break;
if (attempt + 1u < HDA_CODEC_COMMAND_ATTEMPTS) {
controller->CodecCommandRetryCount++;
HdaStall(100);
}
}
if (!KSUCCESS(finalStatus))
controller->CodecCommandFailureCount++;
return finalStatus;
}
// Sends one 20-bit codec verb payload through CORB/RIRB with one retry.
KSTATUS HdaCodecVerb(HDA_CONTROLLER* controller,
uint8_t codecAddress,
uint8_t nodeId,
uint32_t verbPayload,
uint32_t* responseOut) {
if (!controller || controller->Magic != HDA_CONTROLLER_MAGIC ||
codecAddress >= HDA_MAX_CODECS ||
(verbPayload & 0xFFF00000u) != 0 || !controller->CommandMutex)
return NTLSTATUS_INVALID_PARAMETER;
if (controller->Removed || controller->SurpriseRemoved)
return NTLSTATUS_DEVICE_REMOVED;
KSTATUS status = KeAcquireMutex(controller->CommandMutex, KE_WAIT_INFINITE);
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status))
return status;
status = HdaCodecVerbSerialized(controller,
codecAddress,
nodeId,
verbPayload,
responseOut);
KeReleaseMutex(controller->CommandMutex);
bool queueDpc = false;
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
queueDpc = controller->Started && !controller->RecoveryInProgress &&
(controller->UnsolicitedCount != 0 ||
controller->PendingRecovery != HDA_RECOVERY_NONE);
KeReleaseSpinlock(&controller->StateLock, flags);
if (queueDpc)
(void)KeInsertQueueDpc(&controller->Dpc, (void*)0, (void*)0);
return status;
}
// Verifies responding codec addresses through the architectural root node.
static KSTATUS HdaProbeCodecs(HDA_CONTROLLER* controller) {
uint16_t present = controller->CodecMask;
controller->CodecMask = 0;
for (uint8_t codec = 0; codec < HDA_MAX_CODECS; codec++) {
if ((present & (uint16_t)(1u << codec)) == 0)
continue;
uint32_t vendorId = 0;
KSTATUS status = HdaCodecVerbSerialized(
controller,
codec,
0,
HDA_VERB_GET_PARAMETER(HDA_PARAMETER_VENDOR_ID),
&vendorId);
if (!KSUCCESS(status))
continue;
controller->CodecMask |= (uint16_t)(1u << codec);
}
return controller->CodecMask ? NTLSTATUS_SUCCESS : NTLSTATUS_NOT_FOUND;
}
// Initializes the controller while no other command producer is active.
static KSTATUS HdaInitializeControllerLocked(HDA_CONTROLLER* controller) {
KSTATUS status = HdaResetControllerHardware(controller);
if (!KSUCCESS(status))
return HdaReportInitializationFailure(controller,
"controller-reset",
status);
uint16_t state = (uint16_t)(HdaRead16(controller, HDA_REG_STATESTS) & 0x7FFFu);
controller->CodecMask = state;
if (state)
HdaWrite16(controller, HDA_REG_STATESTS, state);
status = HdaInitializeCommandRings(controller);
if (!KSUCCESS(status))
return HdaReportInitializationFailure(controller,
"command-rings",
status);
if (controller->PositionBuffer) {
HdaWrite32(controller, HDA_REG_DPLBASE,
(uint32_t)controller->PositionDeviceAddress | HDA_DPLBASE_ENABLE);
HdaWrite32(controller, HDA_REG_DPUBASE,
(uint32_t)(controller->PositionDeviceAddress >> 32));
} else {
HdaWrite32(controller, HDA_REG_DPLBASE, 0);
HdaWrite32(controller, HDA_REG_DPUBASE, 0);
}
status = HdaProgramOutputDescriptor(controller);
if (!KSUCCESS(status))
return HdaReportInitializationFailure(controller,
"output-descriptor",
status);
status = HdaProbeCodecs(controller);
if (!KSUCCESS(status))
return HdaReportInitializationFailure(controller,
"codec-probe",
status);
HdaWrite32(controller,
HDA_REG_GCTL,
HdaRead32(controller, HDA_REG_GCTL) |
HDA_GCTL_RESET |
HDA_GCTL_UNSOLICITED_ENABLE);
HdaWrite32(controller, HDA_REG_INTCTL, 0);
return NTLSTATUS_SUCCESS;
}
// Resets and initializes the controller, command rings, position buffer, and output descriptor.
KSTATUS HdaInitializeController(HDA_CONTROLLER* controller) {
if (!controller || controller->Magic != HDA_CONTROLLER_MAGIC ||
!controller->Registers || !controller->CommandMutex)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = KeAcquireMutex(controller->CommandMutex, KE_WAIT_INFINITE);
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status))
return status;
status = HdaInitializeControllerLocked(controller);
KeReleaseMutex(controller->CommandMutex);
return status;
}
// Arms controller and output-stream interrupts after ISR registration.
void HdaArmInterrupts(HDA_CONTROLLER* controller) {
if (!controller || !controller->Registers || !controller->Started ||
controller->Removed || controller->SurpriseRemoved)
return;
uint32_t streamInterrupt = 1u << controller->Output.DescriptorIndex;
HdaWrite32(controller,
HDA_REG_INTCTL,
HDA_INT_GLOBAL_ENABLE |
HDA_INT_CONTROLLER_ENABLE |
streamInterrupt);
}
// Stops hardware access without releasing software resources.
void HdaQuiesceController(HDA_CONTROLLER* controller) {
if (!controller || !controller->Registers || controller->SurpriseRemoved)
return;
bool streamLocked = false;
bool commandLocked = false;
if (controller->StreamMutex) {
KSTATUS status = KeAcquireMutex(controller->StreamMutex, KE_WAIT_INFINITE);
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status))
return;
streamLocked = true;
}
if (controller->CommandMutex) {
KSTATUS status = KeAcquireMutex(controller->CommandMutex, KE_WAIT_INFINITE);
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status)) {
if (streamLocked)
KeReleaseMutex(controller->StreamMutex);
return;
}
commandLocked = true;
}
HdaWrite32(controller, HDA_REG_INTCTL, 0);
(void)HdaStopOutputHardware(controller);
HdaWrite8(controller, HDA_REG_CORBCTL, 0);
HdaWrite8(controller, HDA_REG_RIRBCTL, 0);
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
controller->Output.Running = false;
KeReleaseSpinlock(&controller->StateLock, flags);
if (commandLocked)
KeReleaseMutex(controller->CommandMutex);
if (streamLocked)
KeReleaseMutex(controller->StreamMutex);
}
// Copies one logical endpoint ring contract for codec and endpoint setup.
KSTATUS HdaDescribeOutput(HDA_CONTROLLER* controller,
uint8_t ringIndex,
HDA_OUTPUT_DESCRIPTION* descriptionOut) {
if (!controller || !descriptionOut || ringIndex >= HDA_MAX_LOGICAL_ENDPOINTS ||
!controller->Output.Rings[ringIndex].Buffer)
return NTLSTATUS_INVALID_PARAMETER;
descriptionOut->PeriodBuffer = controller->Output.Rings[ringIndex].Buffer;
descriptionOut->PeriodBufferBytes = HDA_OUTPUT_BUFFER_BYTES;
descriptionOut->PeriodCount = HDA_OUTPUT_PERIOD_COUNT;
descriptionOut->FramesPerPeriod = HDA_OUTPUT_PERIOD_FRAMES;
descriptionOut->PeriodStrideBytes = HDA_OUTPUT_PERIOD_BYTES;
descriptionOut->HardwareFormat.SampleRate = HDA_OUTPUT_SAMPLE_RATE;
descriptionOut->HardwareFormat.ChannelCount = HDA_OUTPUT_CHANNELS;
descriptionOut->HardwareFormat.SampleFormat =
IoAudioSampleS16;
descriptionOut->StreamTag = controller->Output.StreamTag;
return NTLSTATUS_SUCCESS;
}
// Selects one logical endpoint ring while the stream is stopped.
KSTATUS HdaSelectOutputRing(HDA_CONTROLLER* controller, uint8_t ringIndex) {
if (!controller || !controller->StreamMutex ||
ringIndex >= HDA_MAX_LOGICAL_ENDPOINTS ||
!controller->Output.Rings[ringIndex].Buffer)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = KeAcquireMutex(controller->StreamMutex, KE_WAIT_INFINITE);
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status))
return status;
if (controller->Output.Running) {
KeReleaseMutex(controller->StreamMutex);
return NTLSTATUS_WOULD_BLOCK;
}
controller->Output.ActiveRingIndex = ringIndex;
status = HdaProgramOutputDescriptor(controller);
KeReleaseMutex(controller->StreamMutex);
return status;
}
// Replaces the transport callbacks while the output stream is stopped.
KSTATUS HdaSetTransportCallbacks(HDA_CONTROLLER* controller,
const HDA_TRANSPORT_CALLBACKS* callbacks,
void* context) {
if (!controller || !controller->StreamMutex)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = KeAcquireMutex(controller->StreamMutex, KE_WAIT_INFINITE);
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status))
return status;
if (controller->Output.Running) {
KeReleaseMutex(controller->StreamMutex);
return NTLSTATUS_INVALID_PARAMETER;
}
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
if (callbacks)
controller->Callbacks = *callbacks;
else
memset(&controller->Callbacks, 0, sizeof(controller->Callbacks));
controller->CallbackContext = context;
KeReleaseSpinlock(&controller->StateLock, flags);
KeReleaseMutex(controller->StreamMutex);
return NTLSTATUS_SUCCESS;
}
// Starts one already programmed output descriptor without rebuilding its BDL.
static KSTATUS HdaRunPreparedOutput(HDA_CONTROLLER* controller) {
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
controller->Output.PositionRunBaseFrames = controller->Output.LastPositionFrames;
controller->Output.PositionWrapFrames = 0;
controller->Output.LastHardwarePositionBytes = 0;
controller->Output.PositionInitialized = false;
KeReleaseSpinlock(&controller->StateLock, flags);
HdaMemoryBarrier();
uint32_t control = HdaReadStreamControl(controller) | HDA_STREAM_CTL_RUN;
HdaWriteStreamControl(controller, control);
KSTATUS status = HdaWaitStreamControl(controller,
HDA_STREAM_CTL_RUN,
HDA_STREAM_CTL_RUN,
HDA_STREAM_RESET_TIMEOUT_US);
if (KSUCCESS(status)) {
flags = KeAcquireSpinlock(&controller->StateLock);
controller->Output.Running = true;
KeReleaseSpinlock(&controller->StateLock, flags);
}
return status;
}
// Starts the prepared fixed-format output stream.
KSTATUS HdaStartOutput(HDA_CONTROLLER* controller) {
if (!controller || !controller->StreamMutex)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = KeAcquireMutex(controller->StreamMutex, KE_WAIT_INFINITE);
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status))
return status;
if (!controller->Started || controller->Removed || controller->SurpriseRemoved ||
!controller->Output.Prepared) {
KeReleaseMutex(controller->StreamMutex);
return NTLSTATUS_INVALID_PARAMETER;
}
if (controller->Output.Running) {
KeReleaseMutex(controller->StreamMutex);
return NTLSTATUS_SUCCESS;
}
status = HdaProgramOutputDescriptor(controller);
if (KSUCCESS(status)) {
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
controller->Output.PendingPeriodInterrupts = 0;
controller->Output.PendingDmaErrors = 0;
KeReleaseSpinlock(&controller->StateLock, flags);
status = HdaRunPreparedOutput(controller);
}
KeReleaseMutex(controller->StreamMutex);
return status;
}
// Stops the output stream while retaining its DMA ring.
void HdaStopOutput(HDA_CONTROLLER* controller) {
if (!controller || !controller->Registers || controller->SurpriseRemoved ||
!controller->StreamMutex)
return;
KSTATUS status = KeAcquireMutex(controller->StreamMutex, KE_WAIT_INFINITE);
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status))
return;
(void)HdaQueryOutputPositionFrames(controller);
(void)HdaStopOutputHardware(controller);
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
controller->Output.Running = false;
KeReleaseSpinlock(&controller->StateLock, flags);
KeReleaseMutex(controller->StreamMutex);
}
// Returns the current byte position from the position buffer or LPIB.
static uint32_t HdaQueryOutputPositionBytesLocked(HDA_CONTROLLER* controller) {
uint32_t position;
if (controller->PositionBuffer) {
position = (uint32_t)controller->PositionBuffer[controller->Output.DescriptorIndex];
controller->Output.PositionBufferReadCount++;
} else {
position = HdaRead32(controller,
HdaOutputStreamBase(controller) + HDA_STREAM_REG_LPIB);
controller->Output.LpibReadCount++;
}
if (position > HDA_OUTPUT_BUFFER_BYTES) {
controller->Output.PositionDiscontinuityCount++;
position %= HDA_OUTPUT_BUFFER_BYTES;
} else if (position == HDA_OUTPUT_BUFFER_BYTES) {
position = 0;
}
return position;
}
// Advances the monotonic fine position while the state lock is held.
static uint64_t HdaQueryOutputPositionFramesLocked(HDA_CONTROLLER* controller) {
if (!controller->Output.Running)
return controller->Output.LastPositionFrames;
uint32_t positionBytes = HdaQueryOutputPositionBytesLocked(controller);
if (controller->Output.PositionInitialized &&
positionBytes < controller->Output.LastHardwarePositionBytes) {
controller->Output.PositionWrapFrames +=
HDA_OUTPUT_PERIOD_COUNT * HDA_OUTPUT_PERIOD_FRAMES;
}
controller->Output.PositionInitialized = true;
controller->Output.LastHardwarePositionBytes = positionBytes;
uint64_t candidate = controller->Output.PositionRunBaseFrames +
controller->Output.PositionWrapFrames +
positionBytes / HDA_OUTPUT_BYTES_PER_FRAME;
if (candidate < controller->Output.LastPositionFrames) {
controller->Output.PositionDiscontinuityCount++;
candidate = controller->Output.LastPositionFrames;
}
controller->Output.LastPositionFrames = candidate;
return candidate;
}
// Returns the monotonic fine position derived from the position buffer or LPIB.
uint64_t HdaQueryOutputPositionFrames(HDA_CONTROLLER* controller) {
if (!controller || !controller->Registers)
return 0;
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
uint64_t position = HdaQueryOutputPositionFramesLocked(controller);
KeReleaseSpinlock(&controller->StateLock, flags);
return position;
}
// Captures one controller interrupt and queues deferred processing.
void HdaHandleInterrupt(HDA_CONTROLLER* controller) {
if (!controller)
return;
NtlAtomicIncrement32(&controller->IsrActive);
if (!controller->Started || controller->Removed || controller->SurpriseRemoved)
goto finished;
uint32_t interruptStatus = HdaRead32(controller, HDA_REG_INTSTS);
if (interruptStatus == 0 || interruptStatus == UINT32_MAX)
goto finished;
bool queueDpc = false;
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
bool recovering = controller->RecoveryInProgress;
controller->InterruptCount++;
uint32_t streamBit = 1u << controller->Output.DescriptorIndex;
if (interruptStatus & streamBit) {
uint32_t base = HdaOutputStreamBase(controller);
uint8_t streamStatus = HdaRead8(controller, base + HDA_STREAM_REG_STS);
streamStatus &= HDA_STREAM_STS_INTERRUPT_MASK;
if (streamStatus) {
HdaWrite8(controller, base + HDA_STREAM_REG_STS, streamStatus);
controller->Output.InterruptCount++;
if ((streamStatus & HDA_STREAM_STS_COMPLETE) && !recovering) {
controller->Output.PendingPeriodInterrupts++;
queueDpc = true;
}
if (streamStatus & HDA_STREAM_STS_ERROR_MASK) {
controller->Output.DmaErrorCount++;
if (!recovering) {
controller->Output.PendingDmaErrors++;
queueDpc = true;
}
}
}
}
if (interruptStatus & HDA_INT_CONTROLLER_STATUS) {
controller->ControllerInterruptCount++;
(void)HdaServiceRirbLocked(controller);
if (!recovering) {
queueDpc = queueDpc || controller->UnsolicitedCount != 0 ||
controller->PendingRecovery != HDA_RECOVERY_NONE;
}
uint8_t corbStatus = HdaRead8(controller, HDA_REG_CORBSTS);
if (corbStatus & HDA_CORBSTS_MEMORY_ERROR) {
controller->CorbMemoryErrorCount++;
HdaWrite8(controller, HDA_REG_CORBSTS, corbStatus);
controller->PendingRecovery = HDA_RECOVERY_CONTROLLER;
if (!recovering)
queueDpc = true;
}
}
KeReleaseSpinlock(&controller->StateLock, flags);
if (queueDpc)
(void)KeInsertQueueDpc(&controller->Dpc, (void*)0, (void*)0);
finished:
NtlAtomicDecrement32(&controller->IsrActive);
}
// Promotes an error to stream or controller recovery using the one-second threshold.
static void HdaRecordDmaErrorsLocked(HDA_CONTROLLER* controller,
uint32_t errorCount) {
uint64_t now = HdaQueryTicks();
if (controller->StreamErrorsInWindow == 0 ||
now - controller->StreamErrorWindowStart > HDA_STREAM_ERROR_WINDOW_US) {
controller->StreamErrorWindowStart = now;
controller->StreamErrorsInWindow = 0;
}
controller->StreamErrorsInWindow += errorCount;
uint8_t requested = controller->StreamErrorsInWindow >=
HDA_STREAM_ERRORS_BEFORE_CONTROLLER_RESET
? HDA_RECOVERY_CONTROLLER
: HDA_RECOVERY_STREAM;
if (requested > controller->PendingRecovery)
controller->PendingRecovery = requested;
}
// Runs period delivery, unsolicited callbacks, and recovery scheduling at DPC level.
void HdaDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) {
(void)dpc;
(void)systemArgument1;
(void)systemArgument2;
HDA_CONTROLLER* controller = (HDA_CONTROLLER*)context;
if (!controller)
return;
NtlAtomicIncrement32(&controller->DpcActive);
if (controller->Removed || controller->SurpriseRemoved)
goto finished;
while (true) {
uint32_t periodCount = 0;
uint32_t errorCount = 0;
HDA_UNSOLICITED_RESPONSE unsolicited;
bool haveUnsolicited = false;
HDA_TRANSPORT_CALLBACKS callbacks;
void* callbackContext;
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
if (controller->Output.PendingPeriodInterrupts > 0) {
periodCount = (uint32_t)controller->Output.PendingPeriodInterrupts;
controller->Output.PendingPeriodInterrupts = 0;
}
if (controller->Output.PendingDmaErrors > 0) {
errorCount = (uint32_t)controller->Output.PendingDmaErrors;
controller->Output.PendingDmaErrors = 0;
}
if (controller->UnsolicitedCount) {
unsolicited = controller->Unsolicited[controller->UnsolicitedRead];
controller->UnsolicitedRead =
(uint8_t)((controller->UnsolicitedRead + 1u) %
HDA_MAX_UNSOLICITED_RESPONSES);
controller->UnsolicitedCount--;
haveUnsolicited = true;
}
callbacks = controller->Callbacks;
callbackContext = controller->CallbackContext;
KeReleaseSpinlock(&controller->StateLock, flags);
for (uint32_t index = 0; index < periodCount; index++) {
flags = KeAcquireSpinlock(&controller->StateLock);
uint8_t bdlIndex = controller->Output.NextCompletionBdl;
uint32_t completedPeriod = controller->Output.BdlPeriodMap[bdlIndex];
controller->Output.NextCompletionBdl =
(uint8_t)((bdlIndex + 1u) % HDA_OUTPUT_PERIOD_COUNT);
HDA_OUTPUT_RING* ring =
&controller->Output.Rings[controller->Output.ActiveRingIndex];
ring->NextLogicalPeriod =
(uint8_t)((completedPeriod + 1u) % HDA_OUTPUT_PERIOD_COUNT);
ring->PresentedFrames += HDA_OUTPUT_PERIOD_FRAMES;
controller->Output.PeriodCompletionCount++;
uint64_t presentedFrames = ring->PresentedFrames;
uint64_t finePosition = HdaQueryOutputPositionFramesLocked(controller);
uint64_t interruptCount = controller->Output.InterruptCount;
KeReleaseSpinlock(&controller->StateLock, flags);
uint64_t timestamp = HdaQueryTicks();
if (callbacks.PeriodComplete) {
callbacks.PeriodComplete(callbackContext,
completedPeriod,
presentedFrames,
finePosition,
interruptCount,
timestamp);
}
}
if (haveUnsolicited && callbacks.UnsolicitedResponse) {
callbacks.UnsolicitedResponse(
callbackContext,
(uint8_t)(unsolicited.ResponseExtra & HDA_RIRB_RESPONSE_CODEC_MASK),
unsolicited.Response,
unsolicited.ResponseExtra);
}
if (errorCount) {
flags = KeAcquireSpinlock(&controller->StateLock);
HdaRecordDmaErrorsLocked(controller, errorCount);
KeReleaseSpinlock(&controller->StateLock, flags);
}
bool signalRecovery = false;
flags = KeAcquireSpinlock(&controller->StateLock);
signalRecovery = controller->PendingRecovery != HDA_RECOVERY_NONE;
KeReleaseSpinlock(&controller->StateLock, flags);
if (signalRecovery && controller->RecoverySemaphore)
KeSignalSemaphore(controller->RecoverySemaphore);
if (periodCount == 0 && errorCount == 0 && !haveUnsolicited)
break;
}
finished:
NtlAtomicDecrement32(&controller->DpcActive);
}
// Recovers one isolated stream error without disturbing the codec topology.
static KSTATUS HdaRecoverOutputStream(HDA_CONTROLLER* controller) {
if (controller->Removed || controller->SurpriseRemoved)
return NTLSTATUS_DEVICE_REMOVED;
(void)HdaQueryOutputPositionFrames(controller);
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
bool restart = controller->Output.Running;
controller->Output.Running = false;
KeReleaseSpinlock(&controller->StateLock, flags);
KSTATUS status = HdaProgramOutputDescriptor(controller);
if (!KSUCCESS(status))
return status;
controller->Output.StreamResetCount++;
HdaArmInterrupts(controller);
if (restart)
status = HdaRunPreparedOutput(controller);
return status;
}
// Recovers command and DMA engines, then asks the codec layer to restore routing.
static KSTATUS HdaRecoverController(HDA_CONTROLLER* controller) {
if (controller->Removed || controller->SurpriseRemoved)
return NTLSTATUS_DEVICE_REMOVED;
(void)HdaQueryOutputPositionFrames(controller);
uintptr_t stateFlags = KeAcquireSpinlock(&controller->StateLock);
bool restart = controller->Output.Running;
controller->Output.Running = false;
KeReleaseSpinlock(&controller->StateLock, stateFlags);
KSTATUS status = HdaInitializeController(controller);
if (!KSUCCESS(status))
return status;
controller->ControllerResetCount++;
controller->StreamErrorWindowStart = 0;
controller->StreamErrorsInWindow = 0;
HDA_TRANSPORT_CALLBACKS callbacks;
void* callbackContext;
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
callbacks = controller->Callbacks;
callbackContext = controller->CallbackContext;
KeReleaseSpinlock(&controller->StateLock, flags);
if (callbacks.RestoreAfterControllerReset) {
status = callbacks.RestoreAfterControllerReset(callbackContext);
if (!KSUCCESS(status))
return status;
}
HdaArmInterrupts(controller);
if (restart) {
status = HdaRunPreparedOutput(controller);
if (KSUCCESS(status) && callbacks.ResumeAfterControllerReset)
status = callbacks.ResumeAfterControllerReset(callbackContext);
if (!KSUCCESS(status)) {
(void)HdaStopOutputHardware(controller);
uintptr_t stopFlags = KeAcquireSpinlock(&controller->StateLock);
controller->Output.Running = false;
KeReleaseSpinlock(&controller->StateLock, stopFlags);
}
}
return status;
}
// Waits until every pre-recovery ISR and DPC has left the controller object.
static KSTATUS HdaWaitForInterruptWorkIdle(HDA_CONTROLLER* controller) {
for (uint32_t elapsed = 0; elapsed < 100000u; elapsed += 10u) {
if (controller->IsrActive == 0 &&
controller->Dpc.Inserted == 0 && controller->DpcActive == 0)
return NTLSTATUS_SUCCESS;
if (controller->Removed || controller->SurpriseRemoved)
return NTLSTATUS_DEVICE_REMOVED;
HdaStall(10);
}
return NTLSTATUS_TIMEOUT;
}
// Runs passive-level reset work that cannot safely execute in an ISR or DPC.
static void HdaRecoveryWorker(uintptr_t argument) {
HDA_CONTROLLER* controller = (HDA_CONTROLLER*)argument;
while (controller->RecoveryWorkerRunning) {
KSTATUS waitStatus = KeWaitSemaphore(controller->RecoverySemaphore, KE_WAIT_INFINITE);
if (!KSUCCESS(waitStatus) || !controller->RecoveryWorkerRunning)
break;
uint8_t recovery;
uintptr_t flags = KeAcquireSpinlock(&controller->StateLock);
recovery = controller->PendingRecovery;
controller->PendingRecovery = HDA_RECOVERY_NONE;
controller->RecoveryInProgress = recovery != HDA_RECOVERY_NONE;
KeReleaseSpinlock(&controller->StateLock, flags);
if (recovery == HDA_RECOVERY_NONE)
continue;
if (!controller->Removed && !controller->SurpriseRemoved)
HdaWrite32(controller, HDA_REG_INTCTL, 0);
KSTATUS status = HdaWaitForInterruptWorkIdle(controller);
bool streamLocked = false;
if (KSUCCESS(status)) {
status = KeAcquireMutex(controller->StreamMutex, KE_WAIT_INFINITE);
streamLocked = KE_MUTEX_ACQUIRE_SUCCEEDED(status);
}
if (streamLocked) {
status = recovery == HDA_RECOVERY_CONTROLLER
? HdaRecoverController(controller)
: HdaRecoverOutputStream(controller);
KeReleaseMutex(controller->StreamMutex);
}
HDA_TRANSPORT_CALLBACKS callbacks;
void* callbackContext;
bool queueDpc;
bool repeatRecovery;
flags = KeAcquireSpinlock(&controller->StateLock);
controller->RecoveryInProgress = false;
callbacks = controller->Callbacks;
callbackContext = controller->CallbackContext;
queueDpc = controller->UnsolicitedCount != 0 ||
controller->Output.PendingPeriodInterrupts != 0 ||
controller->Output.PendingDmaErrors != 0;
repeatRecovery = controller->PendingRecovery != HDA_RECOVERY_NONE;
KeReleaseSpinlock(&controller->StateLock, flags);
if (!KSUCCESS(status) && status != NTLSTATUS_DEVICE_REMOVED) {
if (callbacks.TransportFailure)
callbacks.TransportFailure(callbackContext, status);
DbgPrint("HDA: recovery failed status=0x%x\n", status);
}
if (queueDpc)
(void)KeInsertQueueDpc(&controller->Dpc, (void*)0, (void*)0);
if (repeatRecovery)
KeSignalSemaphore(controller->RecoverySemaphore);
}
if (controller->RecoveryFinished)
KeSignalEvent(controller->RecoveryFinished);
}
// Starts the passive recovery worker for one controller.
KSTATUS HdaStartRecoveryWorker(HDA_CONTROLLER* controller) {
if (!controller || !controller->RecoverySemaphore || !controller->RecoveryFinished)
return NTLSTATUS_INVALID_PARAMETER;
controller->RecoveryWorkerRunning = true;
KE_THREAD_CREATE_PARAMETERS parameters =
KE_THREAD_CREATE_NAMED_NORMAL(PriorityHigh, "hda.recovery");
KSTATUS status = KeCreateKernelThread((uintptr_t)HdaRecoveryWorker,
(uintptr_t)controller,
&parameters,
&controller->RecoveryThread);
if (!KSUCCESS(status))
controller->RecoveryWorkerRunning = false;
return status;
}
// Stops and joins the passive recovery worker for one controller.
void HdaStopRecoveryWorker(HDA_CONTROLLER* controller) {
if (!controller)
return;
controller->RecoveryWorkerRunning = false;
if (controller->RecoverySemaphore)
KeSignalSemaphore(controller->RecoverySemaphore);
if (controller->RecoveryThread && controller->RecoveryFinished)
(void)KeWaitForEvent(controller->RecoveryFinished, 0);
if (controller->RecoveryThread) {
ObDereferenceObject((OBJECT*)controller->RecoveryThread);
controller->RecoveryThread = (THREAD*)0;
}
}