Skip to content
This repository has been archived by the owner on Aug 29, 2024. It is now read-only.

Commit

Permalink
[efi] Allow discovery of PCI bus:dev.fn address ranges
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
mcb30 committed Aug 15, 2024
1 parent 7c82ff0 commit 950f6b5
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 78 deletions.
14 changes: 0 additions & 14 deletions src/include/ipxe/efi/efi_pci_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
240 changes: 176 additions & 64 deletions src/interface/efi/efi_pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand All @@ -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
*
Expand Down Expand Up @@ -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 );
Expand Down

0 comments on commit 950f6b5

Please sign in to comment.