1 /* 2 * Copyright (C) 2008 Michael Brown <mbrown (at) 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 FILE_LICENCE ( GPL2_OR_LATER ); 20 21 #include <limits.h> 22 #include <assert.h> 23 #include <unistd.h> 24 #include <gpxe/timer.h> 25 #include <gpxe/efi/efi.h> 26 #include <gpxe/efi/Protocol/Cpu.h> 27 28 /** @file 29 * 30 * gPXE timer API for EFI 31 * 32 */ 33 34 /** Scale factor to apply to CPU timer 0 35 * 36 * The timer is scaled down in order to ensure that reasonable values 37 * for "number of ticks" don't exceed the size of an unsigned long. 38 */ 39 #define EFI_TIMER0_SHIFT 12 40 41 /** Calibration time */ 42 #define EFI_CALIBRATE_DELAY_MS 1 43 44 /** CPU protocol */ 45 static EFI_CPU_ARCH_PROTOCOL *cpu_arch; 46 EFI_REQUIRE_PROTOCOL ( EFI_CPU_ARCH_PROTOCOL, &cpu_arch ); 47 48 /** 49 * Delay for a fixed number of microseconds 50 * 51 * @v usecs Number of microseconds for which to delay 52 */ 53 static void efi_udelay ( unsigned long usecs ) { 54 EFI_BOOT_SERVICES *bs = efi_systab->BootServices; 55 EFI_STATUS efirc; 56 57 if ( ( efirc = bs->Stall ( usecs ) ) != 0 ) { 58 DBG ( "EFI could not delay for %ldus: %s\n", 59 usecs, efi_strerror ( efirc ) ); 60 /* Probably screwed */ 61 } 62 } 63 64 /** 65 * Get current system time in ticks 66 * 67 * @ret ticks Current time, in ticks 68 */ 69 static unsigned long efi_currticks ( void ) { 70 UINT64 time; 71 EFI_STATUS efirc; 72 73 /* Read CPU timer 0 (TSC) */ 74 if ( ( efirc = cpu_arch->GetTimerValue ( cpu_arch, 0, &time, 75 NULL ) ) != 0 ) { 76 DBG ( "EFI could not read CPU timer: %s\n", 77 efi_strerror ( efirc ) ); 78 /* Probably screwed */ 79 return -1UL; 80 } 81 82 return ( time >> EFI_TIMER0_SHIFT ); 83 } 84 85 /** 86 * Get number of ticks per second 87 * 88 * @ret ticks_per_sec Number of ticks per second 89 */ 90 static unsigned long efi_ticks_per_sec ( void ) { 91 static unsigned long ticks_per_sec = 0; 92 93 /* Calibrate timer, if necessary. EFI does nominally provide 94 * the timer speed via the (optional) TimerPeriod parameter to 95 * the GetTimerValue() call, but it gets the speed slightly 96 * wrong. By up to three orders of magnitude. Not helpful. 97 */ 98 if ( ! ticks_per_sec ) { 99 unsigned long start; 100 unsigned long elapsed; 101 102 DBG ( "Calibrating EFI timer with a %d ms delay\n", 103 EFI_CALIBRATE_DELAY_MS ); 104 start = currticks(); 105 mdelay ( EFI_CALIBRATE_DELAY_MS ); 106 elapsed = ( currticks() - start ); 107 ticks_per_sec = ( elapsed * ( 1000 / EFI_CALIBRATE_DELAY_MS )); 108 DBG ( "EFI CPU timer calibrated at %ld ticks in %d ms (%ld " 109 "ticks/sec)\n", elapsed, EFI_CALIBRATE_DELAY_MS, 110 ticks_per_sec ); 111 } 112 113 return ticks_per_sec; 114 } 115 116 PROVIDE_TIMER ( efi, udelay, efi_udelay ); 117 PROVIDE_TIMER ( efi, currticks, efi_currticks ); 118 PROVIDE_TIMER ( efi, ticks_per_sec, efi_ticks_per_sec ); 119