]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Allow EFI to control PCI bus enumeration
authorMichael Brown <mcb30@ipxe.org>
Thu, 17 Feb 2011 00:27:51 +0000 (00:27 +0000)
committerMichael Brown <mcb30@ipxe.org>
Thu, 17 Feb 2011 02:56:55 +0000 (02:56 +0000)
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>
src/arch/x86/prefix/efidrvprefix.c
src/include/ipxe/efi/efi.h
src/include/ipxe/efi/efi_driver.h [new file with mode: 0644]
src/include/ipxe/efi/efi_pci.h [new file with mode: 0644]
src/interface/efi/efi_driver.c [new file with mode: 0644]
src/interface/efi/efi_pci.c
src/interface/efi/efi_snp.c

index 2215104b04e422c2ed24bd069aa0d904887a21f5..a96c5c439b0afb4891698b731405bacc2bdd7d7f 100644 (file)
@@ -41,6 +41,7 @@ EFI_STATUS EFIAPI _efidrv_start ( EFI_HANDLE image_handle,
        initialise();
        startup();
 
-       /* Install SNP driver and return */
-       return RC_TO_EFIRC ( efi_snp_install () );
+       return 0;
 }
+
+REQUIRE_OBJECT ( efi_snp );
index 6dca13243b7d605b22310a93ecfd703ccd1c0907..8a216b5373f6afe9cfafc0dfb87b1ce5b73c4906 100644 (file)
@@ -142,6 +142,5 @@ extern EFI_SYSTEM_TABLE *efi_systab;
 extern const char * efi_strerror ( EFI_STATUS efirc );
 extern EFI_STATUS efi_init ( EFI_HANDLE image_handle,
                             EFI_SYSTEM_TABLE *systab );
-extern int efi_snp_install ( void );
 
 #endif /* _IPXE_EFI_H */
