major refactorings

Signed-off-by: kaguya3311 <kaguya3311@national.shitposting.agency>
This commit is contained in:
kaguya
2026-05-18 04:02:59 -04:00
parent f7aa6f913a
commit b28a6bcf29
211 changed files with 17699 additions and 8107 deletions
+537
View File
@@ -0,0 +1,537 @@
#include "hda.h"
#include "mm/vmm.h"
#include "mm/pmm.h"
#include "mm/memory.h"
#include "libk/stdio.h"
#include "libk/debug.h"
/* ── Module globals ─────────────────────────────────────────────────────────── */
static uintptr_t g_mmio = 0; /* virtual MMIO base */
static uint32_t *g_corb = NULL; /* CORB ring buffer (virtual) */
static uint64_t *g_rirb = NULL; /* RIRB ring buffer (virtual) */
static uintptr_t g_corb_phys = 0;
static uintptr_t g_rirb_phys = 0;
static uint32_t g_corb_wp = 0; /* shadow of hardware CORBWP (starts at 0) */
static uint32_t g_rirb_rp = 0; /* our RIRB read pointer (starts at 0) */
/* Codec state discovered during enumeration */
static uint8_t g_codec = 0; /* codec address (0-15) */
static uint8_t g_afg = 0; /* Audio Function Group NID */
static uint8_t g_dac = 0; /* Output Converter NID */
static uint8_t g_pin = 0; /* Output Pin Widget NID */
static uint8_t g_iss = 0; /* number of input stream descriptors */
/* BDL (allocated once in hda_init, reused each play call) */
static hda_bdl_entry_t *g_bdl = NULL;
static uintptr_t g_bdl_phys = 0;
/* MMIO offset of the output stream descriptor we're using */
static uint32_t g_sd_base = 0;
/* ── MMIO helpers ───────────────────────────────────────────────────────────── */
static inline uint8_t r8 (uint32_t r) { return *(volatile uint8_t *)(g_mmio+r); }
static inline uint16_t r16(uint32_t r) { return *(volatile uint16_t*)(g_mmio+r); }
static inline uint32_t r32(uint32_t r) { return *(volatile uint32_t*)(g_mmio+r); }
static inline void w8 (uint32_t r, uint8_t v){ *(volatile uint8_t *)(g_mmio+r)=v; }
static inline void w16(uint32_t r, uint16_t v){ *(volatile uint16_t*)(g_mmio+r)=v; }
static inline void w32(uint32_t r, uint32_t v){ *(volatile uint32_t*)(g_mmio+r)=v; }
/* Stream descriptor helpers operate relative to g_sd_base */
static inline uint8_t sd_r8 (uint32_t o){ return r8 (g_sd_base+o); }
static inline uint16_t sd_r16(uint32_t o){ return r16(g_sd_base+o); }
static inline uint32_t sd_r32(uint32_t o){ return r32(g_sd_base+o); }
static inline void sd_w8 (uint32_t o,uint8_t v){ w8 (g_sd_base+o,v); }
static inline void sd_w16(uint32_t o,uint16_t v){ w16(g_sd_base+o,v); }
static inline void sd_w32(uint32_t o,uint32_t v){ w32(g_sd_base+o,v); }
/* ── Busy-wait delay ────────────────────────────────────────────────────────── */
static void hda_udelay(uint32_t us)
{
/* ~400 pause iterations ≈ 1 µs on a modern core (generous upper bound) */
volatile uint32_t n = us * 400;
while (n--) asm volatile("pause");
}
/* ── CORB / RIRB verb engine ────────────────────────────────────────────────── */
/*
* hda_send_verb write one 32-bit command word to the CORB and wait for the
* corresponding RIRB response. Returns the 64-bit RIRB entry (response in the
* low 32 bits).
*/
static uint64_t hda_send_verb(uint32_t cmd)
{
g_corb_wp = (g_corb_wp + 1) & 0xFF; /* 256-entry ring */
g_corb[g_corb_wp] = cmd;
/* Tell the hardware there is a new entry */
w16(HDA_CORBWP, (uint16_t)g_corb_wp);
/* Poll until RIRBWP advances (hardware writes one response per command) */
for (uint32_t i = 0; i < 1000000; i++) {
asm volatile("pause");
if ((r8(HDA_RIRBWP) & 0xFF) != (uint8_t)g_rirb_rp) {
g_rirb_rp = (g_rirb_rp + 1) & 0xFF;
return g_rirb[g_rirb_rp];
}
}
kprintf("[HDA] verb timeout cmd=%08x\n", cmd);
return (uint64_t)-1;
}
/*
* hda_verb build and send a codec verb, return the 32-bit response.
*
* Verb encoding:
* verb <= 0x0F : 4-bit verb [19:16], 16-bit payload [15:0] (SET_FMT, SET_AMP …)
* verb >= 0x100: 12-bit verb [19:8], 8-bit payload [7:0] (GET_PARAM, SET_* …)
*/
static uint32_t hda_verb(uint8_t cad, uint8_t nid, uint32_t verb, uint32_t payload)
{
uint32_t cmd;
if (verb <= 0xF)
cmd = ((uint32_t)cad << 28) | ((uint32_t)nid << 20)
| (verb << 16) | (payload & 0xFFFF);
else
cmd = ((uint32_t)cad << 28) | ((uint32_t)nid << 20)
| ((verb & 0xFFF) << 8) | (payload & 0xFF);
return (uint32_t)hda_send_verb(cmd);
}
static uint32_t hda_get_param(uint8_t cad, uint8_t nid, uint8_t param)
{
return hda_verb(cad, nid, 0xF00, param);
}
/* ── CORB initialisation ────────────────────────────────────────────────────── */
static bool corb_init(void)
{
/* Stop DMA engine */
w8(HDA_CORBCTL, r8(HDA_CORBCTL) & ~0x02u);
for (int i = 0; i < 50000 && (r8(HDA_CORBCTL) & 0x02); i++)
asm volatile("pause");
/* Allocate ring: 256 entries × 4 bytes = 1 KB, lives in one page */
void *phys = pmm_allocz(1);
if (!phys) return false;
g_corb_phys = (uintptr_t)phys;
g_corb = (uint32_t *)((uintptr_t)phys + MEM_PHYS_OFFSET);
/* Select 256-entry size (bits[1:0] = 0b10) */
w8(HDA_CORBSIZE, (r8(HDA_CORBSIZE) & ~0x03u) | 0x02u);
/* Program base address */
w32(HDA_CORBLBASE, (uint32_t)(g_corb_phys & 0xFFFFFFFF));
w32(HDA_CORBUBASE, (uint32_t)(g_corb_phys >> 32));
/* Reset WP to 0 */
w16(HDA_CORBWP, 0);
g_corb_wp = 0;
/* Reset RP: set CORBRPRST, wait, clear */
w16(HDA_CORBRP, 0x8000);
hda_udelay(100);
w16(HDA_CORBRP, 0x0000);
hda_udelay(100);
/* Start DMA */
w8(HDA_CORBCTL, r8(HDA_CORBCTL) | 0x02u);
for (int i = 0; i < 50000; i++) {
if (r8(HDA_CORBCTL) & 0x02) return true;
asm volatile("pause");
}
kprintf("[HDA] CORB DMA failed to start\n");
return false;
}
/* ── RIRB initialisation ────────────────────────────────────────────────────── */
static bool rirb_init(void)
{
/* Stop DMA engine */
w8(HDA_RIRBCTL, r8(HDA_RIRBCTL) & ~0x02u);
for (int i = 0; i < 50000 && (r8(HDA_RIRBCTL) & 0x02); i++)
asm volatile("pause");
/* Allocate ring: 256 entries × 8 bytes = 2 KB, one page */
void *phys = pmm_allocz(1);
if (!phys) return false;
g_rirb_phys = (uintptr_t)phys;
g_rirb = (uint64_t *)((uintptr_t)phys + MEM_PHYS_OFFSET);
/* 256-entry size */
w8(HDA_RIRBSIZE, (r8(HDA_RIRBSIZE) & ~0x03u) | 0x02u);
/* Base address */
w32(HDA_RIRBLBASE, (uint32_t)(g_rirb_phys & 0xFFFFFFFF));
w32(HDA_RIRBUBASE, (uint32_t)(g_rirb_phys >> 32));
/* Reset WP (write RIRBWPRST bit) */
w16(HDA_RIRBWP, 0x8000);
g_rirb_rp = 0;
/* Set interrupt count to 0xFF (we poll anyway, so this is a safety valve) */
w16(HDA_RINTCNT, 0xFF);
/* Start DMA */
w8(HDA_RIRBCTL, r8(HDA_RIRBCTL) | 0x02u);
for (int i = 0; i < 50000; i++) {
if (r8(HDA_RIRBCTL) & 0x02) return true;
asm volatile("pause");
}
kprintf("[HDA] RIRB DMA failed to start\n");
return false;
}
/* ── Codec enumeration ──────────────────────────────────────────────────────── */
/*
* Return the NID of connection-list entry `idx` for widget `nid` on codec `cad`.
* Assumes short form (8-bit entries) which covers all common HDA codecs.
*/
static uint8_t conn_entry(uint8_t cad, uint8_t nid, uint8_t idx)
{
/* GET_CONN_LIST_ENTRY (0xF02): payload = starting index, returns 4 packed entries */
uint32_t r = hda_verb(cad, nid, 0xF02, idx & ~3u);
return (uint8_t)(r >> ((idx & 3) * 8));
}
/*
* Walk the AFG widgets to find the first output converter (DAC) and the first
* output-capable pin that connects to it (or to any DAC if the first one is
* not in the pin's connection list).
*/
static bool enumerate_codec(uint8_t cad)
{
/* Root node: get function group range */
uint32_t root_cnt = hda_get_param(cad, 0, HDA_PARAM_NODE_COUNT);
uint8_t fg_start = (root_cnt >> 16) & 0xFF;
uint8_t fg_count = (root_cnt >> 0) & 0xFF;
/* Find the Audio Function Group */
uint8_t afg = 0;
for (uint8_t n = fg_start; n < fg_start + fg_count; n++) {
uint32_t type = hda_get_param(cad, n, HDA_PARAM_FUNC_GROUP_TYPE) & 0xFF;
if (type == 0x01) { afg = n; break; }
}
if (!afg) { kprintf("[HDA] no AFG on codec %u\n", cad); return false; }
/* Enumerate widgets */
uint32_t w_info = hda_get_param(cad, afg, HDA_PARAM_NODE_COUNT);
uint8_t w_start = (w_info >> 16) & 0xFF;
uint8_t w_count = (w_info >> 0) & 0xFF;
uint8_t dac = 0, pin = 0;
for (uint8_t w = w_start; w < w_start + w_count; w++) {
uint32_t wcap = hda_get_param(cad, w, HDA_PARAM_AUDIO_WIDGET_CAP);
uint8_t wtype = (wcap >> 20) & 0xF;
if (wtype == HDA_WTYPE_AUDIO_OUT && !dac) {
dac = w;
} else if (wtype == HDA_WTYPE_PIN) {
uint32_t pcap = hda_get_param(cad, w, HDA_PARAM_PIN_CAPABILITIES);
if ((pcap & HDA_PINCAP_OUTPUT) && !pin) {
pin = w;
}
}
}
if (!dac) { kprintf("[HDA] no DAC found\n"); return false; }
if (!pin) { kprintf("[HDA] no output pin found\n"); return false; }
g_afg = afg; g_dac = dac; g_pin = pin;
kprintf("[HDA] codec=%u AFG=%u DAC=%u Pin=%u\n", cad, afg, dac, pin);
/* Select DAC in pin's connection list if needed */
uint8_t clen = (uint8_t)(hda_get_param(cad, pin, HDA_PARAM_CONN_LIST_LEN) & 0x7F);
for (uint8_t i = 0; i < clen; i++) {
if (conn_entry(cad, pin, i) == dac) {
hda_verb(cad, pin, 0x701, i); /* SET_CONN_SELECT */
break;
}
}
return true;
}
/* ── HDA format word calculation ────────────────────────────────────────────── */
static uint16_t calc_format(hda_format_t f)
{
uint16_t fmt = 0;
/* Channels: field value = channels 1 */
fmt |= (f.channels - 1) & 0xF;
/* Bits per sample in bits [6:4] */
switch (f.bits_per_sample) {
case 8: fmt |= (0u << 4); break;
case 16: fmt |= (1u << 4); break;
case 20: fmt |= (2u << 4); break;
case 24: fmt |= (3u << 4); break;
case 32: fmt |= (4u << 4); break;
default: fmt |= (1u << 4);
kprintf("[HDA] unknown bit depth %u, using 16-bit\n", f.bits_per_sample);
}
/*
* Sample rate: bit14 = base (0 = 48 kHz, 1 = 44.1 kHz),
* bits[13:11] = multiplier 1,
* bits[10:8] = divisor 1.
*/
switch (f.sample_rate) {
case 8000: fmt |= (0u << 14) | (0u << 11) | (5u << 8); break; /* 48000 / 6 */
case 11025: fmt |= (1u << 14) | (0u << 11) | (3u << 8); break; /* 44100 / 4 */
case 16000: fmt |= (0u << 14) | (0u << 11) | (2u << 8); break; /* 48000 / 3 */
case 22050: fmt |= (1u << 14) | (0u << 11) | (1u << 8); break; /* 44100 / 2 */
case 24000: fmt |= (0u << 14) | (0u << 11) | (1u << 8); break; /* 48000 / 2 */
case 32000: fmt |= (0u << 14) | (1u << 11) | (2u << 8); break; /* 48000*2/ 3 */
case 44100: fmt |= (1u << 14) | (0u << 11) | (0u << 8); break; /* 44100 / 1 */
case 48000: break; /* 48000 / 1 */
case 88200: fmt |= (1u << 14) | (1u << 11) | (0u << 8); break; /* 44100*2/ 1 */
case 96000: fmt |= (0u << 14) | (1u << 11) | (0u << 8); break; /* 48000*2/ 1 */
case 176400: fmt |= (1u << 14) | (3u << 11) | (0u << 8); break; /* 44100*4/ 1 */
case 192000: fmt |= (0u << 14) | (3u << 11) | (0u << 8); break; /* 48000*4/ 1 */
default:
kprintf("[HDA] unsupported sample rate %u, defaulting to 48000\n",
f.sample_rate);
}
return fmt;
}
/* ── Stream descriptor reset helper ────────────────────────────────────────── */
static void sd_reset(void)
{
/* Assert stream reset */
sd_w8(HDA_SD_CTL0, sd_r8(HDA_SD_CTL0) | HDA_SD_CTL_SRST);
for (int i = 0; i < 100000; i++) {
if (sd_r8(HDA_SD_CTL0) & HDA_SD_CTL_SRST) break;
asm volatile("pause");
}
/* Deassert stream reset */
sd_w8(HDA_SD_CTL0, sd_r8(HDA_SD_CTL0) & ~HDA_SD_CTL_SRST);
for (int i = 0; i < 100000; i++) {
if (!(sd_r8(HDA_SD_CTL0) & HDA_SD_CTL_SRST)) break;
asm volatile("pause");
}
}
/* ══════════════════════════════════════════════════════════════════════════════
* Public API
* ══════════════════════════════════════════════════════════════════════════════ */
bool hda_init(pci_device_t *dev)
{
/* ── 1. Enable PCI Memory Space + Bus Master ────────────────────────────── */
uint16_t pci_cmd = pci_read16(dev->addr, 0x04);
pci_cmd |= (1u << 1) | (1u << 2); /* Memory Space Enable | Bus Master Enable */
pci_write16(dev->addr, 0x04, pci_cmd);
/* ── 2. Read BAR0 (HDA MMIO, may be 64-bit) ─────────────────────────────── */
uint32_t bar0_raw = dev->bar[0];
uint64_t mmio_phys;
if ((bar0_raw & 0x6u) == 0x4u) {
/* 64-bit BAR: combine BAR0 (lower) and BAR1 (upper) */
mmio_phys = ((uint64_t)(bar0_raw & ~0xFu))
| ((uint64_t)dev->bar[1] << 32);
} else {
mmio_phys = bar0_raw & ~0xFu;
}
if (!mmio_phys) {
kprintf("[HDA] BAR0 is zero, controller not properly enumerated by firmware\n");
return false;
}
/* Map via HHDM (all physical memory is already mapped with MEM_PHYS_OFFSET) */
g_mmio = mmio_phys + MEM_PHYS_OFFSET;
kprintf("[HDA] MMIO phys=%lx virt=%lx\n", (uint64_t)mmio_phys, (uint64_t)g_mmio);
/* ── 3. Controller reset ─────────────────────────────────────────────────── */
/* Assert reset */
w32(HDA_GCTL, r32(HDA_GCTL) & ~HDA_GCTL_CRST);
for (int i = 0; i < 500000; i++) {
if (!(r32(HDA_GCTL) & HDA_GCTL_CRST)) break;
asm volatile("pause");
}
hda_udelay(100);
/* Release reset */
w32(HDA_GCTL, r32(HDA_GCTL) | HDA_GCTL_CRST);
for (int i = 0; i < 500000; i++) {
if (r32(HDA_GCTL) & HDA_GCTL_CRST) break;
asm volatile("pause");
}
/* Wait ≥ 521 µs for codecs to come out of reset */
hda_udelay(1000);
/* ── 4. GCAP: discover stream counts ─────────────────────────────────────── */
uint16_t gcap = r16(HDA_GCAP);
g_iss = (gcap >> 8) & 0xF; /* number of input SDs */
uint8_t oss = (gcap >> 12) & 0xF; /* number of output SDs */
kprintf("[HDA] gcap=%04x ISS=%u OSS=%u\n", gcap, g_iss, oss);
if (!oss) { kprintf("[HDA] no output streams\n"); return false; }
/* Output SD 0 starts immediately after all input SDs */
g_sd_base = 0x80u + (uint32_t)g_iss * HDA_SD_STRIDE;
/* ── 5. CORB + RIRB ─────────────────────────────────────────────────────── */
if (!corb_init()) { kprintf("[HDA] CORB init failed\n"); return false; }
if (!rirb_init()) { kprintf("[HDA] RIRB init failed\n"); return false; }
/* ── 6. Detect and enumerate codecs ─────────────────────────────────────── */
uint16_t statests = r16(HDA_STATESTS);
if (!statests) { kprintf("[HDA] no codecs detected\n"); return false; }
bool found = false;
for (uint8_t c = 0; c < 15; c++) {
if (!(statests & (1u << c))) continue;
kprintf("[HDA] codec %u present\n", c);
if (!found && enumerate_codec(c)) {
g_codec = c;
found = true;
}
}
if (!found) { kprintf("[HDA] no usable codec\n"); return false; }
/* ── 7. Allocate persistent BDL (1 page, 256 possible entries × 16 bytes) ─ */
void *bdl_phys = pmm_allocz(1);
if (!bdl_phys) { kprintf("[HDA] OOM: BDL\n"); return false; }
g_bdl_phys = (uintptr_t)bdl_phys;
g_bdl = (hda_bdl_entry_t *)((uintptr_t)bdl_phys + MEM_PHYS_OFFSET);
kprintf("[HDA] init complete\n");
return true;
}
bool hda_play_pcm(uintptr_t phys_buf, uint32_t size, hda_format_t fmt)
{
if (!g_mmio || !g_dac || !g_pin) {
kprintf("[HDA] not initialised\n");
return false;
}
if (!phys_buf || !size) return false;
uint16_t format_word = calc_format(fmt);
/* ── 1. Wake up codec nodes ─────────────────────────────────────────────── */
/* SET_POWER_STATE D0 (fully on) for AFG, DAC, and Pin */
hda_verb(g_codec, g_afg, 0x705, 0x00);
hda_verb(g_codec, g_dac, 0x705, 0x00);
hda_verb(g_codec, g_pin, 0x705, 0x00);
hda_udelay(100);
/* ── 2. Assign stream tag 1, channel 0 to the DAC ──────────────────────── */
/* SET_STREAM_CHANNEL: bits[7:4]=stream_tag, bits[3:0]=channel */
hda_verb(g_codec, g_dac, 0x706, (1u << 4) | 0u);
/* ── 3. Set sample format on DAC ───────────────────────────────────────── */
hda_verb(g_codec, g_dac, 0x2, format_word); /* SET_CONVERTER_FORMAT (4-bit verb) */
/* ── 4. Unmute DAC output amp to maximum gain ───────────────────────────── */
/*
* SET_AMP_GAIN_MUTE (4-bit verb 0x3) payload:
* bit 15 = output amp
* bit 14 = input amp
* bit 13 = left channel
* bit 12 = right channel
* bit 7 = mute (0 = unmuted)
* bits[6:0] = gain
*/
uint32_t dac_outcap = hda_get_param(g_codec, g_dac, HDA_PARAM_AMP_CAP_OUTPUT);
uint8_t max_gain = (dac_outcap >> 8) & 0x7F;
hda_verb(g_codec, g_dac, 0x3, 0xB000u | max_gain);
/* ── 5. Enable pin output + unmute pin amp ──────────────────────────────── */
/* SET_PIN_WIDGET_CONTROL: bit6=Out Enable */
hda_verb(g_codec, g_pin, 0x707, 0x40u);
/* If the pin has an output amp, unmute it too */
uint32_t pin_outcap = hda_get_param(g_codec, g_pin, HDA_PARAM_AMP_CAP_OUTPUT);
if (pin_outcap & (1u << 31)) { /* has output amp */
uint8_t pin_max = (pin_outcap >> 8) & 0x7F;
hda_verb(g_codec, g_pin, 0x3, 0xB000u | pin_max);
}
/* ── 6. Reset output stream descriptor ─────────────────────────────────── */
sd_reset();
/* ── 7. Build BDL (single entry covering the entire buffer) ─────────────── */
memset(g_bdl, 0, sizeof(hda_bdl_entry_t) * 2);
g_bdl[0].addr = (uint64_t)phys_buf;
g_bdl[0].length = size;
g_bdl[0].flags = 0x1; /* IOC */
/* ── 8. Program stream descriptor ──────────────────────────────────────── */
/* Cyclic Buffer Length = total audio data */
sd_w32(HDA_SD_CBL, size);
/* Last Valid Index = 0 (one BDL entry, index 0) */
sd_w16(HDA_SD_LVI, 0);
/* BDL base address */
sd_w32(HDA_SD_BDPL, (uint32_t)(g_bdl_phys & 0xFFFFFFFF));
sd_w32(HDA_SD_BDPU, (uint32_t)(g_bdl_phys >> 32));
/* Format */
sd_w16(HDA_SD_FMT, format_word);
/* Stream tag = 1 in SD CTL byte 2 bits [7:4] */
sd_w8(HDA_SD_CTL2, (sd_r8(HDA_SD_CTL2) & 0x0Fu) | (1u << 4));
/* Clear any stale status bits (write-1-to-clear) */
sd_w8(HDA_SD_STS, HDA_SD_STS_BCIS | HDA_SD_STS_FIFOE | HDA_SD_STS_DESE);
/* ── 9. Enable stream-level interrupt-on-completion and run ─────────────── */
uint8_t ctl = sd_r8(HDA_SD_CTL0);
ctl |= HDA_SD_CTL_IOCE | HDA_SD_CTL_RUN;
ctl &= ~HDA_SD_CTL_SRST;
sd_w8(HDA_SD_CTL0, ctl);
kprintf("[HDA] playback started: %u Hz %u-bit %uch %u bytes\n",
fmt.sample_rate, fmt.bits_per_sample, fmt.channels, size);
return true;
}
void hda_stop(void)
{
if (!g_mmio) return;
/* Clear RUN bit */
sd_w8(HDA_SD_CTL0, sd_r8(HDA_SD_CTL0) & ~HDA_SD_CTL_RUN);
/* Wait for hardware to acknowledge */
for (int i = 0; i < 100000; i++) {
if (!(sd_r8(HDA_SD_CTL0) & HDA_SD_CTL_RUN)) break;
asm volatile("pause");
}
/* Clear status */
sd_w8(HDA_SD_STS, HDA_SD_STS_BCIS | HDA_SD_STS_FIFOE | HDA_SD_STS_DESE);
}
void hda_wait_complete(void)
{
if (!g_mmio) return;
/* Poll BCIS (Buffer Completion Interrupt Status) */
while (!(sd_r8(HDA_SD_STS) & HDA_SD_STS_BCIS))
asm volatile("pause");
hda_stop();
kprintf("[HDA] playback complete\n");
}
bool hda_is_playing(void)
{
if (!g_mmio) return false;
if (!(sd_r8(HDA_SD_CTL0) & HDA_SD_CTL_RUN)) return false;
if ( sd_r8(HDA_SD_STS) & HDA_SD_STS_BCIS) return false; /* finished */
return true;
}