diff --git a/Kernel/Arch/aarch64/Registers.h b/Kernel/Arch/aarch64/Registers.h index e8b3d985ee9..0ffe1022c06 100644 --- a/Kernel/Arch/aarch64/Registers.h +++ b/Kernel/Arch/aarch64/Registers.h @@ -491,8 +491,8 @@ static_assert(sizeof(ID_AA64DFR1_EL1) == 8); // https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/CNTFRQ-EL0--Counter-timer-Frequency-register // CNTFRQ_EL0, Counter-timer Frequency register struct alignas(u64) CNTFRQ_EL0 { - u64 : 32; u64 ClockFrequency : 32; + u64 : 32; static inline CNTFRQ_EL0 read() { @@ -506,6 +506,71 @@ struct alignas(u64) CNTFRQ_EL0 { }; static_assert(sizeof(CNTFRQ_EL0) == 8); +// https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/CNTP-TVAL-EL0--Counter-timer-Physical-Timer-TimerValue-register +// CNTP_TVAL_EL0, Counter-timer Physical Timer TimerValue register +struct alignas(u64) CNTP_TVAL_EL0 { + u64 TimerValue : 32; + u64 : 32; + + static inline CNTP_TVAL_EL0 read() + { + CNTP_TVAL_EL0 timer_value; + + asm volatile("mrs %[value], CNTP_TVAL_EL0" + : [value] "=r"(timer_value)); + + return timer_value; + } + + static inline void write(CNTP_TVAL_EL0 cntp_tval_el0) + { + asm volatile("msr CNTP_TVAL_EL0, %[value]" ::[value] "r"(cntp_tval_el0)); + } +}; +static_assert(sizeof(CNTP_TVAL_EL0) == 8); + +// https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/CNTP-CTL-EL0--Counter-timer-Physical-Timer-Control-register +// CNTP_CTL_EL0, Counter-timer Physical Timer Control register +struct alignas(u64) CNTP_CTL_EL0 { + u64 ENABLE : 1; + u64 IMASK : 1; + u64 ISTATUS : 1; + u64 : 61; + + static inline CNTP_CTL_EL0 read() + { + CNTP_CTL_EL0 control_register; + + asm volatile("mrs %[value], CNTP_CTL_EL0" + : [value] "=r"(control_register)); + + return control_register; + } + + static inline void write(CNTP_CTL_EL0 cntp_ctl_el0) + { + asm volatile("msr CNTP_CTL_EL0, %[value]" ::[value] "r"(cntp_ctl_el0)); + } +}; +static_assert(sizeof(CNTP_CTL_EL0) == 8); + +// https://developer.arm.com/documentation/ddi0595/2020-12/AArch64-Registers/CNTPCT-EL0--Counter-timer-Physical-Count-register +// CNTPCT_EL0, Counter-timer Physical Count register +struct alignas(u64) CNTPCT_EL0 { + u64 PhysicalCount; + + static inline CNTPCT_EL0 read() + { + CNTPCT_EL0 physical_count; + + asm volatile("mrs %[value], CNTPCT_EL0" + : [value] "=r"(physical_count)); + + return physical_count; + } +}; +static_assert(sizeof(CNTPCT_EL0) == 8); + // https://developer.arm.com/documentation/ddi0595/2021-06/AArch64-Registers/TCR-EL1--Translation-Control-Register--EL1- // Translation Control Register struct alignas(u64) TCR_EL1 { diff --git a/Kernel/Arch/aarch64/Time/ARMv8Timer.cpp b/Kernel/Arch/aarch64/Time/ARMv8Timer.cpp new file mode 100644 index 00000000000..1d410335284 --- /dev/null +++ b/Kernel/Arch/aarch64/Time/ARMv8Timer.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2024, Sönke Holz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Kernel { + +ARMv8Timer::ARMv8Timer(u8 interrupt_number) + : HardwareTimer(interrupt_number) +{ + m_frequency = Aarch64::CNTFRQ_EL0::read().ClockFrequency; + + // TODO: Fall back to the devicetree clock-frequency property. + VERIFY(m_frequency != 0); + + m_interrupt_interval = m_frequency / OPTIMAL_TICKS_PER_SECOND_RATE; + + start_timer(m_interrupt_interval); +} + +ErrorOr> ARMv8Timer::initialize(u8 interrupt_number) +{ + auto timer = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) ARMv8Timer(interrupt_number))); + + // Enable the physical timer. + auto ctl = Aarch64::CNTP_CTL_EL0::read(); + ctl.IMASK = 0; + ctl.ENABLE = 1; + Aarch64::CNTP_CTL_EL0::write(ctl); + + timer->enable_irq(); + + return timer; +} + +u64 ARMv8Timer::current_ticks() +{ + return Aarch64::CNTPCT_EL0::read().PhysicalCount; +} + +bool ARMv8Timer::handle_irq() +{ + HardwareTimer::handle_irq(); + + if (Aarch64::CNTP_CTL_EL0::read().ISTATUS == 0) + return false; + + start_timer(m_interrupt_interval); + + return true; +} + +u64 ARMv8Timer::update_time(u64& seconds_since_boot, u32& ticks_this_second, bool query_only) +{ + // Should only be called by the time keeper interrupt handler! + u64 current_value = current_ticks(); + u64 delta_ticks = m_main_counter_drift; + if (current_value >= m_main_counter_last_read) { + delta_ticks += current_value - m_main_counter_last_read; + } else { + // the counter wrapped around + delta_ticks += (NumericLimits::max() - m_main_counter_last_read + 1) + current_value; + } + + u64 ticks_since_last_second = (u64)ticks_this_second + delta_ticks; + auto frequency = ticks_per_second(); + seconds_since_boot += ticks_since_last_second / frequency; + ticks_this_second = ticks_since_last_second % frequency; + + if (!query_only) { + m_main_counter_drift = 0; + m_main_counter_last_read = current_value; + } + + // Return the time passed (in ns) since last time update_time was called + return (delta_ticks * 1'000'000'000ull) / frequency; +} + +void ARMv8Timer::start_timer(u32 delta) +{ + Aarch64::CNTP_TVAL_EL0::write(Aarch64::CNTP_TVAL_EL0 { + .TimerValue = delta, + }); +} + +static constinit Array const compatibles_array = { + "arm,armv7-timer"sv, // The Raspberry Pi 3 and QEMU virt machine use this compatible string even for AArch64. + "arm,armv8-timer"sv, +}; + +DEVICETREE_DRIVER(ARMv8TimerDriver, compatibles_array); + +// https://www.kernel.org/doc/Documentation/devicetree/bindings/timer/arm,arch_timer.yaml +ErrorOr ARMv8TimerDriver::probe(DeviceTree::Device const& device, StringView) const +{ + auto const interrupts = TRY(device.node().interrupts(DeviceTree::get())); + if (interrupts.size() != 4) + return ENOTSUP; // TODO: Support the interrupt-names property. + + // Index 1 is the interrupt for the EL1 physical timer. This driver currently only uses that timer. + auto const& interrupt = interrupts[1]; + + // FIXME: Don't depend on a specific interrupt descriptor format and implement proper devicetree interrupt mapping/translation. + if (!interrupt.domain_root->is_compatible_with("arm,gic-400"sv) && !interrupt.domain_root->is_compatible_with("arm,cortex-a15-gic"sv)) + return ENOTSUP; + if (interrupt.interrupt_identifier.size() != 3 * sizeof(BigEndian)) + return ENOTSUP; + + // The ARM timer uses a PPI (Private Peripheral Interrupt). + // GIC interrupts 16-31 are for PPIs, so add 16 to get the GIC interrupt ID. + + // The interrupt type is in the first cell. It should be 1 for PPIs. + if (reinterpret_cast const*>(interrupt.interrupt_identifier.data())[0] != 1) + return ENOTSUP; + + // The interrupt number is in the second cell. + auto interrupt_number = (reinterpret_cast const*>(interrupt.interrupt_identifier.data())[1]) + 16; + + DeviceTree::DeviceRecipe> recipe { + name(), + device.node_name(), + [interrupt_number] { + return ARMv8Timer::initialize(interrupt_number); + }, + }; + + TimeManagement::add_recipe(move(recipe)); + + return {}; +} + +} diff --git a/Kernel/Arch/aarch64/Time/ARMv8Timer.h b/Kernel/Arch/aarch64/Time/ARMv8Timer.h new file mode 100644 index 00000000000..a8bece20e2f --- /dev/null +++ b/Kernel/Arch/aarch64/Time/ARMv8Timer.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, Sönke Holz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +// https://developer.arm.com/-/media/Arm%20Developer%20Community/PDF/Learn%20the%20Architecture/Generic%20Timer.pdf + +namespace Kernel { + +class ARMv8Timer final : public HardwareTimer { +public: + static ErrorOr> initialize(u8 interrupt_number); + + virtual HardwareTimerType timer_type() const override { return HardwareTimerType::ARMv8Timer; } + virtual StringView model() const override { return "ARMv8 Timer"sv; } + virtual size_t ticks_per_second() const override { return m_frequency; } + + virtual bool is_periodic() const override { TODO_AARCH64(); } + virtual bool is_periodic_capable() const override { TODO_AARCH64(); } + virtual void set_periodic() override { TODO_AARCH64(); } + virtual void set_non_periodic() override { TODO_AARCH64(); } + virtual void disable() override { TODO_AARCH64(); } + + virtual void reset_to_default_ticks_per_second() override { TODO_AARCH64(); } + virtual bool try_to_set_frequency(size_t) override { TODO_AARCH64(); } + virtual bool is_capable_of_frequency(size_t) const override { TODO_AARCH64(); } + virtual size_t calculate_nearest_possible_frequency(size_t) const override { TODO_AARCH64(); } + + // FIXME: Share code with HPET::update_time + u64 update_time(u64& seconds_since_boot, u32& ticks_this_second, bool query_only); + +private: + ARMv8Timer(u8 interrupt_number); + + static u64 current_ticks(); + static void start_timer(u32 delta); + + // ^IRQHandler + bool handle_irq() override; + + Function m_callback; + u64 m_frequency { 0 }; + u32 m_interrupt_interval { 0 }; + + u64 m_main_counter_last_read { 0 }; + u64 m_main_counter_drift { 0 }; +}; + +} diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 7c30f6ef1b1..3df81dcf24c 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -514,6 +514,7 @@ elseif("${SERENITY_ARCH}" STREQUAL "aarch64") Arch/aarch64/PowerState.cpp Arch/aarch64/SafeMem.cpp Arch/aarch64/SmapDisabler.cpp + Arch/aarch64/Time/ARMv8Timer.cpp Arch/aarch64/vector_table.S Arch/aarch64/PlatformInit/RaspberryPi.cpp diff --git a/Kernel/Time/HardwareTimer.h b/Kernel/Time/HardwareTimer.h index c29d563856f..b3ad914aa20 100644 --- a/Kernel/Time/HardwareTimer.h +++ b/Kernel/Time/HardwareTimer.h @@ -14,18 +14,18 @@ namespace Kernel { enum class HardwareTimerType { -#if ARCH(X86_64) - i8253 = 0x1, /* PIT */ - RTC = 0x2, /* Real Time Clock */ - HighPrecisionEventTimer = 0x3, /* also known as IA-PC HPET */ - LocalAPICTimer = 0x4, /* Local APIC */ -#elif ARCH(AARCH64) - RPiTimer = 0x5, -#elif ARCH(RISCV64) - RISCVTimer = 0x6, -#else -# error Unknown architecture -#endif + // x86 + i8253, // PIT + RTC, // Real Time Clock + HighPrecisionEventTimer, // also known as IA-PC HPET + LocalAPICTimer, // Local APIC + + // AArch64 + RPiTimer, + ARMv8Timer, + + // RISC-V + RISCVTimer, }; template diff --git a/Kernel/Time/TimeManagement.cpp b/Kernel/Time/TimeManagement.cpp index df27f5bca06..4786a7d82b0 100644 --- a/Kernel/Time/TimeManagement.cpp +++ b/Kernel/Time/TimeManagement.cpp @@ -19,6 +19,7 @@ # include #elif ARCH(AARCH64) # include +# include #elif ARCH(RISCV64) # include #else @@ -148,7 +149,12 @@ MonotonicTime TimeManagement::monotonic_time(TimePrecision precision) const HPET::the().update_time(seconds, ticks, true); #elif ARCH(AARCH64) // FIXME: Get rid of these horrible casts - const_cast(static_cast(m_system_timer.ptr()))->update_time(seconds, ticks, true); + if (m_system_timer->timer_type() == HardwareTimerType::RPiTimer) + const_cast(static_cast(m_system_timer.ptr()))->update_time(seconds, ticks, true); + else if (m_system_timer->timer_type() == HardwareTimerType::ARMv8Timer) + const_cast(static_cast(m_system_timer.ptr()))->update_time(seconds, ticks, true); + else + VERIFY_NOT_REACHED(); #elif ARCH(RISCV64) TODO_RISCV64(); #else @@ -497,6 +503,8 @@ UNMAP_AFTER_INIT bool TimeManagement::probe_and_set_aarch64_hardware_timers() u64 delta_ns; if (m_system_timer->timer_type() == HardwareTimerType::RPiTimer) delta_ns = static_cast(m_system_timer.ptr())->update_time(seconds_since_boot, ticks_this_second, false); + else if (m_system_timer->timer_type() == HardwareTimerType::ARMv8Timer) + delta_ns = static_cast(m_system_timer.ptr())->update_time(seconds_since_boot, ticks_this_second, false); else VERIFY_NOT_REACHED();