From 8a663cb94f5aa1a151903bffef47d80806b3d91e Mon Sep 17 00:00:00 2001 From: Bananymous Date: Thu, 10 Jul 2025 17:29:11 +0300 Subject: [PATCH] Kernel: Implement basic AC97 driver --- kernel/CMakeLists.txt | 2 + kernel/include/kernel/Audio/AC97/Controller.h | 51 ++++ .../include/kernel/Audio/AC97/Definitions.h | 15 + kernel/include/kernel/Audio/Controller.h | 48 ++++ kernel/include/kernel/Device/DeviceNumbers.h | 1 + kernel/kernel/Audio/AC97/Controller.cpp | 265 ++++++++++++++++++ kernel/kernel/Audio/Controller.cpp | 105 +++++++ kernel/kernel/PCI.cpp | 15 +- userspace/libraries/LibC/include/sys/ioctl.h | 5 + 9 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 kernel/include/kernel/Audio/AC97/Controller.h create mode 100644 kernel/include/kernel/Audio/AC97/Definitions.h create mode 100644 kernel/include/kernel/Audio/Controller.h create mode 100644 kernel/kernel/Audio/AC97/Controller.cpp create mode 100644 kernel/kernel/Audio/Controller.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 09f4b6ec..8cf6c494 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -6,6 +6,8 @@ set(KERNEL_SOURCES kernel/ACPI/AML/OpRegion.cpp kernel/ACPI/BatterySystem.cpp kernel/APIC.cpp + kernel/Audio/AC97/Controller.cpp + kernel/Audio/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 new file mode 100644 index 00000000..48a26419 --- /dev/null +++ b/kernel/include/kernel/Audio/AC97/Controller.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +namespace Kernel +{ + + class AC97AudioController : public AudioController, public Interruptable + { + public: + static BAN::ErrorOr> create(PCI::Device& pci_device); + + void handle_irq() override; + + protected: + void handle_new_data() override; + + uint32_t get_channels() const override { return 2; } + uint32_t get_sample_rate() const override { return 48000; } + + private: + AC97AudioController(PCI::Device& pci_device) + : m_pci_device(pci_device) + { } + + BAN::ErrorOr initialize(); + BAN::ErrorOr initialize_bld(); + BAN::ErrorOr initialize_interrupts(); + + void queue_samples_to_bld(); + + private: + static constexpr size_t m_bdl_entries = 32; + static constexpr size_t m_samples_per_entry = 0x1000; + + // We only store samples in 2 BDL entries at a time to reduce the amount of samples queued. + // This is to reduce latency as you cannot remove data already passed to the BDLs + static constexpr size_t m_used_bdl_entries = 2; + + PCI::Device& m_pci_device; + BAN::UniqPtr m_mixer; + BAN::UniqPtr m_bus_master; + + BAN::UniqPtr m_bdl_region; + + uint32_t m_bdl_tail { 0 }; + uint32_t m_bdl_head { 0 }; + }; + +} diff --git a/kernel/include/kernel/Audio/AC97/Definitions.h b/kernel/include/kernel/Audio/AC97/Definitions.h new file mode 100644 index 00000000..1de63de2 --- /dev/null +++ b/kernel/include/kernel/Audio/AC97/Definitions.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace Kernel::AC97 +{ + + struct BufferDescriptorListEntry + { + uint32_t address; + uint16_t samples; + uint16_t flags; // bit 14: last entry, bit 15: IOC + }; + +} diff --git a/kernel/include/kernel/Audio/Controller.h b/kernel/include/kernel/Audio/Controller.h new file mode 100644 index 00000000..ef0c0d99 --- /dev/null +++ b/kernel/include/kernel/Audio/Controller.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +namespace Kernel +{ + + class AudioController : public CharacterDevice + { + public: + static BAN::ErrorOr> create(PCI::Device& pci_device); + + dev_t rdev() const override { return m_rdev; } + BAN::StringView name() const override { return m_name; } + + protected: + AudioController(); + + virtual void handle_new_data() = 0; + + virtual uint32_t get_channels() const = 0; + virtual uint32_t get_sample_rate() const = 0; + + bool can_read_impl() const override { return false; } + bool can_write_impl() const override { SpinLockGuard _(m_spinlock); return m_sample_data_size < m_sample_data_capacity; } + bool has_error_impl() const override { return false; } + bool has_hungup_impl() const override { return false; } + + BAN::ErrorOr write_impl(off_t, BAN::ConstByteSpan) override; + + BAN::ErrorOr ioctl_impl(int cmd, void* arg) override; + + protected: + ThreadBlocker m_sample_data_blocker; + mutable SpinLock m_spinlock; + + static constexpr size_t m_sample_data_capacity = 1 << 20; + uint8_t m_sample_data[m_sample_data_capacity]; + size_t m_sample_data_head { 0 }; + size_t m_sample_data_size { 0 }; + + private: + const dev_t m_rdev; + char m_name[10] {}; + }; + +} diff --git a/kernel/include/kernel/Device/DeviceNumbers.h b/kernel/include/kernel/Device/DeviceNumbers.h index e2c43fcb..3a96cb62 100644 --- a/kernel/include/kernel/Device/DeviceNumbers.h +++ b/kernel/include/kernel/Device/DeviceNumbers.h @@ -22,6 +22,7 @@ namespace Kernel Ethernet, Loopback, TmpFS, + AudioController, }; } diff --git a/kernel/kernel/Audio/AC97/Controller.cpp b/kernel/kernel/Audio/AC97/Controller.cpp new file mode 100644 index 00000000..0b790954 --- /dev/null +++ b/kernel/kernel/Audio/AC97/Controller.cpp @@ -0,0 +1,265 @@ +#include +#include + +namespace Kernel +{ + + enum AudioMixerRegister : uint8_t + { + Reset = 0x00, + MasterVolume = 0x02, + AuxOutVolume = 0x04, + MonoVolume = 0x06, + MasterToneRL = 0x08, + PC_BEEPVolume = 0x0A, + PhoneVolume = 0x0C, + MicVolume = 0x0E, + LineInVolume = 0x10, + CDVolume = 0x12, + VideoVolume = 0x14, + AuxInVolume = 0x16, + PCMOutVolume = 0x18, + RecordSelect = 0x1A, + RecordGain = 0x1C, + RecordGainMic = 0x1E, + GeneralPurpose = 0x20, + _3DControl = 0x22, + PowerdownCtrlStat = 0x26, + ExtendedAudio = 0x28, + ExtendedAudioCtrlStat = 0x2A, + PCMFrontDACRate = 0x2C, + PCMSurroundDACRate = 0x2E, + PCMLFEDACRate = 0x30, + PCMLRADCRate = 0x32, + MICADCRate = 0x34, + _6ChVolC_LFE = 0x36, + _6ChVolL_R_Surround = 0x38, + S_PDIFControl = 0x3A + }; + + enum BusMasterRegister : uint8_t + { + PI_BDBAR = 0x00, + PI_CIV = 0x04, + PI_LVI = 0x05, + PI_SR = 0x06, + PI_PICB = 0x08, + PI_PIV = 0x0A, + PI_CR = 0x0B, + PO_BDBAR = 0x10, + PO_CIV = 0x14, + PO_LVI = 0x15, + PO_SR = 0x16, + PO_PICB = 0x18, + PO_PIV = 0x1A, + PO_CR = 0x1B, + MC_BDBAR = 0x20, + MC_CIV = 0x24, + MC_LVI = 0x25, + MC_SR = 0x26, + MC_PICB = 0x28, + MC_PIV = 0x2A, + MC_CR = 0x2B, + GLOB_CNT = 0x2C, + GLOB_STA = 0x30, + CAS = 0x34, + MC2_BDBAR = 0x40, + MC2_CIV = 0x44, + MC2_LVI = 0x45, + MC2_SR = 0x46, + MC2_PICB = 0x48, + MC2_PIV = 0x4A, + MC2_CR = 0x4B, + PI2_BDBAR = 0x50, + PI2_CIV = 0x54, + PI2_LVI = 0x55, + PI2_SR = 0x56, + PI2_PICB = 0x58, + PI2_PIV = 0x5A, + PI2_CR = 0x5B, + SPBAR = 0x60, + SPCIV = 0x64, + SPLVI = 0x65, + SPSR = 0x66, + SPPICB = 0x68, + SPPIV = 0x6A, + SPCR = 0x6B, + SDM = 0x80, + }; + + enum BusMasterStatus : uint16_t + { + DMAHalted = 1 << 0, + CELV = 1 << 1, + LVBCI = 1 << 2, + BCIS = 1 << 3, + FIFOE = 1 << 4, + }; + + enum BusMasterControl : uint8_t + { + RDBM = 1 << 0, + RR = 1 << 1, + LVBIE = 1 << 2, + FEIE = 1 << 3, + IOCE = 1 << 4, + }; + + 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; + } + + BAN::ErrorOr AC97AudioController::initialize() + { + m_pci_device.enable_bus_mastering(); + + m_mixer = TRY(m_pci_device.allocate_bar_region(0)); + m_bus_master = TRY(m_pci_device.allocate_bar_region(1)); + + if (m_mixer->size() < 0x34 || m_bus_master->size() < 0x34) + return BAN::Error::from_errno(ENODEV); + + // Reset bus master + m_bus_master->write32(BusMasterRegister::GLOB_CNT, 0x00); + + // no interrupts, 2 channels, 16 bit samples + m_bus_master->write32(BusMasterRegister::GLOB_CNT, 0x02); + + // Reset mixer to default values + m_mixer->write16(AudioMixerRegister::Reset, 0); + + // Master volume 100%, no mute + m_mixer->write16(AudioMixerRegister::MasterVolume, 0x0000); + + // PCM output volume left/right +0 db, no mute + m_mixer->write16(AudioMixerRegister::PCMOutVolume, 0x0808); + + TRY(initialize_bld()); + + TRY(initialize_interrupts()); + + // disable transfer, enable all interrupts + m_bus_master->write8(BusMasterRegister::PO_CR, IOCE | FEIE | LVBIE); + + return {}; + } + + BAN::ErrorOr AC97AudioController::initialize_bld() + { + const size_t bdl_size = sizeof(AC97::BufferDescriptorListEntry) * m_bdl_entries; + const size_t buffer_size = m_samples_per_entry * sizeof(int16_t); + + m_bdl_region = TRY(DMARegion::create(bdl_size + buffer_size * m_used_bdl_entries)); + memset(reinterpret_cast(m_bdl_region->vaddr()), 0x00, m_bdl_region->size()); + + for (size_t i = 0; i < m_bdl_entries; i++) + { + auto& entry = reinterpret_cast(m_bdl_region->vaddr())[i]; + entry.address = m_bdl_region->paddr() + bdl_size + (i % m_used_bdl_entries) * buffer_size; + entry.samples = 0; + entry.flags = 0; + } + + m_bus_master->write32(BusMasterRegister::PO_BDBAR, m_bdl_region->paddr()); + + return {}; + } + + BAN::ErrorOr AC97AudioController::initialize_interrupts() + { + TRY(m_pci_device.reserve_interrupts(1)); + m_pci_device.enable_interrupt(0, *this); + + // enable interrupts + m_bus_master->write32(BusMasterRegister::GLOB_CNT, m_bus_master->read32(BusMasterRegister::GLOB_CNT) | 0x01); + + return {}; + } + + void AC97AudioController::handle_new_data() + { + ASSERT(m_spinlock.current_processor_has_lock()); + + if (m_bdl_head != m_bdl_tail) + return; + + queue_samples_to_bld(); + } + + void AC97AudioController::queue_samples_to_bld() + { + ASSERT(m_spinlock.current_processor_has_lock()); + + uint32_t lvi = m_bdl_head; + + while (m_bdl_head != (m_bdl_tail + m_used_bdl_entries) % m_bdl_entries) + { + const uint32_t next_bld_head = (m_bdl_head + 1) % m_bdl_entries; + if (next_bld_head == m_bdl_tail) + break; + + 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 max_memcpy = BAN::Math::min(m_sample_data_size, m_sample_data_capacity - sample_data_tail); + const size_t samples = BAN::Math::min(max_memcpy / 2, m_samples_per_entry); + if (samples == 0) + break; + + auto& entry = reinterpret_cast(m_bdl_region->vaddr())[m_bdl_head]; + entry.samples = samples; + entry.flags = (1 << 15); + memcpy( + reinterpret_cast(m_bdl_region->paddr_to_vaddr(entry.address)), + &m_sample_data[sample_data_tail], + samples * 2 + ); + + m_sample_data_size -= samples * 2; + + lvi = m_bdl_head; + m_bdl_head = next_bld_head; + } + + // if head was not updated, no data was queued + if (lvi == m_bdl_head) + return; + + m_sample_data_blocker.unblock(); + + m_bus_master->write8(BusMasterRegister::PO_LVI, lvi); + + // start playing if we are not already + const uint8_t control = m_bus_master->read8(BusMasterRegister::PO_CR); + if (!(control & RDBM)) + m_bus_master->write8(BusMasterRegister::PO_CR, control | RDBM); + } + + void AC97AudioController::handle_irq() + { + const uint16_t status = m_bus_master->read16(BusMasterRegister::PO_SR); + if (!(status & (LVBCI | BCIS | FIFOE))) + return; + m_bus_master->write16(BusMasterRegister::PO_SR, LVBCI | BCIS | FIFOE); + + SpinLockGuard _(m_spinlock); + + if (status & LVBCI) + { + const uint8_t control = m_bus_master->read8(BusMasterRegister::PO_CR); + m_bus_master->write8(BusMasterRegister::PO_CR, control & ~RDBM); + } + + if (status & BCIS) + { + m_bdl_tail = (m_bdl_tail + 1) % m_bdl_entries; + queue_samples_to_bld(); + } + } + +} diff --git a/kernel/kernel/Audio/Controller.cpp b/kernel/kernel/Audio/Controller.cpp new file mode 100644 index 00000000..ff5bb207 --- /dev/null +++ b/kernel/kernel/Audio/Controller.cpp @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include + +#include +#include + +namespace Kernel +{ + + static BAN::Atomic s_next_audio_minor = 0; + + AudioController::AudioController() + : CharacterDevice(0644, 0, 0) + , m_rdev(makedev(DeviceNumber::AudioController, s_next_audio_minor++)) + { + char* ptr = m_name; + BAN::Formatter::print([&ptr](char c) { *ptr++ = c; }, "audio{}", minor(m_rdev)); + } + + 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 + { + dwarnln("Failed to initialize AC97: {}", ret.error()); + return ret.release_error(); + } + default: + dprintln("Unsupported Sound card (PCI {2H}:{2H}:{2H})", + pci_device.class_code(), + pci_device.subclass(), + pci_device.prog_if() + ); + return BAN::Error::from_errno(ENOTSUP); + } + } + + + BAN::ErrorOr AudioController::write_impl(off_t, BAN::ConstByteSpan buffer) + { + SpinLockGuard lock_guard(m_spinlock); + + while (m_sample_data_size >= m_sample_data_capacity) + { + SpinLockGuardAsMutex smutex(lock_guard); + TRY(Thread::current().block_or_eintr_indefinite(m_sample_data_blocker, &smutex)); + } + + size_t nwritten = 0; + while (nwritten < buffer.size()) + { + if (m_sample_data_size >= m_sample_data_capacity) + break; + + const size_t max_memcpy = BAN::Math::min(m_sample_data_capacity - m_sample_data_size, m_sample_data_capacity - m_sample_data_head); + const size_t to_copy = BAN::Math::min(buffer.size() - nwritten, max_memcpy); + memcpy(m_sample_data + m_sample_data_head, buffer.data() + nwritten, to_copy); + + nwritten += to_copy; + m_sample_data_head = (m_sample_data_head + to_copy) % m_sample_data_capacity; + m_sample_data_size += to_copy; + } + + handle_new_data(); + + return nwritten; + } + + BAN::ErrorOr AudioController::ioctl_impl(int cmd, void* arg) + { + switch (cmd) + { + case SND_GET_CHANNELS: + *static_cast(arg) = get_channels(); + return 0; + case SND_GET_SAMPLE_RATE: + *static_cast(arg) = get_sample_rate(); + return 0; + case SND_RESET_BUFFER: + case SND_GET_BUFFERSZ: + { + SpinLockGuard _(m_spinlock); + *static_cast(arg) = m_sample_data_size; + if (cmd == SND_RESET_BUFFER) + m_sample_data_size = 0; + return 0; + } + } + + return CharacterDevice::ioctl_impl(cmd, arg); + } + +} diff --git a/kernel/kernel/PCI.cpp b/kernel/kernel/PCI.cpp index ea68d586..3c386d0c 100644 --- a/kernel/kernel/PCI.cpp +++ b/kernel/kernel/PCI.cpp @@ -1,7 +1,8 @@ #include -#include #include +#include +#include #include #include #include @@ -228,6 +229,18 @@ namespace Kernel::PCI dprintln("{}", res.error()); break; } + case 0x04: + { + switch (pci_device.subclass()) + { + case 0x01: + case 0x03: + if (auto res = AudioController::create(pci_device); res.is_error()) + dprintln("Sound Card: {}", res.error()); + break; + } + break; + } case 0x0C: { switch (pci_device.subclass()) diff --git a/userspace/libraries/LibC/include/sys/ioctl.h b/userspace/libraries/LibC/include/sys/ioctl.h index 74ea0ed0..33a0a781 100644 --- a/userspace/libraries/LibC/include/sys/ioctl.h +++ b/userspace/libraries/LibC/include/sys/ioctl.h @@ -47,6 +47,11 @@ struct winsize #define TIOCGWINSZ 50 #define TIOCSWINSZ 51 +#define SND_GET_CHANNELS 60 /* stores number of channels to uint32_t argument */ +#define SND_GET_SAMPLE_RATE 61 /* stores sample rate to uint32_t argument */ +#define SND_RESET_BUFFER 62 /* stores the size of internal buffer to uint32_t argument and clears the buffer */ +#define SND_GET_BUFFERSZ 63 /* stores the size of internal buffer to uint32_t argument */ + int ioctl(int, int, ...); __END_DECLS