mirror of https://github.com/ipxe/ipxe.git
235 lines
7.1 KiB
C
235 lines
7.1 KiB
C
/*
|
|
* 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,
|
|
};
|