Kernel/aarch64: Add ARMv8Timer support

This timer is used by the Raspberry Pi 3+4 and the QEMU virt machine.

Remove the `#if ARCH`s from HardwareTimerType, as all other enum values
would otherwise have to be updated to keep the values consistent across
all architectures.
This commit is contained in:
Sönke Holz 2024-11-10 00:03:42 +01:00
parent ebe5b0df03
commit 0d6d716e94
6 changed files with 284 additions and 14 deletions

View file

@ -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 {

View file

@ -0,0 +1,139 @@
/*
* Copyright (c) 2024, Sönke Holz <sholz8530@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <Kernel/Arch/aarch64/Time/ARMv8Timer.h>
#include <Kernel/Firmware/DeviceTree/DeviceTree.h>
#include <Kernel/Firmware/DeviceTree/Driver.h>
#include <Kernel/Firmware/DeviceTree/Management.h>
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<NonnullLockRefPtr<ARMv8Timer>> 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<u64>::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<void> 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<u32>))
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<BigEndian<u32> const*>(interrupt.interrupt_identifier.data())[0] != 1)
return ENOTSUP;
// The interrupt number is in the second cell.
auto interrupt_number = (reinterpret_cast<BigEndian<u32> const*>(interrupt.interrupt_identifier.data())[1]) + 16;
DeviceTree::DeviceRecipe<NonnullLockRefPtr<HardwareTimerBase>> recipe {
name(),
device.node_name(),
[interrupt_number] {
return ARMv8Timer::initialize(interrupt_number);
},
};
TimeManagement::add_recipe(move(recipe));
return {};
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2024, Sönke Holz <sholz8530@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <Kernel/Interrupts/GenericInterruptHandler.h>
#include <Kernel/Library/NonnullLockRefPtr.h>
#include <Kernel/Time/HardwareTimer.h>
// https://developer.arm.com/-/media/Arm%20Developer%20Community/PDF/Learn%20the%20Architecture/Generic%20Timer.pdf
namespace Kernel {
class ARMv8Timer final : public HardwareTimer<IRQHandler> {
public:
static ErrorOr<NonnullLockRefPtr<ARMv8Timer>> 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<void()> m_callback;
u64 m_frequency { 0 };
u32 m_interrupt_interval { 0 };
u64 m_main_counter_last_read { 0 };
u64 m_main_counter_drift { 0 };
};
}

View file

@ -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

View file

@ -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<typename InterruptHandlerType>

View file

@ -19,6 +19,7 @@
# include <Kernel/Arch/x86_64/Time/RTC.h>
#elif ARCH(AARCH64)
# include <Kernel/Arch/aarch64/RPi/Timer.h>
# include <Kernel/Arch/aarch64/Time/ARMv8Timer.h>
#elif ARCH(RISCV64)
# include <Kernel/Arch/riscv64/Timer.h>
#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<RPi::Timer*>(static_cast<RPi::Timer const*>(m_system_timer.ptr()))->update_time(seconds, ticks, true);
if (m_system_timer->timer_type() == HardwareTimerType::RPiTimer)
const_cast<RPi::Timer*>(static_cast<RPi::Timer const*>(m_system_timer.ptr()))->update_time(seconds, ticks, true);
else if (m_system_timer->timer_type() == HardwareTimerType::ARMv8Timer)
const_cast<ARMv8Timer*>(static_cast<ARMv8Timer const*>(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<RPi::Timer*>(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<ARMv8Timer*>(m_system_timer.ptr())->update_time(seconds_since_boot, ticks_this_second, false);
else
VERIFY_NOT_REACHED();