diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Devices/PC/ipxe/src/interface/efi/efi_timer.c | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/src/VBox/Devices/PC/ipxe/src/interface/efi/efi_timer.c b/src/VBox/Devices/PC/ipxe/src/interface/efi/efi_timer.c new file mode 100644 index 00000000..405cd345 --- /dev/null +++ b/src/VBox/Devices/PC/ipxe/src/interface/efi/efi_timer.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2008 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. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <ipxe/timer.h> +#include <ipxe/init.h> +#include <ipxe/efi/efi.h> + +/** @file + * + * iPXE timer API for EFI + * + */ + +/** + * Number of jiffies per second + * + * This is a policy decision. + */ +#define EFI_JIFFIES_PER_SEC 32 + +/** Current tick count */ +static unsigned long efi_jiffies; + +/** Timer tick event */ +static EFI_EVENT efi_tick_event; + +/** Colour for debug messages */ +#define colour &efi_jiffies + +/** + * Delay for a fixed number of microseconds + * + * @v usecs Number of microseconds for which to delay + */ +static void efi_udelay ( unsigned long usecs ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + if ( ( efirc = bs->Stall ( usecs ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( colour, "EFI could not delay for %ldus: %s\n", + usecs, strerror ( rc ) ); + /* Probably screwed */ + } +} + +/** + * Get current system time in ticks + * + * @ret ticks Current time, in ticks + */ +static unsigned long efi_currticks ( void ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + + /* UEFI manages to ingeniously combine the worst aspects of + * both polling and interrupt-driven designs. There is no way + * to support proper interrupt-driven operation, since there + * is no way to hook in an interrupt service routine. A + * mockery of interrupts is provided by UEFI timers, which + * trigger at a preset rate and can fire at any time. + * + * We therefore have all of the downsides of a polling design + * (inefficiency and inability to sleep until something + * interesting happens) combined with all of the downsides of + * an interrupt-driven design (the complexity of code that + * could be preempted at any time). + * + * The UEFI specification expects us to litter the entire + * codebase with calls to RaiseTPL() as needed for sections of + * code that are not reentrant. Since this doesn't actually + * gain us any substantive benefits (since even with such + * calls we would still be suffering from the limitations of a + * polling design), we instead choose to run at TPL_CALLBACK + * almost all of the time, dropping to a lower TPL to allow + * timer ticks to occur. + * + * We record the external TPL at the point of entry into iPXE, + * and drop back only as far as this external TPL. This + * avoids the unexpected behaviour that may arise from having + * iPXE temporarily drop to TPL_APPLICATION in the middle of + * an entry point invoked at TPL_CALLBACK. The side effect is + * that iPXE's view of the system time is effectively frozen + * for the duration of any call made in to iPXE at + * TPL_CALLBACK or higher. + * + * + * For added excitement, UEFI 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, all non-trivial 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 during shutdown. + */ + if ( efi_shutdown_in_progress ) { + efi_jiffies++; + } else { + bs->RestoreTPL ( efi_external_tpl ); + bs->RaiseTPL ( TPL_CALLBACK ); + } + + return ( efi_jiffies * ( TICKS_PER_SEC / EFI_JIFFIES_PER_SEC ) ); +} + +/** + * Timer tick + * + * @v event Timer tick event + * @v context Event context + */ +static EFIAPI void efi_tick ( EFI_EVENT event __unused, + void *context __unused ) { + + /* Increment tick count */ + efi_jiffies++; +} + +/** + * Start timer tick + * + */ +static void efi_tick_startup ( void ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + /* Create timer tick event */ + if ( ( efirc = bs->CreateEvent ( ( EVT_TIMER | EVT_NOTIFY_SIGNAL ), + TPL_CALLBACK, efi_tick, NULL, + &efi_tick_event ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( colour, "EFI could not create timer tick: %s\n", + strerror ( rc ) ); + /* Nothing we can do about it */ + return; + } + + /* Start timer tick */ + if ( ( efirc = bs->SetTimer ( efi_tick_event, TimerPeriodic, + ( 10000000 / EFI_JIFFIES_PER_SEC ) ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( colour, "EFI could not start timer tick: %s\n", + strerror ( rc ) ); + /* Nothing we can do about it */ + return; + } + DBGC ( colour, "EFI timer started at %d ticks per second\n", + EFI_JIFFIES_PER_SEC ); +} + +/** + * Stop timer tick + * + * @v booting System is shutting down in order to boot + */ +static void efi_tick_shutdown ( int booting __unused ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + /* Stop timer tick */ + if ( ( efirc = bs->SetTimer ( efi_tick_event, TimerCancel, 0 ) ) != 0 ){ + rc = -EEFI ( efirc ); + DBGC ( colour, "EFI could not stop timer tick: %s\n", + strerror ( rc ) ); + /* Self-destruct initiated */ + return; + } + DBGC ( colour, "EFI timer stopped\n" ); + + /* Destroy timer tick event */ + if ( ( efirc = bs->CloseEvent ( efi_tick_event ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( colour, "EFI could not destroy timer tick: %s\n", + strerror ( rc ) ); + /* Probably non-fatal */ + return; + } +} + +/** Timer tick startup function */ +struct startup_fn efi_tick_startup_fn __startup_fn ( STARTUP_EARLY ) = { + .name = "efi_tick", + .startup = efi_tick_startup, + .shutdown = efi_tick_shutdown, +}; + +/** EFI timer */ +struct timer efi_timer __timer ( TIMER_NORMAL ) = { + .name = "efi", + .currticks = efi_currticks, + .udelay = efi_udelay, +}; |