sound: HDA support with PCM and wav playback

- Implemented HDA driver in src/sound/hda.c, providing initialization, codec enumeration, and playback functionality.
- Defined HDA register offsets and structures in src/sound/hda.h.
- Added WAV file parsing and playback capabilities in src/sound/pcm.c, supporting both WAV and raw PCM formats.
- Created header file src/sound/pcm.h for PCM playback functions and WAV header structures.
- Integrated memory management for DMA buffers during audio playback.

Yes we played Charlie Charlie Kirky as the first sound on KirkOS

Signed-off-by: kaguya <vpshinomiya@protonmail.com>
This commit is contained in:
kaguya
2026-04-16 00:08:56 -04:00
parent 4f0480fa84
commit bd77c7a2b9
7 changed files with 903 additions and 3 deletions
+2 -2
View File
@@ -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);
+6 -1
View File
@@ -25,6 +25,8 @@
#include <uacpi/event.h>
#include <uacpi/sleep.h>
#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");
}
+536
View File
@@ -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;
}
+133
View File
@@ -0,0 +1,133 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#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);
+173
View File
@@ -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);
}
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#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);