Kernel: Implement basic AC97 driver

This commit is contained in:
Bananymous 2025-07-10 17:29:11 +03:00
parent 674e194a91
commit 8a663cb94f
9 changed files with 506 additions and 1 deletions

View File

@ -6,6 +6,8 @@ set(KERNEL_SOURCES
kernel/ACPI/AML/OpRegion.cpp kernel/ACPI/AML/OpRegion.cpp
kernel/ACPI/BatterySystem.cpp kernel/ACPI/BatterySystem.cpp
kernel/APIC.cpp kernel/APIC.cpp
kernel/Audio/AC97/Controller.cpp
kernel/Audio/Controller.cpp
kernel/BootInfo.cpp kernel/BootInfo.cpp
kernel/CPUID.cpp kernel/CPUID.cpp
kernel/Credentials.cpp kernel/Credentials.cpp

View File

@ -0,0 +1,51 @@
#pragma once
#include <kernel/Audio/Controller.h>
#include <kernel/Memory/DMARegion.h>
namespace Kernel
{
class AC97AudioController : public AudioController, public Interruptable
{
public:
static BAN::ErrorOr<BAN::RefPtr<AC97AudioController>> 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<void> initialize();
BAN::ErrorOr<void> initialize_bld();
BAN::ErrorOr<void> 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<PCI::BarRegion> m_mixer;
BAN::UniqPtr<PCI::BarRegion> m_bus_master;
BAN::UniqPtr<DMARegion> m_bdl_region;
uint32_t m_bdl_tail { 0 };
uint32_t m_bdl_head { 0 };
};
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
namespace Kernel::AC97
{
struct BufferDescriptorListEntry
{
uint32_t address;
uint16_t samples;
uint16_t flags; // bit 14: last entry, bit 15: IOC
};
}

View File

@ -0,0 +1,48 @@
#pragma once
#include <kernel/Device/Device.h>
#include <kernel/PCI.h>
namespace Kernel
{
class AudioController : public CharacterDevice
{
public:
static BAN::ErrorOr<BAN::RefPtr<AudioController>> 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<size_t> write_impl(off_t, BAN::ConstByteSpan) override;
BAN::ErrorOr<long> 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] {};
};
}

View File

@ -22,6 +22,7 @@ namespace Kernel
Ethernet, Ethernet,
Loopback, Loopback,
TmpFS, TmpFS,
AudioController,
}; };
} }

View File

@ -0,0 +1,265 @@
#include <kernel/Audio/AC97/Controller.h>
#include <kernel/Audio/AC97/Definitions.h>
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<BAN::RefPtr<AC97AudioController>> 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<AC97AudioController>::adopt(ac97_ptr);
TRY(ac97->initialize());
return ac97;
}
BAN::ErrorOr<void> 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<void> 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<void*>(m_bdl_region->vaddr()), 0x00, m_bdl_region->size());
for (size_t i = 0; i < m_bdl_entries; i++)
{
auto& entry = reinterpret_cast<AC97::BufferDescriptorListEntry*>(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<void> 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<AC97::BufferDescriptorListEntry*>(m_bdl_region->vaddr())[m_bdl_head];
entry.samples = samples;
entry.flags = (1 << 15);
memcpy(
reinterpret_cast<void*>(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();
}
}
}

View File

@ -0,0 +1,105 @@
#include <kernel/Audio/AC97/Controller.h>
#include <kernel/Audio/Controller.h>
#include <kernel/Device/DeviceNumbers.h>
#include <kernel/FS/DevFS/FileSystem.h>
#include <kernel/Lock/SpinLockAsMutex.h>
#include <sys/ioctl.h>
#include <sys/sysmacros.h>
namespace Kernel
{
static BAN::Atomic<dev_t> 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<BAN::RefPtr<AudioController>> 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<AudioController>(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<size_t> 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<long> AudioController::ioctl_impl(int cmd, void* arg)
{
switch (cmd)
{
case SND_GET_CHANNELS:
*static_cast<uint32_t*>(arg) = get_channels();
return 0;
case SND_GET_SAMPLE_RATE:
*static_cast<uint32_t*>(arg) = get_sample_rate();
return 0;
case SND_RESET_BUFFER:
case SND_GET_BUFFERSZ:
{
SpinLockGuard _(m_spinlock);
*static_cast<uint32_t*>(arg) = m_sample_data_size;
if (cmd == SND_RESET_BUFFER)
m_sample_data_size = 0;
return 0;
}
}
return CharacterDevice::ioctl_impl(cmd, arg);
}
}

View File

@ -1,7 +1,8 @@
#include <BAN/ScopeGuard.h> #include <BAN/ScopeGuard.h>
#include <kernel/APIC.h>
#include <kernel/ACPI/ACPI.h> #include <kernel/ACPI/ACPI.h>
#include <kernel/APIC.h>
#include <kernel/Audio/Controller.h>
#include <kernel/IDT.h> #include <kernel/IDT.h>
#include <kernel/IO.h> #include <kernel/IO.h>
#include <kernel/Memory/PageTable.h> #include <kernel/Memory/PageTable.h>
@ -228,6 +229,18 @@ namespace Kernel::PCI
dprintln("{}", res.error()); dprintln("{}", res.error());
break; 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: case 0x0C:
{ {
switch (pci_device.subclass()) switch (pci_device.subclass())

View File

@ -47,6 +47,11 @@ struct winsize
#define TIOCGWINSZ 50 #define TIOCGWINSZ 50
#define TIOCSWINSZ 51 #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, ...); int ioctl(int, int, ...);
__END_DECLS __END_DECLS