diff --git a/src/arch/riscv/core/zicntr.c b/src/arch/riscv/core/zicntr.c new file mode 100644 index 000000000..0ba453c75 --- /dev/null +++ b/src/arch/riscv/core/zicntr.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2024 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 ); + +/** @file + * + * Base counters and timers extension (Zicntr) + * + */ + +#include +#include +#include +#include +#include + +/** Timer increment per microsecond */ +static unsigned long zicntr_mhz; + +/** Minimum resolution for scaled timer */ +#define ZICNTR_SCALED_HZ 32 + +/** + * Timer scale (expressed as a bit shift) + * + * We use this to avoid the need for 64-bit divsion on 32-bit systems. + */ +static unsigned int zicntr_scale; + +/** Number of timer ticks per scaled timer increment */ +static unsigned long zicntr_ticks; + +/** Colour for debug messages */ +#define colour &zicntr_mhz + +/** + * Get low XLEN bits of current time + * + * @ret time Current time + */ +static inline __attribute__ (( always_inline )) unsigned long +rdtime_low ( void ) { + unsigned long time; + + /* Read low XLEN bits of current time */ + __asm__ ( "rdtime %0" : "=r" ( time ) ); + return time; +} + +/** + * Get current time, scaled to avoid rollover within a realistic timescale + * + * @ret time Scaled current time + */ +static inline __attribute__ (( always_inline )) unsigned long +rdtime_scaled ( void ) { + union { + uint64_t time; + struct { + uint32_t low; + uint32_t high; + }; + } u; + unsigned long tmp __attribute__ (( unused )); + + /* Read full current time */ +#if __riscv_xlen >= 64 + __asm__ ( "rdtime %0" : "=r" ( u.time ) ); +#else + __asm__ ( "1:\n\t" + "rdtimeh %1\n\t" + "rdtime %0\n\t" + "rdtimeh %2\n\t" + "bne %1, %2, 1b\n\t" + : "=r" ( u.low ), "=r" ( u.high ), "=r" ( tmp ) ); +#endif + + /* Scale time to avoid XLEN-bit rollover */ + return ( u.time >> zicntr_scale ); +} + +/** + * Get current system time in ticks + * + * @ret ticks Current time, in ticks + */ +static unsigned long zicntr_currticks ( void ) { + unsigned long scaled; + + /* Get scaled time and convert to ticks */ + scaled = rdtime_scaled(); + return ( scaled * zicntr_ticks ); +} + +/** + * Delay for a fixed number of microseconds + * + * @v usecs Number of microseconds for which to delay + */ +static void zicntr_udelay ( unsigned long usecs ) { + unsigned long start; + unsigned long elapsed; + unsigned long threshold; + + /* Delay until sufficient time has elapsed */ + start = rdtime_low(); + threshold = ( usecs * zicntr_mhz ); + do { + elapsed = ( rdtime_low() - start ); + } while ( elapsed < threshold ); +} + +/** + * Probe timer + * + * @ret rc Return status code + */ +static int zicntr_probe ( void ) { + unsigned int offset; + union { + uint64_t freq; + int64_t sfreq; + } u; + int rc; + + /* Check if Zicntr extension is supported */ + if ( ( rc = hart_supported ( "_zicntr" ) ) != 0 ) { + DBGC ( colour, "ZICNTR not supported: %s\n", strerror ( rc ) ); + return rc; + } + + /* Get timer frequency */ + if ( ( ( rc = fdt_path ( "/cpus", &offset ) ) != 0 ) || + ( ( rc = fdt_u64 ( offset, "timebase-frequency", + &u.freq ) ) != 0 ) ) { + DBGC ( colour, "ZICNTR could not determine frequency: %s\n", + strerror ( rc ) ); + return rc; + } + + /* Convert to MHz (without 64-bit division) */ + do { + zicntr_mhz++; + u.sfreq -= 1000000; + } while ( u.sfreq > 0 ); + + /* Calibrate currticks() scaling factor */ + zicntr_scale = 31; + zicntr_ticks = ( ( 1UL << zicntr_scale ) / + ( zicntr_mhz * ( 1000000 / TICKS_PER_SEC ) ) ); + while ( zicntr_ticks > ( TICKS_PER_SEC / ZICNTR_SCALED_HZ ) ) { + zicntr_scale--; + zicntr_ticks >>= 1; + } + DBGC ( colour, "ZICNTR at %ld MHz, %ld ticks per 2^%d increments\n", + zicntr_mhz, zicntr_ticks, zicntr_scale ); + if ( ! zicntr_ticks ) { + DBGC ( colour, "ZICNTR has zero ticks per 2^%d increments\n", + zicntr_scale ); + return -EIO; + } + + return 0; +} + +/** Zicntr timer */ +struct timer zicntr_timer __timer ( TIMER_PREFERRED ) = { + .name = "zicntr", + .probe = zicntr_probe, + .currticks = zicntr_currticks, + .udelay = zicntr_udelay, +}; diff --git a/src/arch/riscv/include/bits/errfile.h b/src/arch/riscv/include/bits/errfile.h index 03f98c206..62288ad9c 100644 --- a/src/arch/riscv/include/bits/errfile.h +++ b/src/arch/riscv/include/bits/errfile.h @@ -16,6 +16,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_sbi_reboot ( ERRFILE_ARCH | ERRFILE_CORE | 0x00000000 ) #define ERRFILE_hart ( ERRFILE_ARCH | ERRFILE_CORE | 0x00010000 ) +#define ERRFILE_zicntr ( ERRFILE_ARCH | ERRFILE_CORE | 0x00020000 ) /** @} */ diff --git a/src/config/config_timer.c b/src/config/config_timer.c index d53c39939..a4fe69b00 100644 --- a/src/config/config_timer.c +++ b/src/config/config_timer.c @@ -49,3 +49,6 @@ REQUIRE_OBJECT ( linux_timer ); #ifdef TIMER_ACPI REQUIRE_OBJECT ( acpi_timer ); #endif +#ifdef TIMER_ZICNTR +REQUIRE_OBJECT ( zicntr ); +#endif