diff --git a/src/include/ipxe/efi/efi_driver.h b/src/include/ipxe/efi/efi_driver.h
new file mode 100644 (file)
index 0000000..0633164
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef _IPXE_EFI_DRIVER_H
+#define _IPXE_EFI_DRIVER_H
+
+/** @file
+ *
+ * EFI driver interface
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/Protocol/DriverBinding.h>
+#include <ipxe/efi/Protocol/ComponentName2.h>
+#include <ipxe/efi/Protocol/DevicePath.h>
+
+/** An EFI driver */
+struct efi_driver {
+       /** Name */
+       const char *name;
+       /** EFI name */
+       CHAR16 *wname;
+       /** EFI driver binding protocol */
+       EFI_DRIVER_BINDING_PROTOCOL driver;
+       /** EFI component name protocol */
+       EFI_COMPONENT_NAME2_PROTOCOL wtf;
+};
+
+/** Initialise an EFI driver
+ *
+ * @v name             Driver name
+ * @v supported                Device supported method
+ * @v start            Device start method
+ * @v stop             Device stop method
+ */
+#define EFI_DRIVER_INIT( _name, _supported, _start, _stop ) {  \
+       .name = _name,                                          \
+       .driver = {                                             \
+               .Supported = _supported,                        \
+               .Start = _start,                                \
+               .Stop = _stop,                                  \
+               .Version = 0x10,                                \
+       } }
+
+extern EFI_DEVICE_PATH_PROTOCOL *
+efi_devpath_end ( EFI_DEVICE_PATH_PROTOCOL *path );
+
+extern EFI_STATUS efi_driver_install ( struct efi_driver *efidrv );
+
+#endif /* _IPXE_EFI_DRIVER_H */
diff --git a/src/include/ipxe/efi/efi_pci.h b/src/include/ipxe/efi/efi_pci.h
new file mode 100644 (file)
index 0000000..e226a56
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef _IPXE_EFI_PCI_H
+#define _IPXE_EFI_PCI_H
+
+/** @file
+ *
+ * EFI driver interface
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/Protocol/PciIo.h>
+#include <ipxe/efi/Protocol/DevicePath.h>
+
+struct efi_driver;
+struct device;
+
+/** An EFI PCI device */
+struct efi_pci_device {
+       /** List of EFI PCI devices */
+       struct list_head list;
+       /** iPXE PCI device */
+       struct pci_device pci;
+       /** Underlying EFI device */
+       EFI_HANDLE device;
+       /** PCI I/O protocol */
+       EFI_PCI_IO_PROTOCOL *pci_io;
+       /** Device path */
+       EFI_DEVICE_PATH_PROTOCOL *path;
+};
+
+extern struct efi_pci_device * efipci_create ( struct efi_driver *efidrv,
+                                              EFI_HANDLE device );
+extern EFI_STATUS efipci_enable ( struct efi_pci_device *efipci );
+extern struct efi_pci_device * efipci_find_efi ( EFI_HANDLE device );
+extern struct efi_pci_device * efipci_find ( struct device *dev );
+extern void efipci_destroy ( struct efi_driver *efidrv,
+                            struct efi_pci_device *efipci );
+
+#endif /* _IPXE_EFI_PCI_H */
diff --git a/src/interface/efi/efi_driver.c b/src/interface/efi/efi_driver.c
new file mode 100644 (file)
index 0000000..17ec881
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2011 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 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stddef.h>
+#include <stdio.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/Protocol/DriverBinding.h>
+#include <ipxe/efi/Protocol/ComponentName2.h>
+#include <ipxe/efi/efi_driver.h>
+#include <config/general.h>
+
+/** @file
+ *
+ * EFI driver interface
+ *
+ */
+
+/** EFI driver binding protocol GUID */
+static EFI_GUID efi_driver_binding_protocol_guid
+       = EFI_DRIVER_BINDING_PROTOCOL_GUID;
+
+/** EFI component name protocol GUID */
+static EFI_GUID efi_component_name2_protocol_guid
+       = EFI_COMPONENT_NAME2_PROTOCOL_GUID;
+
+/**
+ * Find end of device path
+ *
+ * @v path             Path to device
+ * @ret path_end       End of device path
+ */
+EFI_DEVICE_PATH_PROTOCOL * efi_devpath_end ( EFI_DEVICE_PATH_PROTOCOL *path ) {
+
+       while ( path->Type != END_DEVICE_PATH_TYPE ) {
+               path = ( ( ( void * ) path ) +
+                        /* There's this amazing new-fangled thing known as
+                         * a UINT16, but who wants to use one of those? */
+                        ( ( path->Length[1] << 8 ) | path->Length[0] ) );
+       }
+
+       return path;
+}
+
+/**
+ * Look up driver name
+ *
+ * @v wtf              Component name protocol
+ * @v language         Language to use
+ * @v driver_name      Driver name to fill in
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_driver_get_driver_name ( EFI_COMPONENT_NAME2_PROTOCOL *wtf,
+                            CHAR8 *language __unused, CHAR16 **driver_name ) {
+       struct efi_driver *efidrv =
+               container_of ( wtf, struct efi_driver, wtf );
+
+       *driver_name = efidrv->wname;
+       return 0;
+}
+
+/**
+ * Look up controller name
+ *
+ * @v wtf              Component name protocol
+ * @v device           Device
+ * @v child            Child device, or NULL
+ * @v language         Language to use
+ * @v driver_name      Device name to fill in
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_driver_get_controller_name ( EFI_COMPONENT_NAME2_PROTOCOL *wtf __unused,
+                                EFI_HANDLE device __unused,
+                                EFI_HANDLE child __unused,
+                                CHAR8 *language __unused,
+                                CHAR16 **controller_name __unused ) {
+
+       /* Just let EFI use the default Device Path Name */
+       return EFI_UNSUPPORTED;
+}
+
+/**
+ * Install EFI driver
+ *
+ * @v efidrv           EFI driver
+ * @ret efirc          EFI status code
+ */
+EFI_STATUS efi_driver_install ( struct efi_driver *efidrv ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       EFI_DRIVER_BINDING_PROTOCOL *driver = &efidrv->driver;
+       EFI_COMPONENT_NAME2_PROTOCOL *wtf = &efidrv->wtf;
+       char buf[ sizeof ( efidrv->wname ) / sizeof ( efidrv->wname[0] ) ];
+       unsigned int i;
+       EFI_STATUS efirc;
+
+       /* Configure driver binding protocol */
+       driver->ImageHandle = efi_image_handle;
+
+       /* Configure component name protocol */
+       wtf->GetDriverName = efi_driver_get_driver_name;
+       wtf->GetControllerName = efi_driver_get_controller_name;
+       wtf->SupportedLanguages = "en";
+
+       /* Fill in driver name */
+       snprintf ( buf, sizeof ( buf ), PRODUCT_SHORT_NAME " - %s",
+                  efidrv->name );
+       for ( i = 0 ; i < sizeof ( buf ) ; i++ ) {
+               /* Damn Unicode names */
+               efidrv->wname[i] = *( ( ( unsigned char * ) buf ) + i );
+       }
+
+       /* Install driver */
+       if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
+                       &driver->DriverBindingHandle,
+                       &efi_driver_binding_protocol_guid, driver,
+                       &efi_component_name2_protocol_guid, wtf,
+                       NULL ) ) != 0 ) {
+               DBGC ( efidrv, "EFIDRV %s could not install protocol: %s\n",
+                      efidrv->name, efi_strerror ( efirc ) );
+               return efirc;
+       }
+
+       DBGC ( efidrv, "EFIDRV %s installed\n", efidrv->name );
+       return 0;
+}
index eb334b68512c92cd266f3971bea5a2cc37b66f2a..d866e30e7f65a7a1d723d4b17ae54d1e68a7273d 100644 (file)
 
 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,
