Kernel: HDAudio hide unusable pins and cleanup path finding

This commit is contained in:
Bananymous 2026-03-24 00:53:19 +02:00
parent f89d690716
commit 5c20d5e291
4 changed files with 66 additions and 50 deletions

View File

@ -53,8 +53,6 @@ namespace Kernel
void queue_bdl_data(); void queue_bdl_data();
private: private:
static constexpr size_t m_max_path_length = 16;
// use 6 512 sample BDL entries // use 6 512 sample BDL entries
// each entry is ~10.7 ms at 48 kHz // each entry is ~10.7 ms at 48 kHz
// -> total buffered audio is 64 ms // -> total buffered audio is 64 ms
@ -66,6 +64,7 @@ namespace Kernel
const uint8_t m_cid; const uint8_t m_cid;
BAN::Vector<BAN::Vector<const HDAudio::AFGWidget*>> m_output_paths; BAN::Vector<BAN::Vector<const HDAudio::AFGWidget*>> m_output_paths;
BAN::Vector<const HDAudio::AFGWidget*> m_output_pins;
size_t m_output_path_index { SIZE_MAX }; size_t m_output_path_index { SIZE_MAX };
uint8_t m_stream_id { 0xFF }; uint8_t m_stream_id { 0xFF };

View File

@ -50,6 +50,8 @@ namespace Kernel::HDAudio
{ {
bool input; bool input;
bool output; bool output;
bool display; // HDMI or DP
uint32_t config;
} pin_complex; } pin_complex;
}; };

View File

