diff --git a/ext2_root/kirky.wav b/ext2_root/kirky.wav new file mode 100644 index 0000000..a17ec82 Binary files /dev/null and b/ext2_root/kirky.wav differ diff --git a/src/arch/x86_64/pci.c b/src/arch/x86_64/pci.c index 238e6cf..07b2d2f 100644 --- a/src/arch/x86_64/pci.c +++ b/src/arch/x86_64/pci.c @@ -114,7 +114,7 @@ void pci_print_all(void) bool pci_find_hda(pci_device_t* out_dev) { // Same scan as above, but stop at first HDA device - for (uint8_t bus = 0; bus < 1; bus++) { + for (uint8_t bus = 0; bus < 256; bus++) { for (uint8_t dev = 0; dev < 32; dev++) { for (uint8_t func = 0; func < 8; func++) { pci_address_t addr = {bus, dev, func}; @@ -122,7 +122,7 @@ bool pci_find_hda(pci_device_t* out_dev) if (vendor == 0xFFFF) continue; uint16_t class_sub = pci_read16(addr, 0x0A); - if (class_sub == (PCI_CLASS_AUDIO << 8) | PCI_SUBCLASS_HDA) { + if (((class_sub >> 8) == PCI_CLASS_AUDIO) && ((class_sub & 0xFF) == PCI_SUBCLASS_HDA)) { out_dev->addr = addr; out_dev->vendor_id = vendor; out_dev->device_id = pci_read16(addr, 0x02); diff --git a/src/main.c b/src/main.c index 4f8959b..e5322e6 100644 --- a/src/main.c +++ b/src/main.c @@ -25,6 +25,8 @@ #include #include #include "arch/x86_64/pci.h" +#include "sound/hda.h" +#include "sound/pcm.h" uintptr_t g_hhdm_offset; @@ -427,10 +429,13 @@ void kmain(void) { pci_device_t hda; if (pci_find_hda(&hda)) { + printf("[PCI] Found HDA controller at %02x:%02x.%x %04x:%04x\n", hda.addr.bus, hda.addr.device, hda.addr.function, hda.vendor_id, hda.device_id); - // BAR0 is usually at hda.bar[0] & ~0xF (MMIO) + if (hda_init(&hda)) { + pcm_play_file("kirky.wav"); + } } else { printf("[PCI] No HDA controller found!\n"); } diff --git a/src/sound/hda.c b/src/sound/hda.c new file mode 100644 index 0000000..385185c --- /dev/null +++ b/src/sound/hda.c @@ -0,0 +1,536 @@ +#include "hda.h" +#include "mm/vmm.h" +#include "mm/pmm.h" +#include "mm/memory.h" +#include "stdio.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]; + } + } + printf("[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"); + } + printf("[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"); + } + printf("[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) { printf("[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) { printf("[HDA] no DAC found\n"); return false; } + if (!pin) { printf("[HDA] no output pin found\n"); return false; } + + g_afg = afg; g_dac = dac; g_pin = pin; + printf("[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); + printf("[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: + printf("[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) { + printf("[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; + printf("[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 */ + printf("[HDA] gcap=%04x ISS=%u OSS=%u\n", gcap, g_iss, oss); + if (!oss) { printf("[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()) { printf("[HDA] CORB init failed\n"); return false; } + if (!rirb_init()) { printf("[HDA] RIRB init failed\n"); return false; } + + /* ── 6. Detect and enumerate codecs ─────────────────────────────────────── */ + uint16_t statests = r16(HDA_STATESTS); + if (!statests) { printf("[HDA] no codecs detected\n"); return false; } + + bool found = false; + for (uint8_t c = 0; c < 15; c++) { + if (!(statests & (1u << c))) continue; + printf("[HDA] codec %u present\n", c); + if (!found && enumerate_codec(c)) { + g_codec = c; + found = true; + } + } + if (!found) { printf("[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) { printf("[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); + + printf("[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) { + printf("[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); + + printf("[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(); + printf("[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; +} \ No newline at end of file diff --git a/src/sound/hda.h b/src/sound/hda.h new file mode 100644 index 0000000..c027117 --- /dev/null +++ b/src/sound/hda.h @@ -0,0 +1,133 @@ +#pragma once +#include +#include +#include "arch/x86_64/pci.h" + +/* ── HDA MMIO register offsets ─────────────────────────────────────────────── */ +#define HDA_GCAP 0x00 /* u16 Global Capabilities */ +#define HDA_VMIN 0x02 /* u8 Minor Version */ +#define HDA_VMAJ 0x03 /* u8 Major Version */ +#define HDA_OUTPAY 0x04 /* u16 Output Payload Capability */ +#define HDA_INPAY 0x06 /* u16 Input Payload Capability */ +#define HDA_GCTL 0x08 /* u32 Global Control */ +#define HDA_WAKEEN 0x0C /* u16 Wake Enable */ +#define HDA_STATESTS 0x0E /* u16 State Change Status (one bit per codec) */ +#define HDA_GSTS 0x10 /* u16 Global Status */ +#define HDA_INTCTL 0x20 /* u32 Interrupt Control */ +#define HDA_INTSTS 0x24 /* u32 Interrupt Status */ +#define HDA_WALCLK 0x30 /* u32 Wall Clock Counter */ +#define HDA_SSYNC 0x38 /* u32 Stream Synchronisation */ + +/* CORB */ +#define HDA_CORBLBASE 0x40 /* u32 CORB Lower Base Address */ +#define HDA_CORBUBASE 0x44 /* u32 CORB Upper Base Address */ +#define HDA_CORBWP 0x48 /* u16 CORB Write Pointer */ +#define HDA_CORBRP 0x4A /* u16 CORB Read Pointer */ +#define HDA_CORBCTL 0x4C /* u8 CORB Control */ +#define HDA_CORBSTS 0x4D /* u8 CORB Status */ +#define HDA_CORBSIZE 0x4E /* u8 CORB Size */ + +/* RIRB */ +#define HDA_RIRBLBASE 0x50 /* u32 RIRB Lower Base Address */ +#define HDA_RIRBUBASE 0x54 /* u32 RIRB Upper Base Address */ +#define HDA_RIRBWP 0x58 /* u16 RIRB Write Pointer (hardware-maintained)*/ +#define HDA_RINTCNT 0x5A /* u16 Response Interrupt Count */ +#define HDA_RIRBCTL 0x5C /* u8 RIRB Control */ +#define HDA_RIRBSTS 0x5D /* u8 RIRB Status */ +#define HDA_RIRBSIZE 0x5E /* u8 RIRB Size */ + +/* Stream Descriptor register offsets (relative to the SD's MMIO base) */ +#define HDA_SD_CTL0 0x00 /* u8 Control byte 0 (SRST, RUN, IOCE …) */ +#define HDA_SD_CTL1 0x01 /* u8 Control byte 1 */ +#define HDA_SD_CTL2 0x02 /* u8 Control byte 2 bits[7:4] = stream tag */ +#define HDA_SD_STS 0x03 /* u8 Status bit2=BCIS bit3=FIFOE bit4=DESE */ +#define HDA_SD_LPIB 0x04 /* u32 Link Position in Buffer */ +#define HDA_SD_CBL 0x08 /* u32 Cyclic Buffer Length */ +#define HDA_SD_LVI 0x0C /* u16 Last Valid Index */ +#define HDA_SD_FIFOS 0x10 /* u16 FIFO Size (read-only) */ +#define HDA_SD_FMT 0x12 /* u16 Format */ +#define HDA_SD_BDPL 0x18 /* u32 BDL Lower Base Address */ +#define HDA_SD_BDPU 0x1C /* u32 BDL Upper Base Address */ +#define HDA_SD_STRIDE 0x20 /* Each stream descriptor is 0x20 bytes wide */ + +/* GCTL bits */ +#define HDA_GCTL_CRST (1u << 0) /* Controller Reset (0 = reset, 1 = run) */ +#define HDA_GCTL_UNSOL (1u << 8) /* Accept Unsolicited Responses */ + +/* SD CTL byte-0 bits */ +#define HDA_SD_CTL_SRST (1u << 0) /* Stream Reset */ +#define HDA_SD_CTL_RUN (1u << 1) /* Stream Run */ +#define HDA_SD_CTL_IOCE (1u << 2) /* Interrupt on Completion Enable */ +#define HDA_SD_CTL_FEIE (1u << 3) /* FIFO Error Interrupt Enable */ +#define HDA_SD_CTL_DEIE (1u << 4) /* Descriptor Error Interrupt Enable */ + +/* SD STS bits */ +#define HDA_SD_STS_BCIS (1u << 2) /* Buffer Completion Interrupt Status */ +#define HDA_SD_STS_FIFOE (1u << 3) /* FIFO Error */ +#define HDA_SD_STS_DESE (1u << 4) /* Descriptor Error */ + +/* Codec widget types (from AUDIO_WIDGET_CAP bits[23:20]) */ +#define HDA_WTYPE_AUDIO_OUT 0x0 +#define HDA_WTYPE_AUDIO_IN 0x1 +#define HDA_WTYPE_AUDIO_MIXER 0x2 +#define HDA_WTYPE_AUDIO_SEL 0x3 +#define HDA_WTYPE_PIN 0x4 + +/* Codec parameter IDs (used with GET_PARAM verb 0xF00) */ +#define HDA_PARAM_VENDOR_ID 0x00 +#define HDA_PARAM_NODE_COUNT 0x04 +#define HDA_PARAM_FUNC_GROUP_TYPE 0x05 +#define HDA_PARAM_AUDIO_WIDGET_CAP 0x09 +#define HDA_PARAM_SUPPORTED_PCM 0x0A +#define HDA_PARAM_PIN_CAPABILITIES 0x0C +#define HDA_PARAM_CONN_LIST_LEN 0x0E +#define HDA_PARAM_AMP_CAP_OUTPUT 0x12 + +/* Pin capability bits */ +#define HDA_PINCAP_OUTPUT (1u << 4) +#define HDA_PINCAP_INPUT (1u << 5) + +/* ── Types ─────────────────────────────────────────────────────────────────── */ + +typedef struct { + uint32_t sample_rate; + uint8_t channels; /* 1 = mono, 2 = stereo */ + uint8_t bits_per_sample; /* 8 / 16 / 20 / 24 / 32 */ +} hda_format_t; + +/* Buffer Descriptor List entry (must be 128-byte aligned; page-aligned is fine) */ +typedef struct __attribute__((packed)) { + uint64_t addr; /* Physical address of the audio buffer */ + uint32_t length; /* Length in bytes */ + uint32_t flags; /* Bit 0 = IOC (Interrupt on Completion) */ +} hda_bdl_entry_t; + +/* ── Public API ─────────────────────────────────────────────────────────────── */ + +/** + * hda_init - find, map, and initialise the first HDA controller on the bus. + * @dev: PCI device struct returned by pci_find_hda(). + * Returns true on success. + */ +bool hda_init(pci_device_t *dev); + +/** + * hda_play_pcm - set up an output stream and start playback. + * @phys_buf : physical address of the audio data (from pmm_alloc). + * @size : byte count of the audio data. + * @fmt : sample rate / channel count / bit depth. + * Returns true if the stream was started successfully. + */ +bool hda_play_pcm(uintptr_t phys_buf, uint32_t size, hda_format_t fmt); + +/** + * hda_wait_complete - block until the current stream finishes (polls BCIS). + * Stops and resets the stream when done. + */ +void hda_wait_complete(void); + +/** hda_stop - immediately stop the output stream. */ +void hda_stop(void); + +/** hda_is_playing - returns true while the stream is running and not done. */ +bool hda_is_playing(void); \ No newline at end of file diff --git a/src/sound/pcm.c b/src/sound/pcm.c new file mode 100644 index 0000000..a59f7f3 --- /dev/null +++ b/src/sound/pcm.c @@ -0,0 +1,173 @@ +#include "pcm.h" +#include "hda.h" +#include "fs/ext2.h" +#include "mm/pmm.h" +#include "mm/vmm.h" +#include "mm/memory.h" +#include "stdio.h" + +/* ── WAV chunk scanner ──────────────────────────────────────────────────────── */ + +/* + * Scan a buffer for RIFF/WAVE format. If it is a valid PCM WAV file, fills + * in *fmt_out, *data_offset and *data_size and returns true. + * + * WAV chunks are word-padded (each chunk rounds up to an even byte count), so + * we skip through them by (8 + ALIGN_UP(chunk_size, 2)) bytes at a time. + */ +static bool parse_wav(const uint8_t *buf, uint32_t buf_size, + hda_format_t *fmt_out, + uint32_t *data_offset, + uint32_t *data_size) +{ + if (buf_size < 12) return false; + + /* Check RIFF header */ + if (buf[0]!='R'||buf[1]!='I'||buf[2]!='F'||buf[3]!='F') return false; + if (buf[8]!='W'||buf[9]!='A'||buf[10]!='V'||buf[11]!='E') return false; + + uint32_t pos = 12; + bool got_fmt = false; + bool got_data = false; + hda_format_t fmt = {0}; + + while (pos + 8 <= buf_size) { + /* Read chunk ID and size (little-endian) */ + const char *id = (const char *)(buf + pos); + uint32_t chunk_size = *(const uint32_t *)(buf + pos + 4); + pos += 8; + + if (pos + chunk_size > buf_size) break; /* truncated */ + + if (id[0]=='f' && id[1]=='m' && id[2]=='t' && id[3]==' ') { + if (chunk_size < 16) { pos += chunk_size; continue; } + + uint16_t audio_fmt = *(uint16_t *)(buf + pos + 0); + if (audio_fmt != 1) { + printf("[PCM] WAV audio format %u not supported (only PCM=1)\n", + audio_fmt); + return false; + } + fmt.channels = (uint8_t)*(uint16_t *)(buf + pos + 2); + fmt.sample_rate = *(uint32_t *)(buf + pos + 4); + fmt.bits_per_sample = (uint8_t)*(uint16_t *)(buf + pos + 14); + got_fmt = true; + + } else if (id[0]=='d' && id[1]=='a' && id[2]=='t' && id[3]=='a') { + *data_offset = pos; + *data_size = chunk_size; + got_data = true; + break; /* no need to scan further */ + } + + /* Advance past chunk (WAV chunks are word-aligned) */ + pos += chunk_size; + if (chunk_size & 1) pos++; /* skip pad byte */ + } + + if (!got_fmt || !got_data) return false; + *fmt_out = fmt; + return true; +} + +/* ── Shared internal helper ─────────────────────────────────────────────────── */ + +static bool play_from_ext2(const char *filename, + bool force_fmt, + hda_format_t forced) +{ + /* ── 1. Resolve path and read inode ─────────────────────────────────────── */ + uint32_t inum = ext2_resolve_path(filename); + if (!inum) { + printf("[PCM] file not found: %s\n", filename); + return false; + } + + ext2_inode_t inode; + if (!ext2_read_inode(inum, &inode)) { + printf("[PCM] failed to read inode for %s\n", filename); + return false; + } + + uint32_t file_size = inode.i_size; + if (!file_size) { + printf("[PCM] %s is empty\n", filename); + return false; + } + + /* ── 2. Allocate physically-contiguous DMA buffer ────────────────────────── */ + /* + * pmm_alloc() returns a contiguous run of physical pages, which is exactly + * what HDA's DMA engine needs. We access the data through HHDM. + */ + uint32_t pages = (file_size + PAGE_SIZE - 1) / PAGE_SIZE; + void *phys = pmm_alloc(pages); + if (!phys) { + printf("[PCM] OOM: cannot allocate %u pages for %s\n", pages, filename); + return false; + } + uint8_t *virt = (uint8_t *)((uintptr_t)phys + MEM_PHYS_OFFSET); + + /* Zero any padding at the end so stale bytes don't become noise */ + memset(virt + file_size, 0, (size_t)(pages * PAGE_SIZE - file_size)); + + /* ── 3. Read file data ───────────────────────────────────────────────────── */ + if (!ext2_read_file(&inode, virt)) { + printf("[PCM] failed to read %s\n", filename); + pmm_free(phys, pages); + return false; + } + + /* ── 4. Determine format and audio data region ───────────────────────────── */ + hda_format_t fmt; + uintptr_t audio_phys = (uintptr_t)phys; + uint32_t audio_size = file_size; + + if (force_fmt) { + fmt = forced; + /* Treat the whole file as raw audio samples */ + } else { + uint32_t data_offset = 0, data_size = 0; + + if (parse_wav(virt, file_size, &fmt, &data_offset, &data_size)) { + /* Adjust the physical pointer to skip the WAV headers */ + audio_phys += data_offset; + audio_size = data_size; + printf("[PCM] WAV: %u Hz %u-bit %u ch %u bytes of audio\n", + fmt.sample_rate, fmt.bits_per_sample, fmt.channels, audio_size); + } else { + /* Not a WAV – assume raw 48 kHz, 16-bit stereo */ + fmt.sample_rate = 48000; + fmt.bits_per_sample = 16; + fmt.channels = 2; + printf("[PCM] Raw PCM assumed: 48000 Hz 16-bit stereo %u bytes\n", + audio_size); + } + } + + /* ── 5. Play ─────────────────────────────────────────────────────────────── */ + bool ok = hda_play_pcm(audio_phys, audio_size, fmt); + if (ok) + hda_wait_complete(); /* blocks until the stream finishes */ + else + printf("[PCM] hda_play_pcm failed\n"); + + /* ── 6. Free the DMA buffer ─────────────────────────────────────────────── */ + pmm_free(phys, pages); + return ok; +} + +/* ══════════════════════════════════════════════════════════════════════════════ + * Public API + * ══════════════════════════════════════════════════════════════════════════════ */ + +bool pcm_play_file(const char *filename) +{ + hda_format_t dummy = {0}; + return play_from_ext2(filename, false, dummy); +} + +bool pcm_play_raw(const char *filename, hda_format_t fmt) +{ + return play_from_ext2(filename, true, fmt); +} \ No newline at end of file diff --git a/src/sound/pcm.h b/src/sound/pcm.h new file mode 100644 index 0000000..627d474 --- /dev/null +++ b/src/sound/pcm.h @@ -0,0 +1,53 @@ +#pragma once +#include +#include +#include "hda.h" + +/* + * Minimal WAV (RIFF/WAVE) header types. + * pcm_play_file() autodetects RIFF/WAVE and falls back to raw PCM if the + * magic bytes aren't present. + */ + +typedef struct __attribute__((packed)) { + char chunk_id[4]; /* "RIFF" */ + uint32_t chunk_size; /* file size − 8 */ + char format[4]; /* "WAVE" */ +} wav_riff_t; + +typedef struct __attribute__((packed)) { + char chunk_id[4]; /* "fmt " */ + uint32_t chunk_size; /* 16 for PCM */ + uint16_t audio_format; /* 1 = PCM (linear) */ + uint16_t channels; + uint32_t sample_rate; + uint32_t byte_rate; /* sample_rate * channels * bits_per_sample / 8 */ + uint16_t block_align; + uint16_t bits_per_sample; +} wav_fmt_chunk_t; + +/* + * pcm_play_file - read a file from the ext2 root directory and play it via HDA. + * + * Supports: + * • WAV – any PCM WAV (no compression): sample rate / channels / bit depth + * are read from the "fmt " chunk automatically. + * • Raw – no RIFF header; audio is assumed to be 48 000 Hz, 16-bit, stereo. + * Override with pcm_play_raw() if your file has a different format. + * + * The function allocates a physically-contiguous DMA buffer, reads the file, + * starts playback, blocks until complete, then frees the buffer. + * + * @filename : name of the file in the ext2 root directory (e.g. "music.wav"). + * Returns : true on success. + */ +bool pcm_play_file(const char *filename); + +/* + * pcm_play_raw - same as pcm_play_file but forces the given format instead of + * auto-detecting from a WAV header. Useful for headerless .pcm files. + * + * @filename : file in ext2 root. + * @fmt : sample rate / channels / bit depth. + */ +bool pcm_play_raw(const char *filename, hda_format_t fmt); \ No newline at end of file