1929 lines
78 KiB
C
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,
|
|
¶meters,
|
|
&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,
|
|
¶meters,
|
|
&state->PollThread);
|
|
if (!KSUCCESS(status)) {
|
|
state->PollRunning = 0;
|
|
state->WorkRunning = 0;
|
|
KeSignalSemaphore(state->WorkSemaphore);
|
|
(void)KeWaitForEvent(state->WorkFinished, 0);
|
|
ObDereferenceObject((OBJECT*)state->WorkThread);
|
|
state->WorkThread = (THREAD*)0;
|
|
return status;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Registers every discovered logical endpoint against its dedicated DMA ring.
|
|
static KSTATUS HdaCodecRegisterEndpoints(HDA_CODEC_STATE* state) {
|
|
for (uint8_t index = 0; index < state->EndpointCount; index++) {
|
|
HDA_LOGICAL_ENDPOINT* endpoint = &state->Endpoints[index];
|
|
HDA_OUTPUT_DESCRIPTION output;
|
|
KSTATUS status = HdaDescribeOutput(state->Controller,
|
|
endpoint->RingIndex,
|
|
&output);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
IO_AUDIO_ENDPOINT_REGISTRATION registration;
|
|
memset(®istration, 0, sizeof(registration));
|
|
registration.Device = state->Controller->Fdo;
|
|
registration.Kind = endpoint->Kind;
|
|
registration.InitiallyAvailable = endpoint->Available ? 1u : 0u;
|
|
registration.ProviderContext = endpoint;
|
|
registration.Operations.Start = HdaCodecEndpointStart;
|
|
registration.Operations.Stop = HdaCodecEndpointStop;
|
|
registration.PeriodBuffer = output.PeriodBuffer;
|
|
registration.PeriodBufferBytes = output.PeriodBufferBytes;
|
|
registration.PeriodCount = output.PeriodCount;
|
|
registration.FramesPerPeriod = output.FramesPerPeriod;
|
|
registration.PeriodStrideBytes = output.PeriodStrideBytes;
|
|
registration.HardwareFormat = output.HardwareFormat;
|
|
if (endpoint->HasHardwareGain) {
|
|
registration.Capabilities |= IO_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN;
|
|
registration.Operations.SetGain = HdaCodecEndpointSetGain;
|
|
HdaCodecAmpRange(endpoint->HardwareAmpCapabilities,
|
|
®istration.HardwareGainMinimumMilliDb,
|
|
®istration.HardwareGainMaximumMilliDb,
|
|
®istration.HardwareGainStepMilliDb);
|
|
}
|
|
for (uint8_t routeOffset = 0; routeOffset < endpoint->RouteCount; routeOffset++) {
|
|
HDA_ROUTE* route = &state->Routes[endpoint->RouteIndices[routeOffset]];
|
|
HDA_WIDGET* pin = HdaCodecWidget(route->Codec, route->PinNode);
|
|
if (pin && (pin->PinCapabilities & HDA_PIN_CAP_PRESENCE_DETECT) != 0) {
|
|
registration.Capabilities |= IO_AUDIO_ENDPOINT_CAP_JACK_SENSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
status = gHdaDdk->Audio->RegisterEndpoint(®istration,
|
|
&endpoint->KernelEndpoint);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Stops worker threads and releases their thread references.
|
|
static void HdaCodecStopWorkers(HDA_CODEC_STATE* state) {
|
|
if (!state)
|
|
return;
|
|
state->PollRunning = 0;
|
|
state->WorkRunning = 0;
|
|
if (state->WorkSemaphore)
|
|
KeSignalSemaphore(state->WorkSemaphore);
|
|
if (state->PollThread && state->PollFinished)
|
|
(void)KeWaitForEvent(state->PollFinished, 0);
|
|
if (state->WorkThread && state->WorkFinished)
|
|
(void)KeWaitForEvent(state->WorkFinished, 0);
|
|
if (state->PollThread) {
|
|
ObDereferenceObject((OBJECT*)state->PollThread);
|
|
state->PollThread = (THREAD*)0;
|
|
}
|
|
if (state->WorkThread) {
|
|
ObDereferenceObject((OBJECT*)state->WorkThread);
|
|
state->WorkThread = (THREAD*)0;
|
|
}
|
|
}
|
|
|
|
// Discovers codec topology and registers usable analog render endpoints.
|
|
KSTATUS HdaCodecInitialize(HDA_CONTROLLER* controller) {
|
|
if (!controller || !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);
|
|
}
|