aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2020-12-31 20:41:49 +0000
committerMichael Brown <mcb30@ipxe.org>2020-12-31 21:03:10 +0000
commit988d2c13cdf0f0b4140685af35ced70ac5b3283c (patch)
tree16a978aa869e1defba6d76d424f324d83ee9a2a1
parentdced22d6dee346ac4d98502c9008fd3d5c6197d2 (diff)
downloadipxe-988d2c13cdf0f0b4140685af35ced70ac5b3283c.zip
ipxe-988d2c13cdf0f0b4140685af35ced70ac5b3283c.tar.gz
ipxe-988d2c13cdf0f0b4140685af35ced70ac5b3283c.tar.bz2
[efi] Use segment and bus number to identify PCI root bridge I/O protocolv1.21.1
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>
-rw-r--r--src/include/ipxe/acpi.h3
-rw-r--r--src/interface/efi/efi_pci.c72
2 files changed, 71 insertions, 4 deletions
diff --git a/src/include/ipxe/acpi.h b/src/include/ipxe/acpi.h
index e41bd28..f979ace 100644
--- a/src/include/ipxe/acpi.h
+++ b/src/include/ipxe/acpi.h
@@ -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 */
diff --git a/src/interface/efi/efi_pci.c b/src/interface/efi/efi_pci.c
index 6b32fd6..4adee0f 100644
--- a/src/interface/efi/efi_pci.c
+++ b/src/interface/efi/efi_pci.c
@@ -63,6 +63,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
*
* @v pci PCI device
@@ -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;
@@ -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 ) );