Files
ipxe/src/interface/efi/efi_driver.c
Michael Brown e10a40d41f [efi] Avoid dropping below TPL as at entry to iPXE
iPXE will currently drop to TPL_APPLICATION whenever the current
system time is obtained via currticks(), since the system time
mechanism relies on a timer that can fire only when the TPL is below
TPL_CALLBACK.

This can cause unexpected behaviour if the system time is obtained in
the middle of an API call into iPXE by external code.  For example,
MnpDxe sets up a 10ms periodic timer running at TPL_CALLBACK to poll
the underling EFI_SIMPLE_NETWORK_PROTOCOL device for received packets.
If the resulting poll within iPXE happens to hit a code path that
requires obtaining the current system time (e.g. due to reception of
an STP packet, which affects iPXE's blocked link timer), then iPXE
will end up temporarily dropping to TPL_APPLICATION.  This can
potentially result in retriggering the MnpDxe periodic timer, causing
code to be unexpectedly re-entered.

Fix by recording the external TPL at any entry point into iPXE and
dropping only as far as this external TPL, rather than dropping
unconditionally to TPL_APPLICATION.

The side effect of this change is that iPXE's view of the current
system time will be frozen for the duration of any API calls made into
iPXE by external code at TPL_CALLBACK or above.  Since any such
external code is already responsible for allowing execution at
TPL_APPLICATION to occur, then this should not cause a problem in
practice.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
2020-11-20 16:57:50 +00:00

600 lines
16 KiB
C

/*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ipxe/version.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/Protocol/DriverBinding.h>
#include <ipxe/efi/Protocol/ComponentName2.h>
#include <ipxe/efi/Protocol/DevicePath.h>
#include <ipxe/efi/efi_strings.h>
#include <ipxe/efi/efi_path.h>
#include <ipxe/efi/efi_driver.h>
/** @file
*
* EFI driver interface
*
*/
/* Disambiguate the various error causes */
#define EINFO_EEFI_CONNECT \
__einfo_uniqify ( EINFO_EPLATFORM, 0x01, \
"Could not connect controllers" )
#define EINFO_EEFI_CONNECT_PROHIBITED \
__einfo_platformify ( EINFO_EEFI_CONNECT, \
EFI_SECURITY_VIOLATION, \
"Connecting controllers prohibited by " \
"security policy" )
#define EEFI_CONNECT_PROHIBITED \
__einfo_error ( EINFO_EEFI_CONNECT_PROHIBITED )
#define EEFI_CONNECT( efirc ) EPLATFORM ( EINFO_EEFI_CONNECT, efirc, \
EEFI_CONNECT_PROHIBITED )
static EFI_DRIVER_BINDING_PROTOCOL efi_driver_binding;
/** List of controlled EFI devices */
static LIST_HEAD ( efi_devices );
/** We are currently disconnecting drivers */
static int efi_driver_disconnecting;
/**
* Find EFI device
*
* @v device EFI device handle
* @ret efidev EFI device, or NULL if not found
*/
static struct efi_device * efidev_find ( EFI_HANDLE device ) {
struct efi_device *efidev;
/* Look for an existing EFI device */
list_for_each_entry ( efidev, &efi_devices, dev.siblings ) {
if ( efidev->device == device )
return efidev;
}
return NULL;
}
/**
* Get parent EFI device
*
* @v dev Generic device
* @ret efidev Parent EFI device, or NULL
*/
struct efi_device * efidev_parent ( struct device *dev ) {
struct device *parent;
struct efi_device *efidev;
/* Walk upwards until we find a registered EFI device */
while ( ( parent = dev->parent ) ) {
list_for_each_entry ( efidev, &efi_devices, dev.siblings ) {
if ( parent == &efidev->dev )
return efidev;
}
dev = parent;
}
return NULL;
}
/**
* 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
efi_driver_supported ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused,
EFI_HANDLE device, EFI_DEVICE_PATH_PROTOCOL *child ) {
struct efi_driver *efidrv;
int rc;
DBGCP ( device, "EFIDRV %s DRIVER_SUPPORTED",
efi_handle_name ( device ) );
if ( child )
DBGCP ( device, " (child %s)", efi_devpath_text ( child ) );
DBGCP ( device, "\n" );
/* Do nothing if we are already driving this device */
if ( efidev_find ( device ) != NULL ) {
DBGCP ( device, "EFIDRV %s is already started\n",
efi_handle_name ( device ) );
return EFI_ALREADY_STARTED;
}
/* Look for a driver claiming to support this device */
for_each_table_entry ( efidrv, EFI_DRIVERS ) {
if ( ( rc = efidrv->supported ( device ) ) == 0 ) {
DBGC ( device, "EFIDRV %s has driver \"%s\"\n",
efi_handle_name ( device ), efidrv->name );
return 0;
}
}
DBGCP ( device, "EFIDRV %s has no driver\n",
efi_handle_name ( device ) );
return 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
*/
static EFI_STATUS EFIAPI
efi_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused,
EFI_HANDLE device, EFI_DEVICE_PATH_PROTOCOL *child ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
struct efi_driver *efidrv;
struct efi_device *efidev;
struct efi_saved_tpl tpl;
union {
EFI_DEVICE_PATH_PROTOCOL *path;
void *interface;
} path;
EFI_DEVICE_PATH_PROTOCOL *path_end;
size_t path_len;
EFI_STATUS efirc;
int rc;
DBGC ( device, "EFIDRV %s DRIVER_START", efi_handle_name ( device ) );
if ( child )
DBGC ( device, " (child %s)", efi_devpath_text ( child ) );
DBGC ( device, "\n" );
/* Do nothing if we are already driving this device */
efidev = efidev_find ( device );
if ( efidev ) {
DBGCP ( device, "EFIDRV %s is already started\n",
efi_handle_name ( device ) );
efirc = EFI_ALREADY_STARTED;
goto err_already_started;
}
/* Raise TPL */
efi_raise_tpl ( &tpl );
/* Do nothing if we are currently disconnecting drivers */
if ( efi_driver_disconnecting ) {
DBGC ( device, "EFIDRV %s refusing to start during "
"disconnection\n", efi_handle_name ( device ) );
efirc = EFI_NOT_READY;
goto err_disconnecting;
}
/* Open device path */
if ( ( efirc = bs->OpenProtocol ( device,
&efi_device_path_protocol_guid,
&path.interface, efi_image_handle,
device,
EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
rc = -EEFI ( efirc );
DBGC ( device, "EFIDRV %s could not open device path: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
goto err_open_path;
}
path_len = ( efi_path_len ( path.path ) + sizeof ( *path_end ) );
/* Allocate and initialise structure */
efidev = zalloc ( sizeof ( *efidev ) + path_len );
if ( ! efidev ) {
efirc = EFI_OUT_OF_RESOURCES;
goto err_alloc;
}
efidev->device = device;
efidev->dev.desc.bus_type = BUS_TYPE_EFI;
efidev->path = ( ( ( void * ) efidev ) + sizeof ( *efidev ) );
memcpy ( efidev->path, path.path, path_len );
INIT_LIST_HEAD ( &efidev->dev.children );
list_add ( &efidev->dev.siblings, &efi_devices );
/* Close device path */
bs->CloseProtocol ( device, &efi_device_path_protocol_guid,
efi_image_handle, device );
path.path = NULL;
/* Try to start this device */
for_each_table_entry ( efidrv, EFI_DRIVERS ) {
if ( ( rc = efidrv->supported ( device ) ) != 0 ) {
DBGC ( device, "EFIDRV %s is not supported by driver "
"\"%s\": %s\n", efi_handle_name ( device ),
efidrv->name,
strerror ( rc ) );
continue;
}
if ( ( rc = efidrv->start ( efidev ) ) == 0 ) {
efidev->driver = efidrv;
DBGC ( device, "EFIDRV %s using driver \"%s\"\n",
efi_handle_name ( device ),
efidev->driver->name );
efi_restore_tpl ( &tpl );
return 0;
}
DBGC ( device, "EFIDRV %s could not start driver \"%s\": %s\n",
efi_handle_name ( device ), efidrv->name,
strerror ( rc ) );
}
efirc = EFI_UNSUPPORTED;
list_del ( &efidev->dev.siblings );
free ( efidev );
err_alloc:
if ( path.path ) {
bs->CloseProtocol ( device, &efi_device_path_protocol_guid,
efi_image_handle, device );
}
err_open_path:
err_disconnecting:
efi_restore_tpl ( &tpl );
err_already_started:
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
efi_driver_stop ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused,
EFI_HANDLE device, UINTN num_children,
EFI_HANDLE *children ) {
struct efi_driver *efidrv;
struct efi_device *efidev;
struct efi_saved_tpl tpl;
UINTN i;
DBGC ( device, "EFIDRV %s DRIVER_STOP", efi_handle_name ( device ) );
for ( i = 0 ; i < num_children ; i++ ) {
DBGC ( device, "%s%s", ( i ? ", " : " child " ),
efi_handle_name ( children[i] ) );
}
DBGC ( device, "\n" );
/* Do nothing unless we are driving this device */
efidev = efidev_find ( device );
if ( ! efidev ) {
DBGCP ( device, "EFIDRV %s is not started\n",
efi_handle_name ( device ) );
return EFI_DEVICE_ERROR;
}
/* Raise TPL */
efi_raise_tpl ( &tpl );
/* Stop this device */
efidrv = efidev->driver;
assert ( efidrv != NULL );
efidrv->stop ( efidev );
list_del ( &efidev->dev.siblings );
free ( efidev );
efi_restore_tpl ( &tpl );
return 0;
}
/** EFI driver binding protocol */
static EFI_DRIVER_BINDING_PROTOCOL efi_driver_binding = {
.Supported = efi_driver_supported,
.Start = efi_driver_start,
.Stop = efi_driver_stop,
};
/**
* 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_name ( EFI_COMPONENT_NAME2_PROTOCOL *wtf __unused,
CHAR8 *language __unused, CHAR16 **driver_name ) {
const wchar_t *name;
name = ( product_wname[0] ? product_wname : build_wname );
*driver_name = ( ( wchar_t * ) name );
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_controller_name ( EFI_COMPONENT_NAME2_PROTOCOL *wtf __unused,
EFI_HANDLE device, EFI_HANDLE child,
CHAR8 *language, CHAR16 **controller_name ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
union {
EFI_COMPONENT_NAME2_PROTOCOL *name2;
void *interface;
} name2;
EFI_STATUS efirc;
/* Delegate to the EFI_COMPONENT_NAME2_PROTOCOL instance
* installed on child handle, if present.
*/
if ( ( child != NULL ) &&
( ( efirc = bs->OpenProtocol (
child, &efi_component_name2_protocol_guid,
&name2.interface, NULL, NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) == 0 ) ) {
return name2.name2->GetControllerName ( name2.name2, device,
child, language,
controller_name );
}
/* Otherwise, let EFI use the default Device Path Name */
return EFI_UNSUPPORTED;
}
/** EFI component name protocol */
static EFI_COMPONENT_NAME2_PROTOCOL efi_wtf = {
.GetDriverName = efi_driver_name,
.GetControllerName = efi_driver_controller_name,
.SupportedLanguages = "en",
};
/**
* Install EFI driver
*
* @ret rc Return status code
*/
int efi_driver_install ( void ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_STATUS efirc;
int rc;
/* Calculate driver version number. We use the build
* timestamp (in seconds since the Epoch) shifted right by six
* bits: this gives us an approximately one-minute resolution
* and a scheme which will last until the year 10680.
*/
efi_driver_binding.Version = ( build_timestamp >> 6 );
/* Install protocols on image handle */
efi_driver_binding.ImageHandle = efi_image_handle;
efi_driver_binding.DriverBindingHandle = efi_image_handle;
if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
&efi_image_handle,
&efi_driver_binding_protocol_guid, &efi_driver_binding,
&efi_component_name2_protocol_guid, &efi_wtf,
NULL ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( &efi_driver_binding, "EFIDRV could not install "
"protocols: %s\n", strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Uninstall EFI driver
*
*/
void efi_driver_uninstall ( void ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
/* Uninstall protocols */
bs->UninstallMultipleProtocolInterfaces (
efi_image_handle,
&efi_driver_binding_protocol_guid, &efi_driver_binding,
&efi_component_name2_protocol_guid, &efi_wtf, NULL );
}
/**
* Try to connect EFI driver
*
* @v device EFI device
* @ret rc Return status code
*/
static int efi_driver_connect ( EFI_HANDLE device ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_HANDLE drivers[2] =
{ efi_driver_binding.DriverBindingHandle, NULL };
EFI_STATUS efirc;
int rc;
/* Check if we want to drive this device */
if ( ( efirc = efi_driver_supported ( &efi_driver_binding, device,
NULL ) ) != 0 ) {
/* Not supported; not an error */
return 0;
}
/* Disconnect any existing drivers */
DBGC2 ( device, "EFIDRV %s before disconnecting:\n",
efi_handle_name ( device ) );
DBGC2_EFI_PROTOCOLS ( device, device );
DBGC ( device, "EFIDRV %s disconnecting existing drivers\n",
efi_handle_name ( device ) );
efi_driver_disconnecting = 1;
if ( ( efirc = bs->DisconnectController ( device, NULL,
NULL ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( device, "EFIDRV %s could not disconnect existing "
"drivers: %s\n", efi_handle_name ( device ),
strerror ( rc ) );
/* Ignore the error and attempt to connect our drivers */
}
efi_driver_disconnecting = 0;
DBGC2 ( device, "EFIDRV %s after disconnecting:\n",
efi_handle_name ( device ) );
DBGC2_EFI_PROTOCOLS ( device, device );
/* Connect our driver */
DBGC ( device, "EFIDRV %s connecting new drivers\n",
efi_handle_name ( device ) );
if ( ( efirc = bs->ConnectController ( device, drivers, NULL,
TRUE ) ) != 0 ) {
rc = -EEFI_CONNECT ( efirc );
DBGC ( device, "EFIDRV %s could not connect new drivers: "
"%s\n", efi_handle_name ( device ), strerror ( rc ) );
DBGC ( device, "EFIDRV %s connecting driver directly\n",
efi_handle_name ( device ) );
if ( ( efirc = efi_driver_start ( &efi_driver_binding, device,
NULL ) ) != 0 ) {
rc = -EEFI_CONNECT ( efirc );
DBGC ( device, "EFIDRV %s could not connect driver "
"directly: %s\n", efi_handle_name ( device ),
strerror ( rc ) );
return rc;
}
}
DBGC2 ( device, "EFIDRV %s after connecting:\n",
efi_handle_name ( device ) );
DBGC2_EFI_PROTOCOLS ( device, device );
return 0;
}
/**
* Try to disconnect EFI driver
*
* @v device EFI device
* @ret rc Return status code
*/
static int efi_driver_disconnect ( EFI_HANDLE device ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
/* Disconnect our driver */
efi_driver_disconnecting = 1;
bs->DisconnectController ( device,
efi_driver_binding.DriverBindingHandle,
NULL );
efi_driver_disconnecting = 0;
return 0;
}
/**
* Reconnect original EFI driver
*
* @v device EFI device
* @ret rc Return status code
*/
static int efi_driver_reconnect ( EFI_HANDLE device ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
/* Reconnect any available driver */
bs->ConnectController ( device, NULL, NULL, TRUE );
return 0;
}
/**
* Connect/disconnect EFI driver from all handles
*
* @v method Connect/disconnect method
* @ret rc Return status code
*/
static int efi_driver_handles ( int ( * method ) ( EFI_HANDLE handle ) ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_HANDLE *handles;
UINTN num_handles;
EFI_STATUS efirc;
UINTN i;
int rc;
/* Enumerate all handles */
if ( ( efirc = bs->LocateHandleBuffer ( AllHandles, NULL, NULL,
&num_handles,
&handles ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( &efi_driver_binding, "EFIDRV could not list handles: "
"%s\n", strerror ( rc ) );
goto err_locate;
}
/* Connect/disconnect driver from all handles */
for ( i = 0 ; i < num_handles ; i++ ) {
if ( ( rc = method ( handles[i] ) ) != 0 ) {
/* Ignore errors and continue to process
* remaining handles.
*/
}
}
/* Success */
rc = 0;
bs->FreePool ( handles );
err_locate:
return rc;
}
/**
* Connect EFI driver to all possible devices
*
* @ret rc Return status code
*/
int efi_driver_connect_all ( void ) {
DBGC ( &efi_driver_binding, "EFIDRV connecting our drivers\n" );
return efi_driver_handles ( efi_driver_connect );
}
/**
* Disconnect EFI driver from all possible devices
*
* @ret rc Return status code
*/
void efi_driver_disconnect_all ( void ) {
DBGC ( &efi_driver_binding, "EFIDRV disconnecting our drivers\n" );
efi_driver_handles ( efi_driver_disconnect );
}
/**
* Reconnect original EFI drivers to all possible devices
*
* @ret rc Return status code
*/
void efi_driver_reconnect_all ( void ) {
DBGC ( &efi_driver_binding, "EFIDRV reconnecting old drivers\n" );
efi_driver_handles ( efi_driver_reconnect );
}