diff options
Diffstat (limited to 'src/drivers/bus/ecam.c')
| -rw-r--r-- | src/drivers/bus/ecam.c | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/src/drivers/bus/ecam.c b/src/drivers/bus/ecam.c new file mode 100644 index 000000000..f7ba2db7f --- /dev/null +++ b/src/drivers/bus/ecam.c @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2022 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 <errno.h> +#include <ipxe/uaccess.h> +#include <ipxe/ecam.h> + +/** @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 ); |
