mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-22 09:21:57 -05:00
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:
parent
ebe5b0df03
commit
0d6d716e94
6 changed files with 284 additions and 14 deletions
|
@ -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 {
|
||||
|
|
139
Kernel/Arch/aarch64/Time/ARMv8Timer.cpp
Normal file
139
Kernel/Arch/aarch64/Time/ARMv8Timer.cpp
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
57
Kernel/Arch/aarch64/Time/ARMv8Timer.h
Normal file
57
Kernel/Arch/aarch64/Time/ARMv8Timer.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue