// Generic Intel HDA codec discovery and analog render routing. #include #include "hda.h" #include #include #define HDA_CODEC_POOL_TAG MM_POOL_TAG('h', 'd', 'a', 'c') #define HDA_MAX_WIDGETS (HDA_MAX_NODE_ID + 1u) #define HDA_MAX_AUDIO_FUNCTIONS 8u #define HDA_MAX_CONNECTIONS HDA_MAX_WIDGETS #define HDA_MAX_ROUTE_NODES 32u #define HDA_MAX_ROUTES 32u #define HDA_MAX_ENDPOINT_ROUTES 16u #define HDA_INVALID_INDEX 0xFFu #define HDA_JACK_POLL_INTERVAL_MS 500u #define HDA_POWER_TIMEOUT_US 10000u #define HDA_PARAMETER_NODE_COUNT 0x04u #define HDA_PARAMETER_FUNCTION_TYPE 0x05u #define HDA_PARAMETER_AUDIO_WIDGET_CAPS 0x09u #define HDA_PARAMETER_PCM 0x0Au #define HDA_PARAMETER_STREAM_FORMATS 0x0Bu #define HDA_PARAMETER_PIN_CAPS 0x0Cu #define HDA_PARAMETER_INPUT_AMP_CAPS 0x0Du #define HDA_PARAMETER_CONNECTION_LIST_LENGTH 0x0Eu #define HDA_PARAMETER_POWER_STATES 0x0Fu #define HDA_PARAMETER_OUTPUT_AMP_CAPS 0x12u #define HDA_FUNCTION_TYPE_AUDIO 0x01u #define HDA_WIDGET_TYPE_OUTPUT 0x00u #define HDA_WIDGET_TYPE_INPUT 0x01u #define HDA_WIDGET_TYPE_MIXER 0x02u #define HDA_WIDGET_TYPE_SELECTOR 0x03u #define HDA_WIDGET_TYPE_PIN 0x04u #define HDA_WIDGET_TYPE_POWER 0x05u #define HDA_WIDGET_TYPE_VOLUME_KNOB 0x06u #define HDA_WIDGET_TYPE_BEEP 0x07u #define HDA_WIDGET_TYPE_VENDOR 0x0Fu #define HDA_WIDGET_CAP_STEREO 0x00000001u #define HDA_WIDGET_CAP_INPUT_AMP 0x00000002u #define HDA_WIDGET_CAP_OUTPUT_AMP 0x00000004u #define HDA_WIDGET_CAP_AMP_OVERRIDE 0x00000008u #define HDA_WIDGET_CAP_FORMAT_OVERRIDE 0x00000010u #define HDA_WIDGET_CAP_UNSOLICITED 0x00000080u #define HDA_WIDGET_CAP_CONNECTION_LIST 0x00000100u #define HDA_WIDGET_CAP_DIGITAL 0x00000200u #define HDA_WIDGET_CAP_POWER_CONTROL 0x00000400u #define HDA_WIDGET_CAP_TYPE_SHIFT 20u #define HDA_WIDGET_CAP_TYPE_MASK 0x00F00000u #define HDA_PCM_RATE_48000 (1u << 6) #define HDA_PCM_BITS_16 (1u << 17) #define HDA_STREAM_FORMAT_PCM (1u << 0) #define HDA_PIN_CAP_TRIGGER_REQUIRED (1u << 1) #define HDA_PIN_CAP_PRESENCE_DETECT (1u << 2) #define HDA_PIN_CAP_HEADPHONE_DRIVE (1u << 3) #define HDA_PIN_CAP_OUTPUT (1u << 4) #define HDA_PIN_CAP_HDMI (1u << 7) #define HDA_PIN_CAP_EAPD (1u << 16) #define HDA_DEFAULT_DEVICE_LINE_OUT 0x0u #define HDA_DEFAULT_DEVICE_SPEAKER 0x1u #define HDA_DEFAULT_DEVICE_HEADPHONE 0x2u #define HDA_DEFAULT_DEVICE_SPDIF_OUT 0x4u #define HDA_DEFAULT_DEVICE_DIGITAL_OTHER_OUT 0x5u #define HDA_DEFAULT_DEVICE_SHIFT 20u #define HDA_DEFAULT_DEVICE_MASK 0x00F00000u #define HDA_DEFAULT_LOCATION_SHIFT 24u #define HDA_DEFAULT_LOCATION_MASK 0x3F000000u #define HDA_DEFAULT_ASSOCIATION_SHIFT 4u #define HDA_DEFAULT_ASSOCIATION_MASK 0x000000F0u #define HDA_DEFAULT_SEQUENCE_MASK 0x0000000Fu #define HDA_DEFAULT_PORT_SHIFT 30u #define HDA_DEFAULT_PORT_MASK 0xC0000000u #define HDA_DEFAULT_PORT_NONE 0x3u #define HDA_LOCATION_GROSS_INTERNAL 0x1u #define HDA_VERB_GET_CONNECTION_LIST_ENTRY(index) (0xF0200u | ((uint32_t)(index) & 0xFFu)) #define HDA_VERB_GET_POWER_STATE 0xF0500u #define HDA_VERB_GET_PIN_SENSE 0xF0900u #define HDA_VERB_GET_CONFIG_DEFAULT 0xF1C00u #define HDA_VERB_SET_CONNECTION_SELECT(index) (0x70100u | ((uint32_t)(index) & 0xFFu)) #define HDA_VERB_SET_POWER_STATE(state) (0x70500u | ((uint32_t)(state) & 0x0Fu)) #define HDA_VERB_SET_CONVERTER_STREAM(tag, channel) \ (0x70600u | (((uint32_t)(tag) & 0x0Fu) << 4) | ((uint32_t)(channel) & 0x0Fu)) #define HDA_VERB_SET_PIN_CONTROL(value) (0x70700u | ((uint32_t)(value) & 0xFFu)) #define HDA_VERB_SET_UNSOLICITED(value) (0x70800u | ((uint32_t)(value) & 0xFFu)) #define HDA_VERB_EXECUTE_PIN_SENSE 0x70900u #define HDA_VERB_SET_EAPD(value) (0x70C00u | ((uint32_t)(value) & 0xFFu)) #define HDA_VERB_SET_CONVERTER_FORMAT(format) (0x20000u | ((uint32_t)(format) & 0xFFFFu)) #define HDA_VERB_SET_AMP(payload) (0x30000u | ((uint32_t)(payload) & 0xFFFFu)) #define HDA_PIN_CONTROL_OUTPUT_ENABLE 0x40u #define HDA_PIN_CONTROL_HEADPHONE_ENABLE 0x80u #define HDA_EAPD_ENABLE 0x02u #define HDA_UNSOLICITED_ENABLE 0x80u #define HDA_UNSOLICITED_TAG_MASK 0x3Fu #define HDA_UNSOLICITED_TAG_SHIFT 26u #define HDA_AMP_CAP_OFFSET_MASK 0x0000007Fu #define HDA_AMP_CAP_STEPS_SHIFT 8u #define HDA_AMP_CAP_STEPS_MASK 0x00007F00u #define HDA_AMP_CAP_STEP_SIZE_SHIFT 16u #define HDA_AMP_CAP_STEP_SIZE_MASK 0x007F0000u #define HDA_AMP_CAP_MUTE 0x80000000u #define HDA_AMP_SET_OUTPUT (1u << 15) #define HDA_AMP_SET_INPUT (1u << 14) #define HDA_AMP_SET_LEFT (1u << 13) #define HDA_AMP_SET_RIGHT (1u << 12) #define HDA_AMP_SET_INDEX_SHIFT 8u #define HDA_AMP_SET_MUTE (1u << 7) #define HDA_AMP_SET_GAIN_MASK 0x7Fu #define HDA_PIN_SENSE_PRESENT 0x80000000u #define HDA_POWER_STATE_D0 0u #define HDA_POWER_ACTUAL_SHIFT 4u #define HDA_POWER_ACTUAL_MASK 0xF0u typedef enum HDA_ROUTE_CLASS { HdaRouteLineOut = 1, HdaRouteSpeaker, HdaRouteHeadphone, HdaRouteOtherAnalog } HDA_ROUTE_CLASS; typedef struct HDA_WIDGET { bool Present; uint8_t NodeId; uint8_t Type; uint8_t FunctionIndex; uint32_t Capabilities; uint32_t PcmCapabilities; uint32_t StreamFormats; uint32_t PinCapabilities; uint32_t ConfigurationDefault; uint32_t InputAmpCapabilities; uint32_t OutputAmpCapabilities; uint16_t ConnectionCount; uint8_t Connections[HDA_MAX_CONNECTIONS]; bool PresenceKnown; bool PresentAtJack; bool PollPresence; bool UnsolicitedEnabled; uint8_t UnsolicitedTag; } HDA_WIDGET; typedef struct HDA_AUDIO_FUNCTION { uint8_t NodeId; uint32_t PcmCapabilities; uint32_t StreamFormats; uint32_t InputAmpCapabilities; uint32_t OutputAmpCapabilities; } HDA_AUDIO_FUNCTION; typedef struct HDA_CODEC_TOPOLOGY { uint8_t Address; uint32_t VendorId; uint8_t FunctionCount; HDA_AUDIO_FUNCTION Functions[HDA_MAX_AUDIO_FUNCTIONS]; HDA_WIDGET Widgets[HDA_MAX_WIDGETS]; } HDA_CODEC_TOPOLOGY; typedef struct HDA_ROUTE { HDA_CODEC_TOPOLOGY* Codec; HDA_ROUTE_CLASS Class; uint8_t PinNode; uint8_t ConverterNode; uint8_t NodeCount; uint8_t Nodes[HDA_MAX_ROUTE_NODES]; uint8_t ConnectionIndices[HDA_MAX_ROUTE_NODES]; uint32_t SortKey; uint8_t MasterAmpNode; uint32_t MasterAmpCapabilities; } HDA_ROUTE; typedef struct HDA_LOGICAL_ENDPOINT { struct HDA_CODEC_STATE* State; IO_AUDIO_ENDPOINT* KernelEndpoint; IO_AUDIO_ENDPOINT_KIND Kind; uint8_t RingIndex; uint8_t RouteCount; uint8_t RouteIndices[HDA_MAX_ENDPOINT_ROUTES]; uint8_t CurrentRouteIndex; bool Running; bool Starting; bool Available; bool HasHardwareGain; uint32_t HardwareAmpCapabilities; int32_t RequestedGainMilliDb; bool RequestedMute; } HDA_LOGICAL_ENDPOINT; struct HDA_CODEC_STATE { HDA_CONTROLLER* Controller; MUTEX* Lock; SPINLOCK CallbackLock; HDA_CODEC_TOPOLOGY* Codecs[HDA_MAX_CODECS]; HDA_ROUTE Routes[HDA_MAX_ROUTES]; uint8_t RouteCount; HDA_LOGICAL_ENDPOINT Endpoints[HDA_MAX_LOGICAL_ENDPOINTS]; uint8_t EndpointCount; HDA_LOGICAL_ENDPOINT* ActiveEndpoint; uint16_t UnsolicitedNodes[64]; SEMAPHORE* WorkSemaphore; EVENT* WorkFinished; EVENT* PollFinished; THREAD* WorkThread; THREAD* PollThread; volatile int32_t WorkRunning; volatile int32_t PollRunning; volatile int32_t ShuttingDown; uint64_t PendingUnsolicitedTags; bool PollRequested; bool HavePollingPins; uint64_t JackEventCount; uint64_t JackPollCount; uint64_t JackChangeCount; uint64_t RouteChangeCount; uint64_t RouteFailureCount; }; // Returns a widget only when the node was enumerated in this audio function group. static HDA_WIDGET* HdaCodecWidget(HDA_CODEC_TOPOLOGY* codec, uint8_t nodeId) { if (!codec || !codec->Widgets[nodeId].Present) return (HDA_WIDGET*)0; return &codec->Widgets[nodeId]; } // Sends one codec verb through the transport owned by this codec state. static KSTATUS HdaCodecCommand(HDA_CODEC_STATE* state, uint8_t codecAddress, uint8_t nodeId, uint32_t payload, uint32_t* responseOut) { return HdaCodecVerb(state->Controller, codecAddress, nodeId, payload, responseOut); } // Reads one architectural codec parameter. static KSTATUS HdaCodecGetParameter(HDA_CODEC_STATE* state, uint8_t codecAddress, uint8_t nodeId, uint8_t parameter, uint32_t* responseOut) { return HdaCodecCommand(state, codecAddress, nodeId, HDA_VERB_GET_PARAMETER(parameter), responseOut); } // Powers one function group or widget to D0 and waits for the actual state. static KSTATUS HdaCodecPowerD0(HDA_CODEC_STATE* state, uint8_t codecAddress, uint8_t nodeId) { KSTATUS status = HdaCodecCommand(state, codecAddress, nodeId, HDA_VERB_SET_POWER_STATE(HDA_POWER_STATE_D0), (uint32_t*)0); if (!KSUCCESS(status)) return status; for (uint32_t elapsed = 0; elapsed < HDA_POWER_TIMEOUT_US; elapsed += 10u) { uint32_t powerState = 0; status = HdaCodecCommand(state, codecAddress, nodeId, HDA_VERB_GET_POWER_STATE, &powerState); if (!KSUCCESS(status)) return status; if ((powerState & HDA_POWER_ACTUAL_MASK) == (HDA_POWER_STATE_D0 << HDA_POWER_ACTUAL_SHIFT)) return NTLSTATUS_SUCCESS; KeStallExecution(10); } return NTLSTATUS_TIMEOUT; } // Expands one short- or long-form connection list into selectable node IDs. static KSTATUS HdaCodecReadConnections(HDA_CODEC_STATE* state, HDA_CODEC_TOPOLOGY* codec, HDA_WIDGET* widget) { uint32_t lengthParameter = 0; KSTATUS status = HdaCodecGetParameter(state, codec->Address, widget->NodeId, HDA_PARAMETER_CONNECTION_LIST_LENGTH, &lengthParameter); if (!KSUCCESS(status)) return status; uint8_t encodedCount = (uint8_t)(lengthParameter & 0x7Fu); bool longForm = (lengthParameter & 0x80u) != 0; uint8_t entriesPerResponse = longForm ? 2u : 4u; uint16_t nodeMask = longForm ? 0x7FFFu : 0x007Fu; uint16_t rangeMask = longForm ? 0x8000u : 0x0080u; uint16_t previousNode = 0; bool havePrevious = false; widget->ConnectionCount = 0; for (uint8_t offset = 0; offset < encodedCount; offset += entriesPerResponse) { uint32_t response = 0; status = HdaCodecCommand(state, codec->Address, widget->NodeId, HDA_VERB_GET_CONNECTION_LIST_ENTRY(offset), &response); if (!KSUCCESS(status)) return status; for (uint8_t slot = 0; slot < entriesPerResponse && (uint8_t)(offset + slot) < encodedCount; slot++) { uint16_t raw = longForm ? (uint16_t)(response >> (slot * 16u)) : (uint16_t)((response >> (slot * 8u)) & 0xFFu); uint16_t node = raw & nodeMask; bool range = (raw & rangeMask) != 0; uint16_t first = node; if (range) { if (!havePrevious || node <= previousNode) return NTLSTATUS_DEVICE_ERROR; first = (uint16_t)(previousNode + 1u); } for (uint16_t expanded = first; expanded <= node; expanded++) { if (expanded > HDA_MAX_NODE_ID || widget->ConnectionCount == HDA_MAX_CONNECTIONS) return NTLSTATUS_NOT_SUPPORTED; widget->Connections[widget->ConnectionCount++] = (uint8_t)expanded; } previousNode = node; havePrevious = true; } } return NTLSTATUS_SUCCESS; } // Enumerates one widget and inherits function defaults when the widget has no override. static KSTATUS HdaCodecEnumerateWidget(HDA_CODEC_STATE* state, HDA_CODEC_TOPOLOGY* codec, uint8_t functionIndex, uint8_t nodeId) { if (functionIndex >= codec->FunctionCount) return NTLSTATUS_INVALID_PARAMETER; HDA_AUDIO_FUNCTION* function = &codec->Functions[functionIndex]; HDA_WIDGET* widget = &codec->Widgets[nodeId]; uint32_t capabilities = 0; KSTATUS status = HdaCodecGetParameter(state, codec->Address, nodeId, HDA_PARAMETER_AUDIO_WIDGET_CAPS, &capabilities); if (!KSUCCESS(status)) return status; memset(widget, 0, sizeof(*widget)); widget->Present = true; widget->NodeId = nodeId; widget->FunctionIndex = functionIndex; widget->Capabilities = capabilities; widget->Type = (uint8_t)((capabilities & HDA_WIDGET_CAP_TYPE_MASK) >> HDA_WIDGET_CAP_TYPE_SHIFT); widget->PcmCapabilities = function->PcmCapabilities; widget->StreamFormats = function->StreamFormats; widget->InputAmpCapabilities = function->InputAmpCapabilities; widget->OutputAmpCapabilities = function->OutputAmpCapabilities; if ((capabilities & HDA_WIDGET_CAP_FORMAT_OVERRIDE) != 0 && (widget->Type == HDA_WIDGET_TYPE_OUTPUT || widget->Type == HDA_WIDGET_TYPE_INPUT)) { status = HdaCodecGetParameter(state, codec->Address, nodeId, HDA_PARAMETER_PCM, &widget->PcmCapabilities); if (!KSUCCESS(status)) return status; status = HdaCodecGetParameter(state, codec->Address, nodeId, HDA_PARAMETER_STREAM_FORMATS, &widget->StreamFormats); if (!KSUCCESS(status)) return status; } if ((capabilities & HDA_WIDGET_CAP_AMP_OVERRIDE) != 0) { if ((capabilities & HDA_WIDGET_CAP_INPUT_AMP) != 0) { status = HdaCodecGetParameter(state, codec->Address, nodeId, HDA_PARAMETER_INPUT_AMP_CAPS, &widget->InputAmpCapabilities); if (!KSUCCESS(status)) return status; } if ((capabilities & HDA_WIDGET_CAP_OUTPUT_AMP) != 0) { status = HdaCodecGetParameter(state, codec->Address, nodeId, HDA_PARAMETER_OUTPUT_AMP_CAPS, &widget->OutputAmpCapabilities); if (!KSUCCESS(status)) return status; } } if ((capabilities & HDA_WIDGET_CAP_CONNECTION_LIST) != 0) { status = HdaCodecReadConnections(state, codec, widget); if (!KSUCCESS(status)) return status; } if (widget->Type == HDA_WIDGET_TYPE_PIN) { status = HdaCodecGetParameter(state, codec->Address, nodeId, HDA_PARAMETER_PIN_CAPS, &widget->PinCapabilities); if (!KSUCCESS(status)) return status; status = HdaCodecCommand(state, codec->Address, nodeId, HDA_VERB_GET_CONFIG_DEFAULT, &widget->ConfigurationDefault); if (!KSUCCESS(status)) return status; } return NTLSTATUS_SUCCESS; } // Enumerates one audio function group and all of its child widgets. static KSTATUS HdaCodecEnumerateAudioFunction(HDA_CODEC_STATE* state, HDA_CODEC_TOPOLOGY* codec, uint8_t functionNode) { if (codec->FunctionCount == HDA_MAX_AUDIO_FUNCTIONS) return NTLSTATUS_NOT_SUPPORTED; uint8_t functionIndex = codec->FunctionCount++; HDA_AUDIO_FUNCTION* function = &codec->Functions[functionIndex]; memset(function, 0, sizeof(*function)); function->NodeId = functionNode; KSTATUS status = HdaCodecPowerD0(state, codec->Address, functionNode); if (!KSUCCESS(status)) return status; status = HdaCodecGetParameter(state, codec->Address, functionNode, HDA_PARAMETER_PCM, &function->PcmCapabilities); if (!KSUCCESS(status)) return status; status = HdaCodecGetParameter(state, codec->Address, functionNode, HDA_PARAMETER_STREAM_FORMATS, &function->StreamFormats); if (!KSUCCESS(status)) return status; status = HdaCodecGetParameter(state, codec->Address, functionNode, HDA_PARAMETER_INPUT_AMP_CAPS, &function->InputAmpCapabilities); if (!KSUCCESS(status)) function->InputAmpCapabilities = 0; status = HdaCodecGetParameter(state, codec->Address, functionNode, HDA_PARAMETER_OUTPUT_AMP_CAPS, &function->OutputAmpCapabilities); if (!KSUCCESS(status)) function->OutputAmpCapabilities = 0; uint32_t nodeCount = 0; status = HdaCodecGetParameter(state, codec->Address, functionNode, HDA_PARAMETER_NODE_COUNT, &nodeCount); if (!KSUCCESS(status)) return status; uint8_t startNode = (uint8_t)((nodeCount >> 16) & 0xFFu); uint8_t count = (uint8_t)(nodeCount & 0xFFu); if (count == 0 || (uint16_t)startNode + count > HDA_MAX_WIDGETS) return NTLSTATUS_NOT_SUPPORTED; for (uint16_t node = startNode; node < (uint16_t)startNode + count; node++) { if (codec->Widgets[node].Present) return NTLSTATUS_DEVICE_ERROR; status = HdaCodecEnumerateWidget(state, codec, functionIndex, (uint8_t)node); if (!KSUCCESS(status)) return status; } return NTLSTATUS_SUCCESS; } // Enumerates all audio function groups exposed by one responding codec. static KSTATUS HdaCodecEnumerateOne(HDA_CODEC_STATE* state, uint8_t codecAddress) { HDA_CODEC_TOPOLOGY* codec = (HDA_CODEC_TOPOLOGY*)MmAllocatePoolTag( sizeof(HDA_CODEC_TOPOLOGY), HDA_CODEC_POOL_TAG); if (!codec) return NTLSTATUS_NO_MEMORY; memset(codec, 0, sizeof(*codec)); codec->Address = codecAddress; KSTATUS status = HdaCodecGetParameter(state, codecAddress, 0, HDA_PARAMETER_VENDOR_ID, &codec->VendorId); if (!KSUCCESS(status)) { MmFreePool(codec); return status; } uint32_t functionCount = 0; status = HdaCodecGetParameter(state, codecAddress, 0, HDA_PARAMETER_NODE_COUNT, &functionCount); if (!KSUCCESS(status)) { MmFreePool(codec); return status; } uint8_t startNode = (uint8_t)((functionCount >> 16) & 0xFFu); uint8_t count = (uint8_t)(functionCount & 0xFFu); if (count == 0 || (uint16_t)startNode + count > HDA_MAX_WIDGETS) { MmFreePool(codec); return NTLSTATUS_NOT_SUPPORTED; } for (uint16_t node = startNode; node < (uint16_t)startNode + count; node++) { uint32_t functionType = 0; status = HdaCodecGetParameter(state, codecAddress, (uint8_t)node, HDA_PARAMETER_FUNCTION_TYPE, &functionType); if (!KSUCCESS(status)) continue; if ((functionType & 0xFFu) != HDA_FUNCTION_TYPE_AUDIO) continue; status = HdaCodecEnumerateAudioFunction(state, codec, (uint8_t)node); if (!KSUCCESS(status)) { MmFreePool(codec); return status; } } if (codec->FunctionCount == 0) { MmFreePool(codec); return NTLSTATUS_NOT_FOUND; } state->Codecs[codecAddress] = codec; return NTLSTATUS_SUCCESS; } // Enumerates every responding codec without assuming any codec node IDs. static KSTATUS HdaCodecEnumerateAll(HDA_CODEC_STATE* state) { KSTATUS lastStatus = NTLSTATUS_NOT_FOUND; bool found = false; for (uint8_t codecAddress = 0; codecAddress < HDA_MAX_CODECS; codecAddress++) { if ((state->Controller->CodecMask & (uint16_t)(1u << codecAddress)) == 0) continue; KSTATUS status = HdaCodecEnumerateOne(state, codecAddress); if (KSUCCESS(status)) { found = true; continue; } lastStatus = status; DbgPrint( "HDA: codec %u topology discovery failed status=0x%x\n", codecAddress, status); } return found ? NTLSTATUS_SUCCESS : lastStatus; } // Returns whether a converter supports the fixed 48 kHz, stereo, 16-bit PCM contract. static bool HdaCodecConverterSupportsOutput(const HDA_WIDGET* widget) { return widget && widget->Type == HDA_WIDGET_TYPE_OUTPUT && (widget->Capabilities & HDA_WIDGET_CAP_DIGITAL) == 0 && (widget->Capabilities & HDA_WIDGET_CAP_STEREO) != 0 && (widget->StreamFormats & HDA_STREAM_FORMAT_PCM) != 0 && (widget->PcmCapabilities & HDA_PCM_RATE_48000) != 0 && (widget->PcmCapabilities & HDA_PCM_BITS_16) != 0; } // Classifies one analog output pin from its configuration default. static HDA_ROUTE_CLASS HdaCodecClassifyPin(const HDA_WIDGET* pin) { uint8_t device = (uint8_t)((pin->ConfigurationDefault & HDA_DEFAULT_DEVICE_MASK) >> HDA_DEFAULT_DEVICE_SHIFT); if (device == HDA_DEFAULT_DEVICE_LINE_OUT) return HdaRouteLineOut; if (device == HDA_DEFAULT_DEVICE_SPEAKER) return HdaRouteSpeaker; if (device == HDA_DEFAULT_DEVICE_HEADPHONE) return HdaRouteHeadphone; return HdaRouteOtherAnalog; } // Returns whether a pin is a connected analog render destination. static bool HdaCodecPinIsAnalogOutput(const HDA_WIDGET* pin) { if (!pin || pin->Type != HDA_WIDGET_TYPE_PIN || (pin->Capabilities & HDA_WIDGET_CAP_DIGITAL) != 0 || (pin->PinCapabilities & HDA_PIN_CAP_OUTPUT) == 0 || (pin->PinCapabilities & HDA_PIN_CAP_HDMI) != 0) return false; uint8_t port = (uint8_t)((pin->ConfigurationDefault & HDA_DEFAULT_PORT_MASK) >> HDA_DEFAULT_PORT_SHIFT); uint8_t device = (uint8_t)((pin->ConfigurationDefault & HDA_DEFAULT_DEVICE_MASK) >> HDA_DEFAULT_DEVICE_SHIFT); if (port == HDA_DEFAULT_PORT_NONE || device == HDA_DEFAULT_DEVICE_SPDIF_OUT || device == HDA_DEFAULT_DEVICE_DIGITAL_OTHER_OUT) return false; return true; } // Computes a stable route ordering from association, sequence, and node ID. static uint32_t HdaCodecRouteSortKey(const HDA_WIDGET* pin) { uint32_t association = (pin->ConfigurationDefault & HDA_DEFAULT_ASSOCIATION_MASK) >> HDA_DEFAULT_ASSOCIATION_SHIFT; uint32_t sequence = pin->ConfigurationDefault & HDA_DEFAULT_SEQUENCE_MASK; if (association == 0) association = 0x10u; return (association << 16) | (sequence << 8) | pin->NodeId; } // Selects the first controllable output amplifier on a route, preferring the converter. static void HdaCodecSelectMasterAmp(HDA_ROUTE* route) { route->MasterAmpNode = HDA_INVALID_INDEX; route->MasterAmpCapabilities = 0; for (uint8_t reverse = route->NodeCount; reverse > 0; reverse--) { uint8_t nodeId = route->Nodes[reverse - 1u]; HDA_WIDGET* widget = HdaCodecWidget(route->Codec, nodeId); if (!widget || (widget->Capabilities & HDA_WIDGET_CAP_OUTPUT_AMP) == 0) continue; route->MasterAmpNode = nodeId; route->MasterAmpCapabilities = widget->OutputAmpCapabilities; return; } } // Records one converter-to-pin route discovered by the reverse graph walk. static KSTATUS HdaCodecRecordRoute(HDA_CODEC_STATE* state, HDA_CODEC_TOPOLOGY* codec, const uint8_t* nodes, const uint8_t* connectionIndices, uint8_t nodeCount) { if (state->RouteCount == HDA_MAX_ROUTES || nodeCount < 2) return NTLSTATUS_NOT_SUPPORTED; HDA_ROUTE* route = &state->Routes[state->RouteCount]; memset(route, 0, sizeof(*route)); route->Codec = codec; route->PinNode = nodes[0]; route->ConverterNode = nodes[nodeCount - 1u]; route->NodeCount = nodeCount; memcpy(route->Nodes, nodes, nodeCount); memcpy(route->ConnectionIndices, connectionIndices, nodeCount); HDA_WIDGET* pin = HdaCodecWidget(codec, route->PinNode); route->Class = HdaCodecClassifyPin(pin); route->SortKey = HdaCodecRouteSortKey(pin); HdaCodecSelectMasterAmp(route); state->RouteCount++; return NTLSTATUS_SUCCESS; } // Walks upstream connection lists from a pin until a valid output converter is reached. static KSTATUS HdaCodecWalkRoute(HDA_CODEC_STATE* state, HDA_CODEC_TOPOLOGY* codec, uint8_t functionIndex, uint8_t nodeId, uint8_t* nodes, uint8_t* connectionIndices, uint8_t depth, bool* visited) { if (depth == HDA_MAX_ROUTE_NODES) return NTLSTATUS_NOT_SUPPORTED; HDA_WIDGET* widget = HdaCodecWidget(codec, nodeId); if (!widget || widget->FunctionIndex != functionIndex || visited[nodeId] || (widget->Capabilities & HDA_WIDGET_CAP_DIGITAL) != 0) return NTLSTATUS_NOT_FOUND; visited[nodeId] = true; nodes[depth] = nodeId; connectionIndices[depth] = HDA_INVALID_INDEX; if (HdaCodecConverterSupportsOutput(widget)) { KSTATUS status = HdaCodecRecordRoute(state, codec, nodes, connectionIndices, (uint8_t)(depth + 1u)); visited[nodeId] = false; return status; } if (widget->Type == HDA_WIDGET_TYPE_INPUT || widget->ConnectionCount == 0) { visited[nodeId] = false; return NTLSTATUS_NOT_FOUND; } KSTATUS result = NTLSTATUS_NOT_FOUND; for (uint16_t index = 0; index < widget->ConnectionCount; index++) { connectionIndices[depth] = (uint8_t)index; KSTATUS status = HdaCodecWalkRoute(state, codec, functionIndex, widget->Connections[index], nodes, connectionIndices, (uint8_t)(depth + 1u), visited); if (KSUCCESS(status)) { visited[nodeId] = false; return NTLSTATUS_SUCCESS; } if (status != NTLSTATUS_NOT_FOUND && result == NTLSTATUS_NOT_FOUND) result = status; } visited[nodeId] = false; return result; } // Finds every usable analog path from an output converter to an output pin. static KSTATUS HdaCodecBuildRoutes(HDA_CODEC_STATE* state) { uint8_t nodes[HDA_MAX_ROUTE_NODES]; uint8_t connectionIndices[HDA_MAX_ROUTE_NODES]; bool visited[HDA_MAX_WIDGETS]; memset(visited, 0, sizeof(visited)); for (uint8_t codecAddress = 0; codecAddress < HDA_MAX_CODECS; codecAddress++) { HDA_CODEC_TOPOLOGY* codec = state->Codecs[codecAddress]; if (!codec) continue; for (uint16_t node = 0; node < HDA_MAX_WIDGETS; node++) { HDA_WIDGET* pin = HdaCodecWidget(codec, (uint8_t)node); if (!HdaCodecPinIsAnalogOutput(pin)) continue; KSTATUS status = HdaCodecWalkRoute(state, codec, pin->FunctionIndex, (uint8_t)node, nodes, connectionIndices, 0, visited); if (status != NTLSTATUS_NOT_FOUND && !KSUCCESS(status)) return status; } } return state->RouteCount ? NTLSTATUS_SUCCESS : NTLSTATUS_NOT_SUPPORTED; } // Reads the current presence state of one presence-capable analog pin. static KSTATUS HdaCodecReadPinPresence(HDA_CODEC_STATE* state, HDA_CODEC_TOPOLOGY* codec, HDA_WIDGET* pin, bool* changedOut) { if (changedOut) *changedOut = false; if ((pin->PinCapabilities & HDA_PIN_CAP_PRESENCE_DETECT) == 0) { pin->PresenceKnown = true; pin->PresentAtJack = true; return NTLSTATUS_SUCCESS; } if ((pin->PinCapabilities & HDA_PIN_CAP_TRIGGER_REQUIRED) != 0) { KSTATUS status = HdaCodecCommand(state, codec->Address, pin->NodeId, HDA_VERB_EXECUTE_PIN_SENSE, (uint32_t*)0); if (!KSUCCESS(status)) return status; } uint32_t sense = 0; KSTATUS status = HdaCodecCommand(state, codec->Address, pin->NodeId, HDA_VERB_GET_PIN_SENSE, &sense); if (!KSUCCESS(status)) return status; bool present = (sense & HDA_PIN_SENSE_PRESENT) != 0; bool changed = pin->PresenceKnown && pin->PresentAtJack != present; pin->PresenceKnown = true; pin->PresentAtJack = present; if (changedOut) *changedOut = changed; return NTLSTATUS_SUCCESS; } // Returns whether the physical pin behind one route is currently usable. static bool HdaCodecRouteAvailable(const HDA_ROUTE* route) { HDA_WIDGET* pin = HdaCodecWidget(route->Codec, route->PinNode); if (!pin) return false; if ((pin->PinCapabilities & HDA_PIN_CAP_PRESENCE_DETECT) == 0) return true; return pin->PresenceKnown && pin->PresentAtJack; } // Adds one route to a logical endpoint without duplicating route indices. static KSTATUS HdaCodecEndpointAddRoute(HDA_LOGICAL_ENDPOINT* endpoint, uint8_t routeIndex) { if (endpoint->RouteCount == HDA_MAX_ENDPOINT_ROUTES) return NTLSTATUS_NOT_SUPPORTED; endpoint->RouteIndices[endpoint->RouteCount++] = routeIndex; return NTLSTATUS_SUCCESS; } // Returns whether an amplifier range fits the kernel endpoint gain contract. static bool HdaCodecAmpRangeIsRegisterable(uint32_t capabilities) { uint32_t offset = capabilities & HDA_AMP_CAP_OFFSET_MASK; uint32_t steps = (capabilities & HDA_AMP_CAP_STEPS_MASK) >> HDA_AMP_CAP_STEPS_SHIFT; uint32_t step = (((capabilities & HDA_AMP_CAP_STEP_SIZE_MASK) >> HDA_AMP_CAP_STEP_SIZE_SHIFT) + 1u) * 250u; int64_t minimum = -(int64_t)offset * step; int64_t maximum = ((int64_t)steps - offset) * step; return minimum >= -IO_AUDIO_HARDWARE_GAIN_LIMIT_MILLIDB && maximum <= IO_AUDIO_HARDWARE_GAIN_LIMIT_MILLIDB; } // Returns whether every route in one endpoint exposes the same mute-capable output amplifier. static bool HdaCodecEndpointHasCommonGain(HDA_CODEC_STATE* state, HDA_LOGICAL_ENDPOINT* endpoint, uint32_t* capabilitiesOut) { uint32_t capabilities = 0; for (uint8_t index = 0; index < endpoint->RouteCount; index++) { HDA_ROUTE* route = &state->Routes[endpoint->RouteIndices[index]]; if (route->MasterAmpNode == HDA_INVALID_INDEX || (route->MasterAmpCapabilities & HDA_AMP_CAP_MUTE) == 0) return false; if (index == 0) capabilities = route->MasterAmpCapabilities; else if (route->MasterAmpCapabilities != capabilities) return false; } if (endpoint->RouteCount == 0 || !HdaCodecAmpRangeIsRegisterable(capabilities)) return false; if (capabilitiesOut) *capabilitiesOut = capabilities; return true; } // Builds the line-out, speaker/headphone, and other-analog logical endpoint groups. static KSTATUS HdaCodecBuildEndpoints(HDA_CODEC_STATE* state) { HDA_LOGICAL_ENDPOINT groups[HDA_MAX_LOGICAL_ENDPOINTS]; memset(groups, 0, sizeof(groups)); groups[0].Kind = IoAudioEndpointLineOut; groups[1].Kind = IoAudioEndpointSpeakerHeadphone; groups[2].Kind = IoAudioEndpointOtherAnalog; for (uint8_t routeIndex = 0; routeIndex < state->RouteCount; routeIndex++) { HDA_ROUTE* route = &state->Routes[routeIndex]; uint8_t groupIndex = 2; if (route->Class == HdaRouteLineOut) groupIndex = 0; else if (route->Class == HdaRouteSpeaker || route->Class == HdaRouteHeadphone) groupIndex = 1; KSTATUS status = HdaCodecEndpointAddRoute(&groups[groupIndex], routeIndex); if (!KSUCCESS(status)) return status; } for (uint8_t groupIndex = 0; groupIndex < HDA_MAX_LOGICAL_ENDPOINTS; groupIndex++) { if (groups[groupIndex].RouteCount == 0) continue; HDA_LOGICAL_ENDPOINT* endpoint = &state->Endpoints[state->EndpointCount]; *endpoint = groups[groupIndex]; endpoint->State = state; endpoint->RingIndex = state->EndpointCount; endpoint->CurrentRouteIndex = HDA_INVALID_INDEX; endpoint->RequestedGainMilliDb = 0; endpoint->HardwareAmpCapabilities = 0; endpoint->HasHardwareGain = HdaCodecEndpointHasCommonGain( state, endpoint, &endpoint->HardwareAmpCapabilities); state->EndpointCount++; } return state->EndpointCount ? NTLSTATUS_SUCCESS : NTLSTATUS_NOT_SUPPORTED; } // Selects the best currently available route for one logical endpoint. static uint8_t HdaCodecChooseRoute(HDA_LOGICAL_ENDPOINT* endpoint) { HDA_CODEC_STATE* state = endpoint->State; uint8_t selected = HDA_INVALID_INDEX; uint32_t selectedKey = UINT32_MAX; bool selectedHeadphone = false; for (uint8_t index = 0; index < endpoint->RouteCount; index++) { uint8_t routeIndex = endpoint->RouteIndices[index]; HDA_ROUTE* route = &state->Routes[routeIndex]; if (!HdaCodecRouteAvailable(route)) continue; bool headphone = route->Class == HdaRouteHeadphone; if (endpoint->Kind == IoAudioEndpointSpeakerHeadphone) { if (headphone && !selectedHeadphone) { selected = routeIndex; selectedKey = route->SortKey; selectedHeadphone = true; continue; } if (!headphone && selectedHeadphone) continue; } if (selected == HDA_INVALID_INDEX || route->SortKey < selectedKey) { selected = routeIndex; selectedKey = route->SortKey; selectedHeadphone = headphone; } } return selected; } // Converts amplifier capabilities into a millidecibel range and step. static void HdaCodecAmpRange(uint32_t capabilities, int32_t* minimumOut, int32_t* maximumOut, uint32_t* stepOut) { uint32_t offset = capabilities & HDA_AMP_CAP_OFFSET_MASK; uint32_t steps = (capabilities & HDA_AMP_CAP_STEPS_MASK) >> HDA_AMP_CAP_STEPS_SHIFT; uint32_t step = (((capabilities & HDA_AMP_CAP_STEP_SIZE_MASK) >> HDA_AMP_CAP_STEP_SIZE_SHIFT) + 1u) * 250u; *minimumOut = -(int32_t)(offset * step); *maximumOut = (int32_t)((int64_t)steps * step - (int64_t)offset * step); *stepOut = step; } // Maps one millidecibel value to the closest supported amplifier gain index. static uint8_t HdaCodecAmpGainForMilliDb(uint32_t capabilities, int32_t gainMilliDb) { uint32_t offset = capabilities & HDA_AMP_CAP_OFFSET_MASK; uint32_t steps = (capabilities & HDA_AMP_CAP_STEPS_MASK) >> HDA_AMP_CAP_STEPS_SHIFT; uint32_t step = (((capabilities & HDA_AMP_CAP_STEP_SIZE_MASK) >> HDA_AMP_CAP_STEP_SIZE_SHIFT) + 1u) * 250u; int64_t numerator = (int64_t)gainMilliDb + (int64_t)offset * step; if (numerator <= 0) return 0; uint64_t rounded = ((uint64_t)numerator + step / 2u) / step; if (rounded > steps) rounded = steps; return (uint8_t)rounded; } // Programs both channels of one input or output amplifier. static KSTATUS HdaCodecSetAmp(HDA_CODEC_STATE* state, HDA_CODEC_TOPOLOGY* codec, uint8_t nodeId, bool output, uint8_t connectionIndex, uint8_t gain, bool muted) { uint16_t payload = (uint16_t)(HDA_AMP_SET_LEFT | HDA_AMP_SET_RIGHT | ((uint16_t)connectionIndex << HDA_AMP_SET_INDEX_SHIFT) | (gain & HDA_AMP_SET_GAIN_MASK)); payload |= output ? HDA_AMP_SET_OUTPUT : HDA_AMP_SET_INPUT; if (muted) payload |= HDA_AMP_SET_MUTE; return HdaCodecCommand(state, codec->Address, nodeId, HDA_VERB_SET_AMP(payload), (uint32_t*)0); } // Programs selectors and amplifiers along one route while its pin remains disabled. static KSTATUS HdaCodecPrepareRouteLocked(HDA_CODEC_STATE* state, HDA_ROUTE* route, int32_t gainMilliDb, bool muted) { HDA_CODEC_TOPOLOGY* codec = route->Codec; HDA_WIDGET* pin = HdaCodecWidget(codec, route->PinNode); if (!pin || pin->FunctionIndex >= codec->FunctionCount) return NTLSTATUS_DEVICE_ERROR; HDA_AUDIO_FUNCTION* function = &codec->Functions[pin->FunctionIndex]; KSTATUS status = HdaCodecPowerD0(state, codec->Address, function->NodeId); if (!KSUCCESS(status)) return status; status = HdaCodecCommand(state, codec->Address, route->PinNode, HDA_VERB_SET_PIN_CONTROL(0), (uint32_t*)0); if (!KSUCCESS(status)) return status; if ((pin->PinCapabilities & HDA_PIN_CAP_EAPD) != 0) { status = HdaCodecCommand(state, codec->Address, route->PinNode, HDA_VERB_SET_EAPD(0), (uint32_t*)0); if (!KSUCCESS(status)) return status; } for (uint8_t pathIndex = 0; pathIndex < route->NodeCount; pathIndex++) { HDA_WIDGET* widget = HdaCodecWidget(codec, route->Nodes[pathIndex]); if (!widget || widget->FunctionIndex != pin->FunctionIndex) return NTLSTATUS_DEVICE_ERROR; if ((widget->Capabilities & HDA_WIDGET_CAP_POWER_CONTROL) != 0) { status = HdaCodecPowerD0(state, codec->Address, widget->NodeId); if (!KSUCCESS(status)) return status; } if (pathIndex + 1u < route->NodeCount && widget->ConnectionCount > 1u && widget->Type != HDA_WIDGET_TYPE_MIXER) { status = HdaCodecCommand( state, codec->Address, widget->NodeId, HDA_VERB_SET_CONNECTION_SELECT(route->ConnectionIndices[pathIndex]), (uint32_t*)0); if (!KSUCCESS(status)) return status; } if ((widget->Capabilities & HDA_WIDGET_CAP_INPUT_AMP) != 0 && pathIndex + 1u < route->NodeCount) { uint32_t caps = widget->InputAmpCapabilities; uint8_t zeroDb = (uint8_t)(caps & HDA_AMP_CAP_OFFSET_MASK); for (uint16_t input = 0; input < widget->ConnectionCount; input++) { bool inputMuted = input != route->ConnectionIndices[pathIndex] && (caps & HDA_AMP_CAP_MUTE) != 0; status = HdaCodecSetAmp(state, codec, widget->NodeId, false, (uint8_t)input, zeroDb, inputMuted); if (!KSUCCESS(status)) return status; } } if ((widget->Capabilities & HDA_WIDGET_CAP_OUTPUT_AMP) != 0) { uint32_t caps = widget->OutputAmpCapabilities; uint8_t ampGain = (uint8_t)(caps & HDA_AMP_CAP_OFFSET_MASK); bool ampMuted = false; if (widget->NodeId == route->MasterAmpNode) { ampGain = HdaCodecAmpGainForMilliDb(caps, gainMilliDb); ampMuted = muted && (caps & HDA_AMP_CAP_MUTE) != 0; } status = HdaCodecSetAmp(state, codec, widget->NodeId, true, 0, ampGain, ampMuted); if (!KSUCCESS(status)) return status; } } status = HdaCodecCommand(state, codec->Address, route->ConverterNode, HDA_VERB_SET_CONVERTER_FORMAT(HDA_OUTPUT_STREAM_FORMAT), (uint32_t*)0); if (!KSUCCESS(status)) return status; return HdaCodecCommand( state, codec->Address, route->ConverterNode, HDA_VERB_SET_CONVERTER_STREAM(state->Controller->Output.StreamTag, 0), (uint32_t*)0); } // Enables the selected pin and applies the endpoint's requested gain and mute. static KSTATUS HdaCodecActivateRouteLocked(HDA_CODEC_STATE* state, HDA_ROUTE* route, int32_t gainMilliDb, bool muted) { HDA_CODEC_TOPOLOGY* codec = route->Codec; HDA_WIDGET* pin = HdaCodecWidget(codec, route->PinNode); if (!pin) return NTLSTATUS_DEVICE_ERROR; if ((pin->PinCapabilities & HDA_PIN_CAP_EAPD) != 0) { KSTATUS status = HdaCodecCommand(state, codec->Address, route->PinNode, HDA_VERB_SET_EAPD(HDA_EAPD_ENABLE), (uint32_t*)0); if (!KSUCCESS(status)) return status; } uint8_t pinControl = HDA_PIN_CONTROL_OUTPUT_ENABLE; if (route->Class == HdaRouteHeadphone || (pin->PinCapabilities & HDA_PIN_CAP_HEADPHONE_DRIVE) != 0) pinControl |= HDA_PIN_CONTROL_HEADPHONE_ENABLE; KSTATUS status = HdaCodecCommand(state, codec->Address, route->PinNode, HDA_VERB_SET_PIN_CONTROL(pinControl), (uint32_t*)0); if (!KSUCCESS(status)) return status; if (route->MasterAmpNode != HDA_INVALID_INDEX) { uint8_t gain = HdaCodecAmpGainForMilliDb(route->MasterAmpCapabilities, gainMilliDb); status = HdaCodecSetAmp(state, codec, route->MasterAmpNode, true, 0, gain, muted); } return status; } // Mutes and disconnects one route without stopping the controller DMA stream. static void HdaCodecDeactivateRouteLocked(HDA_CODEC_STATE* state, HDA_ROUTE* route) { HDA_CODEC_TOPOLOGY* codec = route->Codec; if (route->MasterAmpNode != HDA_INVALID_INDEX && (route->MasterAmpCapabilities & HDA_AMP_CAP_MUTE) != 0) { uint8_t gain = HdaCodecAmpGainForMilliDb(route->MasterAmpCapabilities, 0); (void)HdaCodecSetAmp(state, codec, route->MasterAmpNode, true, 0, gain, true); } (void)HdaCodecCommand(state, codec->Address, route->PinNode, HDA_VERB_SET_PIN_CONTROL(0), (uint32_t*)0); HDA_WIDGET* pin = HdaCodecWidget(codec, route->PinNode); if (pin && (pin->PinCapabilities & HDA_PIN_CAP_EAPD) != 0) { (void)HdaCodecCommand(state, codec->Address, route->PinNode, HDA_VERB_SET_EAPD(0), (uint32_t*)0); } (void)HdaCodecCommand(state, codec->Address, route->ConverterNode, HDA_VERB_SET_CONVERTER_STREAM(0, 0), (uint32_t*)0); } // Recomputes endpoint availability and reroutes a running combined endpoint in place. static void HdaCodecRefreshEndpointsLocked(HDA_CODEC_STATE* state) { for (uint8_t endpointIndex = 0; endpointIndex < state->EndpointCount; endpointIndex++) { HDA_LOGICAL_ENDPOINT* endpoint = &state->Endpoints[endpointIndex]; uint8_t selectedRoute = HdaCodecChooseRoute(endpoint); bool available = selectedRoute != HDA_INVALID_INDEX; if (endpoint->Available != available) { endpoint->Available = available; if (endpoint->KernelEndpoint) IoAudioSetEndpointAvailable(endpoint->KernelEndpoint, available ? 1u : 0u); } if (!endpoint->Running || endpoint->Starting || endpoint != state->ActiveEndpoint || endpoint->CurrentRouteIndex == selectedRoute) continue; if (endpoint->CurrentRouteIndex != HDA_INVALID_INDEX) HdaCodecDeactivateRouteLocked( state, &state->Routes[endpoint->CurrentRouteIndex]); endpoint->CurrentRouteIndex = selectedRoute; KSTATUS status = NTLSTATUS_SUCCESS; if (selectedRoute != HDA_INVALID_INDEX) { HDA_ROUTE* route = &state->Routes[selectedRoute]; status = HdaCodecPrepareRouteLocked(state, route, endpoint->RequestedGainMilliDb, true); if (KSUCCESS(status)) { status = HdaCodecActivateRouteLocked(state, route, endpoint->RequestedGainMilliDb, endpoint->RequestedMute); } } if (KSUCCESS(status)) { state->RouteChangeCount++; } else { state->RouteFailureCount++; uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); endpoint->Running = false; KeReleaseSpinlock(&state->CallbackLock, flags); DbgPrint( "HDA: jack reroute failed kind=%u route=%u status=0x%x\n", endpoint->Kind, selectedRoute, status); if (endpoint->KernelEndpoint) { IoAudioReportEndpointFailure(endpoint->KernelEndpoint, status, 0); } } } } // Updates one pin after an event or poll and applies any logical routing change. static void HdaCodecRefreshPinLocked(HDA_CODEC_STATE* state, HDA_CODEC_TOPOLOGY* codec, HDA_WIDGET* pin, bool fromPoll) { bool changed = false; KSTATUS status = HdaCodecReadPinPresence(state, codec, pin, &changed); if (!KSUCCESS(status)) { return; } if (fromPoll) state->JackPollCount++; else state->JackEventCount++; if (!changed) return; state->JackChangeCount++; HdaCodecRefreshEndpointsLocked(state); } // Processes pending unsolicited tags and one requested polling pass. static void HdaCodecProcessJackWork(HDA_CODEC_STATE* state) { KSTATUS status = KeAcquireMutex(state->Lock, KE_WAIT_INFINITE); if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status)) return; uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); uint64_t tags = state->PendingUnsolicitedTags; bool poll = state->PollRequested; state->PendingUnsolicitedTags = 0; state->PollRequested = false; KeReleaseSpinlock(&state->CallbackLock, flags); while (tags) { uint8_t tag = 0; while (tag < 64u && (tags & (1ull << tag)) == 0) tag++; if (tag == 64u) break; tags &= ~(1ull << tag); uint16_t encoded = state->UnsolicitedNodes[tag]; uint8_t codecAddress = (uint8_t)(encoded >> 8); uint8_t nodeId = (uint8_t)encoded; HDA_CODEC_TOPOLOGY* codec = codecAddress < HDA_MAX_CODECS ? state->Codecs[codecAddress] : (HDA_CODEC_TOPOLOGY*)0; HDA_WIDGET* pin = HdaCodecWidget(codec, nodeId); if (pin) HdaCodecRefreshPinLocked(state, codec, pin, false); } if (poll) { for (uint8_t codecAddress = 0; codecAddress < HDA_MAX_CODECS; codecAddress++) { HDA_CODEC_TOPOLOGY* codec = state->Codecs[codecAddress]; if (!codec) continue; for (uint16_t node = 0; node < HDA_MAX_WIDGETS; node++) { HDA_WIDGET* pin = HdaCodecWidget(codec, (uint8_t)node); if (pin && pin->PollPresence) HdaCodecRefreshPinLocked(state, codec, pin, true); } } } KeReleaseMutex(state->Lock); } // Runs immediate jack-event work at passive level. static void HdaCodecWorkThread(uintptr_t argument) { HDA_CODEC_STATE* state = (HDA_CODEC_STATE*)argument; while (state->WorkRunning) { KSTATUS status = KeWaitSemaphore(state->WorkSemaphore, KE_WAIT_INFINITE); if (!KSUCCESS(status) || !state->WorkRunning) break; HdaCodecProcessJackWork(state); } KeSignalEvent(state->WorkFinished); } // Requests a fallback presence polling pass every 500 milliseconds. static void HdaCodecPollThread(uintptr_t argument) { HDA_CODEC_STATE* state = (HDA_CODEC_STATE*)argument; while (state->PollRunning) { (void)KeDelayThread(HDA_JACK_POLL_INTERVAL_MS); if (!state->PollRunning) break; uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); state->PollRequested = true; KeReleaseSpinlock(&state->CallbackLock, flags); KeSignalSemaphore(state->WorkSemaphore); } KeSignalEvent(state->PollFinished); } // Queues an unsolicited pin event for passive-level sense and route processing. static void HdaCodecUnsolicitedResponse(void* context, uint8_t codecAddress, uint32_t response, uint32_t responseExtra) { (void)responseExtra; HDA_CODEC_STATE* state = (HDA_CODEC_STATE*)context; if (!state || state->ShuttingDown) return; uint8_t tag = (uint8_t)((response >> HDA_UNSOLICITED_TAG_SHIFT) & HDA_UNSOLICITED_TAG_MASK); if (tag == 0 || state->UnsolicitedNodes[tag] == 0) return; uint16_t encoded = state->UnsolicitedNodes[tag]; if ((uint8_t)(encoded >> 8) != codecAddress) return; uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); state->PendingUnsolicitedTags |= 1ull << tag; KeReleaseSpinlock(&state->CallbackLock, flags); KeSignalSemaphore(state->WorkSemaphore); } // Forwards one hardware period completion to the currently active logical endpoint. static void HdaCodecPeriodComplete(void* context, uint32_t completedPeriodIndex, uint64_t presentedFrames, uint64_t finePositionFrames, uint64_t interruptCount, uint64_t timestampTicks) { HDA_CODEC_STATE* state = (HDA_CODEC_STATE*)context; if (!state || state->ShuttingDown) return; IO_AUDIO_ENDPOINT* endpoint = (IO_AUDIO_ENDPOINT*)0; uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); if (state->ActiveEndpoint && state->ActiveEndpoint->Running) endpoint = state->ActiveEndpoint->KernelEndpoint; KeReleaseSpinlock(&state->CallbackLock, flags); if (endpoint) { IoAudioNotifyPeriod(endpoint, completedPeriodIndex, presentedFrames, finePositionFrames, interruptCount, timestampTicks); } } // Restores power, jack events, and muted route programming after controller reset. static KSTATUS HdaCodecRestoreAfterControllerReset(void* context) { HDA_CODEC_STATE* state = (HDA_CODEC_STATE*)context; if (!state || state->ShuttingDown) return NTLSTATUS_DEVICE_REMOVED; KSTATUS status = KeAcquireMutex(state->Lock, KE_WAIT_INFINITE); if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status)) return status; for (uint8_t codecAddress = 0; codecAddress < HDA_MAX_CODECS; codecAddress++) { HDA_CODEC_TOPOLOGY* codec = state->Codecs[codecAddress]; if (!codec) continue; for (uint8_t functionIndex = 0; functionIndex < codec->FunctionCount; functionIndex++) { status = HdaCodecPowerD0( state, codecAddress, codec->Functions[functionIndex].NodeId); if (!KSUCCESS(status)) break; } if (!KSUCCESS(status)) break; for (uint16_t node = 0; node < HDA_MAX_WIDGETS; node++) { HDA_WIDGET* pin = HdaCodecWidget(codec, (uint8_t)node); if (!pin || !pin->UnsolicitedEnabled) continue; status = HdaCodecCommand( state, codecAddress, pin->NodeId, HDA_VERB_SET_UNSOLICITED(HDA_UNSOLICITED_ENABLE | pin->UnsolicitedTag), (uint32_t*)0); if (!KSUCCESS(status)) break; } if (!KSUCCESS(status)) break; } if (KSUCCESS(status) && state->ActiveEndpoint && state->ActiveEndpoint->Running && state->ActiveEndpoint->CurrentRouteIndex != HDA_INVALID_INDEX) { HDA_LOGICAL_ENDPOINT* endpoint = state->ActiveEndpoint; status = HdaCodecPrepareRouteLocked( state, &state->Routes[endpoint->CurrentRouteIndex], endpoint->RequestedGainMilliDb, true); } KeReleaseMutex(state->Lock); return status; } // Re-enables the physical route after controller recovery has restarted DMA. static KSTATUS HdaCodecResumeAfterControllerReset(void* context) { HDA_CODEC_STATE* state = (HDA_CODEC_STATE*)context; if (!state || state->ShuttingDown) return NTLSTATUS_DEVICE_REMOVED; KSTATUS status = KeAcquireMutex(state->Lock, KE_WAIT_INFINITE); if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status)) return status; if (state->ActiveEndpoint && state->ActiveEndpoint->Running && state->ActiveEndpoint->CurrentRouteIndex != HDA_INVALID_INDEX) { HDA_LOGICAL_ENDPOINT* endpoint = state->ActiveEndpoint; status = HdaCodecActivateRouteLocked( state, &state->Routes[endpoint->CurrentRouteIndex], endpoint->RequestedGainMilliDb, endpoint->RequestedMute); } KeReleaseMutex(state->Lock); return status; } // Fails every logical endpoint after unrecoverable transport failure. static void HdaCodecTransportFailure(void* context, KSTATUS failureStatus) { HDA_CODEC_STATE* state = (HDA_CODEC_STATE*)context; if (!state || state->ShuttingDown) return; uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); if (state->ActiveEndpoint) state->ActiveEndpoint->Running = false; KeReleaseSpinlock(&state->CallbackLock, flags); for (uint8_t index = 0; index < state->EndpointCount; index++) { if (state->Endpoints[index].KernelEndpoint) { IoAudioReportEndpointFailure( state->Endpoints[index].KernelEndpoint, failureStatus, 0); } } } // Starts DMA for one logical endpoint after programming its selected analog route. static KSTATUS HdaCodecEndpointStart(void* providerContext) { HDA_LOGICAL_ENDPOINT* endpoint = (HDA_LOGICAL_ENDPOINT*)providerContext; if (!endpoint || !endpoint->State) return NTLSTATUS_INVALID_PARAMETER; HDA_CODEC_STATE* state = endpoint->State; if (state->ShuttingDown) return NTLSTATUS_DEVICE_REMOVED; KSTATUS status = HdaSelectOutputRing(state->Controller, endpoint->RingIndex); if (!KSUCCESS(status)) return status; status = KeAcquireMutex(state->Lock, KE_WAIT_INFINITE); if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status)) return status; if (state->ActiveEndpoint && state->ActiveEndpoint != endpoint) { KeReleaseMutex(state->Lock); return NTLSTATUS_WOULD_BLOCK; } uint8_t routeIndex = HdaCodecChooseRoute(endpoint); if (routeIndex == HDA_INVALID_INDEX) { KeReleaseMutex(state->Lock); return NTLSTATUS_NOT_FOUND; } endpoint->Starting = true; endpoint->CurrentRouteIndex = routeIndex; uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); state->ActiveEndpoint = endpoint; KeReleaseSpinlock(&state->CallbackLock, flags); status = HdaCodecPrepareRouteLocked(state, &state->Routes[routeIndex], endpoint->RequestedGainMilliDb, true); KeReleaseMutex(state->Lock); if (!KSUCCESS(status)) goto failed; status = HdaStartOutput(state->Controller); if (!KSUCCESS(status)) goto failed; status = KeAcquireMutex(state->Lock, KE_WAIT_INFINITE); if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status)) goto failed_after_start; endpoint->Starting = false; routeIndex = HdaCodecChooseRoute(endpoint); if (routeIndex != endpoint->CurrentRouteIndex) { HdaCodecDeactivateRouteLocked( state, &state->Routes[endpoint->CurrentRouteIndex]); endpoint->CurrentRouteIndex = routeIndex; if (routeIndex != HDA_INVALID_INDEX) { status = HdaCodecPrepareRouteLocked(state, &state->Routes[routeIndex], endpoint->RequestedGainMilliDb, true); } } if (KSUCCESS(status) && routeIndex == HDA_INVALID_INDEX) status = NTLSTATUS_NOT_FOUND; if (KSUCCESS(status)) { status = HdaCodecActivateRouteLocked(state, &state->Routes[routeIndex], endpoint->RequestedGainMilliDb, endpoint->RequestedMute); } flags = KeAcquireSpinlock(&state->CallbackLock); endpoint->Running = KSUCCESS(status); KeReleaseSpinlock(&state->CallbackLock, flags); KeReleaseMutex(state->Lock); if (KSUCCESS(status)) return NTLSTATUS_SUCCESS; failed_after_start: HdaStopOutput(state->Controller); failed: if (KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(state->Lock, KE_WAIT_INFINITE))) { if (endpoint->CurrentRouteIndex != HDA_INVALID_INDEX) { HdaCodecDeactivateRouteLocked( state, &state->Routes[endpoint->CurrentRouteIndex]); } endpoint->Starting = false; endpoint->CurrentRouteIndex = HDA_INVALID_INDEX; flags = KeAcquireSpinlock(&state->CallbackLock); endpoint->Running = false; if (state->ActiveEndpoint == endpoint) state->ActiveEndpoint = (HDA_LOGICAL_ENDPOINT*)0; KeReleaseSpinlock(&state->CallbackLock, flags); KeReleaseMutex(state->Lock); } return status; } // Mutes one logical endpoint and stops the shared output stream. static void HdaCodecEndpointStop(void* providerContext) { HDA_LOGICAL_ENDPOINT* endpoint = (HDA_LOGICAL_ENDPOINT*)providerContext; if (!endpoint || !endpoint->State) return; HDA_CODEC_STATE* state = endpoint->State; KSTATUS status = KeAcquireMutex(state->Lock, KE_WAIT_INFINITE); if (KE_MUTEX_ACQUIRE_SUCCEEDED(status)) { if (!state->ShuttingDown && state->ActiveEndpoint == endpoint && endpoint->CurrentRouteIndex != HDA_INVALID_INDEX) { HdaCodecDeactivateRouteLocked( state, &state->Routes[endpoint->CurrentRouteIndex]); } endpoint->Starting = false; endpoint->CurrentRouteIndex = HDA_INVALID_INDEX; uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); endpoint->Running = false; if (state->ActiveEndpoint == endpoint) state->ActiveEndpoint = (HDA_LOGICAL_ENDPOINT*)0; KeReleaseSpinlock(&state->CallbackLock, flags); KeReleaseMutex(state->Lock); } if (!state->ShuttingDown || !state->Controller->SurpriseRemoved) HdaStopOutput(state->Controller); } // Stores or applies one endpoint hardware gain and mute request. static KSTATUS HdaCodecEndpointSetGain(void* providerContext, int32_t gainMilliDb, bool muted) { HDA_LOGICAL_ENDPOINT* endpoint = (HDA_LOGICAL_ENDPOINT*)providerContext; if (!endpoint || !endpoint->State || !endpoint->HasHardwareGain) return NTLSTATUS_INVALID_PARAMETER; HDA_CODEC_STATE* state = endpoint->State; if (state->ShuttingDown) return NTLSTATUS_DEVICE_REMOVED; KSTATUS status = KeAcquireMutex(state->Lock, KE_WAIT_INFINITE); if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status)) return status; endpoint->RequestedGainMilliDb = gainMilliDb; endpoint->RequestedMute = muted != 0; if (endpoint->Running && state->ActiveEndpoint == endpoint && endpoint->CurrentRouteIndex != HDA_INVALID_INDEX) { HDA_ROUTE* route = &state->Routes[endpoint->CurrentRouteIndex]; uint8_t gain = HdaCodecAmpGainForMilliDb(route->MasterAmpCapabilities, gainMilliDb); status = HdaCodecSetAmp(state, route->Codec, route->MasterAmpNode, true, 0, gain, muted != 0); } KeReleaseMutex(state->Lock); return status; } // Configures initial jack state and chooses event or polling delivery per pin. static KSTATUS HdaCodecInitializeJacks(HDA_CODEC_STATE* state) { uint8_t nextTag = 1; for (uint8_t codecAddress = 0; codecAddress < HDA_MAX_CODECS; codecAddress++) { HDA_CODEC_TOPOLOGY* codec = state->Codecs[codecAddress]; if (!codec) continue; for (uint16_t node = 0; node < HDA_MAX_WIDGETS; node++) { HDA_WIDGET* pin = HdaCodecWidget(codec, (uint8_t)node); if (!HdaCodecPinIsAnalogOutput(pin)) continue; bool changed = false; KSTATUS status = HdaCodecReadPinPresence(state, codec, pin, &changed); if (!KSUCCESS(status)) return status; if ((pin->PinCapabilities & HDA_PIN_CAP_PRESENCE_DETECT) == 0) continue; bool eventCapable = (pin->Capabilities & HDA_WIDGET_CAP_UNSOLICITED) != 0 && nextTag <= HDA_UNSOLICITED_TAG_MASK; if (eventCapable) { status = HdaCodecCommand( state, codecAddress, pin->NodeId, HDA_VERB_SET_UNSOLICITED(HDA_UNSOLICITED_ENABLE | nextTag), (uint32_t*)0); if (KSUCCESS(status)) { pin->UnsolicitedEnabled = true; pin->UnsolicitedTag = nextTag; state->UnsolicitedNodes[nextTag] = (uint16_t)(((uint16_t)codecAddress << 8) | pin->NodeId); nextTag++; } } // Polling remains active as a watchdog because event-capable pins can lose notifications. pin->PollPresence = true; state->HavePollingPins = true; } } HdaCodecRefreshEndpointsLocked(state); return NTLSTATUS_SUCCESS; } // Starts the passive jack worker and optional 500 millisecond polling ticker. static KSTATUS HdaCodecStartWorkers(HDA_CODEC_STATE* state) { KSTATUS status = KeSemaphoreCreate(0, 1, &state->WorkSemaphore); if (!KSUCCESS(status)) return status; status = KeEventCreate(EventManualReset, 0, &state->WorkFinished); if (!KSUCCESS(status)) return status; status = KeEventCreate(EventManualReset, 0, &state->PollFinished); if (!KSUCCESS(status)) return status; state->WorkRunning = 1; const KE_THREAD_CREATE_PARAMETERS workParameters = KE_THREAD_CREATE_NAMED_NORMAL(PriorityNormal, "hda.codec.work"); status = KeCreateKernelThread((uintptr_t)HdaCodecWorkThread, (uintptr_t)state, &workParameters, &state->WorkThread); if (!KSUCCESS(status)) { state->WorkRunning = 0; return status; } if (!state->HavePollingPins) return NTLSTATUS_SUCCESS; state->PollRunning = 1; const KE_THREAD_CREATE_PARAMETERS pollParameters = KE_THREAD_CREATE_NAMED_NORMAL(PriorityNormal, "hda.codec.poll"); status = KeCreateKernelThread((uintptr_t)HdaCodecPollThread, (uintptr_t)state, &pollParameters, &state->PollThread); if (!KSUCCESS(status)) { state->PollRunning = 0; state->WorkRunning = 0; KeSignalSemaphore(state->WorkSemaphore); (void)KeWaitForEvent(state->WorkFinished, 0); ObDereferenceObject((OBJECT*)state->WorkThread); state->WorkThread = (THREAD*)0; return status; } return NTLSTATUS_SUCCESS; } // Registers every discovered logical endpoint against its dedicated DMA ring. static KSTATUS HdaCodecRegisterEndpoints(HDA_CODEC_STATE* state) { for (uint8_t index = 0; index < state->EndpointCount; index++) { HDA_LOGICAL_ENDPOINT* endpoint = &state->Endpoints[index]; HDA_OUTPUT_DESCRIPTION output; KSTATUS status = HdaDescribeOutput(state->Controller, endpoint->RingIndex, &output); if (!KSUCCESS(status)) return status; IO_AUDIO_ENDPOINT_REGISTRATION registration; memset(®istration, 0, sizeof(registration)); registration.Device = state->Controller->Fdo; registration.Kind = endpoint->Kind; registration.InitiallyAvailable = endpoint->Available ? 1u : 0u; registration.ProviderContext = endpoint; registration.Operations.Start = HdaCodecEndpointStart; registration.Operations.Stop = HdaCodecEndpointStop; registration.PeriodBuffer = output.PeriodBuffer; registration.PeriodBufferBytes = output.PeriodBufferBytes; registration.PeriodCount = output.PeriodCount; registration.FramesPerPeriod = output.FramesPerPeriod; registration.PeriodStrideBytes = output.PeriodStrideBytes; registration.HardwareFormat = output.HardwareFormat; if (endpoint->HasHardwareGain) { registration.Capabilities |= IO_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN; registration.Operations.SetGain = HdaCodecEndpointSetGain; HdaCodecAmpRange(endpoint->HardwareAmpCapabilities, ®istration.HardwareGainMinimumMilliDb, ®istration.HardwareGainMaximumMilliDb, ®istration.HardwareGainStepMilliDb); } for (uint8_t routeOffset = 0; routeOffset < endpoint->RouteCount; routeOffset++) { HDA_ROUTE* route = &state->Routes[endpoint->RouteIndices[routeOffset]]; HDA_WIDGET* pin = HdaCodecWidget(route->Codec, route->PinNode); if (pin && (pin->PinCapabilities & HDA_PIN_CAP_PRESENCE_DETECT) != 0) { registration.Capabilities |= IO_AUDIO_ENDPOINT_CAP_JACK_SENSE; break; } } status = IoAudioRegisterEndpoint(®istration, &endpoint->KernelEndpoint); if (!KSUCCESS(status)) return status; } return NTLSTATUS_SUCCESS; } // Stops worker threads and releases their thread references. static void HdaCodecStopWorkers(HDA_CODEC_STATE* state) { if (!state) return; state->PollRunning = 0; state->WorkRunning = 0; if (state->WorkSemaphore) KeSignalSemaphore(state->WorkSemaphore); if (state->PollThread && state->PollFinished) (void)KeWaitForEvent(state->PollFinished, 0); if (state->WorkThread && state->WorkFinished) (void)KeWaitForEvent(state->WorkFinished, 0); if (state->PollThread) { ObDereferenceObject((OBJECT*)state->PollThread); state->PollThread = (THREAD*)0; } if (state->WorkThread) { ObDereferenceObject((OBJECT*)state->WorkThread); state->WorkThread = (THREAD*)0; } } // Discovers codec topology and registers usable analog render endpoints. KSTATUS HdaCodecInitialize(HDA_CONTROLLER* controller) { if (!controller) return NTLSTATUS_INVALID_PARAMETER; HDA_CODEC_STATE* state = (HDA_CODEC_STATE*)MmAllocatePoolTag( sizeof(HDA_CODEC_STATE), HDA_CODEC_POOL_TAG); if (!state) return NTLSTATUS_NO_MEMORY; memset(state, 0, sizeof(*state)); state->Controller = controller; state->CallbackLock = (SPINLOCK)SPINLOCK_INIT; controller->Codec = state; for (uint8_t index = 0; index < HDA_MAX_LOGICAL_ENDPOINTS; index++) state->Endpoints[index].CurrentRouteIndex = HDA_INVALID_INDEX; KSTATUS status = KeMutexCreate(&state->Lock); if (!KSUCCESS(status)) goto failed; status = HdaCodecEnumerateAll(state); if (!KSUCCESS(status)) goto failed; status = HdaCodecBuildRoutes(state); if (!KSUCCESS(status)) { DbgPrint( "HDA: no usable 48000 Hz, stereo, 16-bit analog converter-to-pin route\n"); goto failed; } status = HdaCodecBuildEndpoints(state); if (!KSUCCESS(status)) goto failed; status = KeAcquireMutex(state->Lock, KE_WAIT_INFINITE); if (!KE_MUTEX_ACQUIRE_SUCCEEDED(status)) goto failed; status = HdaCodecInitializeJacks(state); KeReleaseMutex(state->Lock); if (!KSUCCESS(status)) goto failed; HDA_TRANSPORT_CALLBACKS callbacks; memset(&callbacks, 0, sizeof(callbacks)); callbacks.PeriodComplete = HdaCodecPeriodComplete; callbacks.UnsolicitedResponse = HdaCodecUnsolicitedResponse; callbacks.RestoreAfterControllerReset = HdaCodecRestoreAfterControllerReset; callbacks.ResumeAfterControllerReset = HdaCodecResumeAfterControllerReset; callbacks.TransportFailure = HdaCodecTransportFailure; status = HdaSetTransportCallbacks(controller, &callbacks, state); if (!KSUCCESS(status)) goto failed; status = HdaCodecStartWorkers(state); if (!KSUCCESS(status)) goto failed; status = HdaCodecRegisterEndpoints(state); if (!KSUCCESS(status)) goto failed; return NTLSTATUS_SUCCESS; failed: HdaCodecStop(controller, IoAudioOrderlyRemoval); HdaCodecDestroy(controller); return status; } // Stops jack workers and unregisters codec endpoints. void HdaCodecStop(HDA_CONTROLLER* controller, IO_AUDIO_REMOVAL_MODE mode) { if (!controller || !controller->Codec) return; HDA_CODEC_STATE* state = controller->Codec; state->ShuttingDown = 1; HdaCodecStopWorkers(state); if (mode == IoAudioOrderlyRemoval && state->Lock) { KSTATUS status = KeAcquireMutex(state->Lock, KE_WAIT_INFINITE); if (KE_MUTEX_ACQUIRE_SUCCEEDED(status)) { HDA_LOGICAL_ENDPOINT* active = state->ActiveEndpoint; if (active && active->CurrentRouteIndex != HDA_INVALID_INDEX) { HdaCodecDeactivateRouteLocked( state, &state->Routes[active->CurrentRouteIndex]); active->CurrentRouteIndex = HDA_INVALID_INDEX; } uintptr_t flags = KeAcquireSpinlock(&state->CallbackLock); if (active) active->Running = false; state->ActiveEndpoint = (HDA_LOGICAL_ENDPOINT*)0; KeReleaseSpinlock(&state->CallbackLock, flags); KeReleaseMutex(state->Lock); } } for (uint8_t index = state->EndpointCount; index > 0; index--) { HDA_LOGICAL_ENDPOINT* endpoint = &state->Endpoints[index - 1u]; if (!endpoint->KernelEndpoint) continue; IoAudioUnregisterEndpoint(endpoint->KernelEndpoint, mode); endpoint->KernelEndpoint = (IO_AUDIO_ENDPOINT*)0; } if (mode == IoAudioOrderlyRemoval && state->Lock) { KSTATUS status = KeAcquireMutex(state->Lock, KE_WAIT_INFINITE); if (KE_MUTEX_ACQUIRE_SUCCEEDED(status)) { for (uint8_t codecAddress = 0; codecAddress < HDA_MAX_CODECS; codecAddress++) { HDA_CODEC_TOPOLOGY* codec = state->Codecs[codecAddress]; if (!codec) continue; for (uint16_t node = 0; node < HDA_MAX_WIDGETS; node++) { HDA_WIDGET* pin = HdaCodecWidget(codec, (uint8_t)node); if (!pin || !pin->UnsolicitedEnabled) continue; (void)HdaCodecCommand(state, codecAddress, pin->NodeId, HDA_VERB_SET_UNSOLICITED(0), (uint32_t*)0); pin->UnsolicitedEnabled = false; } } KeReleaseMutex(state->Lock); } (void)HdaSetTransportCallbacks(controller, (const HDA_TRANSPORT_CALLBACKS*)0, (void*)0); } } // Releases codec topology storage after interrupt work is quiescent. void HdaCodecDestroy(HDA_CONTROLLER* controller) { if (!controller || !controller->Codec) return; HDA_CODEC_STATE* state = controller->Codec; controller->Codec = (HDA_CODEC_STATE*)0; uintptr_t flags = KeAcquireSpinlock(&controller->StateLock); if (controller->CallbackContext == state) { memset(&controller->Callbacks, 0, sizeof(controller->Callbacks)); controller->CallbackContext = (void*)0; } KeReleaseSpinlock(&controller->StateLock, flags); for (uint8_t codecAddress = 0; codecAddress < HDA_MAX_CODECS; codecAddress++) { MmFreePool(state->Codecs[codecAddress]); state->Codecs[codecAddress] = (HDA_CODEC_TOPOLOGY*)0; } if (state->WorkSemaphore) ObDereferenceObject(&state->WorkSemaphore->Header); if (state->WorkFinished) ObDereferenceObject(&state->WorkFinished->Header); if (state->PollFinished) ObDereferenceObject(&state->PollFinished->Header); if (state->Lock) ObDereferenceObject(&state->Lock->Header); MmFreePool(state); }