summaryrefslogtreecommitdiffstats
path: root/src/interface/efi/efi_pci.c
diff options
context:
space:
mode:
authorMichael Brown2011-02-17 01:27:51 +0100
committerMichael Brown2011-02-17 03:56:55 +0100
commitd7736fbb7bbf2116e95742affcaf45c2a7ee5476 (patch)
treeaad56a264b42dcba5e5e491e3eab8a04766a0647 /src/interface/efi/efi_pci.c
parent[efi] Rename efi_pci.h to efi_pci_api.h (diff)
downloadipxe-d7736fbb7bbf2116e95742affcaf45c2a7ee5476.tar.gz
ipxe-d7736fbb7bbf2116e95742affcaf45c2a7ee5476.tar.xz
ipxe-d7736fbb7bbf2116e95742affcaf45c2a7ee5476.zip
[efi] Allow EFI to control PCI bus enumeration
EFI performs its own PCI bus enumeration. Respect this, and start controlling devices only when instructed to do so by EFI. As a side benefit, we should now correctly create multiple SNP instances for multi-port devices. This should also fix the problem of failing to enumerate devices because the PCI bridges have not yet been enabled at the time the iPXE driver is loaded. Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/interface/efi/efi_pci.c')
-rw-r--r--src/interface/efi/efi_pci.c378
1 files changed, 378 insertions, 0 deletions
diff --git a/src/interface/efi/efi_pci.c b/src/interface/efi/efi_pci.c
index eb334b68..d866e30e 100644
--- a/src/interface/efi/efi_pci.c
+++ b/src/interface/efi/efi_pci.c
@@ -18,9 +18,14 @@
FILE_LICENCE ( GPL2_OR_LATER );
+#include <stdlib.h>
#include <errno.h>
#include <ipxe/pci.h>
+#include <ipxe/init.h>
#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_pci.h>
+#include <ipxe/efi/efi_driver.h>
+#include <ipxe/efi/Protocol/PciIo.h>
#include <ipxe/efi/Protocol/PciRootBridgeIo.h>
/** @file
@@ -29,6 +34,13 @@ FILE_LICENCE ( GPL2_OR_LATER );
*
*/
+/******************************************************************************
+ *
+ * iPXE PCI API
+ *
+ ******************************************************************************
+ */
+
/** PCI root bridge I/O protocol */
static EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *efipci;
EFI_REQUIRE_PROTOCOL ( EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL, &efipci );
@@ -80,3 +92,369 @@ PROVIDE_PCIAPI_INLINE ( efi, pci_read_config_dword );
PROVIDE_PCIAPI_INLINE ( efi, pci_write_config_byte );
PROVIDE_PCIAPI_INLINE ( efi, pci_write_config_word );
PROVIDE_PCIAPI_INLINE ( efi, pci_write_config_dword );
+
+/******************************************************************************
+ *
+ * EFI PCI device instantiation
+ *
+ ******************************************************************************
+ */
+
+/** EFI PCI I/O protocol GUID */
+static EFI_GUID efi_pci_io_protocol_guid
+ = EFI_PCI_IO_PROTOCOL_GUID;
+
+/** EFI device path protocol GUID */
+static EFI_GUID efi_device_path_protocol_guid
+ = EFI_DEVICE_PATH_PROTOCOL_GUID;
+
+/** EFI PCI devices */
+static LIST_HEAD ( efi_pci_devices );
+
+/**
+ * Create EFI PCI device
+ *
+ * @v efidrv EFI driver
+ * @v device EFI device
+ * @ret efipci EFI PCI device, or NULL
+ */
+struct efi_pci_device * efipci_create ( struct efi_driver *efidrv,
+ EFI_HANDLE device ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ struct efi_pci_device *efipci;
+ union {
+ EFI_PCI_IO_PROTOCOL *pci_io;
+ void *interface;
+ } pci_io;
+ union {
+ EFI_DEVICE_PATH_PROTOCOL *path;
+ void *interface;
+ } path;
+ UINTN pci_segment, pci_bus, pci_dev, pci_fn;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Allocate PCI device */
+ efipci = zalloc ( sizeof ( *efipci ) );
+ if ( ! efipci )
+ goto err_zalloc;
+ efipci->device = device;
+
+ /* See if device is a PCI device */
+ if ( ( efirc = bs->OpenProtocol ( device,
+ &efi_pci_io_protocol_guid,
+ &pci_io.interface,
+ efidrv->driver.DriverBindingHandle,
+ device,
+ EFI_OPEN_PROTOCOL_BY_DRIVER )) !=0 ){
+ DBGCP ( efipci, "EFIPCI device %p is not a PCI device\n",
+ device );
+ goto err_open_protocol;
+ }
+ efipci->pci_io = pci_io.pci_io;
+
+ /* Get PCI bus:dev.fn address */
+ if ( ( efirc = pci_io.pci_io->GetLocation ( pci_io.pci_io,
+ &pci_segment,
+ &pci_bus, &pci_dev,
+ &pci_fn ) ) != 0 ) {
+ DBGC ( efipci, "EFIPCI device %p could not get PCI "
+ "location: %s\n", device, efi_strerror ( efirc ) );
+ goto err_get_location;
+ }
+ DBGC2 ( efipci, "EFIPCI device %p is PCI %04lx:%02lx:%02lx.%lx\n",
+ device, ( ( unsigned long ) pci_segment ),
+ ( ( unsigned long ) pci_bus ), ( ( unsigned long ) pci_dev ),
+ ( ( unsigned long ) pci_fn ) );
+
+ /* Populate PCI device */
+ pci_init ( &efipci->pci, PCI_BUSDEVFN ( pci_bus, pci_dev, pci_fn ) );
+ if ( ( rc = pci_read_config ( &efipci->pci ) ) != 0 ) {
+ DBGC ( efipci, "EFIPCI " PCI_FMT " cannot read PCI "
+ "configuration: %s\n",
+ PCI_ARGS ( &efipci->pci ), strerror ( rc ) );
+ goto err_pci_read_config;
+ }
+
+ /* Retrieve device path */
+ if ( ( efirc = bs->OpenProtocol ( device,
+ &efi_device_path_protocol_guid,
+ &path.interface,
+ efidrv->driver.DriverBindingHandle,
+ device,
+ EFI_OPEN_PROTOCOL_BY_DRIVER )) !=0 ){
+ DBGC ( efipci, "EFIPCI " PCI_FMT " has no device path\n",
+ PCI_ARGS ( &efipci->pci ) );
+ goto err_no_device_path;
+ }
+ efipci->path = path.path;
+
+ /* Add to list of PCI devices */
+ list_add ( &efipci->list, &efi_pci_devices );
+
+ return efipci;
+
+ bs->CloseProtocol ( device, &efi_device_path_protocol_guid,
+ efidrv->driver.DriverBindingHandle, device );
+ err_no_device_path:
+ err_pci_read_config:
+ err_get_location:
+ bs->CloseProtocol ( device, &efi_pci_io_protocol_guid,
+ efidrv->driver.DriverBindingHandle, device );
+ err_open_protocol:
+ free ( efipci );
+ err_zalloc:
+ return NULL;
+}
+
+/**
+ * Enable EFI PCI device
+ *
+ * @v efipci EFI PCI device
+ * @ret efirc EFI status code
+ */
+EFI_STATUS efipci_enable ( struct efi_pci_device *efipci ) {
+ EFI_PCI_IO_PROTOCOL *pci_io = efipci->pci_io;
+ EFI_STATUS efirc;
+
+ /* Enable device */
+ if ( ( efirc = pci_io->Attributes ( pci_io,
+ EfiPciIoAttributeOperationSet,
+ EFI_PCI_DEVICE_ENABLE,
+ NULL ) ) != 0 ) {
+ DBGC ( efipci, "EFIPCI " PCI_FMT " could not be enabled: %s\n",
+ PCI_ARGS ( &efipci->pci ), efi_strerror ( efirc ) );
+ return efirc;
+ }
+
+ return 0;
+}
+
+/**
+ * Find EFI PCI device by EFI device
+ *
+ * @v device EFI device
+ * @ret efipci EFI PCI device, or NULL
+ */
+struct efi_pci_device * efipci_find_efi ( EFI_HANDLE device ) {
+ struct efi_pci_device *efipci;
+
+ list_for_each_entry ( efipci, &efi_pci_devices, list ) {
+ if ( efipci->device == device )
+ return efipci;
+ }
+ return NULL;
+}
+
+/**
+ * Find EFI PCI device by iPXE device
+ *
+ * @v dev Device
+ * @ret efipci EFI PCI device, or NULL
+ */
+struct efi_pci_device * efipci_find ( struct device *dev ) {
+ struct efi_pci_device *efipci;
+
+ list_for_each_entry ( efipci, &efi_pci_devices, list ) {
+ if ( &efipci->pci.dev == dev )
+ return efipci;
+ }
+ return NULL;
+}
+
+/**
+ * Destroy EFI PCI device
+ *
+ * @v efidrv EFI driver
+ * @v efipci EFI PCI device
+ */
+void efipci_destroy ( struct efi_driver *efidrv,
+ struct efi_pci_device *efipci ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+
+ list_del ( &efipci->list );
+ bs->CloseProtocol ( efipci->device, &efi_device_path_protocol_guid,
+ efidrv->driver.DriverBindingHandle,
+ efipci->device );
+ bs->CloseProtocol ( efipci->device, &efi_pci_io_protocol_guid,
+ efidrv->driver.DriverBindingHandle,
+ efipci->device );
+ free ( efipci );
+}
+
+/******************************************************************************
+ *
+ * EFI PCI driver
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Check to see if driver supports a device
+ *
+ * @v driver EFI driver
+ * @v device EFI device
+ * @v child Path to child device, if any
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI
+efipci_supported ( EFI_DRIVER_BINDING_PROTOCOL *driver, EFI_HANDLE device,
+ EFI_DEVICE_PATH_PROTOCOL *child ) {
+ struct efi_driver *efidrv =
+ container_of ( driver, struct efi_driver, driver );
+ struct efi_pci_device *efipci;
+ EFI_STATUS efirc;
+ int rc;
+
+ DBGCP ( efidrv, "EFIPCI DRIVER_SUPPORTED %p (%p)\n", device, child );
+
+ /* Create temporary corresponding PCI device, if any */
+ efipci = efipci_create ( efidrv, device );
+ if ( ! efipci ) {
+ /* Non-PCI devices are simply unsupported */
+ efirc = EFI_UNSUPPORTED;
+ goto err_not_pci;
+ }
+
+ /* Look for a driver */
+ if ( ( rc = pci_find_driver ( &efipci->pci ) ) != 0 ) {
+ DBGCP ( efipci, "EFIPCI " PCI_FMT " has no driver\n",
+ PCI_ARGS ( &efipci->pci ) );
+ efirc = EFI_UNSUPPORTED;
+ goto err_no_driver;
+ }
+
+ DBGC ( efipci, "EFIPCI " PCI_FMT " is supported by driver \"%s\"\n",
+ PCI_ARGS ( &efipci->pci ), efipci->pci.id->name );
+
+ /* Destroy temporary PCI device */
+ efipci_destroy ( efidrv, efipci );
+
+ return 0;
+
+ err_no_driver:
+ efipci_destroy ( efidrv, efipci );
+ err_not_pci:
+ return efirc;
+}
+
+/**
+ * Attach driver to device
+ *
+ * @v driver EFI driver
+ * @v device EFI device
+ * @v child Path to child device, if any
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI
+efipci_start ( EFI_DRIVER_BINDING_PROTOCOL *driver, EFI_HANDLE device,
+ EFI_DEVICE_PATH_PROTOCOL *child ) {
+ struct efi_driver *efidrv =
+ container_of ( driver, struct efi_driver, driver );
+ struct efi_pci_device *efipci;
+ EFI_STATUS efirc;
+ int rc;
+
+ DBGC ( efidrv, "EFIPCI DRIVER_START %p (%p)\n", device, child );
+
+ /* Create corresponding PCI device */
+ efipci = efipci_create ( efidrv, device );
+ if ( ! efipci ) {
+ efirc = EFI_OUT_OF_RESOURCES;
+ goto err_create;
+ }
+
+ /* Find driver */
+ if ( ( rc = pci_find_driver ( &efipci->pci ) ) != 0 ) {
+ DBGC ( efipci, "EFIPCI " PCI_FMT " has no driver\n",
+ PCI_ARGS ( &efipci->pci ) );
+ efirc = RC_TO_EFIRC ( rc );
+ goto err_find_driver;
+ }
+
+ /* Enable PCI device */
+ if ( ( efirc = efipci_enable ( efipci ) ) != 0 )
+ goto err_enable;
+
+ /* Probe driver */
+ if ( ( rc = pci_probe ( &efipci->pci ) ) != 0 ) {
+ DBGC ( efipci, "EFIPCI " PCI_FMT " could not probe driver "
+ "\"%s\": %s\n", PCI_ARGS ( &efipci->pci ),
+ efipci->pci.id->name, strerror ( rc ) );
+ efirc = RC_TO_EFIRC ( rc );
+ goto err_probe;
+ }
+
+ return 0;
+
+ pci_remove ( &efipci->pci );
+ err_probe:
+ err_enable:
+ err_find_driver:
+ efipci_destroy ( efidrv, efipci );
+ err_create:
+ return efirc;
+}
+
+/**
+ * Detach driver from device
+ *
+ * @v driver EFI driver
+ * @v device EFI device
+ * @v pci PCI device
+ * @v num_children Number of child devices
+ * @v children List of child devices
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI
+efipci_stop ( EFI_DRIVER_BINDING_PROTOCOL *driver, EFI_HANDLE device,
+ UINTN num_children, EFI_HANDLE *children ) {
+ struct efi_driver *efidrv =
+ container_of ( driver, struct efi_driver, driver );
+ struct efi_pci_device *efipci;
+
+ DBGC ( efidrv, "EFIPCI DRIVER_STOP %p (%ld %p)\n",
+ device, ( ( unsigned long ) num_children ), children );
+
+ /* Find PCI device */
+ efipci = efipci_find_efi ( device );
+ if ( ! efipci ) {
+ DBGC ( efidrv, "EFIPCI device %p not started!\n", device );
+ return EFI_INVALID_PARAMETER;
+ }
+
+ /* Remove device */
+ pci_remove ( &efipci->pci );
+
+ /* Delete EFI PCI device */
+ efipci_destroy ( efidrv, efipci );
+
+ return 0;
+}
+
+/** EFI PCI driver */
+static struct efi_driver efipci_driver =
+ EFI_DRIVER_INIT ( "PCI", efipci_supported, efipci_start, efipci_stop );
+
+/**
+ * Install EFI PCI driver
+ *
+ */
+static void efipci_driver_init ( void ) {
+ struct efi_driver *efidrv = &efipci_driver;
+ EFI_STATUS efirc;
+
+ /* Install driver */
+ if ( ( efirc = efi_driver_install ( efidrv ) ) != 0 ) {
+ DBGC ( efidrv, "EFIPCI could not install driver: %s\n",
+ efi_strerror ( efirc ) );
+ return;
+ }
+
+ DBGC ( efidrv, "EFIPCI driver installed\n" );
+}
+
+/** EFI PCI startup function */
+struct startup_fn startup_pci __startup_fn ( STARTUP_NORMAL ) = {
+ .startup = efipci_driver_init,
+};