/*
 * Copyright (c) 2013 Andreas Sandberg
 * All rights reserved
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Andreas Sandberg
 */

#ifndef __CPU_KVM_X86_CPU_HH__
#define __CPU_KVM_X86_CPU_HH__

#include "cpu/kvm/base.hh"
#include "cpu/kvm/vm.hh"
#include "params/X86KvmCPU.hh"

/**
 * x86 implementation of a KVM-based hardware virtualized CPU.
 */
class X86KvmCPU : public BaseKvmCPU
{
  public:
    X86KvmCPU(X86KvmCPUParams *params);
    virtual ~X86KvmCPU();

    void startup() override;

    /** @{ */
    void dump() const override;
    void dumpFpuRegs() const;
    void dumpIntRegs() const;
    void dumpSpecRegs() const;
    void dumpDebugRegs() const;
    void dumpXCRs() const;
    void dumpXSave() const;
    void dumpVCpuEvents() const;
    void dumpMSRs() const;
    /** @} */

  protected:
    typedef std::vector<struct kvm_msr_entry> KvmMSRVector;

    Tick kvmRun(Tick ticks) override;

    /**
     * Run the virtual CPU until draining completes.
     *
     * In addition to the base functionality provided by
     * BaseKvmCPU::kvmRunDrain(), this method handles x86-specific
     * cases where there are pending interrupt events in the virtual
     * CPU.  These are handled by requesting an interrupt window if
     * interrupts are pending (causing the vCPU to execute until
     * interrupts can be delivered again).
     *
     * @see BaseKvmCPU::kvmRunDrain()
     * @see archIsDrained()
     *
     * @return Number of ticks executed
     */
    Tick kvmRunDrain() override;

    /** Wrapper that synchronizes state in kvm_run */
    Tick kvmRunWrapper(Tick ticks);

    uint64_t getHostCycles() const override;

    /**
     * Methods to access CPUID information using the extended
     * API. Only available if Kvm::capExtendedCPUID() is true.
     *
     * @{
     */
    void setCPUID(const struct kvm_cpuid2 &cpuid);
    void setCPUID(const Kvm::CPUIDVector &cpuid);
    /** @} */

    /**
     * Methods to access MSRs in the guest.
     *
     * @{
     */
    void setMSRs(const struct kvm_msrs &msrs);
    void setMSRs(const KvmMSRVector &msrs);
    void getMSRs(struct kvm_msrs &msrs) const;
    void setMSR(uint32_t index, uint64_t value);
    uint64_t getMSR(uint32_t index) const;
    /** @} */

    /**
     * Get a list of MSRs supported by both gem5 and KVM.
     *
     * @note This method uses an internal cache and only generates the
     * MSR list once.
     *
     * @return reference to a list of msr indices
     */
    const Kvm::MSRIndexVector &getMsrIntersection() const;

    /**
     * Wrappers around KVM's state transfer methods.
     *
     * @{
     */
    void getDebugRegisters(struct kvm_debugregs &regs) const;
    void setDebugRegisters(const struct kvm_debugregs &regs);
    void getXCRs(struct kvm_xcrs &regs) const;
    void setXCRs(const struct kvm_xcrs &regs);
    void getXSave(struct kvm_xsave &xsave) const;
    void setXSave(const struct kvm_xsave &xsave);
    void getVCpuEvents(struct kvm_vcpu_events &events) const;
    void setVCpuEvents(const struct kvm_vcpu_events &events);
    /** @} */

    void updateKvmState() override;
    void updateThreadContext() override;

    /**
     * Inject pending interrupts from gem5 into the virtual CPU.
     */
    void deliverInterrupts();

    /**
     * Handle x86 legacy IO (in/out)
     */
    Tick handleKvmExitIO() override;

    Tick handleKvmExitIRQWindowOpen() override;

    /**
     * Check if there are pending events in the vCPU that prevents it
     * from being drained.
     *
     * There are cases after interrupt injection where the interrupt
     * is still pending in the guest. This method detects such cases
     * and requests additional draining.
     *
     * @return False if there are pending events in the guest, True
     * otherwise.
     */
    bool archIsDrained() const override;

  private:
    /**
     * Support routines to update the state of the KVM CPU from gem5's
     * state representation.
     *
     * @{
     */
    /** Update integer registers */
    void updateKvmStateRegs();
    /** Update control registers (CRx, segments, etc.) */
    void updateKvmStateSRegs();
    /**
     * Update FPU and SIMD registers
     *
     * This method uses the appropriate (depending on availability and
     * user configuration) kernel API by calling
     * updateKvmStateFPULegacy() or updateKvmStateFPUXSave().
     *
     * @see updateKvmStateFPULegacy()
     * @see updateKvmStateFPUXSave()
     */
    void updateKvmStateFPU();
    /**
     * Update FPU and SIMD registers using the legacy API
     *
     * @note This method should normally only be called by
     * updateKvmStateFPU() which automatically chooses between
     * available APIs.
     */
    void updateKvmStateFPULegacy();
    /**
     * Update FPU and SIMD registers using the XSave API
     *
     * @note This method should normally only be called by
     * updateKvmStateFPU() which automatically chooses between
     * available APIs.
     */
    void updateKvmStateFPUXSave();
    /** Update MSR registers */
    void updateKvmStateMSRs();
    /** @} */

    /**
     * Support routines to update the state of gem5's thread context from
     * KVM's state representation.
     *
     * @{
     */
    /** Update integer registers */
    void updateThreadContextRegs(const struct kvm_regs &regs,
                                 const struct kvm_sregs &sregs);
    /** Update control registers (CRx, segments, etc.) */
    void updateThreadContextSRegs(const struct kvm_sregs &sregs);
    /** Update FPU and SIMD registers using the legacy API */
    void updateThreadContextFPU(const struct kvm_fpu &fpu);
    /** Update FPU and SIMD registers using the XSave API */
    void updateThreadContextXSave(const struct kvm_xsave &kxsave);
    /** Update MSR registers */
    void updateThreadContextMSRs();
    /** @} */

    /** Transfer gem5's CPUID values into the virtual CPU. */
    void updateCPUID();

    /**
     * Handle a 32-bit IO access that should be mapped to a MiscReg.
     *
     * @note This method can only be called on when handling IO after
     * a KVM_EXIT_IO.
     *
     * @param miscreg Register to map the current IO access to.
     */
    void handleIOMiscReg32(int miscreg);

    /** Cached intersection of supported MSRs */
    mutable Kvm::MSRIndexVector cachedMsrIntersection;

    /** @{ */
    /** Kvm::capDebugRegs() available? */
    bool haveDebugRegs;
    /** Kvm::capXSave() available? */
    bool haveXSave;
    /**
     * Should the XSave interface be used to sync the FPU and SIMD
     * registers?
     */
    bool useXSave;
    /** Kvm::capXCRs() available? */
    bool haveXCRs;
    /** @} */
};

#endif
