kvmclock: initial implementation

This commit is contained in:
Matheus
2024-09-29 11:21:46 -03:00
parent 11f76ab01d
commit fdd203d1a7
3 changed files with 118 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
#include <kernel/timekeeper.h>
#include <kernel/alloc.h>
#include <arch/msr.h>
#include <cpuid.h>
#include <logging.h>
#include <arch/cpuid.h>
#include <kernel/pmm.h>
#include <arch/tsc.h>
#define KVM_TIMER_SYSTEM_TIME_ALIGNMENT 4
#define KVM_TIMER_SYSTEM_TIME_ENABLE 1
#define KVM_TIMER_SYSTEM_TIME_VERSION_IN_PROGRESS 1
typedef struct {
uint32_t version;
uint32_t padding0;
uint64_t tsc;
uint64_t time;
uint32_t tsc_mul;
int8_t tsc_shift;
uint8_t flags;
uint8_t padding1[2];
} __attribute__((packed)) kvm_timer_info_t;
static bool kvm_timer_probe(void) {
// figure out if we are running in kvm
uint32_t base = cpuid_find_hypervisor_base_kvm();
if (base == 0)
return false;
cpuid_results_t cpuid_results;
// and if the clock is enabled
cpuid(base + 1, &cpuid_results);
if (cpuid_results.eax & (1 << 3))
return true;
return false;
}
static timekeeper_source_info_t *kvm_timer_init(void) {
__assert(kvm_timer_probe());
timekeeper_source_info_t *timekeeper_source_info = alloc(sizeof(timekeeper_source_info_t));
__assert(timekeeper_source_info);
timekeeper_source_info->ticks_per_us = 1000; // each tick in the kvm timer system_time field is 1 ns
void *virtual_address = alloc(sizeof(kvm_timer_info_t));
__assert(virtual_address);
__assert(((uintptr_t)virtual_address % KVM_TIMER_SYSTEM_TIME_ALIGNMENT) == 0);
int page_offset = (uintptr_t)virtual_address % PAGE_SIZE;
void *virtual_page = (void *)((uintptr_t)virtual_address - page_offset);
void *physical_page = vmm_getphysical(virtual_page, false);
__assert(physical_page);
timekeeper_source_info->private = (void *)((uintptr_t)physical_page + page_offset);
wrmsr(MSR_KVM_SYSTEM_TIME_NEW, (uint64_t)timekeeper_source_info->private | KVM_TIMER_SYSTEM_TIME_ENABLE);
return timekeeper_source_info;
}
static time_t kvm_timer_ticks(timekeeper_source_info_t *timekeeper_source_info) {
volatile kvm_timer_info_t *kvm_timer_info = MAKE_HHDM(timekeeper_source_info->private);
wrmsr(MSR_KVM_SYSTEM_TIME_NEW, (uint64_t)timekeeper_source_info->private | KVM_TIMER_SYSTEM_TIME_ENABLE);
// if version is odd, an update is in progress.
// we will wait until one isnt
uint32_t version_old;
uint32_t version_new;
uint64_t ticks = 0;
do {
do {
version_old = kvm_timer_info->version;
} while (version_old & KVM_TIMER_SYSTEM_TIME_VERSION_IN_PROGRESS);
uint64_t tsc = rdtsc_serialized();
ticks = tsc - kvm_timer_info->tsc;
ticks = (kvm_timer_info->tsc_shift >= 0) ? (ticks << kvm_timer_info->tsc_shift) : (ticks >> -kvm_timer_info->tsc_shift);
// we need to pretend ticks is a *128* bit variable!
// this will be done in just an inline assembly stub...
// mulq will leave the 128 bit results in rdx:rax
// shrd will do the 32 bit shift required
// we will stay with the low 64 bits and leave the high 32 bits, as they are not nescessary
// (the 64 bit nanosecond counter will overflow in centuries...)
asm volatile ("mulq %%rdx; shrd $32, %%rdx, %%rax" : "=a"(ticks) : "a"(ticks), "d"(kvm_timer_info->tsc_mul));
ticks = ticks + kvm_timer_info->time;
version_new = kvm_timer_info->version;
} while (version_old != version_new || (version_new & KVM_TIMER_SYSTEM_TIME_VERSION_IN_PROGRESS));
return ticks;
}
TIMEKEEPER_SOURCE(kvmtimer_source,
.name = "KVM clock",
.priority = 1000,
.probe = kvm_timer_probe,
.init = kvm_timer_init,
.ticks = kvm_timer_ticks,
.flags = TIMEKEEPER_SOURCE_FLAGS_EARLY
);
+2
View File
@@ -12,6 +12,8 @@
#define MSR_GSBASE 0xC0000101
#define MSR_KERNELGSBASE 0xC0000102
#define MSR_KVM_SYSTEM_TIME_NEW 0x4b564d01
static inline uint64_t rdmsr(uint32_t which) {
uint64_t low,high;
asm volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(which));
+11
View File
@@ -0,0 +1,11 @@
#ifndef _TSC_H
#define _TSC_H
static inline uint64_t rdtsc_serialized(void) {
uint32_t high;
uint32_t low;
asm volatile("cpuid; rdtsc;" : "=a"(low), "=d"(high) : : "rbx", "rcx");
return ((uint64_t)high << 32) | low;
}
#endif