/* * Copyright (C) 2008 Michael Brown . * * 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., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include #include #include #include #include #include #include /** @file * * iPXE PCI I/O API for EFI * */ /****************************************************************************** * * 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 ); static unsigned long efipci_address ( struct pci_device *pci, unsigned long location ) { return EFI_PCI_ADDRESS ( PCI_BUS ( pci->busdevfn ), PCI_SLOT ( pci->busdevfn ), PCI_FUNC ( pci->busdevfn ), EFIPCI_OFFSET ( location ) ); } int efipci_read ( struct pci_device *pci, unsigned long location, void *value ) { EFI_STATUS efirc; int rc; if ( ( efirc = efipci->Pci.Read ( efipci, EFIPCI_WIDTH ( location ), efipci_address ( pci, location ), 1, value ) ) != 0 ) { rc = -EEFI ( efirc ); DBG ( "EFIPCI config read from " PCI_FMT " offset %02lx " "failed: %s\n", PCI_ARGS ( pci ), EFIPCI_OFFSET ( location ), strerror ( rc ) ); return -EIO; } return 0; } int efipci_write ( struct pci_device *pci, unsigned long location, unsigned long value ) { EFI_STATUS efirc; int rc; if ( ( efirc = efipci->Pci.Write ( efipci, EFIPCI_WIDTH ( location ), efipci_address ( pci, location ), 1, &value ) ) != 0 ) { rc = -EEFI ( efirc ); DBG ( "EFIPCI config write to " PCI_FMT " offset %02lx " "failed: %s\n", PCI_ARGS ( pci ), EFIPCI_OFFSET ( location ), strerror ( rc ) ); return -EIO; } return 0; } PROVIDE_PCIAPI_INLINE ( efi, pci_num_bus ); PROVIDE_PCIAPI_INLINE ( efi, pci_read_config_byte ); PROVIDE_PCIAPI_INLINE ( efi, pci_read_config_word ); 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; efipci->efidrv = efidrv; /* 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 ){ rc = -EEFI ( efirc ); 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 ) { rc = -EEFI ( efirc ); DBGC ( efipci, "EFIPCI device %p could not get PCI " "location: %s\n", device, strerror ( rc ) ); 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 ){ rc = -EEFI ( efirc ); 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 rc Return status code */ int efipci_enable ( struct efi_pci_device *efipci ) { EFI_PCI_IO_PROTOCOL *pci_io = efipci->pci_io; /* Try to enable I/O cycles, memory cycles, and bus mastering. * Some platforms will 'helpfully' report errors if these bits * can't be enabled (for example, if the card doesn't actually * support I/O cycles). Work around any such platforms by * enabling bits individually and simply ignoring any errors. */ pci_io->Attributes ( pci_io, EfiPciIoAttributeOperationEnable, EFI_PCI_IO_ATTRIBUTE_IO, NULL ); pci_io->Attributes ( pci_io, EfiPciIoAttributeOperationEnable, EFI_PCI_IO_ATTRIBUTE_MEMORY, NULL ); pci_io->Attributes ( pci_io, EfiPciIoAttributeOperationEnable, EFI_PCI_IO_ATTRIBUTE_BUS_MASTER, NULL ); 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; } /** * Add EFI device as child of EFI PCI device * * @v efipci EFI PCI device * @v device EFI child device * @ret efirc EFI status code */ int efipci_child_add ( struct efi_pci_device *efipci, EFI_HANDLE device ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; struct efi_driver *efidrv = efipci->efidrv; union { EFI_PCI_IO_PROTOCOL *pci_io; void *interface; } pci_io; EFI_STATUS efirc; int rc; /* Re-open the PCI_IO_PROTOCOL */ if ( ( efirc = bs->OpenProtocol ( efipci->device, &efi_pci_io_protocol_guid, &pci_io.interface, efidrv->driver.DriverBindingHandle, device, EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( efipci, "EFIPCI " PCI_FMT " could not add child: %s\n", PCI_ARGS ( &efipci->pci ), strerror ( rc ) ); return rc; } return 0; } /** * Remove EFI device as child of PCI device * * @v efipci EFI PCI device * @v device EFI child device * @ret efirc EFI status code */ void efipci_child_del ( struct efi_pci_device *efipci, EFI_HANDLE device ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; struct efi_driver *efidrv = efipci->efidrv; bs->CloseProtocol ( efipci->device, &efi_pci_io_protocol_guid, efidrv->driver.DriverBindingHandle, device ); } /** * 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; 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 */ rc = -ENOTSUP; 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 ) ); 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 ( rc ); } /** * 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; int rc; DBGC ( efidrv, "EFIPCI DRIVER_START %p (%p)\n", device, child ); /* Create corresponding PCI device */ efipci = efipci_create ( efidrv, device ); if ( ! efipci ) { rc = -ENOMEM; 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 ) ); goto err_find_driver; } /* Enable PCI device */ if ( ( rc = 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 ) ); goto err_probe; } return 0; pci_remove ( &efipci->pci ); err_probe: err_enable: err_find_driver: efipci_destroy ( efidrv, efipci ); err_create: return EFIRC ( rc ); } /** * 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_startup ( void ) { struct efi_driver *efidrv = &efipci_driver; int rc; /* Install driver */ if ( ( rc = efi_driver_install ( efidrv ) ) != 0 ) { DBGC ( efidrv, "EFIPCI could not install driver: %s\n", strerror ( rc ) ); return; } DBGC ( efidrv, "EFIPCI driver installed\n" ); } /** * Shut down EFI PCI driver * * @v booting System is shutting down for OS boot */ static void efipci_driver_shutdown ( int booting __unused ) { struct efi_driver *efidrv = &efipci_driver; struct efi_pci_device *efipci; struct efi_pci_device *tmp; /* Shut down any remaining devices */ list_for_each_entry_safe ( efipci, tmp, &efi_pci_devices, list ) { DBGC ( efipci, "EFIPCI " PCI_FMT " still active at shutdown; " "forcing close\n", PCI_ARGS ( &efipci->pci ) ); pci_remove ( &efipci->pci ); efipci_destroy ( efidrv, efipci ); } } /** EFI PCI startup function */ struct startup_fn startup_pci __startup_fn ( STARTUP_NORMAL ) = { .startup = efipci_driver_startup, .shutdown = efipci_driver_shutdown, };