1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 05:54:58 +00:00
serenity/Kernel/Devices/Audio/IntelHDA/RingBuffer.h
Liav A 7c0540a229 Everywhere: Move global Kernel pattern code to Kernel/Library directory
This has KString, KBuffer, DoubleBuffer, KBufferBuilder, IOWindow,
UserOrKernelBuffer and ScopedCritical classes being moved to the
Kernel/Library subdirectory.

Also, move the panic and assertions handling code to that directory.
2023-06-04 21:32:34 +02:00

250 lines
9.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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));
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<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>;
}