Kernel: Add generic channel support to AC97

This factors out some hardcoded PCMOut registers into a new private
class called AC97Channel, which wraps around a channel's registers and
provides some shared functionality.

No functional changes.
This commit is contained in:
Jelle Raaijmakers 2021-11-23 12:36:32 +01:00 committed by Andreas Kling
parent d117c4cccc
commit 70ca8b24dc
2 changed files with 90 additions and 48 deletions

View file

@ -43,6 +43,7 @@ UNMAP_AFTER_INIT AC97::AC97(PCI::DeviceIdentifier pci_device_identifier)
, CharacterDevice(42, 42)
, m_io_mixer_base(PCI::get_BAR0(pci_address()) & ~1)
, m_io_bus_base(PCI::get_BAR1(pci_address()) & ~1)
, m_pcm_out_channel(channel("PCMOut"sv, NativeAudioBusChannel::PCMOutChannel))
{
initialize();
}
@ -53,7 +54,7 @@ UNMAP_AFTER_INIT AC97::~AC97()
bool AC97::handle_irq(RegisterState const&)
{
auto pcm_out_status_register = m_io_bus_base.offset(NativeAudioBusRegister::PCMOutStatus);
auto pcm_out_status_register = m_pcm_out_channel.reg(AC97Channel::Register::Status);
auto pcm_out_status = pcm_out_status_register.in<u16>();
bool is_dma_halted = (pcm_out_status & AudioStatusRegisterFlag::DMAControllerHalted) > 0;
@ -138,16 +139,8 @@ ErrorOr<size_t> AC97::read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_
void AC97::reset_pcm_out()
{
auto pcm_out_control_register = m_io_bus_base.offset(NativeAudioBusRegister::PCMOutControl);
pcm_out_control_register.out(AudioControlRegisterFlag::ResetRegisters);
while ((pcm_out_control_register.in<u8>() & AudioControlRegisterFlag::ResetRegisters) > 0)
IO::delay(50);
m_output_buffer_dma_running = false;
m_pcm_out_channel.reset();
m_buffer_descriptor_list_index = 0;
dbgln("AC97 @ {}: PCM out is reset", pci_address());
}
void AC97::set_master_output_volume(u8 left_channel, u8 right_channel, Muted mute)
@ -158,13 +151,6 @@ void AC97::set_master_output_volume(u8 left_channel, u8 right_channel, Muted mut
m_io_mixer_base.offset(NativeAudioMixerRegister::SetMasterOutputVolume).out(volume_value);
}
void AC97::set_output_buffer_dma_lvi(u8 last_valid_index)
{
auto buffer_address = static_cast<u32>(m_buffer_descriptor_list->physical_page(0)->paddr().get());
m_io_bus_base.offset(NativeAudioBusRegister::PCMOutBufferDescriptorListBaseAddress).out(buffer_address);
m_io_bus_base.offset(NativeAudioBusRegister::PCMOutLastValidIndex).out<u8>(last_valid_index);
}
void AC97::set_pcm_output_sample_rate(u16 sample_rate)
{
auto pcm_front_dac_rate_register = m_io_mixer_base.offset(NativeAudioMixerRegister::PCMFrontDACRate);
@ -181,20 +167,6 @@ void AC97::set_pcm_output_volume(u8 left_channel, u8 right_channel, Muted mute)
m_io_mixer_base.offset(NativeAudioMixerRegister::SetPCMOutputVolume).out(volume_value);
}
void AC97::start_output_buffer_dma()
{
dbgln("AC97 @ {}: starting DMA engine", pci_address());
auto pcm_out_control_register = m_io_bus_base.offset(NativeAudioBusRegister::PCMOutControl);
auto pcm_out_control = pcm_out_control_register.in<u8>();
pcm_out_control |= AudioControlRegisterFlag::RunPauseBusMaster;
pcm_out_control |= AudioControlRegisterFlag::FIFOErrorInterruptEnable;
pcm_out_control |= AudioControlRegisterFlag::InterruptOnCompletionEnable;
pcm_out_control_register.out(pcm_out_control);
m_output_buffer_dma_running = true;
}
ErrorOr<size_t> AC97::write(OpenFileDescription&, u64, UserOrKernelBuffer const& data, size_t length)
{
VERIFY(length <= PAGE_SIZE);
@ -211,10 +183,10 @@ ErrorOr<size_t> AC97::write(OpenFileDescription&, u64, UserOrKernelBuffer const&
// Block until we can write into an unused buffer
do {
auto pcm_out_status = m_io_bus_base.offset(NativeAudioBusRegister::PCMOutStatus).in<u16>();
auto pcm_out_status = m_pcm_out_channel.reg(AC97Channel::Register::Status).in<u16>();
auto is_dma_controller_halted = (pcm_out_status & AudioStatusRegisterFlag::DMAControllerHalted) > 0;
auto current_index = m_io_bus_base.offset(NativeAudioBusRegister::PCMOutCurrentIndex).in<u8>();
auto last_valid_index = m_io_bus_base.offset(NativeAudioBusRegister::PCMOutLastValidIndex).in<u8>();
auto current_index = m_pcm_out_channel.reg(AC97Channel::Register::CurrentIndexValue).in<u8>();
auto last_valid_index = m_pcm_out_channel.reg(AC97Channel::Register::LastValidIndex).in<u8>();
auto head_distance = static_cast<int>(last_valid_index) - current_index;
if (head_distance < 0)
@ -226,14 +198,14 @@ ErrorOr<size_t> AC97::write(OpenFileDescription&, u64, UserOrKernelBuffer const&
break;
m_irq_queue.wait_forever("AC97"sv);
} while (m_output_buffer_dma_running);
} while (m_pcm_out_channel.dma_running());
sti();
// Copy data from userspace into one of our buffers
TRY(data.read(m_output_buffer->vaddr_from_page_index(m_output_buffer_page_index).as_ptr(), length));
if (!m_output_buffer_dma_running) {
if (!m_pcm_out_channel.dma_running()) {
reset_pcm_out();
}
@ -243,10 +215,12 @@ ErrorOr<size_t> AC97::write(OpenFileDescription&, u64, UserOrKernelBuffer const&
auto list_entry = &list_entries[m_buffer_descriptor_list_index];
list_entry->buffer_pointer = static_cast<u32>(m_output_buffer->physical_page(m_output_buffer_page_index)->paddr().get());
list_entry->control_and_length = number_of_samples | BufferDescriptorListEntryFlags::InterruptOnCompletion;
set_output_buffer_dma_lvi(m_buffer_descriptor_list_index);
if (!m_output_buffer_dma_running) {
start_output_buffer_dma();
auto buffer_address = static_cast<u32>(m_buffer_descriptor_list->physical_page(0)->paddr().get());
m_pcm_out_channel.set_last_valid_index(buffer_address, m_buffer_descriptor_list_index);
if (!m_pcm_out_channel.dma_running()) {
m_pcm_out_channel.start_dma();
}
m_output_buffer_page_index = (m_output_buffer_page_index + 1) % m_output_buffer_page_count;
@ -255,4 +229,37 @@ ErrorOr<size_t> AC97::write(OpenFileDescription&, u64, UserOrKernelBuffer const&
return length;
}
void AC97::AC97Channel::reset()
{
dbgln("AC97 @ {}: channel {}: resetting", m_device.pci_address(), name());
auto control_register = reg(Register::Control);
control_register.out(AudioControlRegisterFlag::ResetRegisters);
while ((control_register.in<u8>() & AudioControlRegisterFlag::ResetRegisters) > 0)
IO::delay(50);
m_dma_running = false;
}
void AC97::AC97Channel::set_last_valid_index(u32 buffer_address, u8 last_valid_index)
{
reg(Register::BufferDescriptorListBaseAddress).out(buffer_address);
reg(Register::LastValidIndex).out(last_valid_index);
}
void AC97::AC97Channel::start_dma()
{
dbgln("AC97 @ {}: channel {}: starting DMA engine", m_device.pci_address(), name());
auto control_register = reg(Register::Control);
auto control = control_register.in<u8>();
control |= AudioControlRegisterFlag::RunPauseBusMaster;
control |= AudioControlRegisterFlag::FIFOErrorInterruptEnable;
control |= AudioControlRegisterFlag::InterruptOnCompletionEnable;
control_register.out(control);
m_dma_running = true;
}
}

View file

@ -57,13 +57,16 @@ private:
Revision23 = 0b10,
};
enum NativeAudioBusChannel : u8 {
PCMInChannel = 0x00,
PCMOutChannel = 0x10,
MicrophoneInChannel = 0x20,
Microphone2Channel = 0x40,
PCMIn2Channel = 0x50,
SPDIFChannel = 0x60,
};
enum NativeAudioBusRegister : u8 {
PCMOutBufferDescriptorListBaseAddress = 0x10,
PCMOutCurrentIndex = 0x14,
PCMOutLastValidIndex = 0x15,
PCMOutStatus = 0x16,
PCMOutPrefetchedIndex = 0x1a,
PCMOutControl = 0x1b,
GlobalControl = 0x2c,
};
@ -102,6 +105,39 @@ private:
InterruptOnCompletion = 1u << 31,
};
class AC97Channel {
public:
enum Register : u8 {
BufferDescriptorListBaseAddress = 0x00,
CurrentIndexValue = 0x04,
LastValidIndex = 0x05,
Status = 0x06,
PositionInCurrentBuffer = 0x08,
PrefetchedIndexValue = 0x0a,
Control = 0x0b,
};
AC97Channel(AC97& device, StringView name, IOAddress channel_base)
: m_channel_base(channel_base)
, m_device(device)
, m_name(name)
{
}
bool dma_running() const { return m_dma_running; }
StringView name() const { return m_name; }
IOAddress reg(Register reg) const { return m_channel_base.offset(reg); }
void reset();
void set_last_valid_index(u32 buffer_address, u8 last_valid_index);
void start_dma();
private:
IOAddress m_channel_base;
AC97& m_device;
bool m_dma_running = false;
StringView m_name;
};
AC97(PCI::DeviceIdentifier);
// ^IRQHandler
@ -110,13 +146,12 @@ private:
// ^CharacterDevice
virtual StringView class_name() const override { return "AC97"sv; }
AC97Channel channel(StringView name, NativeAudioBusChannel channel) { return AC97Channel(*this, name, m_io_bus_base.offset(channel)); }
void initialize();
void reset_pcm_out();
void set_master_output_volume(u8, u8, Muted);
void set_pcm_output_sample_rate(u16);
void set_pcm_output_volume(u8, u8, Muted);
void set_output_buffer_dma_lvi(u8);
void start_output_buffer_dma();
OwnPtr<Memory::Region> m_buffer_descriptor_list;
u8 m_buffer_descriptor_list_index = 0;
@ -124,9 +159,9 @@ private:
IOAddress m_io_bus_base;
WaitQueue m_irq_queue;
OwnPtr<Memory::Region> m_output_buffer;
bool m_output_buffer_dma_running = false;
u8 m_output_buffer_page_count = 4;
u8 m_output_buffer_page_index = 0;
AC97Channel m_pcm_out_channel;
u16 m_sample_rate = 44100;
};