/* * Copyright (c) 2023, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include namespace Kernel::Audio::IntelHDA { enum class RingBufferType { Input, Output, }; // 4.4.1, 4.4.2: CORB and RIRB template class ControllerRingBuffer { public: ControllerRingBuffer(size_t capacity, NonnullOwnPtr buffer, NonnullOwnPtr register_window) : m_capacity(capacity) , m_buffer(move(buffer)) , m_register_window(move(register_window)) { // 3.3.22, 3.3.29: Read DMA engine running bit u8 control = m_register_window->read8(RingBufferRegisterOffset::Control); m_running = (control & RingBufferControlFlag::DMAEnable) > 0; } static ErrorOr> create(StringView name, NonnullOwnPtr register_window) { // 3.3.24, 3.3.31: Read the size capability auto buffer_size = register_window->read8(RingBufferRegisterOffset::Size); u8 size_capability = buffer_size >> 4; size_t capacity = ((size_capability & SizeCapabilityFlag::Supports2) > 0) ? 2 : 0; if ((size_capability & SizeCapabilityFlag::Supports16) > 0) capacity = 16; if ((size_capability & SizeCapabilityFlag::Supports256) > 0) capacity = 256; if (capacity == 0) return Error::from_string_view_or_print_error_and_return_errno("RingBuffer reports invalid capacity"sv, ENOTSUP); // Create a DMA buffer page to holds the ring buffer VERIFY(PAGE_SIZE >= capacity * sizeof(T)); auto buffer_region = TRY(MM.allocate_dma_buffer_page(name, U == RingBufferType::Input ? Memory::Region::Access::Read : Memory::Region::Access::Write)); // 4.4.1.1, 4.4.2: The CORB buffer in memory must be allocated to start on a 128-byte boundary // and in memory configured to match the access type being used. VERIFY((buffer_region->physical_page(0)->paddr().get() & 0x7f) == 0); return adopt_nonnull_own_or_enomem(new (nothrow) ControllerRingBuffer(capacity, move(buffer_region), move(register_window))); } size_t capacity() const { return m_capacity; } ErrorOr> read_value() requires(U == RingBufferType::Input) { // 4.4.2: Response Inbound Ring Buffer - RIRB auto write_pointer = controller_pointer(); dbgln_if(INTEL_HDA_DEBUG, "ControllerRingBuffer({}) {}: current_pointer {} write_pointer {}", U == RingBufferType::Input ? "input"sv : "output"sv, __FUNCTION__, m_current_pointer, write_pointer); if (m_current_pointer == write_pointer) return Optional {}; m_current_pointer = (m_current_pointer + 1) % m_capacity; return Optional { *(reinterpret_cast(m_buffer->vaddr().get()) + m_current_pointer) }; } // 4.4.1.3, 4.4.2.2: Initializing the CORB/RIRB ErrorOr register_with_controller() { // 4.4.1.3, 4.4.2.2: Stop DMA engine TRY(set_dma_engine_running(false)); // 3.3.18, 3.3.19, 3.3.25, 3.3.26, 4.4.1.3: Set base address PhysicalPtr buffer_address = m_buffer->physical_page(0)->paddr().get(); m_register_window->write32(RingBufferRegisterOffset::LowerBaseAddress, buffer_address & 0xffffff80u); if constexpr (sizeof(PhysicalPtr) == 8) m_register_window->write32(RingBufferRegisterOffset::UpperBaseAddress, buffer_address >> 32); // 3.3.24, 3.3.31, 4.4.1.3: Set buffer capacity if more than one capacity is supported auto buffer_size = m_register_window->read8(RingBufferRegisterOffset::Size) & static_cast(~0b11); u8 size_capability = buffer_size >> 4; if (popcount(size_capability) > 1) { switch (m_capacity) { case 2: break; case 16: buffer_size |= 0b01; break; case 256: buffer_size |= 0b10; break; default: VERIFY_NOT_REACHED(); } m_register_window->write8(RingBufferRegisterOffset::Size, buffer_size); } // 4.4.1.3: Reset read and write pointers to 0 TRY(reset_controller_pointer()); if constexpr (U == RingBufferType::Output) set_write_pointer(0); // FIXME: Qemu's Intel HDA device compares the RINTCNT register with the number of responses sent, even // if interrupts are disabled. This is a workaround and allows us to receive 255 responses. We // should try to fix this upstream or toggle this fix with device quirks logic. if constexpr (U == RingBufferType::Input) m_register_window->write16(RingBufferRegisterOffset::ResponseInterruptCount, 0xff); TRY(set_dma_engine_running(true)); return {}; } ErrorOr write_value(T value) requires(U == RingBufferType::Output) { // 4.4.1.4: Transmitting Commands via the CORB auto read_pointer = controller_pointer(); auto write_pointer = (m_current_pointer + 1) % m_capacity; dbgln_if(INTEL_HDA_DEBUG, "ControllerRingBuffer({}) {}: read_pointer {} write_pointer {}", U == RingBufferType::Input ? "input"sv : "output"sv, __FUNCTION__, read_pointer, write_pointer); if (write_pointer == read_pointer) return ENOSPC; auto* target_slot = reinterpret_cast(m_buffer->vaddr().get()) + write_pointer; *target_slot = value; set_write_pointer(write_pointer); return {}; } private: // 3.3: High Definition Audio Controller Register Set - CORB/RIRB enum RingBufferRegisterOffset : u8 { LowerBaseAddress = 0x0, UpperBaseAddress = 0x4, WritePointer = 0x8, ReadPointer = 0xa, ResponseInterruptCount = 0xa, Control = 0xc, Status = 0xd, Size = 0xe, }; // 3.3.21, 3.3.27: Read/Write Pointer enum PointerFlag : u16 { Reset = 1u << 15, }; // 3.3.22, 3.3.29: Ring Buffer Control enum RingBufferControlFlag : u8 { DMAEnable = 1u << 1, }; // 3.3.24, 3.3.31: Size enum SizeCapabilityFlag : u8 { Supports2 = 1u << 0, Supports16 = 1u << 1, Supports256 = 1u << 2, }; u8 controller_pointer() { // 3.3.21, 3.3.27: Get the Read/Write pointer auto offset = U == RingBufferType::Input ? RingBufferRegisterOffset::WritePointer : RingBufferRegisterOffset::ReadPointer; return m_register_window->read16(offset) & 0xffu; } ErrorOr reset_controller_pointer() { // 3.3.21, 3.3.27: Set the Read/Write pointer reset bit auto offset = U == RingBufferType::Input ? RingBufferRegisterOffset::WritePointer : RingBufferRegisterOffset::ReadPointer; m_register_window->write16(offset, PointerFlag::Reset); if constexpr (U == RingBufferType::Output) { // 3.3.21: "The hardware will physically update this bit to 1 when the CORB pointer reset is // complete. Software must read a 1 to verify that the reset completed correctly." TRY(wait_until(frame_delay_in_microseconds(1), controller_timeout_in_microseconds, [&]() { u16 read_pointer = m_register_window->read16(offset); return (read_pointer & PointerFlag::Reset) > 0; })); // 3.3.21: "Software must clear this bit back to 0, by writing a 0, and then read back the 0 // to verify that the clear completed correctly." m_register_window->write16(offset, 0); TRY(wait_until(frame_delay_in_microseconds(1), controller_timeout_in_microseconds, [&]() { u16 read_pointer = m_register_window->read16(offset); return (read_pointer & PointerFlag::Reset) == 0; })); } dbgln_if(INTEL_HDA_DEBUG, "ControllerRingBuffer({}) {}", U == RingBufferType::Input ? "input"sv : "output"sv, __FUNCTION__); return {}; } ErrorOr set_dma_engine_running(bool running) { if (m_running == running) return {}; // 3.3.22, 3.3.29: Set DMA engine running bit u8 control = m_register_window->read8(RingBufferRegisterOffset::Control); if (running) control |= RingBufferControlFlag::DMAEnable; else control &= ~RingBufferControlFlag::DMAEnable; dbgln_if(INTEL_HDA_DEBUG, "ControllerRingBuffer({}) {}: {:#08b}", U == RingBufferType::Input ? "input"sv : "output"sv, __FUNCTION__, control); m_register_window->write8(RingBufferRegisterOffset::Control, control); // Must read the value back TRY(wait_until(frame_delay_in_microseconds(1), controller_timeout_in_microseconds, [&]() { control = m_register_window->read8(RingBufferRegisterOffset::Control); return (control & RingBufferControlFlag::DMAEnable) == (running ? RingBufferControlFlag::DMAEnable : 0); })); m_running = running; return {}; } void set_write_pointer(u8 pointer) requires(U == RingBufferType::Output) { // 3.3.20: CORBWP – CORB Write Pointer m_register_window->write16(RingBufferRegisterOffset::WritePointer, pointer); m_current_pointer = pointer; } size_t m_capacity; NonnullOwnPtr m_buffer; NonnullOwnPtr m_register_window; bool m_running { false }; u8 m_current_pointer { 0 }; }; using CommandOutboundRingBuffer = ControllerRingBuffer; using ResponseInboundRingBuffer = ControllerRingBuffer; }