aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2022-09-18 13:41:21 +0100
committerMichael Brown <mcb30@ipxe.org>2022-09-18 13:41:21 +0100
commit649176cd608e74ce54d20488a0618b4c6d8be71d (patch)
treef489d7722ada60a42da7ac8dfc23fe89018e0911
parent9448ac544574dc11e1af204de39fcfddbbccb2af (diff)
downloadipxe-649176cd608e74ce54d20488a0618b4c6d8be71d.zip
ipxe-649176cd608e74ce54d20488a0618b4c6d8be71d.tar.gz
ipxe-649176cd608e74ce54d20488a0618b4c6d8be71d.tar.bz2
[pci] Select PCI I/O API at runtime for cloud images
Pretty much all physical machines and off-the-shelf virtual machines will provide a functional PCI BIOS. We therefore default to using only the PCI BIOS, with no fallback to an alternative mechanism if the PCI BIOS fails. AWS EC2 provides the opportunity to experience some exceptions to this rule. For example, the t3a.nano instances in eu-west-1 have no functional PCI BIOS at all. As of commit 83516ba ("[cloud] Use PCIAPI_DIRECT for cloud images") we therefore use direct Type 1 configuration space accesses in the images built and published for use in the cloud. Recent experience has discovered yet more variation in AWS EC2 instances. For example, some of the metal instance types have multiple PCI host bridges and the direct Type 1 accesses therefore see only a subset of the PCI devices. Attempt to accommodate future such variations by making the PCI I/O API selectable at runtime and choosing ECAM (if available), falling back to the PCI BIOS (if available), then finally falling back to direct Type 1 accesses. This is implemented as a dedicated PCIAPI_CLOUD API, rather than by having the PCI core select a suitable API at runtime (as was done for timers in commit 302f1ee ("[time] Allow timer to be selected at runtime"). The common case will remain that only the PCI BIOS API is required, and we would prefer to retain the optimisations that come from inlining the configuration space accesses in this common case. Cloud images are (at present) disk images rather than ROM images, and so the increased code size required for this design approach in the PCIAPI_CLOUD case is acceptable. Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r--src/arch/x86/core/pcidirect.c2
-rw-r--r--src/arch/x86/include/bits/pci_io.h1
-rw-r--r--src/arch/x86/include/ipxe/pcibios.h2
-rw-r--r--src/arch/x86/include/ipxe/pcicloud.h18
-rw-r--r--src/arch/x86/include/ipxe/pcidirect.h2
-rw-r--r--src/arch/x86/interface/pcbios/pcibios.c2
-rw-r--r--src/arch/x86/interface/pcbios/pcicloud.c191
-rw-r--r--src/config/cloud/ioapi.h2
-rw-r--r--src/drivers/bus/ecam.c2
-rw-r--r--src/include/ipxe/ecam.h2
-rw-r--r--src/include/ipxe/pci_io.h33
11 files changed, 256 insertions, 1 deletions
diff --git a/src/arch/x86/core/pcidirect.c b/src/arch/x86/core/pcidirect.c
index 88db904..f4659a1 100644
--- a/src/arch/x86/core/pcidirect.c
+++ b/src/arch/x86/core/pcidirect.c
@@ -53,3 +53,5 @@ PROVIDE_PCIAPI_INLINE ( direct, pci_write_config_byte );
PROVIDE_PCIAPI_INLINE ( direct, pci_write_config_word );
PROVIDE_PCIAPI_INLINE ( direct, pci_write_config_dword );
PROVIDE_PCIAPI_INLINE ( direct, pci_ioremap );
+
+struct pci_api pcidirect_api = PCIAPI_RUNTIME ( direct );
diff --git a/src/arch/x86/include/bits/pci_io.h b/src/arch/x86/include/bits/pci_io.h
index b41e562..a074d33 100644
--- a/src/arch/x86/include/bits/pci_io.h
+++ b/src/arch/x86/include/bits/pci_io.h
@@ -11,5 +11,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <ipxe/pcibios.h>
#include <ipxe/pcidirect.h>
+#include <ipxe/pcicloud.h>
#endif /* _BITS_PCI_IO_H */
diff --git a/src/arch/x86/include/ipxe/pcibios.h b/src/arch/x86/include/ipxe/pcibios.h
index bae4eed..3caea1c 100644
--- a/src/arch/x86/include/ipxe/pcibios.h
+++ b/src/arch/x86/include/ipxe/pcibios.h
@@ -145,4 +145,6 @@ PCIAPI_INLINE ( pcbios, pci_ioremap ) ( struct pci_device *pci __unused,
return ioremap ( bus_addr, len );
}
+extern struct pci_api pcibios_api;
+
#endif /* _IPXE_PCIBIOS_H */
diff --git a/src/arch/x86/include/ipxe/pcicloud.h b/src/arch/x86/include/ipxe/pcicloud.h
new file mode 100644
index 0000000..5226890
--- /dev/null
+++ b/src/arch/x86/include/ipxe/pcicloud.h
@@ -0,0 +1,18 @@
+#ifndef _IPXE_PCICLOUD_H
+#define _IPXE_PCICLOUD_H
+
+/** @file
+ *
+ * Cloud VM PCI configuration space access
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#ifdef PCIAPI_CLOUD
+#define PCIAPI_PREFIX_cloud
+#else
+#define PCIAPI_PREFIX_cloud __cloud_
+#endif
+
+#endif /* _IPXE_PCICLOUD_H */
diff --git a/src/arch/x86/include/ipxe/pcidirect.h b/src/arch/x86/include/ipxe/pcidirect.h
index 394edb2..98c6a2b 100644
--- a/src/arch/x86/include/ipxe/pcidirect.h
+++ b/src/arch/x86/include/ipxe/pcidirect.h
@@ -155,4 +155,6 @@ PCIAPI_INLINE ( direct, pci_ioremap ) ( struct pci_device *pci __unused,
return ioremap ( bus_addr, len );
}
+extern struct pci_api pcidirect_api;
+
#endif /* _PCIDIRECT_H */
diff --git a/src/arch/x86/interface/pcbios/pcibios.c b/src/arch/x86/interface/pcbios/pcibios.c
index 6f31ce9..7b7a769 100644
--- a/src/arch/x86/interface/pcbios/pcibios.c
+++ b/src/arch/x86/interface/pcbios/pcibios.c
@@ -128,3 +128,5 @@ PROVIDE_PCIAPI_INLINE ( pcbios, pci_write_config_byte );
PROVIDE_PCIAPI_INLINE ( pcbios, pci_write_config_word );
PROVIDE_PCIAPI_INLINE ( pcbios, pci_write_config_dword );
PROVIDE_PCIAPI_INLINE ( pcbios, pci_ioremap );
+
+struct pci_api pcibios_api = PCIAPI_RUNTIME ( pcbios );
diff --git a/src/arch/x86/interface/pcbios/pcicloud.c b/src/arch/x86/interface/pcbios/pcicloud.c
new file mode 100644
index 0000000..97d7cac
--- /dev/null
+++ b/src/arch/x86/interface/pcbios/pcicloud.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2022 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+#include <ipxe/init.h>
+#include <ipxe/pci.h>
+#include <ipxe/ecam.h>
+#include <ipxe/pcibios.h>
+#include <ipxe/pcidirect.h>
+#include <ipxe/pcicloud.h>
+
+/** @file
+ *
+ * Cloud VM PCI configuration space access
+ *
+ */
+
+/** Selected PCI configuration space access API */
+static struct pci_api *pcicloud = &ecam_api;
+
+/**
+ * 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 pcicloud_discover ( uint32_t busdevfn, struct pci_range *range ) {
+
+ pcicloud->pci_discover ( busdevfn, range );
+}
+
+/**
+ * Read byte from PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value read
+ * @ret rc Return status code
+ */
+static int pcicloud_read_config_byte ( struct pci_device *pci,
+ unsigned int where, uint8_t *value ) {
+
+ return pcicloud->pci_read_config_byte ( pci, where, value );
+}
+
+/**
+ * Read 16-bit word from PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value read
+ * @ret rc Return status code
+ */
+static int pcicloud_read_config_word ( struct pci_device *pci,
+ unsigned int where, uint16_t *value ) {
+
+ return pcicloud->pci_read_config_word ( pci, where, value );
+}
+
+/**
+ * Read 32-bit dword from PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value read
+ * @ret rc Return status code
+ */
+static int pcicloud_read_config_dword ( struct pci_device *pci,
+ unsigned int where, uint32_t *value ) {
+
+ return pcicloud->pci_read_config_dword ( pci, where, value );
+}
+
+/**
+ * Write byte to PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value to be written
+ * @ret rc Return status code
+ */
+static int pcicloud_write_config_byte ( struct pci_device *pci,
+ unsigned int where, uint8_t value ) {
+
+ return pcicloud->pci_write_config_byte ( pci, where, value );
+}
+
+/**
+ * Write 16-bit word to PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value to be written
+ * @ret rc Return status code
+ */
+static int pcicloud_write_config_word ( struct pci_device *pci,
+ unsigned int where, uint16_t value ) {
+
+ return pcicloud->pci_write_config_word ( pci, where, value );
+}
+
+/**
+ * Write 32-bit dword to PCI configuration space
+ *
+ * @v pci PCI device
+ * @v where Location within PCI configuration space
+ * @v value Value to be written
+ * @ret rc Return status code
+ */
+static int pcicloud_write_config_dword ( struct pci_device *pci,
+ unsigned int where, uint32_t value ) {
+
+ return pcicloud->pci_write_config_dword ( pci, where, value );
+}
+
+/**
+ * Map PCI bus address as an I/O address
+ *
+ * @v bus_addr PCI bus address
+ * @v len Length of region
+ * @ret io_addr I/O address, or NULL on error
+ */
+static void * pcicloud_ioremap ( struct pci_device *pci,
+ unsigned long bus_addr, size_t len ) {
+
+ return pcicloud->pci_ioremap ( pci, bus_addr, len );
+}
+
+PROVIDE_PCIAPI ( cloud, pci_discover, pcicloud_discover );
+PROVIDE_PCIAPI ( cloud, pci_read_config_byte, pcicloud_read_config_byte );
+PROVIDE_PCIAPI ( cloud, pci_read_config_word, pcicloud_read_config_word );
+PROVIDE_PCIAPI ( cloud, pci_read_config_dword, pcicloud_read_config_dword );
+PROVIDE_PCIAPI ( cloud, pci_write_config_byte, pcicloud_write_config_byte );
+PROVIDE_PCIAPI ( cloud, pci_write_config_word, pcicloud_write_config_word );
+PROVIDE_PCIAPI ( cloud, pci_write_config_dword, pcicloud_write_config_dword );
+PROVIDE_PCIAPI ( cloud, pci_ioremap, pcicloud_ioremap );
+
+/**
+ * Initialise cloud VM PCI configuration space access
+ *
+ */
+static void pcicloud_init ( void ) {
+ static struct pci_api *apis[] = {
+ &ecam_api, &pcibios_api, &pcidirect_api
+ };
+ struct pci_range range;
+ unsigned int i;
+
+ /* Select first API that successfully discovers an address range */
+ for ( i = 0 ; i < ( sizeof ( apis ) / sizeof ( apis[0] ) ) ; i++ ) {
+ pcicloud = apis[i];
+ pcicloud_discover ( 0, &range );
+ if ( range.count != 0 ) {
+ DBGC ( pcicloud, "PCICLOUD selected %s API\n",
+ pcicloud->name );
+ break;
+ }
+ }
+
+ /* The PCI direct API can never fail discovery since the range
+ * is hardcoded.
+ */
+ assert ( range.count != 0 );
+}
+
+/** Cloud VM PCI configuration space access initialisation function */
+struct init_fn pcicloud_init_fn __init_fn ( INIT_EARLY ) = {
+ .initialise = pcicloud_init,
+};
diff --git a/src/config/cloud/ioapi.h b/src/config/cloud/ioapi.h
index c7c917f..ba0896a 100644
--- a/src/config/cloud/ioapi.h
+++ b/src/config/cloud/ioapi.h
@@ -3,5 +3,5 @@
*/
#ifdef PLATFORM_pcbios
#undef PCIAPI_PCBIOS
-#define PCIAPI_DIRECT
+#define PCIAPI_CLOUD
#endif
diff --git a/src/drivers/bus/ecam.c b/src/drivers/bus/ecam.c
index f7ba2db..1d57bd2 100644
--- a/src/drivers/bus/ecam.c
+++ b/src/drivers/bus/ecam.c
@@ -263,3 +263,5 @@ PROVIDE_PCIAPI_INLINE ( ecam, pci_write_config_byte );
PROVIDE_PCIAPI_INLINE ( ecam, pci_write_config_word );
PROVIDE_PCIAPI_INLINE ( ecam, pci_write_config_dword );
PROVIDE_PCIAPI_INLINE ( ecam, pci_ioremap );
+
+struct pci_api ecam_api = PCIAPI_RUNTIME ( ecam );
diff --git a/src/include/ipxe/ecam.h b/src/include/ipxe/ecam.h
index 0f0fbf4..683d613 100644
--- a/src/include/ipxe/ecam.h
+++ b/src/include/ipxe/ecam.h
@@ -52,4 +52,6 @@ struct ecam_mapping {
void *regs;
};
+extern struct pci_api ecam_api;
+
#endif /* _IPXE_ECAM_H */
diff --git a/src/include/ipxe/pci_io.h b/src/include/ipxe/pci_io.h
index 35d16f9..4c035b1 100644
--- a/src/include/ipxe/pci_io.h
+++ b/src/include/ipxe/pci_io.h
@@ -15,6 +15,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <config/ioapi.h>
struct pci_device;
+struct pci_api;
/** A PCI bus:dev.fn address range */
struct pci_range {
@@ -149,4 +150,36 @@ int pci_write_config_dword ( struct pci_device *pci, unsigned int where,
void * pci_ioremap ( struct pci_device *pci, unsigned long bus_addr,
size_t len );
+/** A runtime selectable PCI I/O API */
+struct pci_api {
+ const char *name;
+ typeof ( pci_discover ) ( * pci_discover );
+ typeof ( pci_read_config_byte ) ( * pci_read_config_byte );
+ typeof ( pci_read_config_word ) ( * pci_read_config_word );
+ typeof ( pci_read_config_dword ) ( * pci_read_config_dword );
+ typeof ( pci_write_config_byte ) ( * pci_write_config_byte );
+ typeof ( pci_write_config_word ) ( * pci_write_config_word );
+ typeof ( pci_write_config_dword ) ( * pci_write_config_dword );
+ typeof ( pci_ioremap ) ( * pci_ioremap );
+};
+
+/** Provide a runtime selectable PCI I/O API */
+#define PCIAPI_RUNTIME( _subsys ) { \
+ .name = #_subsys, \
+ .pci_discover = PCIAPI_INLINE ( _subsys, pci_discover ), \
+ .pci_read_config_byte = \
+ PCIAPI_INLINE ( _subsys, pci_read_config_byte ), \
+ .pci_read_config_word = \
+ PCIAPI_INLINE ( _subsys, pci_read_config_word ), \
+ .pci_read_config_dword = \
+ PCIAPI_INLINE ( _subsys, pci_read_config_dword ), \
+ .pci_write_config_byte = \
+ PCIAPI_INLINE ( _subsys, pci_write_config_byte ), \
+ .pci_write_config_word = \
+ PCIAPI_INLINE ( _subsys, pci_write_config_word ), \
+ .pci_write_config_dword = \
+ PCIAPI_INLINE ( _subsys, pci_write_config_dword ), \
+ .pci_ioremap = PCIAPI_INLINE ( _subsys, pci_ioremap ), \
+ }
+
#endif /* _IPXE_PCI_IO_H */