123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- /*
- * Copyright (c) 2021-2022, Jelle Raaijmakers <jelle@gmta.nl>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <AK/Format.h>
- #include <Kernel/Arch/Delay.h>
- #include <Kernel/Devices/Audio/AC97.h>
- #include <Kernel/Devices/DeviceManagement.h>
- #include <Kernel/InterruptDisabler.h>
- #include <Kernel/Memory/AnonymousVMObject.h>
- 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<NonnullLockRefPtr<AC97>> AC97::try_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)));
- auto ac97 = adopt_nonnull_lock_ref_or_enomem(new (nothrow) AC97(pci_device_identifier, move(pcm_out_channel), move(mixer_io_window), move(bus_io_window)));
- if (!ac97.is_error())
- TRY(ac97.value()->initialize());
- return ac97;
- }
- UNMAP_AFTER_INIT AC97::AC97(PCI::DeviceIdentifier const& pci_device_identifier, NonnullOwnPtr<AC97Channel> pcm_out_channel, NonnullOwnPtr<IOWindow> mixer_io_window, NonnullOwnPtr<IOWindow> bus_io_window)
- : PCI::Device(const_cast<PCI::DeviceIdentifier&>(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<void> AC97::initialize()
- {
- 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);
- // Read out AC'97 codec revision and vendor
- auto extended_audio_id = m_mixer_io_window->read16(NativeAudioMixerRegister::ExtendedAudioID);
- m_codec_revision = static_cast<AC97Revision>(((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<void> 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<u32>(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);
- }
- LockRefPtr<AudioChannel> AC97::audio_channel(u32 index) const
- {
- if (index == 0)
- return m_audio_channel;
- return {};
- }
- void AC97::detect_hardware_audio_channels(Badge<AudioManagement>)
- {
- m_audio_channel = AudioChannel::must_create(*this, 0);
- }
- ErrorOr<void> 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<u32> AC97::get_pcm_output_sample_rate(size_t channel_index)
- {
- if (channel_index != 0)
- return Error::from_errno(ENODEV);
- return m_sample_rate;
- }
- ErrorOr<size_t> 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<size_t> remaining = length;
- size_t offset = 0;
- while (remaining > static_cast<size_t>(0)) {
- TRY(write_single_buffer(data, offset, min(remaining.value(), PAGE_SIZE)));
- offset += PAGE_SIZE;
- remaining.saturating_sub(PAGE_SIZE);
- }
- return length;
- }
- ErrorOr<void> 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<BufferDescriptorListEntry*>(m_buffer_descriptor_list->vaddr().get());
- 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;
- 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;
- m_buffer_descriptor_list_index = (m_buffer_descriptor_list_index + 1) % buffer_descriptor_list_max_entries;
- return {};
- }
- ErrorOr<NonnullOwnPtr<AC97::AC97Channel>> AC97::AC97Channel::create_with_parent_pci_device(PCI::Address pci_device_address, StringView name, NonnullOwnPtr<IOWindow> 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;
- });
- }
- }
|