mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 17:07:47 +00:00
Kernel: Reorganize Arch/x86 directory to Arch/x86_64 after i686 removal
No functional change.
This commit is contained in:
parent
5ff318cf3a
commit
91db482ad3
129 changed files with 482 additions and 1116 deletions
179
Kernel/Arch/x86_64/Time/APICTimer.cpp
Normal file
179
Kernel/Arch/x86_64/Time/APICTimer.cpp
Normal file
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <Kernel/Arch/x86_64/Interrupts/APIC.h>
|
||||
#include <Kernel/Arch/x86_64/Time/APICTimer.h>
|
||||
#include <Kernel/Panic.h>
|
||||
#include <Kernel/Sections.h>
|
||||
#include <Kernel/Time/TimeManagement.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
#define APIC_TIMER_MEASURE_CPU_CLOCK
|
||||
|
||||
UNMAP_AFTER_INIT APICTimer* APICTimer::initialize(u8 interrupt_number, HardwareTimerBase& calibration_source)
|
||||
{
|
||||
auto timer = adopt_lock_ref(*new APICTimer(interrupt_number, nullptr));
|
||||
timer->register_interrupt_handler();
|
||||
if (!timer->calibrate(calibration_source)) {
|
||||
return nullptr;
|
||||
}
|
||||
return &timer.leak_ref();
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT APICTimer::APICTimer(u8 interrupt_number, Function<void(RegisterState const&)> callback)
|
||||
: HardwareTimer<GenericInterruptHandler>(interrupt_number, move(callback))
|
||||
{
|
||||
disable_remap();
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT bool APICTimer::calibrate(HardwareTimerBase& calibration_source)
|
||||
{
|
||||
VERIFY_INTERRUPTS_DISABLED();
|
||||
|
||||
dmesgln("APICTimer: Using {} as calibration source", calibration_source.model());
|
||||
|
||||
struct {
|
||||
#ifdef APIC_TIMER_MEASURE_CPU_CLOCK
|
||||
bool supports_tsc { Processor::current().has_feature(CPUFeature::TSC) };
|
||||
#endif
|
||||
APIC& apic { APIC::the() };
|
||||
size_t ticks_in_100ms { 0 };
|
||||
Atomic<size_t, AK::memory_order_relaxed> calibration_ticks { 0 };
|
||||
#ifdef APIC_TIMER_MEASURE_CPU_CLOCK
|
||||
volatile u64 start_tsc { 0 }, end_tsc { 0 };
|
||||
#endif
|
||||
volatile u64 start_reference { 0 }, end_reference { 0 };
|
||||
volatile u32 start_apic_count { 0 }, end_apic_count { 0 };
|
||||
bool query_reference { false };
|
||||
} state;
|
||||
|
||||
state.ticks_in_100ms = calibration_source.ticks_per_second() / 10;
|
||||
state.query_reference = calibration_source.can_query_raw();
|
||||
|
||||
// temporarily replace the timer callbacks
|
||||
auto original_source_callback = calibration_source.set_callback([&state, &calibration_source](RegisterState const&) {
|
||||
u32 current_timer_count = state.apic.get_timer_current_count();
|
||||
#ifdef APIC_TIMER_MEASURE_CPU_CLOCK
|
||||
u64 current_tsc = state.supports_tsc ? read_tsc() : 0;
|
||||
#endif
|
||||
u64 current_reference = state.query_reference ? calibration_source.current_raw() : 0;
|
||||
|
||||
auto prev_tick = state.calibration_ticks.fetch_add(1);
|
||||
if (prev_tick == 0) {
|
||||
#ifdef APIC_TIMER_MEASURE_CPU_CLOCK
|
||||
state.start_tsc = current_tsc;
|
||||
#endif
|
||||
state.start_apic_count = current_timer_count;
|
||||
state.start_reference = current_reference;
|
||||
} else if (prev_tick + 1 == state.ticks_in_100ms + 1) {
|
||||
#ifdef APIC_TIMER_MEASURE_CPU_CLOCK
|
||||
state.end_tsc = current_tsc;
|
||||
#endif
|
||||
state.end_apic_count = current_timer_count;
|
||||
state.end_reference = current_reference;
|
||||
}
|
||||
});
|
||||
|
||||
// Setup a counter that should be much longer than our calibration time.
|
||||
// We don't want the APIC timer to actually fire. We do however want the
|
||||
// calbibration_source timer to fire so that we can read the current
|
||||
// tick count from the APIC timer
|
||||
auto original_callback = set_callback([&](RegisterState const&) {
|
||||
// TODO: How should we handle this?
|
||||
PANIC("APICTimer: Timer fired during calibration!");
|
||||
});
|
||||
state.apic.setup_local_timer(0xffffffff, APIC::TimerMode::Periodic, true);
|
||||
|
||||
sti();
|
||||
// Loop for about 100 ms
|
||||
while (state.calibration_ticks.load() <= state.ticks_in_100ms)
|
||||
;
|
||||
cli();
|
||||
|
||||
// Restore timer callbacks
|
||||
calibration_source.set_callback(move(original_source_callback));
|
||||
set_callback(move(original_callback));
|
||||
|
||||
disable_local_timer();
|
||||
|
||||
if (state.query_reference) {
|
||||
u64 one_tick_ns = calibration_source.raw_to_ns((state.end_reference - state.start_reference) / state.ticks_in_100ms);
|
||||
m_frequency = (u32)(1000000000ull / one_tick_ns);
|
||||
dmesgln("APICTimer: Ticks per second: {} ({}.{}ms)", m_frequency, one_tick_ns / 1000000, one_tick_ns % 1000000);
|
||||
} else {
|
||||
// For now, assume the frequency is exactly the same
|
||||
m_frequency = calibration_source.ticks_per_second();
|
||||
dmesgln("APICTimer: Ticks per second: {} (assume same frequency as reference clock)", m_frequency);
|
||||
}
|
||||
|
||||
auto delta_apic_count = state.start_apic_count - state.end_apic_count; // The APIC current count register decrements!
|
||||
m_timer_period = (delta_apic_count * state.apic.get_timer_divisor()) / state.ticks_in_100ms;
|
||||
|
||||
u64 apic_freq = delta_apic_count * state.apic.get_timer_divisor() * 10;
|
||||
dmesgln("APICTimer: Bus clock speed: {}.{} MHz", apic_freq / 1000000, apic_freq % 1000000);
|
||||
if (apic_freq < 1000000) {
|
||||
dmesgln("APICTimer: Frequency too slow!");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef APIC_TIMER_MEASURE_CPU_CLOCK
|
||||
if (state.supports_tsc) {
|
||||
auto delta_tsc = (state.end_tsc - state.start_tsc) * 10;
|
||||
dmesgln("APICTimer: CPU clock speed: {}.{} MHz", delta_tsc / 1000000, delta_tsc % 1000000);
|
||||
}
|
||||
#endif
|
||||
|
||||
enable_local_timer();
|
||||
return true;
|
||||
}
|
||||
|
||||
void APICTimer::enable_local_timer()
|
||||
{
|
||||
APIC::the().setup_local_timer(m_timer_period, m_timer_mode, true);
|
||||
}
|
||||
|
||||
void APICTimer::disable_local_timer()
|
||||
{
|
||||
APIC::the().setup_local_timer(0, APIC::TimerMode::OneShot, false);
|
||||
}
|
||||
|
||||
size_t APICTimer::ticks_per_second() const
|
||||
{
|
||||
return m_frequency;
|
||||
}
|
||||
|
||||
void APICTimer::set_periodic()
|
||||
{
|
||||
// FIXME: Implement it...
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
void APICTimer::set_non_periodic()
|
||||
{
|
||||
// FIXME: Implement it...
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
void APICTimer::reset_to_default_ticks_per_second()
|
||||
{
|
||||
}
|
||||
|
||||
bool APICTimer::try_to_set_frequency([[maybe_unused]] size_t frequency)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool APICTimer::is_capable_of_frequency([[maybe_unused]] size_t frequency) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t APICTimer::calculate_nearest_possible_frequency([[maybe_unused]] size_t frequency) const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
47
Kernel/Arch/x86_64/Time/APICTimer.h
Normal file
47
Kernel/Arch/x86_64/Time/APICTimer.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <Kernel/Arch/x86_64/Interrupts/APIC.h>
|
||||
#include <Kernel/Interrupts/GenericInterruptHandler.h>
|
||||
#include <Kernel/Time/HardwareTimer.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class APICTimer final : public HardwareTimer<GenericInterruptHandler> {
|
||||
public:
|
||||
static APICTimer* initialize(u8, HardwareTimerBase&);
|
||||
virtual HardwareTimerType timer_type() const override { return HardwareTimerType::LocalAPICTimer; }
|
||||
virtual StringView model() const override { return "LocalAPIC"sv; }
|
||||
virtual size_t ticks_per_second() const override;
|
||||
|
||||
virtual bool is_periodic() const override { return m_timer_mode == APIC::TimerMode::Periodic; }
|
||||
virtual bool is_periodic_capable() const override { return true; }
|
||||
virtual void set_periodic() override;
|
||||
virtual void set_non_periodic() override;
|
||||
virtual void disable() override { }
|
||||
|
||||
virtual void reset_to_default_ticks_per_second() override;
|
||||
virtual bool try_to_set_frequency(size_t frequency) override;
|
||||
virtual bool is_capable_of_frequency(size_t frequency) const override;
|
||||
virtual size_t calculate_nearest_possible_frequency(size_t frequency) const override;
|
||||
|
||||
void will_be_destroyed() override { HardwareTimer<GenericInterruptHandler>::will_be_destroyed(); }
|
||||
void enable_local_timer();
|
||||
void disable_local_timer();
|
||||
|
||||
private:
|
||||
explicit APICTimer(u8, Function<void(RegisterState const&)>);
|
||||
|
||||
bool calibrate(HardwareTimerBase&);
|
||||
|
||||
u32 m_timer_period { 0 };
|
||||
APIC::TimerMode m_timer_mode { APIC::TimerMode::Periodic };
|
||||
};
|
||||
|
||||
}
|
462
Kernel/Arch/x86_64/Time/HPET.cpp
Normal file
462
Kernel/Arch/x86_64/Time/HPET.cpp
Normal file
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/StringView.h>
|
||||
#include <Kernel/Arch/x86_64/Time/HPET.h>
|
||||
#include <Kernel/Arch/x86_64/Time/HPETComparator.h>
|
||||
#include <Kernel/Debug.h>
|
||||
#include <Kernel/Firmware/ACPI/Parser.h>
|
||||
#include <Kernel/Memory/MemoryManager.h>
|
||||
#include <Kernel/Memory/TypedMapping.h>
|
||||
#include <Kernel/Sections.h>
|
||||
#include <Kernel/Time/TimeManagement.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
#define ABSOLUTE_MAXIMUM_COUNTER_TICK_PERIOD 0x05F5E100
|
||||
#define NANOSECOND_PERIOD_TO_HERTZ(x) 1000000000 / x
|
||||
#define HERTZ_TO_MEGAHERTZ(x) (x / 1000000)
|
||||
|
||||
namespace HPETFlags {
|
||||
enum class Attributes {
|
||||
Counter64BitCapable = 1 << 13,
|
||||
LegacyReplacementRouteCapable = 1 << 15
|
||||
};
|
||||
|
||||
enum class Configuration {
|
||||
Enable = 1 << 0,
|
||||
LegacyReplacementRoute = 1 << 1
|
||||
};
|
||||
|
||||
enum class TimerConfiguration : u32 {
|
||||
LevelTriggered = 1 << 1,
|
||||
InterruptEnable = 1 << 2,
|
||||
GeneratePeriodicInterrupt = 1 << 3,
|
||||
PeriodicInterruptCapable = 1 << 4,
|
||||
Timer64BitsCapable = 1 << 5,
|
||||
ValueSet = 1 << 6,
|
||||
Force32BitMode = 1 << 8,
|
||||
FSBInterruptEnable = 1 << 14,
|
||||
FSBInterruptDelivery = 1 << 15
|
||||
};
|
||||
};
|
||||
|
||||
struct [[gnu::packed]] HPETRegister {
|
||||
union {
|
||||
volatile u64 full;
|
||||
struct {
|
||||
volatile u32 low;
|
||||
volatile u32 high;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
struct [[gnu::packed]] TimerStructure {
|
||||
volatile u32 capabilities;
|
||||
volatile u32 interrupt_routing;
|
||||
HPETRegister comparator_value;
|
||||
volatile u64 fsb_interrupt_route;
|
||||
u64 reserved;
|
||||
};
|
||||
|
||||
struct [[gnu::packed]] HPETCapabilityRegister {
|
||||
// Note: We must do a 32 bit access to offsets 0x0, or 0x4 only, according to HPET spec.
|
||||
volatile u32 attributes;
|
||||
volatile u32 main_counter_tick_period;
|
||||
u64 reserved;
|
||||
};
|
||||
|
||||
struct [[gnu::packed]] HPETRegistersBlock {
|
||||
HPETCapabilityRegister capabilities;
|
||||
HPETRegister configuration;
|
||||
u64 reserved1;
|
||||
HPETRegister interrupt_status;
|
||||
u8 reserved2[0xF0 - 0x28];
|
||||
HPETRegister main_counter_value;
|
||||
u64 reserved3;
|
||||
TimerStructure timers[32];
|
||||
};
|
||||
|
||||
static_assert(__builtin_offsetof(HPETRegistersBlock, main_counter_value) == 0xf0);
|
||||
static_assert(__builtin_offsetof(HPETRegistersBlock, timers[0]) == 0x100);
|
||||
static_assert(__builtin_offsetof(HPETRegistersBlock, timers[1]) == 0x120);
|
||||
|
||||
// Note: The HPET specification says it reserves the range of byte 0x160 to
|
||||
// 0x400 for comparators 3-31, but for implementing all 32 comparators the HPET
|
||||
// MMIO space has to be 1280 bytes and not 1024 bytes.
|
||||
static_assert(AssertSize<HPETRegistersBlock, 0x500>());
|
||||
|
||||
static u64 read_register_safe64(HPETRegister const& reg)
|
||||
{
|
||||
return reg.full;
|
||||
}
|
||||
|
||||
static HPET* s_hpet;
|
||||
static bool hpet_initialized { false };
|
||||
|
||||
bool HPET::initialized()
|
||||
{
|
||||
return hpet_initialized;
|
||||
}
|
||||
|
||||
HPET& HPET::the()
|
||||
{
|
||||
VERIFY(HPET::initialized());
|
||||
VERIFY(s_hpet != nullptr);
|
||||
return *s_hpet;
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT bool HPET::test_and_initialize()
|
||||
{
|
||||
VERIFY(!HPET::initialized());
|
||||
hpet_initialized = true;
|
||||
auto hpet_table = ACPI::Parser::the()->find_table("HPET"sv);
|
||||
if (!hpet_table.has_value())
|
||||
return false;
|
||||
dmesgln("HPET @ {}", hpet_table.value());
|
||||
|
||||
auto sdt_or_error = Memory::map_typed<ACPI::Structures::HPET>(hpet_table.value());
|
||||
if (sdt_or_error.is_error()) {
|
||||
dbgln("Failed mapping HPET table");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: HPET is only usable from System Memory
|
||||
VERIFY(sdt_or_error.value()->event_timer_block.address_space == (u8)ACPI::GenericAddressStructure::AddressSpace::SystemMemory);
|
||||
|
||||
if (TimeManagement::is_hpet_periodic_mode_allowed()) {
|
||||
if (!check_for_exisiting_periodic_timers()) {
|
||||
dbgln("HPET: No periodic capable timers");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
new HPET(PhysicalAddress(hpet_table.value()));
|
||||
return true;
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT bool HPET::check_for_exisiting_periodic_timers()
|
||||
{
|
||||
auto hpet_table = ACPI::Parser::the()->find_table("HPET"sv);
|
||||
if (!hpet_table.has_value())
|
||||
return false;
|
||||
|
||||
auto sdt_or_error = Memory::map_typed<ACPI::Structures::HPET>(hpet_table.value());
|
||||
if (sdt_or_error.is_error())
|
||||
return false;
|
||||
auto sdt = sdt_or_error.release_value();
|
||||
VERIFY(sdt->event_timer_block.address_space == 0);
|
||||
auto registers_or_error = Memory::map_typed<HPETRegistersBlock>(PhysicalAddress(sdt->event_timer_block.address));
|
||||
if (registers_or_error.is_error())
|
||||
return false;
|
||||
auto registers = registers_or_error.release_value();
|
||||
|
||||
size_t timers_count = ((registers->capabilities.attributes >> 8) & 0x1f) + 1;
|
||||
for (size_t index = 0; index < timers_count; index++) {
|
||||
if (registers->timers[index].capabilities & (u32)HPETFlags::TimerConfiguration::PeriodicInterruptCapable)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HPET::global_disable()
|
||||
{
|
||||
auto& regs = registers();
|
||||
regs.configuration.low = regs.configuration.low & ~(u32)HPETFlags::Configuration::Enable;
|
||||
}
|
||||
void HPET::global_enable()
|
||||
{
|
||||
auto& regs = registers();
|
||||
regs.configuration.low = regs.configuration.low | (u32)HPETFlags::Configuration::Enable;
|
||||
}
|
||||
|
||||
void HPET::update_periodic_comparator_value()
|
||||
{
|
||||
// According to 2.3.9.2.2 the only safe way to change the periodic timer frequency
|
||||
// is to disable all periodic timers, reset the main counter and each timer's comparator value.
|
||||
// This introduces time drift, so it should be avoided unless absolutely necessary.
|
||||
global_disable();
|
||||
auto& regs = registers();
|
||||
|
||||
u64 previous_main_value = (u64)regs.main_counter_value.low | ((u64)regs.main_counter_value.high << 32);
|
||||
m_main_counter_drift += previous_main_value - m_main_counter_last_read;
|
||||
m_main_counter_last_read = 0;
|
||||
regs.main_counter_value.low = 0;
|
||||
if (m_main_counter_64bits)
|
||||
regs.main_counter_value.high = 0;
|
||||
for (auto& comparator : m_comparators) {
|
||||
auto& timer = regs.timers[comparator.comparator_number()];
|
||||
if (!comparator.is_enabled())
|
||||
continue;
|
||||
if (comparator.is_periodic()) {
|
||||
// Note that this means we're restarting all periodic timers. There is no
|
||||
// way to resume periodic timers properly because we reset the main counter
|
||||
// and we can only write the period into the comparator value...
|
||||
timer.capabilities = timer.capabilities | (u32)HPETFlags::TimerConfiguration::ValueSet;
|
||||
u64 value = ns_to_raw_counter_ticks(1000000000ull / comparator.ticks_per_second());
|
||||
dbgln_if(HPET_DEBUG, "HPET: Update periodic comparator {} comparator value to {} main value was: {}",
|
||||
comparator.comparator_number(),
|
||||
value,
|
||||
previous_main_value);
|
||||
timer.comparator_value.low = (u32)value;
|
||||
if (comparator.is_64bit_capable()) {
|
||||
timer.capabilities = timer.capabilities | (u32)HPETFlags::TimerConfiguration::ValueSet;
|
||||
timer.comparator_value.high = (u32)(value >> 32);
|
||||
}
|
||||
} else {
|
||||
// Set the new target comparator value to the delta to the remaining ticks
|
||||
u64 current_value = (u64)timer.comparator_value.low | ((u64)timer.comparator_value.high << 32);
|
||||
u64 value = current_value - previous_main_value;
|
||||
dbgln_if(HPET_DEBUG, "HPET: Update non-periodic comparator {} comparator value from {} to {} main value was: {}",
|
||||
comparator.comparator_number(),
|
||||
current_value,
|
||||
value,
|
||||
previous_main_value);
|
||||
timer.comparator_value.low = (u32)value;
|
||||
if (comparator.is_64bit_capable())
|
||||
timer.comparator_value.high = (u32)(value >> 32);
|
||||
}
|
||||
}
|
||||
|
||||
global_enable();
|
||||
}
|
||||
|
||||
void HPET::update_non_periodic_comparator_value(HPETComparator const& comparator)
|
||||
{
|
||||
VERIFY_INTERRUPTS_DISABLED();
|
||||
VERIFY(!comparator.is_periodic());
|
||||
VERIFY(comparator.comparator_number() <= m_comparators.size());
|
||||
auto& regs = registers();
|
||||
auto& timer = regs.timers[comparator.comparator_number()];
|
||||
u64 value = frequency() / comparator.ticks_per_second();
|
||||
// NOTE: If the main counter passes this new value before we finish writing it, we will never receive an interrupt!
|
||||
u64 new_counter_value = read_main_counter() + value;
|
||||
timer.comparator_value.high = (u32)(new_counter_value >> 32);
|
||||
timer.comparator_value.low = (u32)new_counter_value;
|
||||
}
|
||||
|
||||
u64 HPET::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 = read_main_counter();
|
||||
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
|
||||
if (m_main_counter_64bits) {
|
||||
delta_ticks += (NumericLimits<u64>::max() - m_main_counter_last_read + 1) + current_value;
|
||||
} else {
|
||||
delta_ticks += (NumericLimits<u32>::max() - m_main_counter_last_read + 1) + current_value;
|
||||
m_32bit_main_counter_wraps++;
|
||||
}
|
||||
}
|
||||
|
||||
u64 ticks_since_last_second = (u64)ticks_this_second + delta_ticks;
|
||||
auto ticks_per_second = frequency();
|
||||
seconds_since_boot += ticks_since_last_second / ticks_per_second;
|
||||
ticks_this_second = ticks_since_last_second % ticks_per_second;
|
||||
|
||||
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 * 1000000000ull) / ticks_per_second;
|
||||
}
|
||||
|
||||
u64 HPET::read_main_counter_unsafe() const
|
||||
{
|
||||
auto& main_counter = registers().main_counter_value;
|
||||
if (m_main_counter_64bits)
|
||||
return ((u64)main_counter.high << 32) | (u64)main_counter.low;
|
||||
|
||||
return ((u64)m_32bit_main_counter_wraps << 32) | (u64)main_counter.low;
|
||||
}
|
||||
|
||||
u64 HPET::read_main_counter() const
|
||||
{
|
||||
if (m_main_counter_64bits)
|
||||
return read_register_safe64(registers().main_counter_value);
|
||||
|
||||
auto& main_counter = registers().main_counter_value;
|
||||
u32 wraps = m_32bit_main_counter_wraps;
|
||||
u32 last_read_value = m_main_counter_last_read & 0xffffffff;
|
||||
u32 current_value = main_counter.low;
|
||||
if (current_value < last_read_value)
|
||||
wraps++;
|
||||
return ((u64)wraps << 32) | (u64)current_value;
|
||||
}
|
||||
|
||||
void HPET::enable_periodic_interrupt(HPETComparator const& comparator)
|
||||
{
|
||||
dbgln_if(HPET_DEBUG, "HPET: Set comparator {} to be periodic.", comparator.comparator_number());
|
||||
disable(comparator);
|
||||
VERIFY(comparator.comparator_number() <= m_comparators.size());
|
||||
auto& timer = registers().timers[comparator.comparator_number()];
|
||||
auto capabilities = timer.capabilities;
|
||||
VERIFY(capabilities & (u32)HPETFlags::TimerConfiguration::PeriodicInterruptCapable);
|
||||
timer.capabilities = capabilities | (u32)HPETFlags::TimerConfiguration::GeneratePeriodicInterrupt;
|
||||
if (comparator.is_enabled())
|
||||
enable(comparator);
|
||||
}
|
||||
void HPET::disable_periodic_interrupt(HPETComparator const& comparator)
|
||||
{
|
||||
dbgln_if(HPET_DEBUG, "HPET: Disable periodic interrupt in comparator {}", comparator.comparator_number());
|
||||
disable(comparator);
|
||||
VERIFY(comparator.comparator_number() <= m_comparators.size());
|
||||
auto& timer = registers().timers[comparator.comparator_number()];
|
||||
auto capabilities = timer.capabilities;
|
||||
VERIFY(capabilities & (u32)HPETFlags::TimerConfiguration::PeriodicInterruptCapable);
|
||||
timer.capabilities = capabilities & ~(u32)HPETFlags::TimerConfiguration::GeneratePeriodicInterrupt;
|
||||
if (comparator.is_enabled())
|
||||
enable(comparator);
|
||||
}
|
||||
|
||||
void HPET::disable(HPETComparator const& comparator)
|
||||
{
|
||||
dbgln_if(HPET_DEBUG, "HPET: Disable comparator {}", comparator.comparator_number());
|
||||
VERIFY(comparator.comparator_number() <= m_comparators.size());
|
||||
auto& timer = registers().timers[comparator.comparator_number()];
|
||||
timer.capabilities = timer.capabilities & ~(u32)HPETFlags::TimerConfiguration::InterruptEnable;
|
||||
}
|
||||
void HPET::enable(HPETComparator const& comparator)
|
||||
{
|
||||
dbgln_if(HPET_DEBUG, "HPET: Enable comparator {}", comparator.comparator_number());
|
||||
VERIFY(comparator.comparator_number() <= m_comparators.size());
|
||||
auto& timer = registers().timers[comparator.comparator_number()];
|
||||
timer.capabilities = timer.capabilities | (u32)HPETFlags::TimerConfiguration::InterruptEnable;
|
||||
}
|
||||
|
||||
Vector<unsigned> HPET::capable_interrupt_numbers(HPETComparator const& comparator)
|
||||
{
|
||||
VERIFY(comparator.comparator_number() <= m_comparators.size());
|
||||
Vector<unsigned> capable_interrupts;
|
||||
auto& comparator_registers = registers().timers[comparator.comparator_number()];
|
||||
u32 interrupt_bitfield = comparator_registers.interrupt_routing;
|
||||
for (size_t index = 0; index < 32; index++) {
|
||||
if (interrupt_bitfield & 1)
|
||||
capable_interrupts.append(index);
|
||||
interrupt_bitfield >>= 1;
|
||||
}
|
||||
return capable_interrupts;
|
||||
}
|
||||
|
||||
Vector<unsigned> HPET::capable_interrupt_numbers(u8 comparator_number)
|
||||
{
|
||||
VERIFY(comparator_number <= m_comparators.size());
|
||||
Vector<unsigned> capable_interrupts;
|
||||
auto& comparator_registers = registers().timers[comparator_number];
|
||||
u32 interrupt_bitfield = comparator_registers.interrupt_routing;
|
||||
for (size_t index = 0; index < 32; index++) {
|
||||
if (interrupt_bitfield & 1)
|
||||
capable_interrupts.append(index);
|
||||
interrupt_bitfield >>= 1;
|
||||
}
|
||||
return capable_interrupts;
|
||||
}
|
||||
|
||||
void HPET::set_comparator_irq_vector(u8 comparator_number, u8 irq_vector)
|
||||
{
|
||||
VERIFY(comparator_number <= m_comparators.size());
|
||||
auto& comparator_registers = registers().timers[comparator_number];
|
||||
comparator_registers.capabilities = comparator_registers.capabilities | (irq_vector << 9);
|
||||
}
|
||||
|
||||
bool HPET::is_periodic_capable(u8 comparator_number) const
|
||||
{
|
||||
VERIFY(comparator_number <= m_comparators.size());
|
||||
auto& comparator_registers = registers().timers[comparator_number];
|
||||
return comparator_registers.capabilities & (u32)HPETFlags::TimerConfiguration::PeriodicInterruptCapable;
|
||||
}
|
||||
|
||||
bool HPET::is_64bit_capable(u8 comparator_number) const
|
||||
{
|
||||
VERIFY(comparator_number <= m_comparators.size());
|
||||
auto& comparator_registers = registers().timers[comparator_number];
|
||||
return comparator_registers.capabilities & (u32)HPETFlags::TimerConfiguration::Timer64BitsCapable;
|
||||
}
|
||||
|
||||
void HPET::set_comparators_to_optimal_interrupt_state(size_t)
|
||||
{
|
||||
// FIXME: Implement this method for allowing to use HPET timers 2-31...
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
PhysicalAddress HPET::find_acpi_hpet_registers_block()
|
||||
{
|
||||
auto sdt = Memory::map_typed<const volatile ACPI::Structures::HPET>(m_physical_acpi_hpet_table).release_value_but_fixme_should_propagate_errors();
|
||||
VERIFY(sdt->event_timer_block.address_space == (u8)ACPI::GenericAddressStructure::AddressSpace::SystemMemory);
|
||||
return PhysicalAddress(sdt->event_timer_block.address);
|
||||
}
|
||||
|
||||
HPETRegistersBlock const& HPET::registers() const
|
||||
{
|
||||
return *(HPETRegistersBlock const*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
|
||||
}
|
||||
|
||||
HPETRegistersBlock& HPET::registers()
|
||||
{
|
||||
return *(HPETRegistersBlock*)m_hpet_mmio_region->vaddr().offset(m_physical_acpi_hpet_registers.offset_in_page()).as_ptr();
|
||||
}
|
||||
|
||||
u64 HPET::raw_counter_ticks_to_ns(u64 raw_ticks) const
|
||||
{
|
||||
// ABSOLUTE_MAXIMUM_COUNTER_TICK_PERIOD == 100 nanoseconds
|
||||
return (raw_ticks * (u64)registers().capabilities.main_counter_tick_period * 100ull) / ABSOLUTE_MAXIMUM_COUNTER_TICK_PERIOD;
|
||||
}
|
||||
|
||||
u64 HPET::ns_to_raw_counter_ticks(u64 ns) const
|
||||
{
|
||||
return (ns * 1000000ull) / (u64)registers().capabilities.main_counter_tick_period;
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT HPET::HPET(PhysicalAddress acpi_hpet)
|
||||
: m_physical_acpi_hpet_table(acpi_hpet)
|
||||
, m_physical_acpi_hpet_registers(find_acpi_hpet_registers_block())
|
||||
, m_hpet_mmio_region(MM.allocate_kernel_region(m_physical_acpi_hpet_registers.page_base(), PAGE_SIZE, "HPET MMIO"sv, Memory::Region::Access::ReadWrite).release_value())
|
||||
{
|
||||
s_hpet = this; // Make available as soon as possible so that IRQs can use it
|
||||
|
||||
auto sdt = Memory::map_typed<const volatile ACPI::Structures::HPET>(m_physical_acpi_hpet_table).release_value_but_fixme_should_propagate_errors();
|
||||
m_vendor_id = sdt->pci_vendor_id;
|
||||
m_minimum_tick = sdt->mininum_clock_tick;
|
||||
dmesgln("HPET: Minimum clock tick - {}", m_minimum_tick);
|
||||
|
||||
auto& regs = registers();
|
||||
|
||||
// Note: We must do a 32 bit access to offsets 0x0, or 0x4 only.
|
||||
size_t timers_count = ((regs.capabilities.attributes >> 8) & 0x1f) + 1;
|
||||
m_main_counter_64bits = (regs.capabilities.attributes & (u32)HPETFlags::Attributes::Counter64BitCapable) != 0;
|
||||
dmesgln("HPET: Timers count - {}", timers_count);
|
||||
dmesgln("HPET: Main counter size: {}", (m_main_counter_64bits ? "64-bit" : "32-bit"));
|
||||
for (size_t i = 0; i < timers_count; i++) {
|
||||
bool capable_64_bit = regs.timers[i].capabilities & (u32)HPETFlags::TimerConfiguration::Timer64BitsCapable;
|
||||
dmesgln("HPET: Timer[{}] comparator size: {}, mode: {}", i,
|
||||
(capable_64_bit ? "64-bit" : "32-bit"),
|
||||
((!capable_64_bit || (regs.timers[i].capabilities & (u32)HPETFlags::TimerConfiguration::Force32BitMode)) ? "32-bit" : "64-bit"));
|
||||
}
|
||||
VERIFY(timers_count >= 2);
|
||||
|
||||
global_disable();
|
||||
|
||||
m_frequency = NANOSECOND_PERIOD_TO_HERTZ(raw_counter_ticks_to_ns(1));
|
||||
dmesgln("HPET: frequency {} Hz ({} MHz) resolution: {} ns", m_frequency, HERTZ_TO_MEGAHERTZ(m_frequency), raw_counter_ticks_to_ns(1));
|
||||
|
||||
VERIFY(regs.capabilities.main_counter_tick_period <= ABSOLUTE_MAXIMUM_COUNTER_TICK_PERIOD);
|
||||
|
||||
// Reset the counter, just in case... (needs to match m_main_counter_last_read)
|
||||
regs.main_counter_value.high = 0;
|
||||
regs.main_counter_value.low = 0;
|
||||
if (regs.capabilities.attributes & (u32)HPETFlags::Attributes::LegacyReplacementRouteCapable)
|
||||
regs.configuration.low = regs.configuration.low | (u32)HPETFlags::Configuration::LegacyReplacementRoute;
|
||||
|
||||
m_comparators.append(HPETComparator::create(0, 0, is_periodic_capable(0), is_64bit_capable(0)));
|
||||
m_comparators.append(HPETComparator::create(1, 8, is_periodic_capable(1), is_64bit_capable(1)));
|
||||
|
||||
global_enable();
|
||||
}
|
||||
}
|
83
Kernel/Arch/x86_64/Time/HPET.h
Normal file
83
Kernel/Arch/x86_64/Time/HPET.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <Kernel/Library/NonnullLockRefPtrVector.h>
|
||||
#include <Kernel/Memory/Region.h>
|
||||
#include <Kernel/PhysicalAddress.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class HPETComparator;
|
||||
struct HPETRegistersBlock;
|
||||
|
||||
class HPET {
|
||||
public:
|
||||
static bool initialized();
|
||||
static bool test_and_initialize();
|
||||
static bool check_for_exisiting_periodic_timers();
|
||||
static HPET& the();
|
||||
|
||||
u64 frequency() const { return m_frequency; }
|
||||
u64 raw_counter_ticks_to_ns(u64) const;
|
||||
u64 ns_to_raw_counter_ticks(u64) const;
|
||||
|
||||
NonnullLockRefPtrVector<HPETComparator> const& comparators() const { return m_comparators; }
|
||||
void disable(HPETComparator const&);
|
||||
void enable(HPETComparator const&);
|
||||
|
||||
void update_periodic_comparator_value();
|
||||
void update_non_periodic_comparator_value(HPETComparator const& comparator);
|
||||
|
||||
void set_comparator_irq_vector(u8 comparator_number, u8 irq_vector);
|
||||
|
||||
void enable_periodic_interrupt(HPETComparator const& comparator);
|
||||
void disable_periodic_interrupt(HPETComparator const& comparator);
|
||||
|
||||
u64 update_time(u64& seconds_since_boot, u32& ticks_this_second, bool query_only);
|
||||
u64 read_main_counter_unsafe() const;
|
||||
u64 read_main_counter() const;
|
||||
|
||||
Vector<unsigned> capable_interrupt_numbers(u8 comparator_number);
|
||||
Vector<unsigned> capable_interrupt_numbers(HPETComparator const&);
|
||||
|
||||
private:
|
||||
HPETRegistersBlock const& registers() const;
|
||||
HPETRegistersBlock& registers();
|
||||
|
||||
void global_disable();
|
||||
void global_enable();
|
||||
|
||||
bool is_periodic_capable(u8 comparator_number) const;
|
||||
bool is_64bit_capable(u8 comparator_number) const;
|
||||
void set_comparators_to_optimal_interrupt_state(size_t timers_count);
|
||||
|
||||
u64 nanoseconds_to_raw_ticks() const;
|
||||
|
||||
PhysicalAddress find_acpi_hpet_registers_block();
|
||||
explicit HPET(PhysicalAddress acpi_hpet);
|
||||
PhysicalAddress m_physical_acpi_hpet_table;
|
||||
PhysicalAddress m_physical_acpi_hpet_registers;
|
||||
OwnPtr<Memory::Region> m_hpet_mmio_region;
|
||||
|
||||
u64 m_main_counter_last_read { 0 };
|
||||
u64 m_main_counter_drift { 0 };
|
||||
u32 m_32bit_main_counter_wraps { 0 };
|
||||
|
||||
u16 m_vendor_id;
|
||||
u16 m_minimum_tick;
|
||||
u64 m_frequency;
|
||||
u8 m_revision_id;
|
||||
bool m_main_counter_64bits : 1;
|
||||
bool legacy_replacement_route_capable : 1;
|
||||
|
||||
NonnullLockRefPtrVector<HPETComparator> m_comparators;
|
||||
};
|
||||
}
|
136
Kernel/Arch/x86_64/Time/HPETComparator.cpp
Normal file
136
Kernel/Arch/x86_64/Time/HPETComparator.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <Kernel/Arch/x86_64/Time/HPETComparator.h>
|
||||
#include <Kernel/Assertions.h>
|
||||
#include <Kernel/Debug.h>
|
||||
#include <Kernel/InterruptDisabler.h>
|
||||
#include <Kernel/Sections.h>
|
||||
#include <Kernel/Time/TimeManagement.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
UNMAP_AFTER_INIT NonnullLockRefPtr<HPETComparator> HPETComparator::create(u8 number, u8 irq, bool periodic_capable, bool is_64bit_capable)
|
||||
{
|
||||
auto timer = adopt_lock_ref(*new HPETComparator(number, irq, periodic_capable, is_64bit_capable));
|
||||
timer->register_interrupt_handler();
|
||||
return timer;
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT HPETComparator::HPETComparator(u8 number, u8 irq, bool periodic_capable, bool is_64bit_capable)
|
||||
: HardwareTimer(irq)
|
||||
, m_periodic(false)
|
||||
, m_periodic_capable(periodic_capable)
|
||||
, m_enabled(false)
|
||||
, m_is_64bit_capable(is_64bit_capable)
|
||||
, m_comparator_number(number)
|
||||
{
|
||||
}
|
||||
|
||||
void HPETComparator::disable()
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
m_enabled = false;
|
||||
HPET::the().disable(*this);
|
||||
}
|
||||
|
||||
void HPETComparator::set_periodic()
|
||||
{
|
||||
VERIFY(m_periodic_capable);
|
||||
m_periodic = true;
|
||||
m_enabled = true;
|
||||
HPET::the().enable_periodic_interrupt(*this);
|
||||
}
|
||||
void HPETComparator::set_non_periodic()
|
||||
{
|
||||
VERIFY(m_periodic_capable);
|
||||
m_periodic = false;
|
||||
m_enabled = true;
|
||||
HPET::the().disable_periodic_interrupt(*this);
|
||||
}
|
||||
|
||||
bool HPETComparator::handle_irq(RegisterState const& regs)
|
||||
{
|
||||
auto result = HardwareTimer::handle_irq(regs);
|
||||
if (!is_periodic())
|
||||
set_new_countdown();
|
||||
return result;
|
||||
}
|
||||
|
||||
void HPETComparator::set_new_countdown()
|
||||
{
|
||||
VERIFY_INTERRUPTS_DISABLED();
|
||||
VERIFY(m_frequency <= HPET::the().frequency());
|
||||
HPET::the().update_non_periodic_comparator_value(*this);
|
||||
}
|
||||
|
||||
size_t HPETComparator::ticks_per_second() const
|
||||
{
|
||||
return m_frequency;
|
||||
}
|
||||
|
||||
void HPETComparator::reset_to_default_ticks_per_second()
|
||||
{
|
||||
dbgln("reset_to_default_ticks_per_second");
|
||||
m_frequency = OPTIMAL_TICKS_PER_SECOND_RATE;
|
||||
if (!is_periodic())
|
||||
set_new_countdown();
|
||||
else
|
||||
try_to_set_frequency(m_frequency);
|
||||
}
|
||||
bool HPETComparator::try_to_set_frequency(size_t frequency)
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
if (!is_capable_of_frequency(frequency)) {
|
||||
dbgln("HPETComparator: not capable of frequency: {}", frequency);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto hpet_frequency = HPET::the().frequency();
|
||||
VERIFY(frequency <= hpet_frequency);
|
||||
m_frequency = frequency;
|
||||
m_enabled = true;
|
||||
|
||||
dbgln_if(HPET_COMPARATOR_DEBUG, "HPET Comparator: Max frequency {} Hz, want to set {} Hz, periodic: {}", hpet_frequency, frequency, is_periodic());
|
||||
|
||||
if (is_periodic()) {
|
||||
HPET::the().update_periodic_comparator_value();
|
||||
} else {
|
||||
HPET::the().update_non_periodic_comparator_value(*this);
|
||||
}
|
||||
HPET::the().enable(*this);
|
||||
enable_irq(); // Enable if we haven't already
|
||||
return true;
|
||||
}
|
||||
bool HPETComparator::is_capable_of_frequency(size_t frequency) const
|
||||
{
|
||||
if (frequency > HPET::the().frequency())
|
||||
return false;
|
||||
// HPET::update_periodic_comparator_value and HPET::update_non_periodic_comparator_value
|
||||
// calculate the best counter based on the desired frequency.
|
||||
return true;
|
||||
}
|
||||
size_t HPETComparator::calculate_nearest_possible_frequency(size_t frequency) const
|
||||
{
|
||||
if (frequency > HPET::the().frequency())
|
||||
return HPET::the().frequency();
|
||||
// HPET::update_periodic_comparator_value and HPET::update_non_periodic_comparator_value
|
||||
// calculate the best counter based on the desired frequency.
|
||||
return frequency;
|
||||
}
|
||||
|
||||
u64 HPETComparator::current_raw() const
|
||||
{
|
||||
return HPET::the().read_main_counter();
|
||||
}
|
||||
|
||||
u64 HPETComparator::raw_to_ns(u64 raw_delta) const
|
||||
{
|
||||
return HPET::the().raw_counter_ticks_to_ns(raw_delta);
|
||||
}
|
||||
|
||||
}
|
54
Kernel/Arch/x86_64/Time/HPETComparator.h
Normal file
54
Kernel/Arch/x86_64/Time/HPETComparator.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Types.h>
|
||||
#include <Kernel/Arch/x86_64/Time/HPET.h>
|
||||
#include <Kernel/Time/HardwareTimer.h>
|
||||
|
||||
namespace Kernel {
|
||||
class HPETComparator final : public HardwareTimer<IRQHandler> {
|
||||
friend class HPET;
|
||||
|
||||
public:
|
||||
static NonnullLockRefPtr<HPETComparator> create(u8 number, u8 irq, bool periodic_capable, bool is_64bit_capable);
|
||||
|
||||
virtual HardwareTimerType timer_type() const override { return HardwareTimerType::HighPrecisionEventTimer; }
|
||||
virtual StringView model() const override { return "HPET"sv; }
|
||||
|
||||
u8 comparator_number() const { return m_comparator_number; }
|
||||
bool is_enabled() const { return m_enabled; }
|
||||
bool is_64bit_capable() const { return m_is_64bit_capable; }
|
||||
|
||||
virtual size_t ticks_per_second() const override;
|
||||
|
||||
virtual bool is_periodic() const override { return m_periodic; }
|
||||
virtual bool is_periodic_capable() const override { return m_periodic_capable; }
|
||||
virtual void set_periodic() override;
|
||||
virtual void set_non_periodic() override;
|
||||
virtual void disable() override;
|
||||
virtual bool can_query_raw() const override { return true; }
|
||||
virtual u64 current_raw() const override;
|
||||
virtual u64 raw_to_ns(u64) const override;
|
||||
|
||||
virtual void reset_to_default_ticks_per_second() override;
|
||||
virtual bool try_to_set_frequency(size_t frequency) override;
|
||||
virtual bool is_capable_of_frequency(size_t frequency) const override;
|
||||
virtual size_t calculate_nearest_possible_frequency(size_t frequency) const override;
|
||||
|
||||
private:
|
||||
void set_new_countdown();
|
||||
virtual bool handle_irq(RegisterState const&) override;
|
||||
HPETComparator(u8 number, u8 irq, bool periodic_capable, bool is_64bit_capable);
|
||||
bool m_periodic : 1;
|
||||
bool m_periodic_capable : 1;
|
||||
bool m_enabled : 1;
|
||||
bool m_is_64bit_capable : 1;
|
||||
u8 m_comparator_number { 0 };
|
||||
};
|
||||
}
|
89
Kernel/Arch/x86_64/Time/PIT.cpp
Normal file
89
Kernel/Arch/x86_64/Time/PIT.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <Kernel/Arch/x86_64/IO.h>
|
||||
#include <Kernel/Arch/x86_64/Time/PIT.h>
|
||||
#include <Kernel/InterruptDisabler.h>
|
||||
#include <Kernel/Interrupts/GenericInterruptHandler.h>
|
||||
#include <Kernel/Scheduler.h>
|
||||
#include <Kernel/Sections.h>
|
||||
#include <Kernel/Thread.h>
|
||||
#include <Kernel/Time/TimeManagement.h>
|
||||
|
||||
#define IRQ_TIMER 0
|
||||
namespace Kernel {
|
||||
|
||||
UNMAP_AFTER_INIT NonnullLockRefPtr<PIT> PIT::initialize(Function<void(RegisterState const&)> callback)
|
||||
{
|
||||
return adopt_lock_ref(*new PIT(move(callback)));
|
||||
}
|
||||
|
||||
[[maybe_unused]] inline static void reset_countdown(u16 timer_reload)
|
||||
{
|
||||
IO::out8(PIT_CTL, TIMER0_SELECT | WRITE_WORD | MODE_COUNTDOWN);
|
||||
IO::out8(TIMER0_CTL, LSB(timer_reload));
|
||||
IO::out8(TIMER0_CTL, MSB(timer_reload));
|
||||
}
|
||||
|
||||
PIT::PIT(Function<void(RegisterState const&)> callback)
|
||||
: HardwareTimer(IRQ_TIMER, move(callback))
|
||||
, m_periodic(true)
|
||||
{
|
||||
IO::out8(PIT_CTL, TIMER0_SELECT | WRITE_WORD | MODE_SQUARE_WAVE);
|
||||
|
||||
dmesgln("PIT: {} Hz, square wave ({:#08x})", OPTIMAL_TICKS_PER_SECOND_RATE, BASE_FREQUENCY / OPTIMAL_TICKS_PER_SECOND_RATE);
|
||||
reset_to_default_ticks_per_second();
|
||||
enable_irq();
|
||||
}
|
||||
|
||||
size_t PIT::ticks_per_second() const
|
||||
{
|
||||
return m_frequency;
|
||||
}
|
||||
|
||||
void PIT::set_periodic()
|
||||
{
|
||||
IO::out8(PIT_CTL, TIMER0_CTL | WRITE_WORD | MODE_SQUARE_WAVE);
|
||||
m_periodic = true;
|
||||
}
|
||||
void PIT::set_non_periodic()
|
||||
{
|
||||
IO::out8(PIT_CTL, TIMER0_CTL | WRITE_WORD | MODE_ONESHOT);
|
||||
m_periodic = false;
|
||||
}
|
||||
|
||||
void PIT::reset_to_default_ticks_per_second()
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
bool success = try_to_set_frequency(OPTIMAL_TICKS_PER_SECOND_RATE);
|
||||
VERIFY(success);
|
||||
}
|
||||
|
||||
bool PIT::try_to_set_frequency(size_t frequency)
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
if (!is_capable_of_frequency(frequency))
|
||||
return false;
|
||||
disable_irq();
|
||||
size_t reload_value = BASE_FREQUENCY / frequency;
|
||||
IO::out8(TIMER0_CTL, LSB(reload_value));
|
||||
IO::out8(TIMER0_CTL, MSB(reload_value));
|
||||
m_frequency = frequency;
|
||||
enable_irq();
|
||||
return true;
|
||||
}
|
||||
bool PIT::is_capable_of_frequency(size_t frequency) const
|
||||
{
|
||||
VERIFY(frequency != 0);
|
||||
return frequency <= BASE_FREQUENCY;
|
||||
}
|
||||
size_t PIT::calculate_nearest_possible_frequency(size_t frequency) const
|
||||
{
|
||||
VERIFY(frequency != 0);
|
||||
return frequency;
|
||||
}
|
||||
|
||||
}
|
57
Kernel/Arch/x86_64/Time/PIT.h
Normal file
57
Kernel/Arch/x86_64/Time/PIT.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <Kernel/Library/NonnullLockRefPtr.h>
|
||||
#include <Kernel/Time/HardwareTimer.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
/* Timer related ports */
|
||||
#define TIMER0_CTL 0x40
|
||||
#define TIMER1_CTL 0x41
|
||||
#define TIMER2_CTL 0x42
|
||||
#define PIT_CTL 0x43
|
||||
|
||||
/* Building blocks for PIT_CTL */
|
||||
#define TIMER0_SELECT 0x00
|
||||
#define TIMER1_SELECT 0x40
|
||||
#define TIMER2_SELECT 0x80
|
||||
|
||||
#define MODE_COUNTDOWN 0x00
|
||||
#define MODE_ONESHOT 0x02
|
||||
#define MODE_RATE 0x04
|
||||
#define MODE_SQUARE_WAVE 0x06
|
||||
|
||||
#define WRITE_WORD 0x30
|
||||
|
||||
#define BASE_FREQUENCY 1193182
|
||||
|
||||
class PIT final : public HardwareTimer<IRQHandler> {
|
||||
public:
|
||||
static NonnullLockRefPtr<PIT> initialize(Function<void(RegisterState const&)>);
|
||||
virtual HardwareTimerType timer_type() const override { return HardwareTimerType::i8253; }
|
||||
virtual StringView model() const override { return "i8254"sv; }
|
||||
virtual size_t ticks_per_second() const override;
|
||||
|
||||
virtual bool is_periodic() const override { return m_periodic; }
|
||||
virtual bool is_periodic_capable() const override { return true; }
|
||||
virtual void set_periodic() override;
|
||||
virtual void set_non_periodic() override;
|
||||
virtual void disable() override { }
|
||||
|
||||
virtual void reset_to_default_ticks_per_second() override;
|
||||
virtual bool try_to_set_frequency(size_t frequency) override;
|
||||
virtual bool is_capable_of_frequency(size_t frequency) const override;
|
||||
virtual size_t calculate_nearest_possible_frequency(size_t frequency) const override;
|
||||
|
||||
private:
|
||||
explicit PIT(Function<void(RegisterState const&)>);
|
||||
bool m_periodic { true };
|
||||
};
|
||||
}
|
91
Kernel/Arch/x86_64/Time/RTC.cpp
Normal file
91
Kernel/Arch/x86_64/Time/RTC.cpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <Kernel/Arch/x86_64/CMOS.h>
|
||||
#include <Kernel/Arch/x86_64/IO.h>
|
||||
#include <Kernel/Arch/x86_64/NonMaskableInterruptDisabler.h>
|
||||
#include <Kernel/Arch/x86_64/Time/RTC.h>
|
||||
#include <Kernel/InterruptDisabler.h>
|
||||
#include <Kernel/Time/TimeManagement.h>
|
||||
|
||||
namespace Kernel {
|
||||
#define IRQ_TIMER 8
|
||||
#define MAX_FREQUENCY 8000
|
||||
|
||||
NonnullLockRefPtr<RealTimeClock> RealTimeClock::create(Function<void(RegisterState const&)> callback)
|
||||
{
|
||||
return adopt_lock_ref(*new RealTimeClock(move(callback)));
|
||||
}
|
||||
RealTimeClock::RealTimeClock(Function<void(RegisterState const&)> callback)
|
||||
: HardwareTimer(IRQ_TIMER, move(callback))
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
NonMaskableInterruptDisabler nmi_disabler;
|
||||
enable_irq();
|
||||
CMOS::write(0x8B, CMOS::read(0xB) | 0x40);
|
||||
reset_to_default_ticks_per_second();
|
||||
}
|
||||
bool RealTimeClock::handle_irq(RegisterState const& regs)
|
||||
{
|
||||
auto result = HardwareTimer::handle_irq(regs);
|
||||
CMOS::read(0x8C);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t RealTimeClock::ticks_per_second() const
|
||||
{
|
||||
return m_frequency;
|
||||
}
|
||||
|
||||
void RealTimeClock::reset_to_default_ticks_per_second()
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
bool success = try_to_set_frequency(1024);
|
||||
VERIFY(success);
|
||||
}
|
||||
|
||||
// FIXME: This is a quick & dirty log base 2 with a parameter. Please provide something better in the future.
|
||||
static int quick_log2(size_t number)
|
||||
{
|
||||
int count = 0;
|
||||
while (number >>= 1)
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
bool RealTimeClock::try_to_set_frequency(size_t frequency)
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
if (!is_capable_of_frequency(frequency))
|
||||
return false;
|
||||
disable_irq();
|
||||
u8 previous_rate = CMOS::read(0x8A);
|
||||
u8 rate = quick_log2(32768 / frequency) + 1;
|
||||
dbgln("RTC: Set rate to {}", rate);
|
||||
CMOS::write(0x8A, (previous_rate & 0xF0) | rate);
|
||||
m_frequency = frequency;
|
||||
dbgln("RTC: Set frequency to {} Hz", frequency);
|
||||
enable_irq();
|
||||
return true;
|
||||
}
|
||||
bool RealTimeClock::is_capable_of_frequency(size_t frequency) const
|
||||
{
|
||||
VERIFY(frequency != 0);
|
||||
if (frequency > MAX_FREQUENCY)
|
||||
return false;
|
||||
if (32768 % frequency)
|
||||
return false;
|
||||
|
||||
u16 divider = 32768 / frequency;
|
||||
return (divider <= 16384 && divider >= 4); // Frequency can be in range of 2 Hz to 8 KHz
|
||||
}
|
||||
size_t RealTimeClock::calculate_nearest_possible_frequency(size_t frequency) const
|
||||
{
|
||||
VERIFY(frequency != 0);
|
||||
return frequency;
|
||||
}
|
||||
|
||||
}
|
36
Kernel/Arch/x86_64/Time/RTC.h
Normal file
36
Kernel/Arch/x86_64/Time/RTC.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Kernel/Arch/x86_64/RTC.h>
|
||||
#include <Kernel/Library/NonnullLockRefPtr.h>
|
||||
#include <Kernel/Time/HardwareTimer.h>
|
||||
|
||||
namespace Kernel {
|
||||
class RealTimeClock final : public HardwareTimer<IRQHandler> {
|
||||
public:
|
||||
static NonnullLockRefPtr<RealTimeClock> create(Function<void(RegisterState const&)> callback);
|
||||
virtual HardwareTimerType timer_type() const override { return HardwareTimerType::RTC; }
|
||||
virtual StringView model() const override { return "Real Time Clock"sv; }
|
||||
virtual size_t ticks_per_second() const override;
|
||||
|
||||
virtual bool is_periodic() const override { return true; }
|
||||
virtual bool is_periodic_capable() const override { return true; }
|
||||
virtual void set_periodic() override { }
|
||||
virtual void set_non_periodic() override { }
|
||||
virtual void disable() override { }
|
||||
|
||||
virtual void reset_to_default_ticks_per_second() override;
|
||||
virtual bool try_to_set_frequency(size_t frequency) override;
|
||||
virtual bool is_capable_of_frequency(size_t frequency) const override;
|
||||
virtual size_t calculate_nearest_possible_frequency(size_t frequency) const override;
|
||||
|
||||
private:
|
||||
explicit RealTimeClock(Function<void(RegisterState const&)> callback);
|
||||
virtual bool handle_irq(RegisterState const&) override;
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue