diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index c1115b23..ee038c24 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -9,6 +9,8 @@ set(KERNEL_SOURCES kernel/APIC.cpp kernel/Audio/AC97/Controller.cpp kernel/Audio/Controller.cpp + kernel/Audio/HDAudio/AudioFunctionGroup.cpp + kernel/Audio/HDAudio/Controller.cpp kernel/BootInfo.cpp kernel/CPUID.cpp kernel/Credentials.cpp diff --git a/kernel/include/kernel/Audio/AC97/Controller.h b/kernel/include/kernel/Audio/AC97/Controller.h index 728d685a..64d096c6 100644 --- a/kernel/include/kernel/Audio/AC97/Controller.h +++ b/kernel/include/kernel/Audio/AC97/Controller.h @@ -9,7 +9,7 @@ namespace Kernel class AC97AudioController : public AudioController, public Interruptable { public: - static BAN::ErrorOr> create(PCI::Device& pci_device); + static BAN::ErrorOr create(PCI::Device& pci_device); void handle_irq() override; diff --git a/kernel/include/kernel/Audio/Controller.h b/kernel/include/kernel/Audio/Controller.h index ef0c0d99..dbc2359a 100644 --- a/kernel/include/kernel/Audio/Controller.h +++ b/kernel/include/kernel/Audio/Controller.h @@ -9,7 +9,7 @@ namespace Kernel class AudioController : public CharacterDevice { public: - static BAN::ErrorOr> create(PCI::Device& pci_device); + static BAN::ErrorOr create(PCI::Device& pci_device); dev_t rdev() const override { return m_rdev; } BAN::StringView name() const override { return m_name; } diff --git a/kernel/include/kernel/Audio/HDAudio/AudioFunctionGroup.h b/kernel/include/kernel/Audio/HDAudio/AudioFunctionGroup.h new file mode 100644 index 00000000..99bc0c30 --- /dev/null +++ b/kernel/include/kernel/Audio/HDAudio/AudioFunctionGroup.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +namespace Kernel +{ + + class HDAudioController; + + class HDAudioFunctionGroup : public AudioController + { + public: + static BAN::ErrorOr> create(BAN::RefPtr, uint8_t cid, HDAudio::AFGNode&&); + + void on_stream_interrupt(uint8_t stream_index); + + protected: + // FIXME: allow setting these :D + uint32_t get_channels() const override { return 2; } + uint32_t get_sample_rate() const override { return 48000; } + + void handle_new_data() override; + + private: + HDAudioFunctionGroup(BAN::RefPtr controller, uint8_t cid, HDAudio::AFGNode&& afg_node) + : m_controller(controller) + , m_afg_node(BAN::move(afg_node)) + , m_cid(cid) + { } + ~HDAudioFunctionGroup(); + + BAN::ErrorOr initialize(); + BAN::ErrorOr initialize_stream(); + BAN::ErrorOr initialize_output(); + BAN::ErrorOr enable_output_path(uint8_t index); + + void reset_stream(); + + BAN::ErrorOr recurse_output_paths(const HDAudio::AFGWidget& widget, BAN::Vector& path); + + uint16_t get_format_data() const; + uint16_t get_volume_data() const; + + size_t bdl_offset() const; + + void queue_bdl_data(); + + private: + static constexpr size_t m_max_path_length = 16; + + // use 6 512 sample BDL entries + // each entry is ~10.7 ms at 48 kHz + // -> total buffered audio is 64 ms + static constexpr size_t m_bdl_entry_sample_frames = 512; + static constexpr size_t m_bdl_entry_count = 6; + + BAN::RefPtr m_controller; + const HDAudio::AFGNode m_afg_node; + const uint8_t m_cid; + + BAN::Vector> m_output_paths; + size_t m_output_path_index { SIZE_MAX }; + + uint8_t m_stream_id { 0xFF }; + uint8_t m_stream_index { 0xFF }; + BAN::UniqPtr m_bdl_region; + + size_t m_bdl_head { 0 }; + size_t m_bdl_tail { 0 }; + bool m_stream_running { false }; + }; + +} diff --git a/kernel/include/kernel/Audio/HDAudio/Controller.h b/kernel/include/kernel/Audio/HDAudio/Controller.h new file mode 100644 index 00000000..f936982a --- /dev/null +++ b/kernel/include/kernel/Audio/HDAudio/Controller.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include + +namespace Kernel +{ + + class HDAudioController : public Interruptable, public BAN::RefCounted + { + public: + static BAN::ErrorOr create(PCI::Device& pci_device); + + BAN::ErrorOr send_command(HDAudio::CORBEntry); + + uint8_t get_stream_index(HDAudio::StreamType type, uint8_t index) const; + + BAN::ErrorOr allocate_stream_id(); + void deallocate_stream_id(uint8_t id); + + BAN::ErrorOr allocate_stream(HDAudio::StreamType type, void* afg); + void deallocate_stream(uint8_t index); + + PCI::BarRegion& bar0() { return *m_bar0; } + + bool is_64bit() const { return m_is64bit; } + + void handle_irq() override; + + private: + HDAudioController(PCI::Device& pci_device) + : m_pci_device(pci_device) + { } + + BAN::ErrorOr initialize(); + BAN::ErrorOr initialize_ring_buffers(); + + BAN::ErrorOr reset_controller(); + + BAN::ErrorOr initialize_codec(uint8_t codec); + BAN::ErrorOr initialize_node(uint8_t codec, uint8_t node); + BAN::ErrorOr initialize_widget(uint8_t codec, uint8_t node); + + private: + struct RingBuffer + { + vaddr_t vaddr; + uint32_t index; + uint32_t size; + }; + + private: + PCI::Device& m_pci_device; + BAN::UniqPtr m_bar0; + bool m_is64bit { false }; + + bool m_use_immediate_command { false }; + + uint8_t m_output_streams { 0 }; + uint8_t m_input_streams { 0 }; + uint8_t m_bidir_streams { 0 }; + void* m_allocated_streams[30] {}; + + // NOTE: stream ids are from 1 to 15 + uint16_t m_allocated_stream_ids { 0 }; + + Mutex m_command_mutex; + SpinLock m_rb_lock; + ThreadBlocker m_rb_blocker; + + RingBuffer m_corb; + RingBuffer m_rirb; + BAN::UniqPtr m_ring_buffer_region; + }; + +} diff --git a/kernel/include/kernel/Audio/HDAudio/Definitions.h b/kernel/include/kernel/Audio/HDAudio/Definitions.h new file mode 100644 index 00000000..f094c523 --- /dev/null +++ b/kernel/include/kernel/Audio/HDAudio/Definitions.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +namespace Kernel::HDAudio +{ + + struct CORBEntry + { + union { + struct { + uint32_t data : 8; + uint32_t command : 12; + uint32_t node_index : 8; + uint32_t codec_address : 4; + }; + uint32_t raw; + }; + }; + static_assert(sizeof(CORBEntry) == sizeof(uint32_t)); + + struct BDLEntry + { + paddr_t address; + uint32_t length; + uint32_t ioc; + }; + static_assert(sizeof(BDLEntry) == 16); + + struct AFGWidget + { + enum class Type + { + OutputConverter, + InputConverter, + Mixer, + Selector, + PinComplex, + Power, + VolumeKnob, + BeepGenerator, + }; + + Type type; + uint8_t id; + + union + { + struct + { + bool input; + bool output; + } pin_complex; + }; + + BAN::Vector connections; + }; + + struct AFGNode + { + uint8_t id; + BAN::Vector widgets; + }; + + struct Codec + { + uint8_t id; + BAN::Vector nodes; + }; + + enum class StreamType + { + Input, + Output, + Bidirectional, + }; + +} diff --git a/kernel/include/kernel/Audio/HDAudio/Registers.h b/kernel/include/kernel/Audio/HDAudio/Registers.h new file mode 100644 index 00000000..e25a7c8e --- /dev/null +++ b/kernel/include/kernel/Audio/HDAudio/Registers.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +namespace Kernel::HDAudio +{ + + enum Regs : uint8_t + { + GCAP = 0x00, + VMIN = 0x02, + VMAJ = 0x03, + GCTL = 0x08, + + INTCTL = 0x20, + INTSTS = 0x24, + + CORBLBASE = 0x40, + CORBUBASE = 0x44, + CORBWP = 0x48, + CORBRP = 0x4A, + CORBCTL = 0x4C, + CORBSTS = 0x4D, + CORBSIZE = 0x4E, + + RIRBLBASE = 0x50, + RIRBUBASE = 0x54, + RIRBWP = 0x58, + RINTCNT = 0x5A, + RIRBCTL = 0x5C, + RIRBSTS = 0x5D, + RIRBSIZE = 0x5E, + + ICOI = 0x60, + ICII = 0x64, + ICIS = 0x68, + + SDCTL = 0x00, + SDSTS = 0x03, + SDLPIB = 0x04, + SDCBL = 0x08, + SDLVI = 0x0C, + SDFIFOD = 0x10, + SDFMT = 0x12, + SDBDPL = 0x18, + SDBDPU = 0x1C, + }; + +} diff --git a/kernel/include/kernel/Debug.h b/kernel/include/kernel/Debug.h index b872980a..b0545d39 100644 --- a/kernel/include/kernel/Debug.h +++ b/kernel/include/kernel/Debug.h @@ -70,6 +70,8 @@ #define DEBUG_USB_MOUSE 0 #define DEBUG_USB_MASS_STORAGE 0 +#define DEBUG_HDAUDIO 0 + namespace Debug { diff --git a/kernel/kernel/Audio/AC97/Controller.cpp b/kernel/kernel/Audio/AC97/Controller.cpp index 180debc5..b5c1e9a1 100644 --- a/kernel/kernel/Audio/AC97/Controller.cpp +++ b/kernel/kernel/Audio/AC97/Controller.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace Kernel { @@ -105,14 +106,14 @@ namespace Kernel IOCE = 1 << 4, }; - BAN::ErrorOr> AC97AudioController::create(PCI::Device& pci_device) + BAN::ErrorOr AC97AudioController::create(PCI::Device& pci_device) { auto* ac97_ptr = new AC97AudioController(pci_device); if (ac97_ptr == nullptr) return BAN::Error::from_errno(ENOMEM); auto ac97 = BAN::RefPtr::adopt(ac97_ptr); TRY(ac97->initialize()); - return ac97; + return {}; } BAN::ErrorOr AC97AudioController::initialize() @@ -147,6 +148,8 @@ namespace Kernel // disable transfer, enable all interrupts m_bus_master->write8(BusMasterRegister::PO_CR, IOCE | FEIE | LVBIE); + DevFileSystem::get().add_device(this); + return {}; } diff --git a/kernel/kernel/Audio/Controller.cpp b/kernel/kernel/Audio/Controller.cpp index ff5bb207..d5817c93 100644 --- a/kernel/kernel/Audio/Controller.cpp +++ b/kernel/kernel/Audio/Controller.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -20,23 +21,26 @@ namespace Kernel BAN::Formatter::print([&ptr](char c) { *ptr++ = c; }, "audio{}", minor(m_rdev)); } - BAN::ErrorOr> AudioController::create(PCI::Device& pci_device) + BAN::ErrorOr AudioController::create(PCI::Device& pci_device) { switch (pci_device.subclass()) { case 0x01: // We should confirm that the card is actually AC97 but I'm trusting osdev wiki on this one // > you can probably expect that every sound card with subclass 0x01 is sound card compatibile with AC97 - if (auto ret = AC97AudioController::create(pci_device); !ret.is_error()) - { - DevFileSystem::get().add_device(ret.value()); - return BAN::RefPtr(ret.release_value()); - } - else + if (auto ret = AC97AudioController::create(pci_device); ret.is_error()) { dwarnln("Failed to initialize AC97: {}", ret.error()); return ret.release_error(); } + break; + case 0x03: + if (auto ret = HDAudioController::create(pci_device); ret.is_error()) + { + dwarnln("Failed to initialize Intel HDA: {}", ret.error()); + return ret.release_error(); + } + break; default: dprintln("Unsupported Sound card (PCI {2H}:{2H}:{2H})", pci_device.class_code(), @@ -45,8 +49,9 @@ namespace Kernel ); return BAN::Error::from_errno(ENOTSUP); } - } + return {}; + } BAN::ErrorOr AudioController::write_impl(off_t, BAN::ConstByteSpan buffer) { diff --git a/kernel/kernel/Audio/HDAudio/AudioFunctionGroup.cpp b/kernel/kernel/Audio/HDAudio/AudioFunctionGroup.cpp new file mode 100644 index 00000000..e858e64d --- /dev/null +++ b/kernel/kernel/Audio/HDAudio/AudioFunctionGroup.cpp @@ -0,0 +1,456 @@ +#include +#include +#include + +namespace Kernel +{ + + BAN::ErrorOr> HDAudioFunctionGroup::create(BAN::RefPtr controller, uint8_t cid, HDAudio::AFGNode&& afg_node) + { + auto* audio_group_ptr = new HDAudioFunctionGroup(controller, cid, BAN::move(afg_node)); + if (audio_group_ptr == nullptr) + return BAN::Error::from_errno(ENOMEM); + auto audio_group = BAN::RefPtr::adopt(audio_group_ptr); + TRY(audio_group->initialize()); + return audio_group; + } + + HDAudioFunctionGroup::~HDAudioFunctionGroup() + { + if (m_stream_id != 0xFF) + m_controller->deallocate_stream_id(m_stream_id); + if (m_stream_index != 0xFF) + m_controller->deallocate_stream(m_stream_index); + } + + BAN::ErrorOr HDAudioFunctionGroup::initialize() + { + if constexpr(DEBUG_HDAUDIO) + { + const auto widget_to_string = + [](HDAudio::AFGWidget::Type type) -> const char* + { + using HDAudio::AFGWidget; + switch (type) + { + case AFGWidget::Type::OutputConverter: return "DAC"; + case AFGWidget::Type::InputConverter: return "ADC"; + case AFGWidget::Type::Mixer: return "Mixer"; + case AFGWidget::Type::Selector: return "Selector"; + case AFGWidget::Type::PinComplex: return "Pin"; + case AFGWidget::Type::Power: return "Power"; + case AFGWidget::Type::VolumeKnob: return "VolumeKnob"; + case AFGWidget::Type::BeepGenerator: return "BeepGenerator"; + } + ASSERT_NOT_REACHED(); + }; + + dprintln("AFG {}", m_afg_node.id); + for (auto widget : m_afg_node.widgets) + { + if (widget.type == HDAudio::AFGWidget::Type::PinComplex) + { + const uint32_t config = TRY(m_controller->send_command({ + .data = 0x00, + .command = 0xF1C, + .node_index = widget.id, + .codec_address = m_cid, + })); + + dprintln(" widget {}: {} ({}, {}), {32b}", + widget.id, + widget_to_string(widget.type), + (int)widget.pin_complex.output, + (int)widget.pin_complex.input, + config + ); + } + else + { + dprintln(" widget {}: {}", + widget.id, + widget_to_string(widget.type) + ); + } + + if (!widget.connections.empty()) + dprintln(" connections {}", widget.connections); + } + } + + TRY(initialize_stream()); + TRY(initialize_output()); + DevFileSystem::get().add_device(this); + return {}; + } + + size_t HDAudioFunctionGroup::bdl_offset() const + { + const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t); + const size_t bdl_total_size = bdl_entry_bytes * m_bdl_entry_count; + if (auto rem = bdl_total_size % 128) + return bdl_total_size + (128 - rem); + return bdl_total_size; + } + + BAN::ErrorOr HDAudioFunctionGroup::initialize_stream() + { + const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t); + const size_t bdl_list_size = m_bdl_entry_count * sizeof(HDAudio::BDLEntry); + + m_bdl_region = TRY(DMARegion::create(bdl_offset() + bdl_list_size)); + if (!m_controller->is_64bit() && (m_bdl_region->paddr() >> 32)) + { + dwarnln("no 64 bit support but allocated bdl has 64 bit address :("); + return BAN::Error::from_errno(ENOTSUP); + } + + auto* bdl = reinterpret_cast(m_bdl_region->vaddr() + bdl_offset()); + for (size_t i = 0; i < m_bdl_entry_count; i++) + { + bdl[i].address = m_bdl_region->paddr() + i * bdl_entry_bytes; + bdl[i].length = bdl_entry_bytes; + bdl[i].ioc = 1; + } + + ASSERT(m_stream_id == 0xFF); + m_stream_id = TRY(m_controller->allocate_stream_id()); + + ASSERT(m_stream_index == 0xFF); + m_stream_index = TRY(m_controller->allocate_stream(HDAudio::StreamType::Output, this)); + + reset_stream(); + + return {}; + } + + void HDAudioFunctionGroup::reset_stream() + { + using Regs = HDAudio::Regs; + + auto& bar = m_controller->bar0(); + const auto base = 0x80 + m_stream_index * 0x20; + + const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t); + + // stop stream + bar.write8(base + Regs::SDCTL, bar.read8(base + Regs::SDCTL) & 0xFD); + + // reset stream + bar.write8(base + Regs::SDCTL, (bar.read8(base + Regs::SDCTL) & 0xFE) | 1); + while (!(bar.read8(base + Regs::SDCTL) & 1)) + Processor::pause(); + bar.write8(base + Regs::SDCTL, (bar.read8(base + Regs::SDCTL) & 0xFE)); + while ((bar.read8(base + Regs::SDCTL) & 1)) + Processor::pause(); + + // set bdl address, total size and lvi + const paddr_t bdl_paddr = m_bdl_region->paddr() + bdl_offset(); + bar.write32(base + Regs::SDBDPL, bdl_paddr); + if (m_controller->is_64bit()) + bar.write32(base + Regs::SDBDPU, bdl_paddr >> 32); + bar.write32(base + Regs::SDCBL, bdl_entry_bytes * m_bdl_entry_count); + bar.write16(base + Regs::SDLVI, (bar.read16(base + Regs::SDLVI) & 0xFF00) | (m_bdl_entry_count - 1)); + + // set stream format + bar.write16(base + Regs::SDFMT, get_format_data()); + + // set stream id, not bidirectional + bar.write8(base + Regs::SDCTL + 2, (bar.read8(base + Regs::SDCTL + 2) & 0x07) | (m_stream_id << 4)); + + m_bdl_head = 0; + m_bdl_tail = 0; + m_stream_running = false; + } + + BAN::ErrorOr HDAudioFunctionGroup::initialize_output() + { + BAN::Vector path; + TRY(path.reserve(m_max_path_length)); + + for (const auto& widget : m_afg_node.widgets) + { + if (widget.type != HDAudio::AFGWidget::Type::PinComplex || !widget.pin_complex.output) + continue; + TRY(path.push_back(&widget)); + TRY(recurse_output_paths(widget, path)); + path.pop_back(); + } + + dprintln_if(DEBUG_HDAUDIO, "found {} paths from output to DAC", m_output_paths.size()); + + // select first supported path + // FIXME: prefer associations + // FIXME: does this pin even have a device? + auto result = BAN::Error::from_errno(ENODEV); + for (size_t i = 0; i < m_output_paths.size(); i++) + { + if (auto ret = enable_output_path(i); ret.is_error()) + { + if (ret.error().get_error_code() != ENOTSUP) + return ret.release_error(); + dwarnln("path {} not supported", i); + result = BAN::Error::from_errno(ENOTSUP); + continue; + } + + m_output_path_index = i; + break; + } + + if (m_output_path_index >= m_output_paths.size()) + { + dwarnln("could not find any usable output path"); + return result; + } + + dprintln_if(DEBUG_HDAUDIO, "routed output path"); + for (const auto* widget : m_output_paths[m_output_path_index]) + dprintln_if(DEBUG_HDAUDIO, " {}", widget->id); + + return {}; + } + + uint16_t HDAudioFunctionGroup::get_format_data() const + { + // TODO: don't hardcode this + // format: PCM, 48 kHz, 16 bit, 2 channels + return 0b0'0'000'000'0'001'0001; + } + + uint16_t HDAudioFunctionGroup::get_volume_data() const + { + // TODO: don't hardcode this + // left and right output, no mute, max gain + return 0b1'0'1'1'0000'0'1111111; + } + + BAN::ErrorOr HDAudioFunctionGroup::enable_output_path(uint8_t index) + { + ASSERT(index < m_output_paths.size()); + const auto& path = m_output_paths[index]; + + for (const auto* widget : path) + { + switch (widget->type) + { + using HDAudio::AFGWidget; + case AFGWidget::Type::OutputConverter: + case AFGWidget::Type::PinComplex: + break; + default: + dwarnln("FIXME: support enabling widget type {}", static_cast(widget->type)); + return BAN::Error::from_errno(ENOTSUP); + } + } + + const auto format = get_format_data(); + const auto volume = get_volume_data(); + + for (size_t i = 0; i < path.size(); i++) + { + // set power state D0 + TRY(m_controller->send_command({ + .data = 0x00, + .command = 0x705, + .node_index = path[i]->id, + .codec_address = m_cid, + })); + + // set connection index + if (i + 1 < path.size() && path[i]->connections.size() > 1) + { + uint8_t index = 0; + for (; index < path[i]->connections.size(); index++) + if (path[i]->connections[index] == path[i + 1]->id) + break; + ASSERT(index < path[i]->connections.size()); + + TRY(m_controller->send_command({ + .data = index, + .command = 0x701, + .node_index = path[i]->id, + .codec_address = m_cid, + })); + } + + // set volume + TRY(m_controller->send_command({ + .data = static_cast(volume & 0xFF), + .command = static_cast(0x300 | (volume >> 8)), + .node_index = path[i]->id, + .codec_address = m_cid, + })); + + switch (path[i]->type) + { + using HDAudio::AFGWidget; + + case AFGWidget::Type::OutputConverter: + // set stream and channel 0 + TRY(m_controller->send_command({ + .data = static_cast(m_stream_id << 4), + .command = 0x706, + .node_index = path[i]->id, + .codec_address = m_cid, + })); + // set format + TRY(m_controller->send_command({ + .data = static_cast(format & 0xFF), + .command = static_cast(0x200 | (format >> 8)), + .node_index = path[i]->id, + .codec_address = m_cid, + })); + break; + + case AFGWidget::Type::PinComplex: + // enable output and H-Phn + TRY(m_controller->send_command({ + .data = 0x80 | 0x40, + .command = 0x707, + .node_index = path[i]->id, + .codec_address = m_cid, + })); + // enable EAPD + TRY(m_controller->send_command({ + .data = 0x02, + .command = 0x70C, + .node_index = path[i]->id, + .codec_address = m_cid, + })); + break; + + default: + ASSERT_NOT_REACHED(); + } + } + + return {}; + } + + BAN::ErrorOr HDAudioFunctionGroup::recurse_output_paths(const HDAudio::AFGWidget& widget, BAN::Vector& path) + { + // cycle "detection" + if (path.size() >= m_max_path_length) + return {}; + + // we've reached a DAC + if (widget.type == HDAudio::AFGWidget::Type::OutputConverter) + { + BAN::Vector path_copy; + TRY(path_copy.resize(path.size())); + for (size_t i = 0; i < path.size(); i++) + path_copy[i] = path[i]; + TRY(m_output_paths.push_back(BAN::move(path_copy))); + return {}; + } + + // check all connections + for (const auto& connection : m_afg_node.widgets) + { + if (!widget.connections.contains(connection.id)) + continue; + TRY(path.push_back(&connection)); + TRY(recurse_output_paths(connection, path)); + path.pop_back(); + } + + return {}; + } + + void HDAudioFunctionGroup::handle_new_data() + { + queue_bdl_data(); + } + + void HDAudioFunctionGroup::queue_bdl_data() + { + ASSERT(m_spinlock.current_processor_has_lock()); + + const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t); + + while ((m_bdl_head + 1) % m_bdl_entry_count != m_bdl_tail) + { + const size_t sample_data_tail = (m_sample_data_head + m_sample_data_capacity - m_sample_data_size) % m_sample_data_capacity; + + const size_t sample_frames = BAN::Math::min(m_sample_data_size / get_channels() / sizeof(uint16_t), m_bdl_entry_sample_frames); + if (sample_frames == 0) + break; + + const size_t copy_total_bytes = sample_frames * get_channels() * sizeof(uint16_t); + const size_t copy_before_wrap = BAN::Math::min(copy_total_bytes, m_sample_data_capacity - sample_data_tail); + + memcpy( + reinterpret_cast(m_bdl_region->vaddr() + m_bdl_head * bdl_entry_bytes), + &m_sample_data[sample_data_tail], + copy_before_wrap + ); + + if (copy_before_wrap < copy_total_bytes) + { + memcpy( + reinterpret_cast(m_bdl_region->vaddr() + m_bdl_head * bdl_entry_bytes + copy_before_wrap), + &m_sample_data[0], + copy_total_bytes - copy_before_wrap + ); + } + + if (copy_total_bytes < bdl_entry_bytes) + { + memset( + reinterpret_cast(m_bdl_region->vaddr() + m_bdl_head * bdl_entry_bytes + copy_total_bytes), + 0x00, + bdl_entry_bytes - copy_total_bytes + ); + } + + m_sample_data_size -= copy_total_bytes; + + m_bdl_head = (m_bdl_head + 1) % m_bdl_entry_count; + } + + if (m_bdl_head == m_bdl_tail || m_stream_running) + return; + + // start the stream and enable IOC and descriptor error interrupts + auto& bar = m_controller->bar0(); + const auto base = 0x80 + m_stream_index * 0x20; + bar.write8(base + HDAudio::Regs::SDCTL, bar.read8(base + HDAudio::Regs::SDCTL) | 0x16); + + m_stream_running = true; + } + + void HDAudioFunctionGroup::on_stream_interrupt(uint8_t stream_index) + { + using Regs = HDAudio::Regs; + + ASSERT(stream_index == m_stream_index); + + auto& bar = m_controller->bar0(); + const uint16_t base = 0x80 + stream_index * 0x20; + + const uint8_t sts = bar.read8(base + Regs::SDSTS); + bar.write8(base + Regs::SDSTS, sts & 0x3C); + + if (sts & (1 << 4)) + derrorln("descriptor error"); + + // ignore fifo errors as they are too common on real hw :D + //if (sts & (1 << 3)) + // derrorln("fifo error"); + + if (sts & (1 << 2)) + { + SpinLockGuard _(m_spinlock); + + ASSERT(m_stream_running); + + m_bdl_tail = (m_bdl_tail + 1) % m_bdl_entry_count; + if (m_bdl_tail == m_bdl_head) + reset_stream(); + + queue_bdl_data(); + } + } + +} diff --git a/kernel/kernel/Audio/HDAudio/Controller.cpp b/kernel/kernel/Audio/HDAudio/Controller.cpp new file mode 100644 index 00000000..b3cbb81f --- /dev/null +++ b/kernel/kernel/Audio/HDAudio/Controller.cpp @@ -0,0 +1,492 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace Kernel +{ + + BAN::ErrorOr HDAudioController::create(PCI::Device& pci_device) + { + auto intel_hda_ptr = new HDAudioController(pci_device); + if (intel_hda_ptr == nullptr) + return BAN::Error::from_errno(ENOMEM); + auto intel_hda = BAN::RefPtr::adopt(intel_hda_ptr); + TRY(intel_hda->initialize()); + return {}; + } + + BAN::ErrorOr HDAudioController::initialize() + { + using Regs = HDAudio::Regs; + + m_pci_device.enable_bus_mastering(); + + m_bar0 = TRY(m_pci_device.allocate_bar_region(0)); + + dprintln("HD audio"); + dprintln(" revision {}.{}", + m_bar0->read8(Regs::VMAJ), + m_bar0->read8(Regs::VMIN) + ); + + const uint16_t global_cap = m_bar0->read16(Regs::GCAP); + m_output_streams = (global_cap >> 12) & 0x0F; + m_input_streams = (global_cap >> 8) & 0x0F; + m_bidir_streams = (global_cap >> 3) & 0x1F; + m_is64bit = (global_cap & 1); + + if (m_output_streams + m_input_streams + m_bidir_streams > 30) + { + dwarnln("HD audio controller has {} streams, 30 is the maximum valid count", + m_output_streams + m_input_streams + m_bidir_streams + ); + return BAN::Error::from_errno(EINVAL); + } + + dprintln(" output streams: {}", m_output_streams); + dprintln(" input streams: {}", m_input_streams); + dprintln(" bidir streams: {}", m_bidir_streams); + dprintln(" 64 bit support: {}", m_is64bit); + + TRY(reset_controller()); + + if (auto ret = initialize_ring_buffers(); ret.is_error()) + { + if (ret.error().get_error_code() != ETIMEDOUT) + return ret.release_error(); + m_use_immediate_command = true; + } + + TRY(m_pci_device.reserve_interrupts(1)); + m_pci_device.enable_interrupt(0, *this); + m_bar0->write32(Regs::INTCTL, UINT32_MAX); + + for (uint8_t codec_id = 0; codec_id < 0x10; codec_id++) + { + auto codec_or_error = initialize_codec(codec_id); + if (codec_or_error.is_error()) + continue; + + auto codec = codec_or_error.release_value(); + for (auto& node : codec.nodes) + if (auto ret = HDAudioFunctionGroup::create(this, codec.id, BAN::move(node)); ret.is_error()) + dwarnln("Failed to initialize AFG: {}", ret.error()); + } + + return {}; + } + + BAN::ErrorOr HDAudioController::reset_controller() + { + using HDAudio::Regs; + + const auto timeout_ms = SystemTimer::get().ms_since_boot() + 100; + + // transition into reset state + if (const uint32_t gcap = m_bar0->read32(Regs::GCTL); gcap & 1) + { + m_bar0->write32(Regs::GCTL, gcap & 0xFFFFFEFC); + while (m_bar0->read32(Regs::GCTL) & 1) + { + if (SystemTimer::get().ms_since_boot() > timeout_ms) + return BAN::Error::from_errno(ETIMEDOUT); + Processor::pause(); + } + } + + m_bar0->write32(Regs::GCTL, (m_bar0->read32(Regs::GCTL) & 0xFFFFFEFC) | 1); + while (!(m_bar0->read32(Regs::GCTL) & 1)) + { + if (SystemTimer::get().ms_since_boot() > timeout_ms) + return BAN::Error::from_errno(ETIMEDOUT); + Processor::pause(); + } + + // 4.3 The software must wait at least 521 us (25 frames) after reading CRST as a 1 + // before assuming that codecs have all made status change requests and have been + // registered by the controller + SystemTimer::get().sleep_ms(1); + + return {}; + } + + BAN::ErrorOr HDAudioController::initialize_ring_buffers() + { + using Regs = HDAudio::Regs; + + // CORB: at most 1024 bytes (256 * uint32_t) + // RIRB: at most 2048 bytes (256 * uint32_t * 2) + m_ring_buffer_region = TRY(DMARegion::create(3 * 256 * sizeof(uint32_t))); + + struct SizeInfo + { + uint16_t size; + uint8_t value; + }; + + const auto get_size_info = + [](uint8_t byte) -> BAN::ErrorOr + { + if (byte & 0x40) + return SizeInfo { 256, 2 }; + if (byte & 0x20) + return SizeInfo { 16, 1 }; + if (byte & 0x10) + return SizeInfo { 2, 0 }; + return BAN::Error::from_errno(EINVAL); + }; + + const auto corb_size = TRY(get_size_info(m_bar0->read8(Regs::CORBSIZE))); + const auto rirb_size = TRY(get_size_info(m_bar0->read8(Regs::RIRBSIZE))); + + m_corb = { + .vaddr = m_ring_buffer_region->vaddr(), + .index = 1, + .size = corb_size.size, + }; + + m_rirb = { + .vaddr = m_ring_buffer_region->vaddr() + 1024, + .index = 1, + .size = rirb_size.size, + }; + + const paddr_t corb_paddr = m_ring_buffer_region->paddr(); + const paddr_t rirb_paddr = m_ring_buffer_region->paddr() + 1024; + if (!m_is64bit && ((corb_paddr >> 32) || (rirb_paddr >> 32))) + { + dwarnln("no 64 bit support but allocated ring buffers have 64 bit addresses :("); + return BAN::Error::from_errno(ENOTSUP); + } + + // disable corb and rirb + m_bar0->write8(Regs::CORBCTL, (m_bar0->read8(Regs::CORBCTL) & 0xFC)); + m_bar0->write8(Regs::RIRBCTL, (m_bar0->read8(Regs::RIRBCTL) & 0xF8)); + + // set base address + m_bar0->write32(Regs::CORBLBASE, corb_paddr | (m_bar0->read32(Regs::CORBLBASE) & 0x0000007F)); + if (m_is64bit) + m_bar0->write32(Regs::CORBUBASE, corb_paddr >> 32); + // set number of entries + m_bar0->write8(Regs::CORBSIZE, (m_bar0->read8(Regs::CORBSIZE) & 0xFC) | corb_size.value); + // zero write pointer + m_bar0->write16(Regs::CORBWP, (m_bar0->read16(Regs::CORBWP) & 0xFF00)); + // reset read pointer + const uint64_t corb_timeout_ms = SystemTimer::get().ms_since_boot() + 100; + m_bar0->write16(Regs::CORBRP, (m_bar0->read16(Regs::CORBRP) & 0x7FFF) | 0x8000); + while (!(m_bar0->read16(Regs::CORBRP) & 0x8000)) + { + if (SystemTimer::get().ms_since_boot() > corb_timeout_ms) + return BAN::Error::from_errno(ETIMEDOUT); + Processor::pause(); + } + m_bar0->write16(Regs::CORBRP, (m_bar0->read16(Regs::CORBRP) & 0x7FFF)); + while ((m_bar0->read16(Regs::CORBRP) & 0x8000)) + { + if (SystemTimer::get().ms_since_boot() > corb_timeout_ms) + return BAN::Error::from_errno(ETIMEDOUT); + Processor::pause(); + } + + // set base address + m_bar0->write32(Regs::RIRBLBASE, rirb_paddr | (m_bar0->read32(Regs::RIRBLBASE) & 0x0000007F)); + if (m_is64bit) + m_bar0->write32(Regs::RIRBUBASE, rirb_paddr >> 32); + // set number of entries + m_bar0->write8(Regs::RIRBSIZE, (m_bar0->read8(Regs::RIRBSIZE) & 0xFC) | rirb_size.value); + // reset write pointer + m_bar0->write16(Regs::RIRBWP, (m_bar0->read16(Regs::RIRBWP) & 0x7FFF) | 0x8000); + // send interrupt on every packet + m_bar0->write16(Regs::RINTCNT, (m_bar0->read16(Regs::RINTCNT) & 0xFF00) | 0x01); + + // enable corb and rirb + m_bar0->write8(Regs::CORBCTL, (m_bar0->read8(Regs::CORBCTL) & 0xFC) | 3); + m_bar0->write8(Regs::RIRBCTL, (m_bar0->read8(Regs::RIRBCTL) & 0xF8) | 7); + + return {}; + } + + BAN::ErrorOr HDAudioController::initialize_codec(uint8_t codec) + { + const auto resp = TRY(send_command({ + .data = 0x04, + .command = 0xF00, + .node_index = 0, + .codec_address = codec, + })); + const uint8_t start = (resp >> 16) & 0xFF; + const uint8_t count = (resp >> 0) & 0xFF; + if (count == 0) + return BAN::Error::from_errno(ENODEV); + + HDAudio::Codec result {}; + result.id = codec; + TRY(result.nodes.reserve(count)); + + for (size_t i = 0; i < count; i++) + if (auto node_or_error = initialize_node(codec, start + i); !node_or_error.is_error()) + MUST(result.nodes.emplace_back(node_or_error.release_value())); + + return result; + } + + BAN::ErrorOr HDAudioController::initialize_node(uint8_t codec, uint8_t node) + { + { + const auto resp = TRY(send_command({ + .data = 0x05, + .command = 0xF00, + .node_index = node, + .codec_address = codec, + })); + const uint8_t type = (resp >> 0) & 0xFF; + if (type != 0x01) + return BAN::Error::from_errno(ENODEV); + } + + const auto resp = TRY(send_command({ + .data = 0x04, + .command = 0xF00, + .node_index = node, + .codec_address = codec, + })); + const uint8_t start = (resp >> 16) & 0xFF; + const uint8_t count = (resp >> 0) & 0xFF; + + HDAudio::AFGNode result {}; + result.id = node; + TRY(result.widgets.reserve(count)); + + for (size_t i = 0; i < count; i++) + if (auto widget_or_error = initialize_widget(codec, start + i); !widget_or_error.is_error()) + MUST(result.widgets.emplace_back(widget_or_error.release_value())); + + return result; + } + + BAN::ErrorOr HDAudioController::initialize_widget(uint8_t codec, uint8_t widget) + { + const auto send_command_or_zero = + [codec, widget, this](uint16_t cmd, uint8_t data) -> uint32_t + { + const auto command = HDAudio::CORBEntry { + .data = data, + .command = cmd, + .node_index = widget, + .codec_address = codec, + }; + if (auto res = send_command(command); !res.is_error()) + return res.release_value(); + return 0; + }; + + using HDAudio::AFGWidget; + const AFGWidget::Type type_list[] { + AFGWidget::Type::OutputConverter, + AFGWidget::Type::InputConverter, + AFGWidget::Type::Mixer, + AFGWidget::Type::Selector, + AFGWidget::Type::PinComplex, + AFGWidget::Type::Power, + AFGWidget::Type::VolumeKnob, + AFGWidget::Type::BeepGenerator, + }; + + const uint8_t type = (send_command_or_zero(0xF00, 0x09) >> 20) & 0x0F; + if (type > sizeof(type_list) / sizeof(*type_list)) + return BAN::Error::from_errno(ENOTSUP); + + AFGWidget result {}; + result.type = type_list[type]; + result.id = widget; + + if (result.type == AFGWidget::Type::PinComplex) + { + const uint32_t cap = send_command_or_zero(0xF00, 0x0C); + result.pin_complex.output = !!(cap & (1 << 4)); + result.pin_complex.input = !!(cap & (1 << 5)); + } + + const uint8_t connection_info = send_command_or_zero(0xF00, 0x0E); + const uint8_t conn_width = (connection_info & 0x80) ? 2 : 1; + const uint8_t conn_count = connection_info & 0x3F; + const uint16_t conn_mask = (1 << (8 * conn_width)) - 1; + + TRY(result.connections.resize(conn_count, 0)); + for (size_t i = 0; i < conn_count; i += 4 / conn_width) + { + const uint32_t conn = send_command_or_zero(0xF02, i); + for (size_t j = 0; j < sizeof(conn) / conn_width && i + j < conn_count; j++) + result.connections[i + j] = (conn >> (8 * conn_width * j)) & conn_mask; + } + + return result; + } + + BAN::ErrorOr HDAudioController::send_command(HDAudio::CORBEntry command) + { + using Regs = HDAudio::Regs; + + // TODO: allow concurrent commands with CORB/RIRB + LockGuard _(m_command_mutex); + + if (!m_use_immediate_command) + { + SpinLockGuard sguard(m_rb_lock); + + MMIO::write32(m_corb.vaddr + m_corb.index * sizeof(uint32_t), command.raw); + m_bar0->write16(Regs::CORBWP, (m_bar0->read16(Regs::CORBWP) & 0xFF00) | m_corb.index); + m_corb.index = (m_corb.index + 1) % m_corb.size; + + const uint64_t waketime_ms = SystemTimer::get().ms_since_boot() + 10; + while ((m_bar0->read16(Regs::RIRBWP) & 0xFF) != m_rirb.index) + { + if (SystemTimer::get().ms_since_boot() > waketime_ms) + return BAN::Error::from_errno(ETIMEDOUT); + SpinLockGuardAsMutex smutex(sguard); + m_rb_blocker.block_with_timeout_ms(10, &smutex); + } + + const size_t offset = 2 * m_rirb.index * sizeof(uint32_t); + m_rirb.index = (m_rirb.index + 1) % m_rirb.size; + return MMIO::read32(m_rirb.vaddr + offset); + } + else + { + uint64_t waketime_ms = SystemTimer::get().ms_since_boot() + 10; + while (m_bar0->read16(Regs::ICIS) & 1) + { + if (SystemTimer::get().ms_since_boot() > waketime_ms) + break; + Processor::pause(); + } + + // clear ICB if it did not clear "in reasonable timeout period" + // and make sure IRV is cleared + if (m_bar0->read16(Regs::ICIS) & 3) + m_bar0->write16(Regs::ICIS, (m_bar0->read16(Regs::ICIS) & 0x00FC) | 2); + + m_bar0->write32(Regs::ICOI, command.raw); + + m_bar0->write16(Regs::ICIS, (m_bar0->read16(Regs::ICIS) & 0x00FC) | 1); + + waketime_ms = SystemTimer::get().ms_since_boot() + 10; + while (!(m_bar0->read16(Regs::ICIS) & 2)) + { + if (SystemTimer::get().ms_since_boot() > waketime_ms) + return BAN::Error::from_errno(ETIMEDOUT); + Processor::pause(); + } + + return m_bar0->read32(Regs::ICII); + } + } + + void HDAudioController::handle_irq() + { + using Regs = HDAudio::Regs; + + const uint32_t intsts = m_bar0->read32(Regs::INTSTS); + if (!(intsts & (1u << 31))) + return; + + if (intsts & (1 << 30)) + { + if (const uint8_t rirbsts = m_bar0->read8(Regs::RIRBSTS) & ((1 << 2) | (1 << 0))) + { + if (rirbsts & (1 << 2)) + dwarnln("RIRB response overrun"); + if (rirbsts & (1 << 0)) + { + SpinLockGuard _(m_rb_lock); + m_rb_blocker.unblock(); + } + m_bar0->write8(Regs::RIRBSTS, rirbsts); + } + + if (const uint8_t corbsts = m_bar0->read8(Regs::CORBSTS) & (1 << 0)) + { + dwarnln("CORB memory error"); + m_bar0->write8(Regs::CORBSTS, corbsts); + } + } + + for (size_t i = 0; i < 30; i++) + { + if (!(intsts & (1 << i))) + continue; + if (m_allocated_streams[i] == nullptr) + dwarnln("interrupt from an unallocated stream??"); + else + static_cast(m_allocated_streams[i])->on_stream_interrupt(i); + } + } + + uint8_t HDAudioController::get_stream_index(HDAudio::StreamType type, uint8_t index) const + { + switch (type) + { + case HDAudio::StreamType::Bidirectional: + index += m_output_streams; + [[fallthrough]]; + case HDAudio::StreamType::Output: + index += m_input_streams; + [[fallthrough]]; + case HDAudio::StreamType::Input: + break; + } + return index; + } + + BAN::ErrorOr HDAudioController::allocate_stream_id() + { + for (uint8_t id = 1; id < 16; id++) + { + if (m_allocated_stream_ids & (1 << id)) + continue; + m_allocated_stream_ids |= 1 << id; + return id; + } + + return BAN::Error::from_errno(EAGAIN); + } + + void HDAudioController::deallocate_stream_id(uint8_t id) + { + ASSERT(m_allocated_stream_ids & (1 << id)); + m_allocated_stream_ids &= ~(1 << id); + } + + BAN::ErrorOr HDAudioController::allocate_stream(HDAudio::StreamType type, void* afg) + { + const uint8_t stream_count_lookup[] { + [(int)HDAudio::StreamType::Input] = m_input_streams, + [(int)HDAudio::StreamType::Output] = m_output_streams, + [(int)HDAudio::StreamType::Bidirectional] = m_bidir_streams, + }; + + const uint8_t stream_count = stream_count_lookup[static_cast(type)]; + for (uint8_t i = 0; i < stream_count; i++) + { + const uint8_t index = get_stream_index(type, i); + if (m_allocated_streams[index]) + continue; + m_allocated_streams[index] = afg; + return index; + } + + return BAN::Error::from_errno(EAGAIN); + } + + void HDAudioController::deallocate_stream(uint8_t index) + { + ASSERT(m_allocated_streams[index]); + m_allocated_streams[index] = nullptr; + // TODO: maybe make sure the stream is stopped/reset (?) + } + +}