blob: 6427eb1d82bc94617b261177a627611ddcd31f63 [file] [log] [blame]
/*
* 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,
};