From 3ec2079ce2078ff67c1f857eaf29293586bb5497 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 20 Mar 2018 17:26:49 +0200 Subject: [time] Add support for the ACPI power management timer Allow the ACPI power management timer to be used if enabled via TIMER_ACPI in config/timer.h. This provides an alternative timer on systems where the standard 8254 PIT is unavailable or unreliable. Signed-off-by: Michael Brown --- src/arch/x86/include/bits/errfile.h | 1 + src/arch/x86/interface/pcbios/acpi_timer.c | 136 +++++++++++++++++++++++++++++ src/config/config_timer.c | 3 + src/include/ipxe/acpi.h | 7 ++ 4 files changed, 147 insertions(+) create mode 100644 src/arch/x86/interface/pcbios/acpi_timer.c diff --git a/src/arch/x86/include/bits/errfile.h b/src/arch/x86/include/bits/errfile.h index 9d1fed7f..b0ae1abc 100644 --- a/src/arch/x86/include/bits/errfile.h +++ b/src/arch/x86/include/bits/errfile.h @@ -27,6 +27,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_acpipwr ( ERRFILE_ARCH | ERRFILE_CORE | 0x00100000 ) #define ERRFILE_cpuid ( ERRFILE_ARCH | ERRFILE_CORE | 0x00110000 ) #define ERRFILE_rdtsc_timer ( ERRFILE_ARCH | ERRFILE_CORE | 0x00120000 ) +#define ERRFILE_acpi_timer ( ERRFILE_ARCH | ERRFILE_CORE | 0x00130000 ) #define ERRFILE_bootsector ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00000000 ) #define ERRFILE_bzimage ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00010000 ) diff --git a/src/arch/x86/interface/pcbios/acpi_timer.c b/src/arch/x86/interface/pcbios/acpi_timer.c new file mode 100644 index 00000000..82e85a03 --- /dev/null +++ b/src/arch/x86/interface/pcbios/acpi_timer.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2018 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include + +/** @file + * + * ACPI power management timer + * + */ + +/** ACPI timer frequency (fixed 3.579545MHz) */ +#define ACPI_TIMER_HZ 3579545 + +/** ACPI timer mask + * + * Timers may be implemented as either 24-bit or 32-bit counters. We + * simplify the code by pessimistically assuming that the timer has + * only 24 bits. + */ +#define ACPI_TIMER_MASK 0x00ffffffUL + +/** Power management timer register address */ +static unsigned int pm_tmr; + +struct timer acpi_timer __timer ( TIMER_PREFERRED ); + +/** + * Get current system time in ticks + * + * @ret ticks Current time, in ticks + */ +static unsigned long acpi_currticks ( void ) { + static unsigned long offset; + static uint32_t prev; + uint32_t now; + + /* Read timer and account for wraparound */ + now = ( inl ( pm_tmr ) & ACPI_TIMER_MASK ); + if ( now < prev ) { + offset += ( ( ACPI_TIMER_MASK + 1 ) / + ( ACPI_TIMER_HZ / TICKS_PER_SEC ) ); + } + prev = now; + + /* Convert to timer ticks */ + return ( offset + ( now / ( ACPI_TIMER_HZ / TICKS_PER_SEC ) ) ); +} + +/** + * Delay for a fixed number of microseconds + * + * @v usecs Number of microseconds for which to delay + */ +static void acpi_udelay ( unsigned long usecs ) { + uint32_t start; + uint32_t elapsed; + uint32_t threshold; + + /* Delay until a suitable number of ticks have elapsed. We do + * not need to allow for multiple wraparound, since the + * wraparound period for a 24-bit timer at 3.579545MHz is + * around 4700000us. + */ + start = inl ( pm_tmr ); + threshold = ( ( usecs * ACPI_TIMER_HZ ) / 1000000 ); + do { + elapsed = ( ( inl ( pm_tmr ) - start ) & ACPI_TIMER_MASK ); + } while ( elapsed < threshold ); +} + +/** + * Probe ACPI power management timer + * + * @ret rc Return status code + */ +static int acpi_timer_probe ( void ) { + struct acpi_fadt fadtab; + userptr_t fadt; + unsigned int pm_tmr_blk; + + /* Locate FADT */ + fadt = acpi_find ( FADT_SIGNATURE, 0 ); + if ( ! fadt ) { + DBGC ( &acpi_timer, "ACPI could not find FADT\n" ); + return -ENOENT; + } + + /* Read FADT */ + copy_from_user ( &fadtab, fadt, 0, sizeof ( fadtab ) ); + pm_tmr_blk = le32_to_cpu ( fadtab.pm_tmr_blk ); + if ( ! pm_tmr_blk ) { + DBGC ( &acpi_timer, "ACPI has no timer\n" ); + return -ENOENT; + } + + /* Record power management timer register address */ + pm_tmr = ( pm_tmr_blk + ACPI_PM_TMR ); + + return 0; +} + +/** ACPI timer */ +struct timer acpi_timer __timer ( TIMER_PREFERRED ) = { + .name = "acpi", + .probe = acpi_timer_probe, + .currticks = acpi_currticks, + .udelay = acpi_udelay, +}; diff --git a/src/config/config_timer.c b/src/config/config_timer.c index a462970c..d53c3993 100644 --- a/src/config/config_timer.c +++ b/src/config/config_timer.c @@ -46,3 +46,6 @@ REQUIRE_OBJECT ( efi_timer ); #ifdef TIMER_LINUX REQUIRE_OBJECT ( linux_timer ); #endif +#ifdef TIMER_ACPI +REQUIRE_OBJECT ( acpi_timer ); +#endif diff --git a/src/include/ipxe/acpi.h b/src/include/ipxe/acpi.h index 68131e73..78f40253 100644 --- a/src/include/ipxe/acpi.h +++ b/src/include/ipxe/acpi.h @@ -119,6 +119,10 @@ struct acpi_fadt { uint32_t pm1a_cnt_blk; /** PM1b Control Register Block */ uint32_t pm1b_cnt_blk; + /** PM2 Control Register Block */ + uint32_t pm2_cnt_blk; + /** PM Timer Control Register Block */ + uint32_t pm_tmr_blk; } __attribute__ (( packed )); /** ACPI PM1 Control Register (within PM1a_CNT_BLK or PM1A_CNT_BLK) */ @@ -126,6 +130,9 @@ struct acpi_fadt { #define ACPI_PM1_CNT_SLP_TYP(x) ( (x) << 10 ) /**< Sleep type */ #define ACPI_PM1_CNT_SLP_EN ( 1 << 13 ) /**< Sleep enable */ +/** ACPI PM Timer Register (within PM_TMR_BLK) */ +#define ACPI_PM_TMR 0 + /** Differentiated System Description Table (DSDT) signature */ #define DSDT_SIGNATURE ACPI_SIGNATURE ( 'D', 'S', 'D', 'T' ) -- cgit v1.2.3-55-g7522