]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Allow discovery of PCI bus:dev.fn address ranges
authorMichael Brown <mcb30@ipxe.org>
Wed, 14 Aug 2024 22:40:50 +0000 (23:40 +0100)
committerMichael Brown <mcb30@ipxe.org>
Thu, 15 Aug 2024 08:39:01 +0000 (09:39 +0100)
Generalise the logic for identifying the matching PCI root bridge I/O
protocol to allow for identifying the closest matching PCI bus:dev.fn
address range, and use this to provide PCI address range discovery
(while continuing to inhibit automatic PCI bus probing).

This allows the "pciscan" command to work as expected under UEFI.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/efi/efi_pci_api.h
src/interface/efi/efi_pci.c

index 0c4c1b72c1434af243c4c45d5075d0ca1945ccca..9aca02f65b4f0e71f9f89dbd47bb4bf5b763ee52 100644 (file)
@@ -42,20 +42,6 @@ PCIAPI_INLINE ( efi, pci_can_probe ) ( void ) {
        return 0;
 }
 
-/**
- * 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
  *
index 61071d8a4bb12c4a2bd00612c91afc8da90b1a6e..8d4e085671611fc77dfb98cf569ce0491feeadf7 100644 (file)
@@ -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,37 +205,101 @@ 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
  *
@@ -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 );