/*
 * Copyright 2014 Google, Inc.
 * Copyright (c) 2012, 2015 ARM Limited
 * All rights reserved
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * 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_KVMVM_HH__
#define __CPU_KVM_KVMVM_HH__

#include <vector>

#include "base/addr_range.hh"
#include "sim/sim_object.hh"

// forward declarations
struct KvmVMParams;
class System;

/**
 * @defgroup KvmInterrupts KVM Interrupt handling.
 *
 * These methods control interrupt delivery to the guest system.
 */

/**
 * @defgroup KvmIoctl KVM low-level ioctl interface.
 *
 * These methods provide a low-level interface to the underlying KVM
 * layer.
 */

/**
 * KVM parent interface
 *
 * The main Kvm object is used to provide functionality that is not
 * specific to a VM or CPU. For example, it allows checking of the
 * optional features and creation of VM containers.
 */
class Kvm
{
    friend class KvmVM;

  public:
    virtual ~Kvm();

    Kvm *create();

    /** Get the version of the KVM API implemented by the kernel. */
    int getAPIVersion() const { return apiVersion; }
    /**
     * Get the size of the MMAPed parameter area used to communicate
     * vCPU parameters between the kernel and userspace. This area,
     * amongst other things, contains the kvm_run data structure.
     */
    int getVCPUMMapSize() const { return vcpuMMapSize; }

    /** @{ */
    /** Support for KvmVM::setUserMemoryRegion() */
    bool capUserMemory() const;
    /** Support for KvmVM::setTSSAddress() */
    bool capSetTSSAddress() const;
    /** Support for BaseKvmCPU::setCPUID2 and getSupportedCPUID(). */
    bool capExtendedCPUID() const;
    /** Support for BaseKvmCPU::kvmNonMaskableInterrupt(). */
    bool capUserNMI() const;

    /**
     * Check if coalesced MMIO is supported and which page in the
     * MMAP'ed structure it stores requests in.
     *
     * @return Offset (in pages) into the mmap'ed vCPU area where the
     * MMIO buffer is stored. 0 if unsupported.
     */
    int capCoalescedMMIO() const;

    /**
     * Attempt to determine how many memory slots are available. If it can't
     * be determined, this function returns 0.
     */
    int capNumMemSlots() const;

    /**
     * Support for reading and writing single registers.
     *
     * @see BaseKvmCPU::getOneReg(), and BaseKvmCPU::setOneReg()
     */
    bool capOneReg() const;

    /**
     * Support for creating an in-kernel IRQ chip model.
     *
     * @see KvmVM::createIRQChip()
     */
    bool capIRQChip() const;

    /** Support for getting and setting the kvm_vcpu_events structure. */
    bool capVCPUEvents() const;

    /** Support for getting and setting the kvm_debugregs structure. */
    bool capDebugRegs() const;

    /** Support for getting and setting the x86 XCRs. */
    bool capXCRs() const;

    /** Support for getting and setting the kvm_xsave structure. */
    bool capXSave() const;
    /** @} */

#if defined(__i386__) || defined(__x86_64__)
  public: // x86-specific
    /**
     * @{
     * @name X86-specific APIs
     */

    typedef std::vector<struct kvm_cpuid_entry2> CPUIDVector;
    typedef std::vector<uint32_t> MSRIndexVector;

    /**
     * Get the CPUID features supported by the hardware and Kvm.
     *
     * @note Requires capExtendedCPUID().
     *
     * @return False if the allocation is too small, true on success.
     */
    bool getSupportedCPUID(struct kvm_cpuid2 &cpuid) const;

    /**
     * Get the CPUID features supported by the hardware and Kvm.
     *
     * @note Requires capExtendedCPUID().
     *
     * @note This method uses an internal cache to minimize the number
     * of calls into the kernel.
     *
     * @return Reference to cached MSR index list.
     */
    const CPUIDVector &getSupportedCPUID() const;

    /**
     * Get the MSRs supported by the hardware and Kvm.
     *
     * @return False if the allocation is too small, true on success.
     */
    bool getSupportedMSRs(struct kvm_msr_list &msrs) const;

    /**
     * Get the MSRs supported by the hardware and Kvm.
     *
     * @note This method uses an internal cache to minimize the number
     * of calls into the kernel.
     *
     * @return Reference to cached MSR index list.
     */
    const MSRIndexVector &getSupportedMSRs() const;

  private: // x86-specific
    /** Cached vector of supported CPUID entries. */
    mutable CPUIDVector supportedCPUIDCache;

    /** Cached vector of supported MSRs. */
    mutable MSRIndexVector supportedMSRCache;


