Skip to content
  • James Hogan's avatar
    MIPS: KVM: Rewrite count/compare timer emulation · e30492bb
    James Hogan authored
    
    
    Previously the emulation of the CPU timer was just enough to get a Linux
    guest running but some shortcuts were taken:
     - The guest timer interrupt was hard coded to always happen every 10 ms
       rather than being timed to when CP0_Count would match CP0_Compare.
     - The guest's CP0_Count register was based on the host's CP0_Count
       register. This isn't very portable and fails on cores without a
       CP_Count register implemented such as Ingenic XBurst. It also meant
       that the guest's CP0_Cause.DC bit to disable the CP0_Count register
       took no effect.
     - The guest's CP0_Count register was emulated by just dividing the
       host's CP0_Count register by 4. This resulted in continuity problems
       when used as a clock source, since when the host CP0_Count overflows
       from 0x7fffffff to 0x80000000, the guest CP0_Count transitions
       discontinuously from 0x1fffffff to 0xe0000000.
    
    Therefore rewrite & fix emulation of the guest timer based on the
    monotonic kernel time (i.e. ktime_get()). Internally a 32-bit count_bias
    value is added to the frequency scaled nanosecond monotonic time to get
    the guest's CP0_Count. The frequency of the timer is initialised to
    100MHz and cannot yet be changed, but a later patch will allow the
    frequency to be configured via the KVM_{GET,SET}_ONE_REG ioctl
    interface.
    
    The timer can now be stopped via the CP0_Cause.DC bit (by the guest or
    via the KVM_SET_ONE_REG ioctl interface), at which point the current
    CP0_Count is stored and can be read directly. When it is restarted the
    bias is recalculated such that the CP0_Count value is continuous.
    
    Due to the nature of hrtimer interrupts any read of the guest's
    CP0_Count register while it is running triggers a check for whether the
    hrtimer has expired, so that the guest/userland cannot observe the
    CP0_Count passing CP0_Compare without queuing a timer interrupt. This is
    also taken advantage of when stopping the timer to ensure that a pending
    timer interrupt is queued.
    
    This replaces the implementation of:
     - Guest read of CP0_Count
     - Guest write of CP0_Count
     - Guest write of CP0_Compare
     - Guest write of CP0_Cause
     - Guest read of HWR 2 (CC) with RDHWR
     - Host read of CP0_Count via KVM_GET_ONE_REG ioctl interface
     - Host write of CP0_Count via KVM_SET_ONE_REG ioctl interface
     - Host write of CP0_Compare via KVM_SET_ONE_REG ioctl interface
     - Host write of CP0_Cause via KVM_SET_ONE_REG ioctl interface
    
    Signed-off-by: default avatarJames Hogan <james.hogan@imgtec.com>
    Cc: Paolo Bonzini <pbonzini@redhat.com>
    Cc: Gleb Natapov <gleb@kernel.org>
    Cc: kvm@vger.kernel.org
    Cc: Ralf Baechle <ralf@linux-mips.org>
    Cc: linux-mips@linux-mips.org
    Cc: Sanjay Lal <sanjayl@kymasys.com>
    Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
    e30492bb