631 lines
17 KiB
C++
631 lines
17 KiB
C++
#include <kernel/Audio/HDAudio/AudioFunctionGroup.h>
|
|
#include <kernel/Audio/HDAudio/Registers.h>
|
|
#include <kernel/FS/DevFS/FileSystem.h>
|
|
|
|
#include <BAN/Sort.h>
|
|
|
|
namespace Kernel
|
|
{
|
|
|
|
BAN::ErrorOr<BAN::RefPtr<HDAudioFunctionGroup>> HDAudioFunctionGroup::create(BAN::RefPtr<HDAudioController> controller, uint8_t cid, HDAudio::AFGNode&& afg_node)
|
|
{
|
|
auto* audio_group_ptr = new HDAudioFunctionGroup(controller, cid, BAN::move(afg_node));
|
|
if (audio_group_ptr == nullptr)
|
|
return BAN::Error::from_errno(ENOMEM);
|
|
auto audio_group = BAN::RefPtr<HDAudioFunctionGroup>::adopt(audio_group_ptr);
|
|
TRY(audio_group->initialize());
|
|
return audio_group;
|
|
}
|
|
|
|
HDAudioFunctionGroup::~HDAudioFunctionGroup()
|
|
{
|
|
if (m_stream_id != 0xFF)
|
|
m_controller->deallocate_stream_id(m_stream_id);
|
|
if (m_stream_index != 0xFF)
|
|
m_controller->deallocate_stream(m_stream_index);
|
|
}
|
|
|
|
BAN::ErrorOr<void> HDAudioFunctionGroup::initialize()
|
|
{
|
|
TRY(AudioController::initialize());
|
|
|
|
if constexpr(DEBUG_HDAUDIO)
|
|
{
|
|
const auto widget_to_string =
|
|
[](HDAudio::AFGWidget::Type type) -> const char*
|
|
{
|
|
using HDAudio::AFGWidget;
|
|
switch (type)
|
|
{
|
|
case AFGWidget::Type::OutputConverter: return "DAC";
|
|
case AFGWidget::Type::InputConverter: return "ADC";
|
|
case AFGWidget::Type::Mixer: return "Mixer";
|
|
case AFGWidget::Type::Selector: return "Selector";
|
|
case AFGWidget::Type::PinComplex: return "Pin";
|
|
case AFGWidget::Type::Power: return "Power";
|
|
case AFGWidget::Type::VolumeKnob: return "VolumeKnob";
|
|
case AFGWidget::Type::BeepGenerator: return "BeepGenerator";
|
|
}
|
|
ASSERT_NOT_REACHED();
|
|
};
|
|
|
|
dprintln("AFG {}", m_afg_node.id);
|
|
for (auto widget : m_afg_node.widgets)
|
|
{
|
|
if (widget.type == HDAudio::AFGWidget::Type::PinComplex)
|
|
{
|
|
dprintln(" widget {}: {} ({}, {}, {}), {32b}",
|
|
widget.id,
|
|
widget_to_string(widget.type),
|
|
(int)widget.pin_complex.output,
|
|
(int)widget.pin_complex.input,
|
|
(int)widget.pin_complex.display,
|
|
widget.pin_complex.config
|
|
);
|
|
}
|
|
else
|
|
{
|
|
dprintln(" widget {}: {}",
|
|
widget.id,
|
|
widget_to_string(widget.type)
|
|
);
|
|
}
|
|
|
|
if (!widget.connections.empty())
|
|
dprintln(" connections {}", widget.connections);
|
|
}
|
|
}
|
|
|
|
TRY(initialize_stream());
|
|
|
|
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);
|
|
|
|
return {};
|
|
}
|
|
|
|
uint32_t HDAudioFunctionGroup::get_total_pins() const
|
|
{
|
|
return m_output_pins.size();
|
|
}
|
|
|
|
uint32_t HDAudioFunctionGroup::get_current_pin() const
|
|
{
|
|
const auto current_id = m_output_paths[m_output_path_index].front()->id;
|
|
for (size_t i = 0; i < m_output_pins.size(); i++)
|
|
if (m_output_pins[i]->id == current_id)
|
|
return i;
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
BAN::ErrorOr<void> HDAudioFunctionGroup::set_current_pin(uint32_t pin)
|
|
{
|
|
if (pin >= m_output_pins.size())
|
|
return BAN::Error::from_errno(EINVAL);
|
|
|
|
if (auto ret = disable_output_path(m_output_path_index); ret.is_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++)
|
|
{
|
|
if (m_output_paths[i].front()->id != pin_id)
|
|
continue;
|
|
|
|
if (auto ret = enable_output_path(i); ret.is_error())
|
|
{
|
|
if (ret.error().get_error_code() == ENOTSUP)
|
|
continue;
|
|
dwarnln("path {} not supported", i);
|
|
return ret.release_error();
|
|
}
|
|
|
|
m_output_path_index = i;
|
|
return {};
|
|
}
|
|
|
|
dwarnln("failed to set output widget to {}", pin_id);
|
|
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
}
|
|
|
|
BAN::ErrorOr<void> HDAudioFunctionGroup::set_volume_mdB(int32_t mdB)
|
|
{
|
|
mdB = BAN::Math::clamp(mdB, m_volume_info.min_mdB, m_volume_info.max_mdB);
|
|
|
|
const auto& path = m_output_paths[m_output_path_index];
|
|
for (size_t i = 0; i < path.size(); i++)
|
|
{
|
|
if (!path[i]->output_amplifier.has_value())
|
|
continue;
|
|
|
|
const int32_t step_round = (mdB >= 0)
|
|
? +m_volume_info.step_mdB / 2
|
|
: -m_volume_info.step_mdB / 2;
|
|
const uint32_t step = (mdB + step_round) / m_volume_info.step_mdB + path[i]->output_amplifier->offset;
|
|
const uint32_t volume = 0b1'0'1'1'0000'0'0000000 | step;
|
|
|
|
TRY(m_controller->send_command({
|
|
.data = static_cast<uint8_t>(volume & 0xFF),
|
|
.command = static_cast<uint16_t>(0x300 | (volume >> 8)),
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
|
|
break;
|
|
}
|
|
|
|
m_volume_info.mdB = mdB;
|
|
|
|
return {};
|
|
}
|
|
|
|
size_t HDAudioFunctionGroup::bdl_offset() const
|
|
{
|
|
const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t);
|
|
const size_t bdl_total_size = bdl_entry_bytes * m_bdl_entry_count;
|
|
if (auto rem = bdl_total_size % 128)
|
|
return bdl_total_size + (128 - rem);
|
|
return bdl_total_size;
|
|
}
|
|
|
|
BAN::ErrorOr<void> HDAudioFunctionGroup::initialize_stream()
|
|
{
|
|
const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t);
|
|
const size_t bdl_list_size = m_bdl_entry_count * sizeof(HDAudio::BDLEntry);
|
|
|
|
m_bdl_region = TRY(DMARegion::create(bdl_offset() + bdl_list_size));
|
|
if (!m_controller->is_64bit() && (m_bdl_region->paddr() >> 32))
|
|
{
|
|
dwarnln("no 64 bit support but allocated bdl has 64 bit address :(");
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
}
|
|
|
|
auto* bdl = reinterpret_cast<volatile HDAudio::BDLEntry*>(m_bdl_region->vaddr() + bdl_offset());
|
|
for (size_t i = 0; i < m_bdl_entry_count; i++)
|
|
{
|
|
bdl[i].address = m_bdl_region->paddr() + i * bdl_entry_bytes;
|
|
bdl[i].length = bdl_entry_bytes;
|
|
bdl[i].ioc = 1;
|
|
}
|
|
|
|
ASSERT(m_stream_id == 0xFF);
|
|
m_stream_id = TRY(m_controller->allocate_stream_id());
|
|
|
|
ASSERT(m_stream_index == 0xFF);
|
|
m_stream_index = TRY(m_controller->allocate_stream(HDAudio::StreamType::Output, this));
|
|
|
|
reset_stream();
|
|
|
|
return {};
|
|
}
|
|
|
|
void HDAudioFunctionGroup::reset_stream()
|
|
{
|
|
using Regs = HDAudio::Regs;
|
|
|
|
auto& bar = m_controller->bar0();
|
|
const auto base = 0x80 + m_stream_index * 0x20;
|
|
|
|
const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t);
|
|
|
|
// stop stream
|
|
bar.write8(base + Regs::SDCTL, bar.read8(base + Regs::SDCTL) & 0xFD);
|
|
|
|
// reset stream
|
|
bar.write8(base + Regs::SDCTL, (bar.read8(base + Regs::SDCTL) & 0xFE) | 1);
|
|
while (!(bar.read8(base + Regs::SDCTL) & 1))
|
|
Processor::pause();
|
|
bar.write8(base + Regs::SDCTL, (bar.read8(base + Regs::SDCTL) & 0xFE));
|
|
while ((bar.read8(base + Regs::SDCTL) & 1))
|
|
Processor::pause();
|
|
|
|
// set bdl address, total size and lvi
|
|
const paddr_t bdl_paddr = m_bdl_region->paddr() + bdl_offset();
|
|
bar.write32(base + Regs::SDBDPL, bdl_paddr);
|
|
if (m_controller->is_64bit())
|
|
bar.write32(base + Regs::SDBDPU, bdl_paddr >> 32);
|
|
bar.write32(base + Regs::SDCBL, bdl_entry_bytes * m_bdl_entry_count);
|
|
bar.write16(base + Regs::SDLVI, (bar.read16(base + Regs::SDLVI) & 0xFF00) | (m_bdl_entry_count - 1));
|
|
|
|
// set stream format
|
|
bar.write16(base + Regs::SDFMT, get_format_data());
|
|
|
|
// set stream id, not bidirectional
|
|
bar.write8(base + Regs::SDCTL + 2, (bar.read8(base + Regs::SDCTL + 2) & 0x07) | (m_stream_id << 4));
|
|
|
|
m_bdl_head = 0;
|
|
m_bdl_tail = 0;
|
|
m_stream_running = false;
|
|
}
|
|
|
|
BAN::ErrorOr<void> HDAudioFunctionGroup::initialize_output()
|
|
{
|
|
for (const auto& widget : m_afg_node.widgets)
|
|
{
|
|
if (widget.type != HDAudio::AFGWidget::Type::PinComplex || !widget.pin_complex.output)
|
|
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(recurse_output_paths(widget, path));
|
|
|
|
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());
|
|
|
|
// select first supported path
|
|
// FIXME: prefer associations
|
|
// FIXME: does this pin even have a device?
|
|
auto result = BAN::Error::from_errno(ENODEV);
|
|
for (size_t i = 0; i < m_output_paths.size(); i++)
|
|
{
|
|
if (auto ret = enable_output_path(i); ret.is_error())
|
|
{
|
|
if (ret.error().get_error_code() != ENOTSUP)
|
|
return ret.release_error();
|
|
dwarnln("path {} not supported", i);
|
|
result = BAN::Error::from_errno(ENOTSUP);
|
|
continue;
|
|
}
|
|
|
|
m_output_path_index = i;
|
|
break;
|
|
}
|
|
|
|
if (m_output_path_index >= m_output_paths.size())
|
|
{
|
|
dwarnln("could not find any usable output path");
|
|
return result;
|
|
}
|
|
|
|
dprintln_if(DEBUG_HDAUDIO, "routed output path");
|
|
for (const auto* widget : m_output_paths[m_output_path_index])
|
|
dprintln_if(DEBUG_HDAUDIO, " {}", widget->id);
|
|
|
|
return {};
|
|
}
|
|
|
|
uint16_t HDAudioFunctionGroup::get_format_data() const
|
|
{
|
|
// TODO: don't hardcode this
|
|
// format: PCM, 48 kHz, 16 bit, 2 channels
|
|
return 0b0'0'000'000'0'001'0001;
|
|
}
|
|
|
|
BAN::ErrorOr<void> HDAudioFunctionGroup::enable_output_path(uint8_t index)
|
|
{
|
|
ASSERT(index < m_output_paths.size());
|
|
const auto& path = m_output_paths[index];
|
|
|
|
for (const auto* widget : path)
|
|
{
|
|
switch (widget->type)
|
|
{
|
|
using HDAudio::AFGWidget;
|
|
case AFGWidget::Type::OutputConverter:
|
|
case AFGWidget::Type::PinComplex:
|
|
break;
|
|
default:
|
|
dwarnln("FIXME: support enabling widget type {}", static_cast<int>(widget->type));
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
}
|
|
}
|
|
|
|
const auto format = get_format_data();
|
|
|
|
for (size_t i = 0; i < path.size(); i++)
|
|
{
|
|
// set power state D0
|
|
TRY(m_controller->send_command({
|
|
.data = 0x00,
|
|
.command = 0x705,
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
|
|
// set connection index
|
|
if (i + 1 < path.size() && path[i]->connections.size() > 1)
|
|
{
|
|
uint8_t index = 0;
|
|
for (; index < path[i]->connections.size(); index++)
|
|
if (path[i]->connections[index] == path[i + 1]->id)
|
|
break;
|
|
ASSERT(index < path[i]->connections.size());
|
|
|
|
TRY(m_controller->send_command({
|
|
.data = index,
|
|
.command = 0x701,
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
}
|
|
|
|
// set volume to 0 dB, no mute
|
|
if (path[i]->output_amplifier.has_value())
|
|
{
|
|
const uint32_t volume = 0b1'0'1'1'0000'0'0000000 | path[i]->output_amplifier->offset;
|
|
TRY(m_controller->send_command({
|
|
.data = static_cast<uint8_t>(volume & 0xFF),
|
|
.command = static_cast<uint16_t>(0x300 | (volume >> 8)),
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
}
|
|
|
|
switch (path[i]->type)
|
|
{
|
|
using HDAudio::AFGWidget;
|
|
|
|
case AFGWidget::Type::OutputConverter:
|
|
// set stream and channel 0
|
|
TRY(m_controller->send_command({
|
|
.data = static_cast<uint8_t>(m_stream_id << 4),
|
|
.command = 0x706,
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
// set format
|
|
TRY(m_controller->send_command({
|
|
.data = static_cast<uint8_t>(format & 0xFF),
|
|
.command = static_cast<uint16_t>(0x200 | (format >> 8)),
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
break;
|
|
|
|
case AFGWidget::Type::PinComplex:
|
|
// enable output and H-Phn
|
|
TRY(m_controller->send_command({
|
|
.data = 0x80 | 0x40,
|
|
.command = 0x707,
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
// enable EAPD
|
|
TRY(m_controller->send_command({
|
|
.data = 0x02,
|
|
.command = 0x70C,
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
break;
|
|
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
// update volume info to this path
|
|
m_volume_info.min_mdB = 0;
|
|
m_volume_info.max_mdB = 0;
|
|
m_volume_info.step_mdB = 0;
|
|
for (size_t i = 0; i < path.size(); i++)
|
|
{
|
|
if (!path[i]->output_amplifier.has_value())
|
|
continue;
|
|
const auto& amp = path[i]->output_amplifier.value();
|
|
|
|
const int32_t step_mdB = amp.step_size * 250;
|
|
m_volume_info.step_mdB = step_mdB;
|
|
m_volume_info.min_mdB = -amp.offset * step_mdB;
|
|
m_volume_info.max_mdB = (amp.num_steps - amp.offset) * step_mdB;
|
|
m_volume_info.mdB = BAN::Math::clamp(m_volume_info.mdB, m_volume_info.min_mdB, m_volume_info.max_mdB);
|
|
|
|
const int32_t step_round = (m_volume_info.mdB >= 0)
|
|
? +step_mdB / 2
|
|
: -step_mdB / 2;
|
|
const uint32_t step = (m_volume_info.mdB + step_round) / step_mdB + amp.offset;
|
|
const uint32_t volume = 0b1'0'1'1'0000'0'0000000 | step;
|
|
|
|
TRY(m_controller->send_command({
|
|
.data = static_cast<uint8_t>(volume & 0xFF),
|
|
.command = static_cast<uint16_t>(0x300 | (volume >> 8)),
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
|
|
break;
|
|
}
|
|
|
|
if (m_volume_info.min_mdB == 0 && m_volume_info.max_mdB == 0)
|
|
m_volume_info.mdB = 0;
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> HDAudioFunctionGroup::disable_output_path(uint8_t index)
|
|
{
|
|
ASSERT(index < m_output_paths.size());
|
|
const auto& path = m_output_paths[index];
|
|
|
|
for (size_t i = 0; i < path.size(); i++)
|
|
{
|
|
// set power state D3
|
|
TRY(m_controller->send_command({
|
|
.data = 0x03,
|
|
.command = 0x705,
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
|
|
switch (path[i]->type)
|
|
{
|
|
using HDAudio::AFGWidget;
|
|
|
|
case AFGWidget::Type::OutputConverter:
|
|
break;
|
|
|
|
case AFGWidget::Type::PinComplex:
|
|
// disable output and H-Phn
|
|
TRY(m_controller->send_command({
|
|
.data = 0x00,
|
|
.command = 0x707,
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
// disable EAPD
|
|
TRY(m_controller->send_command({
|
|
.data = 0x00,
|
|
.command = 0x70C,
|
|
.node_index = path[i]->id,
|
|
.codec_address = m_cid,
|
|
}));
|
|
break;
|
|
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> HDAudioFunctionGroup::recurse_output_paths(const HDAudio::AFGWidget& widget, BAN::Vector<const HDAudio::AFGWidget*>& path)
|
|
{
|
|
// we've reached a DAC
|
|
if (widget.type == HDAudio::AFGWidget::Type::OutputConverter)
|
|
{
|
|
BAN::Vector<const HDAudio::AFGWidget*> path_copy;
|
|
TRY(path_copy.resize(path.size()));
|
|
for (size_t i = 0; i < path.size(); i++)
|
|
path_copy[i] = path[i];
|
|
TRY(m_output_paths.push_back(BAN::move(path_copy)));
|
|
return {};
|
|
}
|
|
|
|
// check all connections
|
|
for (const auto& connection : m_afg_node.widgets)
|
|
{
|
|
if (!widget.connections.contains(connection.id))
|
|
continue;
|
|
|
|
// cycle detection
|
|
for (const auto* w : path)
|
|
if (w == &connection)
|
|
goto already_visited;
|
|
|
|
TRY(path.push_back(&connection));
|
|
TRY(recurse_output_paths(connection, path));
|
|
path.pop_back();
|
|
|
|
already_visited:
|
|
continue;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void HDAudioFunctionGroup::handle_new_data()
|
|
{
|
|
queue_bdl_data();
|
|
}
|
|
|
|
void HDAudioFunctionGroup::queue_bdl_data()
|
|
{
|
|
ASSERT(m_spinlock.current_processor_has_lock());
|
|
|
|
const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t);
|
|
|
|
while ((m_bdl_head + 1) % m_bdl_entry_count != m_bdl_tail)
|
|
{
|
|
const size_t sample_frames = BAN::Math::min(m_sample_data->size() / get_channels() / sizeof(uint16_t), m_bdl_entry_sample_frames);
|
|
if (sample_frames == 0)
|
|
break;
|
|
|
|
const size_t copy_total_bytes = sample_frames * get_channels() * sizeof(uint16_t);
|
|
|
|
memcpy(
|
|
reinterpret_cast<void*>(m_bdl_region->vaddr() + m_bdl_head * bdl_entry_bytes),
|
|
m_sample_data->get_data().data(),
|
|
copy_total_bytes
|
|
);
|
|
|
|
if (copy_total_bytes < bdl_entry_bytes)
|
|
{
|
|
memset(
|
|
reinterpret_cast<void*>(m_bdl_region->vaddr() + m_bdl_head * bdl_entry_bytes + copy_total_bytes),
|
|
0x00,
|
|
bdl_entry_bytes - copy_total_bytes
|
|
);
|
|
}
|
|
|
|
m_sample_data->pop(copy_total_bytes);
|
|
m_bdl_head = (m_bdl_head + 1) % m_bdl_entry_count;
|
|
}
|
|
|
|
if (m_bdl_head == m_bdl_tail || m_stream_running)
|
|
return;
|
|
|
|
// start the stream and enable IOC and descriptor error interrupts
|
|
auto& bar = m_controller->bar0();
|
|
const auto base = 0x80 + m_stream_index * 0x20;
|
|
bar.write8(base + HDAudio::Regs::SDCTL, bar.read8(base + HDAudio::Regs::SDCTL) | 0x16);
|
|
|
|
m_stream_running = true;
|
|
}
|
|
|
|
void HDAudioFunctionGroup::on_stream_interrupt(uint8_t stream_index)
|
|
{
|
|
using Regs = HDAudio::Regs;
|
|
|
|
ASSERT(stream_index == m_stream_index);
|
|
|
|
auto& bar = m_controller->bar0();
|
|
const uint16_t base = 0x80 + stream_index * 0x20;
|
|
|
|
const uint8_t sts = bar.read8(base + Regs::SDSTS);
|
|
bar.write8(base + Regs::SDSTS, sts & 0x3C);
|
|
|
|
if (sts & (1 << 4))
|
|
derrorln("descriptor error");
|
|
|
|
// ignore fifo errors as they are too common on real hw :D
|
|
//if (sts & (1 << 3))
|
|
// derrorln("fifo error");
|
|
|
|
if (sts & (1 << 2))
|
|
{
|
|
SpinLockGuard _(m_spinlock);
|
|
|
|
ASSERT(m_stream_running);
|
|
|
|
m_bdl_tail = (m_bdl_tail + 1) % m_bdl_entry_count;
|
|
if (m_bdl_tail == m_bdl_head)
|
|
reset_stream();
|
|
|
|
queue_bdl_data();
|
|
}
|
|
}
|
|
|
|
}
|