/* * Copyright (C) 2022 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 /** @file * * PCI Enhanced Configuration Access Mechanism (ECAM) * */ /** Cached mapped ECAM allocation */ static struct ecam_mapping ecam; /** * Find lowest ECAM allocation not below a given PCI bus:dev.fn address * * @v busdevfn PCI bus:dev.fn address * @v range PCI device address range to fill in * @v alloc ECAM allocation to fill in, or NULL * @ret rc Return status code */ static int ecam_find ( uint32_t busdevfn, struct pci_range *range, struct ecam_allocation *alloc ) { struct ecam_allocation tmp; unsigned int best = 0; unsigned int offset; unsigned int count; unsigned int index; userptr_t mcfg; uint32_t length; uint32_t start; /* Return empty range on error */ range->count = 0; /* Locate MCFG table */ mcfg = acpi_table ( ECAM_SIGNATURE, 0 ); if ( ! mcfg ) { DBGC ( &ecam, "ECAM found no MCFG table\n" ); return -ENOTSUP; } /* Get length of table */ copy_from_user ( &length, mcfg, offsetof ( struct ecam_table, acpi.length ), sizeof ( length ) ); /* Iterate over allocations */ for ( offset = offsetof ( struct ecam_table, alloc ) ; ( offset + sizeof ( tmp ) ) <= le32_to_cpu ( length ) ; offset += sizeof ( tmp ) ) { /* Read allocation */ copy_from_user ( &tmp, mcfg, offset, sizeof ( tmp ) ); DBGC2 ( &ecam, "ECAM %04x:[%02x-%02x] has base %08llx\n", le16_to_cpu ( tmp.segment ), tmp.start, tmp.end, ( ( unsigned long long ) le64_to_cpu ( tmp.base ) ) ); start = PCI_BUSDEVFN ( le16_to_cpu ( tmp.segment ), tmp.start, 0, 0 ); count = PCI_BUSDEVFN ( 0, ( tmp.end - tmp.start + 1 ), 0, 0 ); /* Check for a matching or new closest allocation */ index = ( busdevfn - start ); if ( ( index < count ) || ( index > best ) ) { if ( alloc ) memcpy ( alloc, &tmp, sizeof ( *alloc ) ); range->start = start; range->count = count; best = index; } /* Stop if this range contains the target bus:dev.fn address */ if ( index < count ) return 0; } return ( best ? 0 : -ENOENT ); } /** * Find next PCI bus:dev.fn address range in system * * @v busdevfn Starting PCI bus:dev.fn address * @v range PCI bus:dev.fn address range to fill in */ static void ecam_discover ( uint32_t busdevfn, struct pci_range *range ) { /* Find new range, if any */ ecam_find ( busdevfn, range, NULL ); } /** * Access configuration space for PCI device * * @v pci PCI device * @ret rc Return status code */ static int ecam_access ( struct pci_device *pci ) { uint64_t base; size_t len; int rc; /* Reuse mapping if possible */ if ( ( pci->busdevfn - ecam.range.start ) < ecam.range.count ) return 0; /* Clear any existing mapping */ if ( ecam.regs ) { iounmap ( ecam.regs ); ecam.regs = NULL; } /* Find allocation for this PCI device */ if ( ( rc = ecam_find ( pci->busdevfn, &ecam.range, &ecam.alloc ) ) != 0 ) { DBGC ( &ecam, "ECAM found no allocation for " PCI_FMT ": %s\n", PCI_ARGS ( pci ), strerror ( rc ) ); goto err_find; } if ( ecam.range.start > pci->busdevfn ) { DBGC ( &ecam, "ECAM found no allocation for " PCI_FMT "\n", PCI_ARGS ( pci ) ); goto err_find; } /* Map configuration space for this allocation */ base = le64_to_cpu ( ecam.alloc.base ); len = ( ecam.range.count * ECAM_SIZE ); ecam.regs = ioremap ( base, len ); if ( ! ecam.regs ) { DBGC ( &ecam, "ECAM %04x:[%02x-%02x] could not map " "[%08llx,%08llx)\n", le16_to_cpu ( ecam.alloc.segment ), ecam.alloc.start, ecam.alloc.end, base, ( base + len ) ); rc = -ENODEV; goto err_ioremap; } /* Populate cached mapping */ DBGC ( &ecam, "ECAM %04x:[%02x-%02x] mapped [%08llx,%08llx) -> %p\n", le16_to_cpu ( ecam.alloc.segment ), ecam.alloc.start, ecam.alloc.end, base, ( base + len ), ecam.regs ); return 0; iounmap ( ecam.regs ); err_ioremap: err_find: ecam.range.count = 0; return rc; } /** * Read from PCI configuration space * * @v pci PCI device * @v location Offset and length within PCI configuration space * @v value Value read * @ret rc Return status code */ int ecam_read ( struct pci_device *pci, unsigned int location, void *value ) { unsigned int where = ECAM_WHERE ( location ); unsigned int len = ECAM_LEN ( location ); unsigned int index; void *addr; int rc; /* Return all-ones on error */ memset ( value, 0xff, len ); /* Access configuration space */ if ( ( rc = ecam_access ( pci ) ) != 0 ) return rc; /* Read from address */ index = ( pci->busdevfn - ecam.range.start ); addr = ( ecam.regs + ( index * ECAM_SIZE ) + where ); switch ( len ) { case 4: *( ( uint32_t *) value ) = readl ( addr ); break; case 2: *( ( uint16_t *) value ) = readw ( addr ); break; case 1: *( ( uint8_t *) value ) = readb ( addr ); break; default: assert ( 0 ); } return 0; } /** * Write to PCI configuration space * * @v pci PCI device * @v location Offset and length within PCI configuration space * @v value Value to write * @ret rc Return status code */ int ecam_write ( struct pci_device *pci, unsigned int location, unsigned long value ) { unsigned int where = ECAM_WHERE ( location ); unsigned int len = ECAM_LEN ( location ); unsigned int index; void *addr; int rc; /* Access configuration space */ if ( ( rc = ecam_access ( pci ) ) != 0 ) return rc; /* Read from address */ index = ( pci->busdevfn - ecam.range.start ); addr = ( ecam.regs + ( index * ECAM_SIZE ) + where ); switch ( len ) { case 4: writel ( value, addr ); break; case 2: writew ( value, addr ); break; case 1: writeb ( value, addr ); break; default: assert ( 0 ); } return 0; } PROVIDE_PCIAPI ( ecam, pci_discover, ecam_discover ); PROVIDE_PCIAPI_INLINE ( ecam, pci_read_config_byte ); PROVIDE_PCIAPI_INLINE ( ecam, pci_read_config_word ); PROVIDE_PCIAPI_INLINE ( ecam, pci_read_config_dword ); PROVIDE_PCIAPI_INLINE ( ecam, pci_write_config_byte ); PROVIDE_PCIAPI_INLINE ( ecam, pci_write_config_word ); PROVIDE_PCIAPI_INLINE ( ecam, pci_write_config_dword ); PROVIDE_PCIAPI_INLINE ( ecam, pci_ioremap ); struct pci_api ecam_api = PCIAPI_RUNTIME ( ecam );