blob: 15d3777db91eada5f27b0f08da7c19e3d2415f58 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <bits.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <platform.h>
#include <zircon/types.h>
#include <arch/hypervisor.h>
#include <arch/x86/pv.h>
#include <fbl/atomic_ref.h>
#include <hypervisor/guest_physical_address_space.h>
#include <ktl/atomic.h>
#include <vm/physmap.h>
#include "pv_priv.h"
namespace {
void calculate_scale_factor(uint64_t tsc_freq, uint32_t* mul, int8_t* shift) {
// Guests converts TSC ticks to nanoseconds using this formula:
// ns = #TSCticks * mul * 2^(shift - 32).
// mul * 2^(shift - 32) is a fractional number used as a scale factor in conversion.
// It's very similar to how floating point numbers are usually represented in memory.
static const uint64_t target_freq = 1000000000ul;
DEBUG_ASSERT(tsc_freq != 0);
// We maintain the folowing invariant: 2^(exponent - 32) * x/y ~ target_freq / tsc_freq,
int8_t exponent = 32;
uint64_t x = target_freq;
uint64_t y = tsc_freq;
// First make y small enough so that (y << 31) doesn't overflow in the next step. Adjust
// exponent along the way to maintain invariant.
while (y >= (1ull << 31)) {
y >>= 1;
exponent--;
}
// We scale x/y multiplying x by 2 until it gets big enough or we run out of bits.
while (x < (y << 31) && BIT(x, 63) == 0) {
x <<= 1;
exponent--;
}
// Though it's very unlikely lets also consider a situation when x/y is still too small.
while (x < y) {
y >>= 1;
exponent++;
}
// Finally make sure that x/y fits within 32 bits.
while (x >= (y << 32)) {
x >>= 1;
exponent++;
}
*shift = static_cast<int8_t>(exponent);
*mul = static_cast<uint32_t>(x / y);
}
} // namespace
extern ktl::atomic<int64_t> utc_offset;
namespace {
DECLARE_SINGLETON_MUTEX(UpdateBootTimeLock);
} // namespace
zx_status_t pv_clock_update_boot_time(hypervisor::GuestPhysicalAddressSpace* gpas,
zx_vaddr_t guest_paddr) {
// KVM doesn't provide any protection against concurrent wall time requests from different
// VCPUs, but documentation doesn't mention that it cannot happen and moreover it properly
// protects per VCPU system time. Therefore to be on the safer side we use one global mutex
// for protection.
static uint32_t version TA_GUARDED(UpdateBootTimeLock::Get());
hypervisor::GuestPtr guest_ptr;
zx_status_t status = gpas->CreateGuestPtr(guest_paddr, sizeof(pv_clock_boot_time),
"pv_clock-boot-time-guest-mapping", &guest_ptr);
if (status != ZX_OK) {
return status;
}
auto boot_time = guest_ptr.as<pv_clock_boot_time>();
ZX_DEBUG_ASSERT(boot_time != nullptr);
memset(boot_time, 0, sizeof(*boot_time));
Guard<Mutex> guard(UpdateBootTimeLock::Get());
zx_time_t time = utc_offset.load();
// See the comment for pv_clock_boot_time structure in arch/x86/pv.h
fbl::atomic_ref<uint32_t> guest_version(boot_time->version);
guest_version.store(version + 1, fbl::memory_order_relaxed);
ktl::atomic_thread_fence(ktl::memory_order_seq_cst);
boot_time->seconds = static_cast<uint32_t>(time / ZX_SEC(1));
boot_time->nseconds = static_cast<uint32_t>(time % ZX_SEC(1));
ktl::atomic_thread_fence(ktl::memory_order_seq_cst);
guest_version.store(version + 2, fbl::memory_order_relaxed);
version += 2;
return ZX_OK;
}
zx_status_t pv_clock_reset_clock(PvClockState* pv_clock,
hypervisor::GuestPhysicalAddressSpace* gpas,
zx_vaddr_t guest_paddr) {
zx_status_t status =
gpas->CreateGuestPtr(guest_paddr, sizeof(pv_clock_system_time),
"pv_clock-system-time-guest-mapping", &pv_clock->guest_ptr);
if (status != ZX_OK) {
return status;
}
pv_clock->system_time = pv_clock->guest_ptr.as<pv_clock_system_time>();
ZX_DEBUG_ASSERT(pv_clock->system_time != nullptr);
memset(pv_clock->system_time, 0, sizeof(*pv_clock->system_time));
return ZX_OK;
}
void pv_clock_update_system_time(PvClockState* pv_clock,
hypervisor::GuestPhysicalAddressSpace* gpas) {
if (!pv_clock->system_time) {
return;
}
uint32_t tsc_mul;
int8_t tsc_shift;
calculate_scale_factor(ticks_per_second(), &tsc_mul, &tsc_shift);
// See the comment for pv_clock_boot_time structure in arch/x86/pv.h
pv_clock_system_time* system_time = pv_clock->system_time;
fbl::atomic_ref<uint32_t> guest_version(system_time->version);
guest_version.store(pv_clock->version + 1, fbl::memory_order_relaxed);
ktl::atomic_thread_fence(ktl::memory_order_seq_cst);
system_time->tsc_mul = tsc_mul;
system_time->tsc_shift = tsc_shift;
system_time->system_time = current_time();
system_time->tsc_timestamp = _rdtsc();
system_time->flags = pv_clock->is_stable ? kKvmSystemTimeStable : 0;
ktl::atomic_thread_fence(ktl::memory_order_seq_cst);
guest_version.store(pv_clock->version + 2, fbl::memory_order_relaxed);
pv_clock->version += 2;
}
void pv_clock_stop_clock(PvClockState* pv_clock) {
pv_clock->system_time = nullptr;
pv_clock->guest_ptr.reset();
}
zx_status_t pv_clock_populate_offset(hypervisor::GuestPhysicalAddressSpace* gpas,
zx_vaddr_t guest_paddr) {
hypervisor::GuestPtr guest_ptr;
zx_status_t status = gpas->CreateGuestPtr(guest_paddr, sizeof(PvClockOffset),
"pv_clock-offset-guest-mapping", &guest_ptr);
if (status != ZX_OK) {
return status;
}
auto offset = guest_ptr.as<PvClockOffset>();
ZX_DEBUG_ASSERT(offset != nullptr);
memset(offset, 0, sizeof(*offset));
zx_time_t time = utc_offset.load() + current_time();
uint64_t tsc = _rdtsc();
offset->sec = time / ZX_SEC(1);
offset->nsec = time % ZX_SEC(1);
offset->tsc = tsc;
return ZX_OK;
}