@ -2,6 +2,8 @@
#include <kernel/Audio/HDAudio/Registers.h> #include <kernel/Audio/HDAudio/Registers.h>
#include <kernel/FS/DevFS/FileSystem.h> #include <kernel/FS/DevFS/FileSystem.h>
#include <BAN/Sort.h>
namespace Kernel namespace Kernel
{ {
@ -52,19 +54,13 @@ namespace Kernel
{ {
if (widget.type == HDAudio::AFGWidget::Type::PinComplex) if (widget.type == HDAudio::AFGWidget::Type::PinComplex)
{ {
const uint32_t config = TRY(m_controller->send_command({ dprintln(" widget {}: {} ({}, {}, {}), {32b}",
.data = 0x00,
.command = 0xF1C,
.node_index = widget.id,
.codec_address = m_cid,
}));
dprintln(" widget {}: {} ({}, {}), {32b}",
widget.id, widget.id,
widget_to_string(widget.type), widget_to_string(widget.type),
(int)widget.pin_complex.output, (int)widget.pin_complex.output,
(int)widget.pin_complex.input, (int)widget.pin_complex.input,
config (int)widget.pin_complex.display,
widget.pin_complex.config
); );
} }
else else
@ -81,59 +77,49 @@ namespace Kernel
} }
TRY(initialize_stream()); TRY(initialize_stream());
TRY(initialize_output());
if (auto ret = initialize_output(); ret.is_error())
{
// No usable pins, not really an error
if (ret.error().get_error_code() == ENODEV)
return {};
return ret.release_error();
}
DevFileSystem::get().add_device(this); DevFileSystem::get().add_device(this);
return {}; return {};
} }
uint32_t HDAudioFunctionGroup::get_total_pins() const uint32_t HDAudioFunctionGroup::get_total_pins() const
{ {
uint32_t count = 0; return m_output_pins.size();
for (const auto& widget : m_afg_node.widgets)
if (widget.type == HDAudio::AFGWidget::Type::PinComplex && widget.pin_complex.output)
count++;
return count;
} }
uint32_t HDAudioFunctionGroup::get_current_pin() const uint32_t HDAudioFunctionGroup::get_current_pin() const
{ {
const auto current_id = m_output_paths[m_output_path_index].front()->id; const auto current_id = m_output_paths[m_output_path_index].front()->id;
for (size_t i = 0; i < m_output_pins.size(); i++)
uint32_t pin = 0; if (m_output_pins[i]->id == current_id)
for (const auto& widget : m_afg_node.widgets) return i;
{
if (widget.type != HDAudio::AFGWidget::Type::PinComplex || !widget.pin_complex.output)
continue;
if (widget.id == current_id)
return pin;
pin++;
}
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
} }
BAN::ErrorOr<void> HDAudioFunctionGroup::set_current_pin(uint32_t pin) BAN::ErrorOr<void> HDAudioFunctionGroup::set_current_pin(uint32_t pin)
{ {
uint32_t pin_id = 0; if (pin >= m_output_pins.size())
for (const auto& widget : m_afg_node.widgets) return BAN::Error::from_errno(EINVAL);
{
if (widget.type != HDAudio::AFGWidget::Type::PinComplex || !widget.pin_complex.output)
continue;
if (pin-- > 0)
continue;
pin_id = widget.id;
break;
}
if (auto ret = disable_output_path(m_output_path_index); ret.is_error()) if (auto ret = disable_output_path(m_output_path_index); ret.is_error())
dwarnln("failed to disable old output path {}", ret.error()); dwarnln("failed to disable old output path {}", ret.error());
const uint32_t pin_id = m_output_pins[pin]->id;
for (size_t i = 0; i < m_output_paths.size(); i++) for (size_t i = 0; i < m_output_paths.size(); i++)
{ {
if (m_output_paths[i].front()->id != pin_id) if (m_output_paths[i].front()->id != pin_id)
continue; continue;
if (auto ret = enable_output_path(i); !ret.is_error()) if (auto ret = enable_output_path(i); ret.is_error())
{ {
if (ret.error().get_error_code() == ENOTSUP) if (ret.error().get_error_code() == ENOTSUP)
continue; continue;
@ -141,7 +127,6 @@ namespace Kernel
return ret.release_error(); return ret.release_error();
} }
dprintln("set output widget to {}", pin_id);
m_output_path_index = i; m_output_path_index = i;
return {}; return {};
} }
@ -232,18 +217,39 @@ namespace Kernel
BAN::ErrorOr<void> HDAudioFunctionGroup::initialize_output() BAN::ErrorOr<void> HDAudioFunctionGroup::initialize_output()
{ {
BAN::Vector<const HDAudio::AFGWidget*> path;
TRY(path.reserve(m_max_path_length));
for (const auto& widget : m_afg_node.widgets) for (const auto& widget : m_afg_node.widgets)
{ {
if (widget.type != HDAudio::AFGWidget::Type::PinComplex || !widget.pin_complex.output) if (widget.type != HDAudio::AFGWidget::Type::PinComplex || !widget.pin_complex.output)
continue; continue;
// no physical connection
if ((widget.pin_complex.config >> 30) == 0b01)
continue;
// needs a GPU
if (widget.pin_complex.display)
continue;
BAN::Vector<const HDAudio::AFGWidget*> path;
TRY(path.push_back(&widget)); TRY(path.push_back(&widget));
TRY(recurse_output_paths(widget, path)); TRY(recurse_output_paths(widget, path));
path.pop_back();
if (!m_output_paths.empty() && m_output_paths.back().front()->id == widget.id)
TRY(m_output_pins.push_back(&widget));
} }
if (m_output_pins.empty())
return BAN::Error::from_errno(ENODEV);
// prefer short paths
BAN::sort::sort(m_output_paths.begin(), m_output_paths.end(),
[](const auto& a, const auto& b) {
if (a.front()->id != b.front()->id)
return a.front()->id < b.front()->id;
return a.size() < b.size();
}
);
dprintln_if(DEBUG_HDAUDIO, "found {} paths from output to DAC", m_output_paths.size()); dprintln_if(DEBUG_HDAUDIO, "found {} paths from output to DAC", m_output_paths.size());
// select first supported path // select first supported path
@ -444,10 +450,6 @@ namespace Kernel
BAN::ErrorOr<void> HDAudioFunctionGroup::recurse_output_paths(const HDAudio::AFGWidget& widget, BAN::Vector<const HDAudio::AFGWidget*>& path) BAN::ErrorOr<void> HDAudioFunctionGroup::recurse_output_paths(const HDAudio::AFGWidget& widget, BAN::Vector<const HDAudio::AFGWidget*>& path)
{ {
// cycle "detection"
if (path.size() >= m_max_path_length)
return {};
// we've reached a DAC // we've reached a DAC
if (widget.type == HDAudio::AFGWidget::Type::OutputConverter) if (widget.type == HDAudio::AFGWidget::Type::OutputConverter)
{ {
@ -464,9 +466,18 @@ namespace Kernel
{ {
if (!widget.connections.contains(connection.id)) if (!widget.connections.contains(connection.id))
continue; continue;
// cycle detection
for (const auto* w : path)
if (w == &connection)
goto already_visited;
TRY(path.push_back(&connection)); TRY(path.push_back(&connection));
TRY(recurse_output_paths(connection, path)); TRY(recurse_output_paths(connection, path));
path.pop_back(); path.pop_back();
already_visited:
continue;
} }
return {}; return {};

View File

@ -311,8 +311,12 @@ namespace Kernel
if (result.type == AFGWidget::Type::PinComplex) if (result.type == AFGWidget::Type::PinComplex)
{ {
const uint32_t cap = send_command_or_zero(0xF00, 0x0C); const uint32_t cap = send_command_or_zero(0xF00, 0x0C);
result.pin_complex.output = !!(cap & (1 << 4)); result.pin_complex = {
result.pin_complex.input = !!(cap & (1 << 5)); .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 connection_info = send_command_or_zero(0xF00, 0x0E);