Kernel: Implement PCI interrupt routing
This does not really work but I have no idea what I'm doing wrong
This commit is contained in:
parent
76bad31dd5
commit
96767f5ca8
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <BAN/UniqPtr.h>
|
#include <BAN/UniqPtr.h>
|
||||||
#include <BAN/Vector.h>
|
#include <BAN/Vector.h>
|
||||||
|
#include <kernel/ACPI/AML/Node.h>
|
||||||
#include <kernel/Memory/Types.h>
|
#include <kernel/Memory/Types.h>
|
||||||
#include <kernel/Storage/StorageController.h>
|
#include <kernel/Storage/StorageController.h>
|
||||||
|
|
||||||
|
@ -119,6 +120,9 @@ namespace Kernel::PCI
|
||||||
void enable_pin_interrupts();
|
void enable_pin_interrupts();
|
||||||
void disable_pin_interrupts();
|
void disable_pin_interrupts();
|
||||||
|
|
||||||
|
BAN::ErrorOr<uint8_t> route_prt_entry(const ACPI::AML::Node& routing_entry);
|
||||||
|
BAN::ErrorOr<uint8_t> find_intx_interrupt();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_is_valid { false };
|
bool m_is_valid { false };
|
||||||
uint8_t m_bus { 0 };
|
uint8_t m_bus { 0 };
|
||||||
|
|
|
@ -98,6 +98,8 @@ namespace Kernel
|
||||||
private:
|
private:
|
||||||
Thread(pid_t tid, Process*);
|
Thread(pid_t tid, Process*);
|
||||||
|
|
||||||
|
void setup_exec_impl(uintptr_t entry, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3);
|
||||||
|
|
||||||
static void on_exit_trampoline(Thread*);
|
static void on_exit_trampoline(Thread*);
|
||||||
void on_exit();
|
void on_exit();
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#include <BAN/ScopeGuard.h>
|
||||||
|
|
||||||
|
#include <kernel/APIC.h>
|
||||||
#include <kernel/ACPI/ACPI.h>
|
#include <kernel/ACPI/ACPI.h>
|
||||||
#include <kernel/IDT.h>
|
#include <kernel/IDT.h>
|
||||||
#include <kernel/IO.h>
|
#include <kernel/IO.h>
|
||||||
|
@ -379,7 +382,7 @@ namespace Kernel::PCI
|
||||||
|
|
||||||
uint8_t PCI::Device::get_interrupt(uint8_t index) const
|
uint8_t PCI::Device::get_interrupt(uint8_t index) const
|
||||||
{
|
{
|
||||||
ASSERT(m_offset_msi.has_value() || m_offset_msi_x.has_value() || !InterruptController::get().is_using_apic());
|
ASSERT(m_interrupt_mechanism != InterruptMechanism::NONE);
|
||||||
ASSERT(index < m_reserved_interrupt_count);
|
ASSERT(index < m_reserved_interrupt_count);
|
||||||
|
|
||||||
uint8_t count_found = 0;
|
uint8_t count_found = 0;
|
||||||
|
@ -418,7 +421,8 @@ namespace Kernel::PCI
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
case InterruptMechanism::PIN:
|
case InterruptMechanism::PIN:
|
||||||
enable_pin_interrupts();
|
enable_pin_interrupts();
|
||||||
write_byte(PCI_REG_IRQ_LINE, irq);
|
if (!InterruptController::get().is_using_apic())
|
||||||
|
write_byte(PCI_REG_IRQ_LINE, irq);
|
||||||
InterruptController::get().enable_irq(irq);
|
InterruptController::get().enable_irq(irq);
|
||||||
break;
|
break;
|
||||||
case InterruptMechanism::MSI:
|
case InterruptMechanism::MSI:
|
||||||
|
@ -476,13 +480,229 @@ namespace Kernel::PCI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#if defined(__GNUC__) && !defined(__clang__)
|
||||||
|
#pragma GCC diagnostic ignored "-Wstack-usage="
|
||||||
|
#endif
|
||||||
|
BAN::ErrorOr<uint8_t> PCI::Device::route_prt_entry(const ACPI::AML::Node& prt_entry)
|
||||||
|
{
|
||||||
|
ASSERT(prt_entry.type == ACPI::AML::Node::Type::Package);
|
||||||
|
ASSERT(prt_entry.as.package->num_elements == 4);
|
||||||
|
for (size_t i = 0; i < 4; i++)
|
||||||
|
ASSERT(prt_entry.as.package->elements[i].value.node);
|
||||||
|
|
||||||
|
auto& prt_entry_fields = prt_entry.as.package->elements;
|
||||||
|
|
||||||
|
auto& source_node = *prt_entry_fields[2].value.node;
|
||||||
|
if (source_node.type != ACPI::AML::Node::Type::Reference || source_node.as.reference->node.type != ACPI::AML::Node::Type::Device)
|
||||||
|
{
|
||||||
|
BAN::ScopeGuard debug_guard([] { dwarnln("unknown or invalid _PRT format"); });
|
||||||
|
|
||||||
|
const auto source_value = TRY(ACPI::AML::convert_node(TRY(source_node.copy()), ACPI::AML::ConvInteger, -1)).as.integer.value;
|
||||||
|
if (source_value != 0x00)
|
||||||
|
return BAN::Error::from_errno(EINVAL);
|
||||||
|
|
||||||
|
auto& gsi_node = *prt_entry_fields[3].value.node;
|
||||||
|
const auto gsi_value = TRY(ACPI::AML::convert_node(TRY(gsi_node.copy()), ACPI::AML::ConvInteger, -1)).as.integer.value;
|
||||||
|
|
||||||
|
debug_guard.disable();
|
||||||
|
|
||||||
|
auto& apic = static_cast<APIC&>(InterruptController::get());
|
||||||
|
return TRY(apic.reserve_gsi(gsi_value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BAN::ScopeGuard debug_guard([] { dwarnln("unknown or invalid _PRT format"); });
|
||||||
|
|
||||||
|
auto& acpi_namespace = *ACPI::ACPI::get().acpi_namespace();
|
||||||
|
|
||||||
|
auto source_scope = TRY(acpi_namespace.find_reference_scope(source_node.as.reference));
|
||||||
|
|
||||||
|
auto crs_node = TRY(ACPI::AML::convert_node(TRY(acpi_namespace.evaluate(source_scope, "_CRS"_sv)), ACPI::AML::ConvBuffer, -1));
|
||||||
|
|
||||||
|
auto crs_buffer = BAN::ConstByteSpan(crs_node.as.str_buf->bytes, crs_node.as.str_buf->size);
|
||||||
|
while (!crs_buffer.empty())
|
||||||
|
{
|
||||||
|
if (!(crs_buffer[0] & 0x80))
|
||||||
|
{
|
||||||
|
const uint8_t name = ((crs_buffer[0] >> 3) & 0x0F);
|
||||||
|
const uint8_t length = (crs_buffer[0] & 0x07);
|
||||||
|
if (crs_buffer.size() < static_cast<size_t>(1 + length))
|
||||||
|
return BAN::Error::from_errno(EINVAL);
|
||||||
|
|
||||||
|
// IRQ Format Descriptor
|
||||||
|
if (name == 0x04)
|
||||||
|
{
|
||||||
|
if (length < 2)
|
||||||
|
return BAN::Error::from_errno(EINVAL);
|
||||||
|
|
||||||
|
const uint16_t irq_mask = crs_buffer[1] | (crs_buffer[2] << 8);
|
||||||
|
if (irq_mask == 0)
|
||||||
|
return BAN::Error::from_errno(EINVAL);
|
||||||
|
|
||||||
|
debug_guard.disable();
|
||||||
|
|
||||||
|
uint8_t irq;
|
||||||
|
for (irq = 0; irq < 16; irq++)
|
||||||
|
if (irq_mask & (1 << irq))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (auto ret = InterruptController::get().reserve_irq(irq); ret.is_error())
|
||||||
|
{
|
||||||
|
dwarnln("FIXME: irq sharing");
|
||||||
|
return ret.release_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
return irq;
|
||||||
|
}
|
||||||
|
|
||||||
|
crs_buffer = crs_buffer.slice(1 + length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (crs_buffer.size() < 3)
|
||||||
|
return BAN::Error::from_errno(EINVAL);
|
||||||
|
const uint8_t name = (crs_buffer[0] & 0x7F);
|
||||||
|
const uint16_t length = (crs_buffer[2] << 8) | crs_buffer[1];
|
||||||
|
if (crs_buffer.size() < static_cast<size_t>(3 + length))
|
||||||
|
return BAN::Error::from_errno(EINVAL);
|
||||||
|
|
||||||
|
// Extended Interrupt Descriptor
|
||||||
|
if (name == 0x09)
|
||||||
|
{
|
||||||
|
if (length < 6 || crs_buffer[4] != 1)
|
||||||
|
return BAN::Error::from_errno(EINVAL);
|
||||||
|
|
||||||
|
const uint32_t irq =
|
||||||
|
(static_cast<uint32_t>(crs_buffer[5]) << 0) |
|
||||||
|
(static_cast<uint32_t>(crs_buffer[6]) << 8) |
|
||||||
|
(static_cast<uint32_t>(crs_buffer[7]) << 16) |
|
||||||
|
(static_cast<uint32_t>(crs_buffer[8]) << 24);
|
||||||
|
|
||||||
|
debug_guard.disable();
|
||||||
|
|
||||||
|
if (auto ret = InterruptController::get().reserve_irq(irq); ret.is_error())
|
||||||
|
{
|
||||||
|
dwarnln("FIXME: irq sharing");
|
||||||
|
return ret.release_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
return irq;
|
||||||
|
}
|
||||||
|
|
||||||
|
crs_buffer = crs_buffer.slice(3 + length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BAN::Error::from_errno(EFAULT);
|
||||||
|
}
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
static BAN::ErrorOr<ACPI::AML::Scope> find_pci_bus(uint16_t seg, uint8_t bus)
|
||||||
|
{
|
||||||
|
constexpr BAN::StringView pci_root_bus_ids[] {
|
||||||
|
"PNP0A03"_sv, // PCI
|
||||||
|
"PNP0A08"_sv, // PCIe
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT(ACPI::ACPI::get().acpi_namespace());
|
||||||
|
auto& acpi_namespace = *ACPI::ACPI::get().acpi_namespace();
|
||||||
|
|
||||||
|
for (const auto eisa_id : pci_root_bus_ids)
|
||||||
|
{
|
||||||
|
auto root_buses = TRY(acpi_namespace.find_device_with_eisa_id(eisa_id));
|
||||||
|
|
||||||
|
for (const auto& root_bus : root_buses)
|
||||||
|
{
|
||||||
|
uint64_t bbn_value = 0;
|
||||||
|
if (auto bbn_node_or_error = acpi_namespace.evaluate(root_bus, "_BBN"_sv); !bbn_node_or_error.is_error())
|
||||||
|
bbn_value = TRY(ACPI::AML::convert_node(bbn_node_or_error.release_value(), ACPI::AML::ConvInteger, -1)).as.integer.value;
|
||||||
|
|
||||||
|
uint64_t seg_value = 0;
|
||||||
|
if (auto seg_node_or_error = acpi_namespace.evaluate(root_bus, "_SEG"_sv); !seg_node_or_error.is_error())
|
||||||
|
seg_value = TRY(ACPI::AML::convert_node(seg_node_or_error.release_value(), ACPI::AML::ConvInteger, -1)).as.integer.value;
|
||||||
|
|
||||||
|
if (seg_value == seg && bbn_value == bus)
|
||||||
|
return TRY(root_bus.copy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BAN::Error::from_errno(ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#if defined(__GNUC__) && !defined(__clang__)
|
||||||
|
#pragma GCC diagnostic ignored "-Wstack-usage="
|
||||||
|
#endif
|
||||||
|
// TODO: maybe move this code to ACPI related file?
|
||||||
|
BAN::ErrorOr<uint8_t> PCI::Device::find_intx_interrupt()
|
||||||
|
{
|
||||||
|
ASSERT(InterruptController::get().is_using_apic());
|
||||||
|
|
||||||
|
const uint32_t acpi_device_id = (static_cast<uint32_t>(m_dev) << 16) | 0xFFFF;
|
||||||
|
const uint8_t acpi_pin = read_byte(0x3D) - 1;
|
||||||
|
if (acpi_pin > 0x03)
|
||||||
|
{
|
||||||
|
dwarnln("PCI device is not using PIN interrupts");
|
||||||
|
return BAN::Error::from_errno(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ACPI::ACPI::get().acpi_namespace() == nullptr)
|
||||||
|
return BAN::Error::from_errno(EFAULT);
|
||||||
|
auto& acpi_namespace = *ACPI::ACPI::get().acpi_namespace();
|
||||||
|
|
||||||
|
// FIXME: support segments
|
||||||
|
auto pci_root_bus = TRY(find_pci_bus(0, m_bus));
|
||||||
|
|
||||||
|
auto prt_node = TRY(acpi_namespace.evaluate(pci_root_bus, "_PRT"));
|
||||||
|
if (prt_node.type != ACPI::AML::Node::Type::Package)
|
||||||
|
{
|
||||||
|
dwarnln("{}\\_PRT did not evaluate to package");
|
||||||
|
return BAN::Error::from_errno(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < prt_node.as.package->num_elements; i++)
|
||||||
|
{
|
||||||
|
if (ACPI::AML::resolve_package_element(prt_node.as.package->elements[i], true).is_error())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto& prt_entry = *prt_node.as.package->elements[i].value.node;
|
||||||
|
if (prt_entry.type != ACPI::AML::Node::Type::Package)
|
||||||
|
continue;
|
||||||
|
if (prt_entry.as.package->num_elements != 4)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool resolved = true;
|
||||||
|
for (size_t j = 0; j < 4 && resolved; j++)
|
||||||
|
if (ACPI::AML::resolve_package_element(prt_entry.as.package->elements[j], true).is_error())
|
||||||
|
resolved = false;
|
||||||
|
if (!resolved)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto& prt_entry_fields = prt_entry.as.package->elements;
|
||||||
|
if (TRY(ACPI::AML::convert_node(TRY(prt_entry_fields[0].value.node->copy()), ACPI::AML::ConvInteger, -1)).as.integer.value != acpi_device_id)
|
||||||
|
return BAN::Error::from_errno(ENOENT);
|
||||||
|
if (TRY(ACPI::AML::convert_node(TRY(prt_entry_fields[1].value.node->copy()), ACPI::AML::ConvInteger, -1)).as.integer.value != acpi_pin)
|
||||||
|
return BAN::Error::from_errno(ENOENT);
|
||||||
|
|
||||||
|
auto ret = route_prt_entry(prt_entry);
|
||||||
|
if (!ret.is_error())
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
dwarnln("No routable PCI interrupt found");
|
||||||
|
return BAN::Error::from_errno(EFAULT);
|
||||||
|
}
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
BAN::ErrorOr<void> PCI::Device::reserve_interrupts(uint8_t count)
|
BAN::ErrorOr<void> PCI::Device::reserve_interrupts(uint8_t count)
|
||||||
{
|
{
|
||||||
// FIXME: Allow "late" interrupt reserving
|
// FIXME: Allow "late" interrupt reserving
|
||||||
ASSERT(m_reserved_interrupt_count == 0);
|
ASSERT(m_reserved_interrupt_count == 0);
|
||||||
|
|
||||||
const auto mechanism =
|
const auto mechanism =
|
||||||
[&]() -> InterruptMechanism
|
[this, count]() -> InterruptMechanism
|
||||||
{
|
{
|
||||||
if (!InterruptController::get().is_using_apic())
|
if (!InterruptController::get().is_using_apic())
|
||||||
{
|
{
|
||||||
|
@ -494,34 +714,46 @@ namespace Kernel::PCI
|
||||||
return InterruptMechanism::NONE;
|
return InterruptMechanism::NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_offset_msi_x.has_value())
|
const bool is_xhci = false && m_class_code == 0x0C && m_subclass == 0x03 && m_prog_if == 0x30;
|
||||||
|
|
||||||
|
if (!is_xhci && m_offset_msi_x.has_value())
|
||||||
{
|
{
|
||||||
const uint16_t msg_ctrl = read_word(*m_offset_msi_x + 0x02);
|
const uint16_t msg_ctrl = read_word(*m_offset_msi_x + 0x02);
|
||||||
if (count <= (msg_ctrl & 0x7FF) + 1)
|
if (count <= (msg_ctrl & 0x7FF) + 1)
|
||||||
return InterruptMechanism::MSIX;
|
return InterruptMechanism::MSIX;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_offset_msi.has_value())
|
if (!is_xhci && m_offset_msi.has_value())
|
||||||
{
|
{
|
||||||
if (count == 1)
|
if (count == 1)
|
||||||
return InterruptMechanism::MSI;
|
return InterruptMechanism::MSI;
|
||||||
// FIXME: support multiple message
|
// FIXME: support multiple MSIs
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: support ioapic
|
if (count == 1)
|
||||||
|
return InterruptMechanism::PIN;
|
||||||
return InterruptMechanism::NONE;
|
return InterruptMechanism::NONE;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
|
if (mechanism == InterruptMechanism::NONE)
|
||||||
|
{
|
||||||
|
dwarnln("No supported interrupt mechanism available");
|
||||||
|
return BAN::Error::from_errno(ENOTSUP);
|
||||||
|
}
|
||||||
|
|
||||||
auto get_interrupt_func =
|
auto get_interrupt_func =
|
||||||
[mechanism]() -> BAN::Optional<uint8_t>
|
[this, mechanism]() -> BAN::Optional<uint8_t>
|
||||||
{
|
{
|
||||||
switch (mechanism)
|
switch (mechanism)
|
||||||
{
|
{
|
||||||
case InterruptMechanism::NONE:
|
case InterruptMechanism::NONE:
|
||||||
return {};
|
ASSERT_NOT_REACHED();
|
||||||
case InterruptMechanism::PIN:
|
case InterruptMechanism::PIN:
|
||||||
return InterruptController::get().get_free_irq();
|
if (!InterruptController::get().is_using_apic())
|
||||||
|
return InterruptController::get().get_free_irq();
|
||||||
|
if (auto ret = find_intx_interrupt(); !ret.is_error())
|
||||||
|
return ret.release_value();
|
||||||
|
return {};
|
||||||
case InterruptMechanism::MSI:
|
case InterruptMechanism::MSI:
|
||||||
case InterruptMechanism::MSIX:
|
case InterruptMechanism::MSIX:
|
||||||
return PCIManager::get().reserve_msi();
|
return PCIManager::get().reserve_msi();
|
||||||
|
@ -534,7 +766,7 @@ namespace Kernel::PCI
|
||||||
const auto irq = get_interrupt_func();
|
const auto irq = get_interrupt_func();
|
||||||
if (!irq.has_value())
|
if (!irq.has_value())
|
||||||
{
|
{
|
||||||
dwarnln("Could not reserve {} MSI(-X) interrupts", count);
|
dwarnln("Could not reserve {} interrupts", count);
|
||||||
return BAN::Error::from_errno(EFAULT);
|
return BAN::Error::from_errno(EFAULT);
|
||||||
}
|
}
|
||||||
const uint8_t byte = irq.value() / 8;
|
const uint8_t byte = irq.value() / 8;
|
||||||
|
|
Loading…
Reference in New Issue