+};
index 6bfa188848d4f635ae1cde826112085c6e90bc21..669d189625e5badfbeaee059b610d4abcf6a8f0f 100644 (file)
@@ -28,12 +28,11 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/in.h>
 #include <ipxe/pci.h>
 #include <ipxe/efi/efi.h>
-#include <ipxe/efi/Protocol/DriverBinding.h>
-#include <ipxe/efi/Protocol/PciIo.h>
+#include <ipxe/efi/efi_pci.h>
+#include <ipxe/efi/efi_driver.h>
 #include <ipxe/efi/Protocol/SimpleNetwork.h>
-#include <ipxe/efi/Protocol/ComponentName2.h>
 #include <ipxe/efi/Protocol/NetworkInterfaceIdentifier.h>
-#include <config/general.h>
+#include <ipxe/efi/Protocol/DevicePath.h>
 
 /** @file
  *
@@ -43,6 +42,8 @@ FILE_LICENCE ( GPL2_OR_LATER );
 
 /** An SNP device */
 struct efi_snp_device {
+       /** List of SNP devices */
+       struct list_head list;
        /** The underlying iPXE network device */
        struct net_device *netdev;
        /** EFI device handle */
@@ -81,14 +82,6 @@ struct efi_snp_device {
 static EFI_GUID efi_simple_network_protocol_guid
        = EFI_SIMPLE_NETWORK_PROTOCOL_GUID;
 
-/** EFI driver binding protocol GUID */
-static EFI_GUID efi_driver_binding_protocol_guid
-       = EFI_DRIVER_BINDING_PROTOCOL_GUID;
-
-/** EFI component name protocol GUID */
-static EFI_GUID efi_component_name2_protocol_guid
-       = EFI_COMPONENT_NAME2_PROTOCOL_GUID;
-
 /** EFI device path protocol GUID */
 static EFI_GUID efi_device_path_protocol_guid
        = EFI_DEVICE_PATH_PROTOCOL_GUID;
@@ -107,9 +100,8 @@ static EFI_GUID efi_nii31_protocol_guid = {
        { 0xBC, 0x81, 0x76, 0x7F, 0x1F, 0x97, 0x7A, 0x89 }
 };
 
-/** EFI PCI I/O protocol GUID */
-static EFI_GUID efi_pci_io_protocol_guid
-       = EFI_PCI_IO_PROTOCOL_GUID;
+/** List of SNP devices */
+static LIST_HEAD ( efi_snp_devices );
 
 /**
  * Set EFI SNP mode based on iPXE net device parameters
@@ -750,188 +742,58 @@ static EFI_SIMPLE_NETWORK_PROTOCOL efi_snp_device_snp = {
 };
 
 /**
- * Locate net device corresponding to EFI device
- *
- * @v driver           EFI driver
- * @v device           EFI device
- * @ret netdev         Net device, or NULL if not found
- */
-static struct net_device *
-efi_snp_netdev ( EFI_DRIVER_BINDING_PROTOCOL *driver, EFI_HANDLE device ) {
-       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
-       union {
-               EFI_PCI_IO_PROTOCOL *pci;
-               void *interface;
-       } u;
-       UINTN pci_segment, pci_bus, pci_dev, pci_fn;
-       unsigned int pci_busdevfn;
-       struct net_device *netdev = NULL;
-       EFI_STATUS efirc;
-
-       /* See if device is a PCI device */
-       if ( ( efirc = bs->OpenProtocol ( device,
-                                         &efi_pci_io_protocol_guid,
-                                         &u.interface,
-                                         driver->DriverBindingHandle,
-                                         device,
-                                         EFI_OPEN_PROTOCOL_BY_DRIVER )) !=0 ){
-               DBGCP ( driver, "SNPDRV %p device %p is not a PCI device\n",
-                       driver, device );
-               goto out_no_pci_io;
-       }
-
-       /* Get PCI bus:dev.fn address */
-       if ( ( efirc = u.pci->GetLocation ( u.pci, &pci_segment, &pci_bus,
-                                           &pci_dev, &pci_fn ) ) != 0 ) {
-               DBGC ( driver, "SNPDRV %p device %p could not get PCI "
-                      "location: %s\n",
-                      driver, device, efi_strerror ( efirc ) );
-               goto out_no_pci_location;
-       }
-       DBGCP ( driver, "SNPDRV %p device %p is PCI %04lx:%02lx:%02lx.%lx\n",
-               driver, device, ( ( unsigned long ) pci_segment ),
-               ( ( unsigned long ) pci_bus ), ( ( unsigned long ) pci_dev ),
-               ( ( unsigned long ) pci_fn ) );
-
-       /* Look up corresponding network device */
-       pci_busdevfn = PCI_BUSDEVFN ( pci_bus, pci_dev, pci_fn );
-       if ( ( netdev = find_netdev_by_location ( BUS_TYPE_PCI,
-                                                 pci_busdevfn ) ) == NULL ) {
-               DBGCP ( driver, "SNPDRV %p device %p is not a iPXE network "
-                       "device\n", driver, device );
-               goto out_no_netdev;
-       }
-       DBGC ( driver, "SNPDRV %p device %p is %s\n",
-              driver, device, netdev->name );
-
- out_no_netdev:
- out_no_pci_location:
-       bs->CloseProtocol ( device, &efi_pci_io_protocol_guid,
-                           driver->DriverBindingHandle, device );
- out_no_pci_io:
-       return netdev;
-}
-
-/**
- * Locate SNP corresponding to EFI device
+ * Locate SNP device corresponding to network device
  *
- * @v driver           EFI driver
- * @v device           EFI device
- * @ret snp            EFI SNP, or NULL if not found
+ * @v netdev           Network device
+ * @ret snp            SNP device, or NULL if not found
  */
-static struct efi_snp_device *
-efi_snp_snpdev ( EFI_DRIVER_BINDING_PROTOCOL *driver, EFI_HANDLE device ) {
-       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
-       union {
-               EFI_SIMPLE_NETWORK_PROTOCOL *snp;
-               void *interface;
-       } u;
-       struct efi_snp_device *snpdev = NULL;
-       EFI_STATUS efirc;
+static struct efi_snp_device * efi_snp_demux ( struct net_device *netdev ) {
+       struct efi_snp_device *snpdev;
 
-       if ( ( efirc = bs->OpenProtocol ( device,
-                                         &efi_simple_network_protocol_guid,
-                                         &u.interface,
-                                         driver->DriverBindingHandle,
-                                         device,
-                                         EFI_OPEN_PROTOCOL_GET_PROTOCOL))!=0){
-               DBGC ( driver, "SNPDRV %p device %p could not locate SNP: "
-                      "%s\n", driver, device, efi_strerror ( efirc ) );
-               goto err_no_snp;
+       list_for_each_entry ( snpdev, &efi_snp_devices, list ) {
+               if ( snpdev->netdev == netdev )
+                       return snpdev;
        }
-
-       snpdev =  container_of ( u.snp, struct efi_snp_device, snp );
-       DBGCP ( driver, "SNPDRV %p device %p is SNPDEV %p\n",
-               driver, device, snpdev );
-
-       bs->CloseProtocol ( device, &efi_simple_network_protocol_guid,
-                           driver->DriverBindingHandle, device );
- err_no_snp:
-       return snpdev;
+       return NULL;
 }
 
 /**
- * Check to see if driver supports a device
+ * Create SNP 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
-efi_snp_driver_supported ( EFI_DRIVER_BINDING_PROTOCOL *driver,
-                          EFI_HANDLE device,
-                          EFI_DEVICE_PATH_PROTOCOL *child ) {
-       struct net_device *netdev;
-
-       DBGCP ( driver, "SNPDRV %p DRIVER_SUPPORTED %p (%p)\n",
-               driver, device, child );
-
-       netdev = efi_snp_netdev ( driver, device );
-       return ( netdev ? 0 : EFI_UNSUPPORTED );
-}
-
-/**
- * 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
+ * @v netdev           Network device
+ * @ret rc             Return status code
  */
-static EFI_STATUS EFIAPI
-efi_snp_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver,
-                      EFI_HANDLE device,
-                      EFI_DEVICE_PATH_PROTOCOL *child ) {
+static int efi_snp_probe ( struct net_device *netdev ) {
        EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
-       EFI_DEVICE_PATH_PROTOCOL *path;
-       EFI_DEVICE_PATH_PROTOCOL *subpath;
-       MAC_ADDR_DEVICE_PATH *macpath;
+       struct efi_pci_device *efipci;
        struct efi_snp_device *snpdev;
-       struct net_device *netdev;
-       size_t subpath_len;
+       EFI_DEVICE_PATH_PROTOCOL *path_end;
+       MAC_ADDR_DEVICE_PATH *macpath;
        size_t path_prefix_len = 0;
        unsigned int i;
        EFI_STATUS efirc;
+       int rc;
 
-       DBGCP ( driver, "SNPDRV %p DRIVER_START %p (%p)\n",
-               driver, device, child );
-
-       /* Determine device path prefix length */
-       if ( ( efirc = bs->OpenProtocol ( device,
-                                         &efi_device_path_protocol_guid,
-                                         ( void * ) &path,
-                                         driver->DriverBindingHandle,
-                                         device,
-                                         EFI_OPEN_PROTOCOL_BY_DRIVER )) !=0 ){
-               DBGCP ( driver, "SNPDRV %p device %p has no device path\n",
-                       driver, device );
-               goto err_no_device_path;
-       }
-       subpath = path;
-       while ( subpath->Type != END_DEVICE_PATH_TYPE ) {
-               subpath_len = ( ( subpath->Length[1] << 8 ) |
-                               subpath->Length[0] );
-               path_prefix_len += subpath_len;
-               subpath = ( ( ( void * ) subpath ) + subpath_len );
+       /* Find EFI PCI device */
+       efipci = efipci_find ( netdev->dev );
+       if ( ! efipci ) {
+               DBG ( "SNP skipping non-PCI device %s\n", netdev->name );
+               rc = 0;
+               goto err_no_pci;
        }
 
+       /* Calculate device path prefix length */
+       path_end = efi_devpath_end ( efipci->path );
+       path_prefix_len = ( ( ( void * ) path_end ) -
+                           ( ( void * ) efipci->path ) );
+
        /* Allocate the SNP device */
        snpdev = zalloc ( sizeof ( *snpdev ) + path_prefix_len +
                          sizeof ( *macpath ) );
        if ( ! snpdev ) {
-               efirc = EFI_OUT_OF_RESOURCES;
+               rc = -ENOMEM;
                goto err_alloc_snp;
        }
-
-       /* Identify the net device */
-       netdev = efi_snp_netdev ( driver, device );
-       if ( ! netdev ) {
-               DBGC ( snpdev, "SNPDEV %p cannot find netdev for device %p\n",
-                      snpdev, device );
-               efirc = EFI_UNSUPPORTED;
-               goto err_no_netdev;
-       }
        snpdev->netdev = netdev_get ( netdev );
 
        /* Sanity check */
@@ -939,7 +801,7 @@ efi_snp_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver,
                DBGC ( snpdev, "SNPDEV %p cannot support link-layer address "
                       "length %d for %s\n", snpdev,
                       netdev->ll_protocol->ll_addr_len, netdev->name );
-               efirc = EFI_INVALID_PARAMETER;
+               rc = -ENOTSUP;
                goto err_ll_addr_len;
        }
 
@@ -951,6 +813,7 @@ efi_snp_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver,
                                         &snpdev->snp.WaitForPacket ) ) != 0 ){
                DBGC ( snpdev, "SNPDEV %p could not create event: %s\n",
                       snpdev, efi_strerror ( efirc ) );
+               rc = EFIRC_TO_RC ( efirc );
                goto err_create_event;
        }
 
@@ -973,9 +836,9 @@ efi_snp_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver,
        }
 
        /* Populate the device path */
-       memcpy ( &snpdev->path, path, path_prefix_len );
+       memcpy ( &snpdev->path, efipci->path, path_prefix_len );
        macpath = ( ( ( void * ) &snpdev->path ) + path_prefix_len );
-       subpath = ( ( void * ) ( macpath + 1 ) );
+       path_end = ( ( void * ) ( macpath + 1 ) );
        memset ( macpath, 0, sizeof ( *macpath ) );
        macpath->Header.Type = MESSAGING_DEVICE_PATH;
        macpath->Header.SubType = MSG_MAC_ADDR_DP;
@@ -983,10 +846,10 @@ efi_snp_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver,
        memcpy ( &macpath->MacAddress, netdev->ll_addr,
                 sizeof ( macpath->MacAddress ) );
        macpath->IfType = ntohs ( netdev->ll_protocol->ll_proto );
-       memset ( subpath, 0, sizeof ( *subpath ) );
-       subpath->Type = END_DEVICE_PATH_TYPE;
-       subpath->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE;
-       subpath->Length[0] = sizeof ( *subpath );
+       memset ( path_end, 0, sizeof ( *path_end ) );
+       path_end->Type = END_DEVICE_PATH_TYPE;
+       path_end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE;
+       path_end->Length[0] = sizeof ( *path_end );
 
        /* Install the SNP */
        if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
@@ -998,9 +861,13 @@ efi_snp_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver,
                        NULL ) ) != 0 ) {
                DBGC ( snpdev, "SNPDEV %p could not install protocols: "
                       "%s\n", snpdev, efi_strerror ( efirc ) );
+               rc = EFIRC_TO_RC ( efirc );
                goto err_install_protocol_interface;
        }
 
