| /* | |
| * 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 ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #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 ) { | |
| struct efi_dropped_tpl tpl; | |
| /* 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 { | |
| efi_drop_tpl ( &tpl ); | |
| efi_undrop_tpl ( &tpl ); | |
| } | |
| 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, | |
| }; |