Re: TSC in nested SVM and VMX

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 09/30/2010 12:50 PM, Nadav Har'El wrote:
Hi,

I noticed that the TSC handling code has recently changed, and since it
wasn't done correctly in the nested VMX patch, I wanted to take the opportunity
to fix it.

I looked at what nested SVM does about TSC, and most of it I think I
understand, but a couple of other things I don't understand.

The basic point is that when L1 starts L2 with some vmcs12.tsc_offset
(nested_vmcb->control.tsc_offset in SVM nomenclature), the TSC that L1
actually thinks it is offsetting from is already the hardware TSC plus
the vmcs01.tsc_offset. So when L0 runs L2, it needs to use the offset
vmcs01.tsc_offset + vmcs12.tsc_offset.

This explains the line
         svm->vmcb->control.tsc_offset += nested_vmcb->control.tsc_offset;
in nested_svm_vmrun().

In svm_adjust_tsc_offset(), when the hardware TSC changes (e.g., when moving
cores?) and the TSC offset is changed to keep the guest TSC unchanged,
when in nested mode we need to update both the currently running L2's tsc
offset, but also also L1's when we eventually return to it, which explains
the code in that function.

But there are two things I don't understand:

1. in svm_get_msr(), MSR_IA32_TSC, there is a special is_nested() case which
    basically ignores the above mentioned addition and uses just the L0->L1
    tsc offset. why? Why isn't svm->vmcb->control.tsc_offset + native_read_tsc()
    the correct thing in both cases?

2. In svm_write_tsc_offset(), when in a nested guest, we don't write the offset
    given to us, but (if I understand correctly) set this offset for the *L1*
    guest (and set the L2's tsc offset accordingly, adding to it vmcs12's
    tsc offset). Why was this done? Why was the simple code
	  svm->vmcb->control.tsc_offset = offset;
    and that's it, not the right thing to do in this function?

The offset for the L1 guest is copied out to a temporary structure, nested.hsave; this hold all L1 state information while the L2 guest is running. The L2 guest state is copied directly into the L1 control block, and runs IN PLACE in that structure. Upon nested vmexit, notice that the tsc_offset is NOT subtracted, rather, the original offset from nested.hsave is copied back into place.

Now, this answers both of your questions.

1) When reading an MSR, we are not emulating the L2 guest; we are DIRECTLY reading the MSR for the L1 emulation. Any emulation of the L2 guest is actually done by the code running /inside/ the L1 emulation, so MSR reads for the L2 guest are handed by L1, and MSR reads for the L1 guest are handled by L0, which is this code.

So if we are currently running nested, the L1 tsc_offset is stored in the nested.hsave field; the vmcb which is active is polluted by the L2 guest offset, which would be incorrect to return to the L1 emulation.

2) When writing an MSR, we are also writing directly for the L1 emulation. If we are currently nested, we want the new tsc_offset to be copied back into place from nested.hsave when the L2 guest triggers a vmexit. So if nested, we must write the offset for L1 emulation into nested.hsave. We must also apply the delta which was created by applying this new L1 offset to L2, as we presume, the offset is being used to correct for hardware variations. This is why we compute g_tsc_offset and add it to the active L2 offset (which is running live, directly in the original vmcb).

Now, when we exit and re-enter L2, observe:

delta = L2 offset - L1 offset = svm->vmcb->control.tsc_offset - svm->nested.hsave->control.tsc_offset

now emulation changes the underlying L1 offset to L1':

L1 offset = L1'
active L2 offset += L1' - L1  (*)

Note vmcb is unaffected - vmexit does NOT preserve tsc_offset for the L2 guest.
However, next time vmrun for L2 is attempted, we compute:

active L2 offset = L1' + L2 offset = (L1 + L2 offset) + (L1' - L1) = (*)

so equivalence is preserved.


It took me many hours of staring and scratching my head to figure out that code. I was convinced many times that is was wrong or buggy, until I realized how it actually works.

It gets more complicated when you realize the L1 guest can also be manipulating TSC offset for the L2 guest, inside of the vmcb that we are running in place. However, that is beyond the scope of our work; bugs in the L1 guest stay in the L1 guest, and it will do its own MSR read / write and tsc_offset emulation beyond our visibility.

We really should write this down in a document somewhere. Avi, shall I add this to the timekeeping docs? (And I hope I got all the L1 and L2s correct in the above - apologies if I made it even more confusing by misstating something).

Zach
--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux