[efi] Add EFI image format and basic runtime environment
[people/sha0/gpxe.git] / src / interface / efi / efi_timer.c
1 /*
2  * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <limits.h>
20 #include <assert.h>
21 #include <unistd.h>
22 #include <gpxe/timer.h>
23 #include <gpxe/efi/efi.h>
24 #include <gpxe/efi/Protocol/Cpu.h>
25
26 /** @file
27  *
28  * gPXE timer API for EFI
29  *
30  */
31
32 /** Scale factor to apply to CPU timer 0
33  *
34  * The timer is scaled down in order to ensure that reasonable values
35  * for "number of ticks" don't exceed the size of an unsigned long.
36  */
37 #define EFI_TIMER0_SHIFT 12
38
39 /** Calibration time */
40 #define EFI_CALIBRATE_DELAY_MS 1
41
42 /** CPU protocol */
43 static EFI_CPU_ARCH_PROTOCOL *cpu_arch;
44 EFI_REQUIRE_PROTOCOL ( EFI_CPU_ARCH_PROTOCOL, &cpu_arch );
45
46 /**
47  * Delay for a fixed number of microseconds
48  *
49  * @v usecs             Number of microseconds for which to delay
50  */
51 static void efi_udelay ( unsigned long usecs ) {
52         EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
53         EFI_STATUS efirc;
54
55         if ( ( efirc = bs->Stall ( usecs ) ) != 0 ) {
56                 DBG ( "EFI could not delay for %ldus: %lx\n",
57                       usecs, efirc );
58                 /* Probably screwed */
59         }
60 }
61
62 /**
63  * Get current system time in ticks
64  *
65  * @ret ticks           Current time, in ticks
66  */
67 static unsigned long efi_currticks ( void ) {
68         UINT64 time;
69         EFI_STATUS efirc;
70
71         /* Read CPU timer 0 (TSC) */
72         if ( ( efirc = cpu_arch->GetTimerValue ( cpu_arch, 0, &time,
73                                                  NULL ) ) != 0 ) {
74                 DBG ( "EFI could not read CPU timer: %lx\n", efirc );
75                 /* Probably screwed */
76                 return -1UL;
77         }
78
79         return ( time >> EFI_TIMER0_SHIFT );
80 }
81
82 /**
83  * Get number of ticks per second
84  *
85  * @ret ticks_per_sec   Number of ticks per second
86  */
87 static unsigned long efi_ticks_per_sec ( void ) {
88         static unsigned long ticks_per_sec = 0;
89
90         /* Calibrate timer, if necessary.  EFI does nominally provide
91          * the timer speed via the (optional) TimerPeriod parameter to
92          * the GetTimerValue() call, but it gets the speed slightly
93          * wrong.  By up to three orders of magnitude.  Not helpful.
94          */
95         if ( ! ticks_per_sec ) {
96                 unsigned long start;
97                 unsigned long elapsed;
98
99                 DBG ( "Calibrating EFI timer with a %d ms delay\n",
100                       EFI_CALIBRATE_DELAY_MS );
101                 start = currticks();
102                 mdelay ( EFI_CALIBRATE_DELAY_MS );
103                 elapsed = ( currticks() - start );
104                 ticks_per_sec = ( elapsed * ( 1000 / EFI_CALIBRATE_DELAY_MS ));
105                 DBG ( "EFI CPU timer calibrated at %ld ticks in %d ms (%ld "
106                       "ticks/sec)\n", elapsed, EFI_CALIBRATE_DELAY_MS,
107                       ticks_per_sec );
108         }
109
110         return ticks_per_sec;
111 }
112
113 PROVIDE_TIMER ( efi, udelay, efi_udelay );
114 PROVIDE_TIMER ( efi, currticks, efi_currticks );
115 PROVIDE_TIMER ( efi, ticks_per_sec, efi_ticks_per_sec );