Files
banan-os/kernel/kernel/Audio/HDAudio/Controller.cpp

501 lines
14 KiB
C++

#include <kernel/Audio/HDAudio/AudioFunctionGroup.h>
#include <kernel/Audio/HDAudio/Controller.h>
#include <kernel/Audio/HDAudio/Registers.h>
#include <kernel/Lock/LockGuard.h>
#include <kernel/Lock/SpinLockAsMutex.h>
#include <kernel/MMIO.h>
#include <kernel/Timer/Timer.h>
namespace Kernel
{
BAN::ErrorOr<void> HDAudioController::create(PCI::Device& pci_device)
{
auto intel_hda_ptr = new HDAudioController(pci_device);
if (intel_hda_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM);
auto intel_hda = BAN::RefPtr<HDAudioController>::adopt(intel_hda_ptr);
TRY(intel_hda->initialize());
return {};
}
BAN::ErrorOr<void> HDAudioController::initialize()
{
using Regs = HDAudio::Regs;
m_pci_device.enable_bus_mastering();
m_bar0 = TRY(m_pci_device.allocate_bar_region(0));
dprintln("HD audio");
dprintln(" revision {}.{}",
m_bar0->read8(Regs::VMAJ),
m_bar0->read8(Regs::VMIN)
);
const uint16_t global_cap = m_bar0->read16(Regs::GCAP);
m_output_streams = (global_cap >> 12) & 0x0F;
m_input_streams = (global_cap >> 8) & 0x0F;
m_bidir_streams = (global_cap >> 3) & 0x1F;
m_is64bit = (global_cap & 1);
if (m_output_streams + m_input_streams + m_bidir_streams > 30)
{
dwarnln("HD audio controller has {} streams, 30 is the maximum valid count",
m_output_streams + m_input_streams + m_bidir_streams
);
return BAN::Error::from_errno(EINVAL);
}
dprintln(" output streams: {}", m_output_streams);
dprintln(" input streams: {}", m_input_streams);
dprintln(" bidir streams: {}", m_bidir_streams);
dprintln(" 64 bit support: {}", m_is64bit);
TRY(reset_controller());
if (auto ret = initialize_ring_buffers(); ret.is_error())
{
if (ret.error().get_error_code() != ETIMEDOUT)
return ret.release_error();
m_use_immediate_command = true;
}
TRY(m_pci_device.reserve_interrupts(1));
m_pci_device.enable_interrupt(0, *this);
m_bar0->write32(Regs::INTCTL, UINT32_MAX);
const uint16_t state_sts = m_bar0->read16(Regs::STATESTS);
for (uint8_t codec_id = 0; codec_id < 15; codec_id++)
{
if (!(state_sts & (1 << codec_id)))
continue;
auto codec_or_error = initialize_codec(codec_id);
if (codec_or_error.is_error())
continue;
auto codec = codec_or_error.release_value();
for (auto& node : codec.nodes)
if (auto ret = HDAudioFunctionGroup::create(this, codec.id, BAN::move(node)); ret.is_error())
dwarnln("Failed to initialize AFG: {}", ret.error());
}
return {};
}
BAN::ErrorOr<void> HDAudioController::reset_controller()
{
using HDAudio::Regs;
const auto timeout_ms = SystemTimer::get().ms_since_boot() + 100;
// transition into reset state
if (const uint32_t gcap = m_bar0->read32(Regs::GCTL); gcap & 1)
{
m_bar0->write32(Regs::GCTL, gcap & 0xFFFFFEFC);
while (m_bar0->read32(Regs::GCTL) & 1)
{
if (SystemTimer::get().ms_since_boot() > timeout_ms)
return BAN::Error::from_errno(ETIMEDOUT);
Processor::pause();
}
}
m_bar0->write32(Regs::GCTL, (m_bar0->read32(Regs::GCTL) & 0xFFFFFEFC) | 1);
while (!(m_bar0->read32(Regs::GCTL) & 1))
{
if (SystemTimer::get().ms_since_boot() > timeout_ms)
return BAN::Error::from_errno(ETIMEDOUT);
Processor::pause();
}
// 4.3 The software must wait at least 521 us (25 frames) after reading CRST as a 1
// before assuming that codecs have all made status change requests and have been
// registered by the controller
SystemTimer::get().sleep_ms(1);
return {};
}
BAN::ErrorOr<void> HDAudioController::initialize_ring_buffers()
{
using Regs = HDAudio::Regs;
// CORB: at most 1024 bytes (256 * uint32_t)
// RIRB: at most 2048 bytes (256 * uint32_t * 2)
m_ring_buffer_region = TRY(DMARegion::create(3 * 256 * sizeof(uint32_t)));
struct SizeInfo
{
uint16_t size;
uint8_t value;
};
const auto get_size_info =
[](uint8_t byte) -> BAN::ErrorOr<SizeInfo>
{
if (byte & 0x40)
return SizeInfo { 256, 2 };
if (byte & 0x20)
return SizeInfo { 16, 1 };
if (byte & 0x10)
return SizeInfo { 2, 0 };
return BAN::Error::from_errno(EINVAL);
};
const auto corb_size = TRY(get_size_info(m_bar0->read8(Regs::CORBSIZE)));
const auto rirb_size = TRY(get_size_info(m_bar0->read8(Regs::RIRBSIZE)));
m_corb = {
.vaddr = m_ring_buffer_region->vaddr(),
.index = 1,
.size = corb_size.size,
};
m_rirb = {
.vaddr = m_ring_buffer_region->vaddr() + 1024,
.index = 1,
.size = rirb_size.size,
};
const paddr_t corb_paddr = m_ring_buffer_region->paddr();
const paddr_t rirb_paddr = m_ring_buffer_region->paddr() + 1024;
if (!m_is64bit && ((corb_paddr >> 32) || (rirb_paddr >> 32)))
{
dwarnln("no 64 bit support but allocated ring buffers have 64 bit addresses :(");
return BAN::Error::from_errno(ENOTSUP);
}
// disable corb and rirb
m_bar0->write8(Regs::CORBCTL, (m_bar0->read8(Regs::CORBCTL) & 0xFC));
m_bar0->write8(Regs::RIRBCTL, (m_bar0->read8(Regs::RIRBCTL) & 0xF8));
// set base address
m_bar0->write32(Regs::CORBLBASE, corb_paddr | (m_bar0->read32(Regs::CORBLBASE) & 0x0000007F));
if (m_is64bit)
m_bar0->write32(Regs::CORBUBASE, corb_paddr >> 32);
// set number of entries
m_bar0->write8(Regs::CORBSIZE, (m_bar0->read8(Regs::CORBSIZE) & 0xFC) | corb_size.value);
// zero write pointer
m_bar0->write16(Regs::CORBWP, (m_bar0->read16(Regs::CORBWP) & 0xFF00));
// reset read pointer
const uint64_t corb_timeout_ms = SystemTimer::get().ms_since_boot() + 100;
m_bar0->write16(Regs::CORBRP, (m_bar0->read16(Regs::CORBRP) & 0x7FFF) | 0x8000);
while (!(m_bar0->read16(Regs::CORBRP) & 0x8000))
{
if (SystemTimer::get().ms_since_boot() > corb_timeout_ms)
return BAN::Error::from_errno(ETIMEDOUT);
Processor::pause();
}
m_bar0->write16(Regs::CORBRP, (m_bar0->read16(Regs::CORBRP) & 0x7FFF));
while ((m_bar0->read16(Regs::CORBRP) & 0x8000))
{
if (SystemTimer::get().ms_since_boot() > corb_timeout_ms)
return BAN::Error::from_errno(ETIMEDOUT);
Processor::pause();
}
// set base address
m_bar0->write32(Regs::RIRBLBASE, rirb_paddr | (m_bar0->read32(Regs::RIRBLBASE) & 0x0000007F));
if (m_is64bit)
m_bar0->write32(Regs::RIRBUBASE, rirb_paddr >> 32);
// set number of entries
m_bar0->write8(Regs::RIRBSIZE, (m_bar0->read8(Regs::RIRBSIZE) & 0xFC) | rirb_size.value);
// reset write pointer
m_bar0->write16(Regs::RIRBWP, (m_bar0->read16(Regs::RIRBWP) & 0x7FFF) | 0x8000);
// send interrupt on every packet
m_bar0->write16(Regs::RINTCNT, (m_bar0->read16(Regs::RINTCNT) & 0xFF00) | 0x01);
// enable corb and rirb
m_bar0->write8(Regs::CORBCTL, (m_bar0->read8(Regs::CORBCTL) & 0xFC) | 3);
m_bar0->write8(Regs::RIRBCTL, (m_bar0->read8(Regs::RIRBCTL) & 0xF8) | 7);
return {};
}
BAN::ErrorOr<HDAudio::Codec> HDAudioController::initialize_codec(uint8_t codec)
{
const auto resp = TRY(send_command({
.data = 0x04,
.command = 0xF00,
.node_index = 0,
.codec_address = codec,
}));
const uint8_t start = (resp >> 16) & 0xFF;
const uint8_t count = (resp >> 0) & 0xFF;
if (count == 0)
return BAN::Error::from_errno(ENODEV);
HDAudio::Codec result {};
result.id = codec;
TRY(result.nodes.reserve(count));
for (size_t i = 0; i < count; i++)
if (auto node_or_error = initialize_node(codec, start + i); !node_or_error.is_error())
MUST(result.nodes.emplace_back(node_or_error.release_value()));
return result;
}
BAN::ErrorOr<HDAudio::AFGNode> HDAudioController::initialize_node(uint8_t codec, uint8_t node)
{
{
const auto resp = TRY(send_command({
.data = 0x05,
.command = 0xF00,
.node_index = node,
.codec_address = codec,
}));
const uint8_t type = (resp >> 0) & 0xFF;
if (type != 0x01)
return BAN::Error::from_errno(ENODEV);
}
const auto resp = TRY(send_command({
.data = 0x04,
.command = 0xF00,
.node_index = node,
.codec_address = codec,
}));
const uint8_t start = (resp >> 16) & 0xFF;
const uint8_t count = (resp >> 0) & 0xFF;
HDAudio::AFGNode result {};
result.id = node;
TRY(result.widgets.reserve(count));
for (size_t i = 0; i < count; i++)
if (auto widget_or_error = initialize_widget(codec, start + i); !widget_or_error.is_error())
MUST(result.widgets.emplace_back(widget_or_error.release_value()));
return result;
}
BAN::ErrorOr<HDAudio::AFGWidget> HDAudioController::initialize_widget(uint8_t codec, uint8_t widget)
{
const auto send_command_or_zero =
[codec, widget, this](uint16_t cmd, uint8_t data) -> uint32_t
{
const auto command = HDAudio::CORBEntry {
.data = data,
.command = cmd,
.node_index = widget,
.codec_address = codec,
};
if (auto res = send_command(command); !res.is_error())
return res.release_value();
return 0;
};
using HDAudio::AFGWidget;
const AFGWidget::Type type_list[] {
AFGWidget::Type::OutputConverter,
AFGWidget::Type::InputConverter,
AFGWidget::Type::Mixer,
AFGWidget::Type::Selector,
AFGWidget::Type::PinComplex,
AFGWidget::Type::Power,
AFGWidget::Type::VolumeKnob,
AFGWidget::Type::BeepGenerator,
};
const uint8_t type = (send_command_or_zero(0xF00, 0x09) >> 20) & 0x0F;
if (type > sizeof(type_list) / sizeof(*type_list))
return BAN::Error::from_errno(ENOTSUP);
AFGWidget result {};
result.type = type_list[type];
result.id = widget;
if (result.type == AFGWidget::Type::PinComplex)
{
const uint32_t cap = send_command_or_zero(0xF00, 0x0C);
result.pin_complex = {
.input = !!(cap & (1 << 5)),
.output = !!(cap & (1 << 4)),
.display = !!(cap & ((1 << 7) | (1 << 24))),
.config = send_command_or_zero(0xF1C, 0x00),
};
}
const uint8_t connection_info = send_command_or_zero(0xF00, 0x0E);
const uint8_t conn_width = (connection_info & 0x80) ? 2 : 1;
const uint8_t conn_count = connection_info & 0x3F;
const uint16_t conn_mask = (1 << (8 * conn_width)) - 1;
TRY(result.connections.resize(conn_count, 0));
for (size_t i = 0; i < conn_count; i += 4 / conn_width)
{
const uint32_t conn = send_command_or_zero(0xF02, i);
for (size_t j = 0; j < sizeof(conn) / conn_width && i + j < conn_count; j++)
result.connections[i + j] = (conn >> (8 * conn_width * j)) & conn_mask;
}
return result;
}
BAN::ErrorOr<uint32_t> HDAudioController::send_command(HDAudio::CORBEntry command)
{
using Regs = HDAudio::Regs;
// TODO: allow concurrent commands with CORB/RIRB
LockGuard _(m_command_mutex);
if (!m_use_immediate_command)
{
SpinLockGuard sguard(m_rb_lock);
MMIO::write32(m_corb.vaddr + m_corb.index * sizeof(uint32_t), command.raw);
m_bar0->write16(Regs::CORBWP, (m_bar0->read16(Regs::CORBWP) & 0xFF00) | m_corb.index);
m_corb.index = (m_corb.index + 1) % m_corb.size;
const uint64_t waketime_ms = SystemTimer::get().ms_since_boot() + 10;
while ((m_bar0->read16(Regs::RIRBWP) & 0xFF) != m_rirb.index)
{
if (SystemTimer::get().ms_since_boot() > waketime_ms)
return BAN::Error::from_errno(ETIMEDOUT);
SpinLockGuardAsMutex smutex(sguard);
m_rb_blocker.block_with_timeout_ms(10, &smutex);
}
const size_t offset = 2 * m_rirb.index * sizeof(uint32_t);
m_rirb.index = (m_rirb.index + 1) % m_rirb.size;
return MMIO::read32(m_rirb.vaddr + offset);
}
else
{
uint64_t waketime_ms = SystemTimer::get().ms_since_boot() + 10;
while (m_bar0->read16(Regs::ICIS) & 1)
{
if (SystemTimer::get().ms_since_boot() > waketime_ms)
break;
Processor::pause();
}
// clear ICB if it did not clear "in reasonable timeout period"
// and make sure IRV is cleared
if (m_bar0->read16(Regs::ICIS) & 3)
m_bar0->write16(Regs::ICIS, (m_bar0->read16(Regs::ICIS) & 0x00FC) | 2);
m_bar0->write32(Regs::ICOI, command.raw);
m_bar0->write16(Regs::ICIS, (m_bar0->read16(Regs::ICIS) & 0x00FC) | 1);
waketime_ms = SystemTimer::get().ms_since_boot() + 10;
while (!(m_bar0->read16(Regs::ICIS) & 2))
{
if (SystemTimer::get().ms_since_boot() > waketime_ms)
return BAN::Error::from_errno(ETIMEDOUT);
Processor::pause();
}
return m_bar0->read32(Regs::ICII);
}
}
void HDAudioController::handle_irq()
{
using Regs = HDAudio::Regs;
const uint32_t intsts = m_bar0->read32(Regs::INTSTS);
if (!(intsts & (1u << 31)))
return;
if (intsts & (1 << 30))
{
if (const uint8_t rirbsts = m_bar0->read8(Regs::RIRBSTS) & ((1 << 2) | (1 << 0)))
{
if (rirbsts & (1 << 2))
dwarnln("RIRB response overrun");
if (rirbsts & (1 << 0))
{
SpinLockGuard _(m_rb_lock);
m_rb_blocker.unblock();
}
m_bar0->write8(Regs::RIRBSTS, rirbsts);
}
if (const uint8_t corbsts = m_bar0->read8(Regs::CORBSTS) & (1 << 0))
{
dwarnln("CORB memory error");
m_bar0->write8(Regs::CORBSTS, corbsts);
}
}
for (size_t i = 0; i < 30; i++)
{
if (!(intsts & (1 << i)))
continue;
if (m_allocated_streams[i] == nullptr)
dwarnln("interrupt from an unallocated stream??");
else
static_cast<HDAudioFunctionGroup*>(m_allocated_streams[i])->on_stream_interrupt(i);
}
}
uint8_t HDAudioController::get_stream_index(HDAudio::StreamType type, uint8_t index) const
{
switch (type)
{
case HDAudio::StreamType::Bidirectional:
index += m_output_streams;
[[fallthrough]];
case HDAudio::StreamType::Output:
index += m_input_streams;
[[fallthrough]];
case HDAudio::StreamType::Input:
break;
}
return index;
}
BAN::ErrorOr<uint8_t> HDAudioController::allocate_stream_id()
{
for (uint8_t id = 1; id < 16; id++)
{
if (m_allocated_stream_ids & (1 << id))
continue;
m_allocated_stream_ids |= 1 << id;
return id;
}
return BAN::Error::from_errno(EAGAIN);
}
void HDAudioController::deallocate_stream_id(uint8_t id)
{
ASSERT(m_allocated_stream_ids & (1 << id));
m_allocated_stream_ids &= ~(1 << id);
}
BAN::ErrorOr<uint8_t> HDAudioController::allocate_stream(HDAudio::StreamType type, void* afg)
{
const uint8_t stream_count_lookup[] {
[(int)HDAudio::StreamType::Input] = m_input_streams,
[(int)HDAudio::StreamType::Output] = m_output_streams,
[(int)HDAudio::StreamType::Bidirectional] = m_bidir_streams,
};
const uint8_t stream_count = stream_count_lookup[static_cast<int>(type)];
for (uint8_t i = 0; i < stream_count; i++)
{
const uint8_t index = get_stream_index(type, i);
if (m_allocated_streams[index])
continue;
m_allocated_streams[index] = afg;
return index;
}
return BAN::Error::from_errno(EAGAIN);
}
void HDAudioController::deallocate_stream(uint8_t index)
{
ASSERT(m_allocated_streams[index]);
m_allocated_streams[index] = nullptr;
// TODO: maybe make sure the stream is stopped/reset (?)
}
}