/* * Copyright (c) 2021-2022, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include namespace Kernel { static constexpr int buffer_descriptor_list_max_entries = 32; static constexpr u16 pcm_default_sample_rate = 44100; static constexpr u16 pcm_fixed_sample_rate = 48000; // Valid output range - with double-rate enabled, sample rate can go up to 96kHZ static constexpr u16 pcm_sample_rate_minimum = 8000; static constexpr u16 pcm_sample_rate_maximum = 48000; UNMAP_AFTER_INIT ErrorOr> AC97::create(PCI::DeviceIdentifier const& pci_device_identifier) { auto mixer_io_window = TRY(IOWindow::create_for_pci_device_bar(pci_device_identifier, PCI::HeaderType0BaseRegister::BAR0)); auto bus_io_window = TRY(IOWindow::create_for_pci_device_bar(pci_device_identifier, PCI::HeaderType0BaseRegister::BAR1)); auto pcm_out_channel_io_window = TRY(bus_io_window->create_from_io_window_with_offset(NativeAudioBusChannel::PCMOutChannel)); auto pcm_out_channel = TRY(AC97Channel::create_with_parent_pci_device(pci_device_identifier.address(), "PCMOut"sv, move(pcm_out_channel_io_window))); return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) AC97(pci_device_identifier, move(pcm_out_channel), move(mixer_io_window), move(bus_io_window)))); } UNMAP_AFTER_INIT ErrorOr AC97::probe(PCI::DeviceIdentifier const& device_identifier) { VERIFY(device_identifier.class_code().value() == to_underlying(PCI::ClassID::Multimedia)); return device_identifier.subclass_code().value() == to_underlying(PCI::Multimedia::SubclassID::AudioController); } UNMAP_AFTER_INIT AC97::AC97(PCI::DeviceIdentifier const& pci_device_identifier, NonnullOwnPtr pcm_out_channel, NonnullOwnPtr mixer_io_window, NonnullOwnPtr bus_io_window) : PCI::Device(const_cast(pci_device_identifier)) , IRQHandler(pci_device_identifier.interrupt_line().value()) , m_mixer_io_window(move(mixer_io_window)) , m_bus_io_window(move(bus_io_window)) , m_pcm_out_channel(move(pcm_out_channel)) { } UNMAP_AFTER_INIT AC97::~AC97() = default; bool AC97::handle_irq(RegisterState const&) { auto pcm_out_status = m_pcm_out_channel->io_window().read16(AC97Channel::Register::Status); dbgln_if(AC97_DEBUG, "AC97 @ {}: interrupt received - status: {:#05b}", device_identifier().address(), pcm_out_status); bool is_dma_halted = (pcm_out_status & AudioStatusRegisterFlag::DMAControllerHalted) > 0; bool current_equals_last_valid = (pcm_out_status & AudioStatusRegisterFlag::CurrentEqualsLastValid) > 0; bool is_completion_interrupt = (pcm_out_status & AudioStatusRegisterFlag::BufferCompletionInterruptStatus) > 0; bool is_fifo_error = (pcm_out_status & AudioStatusRegisterFlag::FIFOError) > 0; VERIFY(!is_fifo_error); // If there is no buffer completion, we're not going to do anything if (!is_completion_interrupt) return false; // On interrupt, we need to reset PCM interrupt flags by setting their bits pcm_out_status = AudioStatusRegisterFlag::LastValidBufferCompletionInterrupt | AudioStatusRegisterFlag::BufferCompletionInterruptStatus | AudioStatusRegisterFlag::FIFOError; m_pcm_out_channel->io_window().write16(AC97Channel::Register::Status, pcm_out_status); if (is_dma_halted) { VERIFY(current_equals_last_valid); m_pcm_out_channel->handle_dma_stopped(); } if (!m_irq_queue.is_empty()) m_irq_queue.wake_all(); return true; } UNMAP_AFTER_INIT ErrorOr AC97::initialize(Badge) { dbgln_if(AC97_DEBUG, "AC97 @ {}: mixer base: {:#04x}", device_identifier().address(), m_mixer_io_window); dbgln_if(AC97_DEBUG, "AC97 @ {}: bus base: {:#04x}", device_identifier().address(), m_bus_io_window); m_audio_channel = TRY(AudioChannel::create(*this, 0)); // Read out AC'97 codec revision and vendor auto extended_audio_id = m_mixer_io_window->read16(NativeAudioMixerRegister::ExtendedAudioID); m_codec_revision = static_cast(((extended_audio_id & ExtendedAudioMask::Revision) >> 10) & 0b11); dbgln_if(AC97_DEBUG, "AC97 @ {}: codec revision {:#02b}", device_identifier().address(), to_underlying(m_codec_revision)); if (m_codec_revision == AC97Revision::Reserved) return ENOTSUP; // Report vendor / device ID u32 vendor_id = m_mixer_io_window->read16(NativeAudioMixerRegister::VendorID1) << 16 | m_mixer_io_window->read16(NativeAudioMixerRegister::VendorID2); dmesgln_pci(*this, "Vendor ID: {:#8x}", vendor_id); // Bus cold reset, enable interrupts enable_pin_based_interrupts(); PCI::enable_bus_mastering(device_identifier()); auto control = m_bus_io_window->read32(NativeAudioBusRegister::GlobalControl); control |= GlobalControlFlag::GPIInterruptEnable; control |= GlobalControlFlag::AC97ColdReset; m_bus_io_window->write32(NativeAudioBusRegister::GlobalControl, control); // Reset mixer m_mixer_io_window->write16(NativeAudioMixerRegister::Reset, 1); // Enable variable and double rate PCM audio if supported auto extended_audio_status = m_mixer_io_window->read16(NativeAudioMixerRegister::ExtendedAudioStatusControl); if ((extended_audio_id & ExtendedAudioMask::VariableRatePCMAudio) > 0) { extended_audio_status |= ExtendedAudioStatusControlFlag::VariableRateAudio; m_variable_rate_pcm_supported = true; } if (!m_variable_rate_pcm_supported) { extended_audio_status &= ~ExtendedAudioStatusControlFlag::DoubleRateAudio; } else if ((extended_audio_id & ExtendedAudioMask::DoubleRatePCMAudio) > 0) { extended_audio_status |= ExtendedAudioStatusControlFlag::DoubleRateAudio; m_double_rate_pcm_enabled = true; } m_mixer_io_window->write16(NativeAudioMixerRegister::ExtendedAudioStatusControl, extended_audio_status); TRY(set_pcm_output_sample_rate(m_variable_rate_pcm_supported ? pcm_default_sample_rate : pcm_fixed_sample_rate)); // Left and right volume of 0 means attenuation of 0 dB set_master_output_volume(0, 0, Muted::No); set_pcm_output_volume(0, 0, Muted::No); m_pcm_out_channel->reset(); enable_irq(); return {}; } void AC97::set_master_output_volume(u8 left_channel, u8 right_channel, Muted mute) { u16 volume_value = ((right_channel & 63) << 0) | ((left_channel & 63) << 8) | ((mute == Muted::Yes ? 1 : 0) << 15); m_mixer_io_window->write16(NativeAudioMixerRegister::SetMasterOutputVolume, volume_value); } ErrorOr AC97::set_pcm_output_sample_rate(u32 sample_rate) { if (m_sample_rate == sample_rate) return {}; auto const double_rate_shift = m_double_rate_pcm_enabled ? 1 : 0; auto shifted_sample_rate = sample_rate >> double_rate_shift; if (!m_variable_rate_pcm_supported && shifted_sample_rate != pcm_fixed_sample_rate) return ENOTSUP; if (shifted_sample_rate < pcm_sample_rate_minimum || shifted_sample_rate > pcm_sample_rate_maximum) return ENOTSUP; m_mixer_io_window->write16(NativeAudioMixerRegister::PCMFrontDACRate, shifted_sample_rate); m_sample_rate = static_cast(m_mixer_io_window->read16(NativeAudioMixerRegister::PCMFrontDACRate)) << double_rate_shift; dmesgln_pci(*this, "PCM front DAC rate set to {} Hz", m_sample_rate); // Setting the sample rate stops a running DMA engine, so restart it if (m_pcm_out_channel->dma_running()) m_pcm_out_channel->start_dma(); return {}; } void AC97::set_pcm_output_volume(u8 left_channel, u8 right_channel, Muted mute) { u16 volume_value = ((right_channel & 31) << 0) | ((left_channel & 31) << 8) | ((mute == Muted::Yes ? 1 : 0) << 15); m_mixer_io_window->write16(NativeAudioMixerRegister::SetPCMOutputVolume, volume_value); } RefPtr AC97::audio_channel(u32 index) const { if (index == 0) return m_audio_channel; return {}; } ErrorOr AC97::set_pcm_output_sample_rate(size_t channel_index, u32 samples_per_second_rate) { if (channel_index != 0) return ENODEV; TRY(set_pcm_output_sample_rate(samples_per_second_rate)); return {}; } ErrorOr AC97::get_pcm_output_sample_rate(size_t channel_index) { if (channel_index != 0) return Error::from_errno(ENODEV); return m_sample_rate; } ErrorOr AC97::write(size_t channel_index, UserOrKernelBuffer const& data, size_t length) { if (channel_index != 0) return Error::from_errno(ENODEV); if (!m_output_buffer) m_output_buffer = TRY(MM.allocate_dma_buffer_pages(m_output_buffer_page_count * PAGE_SIZE, "AC97 Output buffer"sv, Memory::Region::Access::Write)); if (!m_buffer_descriptor_list) { size_t buffer_descriptor_list_size = buffer_descriptor_list_max_entries * sizeof(BufferDescriptorListEntry); buffer_descriptor_list_size = TRY(Memory::page_round_up(buffer_descriptor_list_size)); m_buffer_descriptor_list = TRY(MM.allocate_dma_buffer_pages(buffer_descriptor_list_size, "AC97 Buffer Descriptor List"sv, Memory::Region::Access::Write)); } Checked remaining = length; size_t offset = 0; while (remaining > static_cast(0)) { TRY(write_single_buffer(data, offset, min(remaining.value(), PAGE_SIZE))); offset += PAGE_SIZE; remaining.saturating_sub(PAGE_SIZE); } return length; } ErrorOr AC97::write_single_buffer(UserOrKernelBuffer const& data, size_t offset, size_t length) { VERIFY(length <= PAGE_SIZE); { // Block until we can write into an unused buffer InterruptDisabler disabler; do { auto pcm_out_status = m_pcm_out_channel->io_window().read16(AC97Channel::Register::Status); auto current_index = m_pcm_out_channel->io_window().read8(AC97Channel::Register::CurrentIndexValue); int last_valid_index = m_pcm_out_channel->io_window().read8(AC97Channel::Register::LastValidIndex); auto head_distance = last_valid_index - current_index; if (head_distance < 0) head_distance += buffer_descriptor_list_max_entries; if (m_pcm_out_channel->dma_running()) ++head_distance; // Current index has _passed_ last valid index - move our list index up if (head_distance > m_output_buffer_page_count) { m_buffer_descriptor_list_index = current_index + 1; break; } // There is room for our data if (head_distance < m_output_buffer_page_count) break; dbgln_if(AC97_DEBUG, "AC97 @ {}: waiting on interrupt - status: {:#05b} CI: {} LVI: {}", device_identifier().address(), pcm_out_status, current_index, last_valid_index); m_irq_queue.wait_forever("AC97"sv); } while (m_pcm_out_channel->dma_running()); } // 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(), offset, length)); // Write the next entry to the buffer descriptor list u16 number_of_samples = length / sizeof(u16); auto list_entries = reinterpret_cast(m_buffer_descriptor_list->vaddr().get()); auto list_entry = &list_entries[m_buffer_descriptor_list_index]; list_entry->buffer_pointer = static_cast(m_output_buffer->physical_page(m_output_buffer_page_index)->paddr().get()); list_entry->control_and_length = number_of_samples | BufferDescriptorListEntryFlags::InterruptOnCompletion; auto buffer_address = static_cast(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; m_buffer_descriptor_list_index = (m_buffer_descriptor_list_index + 1) % buffer_descriptor_list_max_entries; return {}; } ErrorOr> AC97::AC97Channel::create_with_parent_pci_device(PCI::Address pci_device_address, StringView name, NonnullOwnPtr channel_io_base) { return adopt_nonnull_own_or_enomem(new (nothrow) AC97::AC97Channel(pci_device_address, name, move(channel_io_base))); } void AC97::AC97Channel::handle_dma_stopped() { dbgln_if(AC97_DEBUG, "AC97 @ {}: channel {}: DMA engine has stopped", m_device_pci_address, name()); m_dma_running.with([this](auto& dma_running) { // NOTE: QEMU might send spurious interrupts while we're not running, so we don't want to panic here. if (!dma_running) dbgln("AC97 @ {}: received DMA interrupt while it wasn't running", m_device_pci_address); dma_running = false; }); } void AC97::AC97Channel::reset() { dbgln_if(AC97_DEBUG, "AC97 @ {}: channel {}: resetting", m_device_pci_address, name()); m_channel_io_window->write8(Register::Control, AudioControlRegisterFlag::ResetRegisters); while ((m_channel_io_window->read8(Register::Control) & AudioControlRegisterFlag::ResetRegisters) > 0) microseconds_delay(50); m_dma_running.with([](auto& dma_running) { dma_running = false; }); } void AC97::AC97Channel::set_last_valid_index(u32 buffer_address, u8 last_valid_index) { dbgln_if(AC97_DEBUG, "AC97 @ {}: channel {}: setting buffer address: {:#x} LVI: {}", m_device_pci_address, name(), buffer_address, last_valid_index); m_channel_io_window->write32(Register::BufferDescriptorListBaseAddress, buffer_address); m_channel_io_window->write8(Register::LastValidIndex, last_valid_index); } void AC97::AC97Channel::start_dma() { dbgln_if(AC97_DEBUG, "AC97 @ {}: channel {}: starting DMA engine", m_device_pci_address, name()); auto control = m_channel_io_window->read8(Register::Control); control |= AudioControlRegisterFlag::RunPauseBusMaster; control |= AudioControlRegisterFlag::FIFOErrorInterruptEnable; control |= AudioControlRegisterFlag::InterruptOnCompletionEnable; m_channel_io_window->write8(Register::Control, control); m_dma_running.with([](auto& dma_running) { dma_running = true; }); } }