| /* |
| * 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. |
| */ |
| |
| #include "cpu/kvm/vm.hh" |
| |
| #include <fcntl.h> |
| #include <linux/kvm.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <cerrno> |
| #include <memory> |
| |
| #include "cpu/kvm/base.hh" |
| #include "debug/Kvm.hh" |
| #include "params/KvmVM.hh" |
| #include "sim/system.hh" |
| |
| #define EXPECTED_KVM_API_VERSION 12 |
| |
| #if EXPECTED_KVM_API_VERSION != KVM_API_VERSION |
| #error Unsupported KVM version |
| #endif |
| |
| Kvm *Kvm::instance = NULL; |
| |
| Kvm::Kvm() |
| : kvmFD(-1), apiVersion(-1), vcpuMMapSize(0) |
| { |
| static bool created = false; |
| if (created) |
| warn_once("Use of multiple KvmVMs is currently untested!"); |
| |
| created = true; |
| |
| kvmFD = ::open("/dev/kvm", O_RDWR); |
| if (kvmFD == -1) |
| fatal("KVM: Failed to open /dev/kvm\n"); |
| |
| apiVersion = ioctl(KVM_GET_API_VERSION); |
| if (apiVersion != EXPECTED_KVM_API_VERSION) |
| fatal("KVM: Incompatible API version\n"); |
| |
| vcpuMMapSize = ioctl(KVM_GET_VCPU_MMAP_SIZE); |
| if (vcpuMMapSize == -1) |
| panic("KVM: Failed to get virtual CPU MMAP size\n"); |
| } |
| |
| Kvm::~Kvm() |
| { |
| close(kvmFD); |
| } |
| |
| Kvm * |
| Kvm::create() |
| { |
| if (!instance) |
| instance = new Kvm(); |
| |
| return instance; |
| } |
| |
| bool |
| Kvm::capUserMemory() const |
| { |
| return checkExtension(KVM_CAP_USER_MEMORY) != 0; |
| } |
| |
| bool |
| Kvm::capSetTSSAddress() const |
| { |
| return checkExtension(KVM_CAP_SET_TSS_ADDR) != 0; |
| } |
| |
| bool |
| Kvm::capExtendedCPUID() const |
| { |
| return checkExtension(KVM_CAP_EXT_CPUID) != 0; |
| } |
| |
| bool |
| Kvm::capUserNMI() const |
| { |
| #ifdef KVM_CAP_USER_NMI |
| return checkExtension(KVM_CAP_USER_NMI) != 0; |
| #else |
| return false; |
| #endif |
| } |
| |
| int |
| Kvm::capCoalescedMMIO() const |
| { |
| return checkExtension(KVM_CAP_COALESCED_MMIO); |
| } |
| |
| int |
| Kvm::capNumMemSlots() const |
| { |
| #ifdef KVM_CAP_NR_MEMSLOTS |
| return checkExtension(KVM_CAP_NR_MEMSLOTS); |
| #else |
| return 0; |
| #endif |
| } |
| |
| bool |
| Kvm::capOneReg() const |
| { |
| #ifdef KVM_CAP_ONE_REG |
| return checkExtension(KVM_CAP_ONE_REG) != 0; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool |
| Kvm::capIRQChip() const |
| { |
| return checkExtension(KVM_CAP_IRQCHIP) != 0; |
| } |
| |
| bool |
| Kvm::capVCPUEvents() const |
| { |
| #ifdef KVM_CAP_VCPU_EVENTS |
| return checkExtension(KVM_CAP_VCPU_EVENTS) != 0; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool |
| Kvm::capDebugRegs() const |
| { |
| #ifdef KVM_CAP_DEBUGREGS |
| return checkExtension(KVM_CAP_DEBUGREGS) != 0; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool |
| Kvm::capXCRs() const |
| { |
| #ifdef KVM_CAP_XCRS |
| return checkExtension(KVM_CAP_XCRS) != 0; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool |
| Kvm::capXSave() const |
| { |
| #ifdef KVM_CAP_XSAVE |
| return checkExtension(KVM_CAP_XSAVE) != 0; |
| #else |
| return false; |
| #endif |
| } |
| |
| |
| #if defined(__i386__) || defined(__x86_64__) |
| bool |
| Kvm::getSupportedCPUID(struct kvm_cpuid2 &cpuid) const |
| { |
| if (ioctl(KVM_GET_SUPPORTED_CPUID, (void *)&cpuid) == -1) { |
| if (errno == E2BIG) |
| return false; |
| else |
| panic("KVM: Failed to get supported CPUID (errno: %i)\n", errno); |
| } else |
| return true; |
| } |
| |
| const Kvm::CPUIDVector & |
| Kvm::getSupportedCPUID() const |
| { |
| if (supportedCPUIDCache.empty()) { |
| std::unique_ptr<struct kvm_cpuid2> cpuid; |
| int i(1); |
| do { |
| cpuid.reset((struct kvm_cpuid2 *)operator new( |
| sizeof(kvm_cpuid2) + i * sizeof(kvm_cpuid_entry2))); |
| |
| cpuid->nent = i; |
| ++i; |
| } while (!getSupportedCPUID(*cpuid)); |
| supportedCPUIDCache.assign(cpuid->entries, |
| cpuid->entries + cpuid->nent); |
| } |
| |
| return supportedCPUIDCache; |
| } |
| |
| bool |
| Kvm::getSupportedMSRs(struct kvm_msr_list &msrs) const |
| { |
| if (ioctl(KVM_GET_MSR_INDEX_LIST, (void *)&msrs) == -1) { |
| if (errno == E2BIG) |
| return false; |
| else |
| panic("KVM: Failed to get supported CPUID (errno: %i)\n", errno); |
| } else |
| return true; |
| } |
| |
| const Kvm::MSRIndexVector & |
| Kvm::getSupportedMSRs() const |
| { |
| if (supportedMSRCache.empty()) { |
| std::unique_ptr<struct kvm_msr_list> msrs; |
| int i(0); |
| do { |
| msrs.reset((struct kvm_msr_list *)operator new( |
| sizeof(kvm_msr_list) + i * sizeof(uint32_t))); |
| |
| msrs->nmsrs = i; |
| ++i; |
| } while (!getSupportedMSRs(*msrs)); |
| supportedMSRCache.assign(msrs->indices, msrs->indices + msrs->nmsrs); |
| } |
| |
| return supportedMSRCache; |
| } |
| |
| #endif // x86-specific |
| |
| |
| int |
| Kvm::checkExtension(int extension) const |
| { |
| int ret = ioctl(KVM_CHECK_EXTENSION, extension); |
| if (ret == -1) |
| panic("KVM: ioctl failed when checking for extension\n"); |
| return ret; |
| } |
| |
| int |
| Kvm::ioctl(int request, long p1) const |
| { |
| assert(kvmFD != -1); |
| |
| return ::ioctl(kvmFD, request, p1); |
| } |
| |
| int |
| Kvm::createVM() |
| { |
| int vmFD; |
| |
| vmFD = ioctl(KVM_CREATE_VM); |
| if (vmFD == -1) |
| panic("Failed to create KVM VM\n"); |
| |
| return vmFD; |
| } |
| |
| |
| KvmVM::KvmVM(const KvmVMParams ¶ms) |
| : SimObject(params), |
| kvm(new Kvm()), system(nullptr), |
| vmFD(kvm->createVM()), |
| started(false), |
| _hasKernelIRQChip(false), |
| nextVCPUID(0) |
| { |
| maxMemorySlot = kvm->capNumMemSlots(); |
| /* If we couldn't determine how memory slots there are, guess 32. */ |
| if (!maxMemorySlot) |
| maxMemorySlot = 32; |
| /* Setup the coalesced MMIO regions */ |
| for (int i = 0; i < params.coalescedMMIO.size(); ++i) |
| coalesceMMIO(params.coalescedMMIO[i]); |
| } |
| |
| KvmVM::~KvmVM() |
| { |
| if (vmFD != -1) |
| close(vmFD); |
| |
| if (kvm) |
| delete kvm; |
| } |
| |
| void |
| KvmVM::notifyFork() |
| { |
| if (vmFD != -1) { |
| if (close(vmFD) == -1) |
| warn("kvm VM: notifyFork failed to close vmFD\n"); |
| |
| vmFD = -1; |
| |
| delete kvm; |
| kvm = NULL; |
| } |
| } |
| |
| void |
| KvmVM::cpuStartup() |
| { |
| if (started) |
| return; |
| started = true; |
| |
| delayedStartup(); |
| } |
| |
| void |
| KvmVM::delayedStartup() |
| { |
| assert(system); // set by the system during its construction |
| const std::vector<BackingStoreEntry> &memories( |
| system->getPhysMem().getBackingStore()); |
| |
| DPRINTF(Kvm, "Mapping %i memory region(s)\n", memories.size()); |
| for (int slot(0); slot < memories.size(); ++slot) { |
| if (!memories[slot].kvmMap) { |
| DPRINTF(Kvm, "Skipping region marked as not usable by KVM\n"); |
| continue; |
| } |
| |
| const AddrRange &range(memories[slot].range); |
| void *pmem(memories[slot].pmem); |
| |
| if (pmem) { |
| DPRINTF(Kvm, "Mapping region: 0x%p -> 0x%llx [size: 0x%llx]\n", |
| pmem, range.start(), range.size()); |
| |
| if (range.interleaved()) { |
| panic("Tried to map an interleaved memory range into " |
| "a KVM VM.\n"); |
| } |
| |
| const MemSlot slot = allocMemSlot(range.size()); |
| setupMemSlot(slot, pmem, range.start(), 0/* flags */); |
| } else { |
| DPRINTF(Kvm, "Zero-region not mapped: [0x%llx]\n", range.start()); |
| hack("KVM: Zero memory handled as IO\n"); |
| } |
| } |
| } |
| |
| const KvmVM::MemSlot |
| KvmVM::allocMemSlot(uint64_t size) |
| { |
| if (!size) |
| panic("Memory slots must have non-zero size.\n"); |
| |
| std::vector<MemorySlot>::iterator pos; |
| for (pos = memorySlots.begin(); pos != memorySlots.end(); pos++) { |
| if (!pos->size) { |
| pos->size = size; |
| pos->active = false; |
| return pos->slot; |
| } |
| } |
| |
| uint32_t nextSlot = memorySlots.size(); |
| if (nextSlot > maxMemorySlot) |
| panic("Out of memory slots.\n"); |
| |
| MemorySlot slot; |
| slot.size = size; |
| slot.slot = nextSlot; |
| slot.active = false; |
| |
| memorySlots.push_back(slot); |
| return MemSlot(slot.slot); |
| } |
| |
| void |
| KvmVM::setupMemSlot(const KvmVM::MemSlot num, void *host_addr, Addr guest, |
| uint32_t flags) |
| { |
| MemorySlot &slot = memorySlots.at(num.num); |
| slot.active = true; |
| setUserMemoryRegion(num.num, host_addr, guest, slot.size, flags); |
| } |
| |
| void |
| KvmVM::disableMemSlot(const KvmVM::MemSlot num) |
| { |
| MemorySlot &slot = memorySlots.at(num.num); |
| if (slot.active) |
| setUserMemoryRegion(num.num, NULL, 0, 0, 0); |
| slot.active = false; |
| } |
| |
| void |
| KvmVM::freeMemSlot(const KvmVM::MemSlot num) |
| { |
| disableMemSlot(num.num); |
| MemorySlot &slot = memorySlots.at(num.num); |
| slot.size = 0; |
| } |
| |
| void |
| KvmVM::setUserMemoryRegion(uint32_t slot, |
| void *host_addr, Addr guest_addr, |
| uint64_t len, uint32_t flags) |
| { |
| struct kvm_userspace_memory_region m; |
| |
| memset(&m, 0, sizeof(m)); |
| m.slot = slot; |
| m.flags = flags; |
| m.guest_phys_addr = (uint64_t)guest_addr; |
| m.memory_size = len; |
| m.userspace_addr = (__u64)host_addr; |
| |
| if (ioctl(KVM_SET_USER_MEMORY_REGION, (void *)&m) == -1) { |
| panic("Failed to setup KVM memory region:\n" |
| "\tHost Address: 0x%p\n" |
| "\tGuest Address: 0x%llx\n", |
| "\tSize: %ll\n", |
| "\tFlags: 0x%x\n", |
| m.userspace_addr, m.guest_phys_addr, |
| m.memory_size, m.flags); |
| } |
| } |
| |
| void |
| KvmVM::coalesceMMIO(const AddrRange &range) |
| { |
| coalesceMMIO(range.start(), range.size()); |
| } |
| |
| void |
| KvmVM::coalesceMMIO(Addr start, int size) |
| { |
| struct kvm_coalesced_mmio_zone zone; |
| |
| zone.addr = start; |
| zone.size = size; |
| zone.pad = 0; |
| |
| DPRINTF(Kvm, "KVM: Registering coalesced MMIO region [0x%x, 0x%x]\n", |
| zone.addr, zone.addr + zone.size - 1); |
| if (ioctl(KVM_REGISTER_COALESCED_MMIO, (void *)&zone) == -1) |
| panic("KVM: Failed to register coalesced MMIO region (%i)\n", |
| errno); |
| } |
| |
| void |
| KvmVM::setTSSAddress(Addr tss_address) |
| { |
| if (ioctl(KVM_SET_TSS_ADDR, (unsigned long)tss_address) == -1) |
| panic("KVM: Failed to set VM TSS address\n"); |
| } |
| |
| void |
| KvmVM::createIRQChip() |
| { |
| if (_hasKernelIRQChip) |
| panic("KvmVM::createIRQChip called twice.\n"); |
| |
| if (ioctl(KVM_CREATE_IRQCHIP) != -1) { |
| _hasKernelIRQChip = true; |
| } else { |
| warn("KVM: Failed to create in-kernel IRQ chip (errno: %i)\n", |
| errno); |
| _hasKernelIRQChip = false; |
| } |
| } |
| |
| void |
| KvmVM::setIRQLine(uint32_t irq, bool high) |
| { |
| struct kvm_irq_level kvm_level; |
| |
| kvm_level.irq = irq; |
| kvm_level.level = high ? 1 : 0; |
| |
| if (ioctl(KVM_IRQ_LINE, &kvm_level) == -1) |
| panic("KVM: Failed to set IRQ line level (errno: %i)\n", |
| errno); |
| } |
| |
| int |
| KvmVM::createDevice(uint32_t type, uint32_t flags) |
| { |
| #if defined(KVM_CREATE_DEVICE) |
| struct kvm_create_device dev = { type, 0, flags }; |
| |
| if (ioctl(KVM_CREATE_DEVICE, &dev) == -1) { |
| panic("KVM: Failed to create device (errno: %i)\n", |
| errno); |
| } |
| |
| return dev.fd; |
| #else |
| panic("Kernel headers don't support KVM_CREATE_DEVICE\n"); |
| #endif |
| } |
| |
| void |
| KvmVM::setSystem(System *s) |
| { |
| panic_if(system != nullptr, "setSystem() can only be called once"); |
| panic_if(s == nullptr, "setSystem() called with null System*"); |
| system = s; |
| } |
| |
| long |
| KvmVM::contextIdToVCpuId(ContextID ctx) const |
| { |
| assert(system != nullptr); |
| return dynamic_cast<BaseKvmCPU*> |
| (system->threads[ctx]->getCpuPtr())->getVCpuID(); |
| } |
| |
| int |
| KvmVM::createVCPU(long vcpuID) |
| { |
| int fd; |
| |
| fd = ioctl(KVM_CREATE_VCPU, vcpuID); |
| if (fd == -1) |
| panic("KVM: Failed to create virtual CPU"); |
| |
| return fd; |
| } |
| |
| long |
| KvmVM::allocVCPUID() |
| { |
| return nextVCPUID++; |
| } |
| |
| #if defined(__aarch64__) |
| void |
| KvmVM::kvmArmPreferredTarget(struct kvm_vcpu_init &target) const |
| { |
| if (ioctl(KVM_ARM_PREFERRED_TARGET, &target) == -1) { |
| panic("KVM: Failed to get ARM preferred CPU target (errno: %i)\n", |
| errno); |
| } |
| } |
| #endif |
| |
| int |
| KvmVM::ioctl(int request, long p1) const |
| { |
| assert(vmFD != -1); |
| |
| return ::ioctl(vmFD, request, p1); |
| } |