/* * Copyright (C) 2019 Michael Brown . * * 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 #include #include #include #include /** @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 ""; } } /** * 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)" : "" ) ); }