| // 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 "pvclock_priv.h" |
| #include <arch/hypervisor.h> |
| #include <arch/x86/pvclock.h> |
| #include <bits.h> |
| #include <hypervisor/guest_physical_address_space.h> |
| #include <platform.h> |
| #include <vm/physmap.h> |
| #include <zircon/types.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 fbl::atomic<int64_t> utc_offset; |
| |
| zx_status_t pvclock_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 fbl::Mutex mutex; |
| static uint32_t version __TA_GUARDED(mutex); |
| |
| hypervisor::GuestPtr guest_ptr; |
| zx_status_t status = gpas->CreateGuestPtr(guest_paddr, sizeof(pvclock_boot_time), |
| "pvclock-boot-time-guest-mapping", &guest_ptr); |
| if (status != ZX_OK) { |
| return status; |
| } |
| auto boot_time = guest_ptr.as<pvclock_boot_time>(); |
| ZX_DEBUG_ASSERT(boot_time != nullptr); |
| memset(boot_time, 0, sizeof(*boot_time)); |
| |
| fbl::AutoLock lock(&mutex); |
| zx_time_t time = utc_offset.load(); |
| // See the comment for pvclock_boot_time structure in arch/x86/pvclock.h |
| atomic_store_relaxed_u32(&boot_time->version, version + 1); |
| atomic_fence(); |
| boot_time->seconds = static_cast<uint32_t>(time / ZX_SEC(1)); |
| boot_time->nseconds = static_cast<uint32_t>(time % ZX_SEC(1)); |
| atomic_fence(); |
| atomic_store_relaxed_u32(&boot_time->version, version + 2); |
| version += 2; |
| return ZX_OK; |
| } |
| |
| zx_status_t pvclock_reset_clock(PvClockState* pvclock, hypervisor::GuestPhysicalAddressSpace* gpas, |
| zx_vaddr_t guest_paddr) { |
| zx_status_t status = |
| gpas->CreateGuestPtr(guest_paddr, sizeof(pvclock_system_time), |
| "pvclock-system-time-guest-mapping", &pvclock->guest_ptr); |
| if (status != ZX_OK) { |
| return status; |
| } |
| pvclock->system_time = pvclock->guest_ptr.as<pvclock_system_time>(); |
| ZX_DEBUG_ASSERT(pvclock->system_time != nullptr); |
| memset(pvclock->system_time, 0, sizeof(*pvclock->system_time)); |
| return ZX_OK; |
| } |
| |
| void pvclock_update_system_time(PvClockState* pvclock, |
| hypervisor::GuestPhysicalAddressSpace* gpas) { |
| if (!pvclock->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 pvclock_boot_time structure in arch/x86/pvclock.h |
| pvclock_system_time* system_time = pvclock->system_time; |
| atomic_store_relaxed_u32(&system_time->version, pvclock->version + 1); |
| atomic_fence(); |
| 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 = pvclock->is_stable ? kKvmSystemTimeStable : 0; |
| atomic_fence(); |
| atomic_store_relaxed_u32(&system_time->version, pvclock->version + 2); |
| pvclock->version += 2; |
| } |
| |
| void pvclock_stop_clock(PvClockState* pvclock) { |
| pvclock->system_time = nullptr; |
| pvclock->guest_ptr.reset(); |
| } |
| |
| zx_status_t pvclock_populate_offset(hypervisor::GuestPhysicalAddressSpace* gpas, |
| zx_vaddr_t guest_paddr) { |
| hypervisor::GuestPtr guest_ptr; |
| zx_status_t status = |
| gpas->CreateGuestPtr(guest_paddr, sizeof(PvClockOffset), |
| "pvclock-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; |
| } |