From fdae25369576781d58a426df30d45ca1eeda3ed6 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Fri, 4 Aug 2023 15:15:45 +0300 Subject: [PATCH] Kernel: Add basic HPET support to replace PIT if exists This works same way as the PIT implementation; calls Scheduler every milli second. --- kernel/CMakeLists.txt | 1 + kernel/include/kernel/Timer/HPET.h | 27 +++++++ kernel/kernel/Timer/HPET.cpp | 113 +++++++++++++++++++++++++++++ kernel/kernel/Timer/Timer.cpp | 10 +++ 4 files changed, 151 insertions(+) create mode 100644 kernel/include/kernel/Timer/HPET.h create mode 100644 kernel/kernel/Timer/HPET.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index e3990b571a..ca54b62e43 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -56,6 +56,7 @@ set(KERNEL_SOURCES kernel/Terminal/TTY.cpp kernel/Terminal/VesaTerminalDriver.cpp kernel/Thread.cpp + kernel/Timer/HPET.cpp kernel/Timer/PIT.cpp kernel/Timer/RTC.cpp kernel/Timer/Timer.cpp diff --git a/kernel/include/kernel/Timer/HPET.h b/kernel/include/kernel/Timer/HPET.h new file mode 100644 index 0000000000..3b6bdc1a9e --- /dev/null +++ b/kernel/include/kernel/Timer/HPET.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace Kernel +{ + + class HPET final : public Timer + { + public: + static BAN::ErrorOr> create(); + + virtual uint64_t ms_since_boot() const override; + + private: + HPET() = default; + BAN::ErrorOr initialize(); + + void write_register(ptrdiff_t reg, uint64_t value) const; + uint64_t read_register(ptrdiff_t reg) const; + + private: + uint64_t m_counter_tick_period_fs { 0 }; + vaddr_t m_mmio_base { 0 }; + }; + +} \ No newline at end of file diff --git a/kernel/kernel/Timer/HPET.cpp b/kernel/kernel/Timer/HPET.cpp new file mode 100644 index 0000000000..c6d58426c2 --- /dev/null +++ b/kernel/kernel/Timer/HPET.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define HPET_REG_CAPABILIES 0x00 +#define HPET_REG_CONFIG 0x10 +#define HPET_REG_COUNTER 0xF0 + +#define HPET_CONFIG_ENABLE 0x01 +#define HPET_CONFIG_LEG_RT 0x02 + +#define HPET_REG_TIMER_CONFIG(N) (0x100 + 0x20 * N) +#define HPET_REG_TIMER_COMPARATOR(N) (0x108 + 0x20 * N) + +#define HPET_Tn_INT_ENB_CNF (1 << 2) +#define HPET_Tn_TYPE_CNF (1 << 3) +#define HPET_Tn_PER_INT_CAP (1 << 4) +#define HPET_Tn_VAL_SET_CNF (1 << 6) + +#define FS_PER_MS 1'000'000'000'000 + +namespace Kernel +{ + + static uint64_t s_system_time = 0; + + BAN::ErrorOr> HPET::create() + { + HPET* hpet = new HPET(); + if (hpet == nullptr) + return BAN::Error::from_errno(ENOMEM); + if (auto ret = hpet->initialize(); ret.is_error()) + { + delete hpet; + return ret.release_error(); + } + return BAN::UniqPtr::adopt(hpet); + } + + BAN::ErrorOr HPET::initialize() + { + auto* header = (ACPI::HPET*)ACPI::get().get_header("HPET"); + if (header == nullptr) + return BAN::Error::from_errno(ENODEV); + + if (header->hardware_rev_id == 0) + return BAN::Error::from_errno(EINVAL); + + if (!header->legacy_replacement_irq_routing_cable) + { + dwarnln("HPET doesn't support legacy mapping"); + return BAN::Error::from_errno(ENOTSUP); + } + + m_mmio_base = PageTable::kernel().reserve_free_page(KERNEL_OFFSET); + ASSERT(m_mmio_base); + + PageTable::kernel().map_page_at(header->base_address.address, m_mmio_base, PageTable::Flags::ReadWrite | PageTable::Flags::Present); + BAN::ScopeGuard unmapper([this] { PageTable::kernel().unmap_page(m_mmio_base); }); + + m_counter_tick_period_fs = read_register(HPET_REG_CAPABILIES) >> 32; + + uint64_t ticks_per_ms = FS_PER_MS / m_counter_tick_period_fs; + + uint64_t timer0_config = read_register(HPET_REG_TIMER_CONFIG(0)); + if (!(timer0_config & HPET_Tn_PER_INT_CAP)) + { + dwarnln("timer 0 doesn't support periodic"); + return BAN::Error::from_errno(ENOTSUP); + } + + unmapper.disable(); + + // Enable main counter + write_register(HPET_REG_CONFIG, read_register(HPET_REG_CONFIG) | HPET_CONFIG_LEG_RT | HPET_CONFIG_ENABLE); + + // Enable timer 0 as 1 ms periodic + write_register(HPET_REG_TIMER_CONFIG(0), HPET_Tn_INT_ENB_CNF | HPET_Tn_TYPE_CNF | HPET_Tn_VAL_SET_CNF); + write_register(HPET_REG_TIMER_COMPARATOR(0), read_register(HPET_REG_COUNTER) + ticks_per_ms); + write_register(HPET_REG_TIMER_COMPARATOR(0), ticks_per_ms); + + // Disable timers 1-> + for (int i = 1; i <= header->comparator_count; i++) + write_register(HPET_REG_TIMER_CONFIG(i), 0); + + IDT::register_irq_handler(0, [] { s_system_time++; Scheduler::get().timer_reschedule(); }); + InterruptController::get().enable_irq(0); + + return {}; + } + + uint64_t HPET::ms_since_boot() const + { + // FIXME: 32 bit CPUs should use 32 bit counter with 32 bit reads + return read_register(HPET_REG_COUNTER) * m_counter_tick_period_fs / FS_PER_MS; + } + + void HPET::write_register(ptrdiff_t reg, uint64_t value) const + { + MMIO::write64(m_mmio_base + reg, value); + } + + uint64_t HPET::read_register(ptrdiff_t reg) const + { + return MMIO::read64(m_mmio_base + reg); + } + +} \ No newline at end of file diff --git a/kernel/kernel/Timer/Timer.cpp b/kernel/kernel/Timer/Timer.cpp index a9340051f0..f96fd0cd9c 100644 --- a/kernel/kernel/Timer/Timer.cpp +++ b/kernel/kernel/Timer/Timer.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -32,6 +33,15 @@ namespace Kernel m_rtc = MUST(BAN::UniqPtr::create()); m_boot_time = BAN::to_unix_time(m_rtc->get_current_time()); + if (auto res = HPET::create(); res.is_error()) + dwarnln("HPET: {}", res.error()); + else + { + m_timer = res.release_value(); + dprintln("HPET initialized"); + return; + } + if (auto res = PIT::create(); res.is_error()) dwarnln("PIT: {}", res.error()); else