/* * IntelHda.cpp * Intel High Definition Audio controller driver * Copyright (c) 2026 Daniel Hammer */ #include "IntelHda.hpp" #include #include #include #include #include #include #include #include namespace Drivers::Audio::IntelHda { using namespace Kt; // ========================================================================= // Driver state // ========================================================================= static bool g_initialized = false; static volatile uint8_t* g_mmioBase = nullptr; static uint8_t g_bus, g_dev, g_func; // CORB/RIRB static volatile uint32_t* g_corb = nullptr; static volatile RirbEntry* g_rirb = nullptr; static uint64_t g_corbPhys = 0; static uint64_t g_rirbPhys = 0; static uint16_t g_corbSize = 0; // Actual entries in use static uint16_t g_rirbSize = 0; static uint16_t g_rirbReadPtr = 0; // Codec info static uint8_t g_codecAddr = 0; // First codec found static bool g_codecFound = false; static uint32_t g_codecVendorId = 0; // Vendor (upper 16) | Device (lower 16) // Output path nodes discovered during codec enumeration static uint8_t g_dacNid = 0; // DAC node static uint8_t g_speakerNid = 0; // Speaker / Line Out pin node static uint8_t g_hpNid = 0; // Headphone pin node (0 if none) static uint8_t g_pinNid = 0; // Currently active output pin static uint8_t g_mixerNid = 0; // Mixer node (0 if direct DAC→Pin) static bool g_hpPresenceDetect = false; // HP pin supports presence detect static bool g_hpPlugged = false; // Current jack state // Number of input/output streams from GCAP static uint8_t g_numInputStreams = 0; static uint8_t g_numOutputStreams = 0; // DMA buffers for output static volatile BdlEntry* g_bdl = nullptr; static uint64_t g_bdlPhys = 0; static uint8_t* g_dmaBuffer = nullptr; // Virtual (HHDM) static uint64_t g_dmaBufferPhys = 0; // DMA position buffer static volatile uint32_t* g_dmaPos = nullptr; static uint64_t g_dmaPosPhys = 0; // Active stream static AudioStream g_stream = {}; // Volume (0-127 for HDA, exposed as 0-100 to userspace) static int g_volume = 80; // Default 80% // Set when an unsolicited response is detected (e.g. jack plug/unplug) static volatile bool g_jackEventPending = false; // Debounce: after a switch, ignore jack events for this many Write() calls. // Pin sense is unreliable while the pin widget is settling after reconfiguration. static uint32_t g_jackDebounce = 0; static constexpr uint32_t JACK_DEBOUNCE_COUNT = 512; // ========================================================================= // MMIO register access // ========================================================================= static uint8_t Read8(uint32_t reg) { return *(volatile uint8_t*)(g_mmioBase + reg); } static uint16_t Read16(uint32_t reg) { return *(volatile uint16_t*)(g_mmioBase + reg); } static uint32_t Read32(uint32_t reg) { return *(volatile uint32_t*)(g_mmioBase + reg); } static void Write8(uint32_t reg, uint8_t val) { *(volatile uint8_t*)(g_mmioBase + reg) = val; } static void Write16(uint32_t reg, uint16_t val) { *(volatile uint16_t*)(g_mmioBase + reg) = val; } static void Write32(uint32_t reg, uint32_t val) { *(volatile uint32_t*)(g_mmioBase + reg) = val; } // Stream descriptor register access static uint32_t StreamBase(uint8_t streamIndex) { return SD_BASE + (uint32_t)streamIndex * SD_SIZE; } static uint8_t ReadSD8(uint8_t stream, uint32_t offset) { return Read8(StreamBase(stream) + offset); } static uint16_t ReadSD16(uint8_t stream, uint32_t offset) { return Read16(StreamBase(stream) + offset); } static uint32_t ReadSD32(uint8_t stream, uint32_t offset) { return Read32(StreamBase(stream) + offset); } static void WriteSD8(uint8_t stream, uint32_t offset, uint8_t val) { Write8(StreamBase(stream) + offset, val); } static void WriteSD16(uint8_t stream, uint32_t offset, uint16_t val) { Write16(StreamBase(stream) + offset, val); } static void WriteSD32(uint8_t stream, uint32_t offset, uint32_t val) { Write32(StreamBase(stream) + offset, val); } // ========================================================================= // Delay helper // ========================================================================= static void MicroDelay(int us) { // Simple busy-wait; us is approximate for (volatile int i = 0; i < us * 100; i++) { asm volatile("pause"); } } // ========================================================================= // CORB/RIRB command interface // ========================================================================= static bool SendVerb(uint32_t verb) { // Read current write pointer uint16_t wp = Read16(REG_CORBWP) & 0xFF; // Advance write pointer wp = (wp + 1) % g_corbSize; // Write verb to CORB g_corb[wp] = verb; // Update write pointer Write16(REG_CORBWP, wp); return true; } static bool ReadResponse(uint32_t* response, uint32_t* responseEx, int timeoutUs = 100000) { for (int i = 0; i < timeoutUs / 10; i++) { uint16_t wp = Read16(REG_RIRBWP) & 0xFF; if (g_rirbReadPtr != wp) { g_rirbReadPtr = (g_rirbReadPtr + 1) % g_rirbSize; uint32_t ex = g_rirb[g_rirbReadPtr].ResponseEx; // Bit 4 of ResponseEx = unsolicited response. // Skip it and flag for deferred jack detection. if (ex & (1u << 4)) { g_jackEventPending = true; continue; } if (response) *response = g_rirb[g_rirbReadPtr].Response; if (responseEx) *responseEx = ex; return true; } MicroDelay(10); } return false; } // Send a verb and wait for the response static uint32_t CodecCommand(uint8_t codec, uint8_t nid, uint32_t verb) { uint32_t fullVerb = ((uint32_t)codec << 28) | ((uint32_t)nid << 20) | verb; SendVerb(fullVerb); uint32_t response = 0; if (!ReadResponse(&response, nullptr)) { KernelLogStream(WARNING, "HDA") << "Verb timeout: codec=" << base::dec << (uint64_t)codec << " nid=" << (uint64_t)nid << " verb=" << base::hex << (uint64_t)verb; return 0; } return response; } static uint32_t GetParameter(uint8_t codec, uint8_t nid, uint32_t paramId) { return CodecCommand(codec, nid, 0xF0000 | paramId); } // ========================================================================= // Controller reset // ========================================================================= static bool ResetController() { // Enter reset: clear CRST uint32_t gctl = Read32(REG_GCTL); gctl &= ~GCTL_CRST; Write32(REG_GCTL, gctl); // Wait for reset to take effect for (int i = 0; i < 1000; i++) { if ((Read32(REG_GCTL) & GCTL_CRST) == 0) break; MicroDelay(100); } if (Read32(REG_GCTL) & GCTL_CRST) { KernelLogStream(ERROR, "HDA") << "Controller failed to enter reset"; return false; } MicroDelay(1000); // Exit reset: set CRST gctl = Read32(REG_GCTL); gctl |= GCTL_CRST; Write32(REG_GCTL, gctl); // Wait for controller to come out of reset for (int i = 0; i < 1000; i++) { if (Read32(REG_GCTL) & GCTL_CRST) break; MicroDelay(100); } if (!(Read32(REG_GCTL) & GCTL_CRST)) { KernelLogStream(ERROR, "HDA") << "Controller failed to exit reset"; return false; } // Enable acceptance of unsolicited responses (for jack detect events) gctl = Read32(REG_GCTL); gctl |= GCTL_UNSOL; Write32(REG_GCTL, gctl); // Wait for codecs to initialize (spec says 521us minimum after CRST) MicroDelay(1000); return true; } // ========================================================================= // CORB/RIRB initialization // ========================================================================= static uint16_t DecodeSizeCapability(uint8_t sizeReg) { // Size register bits 7:4 = size capability, bits 1:0 = size select // Capability: bit 6 = 256 entries, bit 5 = 16 entries, bit 4 = 2 entries // We prefer 256 entries if (sizeReg & (1 << 6)) return 256; if (sizeReg & (1 << 5)) return 16; if (sizeReg & (1 << 4)) return 2; return 2; } static uint8_t SizeSelectBits(uint16_t entries) { if (entries == 256) return 0x02; if (entries == 16) return 0x01; return 0x00; // 2 entries } static bool InitCorbRirb() { // Stop CORB and RIRB Write8(REG_CORBCTL, 0); Write8(REG_RIRBCTL, 0); MicroDelay(100); // Determine CORB size uint8_t corbSizeReg = Read8(REG_CORBSIZE); g_corbSize = DecodeSizeCapability(corbSizeReg); // Allocate CORB buffer (aligned to 128 bytes, we allocate a full page) void* corbVirt = Memory::g_pfa->AllocateZeroed(); g_corbPhys = Memory::SubHHDM(corbVirt); g_corb = (volatile uint32_t*)corbVirt; // Set CORB base address Write32(REG_CORBLBASE, (uint32_t)(g_corbPhys & 0xFFFFFFFF)); Write32(REG_CORBUBASE, (uint32_t)(g_corbPhys >> 32)); // Set CORB size Write8(REG_CORBSIZE, (corbSizeReg & 0xF0) | SizeSelectBits(g_corbSize)); // Reset CORB read pointer Write16(REG_CORBRP, CORBRP_RST); MicroDelay(100); // Clear reset bit (some controllers need this) Write16(REG_CORBRP, 0); for (int i = 0; i < 1000; i++) { if ((Read16(REG_CORBRP) & CORBRP_RST) == 0) break; MicroDelay(10); } // Reset CORB write pointer Write16(REG_CORBWP, 0); // Determine RIRB size uint8_t rirbSizeReg = Read8(REG_RIRBSIZE); g_rirbSize = DecodeSizeCapability(rirbSizeReg); // Allocate RIRB buffer (8 bytes per entry) void* rirbVirt = Memory::g_pfa->AllocateZeroed(); g_rirbPhys = Memory::SubHHDM(rirbVirt); g_rirb = (volatile RirbEntry*)rirbVirt; // Set RIRB base address Write32(REG_RIRBLBASE, (uint32_t)(g_rirbPhys & 0xFFFFFFFF)); Write32(REG_RIRBUBASE, (uint32_t)(g_rirbPhys >> 32)); // Set RIRB size Write8(REG_RIRBSIZE, (rirbSizeReg & 0xF0) | SizeSelectBits(g_rirbSize)); // Reset RIRB write pointer Write16(REG_RIRBWP, RIRBWP_RST); // Set response interrupt count Write16(REG_RINTCNT, 1); // Initialize our read pointer to match HW state g_rirbReadPtr = 0; // Start CORB and RIRB Write8(REG_CORBCTL, CORBCTL_RUN); Write8(REG_RIRBCTL, RIRBCTL_RUN | RIRBCTL_RINTCTL); MicroDelay(100); KernelLogStream(OK, "HDA") << "CORB: " << base::dec << (uint64_t)g_corbSize << " entries, RIRB: " << (uint64_t)g_rirbSize << " entries"; return true; } // ========================================================================= // Codec discovery and output path enumeration // ========================================================================= static bool DiscoverCodecs() { uint16_t statests = Read16(REG_STATESTS); KernelLogStream(INFO, "HDA") << "STATESTS=" << base::hex << (uint64_t)statests; for (uint8_t addr = 0; addr < 15; addr++) { if (statests & (1 << addr)) { uint32_t vendorId = GetParameter(addr, 0, PARAM_VENDOR_ID); if (vendorId == 0 || vendorId == 0xFFFFFFFF) continue; g_codecAddr = addr; g_codecFound = true; g_codecVendorId = vendorId; KernelLogStream(OK, "HDA") << "Codec " << base::dec << (uint64_t)addr << ": vendor=" << base::hex << (uint64_t)(vendorId >> 16) << " device=" << (uint64_t)(vendorId & 0xFFFF); // Clear state change status Write16(REG_STATESTS, statests); return true; } } KernelLogStream(ERROR, "HDA") << "No codecs found"; return false; } static bool EnumerateOutputPath() { // Get subordinate node count for root node (node 0) uint32_t subNodes = GetParameter(g_codecAddr, 0, PARAM_SUB_NODE_COUNT); uint8_t startNid = (subNodes >> 16) & 0xFF; uint8_t nodeCount = subNodes & 0xFF; KernelLogStream(INFO, "HDA") << "Root: startNid=" << base::dec << (uint64_t)startNid << " count=" << (uint64_t)nodeCount; // Find Audio Function Group uint8_t afg = 0; for (uint8_t i = 0; i < nodeCount; i++) { uint8_t nid = startNid + i; uint32_t fgType = GetParameter(g_codecAddr, nid, PARAM_FN_GROUP_TYPE); if ((fgType & 0xFF) == 0x01) { // Audio Function Group afg = nid; KernelLogStream(INFO, "HDA") << "Audio Function Group at NID " << (uint64_t)nid; break; } } if (afg == 0) { KernelLogStream(ERROR, "HDA") << "No Audio Function Group found"; return false; } // Power on the AFG CodecCommand(g_codecAddr, afg, 0x70500 | 0x00); // D0 (fully on) // Enumerate widgets in the AFG subNodes = GetParameter(g_codecAddr, afg, PARAM_SUB_NODE_COUNT); startNid = (subNodes >> 16) & 0xFF; nodeCount = subNodes & 0xFF; KernelLogStream(INFO, "HDA") << "AFG widgets: startNid=" << base::dec << (uint64_t)startNid << " count=" << (uint64_t)nodeCount; // Find DAC, output pins, and mixer uint8_t firstDac = 0; uint8_t speakerPin = 0; // Best speaker/line-out pin uint8_t speakerPri = 0xFF; uint8_t hpPin = 0; // Best headphone pin for (uint8_t i = 0; i < nodeCount; i++) { uint8_t nid = startNid + i; uint32_t widgetCap = GetParameter(g_codecAddr, nid, PARAM_AUDIO_WIDGET_CAP); uint8_t widgetType = (widgetCap >> 20) & 0xF; if (widgetType == WIDGET_AUDIO_OUTPUT && firstDac == 0) { firstDac = nid; KernelLogStream(INFO, "HDA") << "DAC found at NID " << base::dec << (uint64_t)nid; } if (widgetType == WIDGET_PIN_COMPLEX) { uint32_t pinCfg = CodecCommand(g_codecAddr, nid, 0xF1C00); uint8_t connectivity = (pinCfg >> 30) & 0x03; uint8_t devType = (pinCfg >> 20) & 0x0F; if (connectivity == 1) // No physical connection continue; KernelLogStream(INFO, "HDA") << "Pin NID " << base::dec << (uint64_t)nid << " type=" << (uint64_t)devType << " connectivity=" << (uint64_t)connectivity; if (devType == PIN_DEV_HP_OUT) { if (hpPin == 0) hpPin = nid; } else if (devType == PIN_DEV_SPEAKER || devType == PIN_DEV_LINE_OUT) { uint8_t pri = (devType == PIN_DEV_SPEAKER) ? 0 : 1; if (pri < speakerPri) { speakerPin = nid; speakerPri = pri; } } } if (widgetType == WIDGET_AUDIO_MIXER) { if (g_mixerNid == 0) g_mixerNid = nid; } } if (firstDac == 0) { KernelLogStream(ERROR, "HDA") << "No DAC found"; return false; } if (speakerPin == 0 && hpPin == 0) { KernelLogStream(ERROR, "HDA") << "No output pin found"; return false; } g_dacNid = firstDac; g_speakerNid = speakerPin; g_hpNid = hpPin; // Default to speaker; if no speaker, use HP g_pinNid = g_speakerNid ? g_speakerNid : g_hpNid; // Check if HP pin supports presence detect if (g_hpNid) { uint32_t pinCaps = GetParameter(g_codecAddr, g_hpNid, PARAM_PIN_CAPS); g_hpPresenceDetect = (pinCaps & (1 << 2)) != 0; // Bit 2 = Presence Detect Capable KernelLogStream(INFO, "HDA") << "HP pin NID " << base::dec << (uint64_t)g_hpNid << (g_hpPresenceDetect ? " (presence detect)" : " (no presence detect)"); // Enable unsolicited responses on the HP pin for jack events if (g_hpPresenceDetect) { // Set Unsolicited Response: enable (bit 7) | tag 0x01 CodecCommand(g_codecAddr, g_hpNid, 0x70800 | (1 << 7) | 0x01); } } KernelLogStream(OK, "HDA") << "Output path: DAC=" << base::dec << (uint64_t)g_dacNid << (g_mixerNid ? " Mixer=" : "") << (g_mixerNid ? (uint64_t)g_mixerNid : 0) << " Speaker=" << (uint64_t)g_speakerNid << " HP=" << (uint64_t)g_hpNid; return true; } // ========================================================================= // Stream format encoding // ========================================================================= static uint16_t EncodeFormat(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample) { uint16_t fmt = 0; // Base rate and multiplier/divisor switch (sampleRate) { case 8000: fmt = FMT_BASE_48K | (0 << 11) | (5 << 8); break; // 48k / 6 case 11025: fmt = FMT_BASE_44K | (0 << 11) | (3 << 8); break; // 44.1k / 4 case 16000: fmt = FMT_BASE_48K | (0 << 11) | (2 << 8); break; // 48k / 3 case 22050: fmt = FMT_BASE_44K | (0 << 11) | (1 << 8); break; // 44.1k / 2 case 32000: fmt = FMT_BASE_48K | (1 << 11) | (2 << 8); break; // 48k * 2 / 3 case 44100: fmt = FMT_BASE_44K | (0 << 11) | (0 << 8); break; // 44.1k case 48000: fmt = FMT_BASE_48K | (0 << 11) | (0 << 8); break; // 48k case 88200: fmt = FMT_BASE_44K | (1 << 11) | (0 << 8); break; // 44.1k * 2 case 96000: fmt = FMT_BASE_48K | (1 << 11) | (0 << 8); break; // 48k * 2 case 176400: fmt = FMT_BASE_44K | (3 << 11) | (0 << 8); break; // 44.1k * 4 case 192000: fmt = FMT_BASE_48K | (3 << 11) | (0 << 8); break; // 48k * 4 default: fmt = FMT_BASE_48K | (0 << 11) | (0 << 8); break; // Default 48k } // Bits per sample switch (bitsPerSample) { case 8: fmt |= (0 << 4); break; case 16: fmt |= (1 << 4); break; case 20: fmt |= (2 << 4); break; case 24: fmt |= (3 << 4); break; case 32: fmt |= (4 << 4); break; default: fmt |= (1 << 4); break; // Default 16-bit } // Channels (0-based) fmt |= (channels - 1) & 0x0F; return fmt; } // ========================================================================= // Volume control // ========================================================================= static void SetOutputVolume(int percent) { if (percent < 0) percent = 0; if (percent > 100) percent = 100; g_volume = percent; // Convert 0-100 to HDA 0-127 gain (7-bit) uint8_t gain = (uint8_t)((percent * 127) / 100); uint16_t ampPayload = AMP_SET_OUTPUT | AMP_SET_LEFT | AMP_SET_RIGHT | gain; // Set DAC output amp CodecCommand(g_codecAddr, g_dacNid, 0x30000 | ampPayload); // Set mixer output amp if present if (g_mixerNid) { CodecCommand(g_codecAddr, g_mixerNid, 0x30000 | ampPayload); } } // ========================================================================= // Pin enable / disable helpers // ========================================================================= static void EnablePin(uint8_t nid) { if (nid == 0) return; // Power on CodecCommand(g_codecAddr, nid, 0x70500 | 0x00); // D0 // Read pin config to determine type uint32_t pinCfg = CodecCommand(g_codecAddr, nid, 0xF1C00); uint8_t devType = (pinCfg >> 20) & 0x0F; uint8_t pinCtl = PIN_CTL_ENABLE_OUTPUT; if (devType == PIN_DEV_HP_OUT) pinCtl |= PIN_CTL_ENABLE_HP; CodecCommand(g_codecAddr, nid, 0x70700 | pinCtl); // Enable EAPD if supported uint32_t pinCaps = GetParameter(g_codecAddr, nid, PARAM_PIN_CAPS); if (pinCaps & (1 << 16)) CodecCommand(g_codecAddr, nid, 0x70C00 | EAPD_ENABLE); // Unmute output amp CodecCommand(g_codecAddr, nid, 0x30000 | AMP_SET_OUTPUT | AMP_SET_LEFT | AMP_SET_RIGHT | 127); } static void DisablePin(uint8_t nid) { if (nid == 0) return; // Mute output amp CodecCommand(g_codecAddr, nid, 0x30000 | AMP_SET_OUTPUT | AMP_SET_LEFT | AMP_SET_RIGHT | AMP_MUTE); // Disable pin output CodecCommand(g_codecAddr, nid, 0x70700 | 0x00); } // ========================================================================= // Jack detection and output switching // ========================================================================= static bool IsHpPlugged() { if (!g_hpNid || !g_hpPresenceDetect) return false; // Get Pin Sense (verb 0xF0900): bit 31 = presence detected uint32_t sense = CodecCommand(g_codecAddr, g_hpNid, 0xF0900); return (sense & (1u << 31)) != 0; } static void SwitchOutput(bool toHeadphones) { if (toHeadphones) { // Mute speaker, enable HP DisablePin(g_speakerNid); EnablePin(g_hpNid); g_pinNid = g_hpNid; KernelLogStream(INFO, "HDA") << "Switched to headphone output"; } else { // Mute HP, enable speaker DisablePin(g_hpNid); EnablePin(g_speakerNid); g_pinNid = g_speakerNid; KernelLogStream(INFO, "HDA") << "Switched to speaker output"; } // Re-apply volume on the now-active pin SetOutputVolume(g_volume); // Start debounce: pin sense is unreliable right after reconfiguration, // and the pin change itself may trigger spurious unsolicited responses. g_jackDebounce = JACK_DEBOUNCE_COUNT; g_jackEventPending = false; } // Drain any unsolicited responses sitting in the RIRB. // During normal playback no CodecCommands are sent, so ReadResponse() // never runs and unsolicited responses (jack events) pile up unread. // This consumes them and sets g_jackEventPending if any are found. static void DrainUnsolicitedResponses() { for (;;) { uint16_t wp = Read16(REG_RIRBWP) & 0xFF; if (g_rirbReadPtr == wp) break; uint16_t next = (g_rirbReadPtr + 1) % g_rirbSize; uint32_t ex = g_rirb[next].ResponseEx; if (!(ex & (1u << 4))) break; // Solicited response — leave for ReadResponse() g_rirbReadPtr = next; g_jackEventPending = true; } } // Poll jack state; call when an unsolicited response fires. // Switches output if state changed. static void PollJackState() { if (!g_hpNid || !g_hpPresenceDetect || !g_speakerNid) return; bool plugged = IsHpPlugged(); if (plugged != g_hpPlugged) { g_hpPlugged = plugged; SwitchOutput(plugged); } } // ========================================================================= // Configure output path (DAC -> [Mixer ->] Pin) // ========================================================================= static void ConfigureOutputPath(uint16_t streamFormat, uint8_t streamTag) { // Power on DAC and mixer CodecCommand(g_codecAddr, g_dacNid, 0x70500 | 0x00); // D0 if (g_mixerNid) CodecCommand(g_codecAddr, g_mixerNid, 0x70500 | 0x00); // Set converter stream/channel on DAC: stream tag in bits 7:4, channel 0 in bits 3:0 CodecCommand(g_codecAddr, g_dacNid, 0x70600 | ((uint32_t)streamTag << 4) | 0); // Set converter format on DAC CodecCommand(g_codecAddr, g_dacNid, 0x20000 | streamFormat); // Unmute DAC output amp SetOutputVolume(g_volume); // If there's a mixer, unmute it if (g_mixerNid) { CodecCommand(g_codecAddr, g_mixerNid, 0x30000 | AMP_SET_INPUT | AMP_SET_LEFT | AMP_SET_RIGHT | 127); } // Check jack state and enable the correct output pin if (g_hpNid && g_hpPresenceDetect && g_speakerNid) { g_hpPlugged = IsHpPlugged(); if (g_hpPlugged) { EnablePin(g_hpNid); DisablePin(g_speakerNid); g_pinNid = g_hpNid; } else { EnablePin(g_speakerNid); DisablePin(g_hpNid); g_pinNid = g_speakerNid; } } else { // Only one output available, just enable it EnablePin(g_pinNid); } } // ========================================================================= // Output stream setup // ========================================================================= static bool SetupOutputStream(uint8_t streamIndex, uint16_t streamFormat) { // Reset stream WriteSD8(streamIndex, SD_CTL, SD_CTL0_SRST); for (int i = 0; i < 1000; i++) { if (ReadSD8(streamIndex, SD_CTL) & SD_CTL0_SRST) break; MicroDelay(10); } // Clear reset WriteSD8(streamIndex, SD_CTL, 0); for (int i = 0; i < 1000; i++) { if ((ReadSD8(streamIndex, SD_CTL) & SD_CTL0_SRST) == 0) break; MicroDelay(10); } // Clear status bits WriteSD8(streamIndex, SD_STS, SD_STS_BCIS | SD_STS_FIFOE | SD_STS_DESE); // Set up BDL (double-buffered) for (int i = 0; i < BUFFER_COUNT; i++) { g_bdl[i].Address = g_dmaBufferPhys + (uint64_t)(i * BUFFER_SIZE); g_bdl[i].Length = BUFFER_SIZE; g_bdl[i].Ioc = 1; // Interrupt on completion for each buffer } // Set BDL address WriteSD32(streamIndex, SD_BDPL, (uint32_t)(g_bdlPhys & 0xFFFFFFFF)); WriteSD32(streamIndex, SD_BDPU, (uint32_t)(g_bdlPhys >> 32)); // Set cyclic buffer length (total bytes across all BDL entries) WriteSD32(streamIndex, SD_CBL, TOTAL_BUFFER_SIZE); // Set last valid index (0-based) WriteSD16(streamIndex, SD_LVI, BUFFER_COUNT - 1); // Set stream format WriteSD16(streamIndex, SD_FMT, streamFormat); // Set stream tag in CTL byte 2 (bits 23:20) uint8_t streamTag = 1; // Use stream tag 1 WriteSD8(streamIndex, SD_CTL + 2, (streamTag << 4)); return true; } static void StartStream(uint8_t streamIndex) { // Enable interrupts and start running uint8_t ctl = ReadSD8(streamIndex, SD_CTL); ctl |= SD_CTL0_RUN | SD_CTL0_IOCE; WriteSD8(streamIndex, SD_CTL, ctl); } static void StopStream(uint8_t streamIndex) { uint8_t ctl = ReadSD8(streamIndex, SD_CTL); ctl &= ~(SD_CTL0_RUN | SD_CTL0_IOCE); WriteSD8(streamIndex, SD_CTL, ctl); // Wait for stream to stop for (int i = 0; i < 1000; i++) { if ((ReadSD8(streamIndex, SD_CTL) & SD_CTL0_RUN) == 0) break; MicroDelay(10); } } // ========================================================================= // MSI setup // ========================================================================= static void HandleInterrupt(uint8_t irq); static bool SetupMsi(uint8_t bus, uint8_t dev, uint8_t func) { uint8_t cap = Pci::FindCapability(bus, dev, func, Pci::PCI_CAP_MSI); if (cap == 0) { KernelLogStream(INFO, "HDA") << "MSI capability not found"; return false; } uint16_t msgCtrl = Pci::LegacyRead16(bus, dev, func, cap + 2); bool is64bit = (msgCtrl & (1 << 7)) != 0; Pci::LegacyWrite32(bus, dev, func, cap + 4, MSI_ADDR_BASE); if (is64bit) { Pci::LegacyWrite32(bus, dev, func, cap + 8, 0); Pci::LegacyWrite16(bus, dev, func, cap + 12, MSI_VECTOR); } else { Pci::LegacyWrite16(bus, dev, func, cap + 8, MSI_VECTOR); } msgCtrl &= ~(0x70); msgCtrl |= (1 << 0); Pci::LegacyWrite16(bus, dev, func, cap + 2, msgCtrl); uint16_t pciCmd = Pci::LegacyRead16(bus, dev, func, (uint8_t)Pci::PCI_REG_COMMAND); pciCmd |= Pci::PCI_CMD_INTX_DISABLE; Pci::LegacyWrite16(bus, dev, func, (uint8_t)Pci::PCI_REG_COMMAND, pciCmd); Hal::RegisterIrqHandler(MSI_IRQ, HandleInterrupt); KernelLogStream(OK, "HDA") << "MSI enabled: vector " << base::dec << (uint64_t)MSI_VECTOR << " (IRQ slot " << (uint64_t)MSI_IRQ << ")" << (is64bit ? " [64-bit]" : " [32-bit]"); return true; } // ========================================================================= // Interrupt handler // ========================================================================= static void HandleInterrupt(uint8_t /*irq*/) { uint32_t intsts = Read32(REG_INTSTS); // Handle stream interrupts (bits 0-29 correspond to stream descriptors) if (g_stream.Active) { uint8_t si = g_stream.StreamIndex; if (intsts & (1u << si)) { uint8_t sts = ReadSD8(si, SD_STS); WriteSD8(si, SD_STS, sts); } } // Handle RIRB interrupt (controller interrupt enable bit 30) // Do NOT advance g_rirbReadPtr here — ReadResponse() owns it. // Just clear status. Unsolicited responses are detected and flagged // in ReadResponse() by checking ResponseEx bit 4. if (intsts & (1u << 30)) { uint8_t rirbSts = Read8(REG_RIRBSTS); Write8(REG_RIRBSTS, rirbSts); } } // ========================================================================= // DMA buffer allocation // ========================================================================= static bool AllocateDmaBuffers() { // Allocate BDL (needs 128-byte alignment, page-aligned is fine) void* bdlVirt = Memory::g_pfa->AllocateZeroed(); g_bdlPhys = Memory::SubHHDM(bdlVirt); g_bdl = (volatile BdlEntry*)bdlVirt; // Allocate DMA audio buffers (BUFFER_COUNT * BUFFER_SIZE = 32 KiB = 8 pages) int pages = TOTAL_BUFFER_SIZE / 0x1000; void* bufVirt = Memory::g_pfa->ReallocConsecutive(nullptr, pages); if (!bufVirt) { KernelLogStream(ERROR, "HDA") << "Failed to allocate DMA buffer (" << base::dec << pages << " pages)"; return false; } memset(bufVirt, 0, TOTAL_BUFFER_SIZE); g_dmaBufferPhys = Memory::SubHHDM(bufVirt); g_dmaBuffer = (uint8_t*)bufVirt; // Allocate DMA position buffer (one page) void* posVirt = Memory::g_pfa->AllocateZeroed(); g_dmaPosPhys = Memory::SubHHDM(posVirt); g_dmaPos = (volatile uint32_t*)posVirt; KernelLogStream(OK, "HDA") << "DMA buffers allocated: " << base::dec << BUFFER_COUNT << "x" << BUFFER_SIZE << " bytes"; return true; } // ========================================================================= // Probe (called by PCI driver matching) // ========================================================================= bool Probe(const Pci::PciDevice& dev) { g_bus = dev.Bus; g_dev = dev.Device; g_func = dev.Function; KernelLogStream(INFO, "HDA") << "Probing Intel HDA at " << base::hex << (uint64_t)g_bus << ":" << (uint64_t)g_dev << "." << base::dec << (uint64_t)g_func << " (device=" << base::hex << (uint64_t)dev.DeviceId << ")"; // Enable memory space and bus mastering Pci::EnableBusMaster(g_bus, g_dev, g_func); // Read BAR0 uint64_t mmioPhys = Pci::ReadBar0(g_bus, g_dev, g_func); if (mmioPhys == 0) { KernelLogStream(ERROR, "HDA") << "BAR0 is zero"; return false; } KernelLogStream(INFO, "HDA") << "MMIO base: " << base::hex << mmioPhys; // Map MMIO region (HDA MMIO is typically 16 KiB) for (uint64_t offset = 0; offset < 0x4000; offset += 0x1000) { Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset)); } g_mmioBase = (volatile uint8_t*)Memory::HHDM(mmioPhys); // Read capabilities uint16_t gcap = Read16(REG_GCAP); uint8_t vmin = Read8(REG_VMIN); uint8_t vmaj = Read8(REG_VMAJ); g_numOutputStreams = (gcap >> 12) & 0xF; g_numInputStreams = (gcap >> 8) & 0xF; uint8_t numBidiStreams = (gcap >> 3) & 0x1F; bool ok64bit = (gcap & 0x01) != 0; KernelLogStream(INFO, "HDA") << "HDA v" << base::dec << (uint64_t)vmaj << "." << (uint64_t)vmin << " | ISS=" << (uint64_t)g_numInputStreams << " OSS=" << (uint64_t)g_numOutputStreams << " BSS=" << (uint64_t)numBidiStreams << (ok64bit ? " 64-bit" : " 32-bit"); if (g_numOutputStreams == 0) { KernelLogStream(ERROR, "HDA") << "No output streams available"; return false; } // Reset the controller if (!ResetController()) return false; // Allocate DMA buffers if (!AllocateDmaBuffers()) return false; // Set up DMA position buffer Write32(REG_DPIBLBASE, (uint32_t)(g_dmaPosPhys & 0xFFFFFFFF) | 1); // Bit 0 = enable Write32(REG_DPIBUBASE, (uint32_t)(g_dmaPosPhys >> 32)); // Initialize CORB/RIRB if (!InitCorbRirb()) return false; // Set up MSI (with legacy fallback) bool msiOk = SetupMsi(g_bus, g_dev, g_func); if (!msiOk) { uint8_t irqLine = Pci::LegacyRead8(g_bus, g_dev, g_func, (uint8_t)Pci::PCI_REG_INTERRUPT); if (irqLine != 0xFF) { KernelLogStream(INFO, "HDA") << "Falling back to legacy IRQ " << base::dec << (uint64_t)irqLine; Hal::RegisterIrqHandler(irqLine, HandleInterrupt); } else { KernelLogStream(WARNING, "HDA") << "No interrupt available, polling only"; } } // Enable interrupts uint32_t intctl = INTCTL_GIE | INTCTL_CIE; // Enable interrupt for all output streams for (uint8_t i = 0; i < g_numOutputStreams; i++) { intctl |= (1u << (g_numInputStreams + i)); } Write32(REG_INTCTL, intctl); // Discover codecs if (!DiscoverCodecs()) return false; // Enumerate output path if (!EnumerateOutputPath()) return false; // Read initial jack state if (g_hpNid && g_hpPresenceDetect) { g_hpPlugged = IsHpPlugged(); KernelLogStream(INFO, "HDA") << "Headphone jack: " << (g_hpPlugged ? "plugged" : "unplugged"); } g_initialized = true; KernelLogStream(OK, "HDA") << "Intel HDA initialized successfully"; return true; } // ========================================================================= // Public API // ========================================================================= bool IsInitialized() { return g_initialized; } uint32_t GetCodecVendorId() { return g_codecVendorId; } int Open(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample) { if (!g_initialized) return -1; if (g_stream.Active) return -1; // Only one stream at a time if (channels < 1 || channels > 8) return -1; if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 20 && bitsPerSample != 24 && bitsPerSample != 32) return -1; // Output stream index = numInputStreams (first output stream) uint8_t streamIndex = g_numInputStreams; uint8_t streamTag = 1; // Encode the stream format uint16_t fmt = EncodeFormat(sampleRate, channels, bitsPerSample); // Configure the codec output path ConfigureOutputPath(fmt, streamTag); // Set up the output stream DMA if (!SetupOutputStream(streamIndex, fmt)) return -1; // Zero the DMA buffer memset(g_dmaBuffer, 0, TOTAL_BUFFER_SIZE); // Record stream state g_stream.Active = true; g_stream.SampleRate = sampleRate; g_stream.Channels = channels; g_stream.BitsPerSample = bitsPerSample; g_stream.StreamIndex = streamIndex; g_stream.StreamTag = streamTag; g_stream.WritePos = 0; // Start the stream StartStream(streamIndex); KernelLogStream(OK, "HDA") << "Stream opened: " << base::dec << (uint64_t)sampleRate << "Hz " << (uint64_t)bitsPerSample << "-bit " << (uint64_t)channels << "ch"; return 0; // Handle 0 } void Close(int handle) { if (handle != 0 || !g_stream.Active) return; StopStream(g_stream.StreamIndex); // Mute output CodecCommand(g_codecAddr, g_dacNid, 0x30000 | AMP_SET_OUTPUT | AMP_SET_LEFT | AMP_SET_RIGHT | AMP_MUTE); g_stream.Active = false; KernelLogStream(OK, "HDA") << "Stream closed"; } int Write(int handle, const uint8_t* data, uint32_t size) { if (handle != 0 || !g_stream.Active || !data || size == 0) return -1; // Drain unsolicited responses from the RIRB — during playback no // CodecCommands are sent, so ReadResponse() never runs and jack // events would otherwise go unnoticed. if (g_hpPresenceDetect && g_speakerNid) { DrainUnsolicitedResponses(); if (g_jackDebounce > 0) { g_jackDebounce--; g_jackEventPending = false; } else if (g_jackEventPending) { g_jackEventPending = false; PollJackState(); } } // Get current hardware playback position from DMA position buffer // Each stream's position is at dmaPos[streamIndex * 2] uint32_t hwPos = g_dmaPos[g_stream.StreamIndex * 2]; uint32_t writePos = g_stream.WritePos; // Calculate available space in the ring buffer uint32_t available; if (writePos >= hwPos) { available = TOTAL_BUFFER_SIZE - (writePos - hwPos); } else { available = hwPos - writePos; } // Leave a small gap to avoid write pointer catching up to read pointer if (available > 64) available -= 64; else available = 0; if (size > available) size = available; if (size == 0) return 0; // Write data to DMA buffer (handle wrap-around) uint32_t firstChunk = TOTAL_BUFFER_SIZE - writePos; if (firstChunk > size) firstChunk = size; memcpy(g_dmaBuffer + writePos, data, firstChunk); if (size > firstChunk) { memcpy(g_dmaBuffer, data + firstChunk, size - firstChunk); } g_stream.WritePos = (writePos + size) % TOTAL_BUFFER_SIZE; return (int)size; } int Control(int handle, int cmd, int value) { if (handle != 0) return -1; switch (cmd) { case AUDIO_CTL_SET_VOLUME: if (!g_initialized) { g_volume = value; return 0; } SetOutputVolume(value); return 0; case AUDIO_CTL_GET_VOLUME: return g_volume; case AUDIO_CTL_GET_POS: if (!g_stream.Active) return 0; return (int)g_dmaPos[g_stream.StreamIndex * 2]; case AUDIO_CTL_PAUSE: if (!g_stream.Active) return -1; if (value) StopStream(g_stream.StreamIndex); else StartStream(g_stream.StreamIndex); return 0; default: return -1; } } };