| /* |
| * 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 ( efi_internal_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, |
| }; |