Files
nt-lite/code/drivers/audio/hda/hda_codec.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

1932 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;
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(&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);
}