aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/include/ipxe/efi/efi_pci_api.h14
-rw-r--r--src/interface/efi/efi_pci.c240
2 files changed, 176 insertions, 78 deletions
diff --git a/src/include/ipxe/efi/efi_pci_api.h b/src/include/ipxe/efi/efi_pci_api.h
index 0c4c1b72c..9aca02f65 100644
--- a/src/include/ipxe/efi/efi_pci_api.h
+++ b/src/include/ipxe/efi/efi_pci_api.h
@@ -43,20 +43,6 @@ PCIAPI_INLINE ( efi, pci_can_probe ) ( void ) {
}
/**
- * 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 inline __always_inline void
-PCIAPI_INLINE ( efi, pci_discover ) ( uint32_t busdevfn __unused,
- struct pci_range *range ) {
-
- /* EFI does not want us to scan the PCI bus ourselves */
- range->count = 0;
-}
-
-/**
* Read byte from PCI configuration space via EFI
*
* @v pci PCI device
diff --git a/src/interface/efi/efi_pci.c b/src/interface/efi/efi_pci.c
index 61071d8a4..8d4e08567 100644
--- a/src/interface/efi/efi_pci.c
+++ b/src/interface/efi/efi_pci.c
@@ -63,91 +63,139 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*/
/**
- * Check for a matching PCI root bridge I/O protocol
+ * Find closest bus:dev.fn address range within a root bridge
*
- * @v pci PCI device
+ * @v pci Starting PCI device
* @v handle EFI PCI root bridge handle
- * @v root EFI PCI root bridge I/O protocol
+ * @v range PCI bus:dev.fn address range to fill in
* @ret rc Return status code
*/
-static int efipci_root_match ( struct pci_device *pci, EFI_HANDLE handle,
- EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *root ) {
+static int efipci_discover_one ( struct pci_device *pci, EFI_HANDLE handle,
+ struct pci_range *range ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ union {
+ void *interface;
+ EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *root;
+ } root;
union {
union acpi_resource *res;
void *raw;
- } u;
- unsigned int segment = PCI_SEG ( pci->busdevfn );
- unsigned int bus = PCI_BUS ( pci->busdevfn );
- unsigned int start;
- unsigned int end;
+ } acpi;
+ uint32_t best = 0;
+ uint32_t start;
+ uint32_t count;
+ uint32_t index;
unsigned int tag;
EFI_STATUS efirc;
int rc;
- /* Check segment number */
- if ( root->SegmentNumber != segment )
- return -ENOENT;
+ /* Return empty range on error */
+ range->start = 0;
+ range->count = 0;
+
+ /* Open root bridge I/O protocol */
+ if ( ( efirc = bs->OpenProtocol ( handle,
+ &efi_pci_root_bridge_io_protocol_guid,
+ &root.interface, efi_image_handle, handle,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( pci, "EFIPCI " PCI_FMT " cannot open %s: %s\n",
+ PCI_ARGS ( pci ), efi_handle_name ( handle ),
+ strerror ( rc ) );
+ goto err_open;
+ }
/* Get ACPI resource descriptors */
- if ( ( efirc = root->Configuration ( root, &u.raw ) ) != 0 ) {
+ if ( ( efirc = root.root->Configuration ( root.root,
+ &acpi.raw ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( pci, "EFIPCI " PCI_FMT " cannot get configuration for "
"%s: %s\n", PCI_ARGS ( pci ),
efi_handle_name ( handle ), strerror ( rc ) );
- return rc;
+ goto err_config;
}
- /* Assume success if no bus number range descriptors are found */
- rc = 0;
-
/* Parse resource descriptors */
- for ( ; ( ( tag = acpi_resource_tag ( u.res ) ) != ACPI_END_RESOURCE ) ;
- u.res = acpi_resource_next ( u.res ) ) {
+ for ( ; ( ( tag = acpi_resource_tag ( acpi.res ) ) !=
+ ACPI_END_RESOURCE ) ;
+ acpi.res = acpi_resource_next ( acpi.res ) ) {
/* Ignore anything other than a bus number range descriptor */
if ( tag != ACPI_QWORD_ADDRESS_SPACE_RESOURCE )
continue;
- if ( u.res->qword.type != ACPI_ADDRESS_TYPE_BUS )
+ if ( acpi.res->qword.type != ACPI_ADDRESS_TYPE_BUS )
continue;
- /* Check for a matching bus number */
- start = le64_to_cpu ( u.res->qword.min );
- end = ( start + le64_to_cpu ( u.res->qword.len ) );
- if ( ( bus >= start ) && ( bus < end ) )
- return 0;
+ /* Get range for this descriptor */
+ start = PCI_BUSDEVFN ( root.root->SegmentNumber,
+ le64_to_cpu ( acpi.res->qword.min ),
+ 0, 0 );
+ count = PCI_BUSDEVFN ( 0, le64_to_cpu ( acpi.res->qword.len ),
+ 0, 0 );
+ DBGC2 ( pci, "EFIPCI " PCI_FMT " found %04x:[%02x-%02x] via "
+ "%s\n", PCI_ARGS ( pci ), root.root->SegmentNumber,
+ PCI_BUS ( start ), PCI_BUS ( start + count - 1 ),
+ efi_handle_name ( handle ) );
+
+ /* Check for a matching or new closest range */
+ index = ( pci->busdevfn - start );
+ if ( ( index < count ) || ( index > best ) ) {
+ range->start = start;
+ range->count = count;
+ best = index;
+ }
- /* We have seen at least one non-matching range
- * descriptor, so assume failure unless we find a
- * subsequent match.
- */
- rc = -ENOENT;
+ /* Stop if this range contains the target bus:dev.fn address */
+ if ( index < count )
+ break;
+ }
+
+ /* If no range descriptors were seen, assume that the root
+ * bridge has a single bus.
+ */
+ if ( ! range->count ) {
+ range->start = PCI_BUSDEVFN ( root.root->SegmentNumber,
+ 0, 0, 0 );
+ range->count = PCI_BUSDEVFN ( 0, 1, 0, 0 );
}
+ /* Success */
+ rc = 0;
+
+ err_config:
+ bs->CloseProtocol ( handle, &efi_pci_root_bridge_io_protocol_guid,
+ efi_image_handle, handle );
+ err_open:
return rc;
}
/**
- * Open EFI PCI root bridge I/O protocol
+ * Find closest bus:dev.fn address range within any root bridge
*
- * @v pci PCI device
- * @ret handle EFI PCI root bridge handle
- * @ret root EFI PCI root bridge I/O protocol, or NULL if not found
+ * @v pci Starting PCI device
+ * @v range PCI bus:dev.fn address range to fill in
+ * @v handle PCI root bridge I/O handle to fill in
* @ret rc Return status code
*/
-static int efipci_root_open ( struct pci_device *pci, EFI_HANDLE *handle,
- EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL **root ) {
+static int efipci_discover_any ( struct pci_device *pci,
+ struct pci_range *range,
+ EFI_HANDLE *handle ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ uint32_t best = 0;
+ uint32_t index;
+ struct pci_range tmp;
EFI_HANDLE *handles;
UINTN num_handles;
- union {
- void *interface;
- EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *root;
- } u;
- EFI_STATUS efirc;
UINTN i;
+ EFI_STATUS efirc;
int rc;
- /* Enumerate all handles */
+ /* Return an empty range and no handle on error */
+ range->start = 0;
+ range->count = 0;
+ *handle = NULL;
+
+ /* Enumerate all root bridge I/O protocol handles */
if ( ( efirc = bs->LocateHandleBuffer ( ByProtocol,
&efi_pci_root_bridge_io_protocol_guid,
NULL, &num_handles, &handles ) ) != 0 ) {
@@ -157,38 +205,102 @@ static int efipci_root_open ( struct pci_device *pci, EFI_HANDLE *handle,
goto err_locate;
}
- /* Look for matching root bridge I/O protocol */
+ /* Iterate over all root bridge I/O protocols */
for ( i = 0 ; i < num_handles ; i++ ) {
- *handle = handles[i];
- if ( ( efirc = bs->OpenProtocol ( *handle,
- &efi_pci_root_bridge_io_protocol_guid,
- &u.interface, efi_image_handle, *handle,
- EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) != 0 ) {
- rc = -EEFI ( efirc );
- DBGC ( pci, "EFIPCI " PCI_FMT " cannot open %s: %s\n",
- PCI_ARGS ( pci ), efi_handle_name ( *handle ),
- strerror ( rc ) );
+
+ /* Get matching or closest range for this root bridge */
+ if ( ( rc = efipci_discover_one ( pci, handles[i],
+ &tmp ) ) != 0 )
continue;
+
+ /* Check for a matching or new closest range */
+ index = ( pci->busdevfn - tmp.start );
+ if ( ( index < tmp.count ) || ( index > best ) ) {
+ range->start = tmp.start;
+ range->count = tmp.count;
+ best = index;
}
- if ( efipci_root_match ( pci, *handle, u.root ) == 0 ) {
- *root = u.root;
- bs->FreePool ( handles );
- return 0;
+
+ /* Stop if this range contains the target bus:dev.fn address */
+ if ( index < tmp.count ) {
+ *handle = handles[i];
+ break;
}
- bs->CloseProtocol ( *handle,
- &efi_pci_root_bridge_io_protocol_guid,
- efi_image_handle, *handle );
}
- DBGC ( pci, "EFIPCI " PCI_FMT " found no root bridge\n",
- PCI_ARGS ( pci ) );
- rc = -ENOENT;
+ /* Check for a range containing the target bus:dev.fn address */
+ if ( ! *handle ) {
+ rc = -ENOENT;
+ goto err_range;
+ }
+
+ /* Success */
+ rc = 0;
+
+ err_range:
bs->FreePool ( handles );
err_locate:
return rc;
}
/**
+ * 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 efipci_discover ( uint32_t busdevfn, struct pci_range *range ) {
+ struct pci_device pci;
+ EFI_HANDLE handle;
+
+ /* Find range */
+ memset ( &pci, 0, sizeof ( pci ) );
+ pci_init ( &pci, busdevfn );
+ efipci_discover_any ( &pci, range, &handle );
+}
+
+/**
+ * Open EFI PCI root bridge I/O protocol
+ *
+ * @v pci PCI device
+ * @ret handle EFI PCI root bridge handle
+ * @ret root EFI PCI root bridge I/O protocol, or NULL if not found
+ * @ret rc Return status code
+ */
+static int efipci_root_open ( struct pci_device *pci, EFI_HANDLE *handle,
+ EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL **root ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ struct pci_range tmp;
+ union {
+ void *interface;
+ EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *root;
+ } u;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Find matching root bridge I/O protocol handle */
+ if ( ( rc = efipci_discover_any ( pci, &tmp, handle ) ) != 0 )
+ return rc;
+
+ /* (Re)open PCI root bridge I/O protocol */
+ if ( ( efirc = bs->OpenProtocol ( *handle,
+ &efi_pci_root_bridge_io_protocol_guid,
+ &u.interface, efi_image_handle, *handle,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( pci, "EFIPCI " PCI_FMT " cannot open %s: %s\n",
+ PCI_ARGS ( pci ), efi_handle_name ( *handle ),
+ strerror ( rc ) );
+ return rc;
+ }
+
+ /* Return opened protocol */
+ *root = u.root;
+
+ return 0;
+}
+
+/**
* Close EFI PCI root bridge I/O protocol
*
* @v handle EFI PCI root bridge handle
@@ -363,7 +475,7 @@ void * efipci_ioremap ( struct pci_device *pci, unsigned long bus_addr,
}
PROVIDE_PCIAPI_INLINE ( efi, pci_can_probe );
-PROVIDE_PCIAPI_INLINE ( efi, pci_discover );
+PROVIDE_PCIAPI ( efi, pci_discover, efipci_discover );
PROVIDE_PCIAPI_INLINE ( efi, pci_read_config_byte );
PROVIDE_PCIAPI_INLINE ( efi, pci_read_config_word );
PROVIDE_PCIAPI_INLINE ( efi, pci_read_config_dword );