diff --git a/src/include/ipxe/efi/efi.h b/src/include/ipxe/efi/efi.h index d3415532..48ec0968 100644 --- a/src/include/ipxe/efi/efi.h +++ b/src/include/ipxe/efi/efi.h @@ -214,6 +214,7 @@ extern EFI_HANDLE efi_image_handle; extern EFI_LOADED_IMAGE_PROTOCOL *efi_loaded_image; extern EFI_DEVICE_PATH_PROTOCOL *efi_loaded_image_path; extern EFI_SYSTEM_TABLE *efi_systab; +extern int efi_shutdown_in_progress; extern const __attribute__ (( pure )) char * efi_guid_ntoa ( EFI_GUID *guid ); extern const __attribute__ (( pure )) char * diff --git a/src/interface/efi/efi_init.c b/src/interface/efi/efi_init.c index 93ada21d..cfaff606 100644 --- a/src/interface/efi/efi_init.c +++ b/src/interface/efi/efi_init.c @@ -35,6 +35,9 @@ EFI_LOADED_IMAGE_PROTOCOL *efi_loaded_image; /** System table passed to entry point */ EFI_SYSTEM_TABLE *efi_systab; +/** EFI shutdown is in progress */ +int efi_shutdown_in_progress; + /** Event used to signal shutdown */ static EFI_EVENT efi_shutdown_event; @@ -50,6 +53,13 @@ static EFI_STATUS EFIAPI efi_unload ( EFI_HANDLE image_handle ); */ static EFIAPI void efi_shutdown_hook ( EFI_EVENT event __unused, void *context __unused ) { + + /* Mark shutdown as being in progress, to indicate that large + * parts of the system (e.g. timers) are no longer functional. + */ + efi_shutdown_in_progress = 1; + + /* Shut down iPXE */ shutdown_boot(); } diff --git a/src/interface/efi/efi_timer.c b/src/interface/efi/efi_timer.c index da064120..1fd9971e 100644 --- a/src/interface/efi/efi_timer.c +++ b/src/interface/efi/efi_timer.c @@ -70,6 +70,31 @@ static void efi_udelay ( unsigned long usecs ) { */ static unsigned long efi_currticks ( void ) { + /* EFI provides no clean way for device drivers to shut down + * in preparation for handover to a booted operating system. + * The platform firmware simply doesn't bother to call the + * drivers' Stop() methods. Instead, drivers must register an + * EVT_SIGNAL_EXIT_BOOT_SERVICES event to be signalled when + * ExitBootServices() is called, and clean up without any + * reference to the EFI driver model. + * + * Unfortunately, all timers silently stop working when + * ExitBootServices() is called. Even more unfortunately, and + * for no discernible reason, this happens before any + * EVT_SIGNAL_EXIT_BOOT_SERVICES events are signalled. The + * net effect of this entertaining design choice is that any + * timeout loops on the shutdown path (e.g. for gracefully + * closing outstanding TCP connections) may wait indefinitely. + * + * There is no way to report failure from currticks(), since + * the API lazily assumes that the host system continues to + * travel through time in the usual direction. Work around + * EFI's violation of this assumption by falling back to a + * simple free-running monotonic counter. + */ + if ( efi_shutdown_in_progress ) + efi_jiffies++; + return efi_jiffies; }