forked from Bananymous/banan-os
247 lines
5.8 KiB
C++
247 lines
5.8 KiB
C++
#include <BAN/Sort.h>
|
|
|
|
#include <kernel/CPUID.h>
|
|
#include <kernel/Scheduler.h>
|
|
#include <kernel/Timer/HPET.h>
|
|
#include <kernel/Timer/PIT.h>
|
|
#include <kernel/Timer/Timer.h>
|
|
|
|
namespace Kernel
|
|
{
|
|
|
|
static SystemTimer* s_instance = nullptr;
|
|
|
|
struct pvclock_vcpu_time_info
|
|
{
|
|
uint32_t version;
|
|
uint32_t pad0;
|
|
uint64_t tsc_timestamp;
|
|
uint64_t system_time;
|
|
uint32_t tsc_to_system_mul;
|
|
int8_t tsc_shift;
|
|
uint8_t flags;
|
|
uint8_t pad[2];
|
|
};
|
|
|
|
void SystemTimer::initialize()
|
|
{
|
|
ASSERT(s_instance == nullptr);
|
|
auto* temp = new SystemTimer;
|
|
ASSERT(temp);
|
|
temp->initialize_timers();
|
|
s_instance = temp;
|
|
}
|
|
|
|
SystemTimer& SystemTimer::get()
|
|
{
|
|
ASSERT(s_instance);
|
|
return *s_instance;
|
|
}
|
|
|
|
bool SystemTimer::is_initialized()
|
|
{
|
|
return !!s_instance;
|
|
}
|
|
|
|
void SystemTimer::initialize_timers()
|
|
{
|
|
m_rtc = MUST(BAN::UniqPtr<RTC>::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
|
|
{
|
|
m_timer = res.release_value();
|
|
dprintln("PIT initialized");
|
|
return;
|
|
}
|
|
|
|
Kernel::panic("Could not initialize any timer");
|
|
}
|
|
|
|
void SystemTimer::initialize_tsc()
|
|
{
|
|
if (CPUID::has_kvm_pvclock())
|
|
return initialize_pvclock();
|
|
if (CPUID::has_invariant_tsc())
|
|
return initialize_invariant_tsc();
|
|
dwarnln("No supported TSC based timers available");
|
|
}
|
|
|
|
void SystemTimer::initialize_invariant_tsc()
|
|
{
|
|
const uint64_t tsc_freq = [this]() -> uint64_t {
|
|
if (const auto cpuid_freq = CPUID::get_tsc_frequency())
|
|
return cpuid_freq;
|
|
|
|
// take 5x 50 ms samples and use the median value
|
|
|
|
constexpr size_t tsc_sample_count = 5;
|
|
constexpr size_t tsc_sample_ns = 50'000'000;
|
|
|
|
uint64_t tsc_freq_samples[tsc_sample_count];
|
|
for (size_t i = 0; i < tsc_sample_count; i++)
|
|
{
|
|
const auto start_ns = m_timer->ns_since_boot();
|
|
|
|
const auto start_tsc = __builtin_ia32_rdtsc();
|
|
while (m_timer->ns_since_boot() < start_ns + tsc_sample_ns)
|
|
Processor::pause();
|
|
const auto stop_tsc = __builtin_ia32_rdtsc();
|
|
|
|
const auto stop_ns = m_timer->ns_since_boot();
|
|
|
|
const auto duration_ns = stop_ns - start_ns;
|
|
const auto count_tsc = stop_tsc - start_tsc;
|
|
|
|
tsc_freq_samples[i] = count_tsc * 1'000'000'000 / duration_ns;
|
|
}
|
|
|
|
BAN::sort::sort(tsc_freq_samples, tsc_freq_samples + tsc_sample_count);
|
|
|
|
return tsc_freq_samples[tsc_sample_count / 2];
|
|
}();
|
|
|
|
m_tsc_info = { .invariant = {
|
|
.shift = 0,
|
|
.mult = static_cast<uint32_t>((1'000'000'000ull << 32) / tsc_freq),
|
|
}};
|
|
m_tsc_type = TSCType::Invariant;
|
|
Processor::initialize_tsc(m_boot_time);
|
|
|
|
dprintln("Initialized invariant TSC ({} Hz)", tsc_freq);
|
|
}
|
|
|
|
static pvclock_vcpu_time_info read_pvclock_safe(vaddr_t pvclock_vaddr)
|
|
{
|
|
for (;;)
|
|
{
|
|
const volatile auto& pvclock = *reinterpret_cast<const volatile pvclock_vcpu_time_info*>(pvclock_vaddr);
|
|
|
|
const auto version = pvclock.version;
|
|
if (version & 1)
|
|
continue;
|
|
|
|
pvclock_vcpu_time_info copy;
|
|
memcpy(©, const_cast<const pvclock_vcpu_time_info*>(&pvclock), sizeof(pvclock_vcpu_time_info));
|
|
|
|
if (pvclock.version == version)
|
|
return copy;
|
|
}
|
|
}
|
|
|
|
void SystemTimer::initialize_pvclock()
|
|
{
|
|
m_tsc_page = MUST(DMARegion::create(sizeof(pvclock_vcpu_time_info), PageTable::MemoryType::Normal));
|
|
memset(reinterpret_cast<void*>(m_tsc_page->vaddr()), 0, sizeof(pvclock_vcpu_time_info));
|
|
|
|
const uint32_t paddr_hi = m_tsc_page->paddr() >> 32;
|
|
const uint32_t paddr_lo = m_tsc_page->paddr() & 0xFFFFFFFF;
|
|
asm volatile("wrmsr" :: "d"(paddr_hi), "a"(paddr_lo | 1), "c"(0x4b564d01));
|
|
|
|
m_tsc_type = TSCType::PVClock;
|
|
Processor::initialize_tsc(m_boot_time);
|
|
|
|
dprintln("Initialized pvclock");
|
|
}
|
|
|
|
void SystemTimer::update_tsc()
|
|
{
|
|
if (m_tsc_type == TSCType::None)
|
|
return;
|
|
|
|
// only update once per second
|
|
const uint64_t current_ns = Processor::ns_since_boot_tsc();
|
|
if (current_ns < m_tsc_update_ns)
|
|
return;
|
|
m_tsc_update_ns = current_ns + 1'000'000'000;
|
|
|
|
Processor::update_tsc();
|
|
Processor::broadcast_smp_message({
|
|
.type = Processor::SMPMessage::Type::UpdateTSC,
|
|
.dummy = 0,
|
|
});
|
|
}
|
|
|
|
SystemTimer::TSCInfo SystemTimer::tsc_info() const
|
|
{
|
|
switch (m_tsc_type)
|
|
{
|
|
case TSCType::None:
|
|
ASSERT_NOT_REACHED();
|
|
case TSCType::Invariant:
|
|
return {
|
|
.shift = m_tsc_info.invariant.shift,
|
|
.mult = m_tsc_info.invariant.mult,
|
|
};
|
|
case TSCType::PVClock:
|
|
const auto pvclock = read_pvclock_safe(m_tsc_page->vaddr());
|
|
return {
|
|
.shift = pvclock.tsc_shift,
|
|
.mult = pvclock.tsc_to_system_mul,
|
|
};
|
|
}
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
uint64_t SystemTimer::ms_since_boot() const
|
|
{
|
|
if (m_tsc_type == TSCType::None)
|
|
return m_timer->ms_since_boot();
|
|
return Processor::ns_since_boot_tsc() / 1'000'000;
|
|
}
|
|
|
|
uint64_t SystemTimer::ns_since_boot() const
|
|
{
|
|
if (m_tsc_type == TSCType::None)
|
|
return m_timer->ns_since_boot();
|
|
return Processor::ns_since_boot_tsc();
|
|
}
|
|
|
|
timespec SystemTimer::time_since_boot() const
|
|
{
|
|
if (m_tsc_type == TSCType::None)
|
|
return m_timer->time_since_boot();
|
|
const auto ns_since_boot = Processor::ns_since_boot_tsc();
|
|
return {
|
|
.tv_sec = static_cast<time_t>(ns_since_boot / 1'000'000'000),
|
|
.tv_nsec = static_cast<long>(ns_since_boot % 1'000'000'000)
|
|
};
|
|
}
|
|
|
|
bool SystemTimer::pre_scheduler_sleep_needs_lock() const
|
|
{
|
|
return m_timer->pre_scheduler_sleep_needs_lock();
|
|
}
|
|
|
|
void SystemTimer::pre_scheduler_sleep_ns(uint64_t ns)
|
|
{
|
|
return m_timer->pre_scheduler_sleep_ns(ns);
|
|
}
|
|
|
|
void SystemTimer::sleep_ns(uint64_t ns) const
|
|
{
|
|
if (ns == 0)
|
|
return;
|
|
Processor::scheduler().block_current_thread(nullptr, ns_since_boot() + ns, nullptr);
|
|
}
|
|
|
|
timespec SystemTimer::real_time() const
|
|
{
|
|
auto result = time_since_boot();
|
|
result.tv_sec += m_boot_time;
|
|
return result;
|
|
}
|
|
|
|
}
|