diff options
-rw-r--r-- | src/drivers/bus/ecam.c | 265 | ||||
-rw-r--r-- | src/include/ipxe/ecam.h | 55 | ||||
-rw-r--r-- | src/include/ipxe/ecam_io.h | 139 | ||||
-rw-r--r-- | src/include/ipxe/errfile.h | 1 | ||||
-rw-r--r-- | src/include/ipxe/pci_io.h | 1 |
5 files changed, 461 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 ); diff --git a/src/include/ipxe/ecam.h b/src/include/ipxe/ecam.h new file mode 100644 index 000000000..0f0fbf4bf --- /dev/null +++ b/src/include/ipxe/ecam.h @@ -0,0 +1,55 @@ +#ifndef _IPXE_ECAM_H +#define _IPXE_ECAM_H + +/** @file + * + * PCI I/O API for Enhanced Configuration Access Mechanism (ECAM) + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/acpi.h> +#include <ipxe/pci.h> + +/** Enhanced Configuration Access Mechanism per-device size */ +#define ECAM_SIZE 4096 + +/** Enhanced Configuration Access Mechanism table signature */ +#define ECAM_SIGNATURE ACPI_SIGNATURE ( 'M', 'C', 'F', 'G' ) + +/** An Enhanced Configuration Access Mechanism allocation */ +struct ecam_allocation { + /** Base address */ + uint64_t base; + /** PCI segment number */ + uint16_t segment; + /** Start PCI bus number */ + uint8_t start; + /** End PCI bus number */ + uint8_t end; + /** Reserved */ + uint8_t reserved[4]; +} __attribute__ (( packed )); + +/** An Enhanced Configuration Access Mechanism table */ +struct ecam_table { + /** ACPI header */ + struct acpi_header acpi; + /** Reserved */ + uint8_t reserved[8]; + /** Allocation structures */ + struct ecam_allocation alloc[0]; +} __attribute__ (( packed )); + +/** A mapped Enhanced Configuration Access Mechanism allocation */ +struct ecam_mapping { + /** Allocation */ + struct ecam_allocation alloc; + /** PCI bus:dev.fn address range */ + struct pci_range range; + /** MMIO base address */ + void *regs; +}; + +#endif /* _IPXE_ECAM_H */ diff --git a/src/include/ipxe/ecam_io.h b/src/include/ipxe/ecam_io.h new file mode 100644 index 000000000..4fb24db33 --- /dev/null +++ b/src/include/ipxe/ecam_io.h @@ -0,0 +1,139 @@ +#ifndef _IPXE_ECAM_IO_H +#define _IPXE_ECAM_IO_H + +/** @file + * + * PCI I/O API for Enhanced Configuration Access Mechanism (ECAM) + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> + +#ifdef PCIAPI_ECAM +#define PCIAPI_PREFIX_ecam +#else +#define PCIAPI_PREFIX_ecam __ecam_ +#endif + +struct pci_device; + +/** Construct ECAM location */ +#define ECAM_LOC( where, len ) ( ( (len) << 16 ) | where ) + +/** Extract offset from ECAM location */ +#define ECAM_WHERE( location ) ( (location) & 0xffff ) + +/** Extract length from ECAM location */ +#define ECAM_LEN( location ) ( (location) >> 16 ) + +extern int ecam_read ( struct pci_device *pci, unsigned int location, + void *value ); +extern int ecam_write ( struct pci_device *pci, unsigned int location, + unsigned long value ); + +/** + * Read byte from PCI configuration space via ECAM + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __always_inline int +PCIAPI_INLINE ( ecam, pci_read_config_byte ) ( struct pci_device *pci, + unsigned int where, + uint8_t *value ) { + return ecam_read ( pci, ECAM_LOC ( where, sizeof ( *value ) ), value ); +} + +/** + * Read word from PCI configuration space via ECAM + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __always_inline int +PCIAPI_INLINE ( ecam, pci_read_config_word ) ( struct pci_device *pci, + unsigned int where, + uint16_t *value ) { + return ecam_read ( pci, ECAM_LOC ( where, sizeof ( *value ) ), value ); +} + +/** + * Read dword from PCI configuration space via ECAM + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value read + * @ret rc Return status code + */ +static inline __always_inline int +PCIAPI_INLINE ( ecam, pci_read_config_dword ) ( struct pci_device *pci, + unsigned int where, + uint32_t *value ) { + return ecam_read ( pci, ECAM_LOC ( where, sizeof ( *value ) ), value ); +} + +/** + * Write byte to PCI configuration space via ECAM + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __always_inline int +PCIAPI_INLINE ( ecam, pci_write_config_byte ) ( struct pci_device *pci, + unsigned int where, + uint8_t value ) { + return ecam_write ( pci, ECAM_LOC ( where, sizeof ( value ) ), value ); +} + +/** + * Write word to PCI configuration space via ECAM + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __always_inline int +PCIAPI_INLINE ( ecam, pci_write_config_word ) ( struct pci_device *pci, + unsigned int where, + uint16_t value ) { + return ecam_write ( pci, ECAM_LOC ( where, sizeof ( value ) ), value ); +} + +/** + * Write dword to PCI configuration space via ECAM + * + * @v pci PCI device + * @v where Location within PCI configuration space + * @v value Value to be written + * @ret rc Return status code + */ +static inline __always_inline int +PCIAPI_INLINE ( ecam, pci_write_config_dword ) ( struct pci_device *pci, + unsigned int where, + uint32_t value ) { + return ecam_write ( pci, ECAM_LOC ( where, sizeof ( value ) ), value ); +} + +/** + * Map PCI bus address as an I/O address + * + * @v bus_addr PCI bus address + * @v len Length of region + * @ret io_addr I/O address, or NULL on error + */ +static inline __always_inline void * +PCIAPI_INLINE ( ecam, pci_ioremap ) ( struct pci_device *pci __unused, + unsigned long bus_addr, size_t len ) { + return ioremap ( bus_addr, len ); +} + +#endif /* _IPXE_ECAM_IO_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 9b955e574..2b8adbfd6 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -216,6 +216,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_slirp ( ERRFILE_DRIVER | 0x00d00000 ) #define ERRFILE_rdc ( ERRFILE_DRIVER | 0x00d10000 ) #define ERRFILE_ice ( ERRFILE_DRIVER | 0x00d20000 ) +#define ERRFILE_ecam ( ERRFILE_DRIVER | 0x00d30000 ) #define ERRFILE_aoe ( ERRFILE_NET | 0x00000000 ) #define ERRFILE_arp ( ERRFILE_NET | 0x00010000 ) diff --git a/src/include/ipxe/pci_io.h b/src/include/ipxe/pci_io.h index 91359cec8..35d16f95e 100644 --- a/src/include/ipxe/pci_io.h +++ b/src/include/ipxe/pci_io.h @@ -58,6 +58,7 @@ struct pci_range { PROVIDE_SINGLE_API_INLINE ( PCIAPI_PREFIX_ ## _subsys, _api_func ) /* Include all architecture-independent I/O API headers */ +#include <ipxe/ecam_io.h> #include <ipxe/efi/efi_pci_api.h> #include <ipxe/linux/linux_pci.h> |