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

1929 lines
78 KiB
C

// Generic Intel HDA codec discovery and analog render routing.
#include "hda.h"
#include <limits.h>
#include <string.h>
#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 long WorkRunning;
volatile long PollRunning;
volatile long 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;
gHdaDdk->Timer->Stall(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;
gHdaDdk->Debug->Print(
"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)
gHdaDdk->Audio->SetEndpointAvailable(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);
gHdaDdk->Debug->Print(
"HDA: jack reroute failed kind=%u route=%u status=0x%x\n",
endpoint->Kind,
selectedRoute,
status);
if (endpoint->KernelEndpoint) {
gHdaDdk->Audio->ReportEndpointFailure(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)gHdaDdk->Scheduler->DelayThread(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) {
gHdaDdk->Audio->NotifyPeriod(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) {
gHdaDdk->Audio->ReportEndpointFailure(
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,
uint8_t 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;
KE_THREAD_CREATE_PARAMETERS parameters = KE_THREAD_CREATE_NORMAL(PriorityNormal);
status = KeCreateKernelThread((uintptr_t)HdaCodecWorkThread,
(uintptr_t)state,
&parameters,
&state->WorkThread);
if (!KSUCCESS(status)) {
state->WorkRunning = 0;
return status;
}
if (!state->HavePollingPins)
return NTLSTATUS_SUCCESS;
state->PollRunning = 1;
status = KeCreateKernelThread((uintptr_t)HdaCodecPollThread,
(uintptr_t)state,
&parameters,
&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(&registration, 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,
&registration.HardwareGainMinimumMilliDb,
&registration.HardwareGainMaximumMilliDb,
&registration.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 = gHdaDdk->Audio->RegisterEndpoint(&registration,
&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 || !gHdaDdk || !gHdaDdk->Audio || !gHdaDdk->Scheduler)
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)) {
gHdaDdk->Debug->Print(
"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;
gHdaDdk->Audio->UnregisterEndpoint(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);
}