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