/*
* Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Devices/Audio/IntelHDA/Timing.h>
#include <Kernel/Library/IOWindow.h>
namespace Kernel::Audio::IntelHDA {
enum class RingBufferType {
Input,
Output,
};
// 4.4.1, 4.4.2: CORB and RIRB
template<typename T, RingBufferType U>
class ControllerRingBuffer {
public:
ControllerRingBuffer(size_t capacity, NonnullOwnPtr<Memory::Region> buffer, NonnullOwnPtr<IOWindow> 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<NonnullOwnPtr<ControllerRingBuffer>> create(StringView name, NonnullOwnPtr<IOWindow> 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));
// FIXME: Synchronize DMA buffer accesses correctly and set the MemoryType to NonCacheable.
auto buffer_region = TRY(MM.allocate_dma_buffer_page(name, U == RingBufferType::Input ? Memory::Region::Access::Read : Memory::Region::Access::Write, Memory::MemoryType::IO));
// 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<Optional<T>> 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<T> {};
m_current_pointer = (m_current_pointer + 1) % m_capacity;
return Optional<T> { *(reinterpret_cast<T*>(m_buffer->vaddr().get()) + m_current_pointer) };
}
// 4.4.1.3, 4.4.2.2: Initializing the CORB/RIRB
ErrorOr<void> 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<u8>(~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<void> 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<T*>(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<void> 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<void> 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<Memory::Region> m_buffer;
NonnullOwnPtr<IOWindow> m_register_window;
bool m_running { false };
u8 m_current_pointer { 0 };
};
using CommandOutboundRingBuffer = ControllerRingBuffer<u32, RingBufferType::Output>;
using ResponseInboundRingBuffer = ControllerRingBuffer<u64, RingBufferType::Input>;
}