+       /* Add to list of SNP devices */
+       list_add ( &snpdev->list, &efi_snp_devices );
+
        DBGC ( snpdev, "SNPDEV %p installed for %s as device %p\n",
               snpdev, netdev->name, snpdev->handle );
        return 0;
@@ -1017,44 +884,39 @@ efi_snp_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver,
  err_create_event:
  err_ll_addr_len:
        netdev_put ( netdev );
- err_no_netdev:
        free ( snpdev );
  err_alloc_snp:
-       bs->CloseProtocol ( device, &efi_device_path_protocol_guid,
-                           driver->DriverBindingHandle, device );
- err_no_device_path:
-       return efirc;
+ err_no_pci:
+       return rc;
 }
 
 /**
- * Detach driver from device
+ * Handle SNP device or link state change
  *
- * @v driver           EFI driver
- * @v device           EFI device
- * @v num_children     Number of child devices
- * @v children         List of child devices
- * @ret efirc          EFI status code
+ * @v netdev           Network device
  */
-static EFI_STATUS EFIAPI
-efi_snp_driver_stop ( EFI_DRIVER_BINDING_PROTOCOL *driver,
-                     EFI_HANDLE device,
-                     UINTN num_children,
-                     EFI_HANDLE *children ) {
+static void efi_snp_notify ( struct net_device *netdev __unused ) {
+       /* Nothing to do */
+}
+
+/**
+ * Destroy SNP device
+ *
+ * @v netdev           Network device
+ */
+static void efi_snp_remove ( struct net_device *netdev ) {
        EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
        struct efi_snp_device *snpdev;
 
-       DBGCP ( driver, "SNPDRV %p DRIVER_STOP %p (%ld %p)\n",
-               driver, device, ( ( unsigned long ) num_children ), children );
-
        /* Locate SNP device */
-       snpdev = efi_snp_snpdev ( driver, device );
+       snpdev = efi_snp_demux ( netdev );
        if ( ! snpdev ) {
-               DBGC ( driver, "SNPDRV %p device %p could not find SNPDEV\n",
-                      driver, device );
-               return EFI_DEVICE_ERROR;
+               DBG ( "SNP skipping non-SNP device %s\n", netdev->name );
+               return;
        }
 
        /* Uninstall the SNP */
+       list_del ( &snpdev->list );
        bs->UninstallMultipleProtocolInterfaces (
                        snpdev->handle,
                        &efi_simple_network_protocol_guid, &snpdev->snp,
@@ -1065,87 +927,12 @@ efi_snp_driver_stop ( EFI_DRIVER_BINDING_PROTOCOL *driver,
        bs->CloseEvent ( snpdev->snp.WaitForPacket );
        netdev_put ( snpdev->netdev );
        free ( snpdev );
-       bs->CloseProtocol ( device, &efi_device_path_protocol_guid,
-                           driver->DriverBindingHandle, device );
-       return 0;
 }
 
-/** EFI SNP driver binding */
-static EFI_DRIVER_BINDING_PROTOCOL efi_snp_binding = {
-       efi_snp_driver_supported,
-       efi_snp_driver_start,
-       efi_snp_driver_stop,
-       0x10,
-       NULL,
-       NULL
+/** SNP driver */
+struct net_driver efi_snp_driver __net_driver = {
+       .name = "SNP",
+       .probe = efi_snp_probe,
+       .notify = efi_snp_notify,
+       .remove = efi_snp_remove,
 };
-
-/**
- * Look up driver name
- *
- * @v wtf              Component name protocol
- * @v language         Language to use
- * @v driver_name      Driver name to fill in
- * @ret efirc          EFI status code
- */
-static EFI_STATUS EFIAPI
-efi_snp_get_driver_name ( EFI_COMPONENT_NAME2_PROTOCOL *wtf __unused,
-                         CHAR8 *language __unused, CHAR16 **driver_name ) {
-
-       *driver_name = L"" PRODUCT_SHORT_NAME " Driver";
-       return 0;
-}
-
-/**
- * Look up controller name
- *
- * @v wtf              Component name protocol
- * @v device           Device
- * @v child            Child device, or NULL
- * @v language         Language to use
- * @v driver_name      Device name to fill in
- * @ret efirc          EFI status code
- */
-static EFI_STATUS EFIAPI
-efi_snp_get_controller_name ( EFI_COMPONENT_NAME2_PROTOCOL *wtf __unused,
-                             EFI_HANDLE device __unused,
-                             EFI_HANDLE child __unused,
-                             CHAR8 *language __unused,
-                             CHAR16 **controller_name __unused ) {
-
-       /* Just let EFI use the default Device Path Name */
-       return EFI_UNSUPPORTED;
-}
-
-/** EFI SNP component name protocol */
-static EFI_COMPONENT_NAME2_PROTOCOL efi_snp_name = {
-       efi_snp_get_driver_name,
-       efi_snp_get_controller_name,
-       "en"
-};
-
-/**
- * Install EFI SNP driver
- *
- * @ret rc             Return status code
- */
-int efi_snp_install ( void ) {
-       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
-       EFI_DRIVER_BINDING_PROTOCOL *driver = &efi_snp_binding;
-       EFI_STATUS efirc;
-
-       driver->ImageHandle = efi_image_handle;
-       if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
-                       &driver->DriverBindingHandle,
-                       &efi_driver_binding_protocol_guid, driver,
-                       &efi_component_name2_protocol_guid, &efi_snp_name,
-                       NULL ) ) != 0 ) {
-               DBGC ( driver, "SNPDRV %p could not install protocols: "
-                      "%s\n", driver, efi_strerror ( efirc ) );
-               return EFIRC_TO_RC ( efirc );
-       }
-
-       DBGC ( driver, "SNPDRV %p driver binding installed as %p\n",
-              driver, driver->DriverBindingHandle );
-       return 0;
-}