    /** @} */
#endif

  protected:
    /**
     * Check for the presence of an extension to the KVM API.
     *
     * The return value depends on the extension, but is always zero
     * if it is unsupported or positive otherwise. Some extensions use
     * the return value provide additional data about the extension.
     *
     * @return 0 if the extension is unsupported, positive integer
     * otherwise.
     */
    int checkExtension(int extension) const;

    /**
     * @addtogroup KvmIoctl
     * @{
     */
    /**
     * Main VM ioctl interface.
     *
     * @param request KVM request
     * @param p1 Optional request parameter
     *
     * @return -1 on error (error number in errno), ioctl dependent
     * value otherwise.
     */
    int ioctl(int request, long p1) const;
    int ioctl(int request, void *p1) const {
        return ioctl(request, (long)p1);
    }
    int ioctl(int request) const {
        return ioctl(request, 0L);
    }
    /** @} */

  private:
    // This object is a singleton, so prevent instantiation.
    Kvm();

    // Prevent copying
    Kvm(const Kvm &kvm);
    // Prevent assignment
    Kvm &operator=(const Kvm &kvm);

    /**
     * Create a KVM Virtual Machine
     *
     * @return File descriptor pointing to the VM
     */
    int createVM();

    /** KVM VM file descriptor */
    int kvmFD;
    /** KVM API version */
    int apiVersion;
    /** Size of the MMAPed vCPU parameter area. */
    int vcpuMMapSize;

    /** Singleton instance */
    static Kvm *instance;
};

/**
 * KVM VM container
 *
 * A KVM VM container normally contains all the CPUs in a shared
 * memory machine. The VM container handles things like physical
 * memory and to some extent interrupts. Normally, the VM API is only
 * used for interrupts when the PIC is emulated by the kernel, which
 * is a feature we do not use. However, some architectures (notably
 * ARM) use the VM interface to deliver interrupts to specific CPUs as
 * well.
 *
 * VM initialization is a bit different from that of other
 * SimObjects. When we initialize the VM, we discover all physical
 * memory mappings in the system. Since AbstractMem::unserialize
 * re-maps the guests memory, we need to make sure that this is done
 * after the memory has been re-mapped, but before the vCPUs are
 * initialized (KVM requires memory mappings to be setup before CPUs
 * can be created). Normally, we would just initialize the VM in
 * init() or startup(), however, we can not use init() since this is
 * called before AbstractMem::unserialize() and we can not use
 * startup() since it must be called before BaseKvmCPU::startup() and
 * the simulator framework does not guarantee call order. We therefore
 * call cpuStartup() from BaseKvmCPU::startup() instead and execute
 * the initialization code once when the first CPU in the VM is
 * starting.
 */
class KvmVM : public SimObject
{
    friend class BaseKvmCPU;

  public:
    KvmVM(KvmVMParams *params);
    virtual ~KvmVM();

    void notifyFork();

    /**
     * Setup a shared three-page memory region used by the internals
     * of KVM. This is currently only needed by x86 implementations.
     *
     * @param tss_address Physical address of the start of the TSS
     */
    void setTSSAddress(Addr tss_address);

    /** @{ */
    /**
     * Request coalescing MMIO for a memory range.
     *
     * @param start Physical start address in guest
     * @param size Size of the MMIO region
     */
    void coalesceMMIO(Addr start, int size);

    /**
     * Request coalescing MMIO for a memory range.
     *
     * @param range Coalesced MMIO range
     */
    void coalesceMMIO(const AddrRange &range);
    /** @} */

    /**
     * @addtogroup KvmInterrupts
     * @{
     */
    /**
     * Create an in-kernel interrupt  controller
     *
     * @note This functionality depends on Kvm::capIRQChip().
     */
    void createIRQChip();

    /**
     * Set the status of an IRQ line using KVM_IRQ_LINE.
     *
     * @note This ioctl is usually only used if the interrupt
     * controller is emulated by the kernel (i.e., after calling
     * createIRQChip()). Some architectures (e.g., ARM) use it instead
     * of BaseKvmCPU::kvmInterrupt().
     *
     * @param irq Interrupt number
     * @param high Line level (true for high, false for low)
     */
    void setIRQLine(uint32_t irq, bool high);

    /**
     * Is in-kernel IRQ chip emulation enabled?
     */
    bool hasKernelIRQChip() const { return _hasKernelIRQChip; }
    /** @} */

    struct MemSlot
    {
        MemSlot(uint32_t _num) : num(_num)
        {}
        MemSlot() : num(-1)
        {}

        int32_t num;
    };

