From 82cf0bfb7539423cf42b8e255d5ae904c66726d0 Mon Sep 17 00:00:00 2001 From: Pankaj Raghav Date: Fri, 5 May 2023 14:38:46 +0200 Subject: [PATCH] Kernel: Add APIs to PCI Device to use MSI(x) Add reserve_irqs, allocate_irq, enable_interrupt and disable_interrupt API to a PCI device. reserve_irqs() can be used by a device driver that would like to reserve irqs for MSI(x) interrupts. The API returns the type of IRQ that was reserved by the PCI device. If the PCI device does not support MSI(x), then it is a noop. allocate_irq() API can be used to allocate an IRQ at an index. For MSIx the driver needs to map the vector table into the memory and add the corresponding IRQ at the given index. This API will return the actual IRQ that was used so that the driver can use it create interrupt handler for that IRQ. {enable, disable}_interrupt API is used to enable or disable a particular IRQ at the given index. It is a noop for pin-based interrupts. This could be used by IRQHandler to enable or disable an interrupt. --- Kernel/Bus/PCI/Device.cpp | 109 ++++++++++++++++++++++++++++++++++++++ Kernel/Bus/PCI/Device.h | 28 ++++++++++ 2 files changed, 137 insertions(+) diff --git a/Kernel/Bus/PCI/Device.cpp b/Kernel/Bus/PCI/Device.cpp index 3eb7551a70..09e6bc06a6 100644 --- a/Kernel/Bus/PCI/Device.cpp +++ b/Kernel/Bus/PCI/Device.cpp @@ -5,8 +5,11 @@ */ #include +#include +#include #include #include +#include namespace Kernel::PCI { @@ -14,6 +17,8 @@ Device::Device(DeviceIdentifier const& pci_identifier) : m_pci_identifier(pci_identifier) { m_pci_identifier->initialize(); + m_interrupt_range.m_start_irq = m_pci_identifier->interrupt_line().value(); + m_interrupt_range.m_irq_count = 1; } bool Device::is_msi_capable() const @@ -61,4 +66,108 @@ void Device::disable_extended_message_signalled_interrupts() } } +PCI::InterruptType Device::get_interrupt_type() +{ + return m_interrupt_range.m_type; +} + +// Reserve `numbers_of_irqs` for this device. Returns the interrupt type +// that was reserved. It is a noop for pin based interrupts as there +// is nothing left to do. The second parameter `msi` is used by the +// driver to indicate its intent to use message signalled interrupts. +// MSI(x) is preferred over MSI if the device supports both. +ErrorOr Device::reserve_irqs(u8 number_of_irqs, bool msi) +{ + // Let us not allow partial allocation of IRQs for MSIx. + if (msi && is_msix_capable()) { + m_interrupt_range.m_start_irq = TRY(reserve_interrupt_handlers(number_of_irqs)); + m_interrupt_range.m_irq_count = number_of_irqs; + m_interrupt_range.m_type = InterruptType::MSIX; + // If MSIx is available, disable the pin based interrupts + disable_pin_based_interrupts(); + enable_extended_message_signalled_interrupts(); + } else if (msi && is_msi_capable()) { + TODO(); + } + return m_interrupt_range.m_type; +} + +PhysicalAddress Device::msix_table_entry_address(u8 irq) +{ + auto index = static_cast(irq) - m_interrupt_range.m_start_irq; + + VERIFY(index < m_interrupt_range.m_irq_count); + VERIFY(index >= 0); + auto table_bar_ptr = PCI::get_BAR(device_identifier(), static_cast(m_pci_identifier->get_msix_table_bar())) & PCI::bar_address_mask; + auto table_offset = m_pci_identifier->get_msix_table_offset(); + + return PhysicalAddress(table_bar_ptr + table_offset + (index * 16)); +} + +// This function is used to allocate an irq at an index and returns +// the actual IRQ that was programmed at that index. This function is +// mainly useful for MSI/MSIx based interrupt mechanism where the driver +// needs to program. If the PCI device doesn't support MSIx interrupts, then +// this function will just return the irq used for pin based interrupt. +ErrorOr Device::allocate_irq(u8 index) +{ + if (Checked::addition_would_overflow(m_interrupt_range.m_start_irq, index)) + return Error::from_errno(EINVAL); + + if ((m_interrupt_range.m_type == InterruptType::MSIX) && is_msix_capable()) { + auto entry_ptr = TRY(Memory::map_typed_writable(msix_table_entry_address(index + m_interrupt_range.m_start_irq))); + entry_ptr->data = msi_data_register(m_interrupt_range.m_start_irq + index, false, false); + // TODO: we map all the IRQs to cpu 0 by default. We could attach + // cpu affinity in the future where specific LAPIC id could be used. + u64 addr = msi_address_register(0, false, false); + entry_ptr->address_low = addr & 0xffffffff; + entry_ptr->address_high = addr >> 32; + + u32 vector_ctrl = msix_vector_control_register(entry_ptr->vector_control, true); + entry_ptr->vector_control = vector_ctrl; + + return m_interrupt_range.m_start_irq + index; + } else if ((m_interrupt_range.m_type == InterruptType::MSI) && is_msi_capable()) { + TODO(); + } + // For pin based interrupts, we share the IRQ. + return m_interrupt_range.m_start_irq; +} + +void Device::enable_interrupt(u8 irq) +{ + if ((m_interrupt_range.m_type == InterruptType::MSIX) && is_msix_capable()) { + auto entry = Memory::map_typed_writable(PhysicalAddress(msix_table_entry_address(irq))); + + if (entry.is_error()) { + dmesgln_pci(*this, "Unable to map the MSIx table area"); + return; + } + + auto entry_ptr = entry.release_value(); + u32 vector_ctrl = msix_vector_control_register(entry_ptr->vector_control, false); + entry_ptr->vector_control = vector_ctrl; + } else if ((m_interrupt_range.m_type == InterruptType::MSI) && is_msi_capable()) { + TODO(); + } +} + +void Device::disable_interrupt(u8 irq) +{ + if ((m_interrupt_range.m_type == InterruptType::MSIX) && is_msix_capable()) { + auto entry = Memory::map_typed_writable(PhysicalAddress(msix_table_entry_address(irq))); + + if (entry.is_error()) { + dmesgln_pci(*this, "Unable to map the MSIx table area"); + return; + } + + auto entry_ptr = entry.release_value(); + u32 vector_ctrl = msix_vector_control_register(entry_ptr->vector_control, true); + entry_ptr->vector_control = vector_ctrl; + } else if ((m_interrupt_range.m_type == InterruptType::MSI) && is_msi_capable()) { + TODO(); + } +} + } diff --git a/Kernel/Bus/PCI/Device.h b/Kernel/Bus/PCI/Device.h index bb75f75f0a..f6529fe5fc 100644 --- a/Kernel/Bus/PCI/Device.h +++ b/Kernel/Bus/PCI/Device.h @@ -14,6 +14,25 @@ namespace Kernel::PCI { +enum class InterruptType { + PIN, + MSI, + MSIX +}; + +struct InterruptRange { + u8 m_start_irq { 0 }; + u8 m_irq_count { 0 }; + InterruptType m_type { InterruptType::PIN }; +}; + +struct [[gnu::packed]] MSIxTableEntry { + u32 address_low; + u32 address_high; + u32 data; + u32 vector_control; +}; + class Device { public: DeviceIdentifier const& device_identifier() const { return *m_pci_identifier; }; @@ -33,12 +52,21 @@ public: void enable_extended_message_signalled_interrupts(); void disable_extended_message_signalled_interrupts(); + ErrorOr reserve_irqs(u8 number_of_irqs, bool msi); + ErrorOr allocate_irq(u8 index); + PCI::InterruptType get_interrupt_type(); + void enable_interrupt(u8 irq); + void disable_interrupt(u8 irq); protected: explicit Device(DeviceIdentifier const& pci_identifier); +private: + PhysicalAddress msix_table_entry_address(u8 irq); + private: NonnullRefPtr const m_pci_identifier; + InterruptRange m_interrupt_range; }; template