major refactorings
Signed-off-by: kaguya3311 <kaguya3311@national.shitposting.agency>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "arch/x86_64/bus/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);
|
||||
@@ -0,0 +1,173 @@
|
||||
#include "pcm.h"
|
||||
#include "hda.h"
|
||||
#include "mm/pmm.h"
|
||||
#include "mm/vmm.h"
|
||||
#include "mm/memory.h"
|
||||
#include "libk/stdio.h"
|
||||
#include "libk/debug.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) {
|
||||
kprintf("[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) {
|
||||
kprintf("[PCM] file not found: %s\n", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
ext2_inode_t inode;
|
||||
if (!ext2_read_inode(inum, &inode)) {
|
||||
kprintf("[PCM] failed to read inode for %s\n", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t file_size = inode.i_size;
|
||||
if (!file_size) {
|
||||
kprintf("[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) {
|
||||
kprintf("[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)) {
|
||||
kprintf("[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;
|
||||
kprintf("[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;
|
||||
kprintf("[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
|
||||
kprintf("[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);
|
||||
}
|
||||
@@ -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 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);
|
||||
+1401
File diff suppressed because it is too large
Load Diff
+220
@@ -0,0 +1,220 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define EXT2_SUPERBLOCK_OFFSET 1024
|
||||
#define EXT2_SUPERBLOCK_SIZE 1024
|
||||
#define EXT2_MAGIC 0xEF53
|
||||
#define EXT2_NAME_LEN 255
|
||||
|
||||
typedef struct ext2_superblock {
|
||||
unsigned int s_inodes_count; /* Inodes count */
|
||||
unsigned int s_blocks_count; /* Blocks count */
|
||||
unsigned int s_r_blocks_count; /* Reserved blocks count */
|
||||
unsigned int s_free_blocks_count; /* Free blocks count */
|
||||
unsigned int s_free_inodes_count; /* Free inodes count */
|
||||
unsigned int s_first_data_block; /* First Data Block */
|
||||
unsigned int s_log_block_size; /* Block size */
|
||||
unsigned int s_log_frag_size; /* Fragment size */
|
||||
unsigned int s_blocks_per_group; /* # Blocks per group */
|
||||
unsigned int s_frags_per_group; /* # Fragments per group */
|
||||
unsigned int s_inodes_per_group; /* # Inodes per group */
|
||||
unsigned int s_mtime; /* Mount time */
|
||||
unsigned int s_wtime; /* Write time */
|
||||
unsigned short s_mnt_count; /* Mount count */
|
||||
unsigned short s_max_mnt_count; /* Maximal mount count */
|
||||
unsigned short s_magic; /* Magic signature */
|
||||
unsigned short s_state; /* File system state */
|
||||
unsigned short s_errors; /* Behaviour when detecting errors */
|
||||
unsigned short s_minor_rev_level; /* minor revision level */
|
||||
unsigned int s_lastcheck; /* time of last check */
|
||||
unsigned int s_checkinterval; /* max. time between checks */
|
||||
unsigned int s_creator_os; /* OS */
|
||||
unsigned int s_rev_level; /* Revision level */
|
||||
unsigned short s_def_resuid; /* Default uid for reserved blocks */
|
||||
unsigned short s_def_resgid; /* Default gid for reserved blocks */
|
||||
/*
|
||||
* These fields are for EXT2_DYNAMIC_REV superblocks only.
|
||||
*
|
||||
* Note: the difference between the compatible feature set and
|
||||
* the incompatible feature set is that if there is a bit set
|
||||
* in the incompatible feature set that the kernel doesn't
|
||||
* know about, it should refuse to mount the filesystem.
|
||||
*
|
||||
* e2fsck's requirements are more strict; if it doesn't know
|
||||
* about a feature in either the compatible or incompatible
|
||||
* feature set, it must abort and not try to meddle with
|
||||
* things it doesn't understand...
|
||||
*/
|
||||
unsigned int s_first_ino; /* First non-reserved inode */
|
||||
unsigned short s_inode_size; /* size of inode structure */
|
||||
unsigned short s_block_group_nr; /* block group # of this superblock */
|
||||
unsigned int s_feature_compat; /* compatible feature set */
|
||||
unsigned int s_feature_incompat; /* incompatible feature set */
|
||||
unsigned int s_feature_ro_compat; /* readonly-compatible feature set */
|
||||
unsigned char s_uuid[16]; /* 128-bit uuid for volume */
|
||||
char s_volume_name[16]; /* volume name */
|
||||
char s_last_mounted[64]; /* directory where last mounted */
|
||||
unsigned int s_algorithm_usage_bitmap; /* For compression */
|
||||
/*
|
||||
* Performance hints. Directory preallocation should only
|
||||
* happen if the EXT2_COMPAT_PREALLOC flag is on.
|
||||
*/
|
||||
unsigned char s_prealloc_blocks; /* Nr of blocks to try to preallocate*/
|
||||
unsigned char s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
|
||||
unsigned short s_padding1;
|
||||
/*
|
||||
* Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
|
||||
*/
|
||||
unsigned char s_journal_uuid[16]; /* uuid of journal superblock */
|
||||
unsigned int s_journal_inum; /* inode number of journal file */
|
||||
unsigned int s_journal_dev; /* device number of journal file */
|
||||
unsigned int s_last_orphan; /* start of list of inodes to delete */
|
||||
unsigned int s_hash_seed[4]; /* HTREE hash seed */
|
||||
unsigned char s_def_hash_version; /* Default hash version to use */
|
||||
unsigned char s_reserved_char_pad;
|
||||
unsigned short s_reserved_word_pad;
|
||||
unsigned int s_default_mount_opts;
|
||||
unsigned int s_first_meta_bg; /* First metablock block group */
|
||||
unsigned int s_reserved[190]; /* Padding to the end of the block */
|
||||
} __attribute__((packed)) ext2_superblock_t;
|
||||
|
||||
typedef struct ext2_group_desc {
|
||||
unsigned int bg_block_bitmap; /* Blocks bitmap block */
|
||||
unsigned int bg_inode_bitmap; /* Inodes bitmap block */
|
||||
unsigned int bg_inode_table; /* Inodes table block */
|
||||
unsigned short bg_free_blocks_count; /* Free blocks count */
|
||||
unsigned short bg_free_inodes_count; /* Free inodes count */
|
||||
unsigned short bg_used_dirs_count; /* Directories count */
|
||||
unsigned short bg_pad;
|
||||
unsigned int bg_reserved[3];
|
||||
} __attribute__((packed)) ext2_group_desc_t;
|
||||
|
||||
typedef struct ext2_inode {
|
||||
unsigned short i_mode; /* File mode */
|
||||
unsigned short i_uid; /* Low 16 bits of Owner Uid */
|
||||
unsigned int i_size; /* Size in bytes */
|
||||
unsigned int i_atime; /* Access time */
|
||||
unsigned int i_ctime; /* Creation time */
|
||||
unsigned int i_mtime; /* Modification time */
|
||||
unsigned int i_dtime; /* Deletion Time */
|
||||
unsigned short i_gid; /* Low 16 bits of Group Id */
|
||||
unsigned short i_links_count; /* Links count */
|
||||
unsigned int i_blocks; /* Blocks count IN DISK SECTORS*/
|
||||
unsigned int i_flags; /* File flags */
|
||||
unsigned int osd1; /* OS dependent 1 */
|
||||
unsigned int i_block[15]; /* Pointers to blocks */
|
||||
unsigned int i_generation; /* File version (for NFS) */
|
||||
unsigned int i_file_acl; /* File ACL */
|
||||
unsigned int i_dir_acl; /* Directory ACL */
|
||||
unsigned int i_faddr; /* Fragment address */
|
||||
unsigned int extra[3];
|
||||
} __attribute__((packed)) ext2_inode_t;
|
||||
|
||||
typedef struct ext2_dir_entry {
|
||||
unsigned int inode; /* Inode number */
|
||||
unsigned short rec_len; /* Directory entry length */
|
||||
unsigned char name_len; /* Name length */
|
||||
unsigned char file_type;
|
||||
char name[]; /* File name, up to EXT2_NAME_LEN */
|
||||
} __attribute__((packed)) ext2_dir_entry_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t ino;
|
||||
uint16_t mode;
|
||||
uint16_t uid;
|
||||
uint16_t gid;
|
||||
uint32_t size;
|
||||
uint32_t atime;
|
||||
uint32_t mtime;
|
||||
uint32_t ctime;
|
||||
uint32_t nlink;
|
||||
uint32_t blocks;
|
||||
} ext2_stat_t;
|
||||
|
||||
typedef enum {
|
||||
EXT2_WRITE_OVERWRITE,
|
||||
EXT2_WRITE_APPEND,
|
||||
} ext2_write_mode_t;
|
||||
|
||||
// Inode type and permissions
|
||||
#define EXT2_S_IFIFO 0x1000
|
||||
#define EXT2_S_IFCHR 0x2000
|
||||
#define EXT2_S_IFDIR 0x4000
|
||||
#define EXT2_S_IFBLK 0x6000
|
||||
#define EXT2_S_IFREG 0x8000
|
||||
#define EXT2_S_IFLNK 0xA000
|
||||
#define EXT2_S_IFSOCK 0xC000
|
||||
|
||||
#define EXT2_S_IRUSR 0x0100
|
||||
#define EXT2_S_IWUSR 0x0080
|
||||
#define EXT2_S_IXUSR 0x0040
|
||||
#define EXT2_S_IRGRP 0x0020
|
||||
#define EXT2_S_IWGRP 0x0010
|
||||
#define EXT2_S_IXGRP 0x0008
|
||||
#define EXT2_S_IROTH 0x0004
|
||||
#define EXT2_S_IWOTH 0x0002
|
||||
#define EXT2_S_IXOTH 0x0001
|
||||
|
||||
// File types for directory entries
|
||||
#define EXT2_FT_UNKNOWN 0
|
||||
#define EXT2_FT_REG_FILE 1
|
||||
#define EXT2_FT_DIR 2
|
||||
#define EXT2_FT_CHRDEV 3
|
||||
#define EXT2_FT_BLKDEV 4
|
||||
#define EXT2_FT_FIFO 5
|
||||
#define EXT2_FT_SOCK 6
|
||||
#define EXT2_FT_SYMLINK 7
|
||||
|
||||
|
||||
|
||||
|
||||
// ── init ──────────────────────────────────────────────────────────────────────
|
||||
bool ext2_read_superblock(void);
|
||||
bool ext2_read_group_desc_table(void);
|
||||
|
||||
// ── low-level ────────────────────────────────────────────────────────────────
|
||||
bool ext2_read_block(uint32_t block_num, void* buf);
|
||||
bool ext2_write_block(uint32_t block_num, const void* buf);
|
||||
bool ext2_read_inode(uint32_t inode_num, ext2_inode_t* out);
|
||||
bool ext2_write_inode(uint32_t inode_num, ext2_inode_t* inode);
|
||||
|
||||
// ── path / directory ─────────────────────────────────────────────────────────
|
||||
uint32_t ext2_resolve_path(const char* path); // returns inode num, 0 = not found
|
||||
bool ext2_find_in_dir(ext2_inode_t* dir, const char* name, uint32_t* out_inum);
|
||||
bool ext2_read_dir(ext2_inode_t* dir);
|
||||
bool ext2_read_root_dir(void);
|
||||
|
||||
// ── file I/O ─────────────────────────────────────────────────────────────────
|
||||
bool ext2_read_file(ext2_inode_t* inode, uint8_t* buf);
|
||||
bool ext2_write_file(ext2_inode_t* inode, uint32_t inode_num,
|
||||
const uint8_t* data, uint32_t size, ext2_write_mode_t mode);
|
||||
bool ext2_truncate(ext2_inode_t* inode, uint32_t inode_num, uint32_t new_size);
|
||||
|
||||
// ── high-level (operate from a path string) ───────────────────────────────────
|
||||
bool ext2_read_file_from_root(const char* name, uint8_t* buf, uint32_t* size);
|
||||
bool ext2_write_file_from_root(const char* name, const uint8_t* data,
|
||||
uint32_t size, ext2_write_mode_t mode);
|
||||
|
||||
// ── create / delete ───────────────────────────────────────────────────────────
|
||||
bool ext2_create_file(ext2_inode_t* dir, uint32_t dir_inum,
|
||||
const char* name, uint16_t mode, uint32_t* out_inum);
|
||||
bool ext2_mkdir(ext2_inode_t* parent, uint32_t parent_inum,
|
||||
const char* name, uint32_t* out_inum);
|
||||
bool ext2_unlink(ext2_inode_t* dir, uint32_t dir_inum, const char* name);
|
||||
bool ext2_rmdir(ext2_inode_t* parent, uint32_t parent_inum, const char* name);
|
||||
bool ext2_rename(ext2_inode_t* src_dir, uint32_t src_inum,
|
||||
ext2_inode_t* dst_dir, uint32_t dst_inum,
|
||||
const char* old_name, const char* new_name);
|
||||
|
||||
// ── symlinks ──────────────────────────────────────────────────────────────────
|
||||
bool ext2_symlink(ext2_inode_t* dir, uint32_t dir_inum,
|
||||
const char* name, const char* target);
|
||||
bool ext2_readlink(uint32_t inode_num, char* buf, uint32_t buf_size);
|
||||
|
||||
// ── metadata ─────────────────────────────────────────────────────────────────
|
||||
bool ext2_stat(uint32_t inode_num, ext2_stat_t* st);
|
||||
bool ext2_chmod(uint32_t inode_num, uint16_t mode);
|
||||
bool ext2_chown(uint32_t inode_num, uint16_t uid, uint16_t gid);
|
||||
|
||||
const char* ext2_file_type_string(uint8_t type);
|
||||
@@ -0,0 +1,150 @@
|
||||
#include "drivers/input/input.h"
|
||||
#include "mp/spinlock.h"
|
||||
#include "libk/stdio.h"
|
||||
#include "fs/vfs.h"
|
||||
#include <stddef.h>
|
||||
#include "mm/memory.h"
|
||||
#include "libk/string.h"
|
||||
#include <stddef.h>
|
||||
#include "libk/debug.h"
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Event ring-buffer
|
||||
* ------------------------------------------------------------------ */
|
||||
#define INPUT_BUF_SIZE 512
|
||||
#define CONSOLE_BUF_SIZE 256
|
||||
|
||||
static char s_console_buf[CONSOLE_BUF_SIZE];
|
||||
static size_t s_console_rpos = 0;
|
||||
static size_t s_console_wpos = 0;
|
||||
|
||||
static input_event_t s_buf[INPUT_BUF_SIZE];
|
||||
static size_t s_rpos = 0;
|
||||
static size_t s_wpos = 0;
|
||||
static spinlock_t s_lock = SPINLOCK_INIT;
|
||||
|
||||
/* Called from IRQ context */
|
||||
void input_push_char(char c)
|
||||
{
|
||||
uint64_t flags;
|
||||
spinlock_acquire_irqsave(&s_lock, &flags);
|
||||
size_t next = (s_console_wpos + 1) % CONSOLE_BUF_SIZE;
|
||||
if (next != s_console_rpos) { /* drop if full */
|
||||
s_console_buf[s_console_wpos] = c;
|
||||
s_console_wpos = next;
|
||||
}
|
||||
spinlock_release_irqrestore(&s_lock, flags);
|
||||
}
|
||||
|
||||
int input_read_console(void *buf, size_t len)
|
||||
{
|
||||
uint8_t *p = buf;
|
||||
size_t count = 0;
|
||||
uint64_t flags;
|
||||
|
||||
spinlock_acquire_irqsave(&s_lock, &flags);
|
||||
|
||||
while (count < len && s_console_rpos != s_console_wpos) {
|
||||
p[count++] = s_console_buf[s_console_rpos];
|
||||
s_console_rpos = (s_console_rpos + 1) % CONSOLE_BUF_SIZE;
|
||||
}
|
||||
|
||||
spinlock_release_irqrestore(&s_lock, flags);
|
||||
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
static void push_event(const input_event_t *ev)
|
||||
{
|
||||
spinlock_acquire_or_wait(&s_lock);
|
||||
size_t next = (s_wpos + 1) % INPUT_BUF_SIZE;
|
||||
if (next != s_rpos) { /* drop if full */
|
||||
s_buf[s_wpos] = *ev;
|
||||
s_wpos = next;
|
||||
}
|
||||
spinlock_drop(&s_lock);
|
||||
}
|
||||
|
||||
bool input_poll(input_event_t *out)
|
||||
{
|
||||
spinlock_acquire_or_wait(&s_lock);
|
||||
if (s_rpos == s_wpos) {
|
||||
spinlock_drop(&s_lock);
|
||||
return false;
|
||||
}
|
||||
*out = s_buf[s_rpos];
|
||||
s_rpos = (s_rpos + 1) % INPUT_BUF_SIZE;
|
||||
spinlock_drop(&s_lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool input_has_event(void)
|
||||
{
|
||||
spinlock_acquire_or_wait(&s_lock);
|
||||
bool has = (s_rpos != s_wpos);
|
||||
spinlock_drop(&s_lock);
|
||||
return has;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Driver-facing helpers
|
||||
* ------------------------------------------------------------------ */
|
||||
void input_push_key(uint8_t scancode, char ascii, bool pressed)
|
||||
{
|
||||
input_event_t ev = {
|
||||
.type = INPUT_EV_KEY,
|
||||
.key = { .scancode = scancode, .ascii = ascii, .pressed = pressed },
|
||||
};
|
||||
push_event(&ev);
|
||||
}
|
||||
|
||||
void input_push_mouse_rel(int16_t dx, int16_t dy)
|
||||
{
|
||||
input_event_t ev = { .type = INPUT_EV_MOUSE_REL, .rel = { dx, dy } };
|
||||
push_event(&ev);
|
||||
}
|
||||
|
||||
void input_push_mouse_btn(uint8_t button, bool pressed)
|
||||
{
|
||||
input_event_t ev = {
|
||||
.type = INPUT_EV_MOUSE_BTN,
|
||||
.btn = { .button = button, .pressed = pressed },
|
||||
};
|
||||
push_event(&ev);
|
||||
}
|
||||
|
||||
void input_push_mouse_wheel(int8_t delta)
|
||||
{
|
||||
input_event_t ev = { .type = INPUT_EV_MOUSE_WHEEL, .wheel = { delta } };
|
||||
push_event(&ev);
|
||||
}
|
||||
|
||||
|
||||
static int dev_input_read(void *buf, size_t len)
|
||||
{
|
||||
size_t count = 0;
|
||||
while (count + sizeof(input_event_t) <= len) {
|
||||
input_event_t ev;
|
||||
if (!input_poll(&ev)) break;
|
||||
memcpy((uint8_t*)buf + count, &ev, sizeof(ev));
|
||||
count += sizeof(ev);
|
||||
}
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
static int dev_input_write(const void *buf, size_t len)
|
||||
{
|
||||
(void)buf; (void)len;
|
||||
return -1; /* read-only */
|
||||
}
|
||||
|
||||
void input_register_devnodes(void)
|
||||
{
|
||||
VFS_RegisterCharDev("/dev/input/event0", dev_input_read, dev_input_write);
|
||||
}
|
||||
|
||||
void input_init(void)
|
||||
{
|
||||
input_register_devnodes();
|
||||
kprintf("input: subsystem initialized\n");
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Generic input event types
|
||||
* ------------------------------------------------------------------ */
|
||||
typedef enum {
|
||||
INPUT_EV_NONE = 0,
|
||||
INPUT_EV_KEY, /* key press / release */
|
||||
INPUT_EV_MOUSE_REL, /* relative mouse movement */
|
||||
INPUT_EV_MOUSE_BTN, /* mouse button press/release */
|
||||
INPUT_EV_MOUSE_WHEEL,
|
||||
} input_ev_type_t;
|
||||
|
||||
typedef struct {
|
||||
input_ev_type_t type;
|
||||
|
||||
union {
|
||||
/* INPUT_EV_KEY */
|
||||
struct {
|
||||
uint8_t scancode; /* raw set-1 scancode (break bit cleared) */
|
||||
char ascii; /* 0 if non-printable */
|
||||
bool pressed;
|
||||
} key;
|
||||
|
||||
/* INPUT_EV_MOUSE_REL */
|
||||
struct {
|
||||
int16_t dx, dy;
|
||||
} rel;
|
||||
|
||||
/* INPUT_EV_MOUSE_BTN */
|
||||
struct {
|
||||
uint8_t button; /* 0=left 1=right 2=middle */
|
||||
bool pressed;
|
||||
} btn;
|
||||
|
||||
/* INPUT_EV_MOUSE_WHEEL */
|
||||
struct {
|
||||
int8_t delta;
|
||||
} wheel;
|
||||
};
|
||||
} input_event_t;
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Public API
|
||||
* ------------------------------------------------------------------ */
|
||||
void input_init(void);
|
||||
|
||||
/* Called by drivers (PS/2, USB HID, …) to publish events */
|
||||
void input_push_key(uint8_t scancode, char ascii, bool pressed);
|
||||
void input_push_mouse_rel(int16_t dx, int16_t dy);
|
||||
void input_push_mouse_btn(uint8_t button, bool pressed);
|
||||
void input_push_mouse_wheel(int8_t delta);
|
||||
|
||||
void input_push_char(char c);
|
||||
int input_read_console(void *buf, size_t len);
|
||||
|
||||
/* Consumer API */
|
||||
bool input_poll(input_event_t *out); /* non-blocking; false if empty */
|
||||
bool input_has_event(void);
|
||||
|
||||
/* VFS helpers — call after VFS is ready */
|
||||
void input_register_devnodes(void); /* creates /dev/input/event0 */
|
||||
@@ -0,0 +1,195 @@
|
||||
#include "ps2.h"
|
||||
#include "drivers/input/input.h"
|
||||
#include "arch/x86_64/sys/irq.h"
|
||||
#include "arch/x86_64/boot/isr.h"
|
||||
#include "arch/x86_64/cpu/io.h"
|
||||
#include "arch/x86_64/sys/ioapic.h"
|
||||
#include "arch/x86_64/sys/apic.h"
|
||||
#include "libk/stdio.h"
|
||||
#include "libk/debug.h"
|
||||
#include "drivers/video/render.h"
|
||||
|
||||
/* ── PS/2 I/O ports ───────────────────────────────────────────────────────── */
|
||||
#define PS2_DATA_PORT 0x60 /* Read: scancode / Write: command data */
|
||||
#define PS2_STATUS_PORT 0x64 /* Read: controller status */
|
||||
#define PS2_CMD_PORT 0x64 /* Write: controller command */
|
||||
|
||||
/* Status register bits */
|
||||
#define PS2_STATUS_OBF (1 << 0) /* Output buffer full (data ready to read) */
|
||||
#define PS2_STATUS_IBF (1 << 1) /* Input buffer full (don't write yet) */
|
||||
|
||||
/* ── IRQ number for the keyboard ─────────────────────────────────────────── */
|
||||
#define KBD_IRQ 1
|
||||
#define KBD_IDT_VECTOR (0x20 + KBD_IRQ) /* 0x21 with PIC_REMAP_OFFSET=0x20 */
|
||||
|
||||
/* ── Modifier state ───────────────────────────────────────────────────────── */
|
||||
static bool s_shift = false;
|
||||
static bool s_ctrl = false;
|
||||
static bool s_alt = false;
|
||||
static bool s_caps_lock = false;
|
||||
static bool s_extended = false; /* true after receiving 0xE0 prefix */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Set-1 keycode → ASCII translation tables
|
||||
*
|
||||
* Index = Set-1 make code (0x00–0x58 covers the full AT-101 layout).
|
||||
* 0x00 = unmapped / non-printable.
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
static const char s_normal[128] = {
|
||||
/*00*/ 0, 0, '1', '2', '3', '4', '5', '6',
|
||||
/*08*/ '7', '8', '9', '0', '-', '=', '\b', '\t',
|
||||
/*10*/ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
|
||||
/*18*/ 'o', 'p', '[', ']', '\n', 0, 'a', 's',
|
||||
/*20*/ 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
|
||||
/*28*/ '\'','`', 0, '\\','z', 'x', 'c', 'v',
|
||||
/*30*/ 'b', 'n', 'm', ',', '.', '/', 0, '*',
|
||||
/*38*/ 0, ' ', 0, 0, 0, 0, 0, 0,
|
||||
/*40*/ 0, 0, 0, 0, 0, 0, 0, '7',
|
||||
/*48*/ '8', '9', '-', '4', '5', '6', '+', '1',
|
||||
/*50*/ '2', '3', '0', '.', 0, 0, 0, 0,
|
||||
/*58*/ 0
|
||||
};
|
||||
|
||||
static const char s_shifted[128] = {
|
||||
/*00*/ 0, 0, '!', '@', '#', '$', '%', '^',
|
||||
/*08*/ '&', '*', '(', ')', '_', '+', '\b', '\t',
|
||||
/*10*/ 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I',
|
||||
/*18*/ 'O', 'P', '{', '}', '\n', 0, 'A', 'S',
|
||||
/*20*/ 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':',
|
||||
/*28*/ '"', '~', 0, '|', 'Z', 'X', 'C', 'V',
|
||||
/*30*/ 'B', 'N', 'M', '<', '>', '?', 0, '*',
|
||||
/*38*/ 0, ' ', 0, 0, 0, 0, 0, 0,
|
||||
/*40*/ 0, 0, 0, 0, 0, 0, 0, '7',
|
||||
/*48*/ '8', '9', '-', '4', '5', '6', '+', '1',
|
||||
/*50*/ '2', '3', '0', '.', 0, 0, 0, 0,
|
||||
/*58*/ 0
|
||||
};
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Extended (0xE0-prefixed) make codes we care about
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
typedef enum {
|
||||
/* Regular Set-1 make codes (non-extended) */
|
||||
SC_LSHIFT = 0x2A,
|
||||
SC_RSHIFT = 0x36,
|
||||
SC_LCTRL = 0x1D,
|
||||
SC_LALT = 0x38,
|
||||
SC_CAPSLOCK = 0x3A,
|
||||
SC_BACKSPACE= 0x0E,
|
||||
SC_ENTER = 0x1C,
|
||||
SC_TAB = 0x0F,
|
||||
SC_ESC = 0x01,
|
||||
|
||||
/* Function keys */
|
||||
SC_F1 = 0x3B, SC_F2 = 0x3C, SC_F3 = 0x3D, SC_F4 = 0x3E,
|
||||
SC_F5 = 0x3F, SC_F6 = 0x40, SC_F7 = 0x41, SC_F8 = 0x42,
|
||||
SC_F9 = 0x43, SC_F10 = 0x44, SC_F11 = 0x57, SC_F12 = 0x58,
|
||||
} scancode_t;
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* IRQ1 handler
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void ps2_kbd_handler(Registers *regs)
|
||||
{
|
||||
(void)regs;
|
||||
|
||||
/*
|
||||
* Always drain the output buffer even if we don't handle the byte,
|
||||
* otherwise the PS/2 controller stalls and stops sending interrupts.
|
||||
*/
|
||||
uint8_t status = x86_64_inb(PS2_STATUS_PORT);
|
||||
if (!(status & PS2_STATUS_OBF)) {
|
||||
/* Spurious interrupt with no data — just return */
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t byte = x86_64_inb(PS2_DATA_PORT);
|
||||
|
||||
/* ── Handle 0xE0 extended-key prefix ─────────────────────────────── */
|
||||
if (byte == 0xE0) {
|
||||
s_extended = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/* ── Decode make / break ─────────────────────────────────────────── */
|
||||
bool pressed = !(byte & 0x80); /* bit 7 = 0 → make, 1 → break */
|
||||
uint8_t scancode = byte & 0x7F; /* strip the break bit */
|
||||
bool extended = s_extended;
|
||||
s_extended = false;
|
||||
|
||||
/* ── Update modifier keys ────────────────────────────────────────── */
|
||||
if (!extended) {
|
||||
switch (scancode) {
|
||||
case SC_LSHIFT: case SC_RSHIFT:
|
||||
s_shift = pressed;
|
||||
return; /* don't push a key event for bare modifiers */
|
||||
|
||||
case SC_LCTRL:
|
||||
s_ctrl = pressed;
|
||||
return;
|
||||
|
||||
case SC_LALT:
|
||||
s_alt = pressed;
|
||||
return;
|
||||
|
||||
case SC_CAPSLOCK:
|
||||
if (pressed) s_caps_lock = !s_caps_lock;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Translate to ASCII ──────────────────────────────────────────── */
|
||||
char ascii = 0;
|
||||
|
||||
if (!extended && scancode < 128) {
|
||||
bool use_upper = s_shift ^ s_caps_lock; /* XOR: caps inverts shift */
|
||||
ascii = use_upper ? s_shifted[scancode] : s_normal[scancode];
|
||||
}
|
||||
/* Extended keys (arrows, home, end, etc.) leave ascii=0 */
|
||||
|
||||
/* ── Push event ──────────────────────────────────────────────────── */
|
||||
input_push_key(scancode, ascii, pressed);
|
||||
|
||||
|
||||
if (pressed && ascii) {
|
||||
input_push_char(ascii);
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Public helpers
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
uint8_t ps2_kbd_read_scancode(void)
|
||||
{
|
||||
/* Spin until data is available (for early/polled use only) */
|
||||
while (!(x86_64_inb(PS2_STATUS_PORT) & PS2_STATUS_OBF))
|
||||
asm volatile("pause");
|
||||
return x86_64_inb(PS2_DATA_PORT);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Initialisation
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void ps2_kbd_init(void)
|
||||
{
|
||||
/*
|
||||
* ── Step 1: Flush the PS/2 output buffer ─────────────────────────
|
||||
*
|
||||
* Any leftover bytes from before boot will cause the controller to
|
||||
* withhold interrupts. Drain them now.
|
||||
*/
|
||||
int attempts = 16;
|
||||
while (attempts-- && (x86_64_inb(PS2_STATUS_PORT) & PS2_STATUS_OBF))
|
||||
(void)x86_64_inb(PS2_DATA_PORT);
|
||||
|
||||
|
||||
|
||||
x86_64_APIC_IRQ_RedirectAndRegister(1, 0x21, ps2_kbd_handler);
|
||||
|
||||
kprintf("[PS2] Keyboard driver initialised (IRQ%d vector 0x%02x)\n",
|
||||
KBD_IRQ, KBD_IDT_VECTOR);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
|
||||
void ps2_kbd_init(void);
|
||||
|
||||
uint8_t ps2_kbd_read_scancode(void);
|
||||
@@ -0,0 +1,205 @@
|
||||
#include "random.h"
|
||||
#include "fs/vfs.h"
|
||||
#include "libk/stdio.h"
|
||||
#include "arch/x86_64/sys/tsc.h"
|
||||
#include "arch/x86_64/sys/pit.h"
|
||||
#include "errno.h"
|
||||
#include <stdbool.h>
|
||||
#include "libk/string.h"
|
||||
#include "mm/memory.h"
|
||||
#include "limine.h"
|
||||
#include "libk/debug.h"
|
||||
#include "libk/debug.h"
|
||||
|
||||
/* ChaCha20 core (public-domain, 20-round) */
|
||||
static inline void chacha20_quarterround(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d) {
|
||||
*a += *b; *d ^= *a; *d = (*d << 16) | (*d >> 16);
|
||||
*c += *d; *b ^= *c; *b = (*b << 12) | (*b >> 20);
|
||||
*a += *b; *d ^= *a; *d = (*d << 8) | (*d >> 24);
|
||||
*c += *d; *b ^= *c; *b = (*b << 7) | (*b >> 25);
|
||||
}
|
||||
|
||||
static void chacha20_block(uint32_t state[16], uint8_t output[64]) {
|
||||
uint32_t x[16];
|
||||
for (int i = 0; i < 16; ++i) x[i] = state[i];
|
||||
|
||||
for (int i = 0; i < 10; ++i) { /* 10 double-rounds = 20 rounds */
|
||||
/* column rounds */
|
||||
chacha20_quarterround(&x[0], &x[4], &x[8], &x[12]);
|
||||
chacha20_quarterround(&x[1], &x[5], &x[9], &x[13]);
|
||||
chacha20_quarterround(&x[2], &x[6], &x[10], &x[14]);
|
||||
chacha20_quarterround(&x[3], &x[7], &x[11], &x[15]);
|
||||
/* diagonal rounds */
|
||||
chacha20_quarterround(&x[0], &x[5], &x[10], &x[15]);
|
||||
chacha20_quarterround(&x[1], &x[6], &x[11], &x[12]);
|
||||
chacha20_quarterround(&x[2], &x[7], &x[8], &x[13]);
|
||||
chacha20_quarterround(&x[3], &x[4], &x[9], &x[14]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
x[i] += state[i];
|
||||
((uint32_t*)output)[i] = x[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Global ChaCha20 PRNG state */
|
||||
static uint8_t rng_key[32];
|
||||
static uint64_t rng_counter = 0;
|
||||
static bool rng_seeded = false;
|
||||
|
||||
static void rng_generate(uint8_t *buf, size_t len) {
|
||||
size_t i = 0;
|
||||
while (len > 0) {
|
||||
uint32_t state[16];
|
||||
static const uint32_t sigma[4] = {0x61707865, 0x3320646e, 0x79622d32, 0x6b206574};
|
||||
|
||||
state[0] = sigma[0]; state[1] = sigma[1]; state[2] = sigma[2]; state[3] = sigma[3];
|
||||
for (int j = 0; j < 8; ++j)
|
||||
state[4 + j] = ((uint32_t*)rng_key)[j];
|
||||
|
||||
state[12] = (uint32_t)rng_counter;
|
||||
state[13] = (uint32_t)(rng_counter >> 32);
|
||||
state[14] = 0xdeadbeef; /* fixed nonce (PRNG only) */
|
||||
state[15] = 0xbeefdead;
|
||||
|
||||
uint8_t block[64];
|
||||
chacha20_block(state, block);
|
||||
|
||||
size_t copy = (len < 64) ? len : 64;
|
||||
for (size_t k = 0; k < copy; ++k)
|
||||
buf[i + k] = block[k];
|
||||
|
||||
i += copy;
|
||||
len -= copy;
|
||||
rng_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
static void rng_add_entropy(const uint8_t *data, size_t len) {
|
||||
if (len == 0 || !data) return;
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
rng_key[i % 32] ^= data[i];
|
||||
|
||||
/* stir (forward secrecy) */
|
||||
uint8_t discard[64];
|
||||
rng_generate(discard, 64);
|
||||
|
||||
rng_seeded = true;
|
||||
}
|
||||
|
||||
int random_read(void* buf, size_t len) {
|
||||
if (len == 0) return 0;
|
||||
if (!buf) return -EFAULT;
|
||||
if (!rng_seeded) return -EAGAIN; /* should never happen after init */
|
||||
|
||||
rng_generate((uint8_t*)buf, len);
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
int random_write(const void* buf, size_t len) {
|
||||
if (len == 0) return 0;
|
||||
if (!buf) return -EFAULT;
|
||||
|
||||
rng_add_entropy((const uint8_t*)buf, len);
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
int getrandom(void *buf, size_t buflen, unsigned int flags)
|
||||
{
|
||||
if (!buf) return -EFAULT;
|
||||
if (buflen == 0) return 0;
|
||||
|
||||
if (flags & GRND_INSECURE) {
|
||||
// maybe fall back to weaker RNG?
|
||||
}
|
||||
|
||||
if (!rng_seeded) {
|
||||
if (flags & GRND_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
// could block here in the future
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
rng_generate((uint8_t*)buf, buflen);
|
||||
return (int)buflen;
|
||||
|
||||
}
|
||||
|
||||
void random_init(void) {
|
||||
kprintf("Initializing ChaCha20-based CSPRNG for /dev/random and /dev/urandom...\n");
|
||||
|
||||
uint8_t entropy[64] = {0};
|
||||
size_t epos = 0;
|
||||
|
||||
/* === SEEDING SOURCES === */
|
||||
|
||||
/* 1. Limine boot timestamp (wall-clock) */
|
||||
extern volatile struct limine_date_at_boot_request boot_request;
|
||||
if (boot_request.response) {
|
||||
uint64_t ts = boot_request.response->timestamp;
|
||||
if (epos + sizeof(ts) <= sizeof(entropy)) {
|
||||
memcpy(entropy + epos, &ts, sizeof(ts));
|
||||
epos += sizeof(ts);
|
||||
}
|
||||
}
|
||||
|
||||
/* 2. Multiple TSC samples (high-resolution jitter) */
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
uint64_t tsc_val = rdtsc();
|
||||
if (epos + sizeof(tsc_val) <= sizeof(entropy)) {
|
||||
memcpy(entropy + epos, &tsc_val, sizeof(tsc_val));
|
||||
epos += sizeof(tsc_val);
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. PIT tick counter */
|
||||
uint64_t pit_ticks = PIT_GetTicks();
|
||||
if (epos + sizeof(pit_ticks) <= sizeof(entropy)) {
|
||||
memcpy(entropy + epos, &pit_ticks, sizeof(pit_ticks));
|
||||
epos += sizeof(pit_ticks);
|
||||
}
|
||||
|
||||
/* 4. RDRAND (hardware TRNG) if present */
|
||||
{
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(1));
|
||||
if (ecx & (1u << 30)) {
|
||||
kprintf("RDRAND detected - using hardware RNG for seeding\n");
|
||||
for (int i = 0; i < 4 && epos + 8 <= sizeof(entropy); ++i) {
|
||||
uint64_t rd;
|
||||
asm volatile("1: rdrand %0\n\tjnc 1b" : "=r"(rd));
|
||||
memcpy(entropy + epos, &rd, sizeof(rd));
|
||||
epos += sizeof(rd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 5. Unix seconds from PIT (time-based entropy) */
|
||||
extern uint64_t g_Unixseconds;
|
||||
uint64_t unix_sec = g_Unixseconds;
|
||||
if (epos + sizeof(unix_sec) <= sizeof(entropy)) {
|
||||
memcpy(entropy + epos, &unix_sec, sizeof(unix_sec));
|
||||
epos += sizeof(unix_sec);
|
||||
}
|
||||
|
||||
/* 6. Final TSC padding (extra jitter) */
|
||||
while (epos < 32) {
|
||||
uint64_t t = rdtsc();
|
||||
memcpy(entropy + epos, &t, (epos + 8 <= 32) ? 8 : (32 - epos));
|
||||
epos += (epos + 8 <= 32) ? 8 : (32 - epos);
|
||||
}
|
||||
|
||||
/* Initialise ChaCha20 key from collected entropy */
|
||||
memcpy(rng_key, entropy, 32);
|
||||
rng_counter = rdtsc(); /* random starting counter */
|
||||
rng_seeded = true;
|
||||
|
||||
kprintf("ChaCha20 RNG seeded with %zu bytes of entropy (TSC, PIT, RDRAND, boot time, unix time, etc.)\n", epos);
|
||||
|
||||
/* Register both devices (open as "/dev/random" and "/dev/urandom") */
|
||||
VFS_RegisterCharDev("/dev/random", random_read, random_write);
|
||||
VFS_RegisterCharDev("/dev/urandom", random_read, random_write);
|
||||
|
||||
kprintf("Registered chardevs: /dev/random and /dev/urandom (ChaCha20 CSPRNG)\n");
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define GRND_NONBLOCK 0x0001
|
||||
#define GRND_RANDOM 0x0002
|
||||
#define GRND_INSECURE 0x0004
|
||||
|
||||
void random_init(void);
|
||||
int random_read(void* buf, size_t len);
|
||||
int random_write(const void* buf, size_t len);
|
||||
int getrandom(void *buf, size_t buflen, unsigned int flags);
|
||||
Reference in New Issue
Block a user