diff --git a/.gitignore b/.gitignore index 1b9259f..36341b3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /target .DS_Store +kernel/qemu.wav \ No newline at end of file diff --git a/README.md b/README.md index 1ccce94..5bdb4db 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,5 @@ Compilation has been tested with WSL, Linux, and Mac. We provide no guarantees t - To run in terminal mode: make run-term - To run tests: make test - To ensure compliance with clippy and formatting: make check -- To format: make fmt \ No newline at end of file +- To format: make fmt +- If you want to hear audio, uncomment [kernel/src/devices/audio/hda.rs:137](kernel/src/devices/audio/hda.rs#L137) and run in terminal mode \ No newline at end of file diff --git a/kernel/.DS_Store b/kernel/.DS_Store new file mode 100644 index 0000000..3522c96 Binary files /dev/null and b/kernel/.DS_Store differ diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 2f24240..c875449 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -255,9 +255,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", @@ -522,9 +522,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -566,6 +566,7 @@ dependencies = [ "spin", "talc", "uart_16550", + "wavv", "x86_64 0.15.2", "zerocopy", ] @@ -619,6 +620,11 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" +[[package]] +name = "wavv" +version = "0.2.0" +source = "git+https://github.com/samuelleeuwenburg/wavv.git#753a315d2434c9f4440e70c86054168ca0deee95" + [[package]] name = "x86" version = "0.52.0" @@ -656,18 +662,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 70ccf17..bad855d 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -28,6 +28,7 @@ futures-util = { version = "0.3.31", default-features = false, features = [ "async-await-macro", "futures-macro", ] } +wavv = {version = "0.2.0"} goblin = { version = "0.9.3", default-features = false, features = [ "alloc", "elf64", @@ -59,3 +60,6 @@ talc = "4.4.2" uart_16550 = "0.3.2" x86_64 = "0.15.2" zerocopy = { version = "0.8", features = ["derive"] } + +[patch.crates-io] +wavv = { git = 'https://github.com/samuelleeuwenburg/wavv.git' } \ No newline at end of file diff --git a/kernel/limage_config.toml b/kernel/limage_config.toml index 491413b..d959975 100644 --- a/kernel/limage_config.toml +++ b/kernel/limage_config.toml @@ -19,8 +19,9 @@ base_args = [ "-device", "usb-net,netdev=net0", # Audio - "-device", "intel-hda", - "-device", "hda-duplex", + "-audiodev", "wav,id=snd0", + "-device", "intel-hda,debug=0", + "-device", "hda-duplex,audiodev=snd0", # General Storage "-drive", "id=mysdcard,file=storage_test.img,if=none,format=raw", diff --git a/kernel/src/.DS_Store b/kernel/src/.DS_Store new file mode 100644 index 0000000..75b742b Binary files /dev/null and b/kernel/src/.DS_Store differ diff --git a/kernel/src/constants/devices.rs b/kernel/src/constants/devices.rs index 03f5940..fdcda02 100644 --- a/kernel/src/constants/devices.rs +++ b/kernel/src/constants/devices.rs @@ -1 +1,3 @@ pub const SD_REQ_TIMEOUT_NANOS: u64 = 1_000_000_000; + +pub const TEST_WAV: &[u8] = include_bytes!("../devices/audio/new_romantics_swift.wav"); diff --git a/kernel/src/constants/idt.rs b/kernel/src/constants/idt.rs index f523c34..714315a 100644 --- a/kernel/src/constants/idt.rs +++ b/kernel/src/constants/idt.rs @@ -5,5 +5,6 @@ pub const TIMER_VECTOR: u8 = 32; pub const SYSCALL_HANDLER: u8 = 0x80; pub const KEYBOARD_VECTOR: u8 = 33; pub const MOUSE_VECTOR: u8 = 44; +pub const HDA_VECTOR: u8 = 45; pub const TLB_SHOOTDOWN_VECTOR: u8 = 33; diff --git a/kernel/src/constants/memory.rs b/kernel/src/constants/memory.rs index 2cea5b6..562e9a4 100644 --- a/kernel/src/constants/memory.rs +++ b/kernel/src/constants/memory.rs @@ -10,10 +10,10 @@ pub const FRAME_SIZE: usize = 4096; pub const HEAP_START: *mut u8 = 0x_FFFF_8100_0000_0000 as *mut u8; /// Initial size of the kernel heap (10 MB). -pub const HEAP_SIZE: usize = 1024 * 1024 * 10; +pub const HEAP_SIZE: usize = 1024 * 1024 * 30; /// Maximum number of frames that can be allocated. -pub const MAX_ALLOCATED_FRAMES: usize = 1024 * 5; +pub const MAX_ALLOCATED_FRAMES: usize = 1024 * 10; /// Size of each bitmap entry in bits. pub const BITMAP_ENTRY_SIZE: usize = 64; diff --git a/kernel/src/devices/.DS_Store b/kernel/src/devices/.DS_Store new file mode 100644 index 0000000..1e7bf43 Binary files /dev/null and b/kernel/src/devices/.DS_Store differ diff --git a/kernel/src/devices/audio/.DS_Store b/kernel/src/devices/audio/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/kernel/src/devices/audio/.DS_Store differ diff --git a/kernel/src/devices/audio/buffer.rs b/kernel/src/devices/audio/buffer.rs new file mode 100644 index 0000000..e70e2c1 --- /dev/null +++ b/kernel/src/devices/audio/buffer.rs @@ -0,0 +1,61 @@ +use core::ptr::write_volatile; + +/// Intel HDA Buffer Descriptor (BDL) Entry +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct BdlEntry { + pub address: u32, + pub address_high: u32, + pub length: u32, + pub flags: u32, +} + +impl BdlEntry { + pub const IOC_FLAG: u32 = 1; + + pub fn new(addr: u64, len: u32, interrupt_on_completion: bool) -> Self { + BdlEntry { + address: addr as u32, + address_high: (addr >> 32) as u32, + length: len, + flags: if interrupt_on_completion { + Self::IOC_FLAG + } else { + 0 + }, + } + } +} +/// Write BDL entries into the physical memory region (must be DMA accessible) +/// +/// # Safety +/// preforms a pointer write +pub unsafe fn setup_bdl( + bdl_virt_addr: *mut BdlEntry, + buffer_phys_start: u64, + total_size: u32, + entry_size: u32, +) -> usize { + let num_entries_needed = total_size.div_ceil(entry_size) as u64; + let num_entries = if num_entries_needed > 256 { + 256 + } else { + num_entries_needed + }; + + let mut offset = 0; + + for i in 0..num_entries { + let phys_addr = buffer_phys_start + offset as u64; + let is_last = i == num_entries - 1; + let entry = BdlEntry::new(phys_addr, entry_size, is_last); + + unsafe { + write_volatile(bdl_virt_addr.add(i as usize), entry); + } + + offset += entry_size; + } + + num_entries as usize +} diff --git a/kernel/src/devices/audio/command_buffer.rs b/kernel/src/devices/audio/command_buffer.rs new file mode 100644 index 0000000..982b743 --- /dev/null +++ b/kernel/src/devices/audio/command_buffer.rs @@ -0,0 +1,181 @@ +use crate::devices::audio::dma::DmaBuffer; +use core::ptr::{read_volatile, write_volatile}; + +use super::commands::{CorbEntry, RirbEntry}; + +// These bitflags are not a bad idea, they should have been actual bitflags types and we should have used them +// TODO: refactor some of this code to be nicer. +// const CORBRUN: u8 = 1 << 1; +// const CORBRPRST: u16 = 1 << 15; +// const RIRBDMAEN: u8 = 1 << 1; +// const RINTCTL: u8 = 1 << 0; +// const RIRBWPRST: u16 = 1 << 15; +// const ICB: u16 = 1 << 0; +// const IRV: u16 = 1 << 1; + +#[derive(Copy, Clone)] +pub struct WidgetAddr(pub u8, pub u8); + +/// A struct for operating the CORB +pub struct CorbBuffer { + /// The virtual base address of the CORB + pub base: u64, + /// The physical base address of the CORB + pub phys_base: u64, + /// The next block to be written to. It is an index + pub write_idx: u16, + /// The next block to read from, only hear to see when the CORB is full. + pub read_idx: u16, + /// Then number of entries that are in this CORB + pub size: u16, +} + +unsafe impl Send for CorbBuffer {} +unsafe impl Sync for CorbBuffer {} + +impl CorbBuffer { + /// Initializes a new CORB + /// + /// # Arguments + /// * `base`: a DmaBuffer object that points to the memory that the CORB is on + /// * `size`: The number of entries that are contained in this CORB + /// + /// # Returns + /// `self` + pub fn new(base: &DmaBuffer, size: u16) -> Self { + Self { + base: base.virt_addr.as_u64(), + phys_base: base.phys_addr.as_u64(), + write_idx: 0, + read_idx: 0, + size, + } + } + + /// Returns the index of the last written command + pub fn get_write_idx(&self) -> u16 { + self.write_idx + } + + /// Returns the index of the last read command from the hardware + pub fn get_read_idx(&self) -> u16 { + self.read_idx + } + + /// Sets the read idx to `idx` + pub fn set_read_idx(&mut self, idx: u16) { + self.read_idx = idx; + } + + /// Checks to see if the CORB is full + /// + /// # Returns + /// * `true` if `write_idx + 1 == read_idx` + /// * `false` otherwise + pub fn is_full(&self) -> bool { + let mut next_write = self.write_idx + 1; + if next_write == self.size { + next_write = 0; + } + next_write == self.read_idx + } + + /// Sends a cmd onto the CORB and increments the write index + /// + /// # Arguments + /// * `cmd`: the command to be written onto the CORB + /// + /// # Safety + /// This function preforms a raw pointer write to write the command to the CORB. This function assumes that the CORB is not full. + pub async unsafe fn send(&mut self, cmd: CorbEntry) { + assert!(!self.is_full()); + let mut next_write = self.write_idx + 1; + if next_write == self.size { + next_write = 0; + } + + let write_address = (self.base + next_write as u64 * 4) as *mut CorbEntry; + write_volatile(write_address, cmd); + + self.write_idx = next_write; + } +} + +/// A struct for operating the RIRB +pub struct RirbBuffer { + /// The virtual base address of the RIRB + pub base: u64, + /// The physical base address of the RIRB + pub phys_base: u64, + /// The next block to be written to + pub write_idx: u16, + /// The next block to read from + pub read_idx: u16, + /// Then number of entries that are in this RIRB + pub size: u16, +} + +unsafe impl Send for RirbBuffer {} +unsafe impl Sync for RirbBuffer {} + +impl RirbBuffer { + /// Initializes a new RIRB + /// + /// # Arguments + /// * `base`: a DmaBuffer object that points to the memory that the RIRB is on + /// * `size`: The number of entries that are contained in this RIRB + /// + /// # Returns + /// `self` + pub fn new(base: &DmaBuffer, size: u16) -> Self { + Self { + base: base.virt_addr.as_u64(), + phys_base: base.phys_addr.as_u64(), + write_idx: 0, + read_idx: 0, + size, + } + } + + /// Returns the current read index + pub fn get_read_idx(&self) -> u16 { + self.read_idx + } + + /// Sets the read index + pub fn set_read_idx(&mut self, idx: u16) { + self.read_idx = idx; + } + + /// sets the index of the last written command + pub fn set_write_idx(&mut self, idx: u16) { + self.write_idx = idx; + } + + /// Checks to see if the RIRB is empty + /// + /// # Returns + /// * `true` if `write_idx == read_idx` + /// * `false` otherwise + pub fn is_empty(&self) -> bool { + self.write_idx == self.read_idx + } + + /// Reads the next response from the RIRB and increments the read index + /// + /// # Returns + /// Returns the 8 byte response that is on the RIRB + /// + /// # Safety + /// This function preforms a raw pointer read to read from the RIRB. This function assumes that the RIRB is not empty. + pub async unsafe fn read(&mut self) -> RirbEntry { + assert!(!self.is_empty()); + let next_idx = (self.read_idx + 1) % self.size; + + let read_addr = (self.base + (next_idx as u64 * 8)) as *mut RirbEntry; + let response = read_volatile(read_addr); + + self.read_idx = next_idx; + response + } +} diff --git a/kernel/src/devices/audio/commands.rs b/kernel/src/devices/audio/commands.rs new file mode 100644 index 0000000..599c36a --- /dev/null +++ b/kernel/src/devices/audio/commands.rs @@ -0,0 +1,155 @@ +use crate::debug_println; + +#[repr(u32)] +#[derive(Copy, Clone, Debug)] +/// Intel HDA Verb Commands +pub enum HdaVerb { + GetParameter = 0xF00, + GetConnectionListEntry = 0xF02, + GetPowerState = 0xF05, + SetPowerState = 0x705, + GetPinControl = 0xF07, + GetEAPDBTLEnable = 0xF0C, + SetEAPDBTLEnable = 0x70C, + SetPinControl = 0x707, + GetConfigDefault = 0xF1C, + GetVolumeKnobCaps = 0x13, + SetConverterFormat = 0x02, + SetAmplifierGain = 0x03, + SetStreamChannel = 0x706, + SetDacEnable = 0x701, + KickStart = 0xF81, + GetBeepGen = 0xF0A, + SetBeepGen = 0x70A, +} + +impl HdaVerb { + pub fn as_u32(self) -> u32 { + self as u32 + } +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug)] +/// Codec Node Parameters that can be used with GetParameter verb. These values were taken from the OSDev page for the Intel-HDA. +pub enum NodeParams { + VendorDeviceID = 0x0, + RevisionID = 0x2, + NodeCount = 0x4, + FunctionGroupType = 0x5, + AudioGroupCap = 0x8, + AudioWidgetCap, + SupportPCMRates, + SupportedFormats, + PinCap, + InputAmplifierCap, + ConnectionListLength, + SupportedPowerStates, + ProcessingCap, + GPIOCount, + OutputAmplifierCap, + VolumeCap, +} + +impl NodeParams { + pub fn as_u8(self) -> u8 { + self as u8 + } + + pub fn as_u16(self) -> u16 { + self as u16 + } +} + +#[derive(Copy, Clone, Debug)] +/// A struct representing an entry in the CORB +pub struct CorbEntry { + /// The data of the command, the description of each bit range is defined as follows: + /// - `[31:28]`: Codec Address + /// - `[27:20]`: Node Index + /// - `[19:8]`: Command + /// - `[7:0]`: Data + pub cmd: u32, +} + +impl CorbEntry { + /// creates a new corb entry + /// + /// # Arguments + /// * `codec`: the address of the target codec + /// * `nid`: the id of the target node + /// * `command`: the command to be sent + /// * `data`: the data to be sent with the command + /// + /// # Returns + /// `self` initialized with the values passed in + pub fn create_entry(codec: u32, nid: u32, command: HdaVerb, data: u16) -> Self { + let command_num = command.as_u32(); + let cmd_lo = if command_num == HdaVerb::SetAmplifierGain.as_u32() + || command_num == HdaVerb::SetConverterFormat.as_u32() + { + (command_num << 16) | (data as u32 & 0xFFFF) + } else { + (command_num << 8) | ((data & 0xFF) as u32) + }; + + let cmd = (codec << 28) | (nid << 20) | cmd_lo; + + Self { cmd } + } + + /// returns cmd for debugging + pub fn get_cmd(&self) -> u32 { + self.cmd + } +} + +// TODO: add #[repr(C)] ? +#[derive(Copy, Clone, Debug)] +/// A struct representing an entry in the RIRB +pub struct RirbEntry { + /// The response data received from the codec + response: u32, + /// Infor added to the response by the controller + resp_ex: u32, +} + +impl RirbEntry { + /// gets the codec's response + /// + /// # Returns + /// `response` + pub fn get_response(&self) -> u32 { + self.response + } + + pub fn get_response_ex(&self) -> u32 { + self.resp_ex + } + + /// gets the codec address from this entry + /// + /// # Returns + /// bits `[3:0]` of `resp_ex` + pub fn get_codec_address(&self) -> u8 { + (self.resp_ex & 0xF) as u8 + } + + /// checks if this response is unsolicited + /// + /// # Returns + /// * `true` if bit `4` of `resp_ex` is set, indicating that this was an unsolicited response. + /// * `false` otherwise, indicating that this was a solicited response. + pub fn is_unsolicited_resp(&self) -> bool { + self.resp_ex & 0x10 != 0 + } + + /// prints out the contents of self, this is used for debugging purposes + pub fn print_response(&self) { + debug_println!( + "Response: 0x{:X}, resp_ex: {:X}", + self.response, + self.resp_ex + ); + } +} diff --git a/kernel/src/devices/audio/debug.rs b/kernel/src/devices/audio/debug.rs new file mode 100644 index 0000000..9e636a8 --- /dev/null +++ b/kernel/src/devices/audio/debug.rs @@ -0,0 +1,88 @@ +// use crate::{ +// devices::audio::hda_regs::{HdaRegisters, StreamDescriptor}, +// serial_println, +// }; +// use core::mem::size_of; + +// /// Helper to print field offset relative to struct base +// fn print_field_offset(base: &T, field: &F, name: &str) { +// let base_ptr = base as *const T as usize; +// let field_ptr = field as *const F as usize; +// let offset = field_ptr - base_ptr; +// serial_println!("{:>25}: 0x{:X}", name, offset); +// } + +// /// Call this from `init()` or `test_dma_transfer()` to verify layout +// pub fn debug_hda_register_layout(regs: &HdaRegisters) { +// serial_println!("===== HDA Register Layout Debug (Safe Rust) ====="); + +// serial_println!("--- HdaRegisters ---"); +// print_field_offset(regs, ®s.gcap, "gcap"); +// print_field_offset(regs, ®s.vmin, "vmin"); +// print_field_offset(regs, ®s.vmaj, "vmaj"); +// print_field_offset(regs, ®s.outpay, "outpay"); +// print_field_offset(regs, ®s.inpay, "inpay"); +// print_field_offset(regs, ®s.gctl, "gctl"); +// print_field_offset(regs, ®s.wakeen, "wakeen"); +// print_field_offset(regs, ®s.statests, "statests"); +// print_field_offset(regs, ®s.gsts, "gsts"); +// print_field_offset(regs, ®s.outstrmpay, "outstrmpay"); +// print_field_offset(regs, ®s.instrmpay, "instrmpay"); +// print_field_offset(regs, ®s.intctl, "intctl"); +// print_field_offset(regs, ®s.intsts, "intsts"); +// print_field_offset(regs, ®s.walclk, "walclk"); +// print_field_offset(regs, ®s.ssync, "ssync"); +// print_field_offset(regs, ®s.corblbase, "corblbase"); +// print_field_offset(regs, ®s.corbubase, "corbubase"); +// print_field_offset(regs, ®s.corbwp, "corbwp"); +// print_field_offset(regs, ®s.corbrp, "corbrp"); +// print_field_offset(regs, ®s.corbctl, "corbctl"); +// print_field_offset(regs, ®s.corbsts, "corbsts"); +// print_field_offset(regs, ®s.corbsize, "corbsize"); +// print_field_offset(regs, ®s.rirblbase, "rirblbase"); +// print_field_offset(regs, ®s.rirbubase, "rirbubase"); +// print_field_offset(regs, ®s.rirbwp, "rirbwp"); +// print_field_offset(regs, ®s.rirbctl, "rirbctl"); +// print_field_offset(regs, ®s.rirbsts, "rirbsts"); +// print_field_offset(regs, ®s.rirbsize, "rirbsize"); +// print_field_offset(regs, ®s.icoi, "icoi"); +// print_field_offset(regs, ®s.icii, "icii"); +// print_field_offset(regs, ®s.icis, "icis"); +// print_field_offset(regs, ®s.dplbase, "dplbase"); +// print_field_offset(regs, ®s.dpubase, "dpubase"); +// print_field_offset(regs, ®s.stream_regs, "stream_regs"); + +// serial_println!("Size of HdaRegisters: 0x{:X}", size_of::()); + +// serial_println!("--- StreamDescriptor ---"); + +// let dummy = StreamDescriptor { +// ctl: 0, +// sts: 0, +// lpib: 0, +// cbl: 0, +// lvi: 0, +// // reserved: 0, +// fmt: 0, +// // reserved2: 0, +// bdlpl: 0, +// bdlpu: 0, +// }; + +// print_field_offset(&dummy, &dummy.ctl, "ctl"); +// print_field_offset(&dummy, &dummy.sts, "sts"); +// print_field_offset(&dummy, &dummy.lpib, "lpib"); +// print_field_offset(&dummy, &dummy.cbl, "cbl"); +// print_field_offset(&dummy, &dummy.lvi, "lvi"); +// // print_field_offset(&dummy, &dummy.reserved, "reserved"); +// print_field_offset(&dummy, &dummy.fmt, "fmt"); +// // print_field_offset(&dummy, &dummy.reserved2, "reserved2"); +// print_field_offset(&dummy, &dummy.bdlpl, "bdlpl"); +// print_field_offset(&dummy, &dummy.bdlpu, "bdlpu"); + +// serial_println!( +// "Size of StreamDescriptor: 0x{:X}", +// size_of::() +// ); +// serial_println!("======================================================"); +// } diff --git a/kernel/src/devices/audio/dma.rs b/kernel/src/devices/audio/dma.rs new file mode 100644 index 0000000..40b9dd1 --- /dev/null +++ b/kernel/src/devices/audio/dma.rs @@ -0,0 +1,68 @@ +use crate::memory::{frame_allocator::with_buddy_frame_allocator, HHDM_OFFSET}; + +use x86_64::{PhysAddr, VirtAddr}; + +#[derive(Clone)] +/// DMA buffer that is physically contiguous and mapped to virtual memory +pub struct DmaBuffer { + pub virt_addr: VirtAddr, + pub phys_addr: PhysAddr, + pub size: usize, +} + +impl DmaBuffer { + /// Allocates `size` bytes of DMA-safe memory, shhould be page aligned + pub fn new(size: usize) -> Option { + let page_count = size.div_ceil(0x1000); + + let mut virt_addr = None; + let mut phys_addr = None; + let order = page_count.ilog2() + 1; + + with_buddy_frame_allocator(|f| { + let frames = f.allocate_block(order.try_into().unwrap()); + for (idx, frame) in frames.iter().enumerate() { + let temp_virt = *HHDM_OFFSET + frame.start_address().as_u64(); + + if idx == 0 { + virt_addr = Some(temp_virt); + phys_addr = Some(frame.start_address()); + } + + unsafe { + core::ptr::write_bytes(temp_virt.as_mut_ptr::(), 0, 0x1000); + } + } + }); + + let virt = virt_addr?; + let phys = phys_addr?; + + Some(Self { + virt_addr: virt, + phys_addr: phys, + size: page_count * 0x1000, + }) + } + + /// Zero out the buffer + pub fn zero(&self) { + unsafe { + for i in 0..self.size { + core::ptr::write_volatile(self.virt_addr.as_mut_ptr::().add(i), 0); + } + } + } + + /// offsets the dma buffer by offset bytes + pub fn offset(&mut self, offset: u64) { + self.virt_addr += offset; + self.phys_addr += offset; + self.size -= offset as usize; + } + + /// Interpret buffer as pointer to `T` + pub fn as_ptr(&self) -> *mut T { + self.virt_addr.as_mut_ptr() + } +} diff --git a/kernel/src/devices/audio/hda.rs b/kernel/src/devices/audio/hda.rs new file mode 100644 index 0000000..de9269c --- /dev/null +++ b/kernel/src/devices/audio/hda.rs @@ -0,0 +1,1001 @@ +use crate::{ + debug_println, + devices::{ + audio::{ + buffer::{setup_bdl, BdlEntry}, + command_buffer::RirbBuffer, + commands::{CorbEntry, HdaVerb, NodeParams}, + wav_parser::load_wav, + }, + mmio::MMioConstPtr, + pci::{read_config, walk_pci_bus, DeviceInfo}, + }, + events::{futures::devices::HWRegisterWrite, nanosleep_current_event}, + interrupts::x2apic, + memory::HHDM_OFFSET, + serial_println, +}; + +use crate::devices::{audio::hda_regs::HdaRegisters, mmio::MMioPtr}; +use alloc::{vec, vec::Vec}; +use core::{ + mem::offset_of, + ptr::{read_volatile, write_volatile}, +}; + +use crate::devices::audio::dma::DmaBuffer; +use wavv::Data; +use x86_64::structures::idt::InterruptStackFrame; + +use super::{command_buffer::CorbBuffer, commands::RirbEntry, widget_info::WidgetInfo}; + +/// Physical BAR address (used during development before PCI scan) +/// TODO - need to find a betterr way so this variable doesnt exist +const HDA_BAR_PHYS: u32 = 0x81010000; + +pub struct AudioData { + pub bytes: MMioConstPtr, + pub len: usize, + pub data: Data, + pub fmt: u16, +} + +/// Interrupt handler for Intel HDA. +/// - Handles interrupts by reading INTSTS: interrupt status registeer (0x20) and RIRBSTS: response ring buffer status (0x5d). +/// - Clears the respective bits by writing them back. +/// - Sends EOI after handling. +pub extern "x86-interrupt" fn hda_interrupt_handler(_frame: InterruptStackFrame) { + let virt = *HHDM_OFFSET + HDA_BAR_PHYS as u64; + let regs = unsafe { &*(virt.as_u64() as *const HdaRegisters) }; + + let regs_base = regs as *const _ as *const u8; + let intsts_ptr = unsafe { regs_base.add(offset_of!(HdaRegisters, intsts)) as *mut u32 }; + let rirbsts_ptr = unsafe { regs_base.add(offset_of!(HdaRegisters, rirbsts)) as *mut u8 }; + + unsafe { + let int_status = read_volatile(intsts_ptr); + let rirb_status = read_volatile(rirbsts_ptr); + + serial_println!( + "HDA interrupt received: INTSTS=0x{:08X}, RIRBSTS=0x{:02X}", + int_status, + rirb_status + ); + + if int_status != 0 { + write_volatile(intsts_ptr, int_status); + } + + if rirb_status != 0 { + write_volatile(rirbsts_ptr, rirb_status); + } + } + + x2apic::send_eoi(); +} + +pub struct IntelHDA { + pub base: u32, + pub virt_base: u64, + pub vendor_id: u16, + pub device_id: u16, + pub regs: &'static mut HdaRegisters, + pub cmd_buf: Option, + pub rirb_buf: Option, +} + +impl IntelHDA { + //// Initializes the Intel HDA controller. + /// + /// Steps: + /// - Locates the HDA-compatible PCI device (Class 0x04, Subclass 0x03). + /// - Maps the controller registers into virtual memory. + /// - Resets the HDA controller via `reset()`. + /// - Initializes CORB and RIRB buffers. + /// - Enables interrupt delivery. + pub async fn init() -> Option { + let device = find_hda_device()?; + let bar = get_bar(&device)?; + + let virt = *HHDM_OFFSET + bar as u64; + let regs = unsafe { &mut *(virt.as_u64() as *mut HdaRegisters) }; + + // Just gonna keep this one + debug_println!( + "Intel HDA found: vendor=0x{:X}, device=0x{:X}, BAR=0x{:X} (virt: 0x{:X})", + device.vendor_id, + device.device_id, + bar, + virt + ); + + let mut hda = IntelHDA { + base: bar, + virt_base: virt.as_u64(), + regs, + vendor_id: device.vendor_id, + device_id: device.device_id, + cmd_buf: None, + rirb_buf: None, + }; + + // reset the HDA + hda.reset().await; + + hda.init_corb().await; + + hda.init_rirb().await; + + // enable interrupts + let intctl_addr = (hda.virt_base + 0x20) as *mut u32; + let intctl_val = ((1 << 31) | (1 << 4)) as u32; + unsafe { + write_volatile(intctl_addr, intctl_val); + } + + // test we can play some audio (commented out cause make test does not like the length) + // hda.test_dma_transfer().await; + + Some(hda) + } + + /// Initializes the CORB + /// Steps: + /// - Stops the CORB DMA engine and waits for the confirmation + /// - Selects and configures the CORB size + /// - Allocates a DMA bufferr and sets base address reegisterrs + /// - Resets thehardwarer rerad/write pointerrs + /// - Starts theCORB engines. + pub async fn init_corb(&mut self) { + // stop the CORB DMA engine + let corbctl_addr = (self.virt_base + 0x4c) as *mut u8; + // let mut corbctl_val = unsafe {read_volatile(corbctl_addr)}; + unsafe { + write_volatile(corbctl_addr, 0); + } + + // wait for the hw to acknowledge the write + let mut corbctl_val = unsafe { read_volatile(corbctl_addr) }; + // TODO: fix this spin loop + while (corbctl_val >> 1) & 1 != 0 { + corbctl_val = unsafe { read_volatile(corbctl_addr) }; + } + + // Determine the size of the CORB + let corbsize_addr = (self.virt_base + 0x4e) as *mut u8; + let corb_size_reg = unsafe { read_volatile(corbsize_addr) }; + let corb_size: u16; + let corb_size_val: u8; + if (corb_size_reg >> 6) & 1 == 1 { + corb_size = 256; + corb_size_val = 2; + } else if (corb_size_reg >> 5) & 1 == 1 { + corb_size = 16; + corb_size_val = 1; + } else { + corb_size = 2; + corb_size_val = 0; + } + // now actually write the size + unsafe { + write_volatile(corbsize_addr, corb_size_val); + } // TODO: This line isnt actually needed since qemu only suports one size so remove maybe? + + // Allocate a buffer + let corb_buf = DmaBuffer::new(4096).expect("Failed to alloc CORB"); + assert_eq!(corb_buf.virt_addr.as_u64() & (128 - 1), 0); + let corb = CorbBuffer::new(&corb_buf, corb_size); + self.cmd_buf = Some(corb); // TODO: why is this an option? + + // program the CORB base registers + let corbbase = corb_buf.phys_addr.as_u64() & !(128 - 1); + let corbbase_addr = (self.virt_base + 0x40) as *mut u32; + unsafe { + write_volatile(corbbase_addr, (corbbase & 0xFFFFFFC0) as u32); + write_volatile(corbbase_addr.add(1), ((corbbase >> 32) & 0xFFFFFFFF) as u32); + } + + // reset the hw read pointer + let rp_addr = (self.virt_base + 0x4a) as *mut u16; + unsafe { + write_volatile(rp_addr, 1 << 15); + } + + let mut rp_val = unsafe { read_volatile(rp_addr) }; + while rp_val >> 15 != 1 { + rp_val = unsafe { read_volatile(rp_addr) }; + } + + unsafe { + write_volatile(rp_addr, 0); + } + rp_val = unsafe { read_volatile(rp_addr) }; + // TODO: fix this spin loop + while rp_val >> 15 != 0 { + rp_val = unsafe { read_volatile(rp_addr) }; + } + + // set the write pointer to 0 + let wp_addr = (self.virt_base + 0x48) as *mut u8; + unsafe { + write_volatile(wp_addr, 0); + } + + // set the run bit + unsafe { + write_volatile(corbctl_addr, 2); + } // TODO: figure out if this is needed or do we only set the run bit if there are commands to process? + corbctl_val = unsafe { read_volatile(corbctl_addr) }; + // TODO: fix this spin loop + while (corbctl_val >> 1) & 1 != 1 { + corbctl_val = unsafe { read_volatile(corbctl_addr) }; + } + } + + /// initializes the RIRB + /// Steps: + /// - Disables the RIRB DMA engine + /// - Deterrmines supported RIRB size and allocates buffer + /// - Sets RIRB baseaddressregisterrs and configures interrupt count + /// - Starts the RIRB engine + pub async fn init_rirb(&mut self) { + // stop the DMA engine + let rirbctl_addr = (self.virt_base + 0x5c) as *mut u8; + unsafe { + write_volatile(rirbctl_addr, 0); + } + + // wait for ack + let mut ctl_val = unsafe { read_volatile(rirbctl_addr) }; + // TODO: fix this spin loop + while (ctl_val >> 1) & 1 != 0 { + ctl_val = unsafe { read_volatile(rirbctl_addr) }; + } + + // Determine the RIRB size + let rirbsize_addr = (self.virt_base + 0x5e) as *mut u8; + let rirbsize_val = unsafe { read_volatile(rirbsize_addr) }; + let rirb_size: u16; + if (rirbsize_val >> 6) & 1 == 1 { + rirb_size = 256; + } else if (rirbsize_val >> 5) & 1 == 1 { + rirb_size = 16; + } else { + rirb_size = 2; + } + + // Allocate a buffer + let rirb_buf = DmaBuffer::new(4096).expect("Failed to alloc CORB"); + assert_eq!(rirb_buf.virt_addr.as_u64() & (128 - 1), 0); + let rirb = RirbBuffer::new(&rirb_buf, rirb_size); + self.rirb_buf = Some(rirb); + + // program the RIRB base registers + let rirbbase = rirb_buf.phys_addr.as_u64() & !(128 - 1); + let rirbbase_addr = (self.virt_base + 0x50) as *mut u32; + unsafe { + write_volatile(rirbbase_addr, (rirbbase & 0xFFFFFFC0) as u32); + write_volatile(rirbbase_addr.add(1), ((rirbbase >> 32) & 0xFFFFFFFF) as u32); + } + + // reset the hw write pointer + let wp_addr = (self.virt_base + 0x58) as *mut u16; + unsafe { + write_volatile(wp_addr, 0); + } + // no need to check and wait like in corb cause this bit is always read a 0 for some reason + + // set interrupt count to half the size (it should accept 0 as 256 but it does not for some reason) + let intcnt_addr = (self.virt_base + 0x5A) as *mut u16; + unsafe { + write_volatile(intcnt_addr, rirb_size / 2); + } + + // set the run bit + unsafe { + write_volatile(rirbctl_addr, 2); + } + ctl_val = unsafe { read_volatile(rirbctl_addr) }; + // TODO: fix this spin loop + while (ctl_val >> 1) & 1 != 1 { + ctl_val = unsafe { read_volatile(rirbctl_addr) }; + } + } + + /// sends a command if the buffer is not full + /// + /// # Arguments + /// * `codec_address`: ID of the codec on the HDA link + /// * `node_id`: NID of the codec node to target + /// * `command`: The verb of the opcode (e.g GetParameter, SetStream, etc) + /// * `data`: Additional data or sub-verb argument + /// + /// # Returns + /// `None` if the CORB is full and sending the command failed, otherwise retuns `Some(())` + pub async fn send_command( + &mut self, + codec_address: u32, + node_id: u32, + command: HdaVerb, + data: u16, + ) -> Option<()> { + let corb = self.cmd_buf.as_mut().unwrap(); + // first check if the buffer is full + let corbrp_addr = (self.virt_base + 0x4A) as *mut u8; + let corbrp_val = unsafe { read_volatile(corbrp_addr) }; + corb.set_read_idx(corbrp_val as u16); + if corb.is_full() { + return None; + } + + let cmd = CorbEntry::create_entry(codec_address, node_id, command, data); + unsafe { + corb.send(cmd).await; + // next update the write pointer to indicate to hardware + let wp_addr = (self.virt_base + 0x48) as *mut u8; + write_volatile( + wp_addr, + (self.cmd_buf.as_ref().unwrap().get_write_idx() & 0xFF) as u8, + ); + } + Some(()) + } + + /// receive a response from the RIRB + /// + /// # Returns + /// the next available `RirbEntry` if present and `None` if a timeout occurs + pub async fn receive_response(&mut self) -> Option { + let rirb = self.rirb_buf.as_mut().unwrap(); + let rirbwp_addr = (self.virt_base + 0x58) as *mut u16; + let mut rirbwp_val = unsafe { read_volatile(rirbwp_addr) }; + rirb.set_write_idx(rirbwp_val); + + // TODO: figure out a better way to wait for a response (this is not very elegant) + let mut counter = 100000; + while rirb.is_empty() && counter > 0 { + rirbwp_val = unsafe { read_volatile(rirbwp_addr) }; + rirb.set_write_idx(rirbwp_val); + counter -= 1; + } + + if counter == 0 { + // timeout + debug_println!("timed out while waiting"); + return None; + } + + unsafe { Some(rirb.read().await) } + } + + /// Probes the audio function group and its widgets + /// - Identifies the AFG node in thecodec + /// - Queries its child nodes and their widget types and capabilities. + /// - Populates a list of WidgetInffo structswith + /// - Widget type + /// - Connection List + /// - Amplifier capabipities + /// - Pin configurations + /// - Default configuration and volume controle + /// + /// # Returns + /// a vector of `WidgetInfo` representing the widget topology + pub async fn probe_afg_and_widgets(&mut self) -> Vec { + let mut widgets = Vec::new(); + + self.send_command(0, 0, HdaVerb::GetParameter, NodeParams::NodeCount.as_u16()) + .await + .expect("Failed to send GetParameter NodeCount"); + + let fg_count_raw = self + .receive_response() + .await + .expect("Failed to receive NodeCount response") + .get_response(); + + let fg_count = fg_count_raw & 0xFF; + + let mut afg_nid = None; + for i in 1..=fg_count { + self.send_command( + 0, + i, + HdaVerb::GetParameter, + NodeParams::FunctionGroupType.as_u16(), + ) + .await + .expect("Failed to send GetParameter FunctionGroupType"); + + let group_type = self + .receive_response() + .await + .expect("Failed to receive FunctionGroupType response") + .get_response(); + + if (group_type & 0xF) == 0x01 { + afg_nid = Some(i as u8); + break; + } + } + + let afg_node = match afg_nid { + Some(nid) => nid, + None => { + return widgets; + } + }; + + self.send_command( + 0, + afg_node as u32, + HdaVerb::GetParameter, + NodeParams::NodeCount.as_u16(), + ) + .await + .expect("Failed to send GetParameter NodeCount for AFG"); + + let list_length = self + .receive_response() + .await + .expect("Failed to receive NodeCount for AFG"); + + let total_nodes = (list_length.get_response() & 0xFF) as u8; + let start_id = ((list_length.get_response() >> 16) & 0xFF) as u8; + + for node in 0..total_nodes { + let nid = start_id + node; + + self.send_command( + 0, + nid as u32, + HdaVerb::GetParameter, + NodeParams::AudioWidgetCap.as_u16(), + ) + .await + .expect("Failed to send GetParameter AudioWidgetCap"); + + let val = self + .receive_response() + .await + .expect("Failed to receive AudioWidgetCap"); + + let wtype = (val.get_response() >> 20) & 0xF; + + let mut w = WidgetInfo::new(nid); + w.widget_type = wtype; + + self.send_command( + 0, + nid as u32, + HdaVerb::GetParameter, + NodeParams::NodeCount.as_u16(), + ) + .await + .expect("nope"); + w.node_count = self + .receive_response() + .await + .expect("failed to get node count") + .get_response(); + + self.send_command( + 0, + nid as u32, + HdaVerb::GetParameter, + NodeParams::PinCap.as_u16(), + ) + .await + .expect("Failed to send GetParameter PinCap"); + w.pin_caps = self + .receive_response() + .await + .expect("Failed to receive PinCap") + .get_response(); + + self.send_command( + 0, + nid as u32, + HdaVerb::GetParameter, + NodeParams::InputAmplifierCap.as_u16(), + ) + .await + .expect("Failed to send GetAmpCapabilities"); + w.amp_in_caps = self + .receive_response() + .await + .expect("Failed to receive GetAmpCapabilities") + .get_response(); + + self.send_command( + 0, + nid as u32, + HdaVerb::GetParameter, + NodeParams::OutputAmplifierCap.as_u16(), + ) + .await + .expect("Failed to send GetAmpOutCaps"); + w.amp_out_caps = self + .receive_response() + .await + .expect("Failed to receive GetAmpOutCaps") + .get_response(); + + self.send_command(0, nid as u32, HdaVerb::GetVolumeKnobCaps, 0) + .await + .expect("Failed to send GetVolumeKnobCaps"); + w.volume_knob = self + .receive_response() + .await + .expect("Failed to receive GetVolumeKnobCaps") + .get_response(); + + self.send_command(0, nid as u32, HdaVerb::GetConfigDefault, 0) + .await + .expect("Failed to send GetConfigDefault"); + w.config_default = self + .receive_response() + .await + .expect("Failed to receive GetConfigDefault") + .get_response(); + + self.send_command( + 0, + nid as u32, + HdaVerb::GetParameter, + NodeParams::ConnectionListLength.as_u16(), + ) + .await + .expect("Failed to send GetParameter ConnectionListLength"); + let resp_val = self + .receive_response() + .await + .expect("Failed to receive ConnectionListLength") + .get_response(); + + let conn_len = (resp_val & 0x7F) as u8; + + for i in 0..conn_len { + self.send_command(0, nid as u32, HdaVerb::GetConnectionListEntry, i as u16) + .await + .expect("Failed to send GetConnectionListEntry"); + let conn = (self + .receive_response() + .await + .expect("Failed to receive GetConnectionListEntry") + .get_response() + & 0xFF) as u8; + + w.conn_list.push(conn); + } + + widgets.push(w); + } + + widgets + } + + /// Recursively traces a valid signal path from a pin node to a DAC node. + /// + /// # Arguments + /// - `pin_node`: Node ID of the output pin. + /// + /// # Returns: + /// - `Some((pin_node, dac_node))` if a DAC is found along the connection path. + /// - `None` if no DAC is reachable. + pub async fn trace_path_to_dac(&mut self, pin_node: u8) -> Option<(u8, u8)> { + let mut stack: Vec<(u8, Vec)> = vec![(pin_node, vec![pin_node])]; + + while let Some((node, path)) = stack.pop() { + self.send_command( + 0, + node as u32, + HdaVerb::GetParameter, + NodeParams::AudioWidgetCap.as_u16(), + ) + .await + .expect("Failed to send AudioWidgetCap"); + let widget_type = self + .receive_response() + .await + .expect("Failed to receive AudioWidgetCap") + .get_response(); + let wtype = (widget_type >> 20) & 0xF; + + if wtype == 0x0 { + return Some((pin_node, node)); + } + + self.send_command( + 0, + node as u32, + HdaVerb::GetParameter, + NodeParams::ConnectionListLength.as_u16(), + ) + .await + .expect("Failed to send ConnectionListLength"); + let conn_len_resp = self + .receive_response() + .await + .expect("Failed to receive ConnectionListLength") + .get_response(); + let conn_len = (conn_len_resp & 0x7F) as u8; + + for i in 0..conn_len { + self.send_command(0, node as u32, HdaVerb::GetConnectionListEntry, i as u16) + .await + .expect("Failed to send GetConnectionListEntry"); + let conn_resp = self + .receive_response() + .await + .expect("Failed to receive GetConnectionListEntry") + .get_response(); + let conn_node = (conn_resp & 0xFF) as u8; + + let mut new_path = path.clone(); + new_path.push(conn_node); + stack.push((conn_node, new_path)); + } + } + + None + } + + /// Resets the controller using GCTL register: + /// - Clears and sets CRST bit (bit 0) + pub async fn reset(&mut self) { + unsafe { + let gctl_ptr = MMioPtr( + (self.regs as *const _ as *const u8).add(offset_of!(HdaRegisters, gctl)) + as *mut u32, + ); + let gctl = gctl_ptr.read(); + + gctl_ptr.write(gctl & !(1 << 0)); + HWRegisterWrite::new(gctl_ptr.as_ptr(), 0x1, 0).await; + + let gctl = gctl_ptr.read(); + + gctl_ptr.write(gctl | (1 << 0)); + + HWRegisterWrite::new(gctl_ptr.as_ptr(), 0x1, 1).await; + nanosleep_current_event(500_000).unwrap().await; // wait 0.5 ms + + // Delay 0.1 ms for codecs to get initialized (can we safely go smaller?) + nanosleep_current_event(1_000_000).unwrap().await; + } + } + + /// Starts audio stream (sets RUN bit in SDxCTL) + pub fn start_stream(&mut self, stream_idx: usize) { + unsafe { + let ctl = &mut self.regs.stream_regs[stream_idx].ctl0; + write_volatile(ctl, read_volatile(ctl) | (1 << 1)); // RUN RUN RUN PLZ + } + } + + /// Stops audio stream (clears RUN bit in SDxCTL) + pub fn stop_stream(&mut self, stream_idx: usize) { + unsafe { + let ctl = &mut self.regs.stream_regs[stream_idx].ctl0; + write_volatile(ctl, read_volatile(ctl) & !(1 << 1)); // RUN STOPPPP + } + } + + /// Plays a WAV file throughh the audio output usning DMA. + /// Steps: + /// - Powers up codecs and discovers the pin-to-DAC signal path + /// - Sends configuration verbs + /// - Allocates and sets up BDLs for streaming audio chunks + /// - Initializes the stream descriptor with control info + /// - ALterrnates between the BDLs based on the IOC bit + /// - + pub async fn test_dma_transfer(&mut self) { + // turn on the nodes + self.send_command(0, 0, HdaVerb::SetPowerState, 0) + .await + .expect("Failed to send command to the CORB"); + self.receive_response() + .await + .expect("Failed to receive response from the RIRB"); + + self.send_command(0, 0, HdaVerb::GetParameter, NodeParams::NodeCount.as_u16()) + .await + .expect("Failed to send command to the CORB"); + self.receive_response() + .await + .expect("Failed to receive response from the RIRB"); + + let audio_data = load_wav().await.expect("Wav error"); + + self.probe_afg_and_widgets().await; + + if let Some((pin_node, dac_node)) = self.trace_path_to_dac(3).await { + self.send_command(0, dac_node as u32, HdaVerb::SetPowerState, 0x00) + .await + .expect("Failed to send SetPowerState to DAC"); + self.receive_response() + .await + .expect("No response to SetPowerState"); + + // 0x10 means strream number 1, channel 0 + self.send_command(0, dac_node as u32, HdaVerb::SetStreamChannel, 0x10) + .await + .expect("Failed to send SetStreamChannel"); + self.receive_response() + .await + .expect("No response to SetStreamChannel"); + + // Set ampplifier gain on the DAC: unmuted, 0 dB gain + // 0xB035 enabbles output for both channels with default ggain + self.send_command(0, dac_node as u32, HdaVerb::SetAmplifierGain, 0xB035) + .await + .expect("Failed to send SetAmplifierGain"); + self.receive_response() + .await + .expect("No response to SetAmplifierGain"); + + // Configure the DAC format to 48kHz, 16-bit, stereoo + self.send_command( + 0, + dac_node as u32, + HdaVerb::SetConverterFormat, + audio_data.fmt, + ) + .await + .expect("Failed to send SetConverterFormat"); + self.receive_response() + .await + .expect("No response to SetConverterFormat"); + + // Read current pin conttrol to preserve existing bitts before setting EAPD/output + self.send_command(0, pin_node as u32, HdaVerb::GetPinControl, 0) + .await + .expect("Failed to send GetPinControl"); + let mut pin_ctrl = self + .receive_response() + .await + .expect("No response to GetPinControl") + .get_response(); + + // Enable output and EAPD by setting bbbits 6 and 7 (0xC0) + pin_ctrl |= 0xC0; + self.send_command( + 0, + pin_node as u32, + HdaVerb::SetPinControl, + (pin_ctrl & 0xFF) as u16, + ) + .await + .expect("Failed to send SetPinControl"); + self.receive_response() + .await + .expect("No response to SetPinControl"); + } else { + debug_println!("Could not trace a valid path from pin to DAC."); + } + + // create BDL stuff + let audio_buf = DmaBuffer::new(audio_data.len).expect("Failed to allocate audio buffer"); + let bdl_buf1 = DmaBuffer::new(core::mem::size_of::() * 16).expect("Failed BDL"); + let bdl_buf2 = DmaBuffer::new(core::mem::size_of::() * 16).expect("Failed BDL"); + + assert_eq!( + bdl_buf1.phys_addr.as_u64() % 128, + 0, + "BDL 1 not 128-byte aligned" + ); + assert_eq!( + bdl_buf2.phys_addr.as_u64() % 128, + 0, + "BDL 2 not 128-byte aligned" + ); + + unsafe { + core::ptr::copy_nonoverlapping( + audio_data.bytes.as_ptr(), + audio_buf.virt_addr.as_mut_ptr::(), + audio_data.len, + ); + } + + let mut last_byte_written = audio_buf.clone(); // tbh this probably does not need to exist but it is wtv atp + + let bdl_ptr_1 = bdl_buf1.as_ptr::(); + let mut num_entries = unsafe { + setup_bdl( + bdl_ptr_1, + last_byte_written.phys_addr.as_u64(), + last_byte_written.size as u32, + 0x1000, + ) + }; + + let num_entries_1 = num_entries; + + let mut num_bytes_bdl = (num_entries * 0x1000) as u32; + + last_byte_written.offset(num_bytes_bdl as u64); + + let bdl_ptr_2 = bdl_buf2.as_ptr::(); + + num_entries = unsafe { + setup_bdl( + bdl_ptr_2, + last_byte_written.phys_addr.as_u64(), + last_byte_written.size as u32, + 0x1000, + ) + }; + last_byte_written.offset((num_entries * 0x1000) as u64); + + // begin configuring stream desc + let stream_base = self.virt_base + 0x80 + 4 * 0x20; + + // Flush cache to ensure BDL/Audio in RAM for DMA + unsafe { + core::arch::asm!("wbinvd"); + } + + // first make sure that the stream is stopped and then reset it + let ctl0_addr = stream_base as *mut u8; + let mut ctl0_val = unsafe { read_volatile(ctl0_addr) }; + unsafe { + write_volatile(ctl0_addr, ctl0_val & !(1 << 1)); + ctl0_val = read_volatile(ctl0_addr); + write_volatile(ctl0_addr, ctl0_val | 1); + ctl0_val = read_volatile(ctl0_addr); + } + + // wait for hw to ack + while ctl0_val & 1 != 1 { + ctl0_val = unsafe { read_volatile(ctl0_addr) }; + } + + // now clear SRST + unsafe { + write_volatile(ctl0_addr, ctl0_val & !1); + ctl0_val = read_volatile(ctl0_addr); + } + while ctl0_val & 1 != 0 { + ctl0_val = unsafe { read_volatile(ctl0_addr) }; + } + + // lettuce clear our status bits + let sdsts_addr = (stream_base + 0x3) as *const u8; + + unsafe { + write_volatile(sdsts_addr.cast_mut(), 0x1C); + } + + // write bdl address + let bdladr_addr = (stream_base + 0x18) as *mut u64; + unsafe { + write_volatile(bdladr_addr, bdl_buf1.phys_addr.as_u64()); + } + + // write the cbl + let cbl_addr = (stream_base + 0x8) as *mut u32; + unsafe { + write_volatile(cbl_addr, num_bytes_bdl); + } + + // write the LVI + let lvi_addr = (stream_base + 0xC) as *mut u16; + unsafe { + write_volatile(lvi_addr, num_entries_1 as u16 - 1); + } + + // now lets congifure the fmt reg + let fmt_addr = (stream_base + 0x12) as *mut u16; + let fmt_write = audio_data.fmt; // 8 bits per sample + unsafe { + write_volatile(fmt_addr, fmt_write); + } + + // set the stream num + let ctl2_addr = (stream_base + 2) as *mut u8; + unsafe { + write_volatile(ctl2_addr, 1 << 4); + } + + // now set the run bit and hope that the thing works (gonna set some interrupt bits as well) + unsafe { + write_volatile(ctl0_addr, 0x2); + } + + let mut sts = unsafe { read_volatile((stream_base + 0x3) as *const u8) }; + let mut flag = true; + let mut counter = 0; + while (sts >> 2) & 1 != 1 { + unsafe { + // check for an IOC + sts = read_volatile((stream_base + 0x3) as *const u8); + + if (sts >> 2) & 1 == 1 { + // stop stream + write_volatile(stream_base as *mut u8, 0x0); + + if flag { + // clear sts + write_volatile((stream_base + 0x3) as *mut u8, 0x40); + sts = read_volatile((stream_base + 0x3) as *const u8); + + // update cbl + num_bytes_bdl = 0x1000 * num_entries as u32; + write_volatile((stream_base + 0x8) as *mut u32, num_bytes_bdl); + + // update lvi + write_volatile((stream_base + 0xC) as *mut u16, num_entries as u16 - 1); + + // update bdl ptr + if counter % 2 == 0 { + write_volatile( + (stream_base + 0x18) as *mut u64, + bdl_buf2.phys_addr.as_u64(), + ); + } else { + write_volatile( + (stream_base + 0x18) as *mut u64, + bdl_buf1.phys_addr.as_u64(), + ); + } + + // start stream + write_volatile(stream_base as *mut u8, 0x2); + + // now we gotta load up the next buffer + if counter % 2 == 0 { + // load up buf1 + num_entries = setup_bdl( + bdl_ptr_1, + last_byte_written.phys_addr.as_u64(), + last_byte_written.size as u32, + 0x1000, + ); + } else { + // load up buf2 + num_entries = setup_bdl( + bdl_ptr_1, + last_byte_written.phys_addr.as_u64(), + last_byte_written.size as u32, + 0x1000, + ); + } + + // move the copy of the audio data + num_bytes_bdl = (num_entries * 0x1000) as u32; + + last_byte_written.offset(num_bytes_bdl as u64); + + if last_byte_written.size == 0 { + flag = false; + } + counter += 1; + } + } + } + } + } +} + +/// Walk PCI bus to find device with Class 0x04, Subclass 0x03 (FOUND FROM OSDEV) +fn find_hda_device() -> Option { + let devices = walk_pci_bus(); + for dev in devices { + let dev = dev.lock(); + if dev.class_code == 0x04 && dev.subclass == 0x03 { + return Some((*dev).clone()); + } + } + None +} + +/// Reads BAR0 from PCI config space (offset 0x10) and masks off flags? +fn get_bar(device: &DeviceInfo) -> Option { + let bar = read_config(device.bus, device.device, 0, 0x10); + if bar & 0x1 == 0 { + Some(bar & 0xFFFFFFF0) + } else { + None + } +} diff --git a/kernel/src/devices/audio/hda_regs.rs b/kernel/src/devices/audio/hda_regs.rs new file mode 100644 index 0000000..ee9db90 --- /dev/null +++ b/kernel/src/devices/audio/hda_regs.rs @@ -0,0 +1,64 @@ +#[repr(C)] +pub struct HdaRegisters { + pub gcap: u16, + pub vmin: u8, + pub vmaj: u8, + pub outpay: u16, + pub inpay: u16, + pub gctl: u32, + pub wakeen: u16, + pub statests: u16, + pub gsts: u16, + pub _pad: [u8; 6], + pub outstrmpay: u16, + pub instrmpay: u16, + pub _pad1: [u8; 4], + pub intctl: u32, + pub intsts: u32, + pub _pad2: [u8; 8], + pub walclk: u32, + pub _pad3: u32, + pub ssync: u32, + pub _pad4: u32, + pub corblbase: u32, + pub corbubase: u32, + pub corbwp: u16, + pub corbrp: u16, + pub corbctl: u8, + pub corbsts: u8, + pub corbsize: u8, + pub _pad5: u8, + pub rirblbase: u32, + pub rirbubase: u32, + pub rirbwp: u16, + pub rintcnt: u16, + pub rirbctl: u8, + pub rirbsts: u8, + pub rirbsize: u8, + pub _pad6: u8, + pub icoi: u32, + pub icii: u32, + pub icis: u32, + pub _pad7: u32, + pub dplbase: u32, + pub dpubase: u32, + pub _pad8: [u8; 8], + pub stream_regs: [StreamDescriptor; 30], +} + +#[repr(C)] +pub struct StreamDescriptor { + pub ctl0: u8, + pub ctl1: u8, + pub ctl2: u8, + pub sts: u8, + pub lpib: u32, + pub cbl: u32, + pub lvi: u16, + pub _pad: u16, + pub fifo: u16, + pub fmt: u16, + pub _pad1: u32, + pub bdlpl: u32, + pub bdlpu: u32, +} diff --git a/kernel/src/devices/audio/mod.rs b/kernel/src/devices/audio/mod.rs new file mode 100644 index 0000000..6c59c00 --- /dev/null +++ b/kernel/src/devices/audio/mod.rs @@ -0,0 +1,9 @@ +pub mod buffer; +pub mod command_buffer; +pub mod commands; +pub mod debug; +pub mod dma; +pub mod hda; +pub mod hda_regs; +pub mod wav_parser; +pub mod widget_info; diff --git a/kernel/src/devices/audio/myfile2.wav b/kernel/src/devices/audio/myfile2.wav new file mode 100644 index 0000000..fec8311 Binary files /dev/null and b/kernel/src/devices/audio/myfile2.wav differ diff --git a/kernel/src/devices/audio/new_romantics_swift.wav b/kernel/src/devices/audio/new_romantics_swift.wav new file mode 100644 index 0000000..3524303 Binary files /dev/null and b/kernel/src/devices/audio/new_romantics_swift.wav differ diff --git a/kernel/src/devices/audio/victory-fanfare-comp.wav b/kernel/src/devices/audio/victory-fanfare-comp.wav new file mode 100644 index 0000000..b3d5717 Binary files /dev/null and b/kernel/src/devices/audio/victory-fanfare-comp.wav differ diff --git a/kernel/src/devices/audio/wav_parser.rs b/kernel/src/devices/audio/wav_parser.rs new file mode 100644 index 0000000..050ce01 --- /dev/null +++ b/kernel/src/devices/audio/wav_parser.rs @@ -0,0 +1,107 @@ +use crate::{ + constants::devices::TEST_WAV, + devices::mmio::MMioConstPtr, + filesys::ext2::filesystem::{Ext2, FilesystemError}, +}; +use wavv::{Data, Wav}; + +use super::hda::AudioData; + +pub async fn read_wav(fs: &Ext2, path: &str) -> Result { + let wav_bytes = fs.read_file(path).await?; + match Wav::from_bytes(&wav_bytes) { + Ok(wav) => Ok(wav), + Err(_) => Err(FilesystemError::InvalidFd), + } +} + +pub async fn write_wav(fs: &Ext2, path: &str, wav: &Wav) -> Result<(), FilesystemError> { + let bytes = wav.to_bytes(); + fs.write_file(path, &bytes).await.map(|_| ()) +} + +/// Call this from hda.rs to load and re-save a WAV file +pub async fn load_wav(/*fs: &Ext2*/) -> Result { + let input_path = "kernel/src/devices/audio/new_romantics_swift.wav"; + + match Wav::from_bytes(TEST_WAV) { + Ok(wav) => { + crate::debug!( + "WAV loaded: {} ch, {} bit, {} Hz", + wav.fmt.num_channels, + wav.fmt.bit_depth, + wav.fmt.sample_rate + ); + + let (samples, len) = match &wav.data { + Data::BitDepth8(samples) => (samples.as_ptr(), samples.len()), + Data::BitDepth16(samples) => (samples.as_ptr() as *const u8, samples.len() * 2), + Data::BitDepth24(samples) => (samples.as_ptr() as *const u8, samples.len() * 4), + }; + + let fmt = get_fmt(&wav); + + Ok(AudioData { + bytes: MMioConstPtr(samples), + len, + data: wav.data, // holding onto data to prevent deallocation of buffer + fmt, + }) + } + Err(e) => { + crate::debug!("Failed to read WAV file {}: {:?}", input_path, e); + Err(FilesystemError::InvalidPath) + } + } +} + +pub fn get_fmt(wav: &Wav) -> u16 { + let bit_depth: u8 = match wav.fmt.bit_depth { + 8 => 0, + 16 => 1, + 20 => 2, + 24 => 3, + 32 => 4, + _ => 5, + }; + + let channels = wav.fmt.num_channels - 1; + let sample_base = if wav.fmt.sample_rate == 44_100 { 1 } else { 0 }; + + let multiplier = match wav.fmt.sample_rate { + 96_000 | 88_200 | 32_000 => 0b001, + 144_000 => 0b010, + 192_000 | 176_400 => 0b011, + _ => 0b000, + }; + + let divider = match wav.fmt.sample_rate { + 24_000 | 22_050 => 0b001, + 16_000 => 0b010, // | 24_000? + 11_025 => 0b011, + 9_600 => 0b100, + 8_000 => 0b101, + 6_857 => 0b110, + 6_000 => 0b111, + _ => 0b000, + }; + + crate::debug!("sample_rate = {:#X}", wav.fmt.sample_rate); + crate::debug!("num_channels = {:#X}", wav.fmt.num_channels); + crate::debug!("bit_depth = {:#X}", wav.fmt.bit_depth); + crate::debug!("sample_base = {:#X}", sample_base); + crate::debug!("multiplier = {:#X}", multiplier); + crate::debug!("divider = {:#X}", divider); + crate::debug!("samples = {:#X}", bit_depth); + crate::debug!("channels = {:#X}", channels); + + let fmt: u16 = ((sample_base as u16) << 14) + | ((multiplier as u16) << 11) + | ((divider as u16) << 8) + | ((bit_depth as u16) << 4) + | channels; + + crate::debug!("FMT: {:#X}", fmt); + + fmt +} diff --git a/kernel/src/devices/audio/widget_info.rs b/kernel/src/devices/audio/widget_info.rs new file mode 100644 index 0000000..06c0440 --- /dev/null +++ b/kernel/src/devices/audio/widget_info.rs @@ -0,0 +1,30 @@ +use alloc::vec::Vec; + +#[derive(Debug, Clone)] +pub struct WidgetInfo { + pub nid: u8, + pub node_count: u32, + pub widget_type: u32, + pub conn_list: Vec, + pub config_default: u32, + pub pin_caps: u32, + pub amp_in_caps: u32, + pub amp_out_caps: u32, + pub volume_knob: u32, +} + +impl WidgetInfo { + pub fn new(nid: u8) -> Self { + WidgetInfo { + nid, + node_count: 0, + widget_type: 0, + conn_list: Vec::new(), + config_default: 0, + pin_caps: 0, + amp_in_caps: 0, + amp_out_caps: 0, + volume_knob: 0, + } + } +} diff --git a/kernel/src/devices/mmio.rs b/kernel/src/devices/mmio.rs index 28a41c1..b0cb22a 100644 --- a/kernel/src/devices/mmio.rs +++ b/kernel/src/devices/mmio.rs @@ -1,3 +1,96 @@ +#[repr(transparent)] +pub struct MMioPtr(pub *mut T); + +unsafe impl Send for MMioPtr {} + +impl MMioPtr { + /// reads a pointer + /// + /// # Safety + /// preforms a pointer access + pub unsafe fn read(&self) -> T { + core::ptr::read_volatile(self.0) + } + + #[allow(dead_code)] + /// preforms a read with a potentially unaligned pointer + /// + /// # Safety + /// preforms a pointer access + pub unsafe fn read_unaliged(&self) -> T { + core::ptr::read_unaligned(self.0) + } + + /// writes a val to a pointer + /// + /// # Safety + /// preforms a pointer access and modifies the val of the memory pointed to + pub unsafe fn write(&self, val: T) { + core::ptr::write_volatile(self.0, val); + } + + #[allow(dead_code)] + /// writes a val to a potentially unaligned pointer + /// + /// # Safety + /// preforms a pointer access and modifies the val of the memory pointed to + pub unsafe fn write_unaligned(&self, val: T) { + core::ptr::write_unaligned(self.0, val); + } + + pub fn as_ptr(&self) -> *mut T { + self.0 + } + + #[allow(dead_code)] + /// adds an offset to this pointer + /// + /// # Safety + /// adds an offset directly to a pointer + pub unsafe fn add(&self, offset: usize) -> MMioPtr { + MMioPtr(self.0.add(offset) as *mut EndType) + } +} + +#[repr(transparent)] +pub struct MMioConstPtr(pub *const T); + +unsafe impl Send for MMioConstPtr {} + +impl MMioConstPtr { + #[allow(dead_code)] + /// reads a pointer + /// + /// # Safety + /// preforms a pointer access + pub unsafe fn read(&self) -> T { + core::ptr::read_volatile(self.0) + } + + #[allow(dead_code)] + /// preforms a read with a potentially unaligned pointer + /// + /// # Safety + /// preforms a pointer access + pub unsafe fn read_unaligned(&self) -> T { + core::ptr::read_unaligned(self.0) + } + + #[allow(dead_code)] + pub fn as_ptr(&self) -> *const T { + self.0 + } + + #[allow(dead_code)] + /// adds an offset to this pointer + /// + /// # Safety + /// adds an offset directly to a pointer + pub unsafe fn add(&self, offset: usize) -> MMioConstPtr { + MMioConstPtr(self.0.add(offset) as *mut EndType) + } +} + use x86_64::{ structures::paging::{ mapper::{MappedFrame, TranslateResult}, diff --git a/kernel/src/devices/mod.rs b/kernel/src/devices/mod.rs index b6fd048..20cbc52 100644 --- a/kernel/src/devices/mod.rs +++ b/kernel/src/devices/mod.rs @@ -5,12 +5,13 @@ //! - Frame buffer for screen output //! - Future device support will be added here -use crate::serial_println; +use crate::{events::schedule_kernel, serial_println}; use pci::walk_pci_bus; use sd_card::{find_sd_card, initalize_sd_card}; use xhci::{find_xhci_inferface, initalize_xhci_hub}; pub mod graphics; use graphics::framebuffer::{self, colors}; +pub mod audio; pub mod mmio; pub mod pci; pub mod ps2_dev; @@ -74,5 +75,16 @@ pub fn init(cpu_id: u32) { initalize_xhci_hub(&xhci_device).unwrap(); ps2_dev::init(); + + schedule_kernel( + async { + if let Some(hda) = audio::hda::IntelHDA::init().await { + serial_println!("HDA initialized at base address 0x{:X}", hda.base); + } else { + serial_println!("HDA controller not found."); + } + }, + 0, + ); } } diff --git a/kernel/src/devices/pci.rs b/kernel/src/devices/pci.rs index 76175ca..18a21ce 100644 --- a/kernel/src/devices/pci.rs +++ b/kernel/src/devices/pci.rs @@ -50,6 +50,7 @@ bitflags! { #[derive(Debug)] /// A generic representation of a single funcion pci /// device. To note +#[derive(Clone)] pub struct DeviceInfo { /// The bus that this device is on pub bus: u8, diff --git a/kernel/src/events/futures/devices.rs b/kernel/src/events/futures/devices.rs index 0eb16a5..9cf3748 100644 --- a/kernel/src/events/futures/devices.rs +++ b/kernel/src/events/futures/devices.rs @@ -1,7 +1,10 @@ use alloc::sync::Arc; use core::{ + fmt::LowerHex, future::Future, + ops::BitAnd, pin::Pin, + ptr::read_volatile, task::{Context, Poll}, }; @@ -9,7 +12,7 @@ use futures::task::ArcWake; use crate::{ devices::sd_card::{PresentState, SDCardError}, - events::{runner_timestamp, Event}, + events::{current_running_event, runner_timestamp, Event}, }; /// Future to sleep an event until a target timestamp (in system ticks) @@ -101,3 +104,71 @@ impl Future for SDCardReq { } } } + +#[derive(Clone)] +pub struct HWRegisterWrite + PartialEq + Copy> { + reg: *mut T, + mask: T, + expected: T, + event: Arc, +} + +unsafe impl + PartialEq + Copy> Send for HWRegisterWrite {} + +impl + PartialEq + Copy> HWRegisterWrite { + pub fn new(reg: *mut T, mask: T, expected: T) -> HWRegisterWrite { + HWRegisterWrite { + reg, + mask, + expected, + event: current_running_event().expect("Blocking on MMIO HW Register outside event"), + } + } + + pub fn awake(&self) { + self.event.clone().wake(); + } + + pub fn get_id(&self) -> u64 { + self.event.eid.0 + } +} + +/// Order SDCardReq futures with earlier timestamps given "higher" values. +/// +/// PID then EID to tie break. +/// Thus, events created earlier awaken first in the very rare event of a tie. +/// This helps preventing compounding error from old events frequently blocking. +impl + PartialEq + Copy> Ord for HWRegisterWrite { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.event.eid.cmp(&other.event.eid) + } +} + +impl + PartialEq + Copy> PartialOrd for HWRegisterWrite { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl + PartialEq + Copy> PartialEq for HWRegisterWrite { + fn eq(&self, other: &Self) -> bool { + self.event.eid == other.event.eid + } +} + +impl + PartialEq + Copy> Eq for HWRegisterWrite {} + +impl + PartialEq + Copy + LowerHex> Future for HWRegisterWrite { + type Output = (); + + fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + unsafe { + if (read_volatile(self.reg) & self.mask) == self.expected { + Poll::Ready(()) + } else { + Poll::Pending + } + } + } +} diff --git a/kernel/src/interrupts/idt.rs b/kernel/src/interrupts/idt.rs index 13a3b34..948aa6d 100644 --- a/kernel/src/interrupts/idt.rs +++ b/kernel/src/interrupts/idt.rs @@ -15,9 +15,14 @@ use x86_64::{ structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}, }; +use crate::devices::audio::hda::hda_interrupt_handler; + use crate::{ constants::{ - idt::{KEYBOARD_VECTOR, MOUSE_VECTOR, SYSCALL_HANDLER, TIMER_VECTOR, TLB_SHOOTDOWN_VECTOR}, + idt::{ + HDA_VECTOR, KEYBOARD_VECTOR, MOUSE_VECTOR, SYSCALL_HANDLER, TIMER_VECTOR, + TLB_SHOOTDOWN_VECTOR, + }, syscalls::{SYSCALL_EXIT, SYSCALL_MMAP, SYSCALL_NANOSLEEP, SYSCALL_PRINT}, }, devices::ps2_dev::{keyboard_interrupt_handler, mouse_interrupt_handler}, @@ -63,6 +68,7 @@ lazy_static! { idt[TLB_SHOOTDOWN_VECTOR].set_handler_fn(tlb_shootdown_handler); idt[KEYBOARD_VECTOR].set_handler_fn(keyboard_interrupt_handler); idt[MOUSE_VECTOR].set_handler_fn(mouse_interrupt_handler); + idt[HDA_VECTOR].set_handler_fn(hda_interrupt_handler); idt }; } diff --git a/kernel/src/processes/process.rs b/kernel/src/processes/process.rs index ca35911..f4d0055 100644 --- a/kernel/src/processes/process.rs +++ b/kernel/src/processes/process.rs @@ -295,7 +295,7 @@ unsafe fn create_process_page_table() -> PhysFrame { (*ptr).zero(); let kernel_pml4 = mapper.level_4_table(); for i in 256..512 { - (*ptr)[i].set_addr(kernel_pml4[i].addr(), kernel_pml4[i].flags()); + (&mut (*ptr))[i].set_addr(kernel_pml4[i].addr(), kernel_pml4[i].flags()); } }