Skip to content

Commit

Permalink
[efi] Use segment and bus number to identify PCI root bridge I/O prot…
Browse files Browse the repository at this point in the history
…ocol

There may be multiple instances of EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL for
a single PCI segment.  Use the bus number range descriptor from the
ACPI resource list to identify the correct protocol instance.

There is some discrepancy between the ACPI and UEFI specifications
regarding the interpretation of values within the ACPI resource list.

The ACPI specification defines the min/max field values to be within
the secondary (device-side) address space, and defines the offset
field value as "the offset that must be added to the address on the
secondary side to obtain the address on the primary side".

The UEFI specification states instead that the offset field value is
the "offset to apply to the starting address to convert it to a PCI
address", helpfully omitting to clarify whether "to apply" in this
context means "to add" or "to subtract".  The implication of the
wording is also that the "starting address" is not already a "PCI
address" and must therefore be a host-side address rather than the
ACPI-defined device-side address.

Code comments in the EDK2 codebase seem to support the latter
(non-ACPI) interpretation of these ACPI structures.  For example, in
the PciHostBridgeDxe driver there can be found the comment

  Macros to translate device address to host address and vice versa.
  According to UEFI 2.7, device address = host address + translation
  offset.

along with a pair of macros TO_HOST_ADDRESS() and TO_DEVICE_ADDRESS()
which similarly negate the sense of the "translation offset" from the
definition found in the ACPI specification.

The existing logic in efipci_ioremap() (based on a presumed-working
externally contributed patch) applies the non-ACPI interpretation: it
assumes that min/max field values are host-side addresses and that the
offset field value is negated.

Match this existing logic by assuming that min/max field values are
host-side bus numbers.  (The bus number offset value is therefore not
required and so can be ignored.)

As noted in commit 9b25f6e ("[efi] Fall back to assuming identity
mapping of MMIO address space"), some systems seem to fail to provide
MMIO address space descriptors.  Assume that some systems may
similarly fail to provide bus number range descriptors, and fall back
in this situation to assuming that matching on segment number alone is
sufficient.

Testing any of this is unfortunately impossible without access to
esoteric hardware that actually uses non-zero translation offsets.

Originally-implemented-by: Thomas Walker <twalker@twosigma.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
  • Loading branch information
mcb30 committed Dec 31, 2020
1 parent dced22d commit 988d2c1
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 4 deletions.
3 changes: 3 additions & 0 deletions src/include/ipxe/acpi.h
Expand Up @@ -78,6 +78,9 @@ struct acpi_qword_address_space_resource {
/** A memory address space type */
#define ACPI_ADDRESS_TYPE_MEM 0x00

/** A bus number address space type */
#define ACPI_ADDRESS_TYPE_BUS 0x02

/** An ACPI resource descriptor */
union acpi_resource {
/** Tag byte */
Expand Down
72 changes: 68 additions & 4 deletions src/interface/efi/efi_pci.c
Expand Up @@ -62,6 +62,70 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
******************************************************************************
*/

/**
* Check for a matching PCI root bridge I/O protocol
*
* @v pci PCI device
* @v handle EFI PCI root bridge handle
* @v root EFI PCI root bridge I/O protocol
* @ret rc Return status code
*/
static int efipci_root_match ( struct pci_device *pci, EFI_HANDLE handle,
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *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;
unsigned int tag;
EFI_STATUS efirc;
int rc;

/* Check segment number */
if ( root->SegmentNumber != segment )
return -ENOENT;

/* Get ACPI resource descriptors */
if ( ( efirc = root->Configuration ( root, &u.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;
}

/* 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 ) ) {

/* 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 )
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;

/* We have seen at least one non-matching range
* descriptor, so assume failure unless we find a
* subsequent match.
*/
rc = -ENOENT;
}

return rc;
}

/**
* Open EFI PCI root bridge I/O protocol
*
Expand Down Expand Up @@ -106,7 +170,7 @@ static int efipci_root_open ( struct pci_device *pci, EFI_HANDLE *handle,
strerror ( rc ) );
continue;
}
if ( u.root->SegmentNumber == PCI_SEG ( pci->busdevfn ) ) {
if ( efipci_root_match ( pci, *handle, u.root ) == 0 ) {
*root = u.root;
bs->FreePool ( handles );
return 0;
Expand Down Expand Up @@ -263,13 +327,13 @@ void * efipci_ioremap ( struct pci_device *pci, unsigned long bus_addr,
for ( ; ( ( tag = acpi_resource_tag ( u.res ) ) != ACPI_END_RESOURCE ) ;
u.res = acpi_resource_next ( u.res ) ) {

/* Ignore anything other than an address space descriptor */
/* Ignore anything other than a memory range descriptor */
if ( tag != ACPI_QWORD_ADDRESS_SPACE_RESOURCE )
continue;

/* Ignore descriptors that do not cover this memory range */
if ( u.res->qword.type != ACPI_ADDRESS_TYPE_MEM )
continue;

/* Ignore descriptors that do not cover this memory range */
offset = le64_to_cpu ( u.res->qword.offset );
start = ( offset + le64_to_cpu ( u.res->qword.min ) );
end = ( start + le64_to_cpu ( u.res->qword.len ) );
Expand Down

0 comments on commit 988d2c1

Please sign in to comment.