    /**
     *  Allocate a memory slot within the VM.
     */
    const MemSlot allocMemSlot(uint64_t size);

    /**
     * Setup a region of physical memory in the guest
     *
     * @param slot KVM memory slot ID returned by allocMemSlot
     * @param host_addr Memory allocation backing the memory
     * @param guest_addr Address in the guest
     * @param flags Flags (see the KVM API documentation)
     */
    void setupMemSlot(const MemSlot slot, void *host_addr, Addr guest_addr,
                      uint32_t flags);

    /**
     * Disable a memory slot.
     */
    void disableMemSlot(const MemSlot slot);

    /**
     *  Free a previously allocated memory slot.
     */
    void freeMemSlot(const MemSlot slot);

    /**
     * Create an in-kernel device model.
     *
     * @param type Device type (KVM_DEV_TYPE_xxx)
     * @param flags Creation flags (KVM_CREATE_DEVICE_xxx)
     * @return Device file descriptor
     */
    int createDevice(uint32_t type, uint32_t flags = 0);

    /** Global KVM interface */
    Kvm *kvm;

#if defined(__aarch64__)
  public: // ARM-specific
    /**
     * Ask the kernel for the preferred CPU target to simulate.
     *
     * When creating an ARM vCPU in Kvm, we need to initialize it with
     * a call to BaseArmKvmCPU::kvmArmVCpuInit(). When calling this
     * function, we need to know what type of CPU the host has. This
     * call sets up the kvm_vcpu_init structure with the values the
     * kernel wants.
     *
     * @param[out] target Target structure to initialize.
     */
    void kvmArmPreferredTarget(struct kvm_vcpu_init &target) const;

#endif

  protected:
    /**
     * VM CPU initialization code.
     *
     * This method is called from BaseKvmCPU::startup() when a CPU in
     * the VM executes its BaseKvmCPU::startup() method. The first
     * time method is executed on a VM, it calls the delayedStartup()
     * method.
     */
    void cpuStartup();

    /**
     * Delayed initialization, executed once before the first CPU
     * starts.
     *
     * This method provides a way to do VM initialization once before
     * the first CPU in a VM starts. It is needed since some resources
     * (e.g., memory mappings) can change in the normal
     * SimObject::startup() path. Since the call order of
     * SimObject::startup() is not guaranteed, we simply defer some
     * initialization until a CPU is about to start.
     */
    void delayedStartup();


    /** @{ */
    /**
     * Setup a region of physical memory in the guest
     *
     * @param slot KVM memory slot ID (must be unique)
     * @param host_addr Memory allocation backing the memory
     * @param guest_addr Address in the guest
     * @param len Size of the allocation in bytes
     * @param flags Flags (see the KVM API documentation)
     */
    void setUserMemoryRegion(uint32_t slot,
                             void *host_addr, Addr guest_addr,
                             uint64_t len, uint32_t flags);
    /** @} */

    /**
     * Create a new vCPU within a VM.
     *
     * @param vcpuID ID of the new CPU within the VM.
     * @return File descriptor referencing the CPU.
     */
    int createVCPU(long vcpuID);

    /**
     * Allocate a new vCPU ID within the VM.
     *
     * The returned vCPU ID is guaranteed to be unique within the
     * VM. New IDs are allocated sequentially starting from 0.
     *
     * @return ID of the new vCPU
     */
    long allocVCPUID();

    /**
     * @addtogroup KvmIoctl
     * @{
     */
    /**
     * KVM VM ioctl interface.
     *
     * @param request KVM VM request
     * @param p1 Optional request parameter
     *
     * @return -1 on error (error number in errno), ioctl dependent
     * value otherwise.
     */
    int ioctl(int request, long p1) const;
    int ioctl(int request, void *p1) const {
        return ioctl(request, (long)p1);
    }
    int ioctl(int request) const {
        return ioctl(request, 0L);
    }
    /**@}*/

  private:
    // Prevent copying
    KvmVM(const KvmVM &vm);
    // Prevent assignment
    KvmVM &operator=(const KvmVM &vm);

    System *system;

    /** KVM VM file descriptor */
    int vmFD;

    /** Has delayedStartup() already been called? */
    bool started;

    /** Do we have in-kernel IRQ-chip emulation enabled? */
    bool _hasKernelIRQChip;

    /** Next unallocated vCPU ID */
    long nextVCPUID;

    /**
     *  Structures tracking memory slots.
     */
    class MemorySlot
    {
      public:
        uint64_t size;
        uint32_t slot;
        bool active;
    };
    std::vector<MemorySlot> memorySlots;
    uint32_t maxMemorySlot;
};

#endif
