KVM_{GET,SET}_TSC_OFFSET ioctls
Subsystems (5)
v2 cover KVM_{GET,SET}_TSC_OFFSET ioctls
To date, VMMs have typically restored the guest's TSCs by value using the KVM_SET_MSRS ioctl for each vCPU. However, restoring the TSCs by value introduces some challenges with synchronization as the TSCs continue to tick throughout the restoration process. As such, KVM has some heuristics around TSC writes to infer whether or not the guest or host is attempting to synchronize the TSCs.
Instead of guessing at the intentions of a VMM, it'd be better to provide an interface that allows for explicit synchronization of the guest's TSCs. To that end, this series introduces the KVM_{GET,SET}_TSC_OFFSET ioctls, yielding control of the TSC offset to userspace.
v1 => v2:
- Added clarification to the documentation of KVM_SET_TSC_OFFSET to indicate that it can be used instead of an IA32_TSC MSR restore through KVM_SET_MSRS
- Fixed KVM_SET_TSC_OFFSET to participate in the existing TSC synchronization heuristics, thereby enabling the KVM masterclock when all vCPUs are in phase.
Oliver Upton (4):
kvm: x86: refactor masterclock sync heuristics out of kvm_write_tsc
kvm: vmx: check tsc offsetting with nested_cpu_has()
selftests: kvm: use a helper function for reading cpuid
selftests: kvm: introduce tsc_offset_test
Peter Hornyack (1):
kvm: x86: add KVM_{GET,SET}_TSC_OFFSET ioctls
Documentation/virt/kvm/api.rst | 31 ++ arch/x86/include/asm/kvm_host.h | 1 + arch/x86/kvm/vmx/vmx.c | 2 +- arch/x86/kvm/x86.c | 147 ++++--- include/uapi/linux/kvm.h | 5 + tools/testing/selftests/kvm/.gitignore | 1 + tools/testing/selftests/kvm/Makefile | 1 + .../testing/selftests/kvm/include/test_util.h | 3 + .../selftests/kvm/include/x86_64/processor.h | 15 + .../selftests/kvm/include/x86_64/svm_util.h | 10 +- .../selftests/kvm/include/x86_64/vmx.h | 9 + tools/testing/selftests/kvm/lib/kvm_util.c | 1 + tools/testing/selftests/kvm/lib/x86_64/vmx.c | 11 + .../selftests/kvm/x86_64/tsc_offset_test.c | 362 ++++++++++++++++++ 14 files changed, 550 insertions(+), 49 deletions(-) create mode 100644 tools/testing/selftests/kvm/x86_64/tsc_offset_test.c
Signature · 1 line
2.28.0.rc0.142.g3c755180ce-goog
PATCH v2 1/5 kvm: x86: refactor masterclock sync heuristics out of kvm_write_tsc
kvm_write_tsc() determines when the host or guest is attempting to synchronize the TSCs and does some bookkeeping for tracking this. Retain the existing sync checks (host is writing 0 or TSCs being written within a second of one another) in kvm_write_tsc(), but split off the bookkeeping and TSC offset write into a new function. This allows for a new ioctl to be introduced, yielding control of the TSC offset field to userspace in a manner that respects the existing masterclock heuristics.
--- arch/x86/kvm/x86.c | 98 +++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 44 deletions(-)
arch/x86/kvm/x86.c +54 -44
@@ -2045,13 +2045,64 @@ static inline bool kvm_check_tsc_unstable(void)return check_tsc_unstable (); } +/* +* Sets the TSC offset for the vCPU and tracks the TSC matching generation. Must +* hold the kvm->arch.tsc_write_lock to call this function. +*/ +void kvm_write_tsc_offset (struct kvm_vcpu *vcpu,u64 offset,u64 tsc,u64 ns, +bool matched) +{ +struct kvm *kvm = vcpu->kvm; +bool already_matched; + + already_matched = (vcpu->arch.this_tsc_generation == kvm->arch.cur_tsc_generation); +/* +* We also track the most recent recorded KHZ, write and time to +* allow the matching interval to be extended at each write. +*/ + kvm->arch.last_tsc_nsec = ns; + kvm->arch.last_tsc_write = tsc; + kvm->arch.last_tsc_khz = vcpu->arch.virtual_tsc_khz; + + vcpu->arch.last_guest_tsc = tsc; + +/* Keep track of which generation this VCPU has synchronized to */ + vcpu->arch.this_tsc_generation = kvm->arch.cur_tsc_generation; + vcpu->arch.this_tsc_nsec = kvm->arch.cur_tsc_nsec; + vcpu->arch.this_tsc_write = kvm->arch.cur_tsc_write; + +kvm_vcpu_write_tsc_offset (vcpu, offset); + +spin_lock (&kvm->arch.pvclock_gtod_sync_lock); +if (!matched) { +/* +* We split periods of matched TSC writes into generations. +* For each generation, we track the original measured +* nanosecond time, offset, and write, so if TSCs are in +* sync, we can match exact offset, and if not, we can match +* exact software computation in compute_guest_tsc() +* +* These values are tracked in kvm->arch.cur_xxx variables. +*/ + kvm->arch.nr_vcpus_matched_tsc =0 ; + kvm->arch.cur_tsc_generation++; + kvm->arch.cur_tsc_nsec = ns; + kvm->arch.cur_tsc_write = tsc; + kvm->arch.cur_tsc_offset = offset; + }else if (!already_matched) { + kvm->arch.nr_vcpus_matched_tsc++; + } + +kvm_track_tsc_matching (vcpu); +spin_unlock (&kvm->arch.pvclock_gtod_sync_lock); +} +void kvm_write_tsc (struct kvm_vcpu *vcpu,struct msr_data *msr) {struct kvm *kvm = vcpu->kvm;u64 offset, ns, elapsed;unsigned long flags; -bool matched; -bool already_matched; +bool matched = false;u64 data = msr->data;bool synchronizing = false;
@@ -2098,54 +2149,13 @@ void kvm_write_tsc(struct kvm_vcpu *vcpu, struct msr_data *msr) offset =kvm_compute_tsc_offset (vcpu, data); } matched = true; - already_matched = (vcpu->arch.this_tsc_generation == kvm->arch.cur_tsc_generation); - }else { -/* -* We split periods of matched TSC writes into generations. -* For each generation, we track the original measured -* nanosecond time, offset, and write, so if TSCs are in -* sync, we can match exact offset, and if not, we can match -* exact software computation in compute_guest_tsc() -* -* These values are tracked in kvm->arch.cur_xxx variables. -*/ - kvm->arch.cur_tsc_generation++; - kvm->arch.cur_tsc_nsec = ns; - kvm->arch.cur_tsc_write = data; - kvm->arch.cur_tsc_offset = offset; - matched = false; } -/* -* We also track th most recent recorded KHZ, write and time to -* allow the matching interval to be extended at each write. -*/ - kvm->arch.last_tsc_nsec = ns; - kvm->arch.last_tsc_write = data; - kvm->arch.last_tsc_khz = vcpu->arch.virtual_tsc_khz; - - vcpu->arch.last_guest_tsc = data; - -/* Keep track of which generation this VCPU has synchronized to */ - vcpu->arch.this_tsc_generation = kvm->arch.cur_tsc_generation; - vcpu->arch.this_tsc_nsec = kvm->arch.cur_tsc_nsec; - vcpu->arch.this_tsc_write = kvm->arch.cur_tsc_write; -if (!msr->host_initiated &&guest_cpuid_has (vcpu,X86_FEATURE_TSC_ADJUST ))update_ia32_tsc_adjust_msr (vcpu, offset); -kvm_vcpu_write_tsc_offset (vcpu, offset); +kvm_write_tsc_offset (vcpu, offset, data, ns, matched);raw_spin_unlock_irqrestore (&kvm->arch.tsc_write_lock, flags); - -spin_lock (&kvm->arch.pvclock_gtod_sync_lock); -if (!matched) { - kvm->arch.nr_vcpus_matched_tsc =0 ; - }else if (!already_matched) { - kvm->arch.nr_vcpus_matched_tsc++; - } - -kvm_track_tsc_matching (vcpu); -spin_unlock (&kvm->arch.pvclock_gtod_sync_lock); }EXPORT_SYMBOL_GPL (kvm_write_tsc);
Signature · 1 line
2.28.0.rc0.142.g3c755180ce-goog
kernel test robot
build botRe: [PATCH v2 1/5] kvm: x86: refactor masterclock sync heuristics out of kvm_write_tsc
Thank you for the patch! Perhaps something to improve:
Hi Oliver,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on kvm/linux-next]
[also build test WARNING on vhost/linux-next v5.8-rc6 next-20200721] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]
url: https://github.com/0day-ci/linux/commits/Oliver-Upton/KVM_-GET-SET-_TSC_OFFSET-ioctls/20200722-042050 base: https://git.kernel.org/pub/scm/virt/kvm/kvm.git linux-next config: x86_64-allmodconfig (attached as .config) compiler: gcc-9 (Debian 9.3.0-14) 9.3.0 reproduce (this is a W=1 build): # save the attached .config to linux build tree make W=1 ARCH=x86_64
If you fix the issue, kindly add following tag as appropriate
All warnings (new ones prefixed by >>):
>> arch/x86/kvm/x86.c:2052:6: warning: no previous prototype for 'kvm_write_tsc_offset' [-Wmissing-prototypes]
2052 | void kvm_write_tsc_offset(struct kvm_vcpu *vcpu, u64 offset, u64 tsc, u64 ns,
| ^~~~~~~~~~~~~~~~~~~~
vim +/kvm_write_tsc_offset +2052 arch/x86/kvm/x86.c
2047
2048 /*
2049 * Sets the TSC offset for the vCPU and tracks the TSC matching generation. Must
2050 * hold the kvm->arch.tsc_write_lock to call this function.
2051 */
> 2052 void kvm_write_tsc_offset(struct kvm_vcpu *vcpu, u64 offset, u64 tsc, u64 ns,
2053 bool matched)
2054 {
2055 struct kvm *kvm = vcpu->kvm;
2056 bool already_matched;
2057
2058 already_matched = (vcpu->arch.this_tsc_generation == kvm->arch.cur_tsc_generation);
2059 /*
2060 * We also track the most recent recorded KHZ, write and time to
2061 * allow the matching interval to be extended at each write.
2062 */
2063 kvm->arch.last_tsc_nsec = ns;
2064 kvm->arch.last_tsc_write = tsc;
2065 kvm->arch.last_tsc_khz = vcpu->arch.virtual_tsc_khz;
2066
2067 vcpu->arch.last_guest_tsc = tsc;
2068
2069 /* Keep track of which generation this VCPU has synchronized to */
2070 vcpu->arch.this_tsc_generation = kvm->arch.cur_tsc_generation;
2071 vcpu->arch.this_tsc_nsec = kvm->arch.cur_tsc_nsec;
2072 vcpu->arch.this_tsc_write = kvm->arch.cur_tsc_write;
2073
2074 kvm_vcpu_write_tsc_offset(vcpu, offset);
2075
2076 spin_lock(&kvm->arch.pvclock_gtod_sync_lock);
2077 if (!matched) {
2078 /*
2079 * We split periods of matched TSC writes into generations.
2080 * For each generation, we track the original measured
2081 * nanosecond time, offset, and write, so if TSCs are in
2082 * sync, we can match exact offset, and if not, we can match
2083 * exact software computation in compute_guest_tsc()
2084 *
2085 * These values are tracked in kvm->arch.cur_xxx variables.
2086 */
2087 kvm->arch.nr_vcpus_matched_tsc = 0;
2088 kvm->arch.cur_tsc_generation++;
2089 kvm->arch.cur_tsc_nsec = ns;
2090 kvm->arch.cur_tsc_write = tsc;
2091 kvm->arch.cur_tsc_offset = offset;
2092 } else if (!already_matched) {
2093 kvm->arch.nr_vcpus_matched_tsc++;
2094 }
2095
2096 kvm_track_tsc_matching(vcpu);
2097 spin_unlock(&kvm->arch.pvclock_gtod_sync_lock);
2098 }
2099
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org
PATCH v2 2/5 kvm: x86: add KVM_{GET,SET}_TSC_OFFSET ioctls
In reply to Oliver UptonFrom: Peter Hornyack <[email protected]>
The KVM_SET_MSR vcpu ioctl has some temporal and value-based heuristics for determining when userspace is attempting to synchronize TSCs. Instead of guessing at userspace's intentions in the kernel, directly expose control of the TSC offset field to userspace such that userspace may deliberately synchronize the guest TSCs.
Note that TSC offset support is mandatory for KVM on both SVM and VMX.
--- Documentation/virt/kvm/api.rst | 31 +++++++++++++++++++++ arch/x86/include/asm/kvm_host.h | 1 + arch/x86/kvm/x86.c | 49 +++++++++++++++++++++++++++++++++ include/uapi/linux/kvm.h | 5 ++++ 4 files changed, 86 insertions(+)
Documentation/virt/kvm/api.rst +31 -0
@@ -4701,6 +4701,37 @@ KVM_PV_VM_VERIFY KVM is allowed to start protected VCPUs. +4.126 KVM_GET_TSC_OFFSET +------------------------ + +:Capability: KVM_CAP_TSC_OFFSET +:Architectures: x86 +:Type: vcpu ioctl +:Parameters: __u64 (out) +:Returns: 0 on success, < 0 on error + +This ioctl gets the TSC offset field. The offset is returned as an +unsigned value, though it is interpreted as a signed value by hardware. + + +4.127 KVM_SET_TSC_OFFSET +------------------------ + +:Capability: KVM_CAP_TSC_OFFSET +:Architectures: x86 +:Type: vcpu ioctl +:Parameters: __u64 (in) +:Returns: 0 on success, < 0 on error + +This ioctl sets the TSC offset field. The offset is represented as an +unsigned value, though it is interpreted as a signed value by hardware. +The guest's TSC value will change based on the written offset. A +userspace VMM may use this ioctl to restore the guest's TSC instead of +KVM_SET_MSRS, thereby avoiding the in-kernel TSC synchronization +detection. To that end, a userspace VMM may deliberately synchronize +TSCs by setting the same offset value for all vCPUs. + + 5. The kvm_run structure ========================
arch/x86/include/asm/kvm_host.h +1 -0
@@ -920,6 +920,7 @@ struct kvm_arch {u64 last_tsc_nsec;u64 last_tsc_write;u32 last_tsc_khz; +u64 last_tsc_offset;u64 cur_tsc_nsec;u64 cur_tsc_write;u64 cur_tsc_offset;
arch/x86/kvm/x86.c +49 -0
79 lines not on this page · raw message
include/uapi/linux/kvm.h +5 -0
@@ -1033,6 +1033,7 @@ struct kvm_ppc_resize_hpt {#define KVM_CAP_HALT_POLL 182#define KVM_CAP_ASYNC_PF_INT 183#define KVM_CAP_LAST_CPU 184 +#define KVM_CAP_TSC_OFFSET 185#ifdef KVM_CAP_IRQ_ROUTING
@@ -1501,6 +1502,10 @@ struct kvm_enc_region {#define KVM_S390_NORMAL_RESET _IO(KVMIO, 0xc3)#define KVM_S390_CLEAR_RESET _IO(KVMIO, 0xc4) +/* Available with KVM_CAP_TSC_OFFSET */ +#define KVM_GET_TSC_OFFSET _IOR(KVMIO, 0xc5, __u64) +#define KVM_SET_TSC_OFFSET _IOW(KVMIO, 0xc6, __u64) +struct kvm_s390_pv_sec_parm {__u64 origin;__u64 length;
Signature · 1 line
2.28.0.rc0.142.g3c755180ce-goog
PATCH v2 3/5 kvm: vmx: check tsc offsetting with nested_cpu_has()
In reply to Oliver UptonNo functional change intended.
--- arch/x86/kvm/vmx/vmx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/x86/kvm/vmx/vmx.c +1 -1
@@ -1775,7 +1775,7 @@ static u64 vmx_write_l1_tsc_offset(struct kvm_vcpu *vcpu, u64 offset)* to the newly set TSC to get L2's TSC. */ if (is_guest_mode (vcpu) && - (vmcs12->cpu_based_vm_exec_control &CPU_BASED_USE_TSC_OFFSETTING )) +nested_cpu_has (vmcs12,CPU_BASED_USE_TSC_OFFSETTING )) g_tsc_offset = vmcs12->tsc_offset; trace_kvm_write_tsc_offset(vcpu->vcpu_id,
Signature · 1 line
2.28.0.rc0.142.g3c755180ce-goog
PATCH v2 4/5 selftests: kvm: use a helper function for reading cpuid
In reply to Oliver UptonDerived from kvm-unit-tests.
--- .../selftests/kvm/include/x86_64/processor.h | 15 +++++++++++++++ .../selftests/kvm/include/x86_64/svm_util.h | 7 ++----- 2 files changed, 17 insertions(+), 5 deletions(-)
tools/testing/selftests/kvm/include/x86_64/processor.h +15 -0
@@ -338,6 +338,21 @@ uint32_t kvm_get_cpuid_max_basic(void);uint32_t kvm_get_cpuid_max_extended (void );void kvm_get_cpu_address_width (unsigned int *pa_bits,unsigned int *va_bits); +struct cpuid { +u32 a, b, c, d; +}; + +static inline struct cpuid raw_cpuid (u32 function,u32 index) +{ +struct cpuid r; + + asmvolatile ("cpuid" + :"=a" (r.a),"=b" (r.b),"=c" (r.c),"=d" (r.d) + :"0" (function),"2" (index)); + +return r; +} +/* * Basic CPU control in CR0 */
tools/testing/selftests/kvm/include/x86_64/svm_util.h +2 -5
@@ -38,12 +38,9 @@ void nested_svm_check_supported(void);static inline bool cpu_has_svm (void ) { -u32 eax =0x80000001 , ecx; +struct cpuid r =raw_cpuid (0x80000001 ,0 ); - asm("cpuid" : -"=a" (eax),"=c" (ecx) :"0" (eax) :"ebx" ,"edx" ); - -return ecx &CPUID_SVM ; +return r.c &CPUID_SVM ; }#endif /* SELFTEST_KVM_SVM_UTILS_H */
Signature · 1 line
2.28.0.rc0.142.g3c755180ce-goog
PATCH v2 5/5 selftests: kvm: introduce tsc_offset_test
In reply to Oliver UptonTest the KVM_{GET,SET}_TSC_OFFSET ioctls. Ensure the following:
- KVM_GET_TSC_OFFSET returns the value written by KVM_SET_TSC_OFFSET
- The L1 TSC is appropriately adjusted based on the written value
- Guest manipulation of the TSC results in a changed offset in KVM_GET_TSC_OFFSET
- Modifying the TSC offset while in guest mode affects L2 guest
- Modification in guest mode is also reflected in L1
--- tools/testing/selftests/kvm/.gitignore | 1 + tools/testing/selftests/kvm/Makefile | 1 + .../testing/selftests/kvm/include/test_util.h | 3 + .../selftests/kvm/include/x86_64/svm_util.h | 5 + .../selftests/kvm/include/x86_64/vmx.h | 9 + tools/testing/selftests/kvm/lib/kvm_util.c | 1 + tools/testing/selftests/kvm/lib/x86_64/vmx.c | 11 + .../selftests/kvm/x86_64/tsc_offset_test.c | 362 ++++++++++++++++++ 8 files changed, 393 insertions(+) create mode 100644 tools/testing/selftests/kvm/x86_64/tsc_offset_test.c
tools/testing/selftests/kvm/.gitignore +1 -0
@@ -11,6 +11,7 @@ /x86_64/set_sregs_test /x86_64/smm_test /x86_64/state_test +/x86_64/tsc_offset_test /x86_64/vmx_preemption_timer_test /x86_64/svm_vmcall_test /x86_64/sync_regs_test
tools/testing/selftests/kvm/Makefile +1 -0
@@ -55,6 +55,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/vmx_set_nested_state_test TEST_GEN_PROGS_x86_64 += x86_64/vmx_tsc_adjust_test TEST_GEN_PROGS_x86_64 += x86_64/xss_msr_test TEST_GEN_PROGS_x86_64 += x86_64/debug_regs +TEST_GEN_PROGS_x86_64 += x86_64/tsc_offset_test TEST_GEN_PROGS_x86_64 += clear_dirty_log_test TEST_GEN_PROGS_x86_64 += demand_paging_test TEST_GEN_PROGS_x86_64 += dirty_log_test
tools/testing/selftests/kvm/include/test_util.h +3 -0
@@ -65,4 +65,7 @@ struct timespec timespec_add_ns(struct timespec ts, int64_t ns);struct timespec timespec_add (struct timespec ts1,struct timespec ts2);struct timespec timespec_sub (struct timespec ts1,struct timespec ts2); +#define swap (a, b) \ + do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0) +#endif /* SELFTEST_KVM_TEST_UTIL_H */
tools/testing/selftests/kvm/include/x86_64/svm_util.h +5 -0
@@ -43,4 +43,9 @@ static inline bool cpu_has_svm(void) return r.c & CPUID_SVM; } +static inline void vmmcall (void ) +{ + __asm__ __volatile__("vmmcall" ); +} +#endif /* SELFTEST_KVM_SVM_UTILS_H */
tools/testing/selftests/kvm/include/x86_64/vmx.h +9 -0
@@ -602,6 +602,8 @@ struct vmx_pages *vcpu_alloc_vmx(struct kvm_vm *vm, vm_vaddr_t *p_vmx_gva);bool prepare_for_vmx_operation (struct vmx_pages *vmx);void prepare_vmcs (struct vmx_pages *vmx,void *guest_rip,void *guest_rsp);bool load_vmcs (struct vmx_pages *vmx); +void generic_vmx_setup (struct vmx_pages *vmx,void *guest_rip, +void *guest_rsp);bool nested_vmx_supported (void );void nested_vmx_check_supported (void );
@@ -616,4 +618,11 @@ void nested_map_memslot(struct vmx_pages *vmx, struct kvm_vm *vm,void prepare_eptp (struct vmx_pages *vmx,struct kvm_vm *vm,uint32_t eptp_memslot); +static inline bool cpu_has_vmx (void ) +{ +struct cpuid r =raw_cpuid (1 ,0 ); + +return r.c &CPUID_VMX ; +} +#endif /* SELFTEST_KVM_VMX_H */
tools/testing/selftests/kvm/lib/kvm_util.c +1 -0
@@ -23,6 +23,7 @@static void *align (void *x,size_t size) {size_t mask = size -1 ; +TEST_ASSERT (size !=0 && !(size & (size -1 )),"size not a power of 2: %lu" , size);return (void *) (((size_t ) x + mask) & ~mask);
tools/testing/selftests/kvm/lib/x86_64/vmx.c +11 -0
@@ -542,3 +542,14 @@ void prepare_eptp(struct vmx_pages *vmx, struct kvm_vm *vm, vmx->eptp_hva =addr_gva2hva (vm, (uintptr_t )vmx->eptp); vmx->eptp_gpa =addr_gva2gpa (vm, (uintptr_t )vmx->eptp); } + +void generic_vmx_setup (struct vmx_pages *vmx_pages,void *guest_rip, +void *guest_rsp) +{ +GUEST_ASSERT (vmx_pages && vmx_pages->vmcs_gpa); +GUEST_ASSERT (prepare_for_vmx_operation (vmx_pages)); +GUEST_ASSERT (load_vmcs (vmx_pages)); +GUEST_ASSERT (vmptrstz () == vmx_pages->vmcs_gpa); +prepare_vmcs (vmx_pages, guest_rip, guest_rsp); +} +
tools/testing/selftests/kvm/x86_64/tsc_offset_test.c new file +362 -0
362 lines not on this page · raw message
Signature · 1 line
2.28.0.rc0.142.g3c755180ce-goog