blob: 54795214707d8d3725ea456b634caaa28a21522e [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 <hypervisor/guest_physical_address_space.h>
#include <ktl/atomic.h>
#include <vm/physmap.h>
#include "pv_priv.h"
#include <ktl/enforce.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
zx::status<> pv_clock_update_boot_time(hypervisor::GuestPhysicalAddressSpace* gpas,
zx_vaddr_t guest_paddr) {
// Zircon does not maintain a UTC or local time to set a meaningful boot time
// hence the value is fixed at zero.
auto guest_ptr = gpas->CreateGuestPtr(guest_paddr, sizeof(pv_clock_boot_time),
"pv_clock-boot-time-guest-mapping");
if (guest_ptr.is_error()) {
return guest_ptr.take_error();
}
auto boot_time = guest_ptr->as<pv_clock_boot_time>();
if (boot_time == nullptr) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
memset(boot_time, 0, sizeof(*boot_time));
return zx::ok();
}
zx::status<> pv_clock_reset_clock(PvClockState* pv_clock,
hypervisor::GuestPhysicalAddressSpace* gpas,
zx_vaddr_t guest_paddr) {
auto guest_ptr = gpas->CreateGuestPtr(guest_paddr, sizeof(pv_clock_system_time),
"pv_clock-system-time-guest-mapping");
if (guest_ptr.is_error()) {
return guest_ptr.take_error();
}
pv_clock->system_time = guest_ptr->as<pv_clock_system_time>();
if (pv_clock->system_time == nullptr) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
memset(pv_clock->system_time, 0, sizeof(*pv_clock->system_time));
pv_clock->guest_ptr = ktl::move(*guest_ptr);
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;
ktl::atomic_ref<uint32_t> guest_version(system_time->version);
guest_version.store(pv_clock->version + 1, ktl::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, ktl::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<> pv_clock_populate_offset(hypervisor::GuestPhysicalAddressSpace* gpas,
zx_vaddr_t guest_paddr) {
auto guest_ptr =
gpas->CreateGuestPtr(guest_paddr, sizeof(PvClockOffset), "pv_clock-offset-guest-mapping");
if (guest_ptr.is_error()) {
return guest_ptr.take_error();
}
auto offset = guest_ptr->as<PvClockOffset>();
if (offset == nullptr) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
memset(offset, 0, sizeof(*offset));
// Zircon does not maintain a UTC or local time. We populate offset using the
// only time available - time since the device was powered on.
zx_time_t time = current_time();
uint64_t tsc = _rdtsc();
offset->sec = time / ZX_SEC(1);
offset->nsec = time % ZX_SEC(1);
offset->tsc = tsc;
return zx::ok();
}