196 lines
5.4 KiB
C
196 lines
5.4 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 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, 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.
|
|
*/
|
|
if ( efi_shutdown_in_progress )
|
|
efi_jiffies++;
|
|
|
|
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 ) = {
|
|
.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,
|
|
};
|