summaryrefslogtreecommitdiffstats
path: root/src/drivers/bus/pcimsix.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers/bus/pcimsix.c')
-rw-r--r--src/drivers/bus/pcimsix.c251
1 files changed, 251 insertions, 0 deletions
diff --git a/src/drivers/bus/pcimsix.c b/src/drivers/bus/pcimsix.c
new file mode 100644
index 00000000..80893c41
--- /dev/null
+++ b/src/drivers/bus/pcimsix.c
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2019 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <ipxe/pci.h>
+#include <ipxe/pcimsix.h>
+
+/** @file
+ *
+ * PCI MSI-X interrupts
+ *
+ */
+
+/**
+ * Get MSI-X descriptor name (for debugging)
+ *
+ * @v cfg Configuration space offset
+ * @ret name Descriptor name
+ */
+static const char * pci_msix_name ( unsigned int cfg ) {
+
+ switch ( cfg ) {
+ case PCI_MSIX_DESC_TABLE: return "table";
+ case PCI_MSIX_DESC_PBA: return "PBA";
+ default: return "<UNKNOWN>";
+ }
+}
+
+/**
+ * Map MSI-X BAR portion
+ *
+ * @v pci PCI device
+ * @v msix MSI-X capability
+ * @v cfg Configuration space offset
+ * @ret io I/O address
+ */
+static void * pci_msix_ioremap ( struct pci_device *pci, struct pci_msix *msix,
+ unsigned int cfg ) {
+ uint32_t desc;
+ unsigned int bar;
+ unsigned long start;
+ unsigned long offset;
+ unsigned long base;
+ void *io;
+
+ /* Read descriptor */
+ pci_read_config_dword ( pci, ( msix->cap + cfg ), &desc );
+
+ /* Get BAR */
+ bar = PCI_MSIX_DESC_BIR ( desc );
+ offset = PCI_MSIX_DESC_OFFSET ( desc );
+ start = pci_bar_start ( pci, PCI_BASE_ADDRESS ( bar ) );
+ if ( ! start ) {
+ DBGC ( msix, "MSI-X %p %s could not find BAR%d\n",
+ msix, pci_msix_name ( cfg ), bar );
+ return NULL;
+ }
+ base = ( start + offset );
+ DBGC ( msix, "MSI-X %p %s at %#08lx (BAR%d+%#lx)\n",
+ msix, pci_msix_name ( cfg ), base, bar, offset );
+
+ /* Map BAR portion */
+ io = ioremap ( ( start + offset ), PCI_MSIX_LEN );
+ if ( ! io ) {
+ DBGC ( msix, "MSI-X %p %s could not map %#08lx\n",
+ msix, pci_msix_name ( cfg ), base );
+ return NULL;
+ }
+
+ return io;
+}
+
+/**
+ * Enable MSI-X interrupts
+ *
+ * @v pci PCI device
+ * @v msix MSI-X capability
+ * @ret rc Return status code
+ */
+int pci_msix_enable ( struct pci_device *pci, struct pci_msix *msix ) {
+ uint16_t ctrl;
+ int rc;
+
+ /* Locate capability */
+ msix->cap = pci_find_capability ( pci, PCI_CAP_ID_MSIX );
+ if ( ! msix->cap ) {
+ DBGC ( msix, "MSI-X %p found no MSI-X capability in "
+ PCI_FMT "\n", msix, PCI_ARGS ( pci ) );
+ rc = -ENOENT;
+ goto err_cap;
+ }
+
+ /* Extract interrupt count */
+ pci_read_config_word ( pci, ( msix->cap + PCI_MSIX_CTRL ), &ctrl );
+ msix->count = ( PCI_MSIX_CTRL_SIZE ( ctrl ) + 1 );
+ DBGC ( msix, "MSI-X %p has %d vectors for " PCI_FMT "\n",
+ msix, msix->count, PCI_ARGS ( pci ) );
+
+ /* Map MSI-X table */
+ msix->table = pci_msix_ioremap ( pci, msix, PCI_MSIX_DESC_TABLE );
+ if ( ! msix->table ) {
+ rc = -ENOENT;
+ goto err_table;
+ }
+
+ /* Map pending bit array */
+ msix->pba = pci_msix_ioremap ( pci, msix, PCI_MSIX_DESC_PBA );
+ if ( ! msix->pba ) {
+ rc = -ENOENT;
+ goto err_pba;
+ }
+
+ /* Enable MSI-X */
+ ctrl &= ~PCI_MSIX_CTRL_MASK;
+ ctrl |= PCI_MSIX_CTRL_ENABLE;
+ pci_write_config_word ( pci, ( msix->cap + PCI_MSIX_CTRL ), ctrl );
+
+ return 0;
+
+ iounmap ( msix->pba );
+ err_pba:
+ iounmap ( msix->table );
+ err_table:
+ err_cap:
+ return rc;
+}
+
+/**
+ * Disable MSI-X interrupts
+ *
+ * @v pci PCI device
+ * @v msix MSI-X capability
+ */
+void pci_msix_disable ( struct pci_device *pci, struct pci_msix *msix ) {
+ uint16_t ctrl;
+
+ /* Disable MSI-X */
+ pci_read_config_word ( pci, ( msix->cap + PCI_MSIX_CTRL ), &ctrl );
+ ctrl &= ~PCI_MSIX_CTRL_ENABLE;
+ pci_write_config_word ( pci, ( msix->cap + PCI_MSIX_CTRL ), ctrl );
+
+ /* Unmap pending bit array */
+ iounmap ( msix->pba );
+
+ /* Unmap MSI-X table */
+ iounmap ( msix->table );
+}
+
+/**
+ * Map MSI-X interrupt vector
+ *
+ * @v msix MSI-X capability
+ * @v vector MSI-X vector
+ * @v address Message address
+ * @v data Message data
+ */
+void pci_msix_map ( struct pci_msix *msix, unsigned int vector,
+ physaddr_t address, uint32_t data ) {
+ void *base;
+
+ /* Sanity check */
+ assert ( vector < msix->count );
+
+ /* Map interrupt vector */
+ base = ( msix->table + PCI_MSIX_VECTOR ( vector ) );
+ writel ( ( address & 0xffffffffUL ), ( base + PCI_MSIX_ADDRESS_LO ) );
+ if ( sizeof ( address ) > sizeof ( uint32_t ) ) {
+ writel ( ( ( ( uint64_t ) address ) >> 32 ),
+ ( base + PCI_MSIX_ADDRESS_HI ) );
+ } else {
+ writel ( 0, ( base + PCI_MSIX_ADDRESS_HI ) );
+ }
+ writel ( data, ( base + PCI_MSIX_DATA ) );
+}
+
+/**
+ * Control MSI-X interrupt vector
+ *
+ * @v msix MSI-X capability
+ * @v vector MSI-X vector
+ * @v mask Control mask
+ */
+void pci_msix_control ( struct pci_msix *msix, unsigned int vector,
+ uint32_t mask ) {
+ void *base;
+ uint32_t ctrl;
+
+ /* Mask/unmask interrupt vector */
+ base = ( msix->table + PCI_MSIX_VECTOR ( vector ) );
+ ctrl = readl ( base + PCI_MSIX_CONTROL );
+ ctrl &= ~PCI_MSIX_CONTROL_MASK;
+ ctrl |= mask;
+ writel ( ctrl, ( base + PCI_MSIX_CONTROL ) );
+}
+
+/**
+ * Dump MSI-X interrupt state (for debugging)
+ *
+ * @v msix MSI-X capability
+ * @v vector MSI-X vector
+ */
+void pci_msix_dump ( struct pci_msix *msix, unsigned int vector ) {
+ void *base;
+ uint32_t address_hi;
+ uint32_t address_lo;
+ physaddr_t address;
+ uint32_t data;
+ uint32_t ctrl;
+ uint32_t pba;
+
+ /* Do nothing in non-debug builds */
+ if ( ! DBG_LOG )
+ return;
+
+ /* Mask/unmask interrupt vector */
+ base = ( msix->table + PCI_MSIX_VECTOR ( vector ) );
+ address_hi = readl ( base + PCI_MSIX_ADDRESS_HI );
+ address_lo = readl ( base + PCI_MSIX_ADDRESS_LO );
+ data = readl ( base + PCI_MSIX_DATA );
+ ctrl = readl ( base + PCI_MSIX_CONTROL );
+ pba = readl ( msix->pba );
+ address = ( ( ( ( uint64_t ) address_hi ) << 32 ) | address_lo );
+ DBGC ( msix, "MSI-X %p vector %d %#08x => %#08lx%s%s\n",
+ msix, vector, data, address,
+ ( ( ctrl & PCI_MSIX_CONTROL_MASK ) ? " (masked)" : "" ),
+ ( ( pba & ( 1 << vector ) ) ? " (pending)" : "" ) );
+}