/* * Copyright (C) 2018-2022 Intel Corporation. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DBG_LEVEL_HYCALL 6U typedef int32_t (*emul_dev_create) (struct acrn_vm *vm, struct acrn_vdev *dev); typedef int32_t (*emul_dev_destroy) (struct pci_vdev *vdev); struct emul_dev_ops { /* * The low 32 bits represent the vendor id and device id of PCI device, * and the high 32 bits represent the device number of the legacy device */ uint64_t dev_id; /* TODO: to re-use vdev_init/vdev_deinit directly in hypercall */ emul_dev_create create; emul_dev_destroy destroy; }; static struct emul_dev_ops emul_dev_ops_tbl[] = { #ifdef CONFIG_IVSHMEM_ENABLED {(IVSHMEM_VENDOR_ID | (IVSHMEM_DEVICE_ID << 16U)), create_ivshmem_vdev , destroy_ivshmem_vdev}, #else {(IVSHMEM_VENDOR_ID | (IVSHMEM_DEVICE_ID << 16U)), NULL, NULL}, #endif {(MCS9900_VENDOR | (MCS9900_DEV << 16U)), create_vmcs9900_vdev, destroy_vmcs9900_vdev}, {(VRP_VENDOR | (VRP_DEVICE << 16U)), create_vrp, destroy_vrp}, }; bool is_hypercall_from_ring0(void) { uint16_t cs_sel; bool ret; cs_sel = exec_vmread16(VMX_GUEST_CS_SEL); /* cs_selector[1:0] is CPL */ if ((cs_sel & 0x3U) == 0U) { ret = true; } else { ret = false; } return ret; } /** * @brief offline vcpu from Service VM * * The function offline specific vcpu from Service VM. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param param1 lapic id of the vcpu which wants to offline * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_service_vm_offline_cpu(struct acrn_vcpu *vcpu, __unused struct acrn_vm *target_vm, uint64_t param1, __unused uint64_t param2) { struct acrn_vcpu *target_vcpu; uint16_t i; int32_t ret = 0; uint32_t lapicid = (uint32_t)param1; pr_info("Service VM offline cpu with lapicid %ld", lapicid); foreach_vcpu(i, vcpu->vm, target_vcpu) { if (vlapic_get_apicid(vcpu_vlapic(target_vcpu)) == lapicid) { /* should not offline BSP */ if (target_vcpu->vcpu_id == BSP_CPU_ID) { ret = -1; break; } zombie_vcpu(target_vcpu, VCPU_ZOMBIE); offline_vcpu(target_vcpu); } } return ret; } /** * @brief Get hypervisor api version * * The function only return api version information when VM is Service VM. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param param1 guest physical memory address. The api version returned * will be copied to this gpa * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_get_api_version(struct acrn_vcpu *vcpu, __unused struct acrn_vm *target_vm, uint64_t param1, __unused uint64_t param2) { struct hc_api_version version; version.major_version = HV_API_MAJOR_VERSION; version.minor_version = HV_API_MINOR_VERSION; return copy_to_gpa(vcpu->vm, &version, param1, sizeof(version)); } /* * nearest_pow2(n) is the nearest power of 2 integer that is not less than n * The last (most significant) bit set of (n*2-1) matches the above definition */ static uint32_t nearest_pow2(uint32_t n) { uint32_t p = n; if (n >= 2U) { p = fls32(2U*n - 1U); } return p; } void get_cache_shift(uint32_t *l2_shift, uint32_t *l3_shift) { uint32_t subleaf; *l2_shift = 0U; *l3_shift = 0U; for (subleaf = 0U;; subleaf++) { uint32_t eax, ebx, ecx, edx; uint32_t cache_type, cache_level, id, shift; cpuid_subleaf(0x4U, subleaf, &eax, &ebx, &ecx, &edx); cache_type = eax & 0x1fU; cache_level = (eax >> 5U) & 0x7U; id = (eax >> 14U) & 0xfffU; shift = nearest_pow2(id + 1U); /* No more caches */ if ((cache_type == 0U) || (cache_type >= 4U)) { break; } if (cache_level == 2U) { *l2_shift = shift; } else if (cache_level == 3U) { *l3_shift = shift; } else { /* this api only for L2 & L3 cache */ } } } /** * @brief create virtual machine * * Create a virtual machine based on parameter, currently there is no * limitation for calling times of this function, will add MAX_VM_NUM * support later. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param1 guest physical memory address. This gpa points to * struct acrn_vm_creation * * @pre is_service_vm(vcpu->vm) * @pre get_vm_config(target_vm->vm_id) != NULL * @return 0 on success, non-zero on error. */ int32_t hcall_create_vm(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, uint64_t param1, __unused uint64_t param2) { struct acrn_vm *vm = vcpu->vm; uint16_t vmid = target_vm->vm_id; int32_t ret = -1; struct acrn_vm *tgt_vm = NULL; struct acrn_vm_creation cv; struct acrn_vm_config *vm_config = get_vm_config(vmid); if (copy_from_gpa(vm, &cv, param1, sizeof(cv)) == 0) { if (is_poweroff_vm(get_vm_from_vmid(vmid))) { /* Filter out the bits should not set by DM and then assign it to guest_flags */ vm_config->guest_flags &= ~DM_OWNED_GUEST_FLAG_MASK; vm_config->guest_flags |= (cv.vm_flag & DM_OWNED_GUEST_FLAG_MASK); /* post-launched VM is allowed to choose pCPUs from vm_config->cpu_affinity only */ if ((cv.cpu_affinity & ~(vm_config->cpu_affinity)) == 0UL) { /* By default launch VM with all the configured pCPUs */ uint64_t pcpu_bitmap = vm_config->cpu_affinity; if (cv.cpu_affinity != 0UL) { /* overwrite the statically configured CPU affinity */ pcpu_bitmap = cv.cpu_affinity; } /* * GUEST_FLAG_RT must be set if we have GUEST_FLAG_LAPIC_PASSTHROUGH * set in guest_flags */ if (((vm_config->guest_flags & GUEST_FLAG_LAPIC_PASSTHROUGH) != 0UL) && ((vm_config->guest_flags & GUEST_FLAG_RT) == 0UL)) { pr_err("Wrong guest flags 0x%lx\n", vm_config->guest_flags); } else { if (create_vm(vmid, pcpu_bitmap, vm_config, &tgt_vm) == 0) { /* return a relative vm_id from Service VM view */ cv.vmid = vmid_2_rel_vmid(vm->vm_id, vmid); cv.vcpu_num = tgt_vm->hw.created_vcpus; } else { dev_dbg(DBG_LEVEL_HYCALL, "HCALL: Create VM failed"); cv.vmid = ACRN_INVALID_VMID; } ret = copy_to_gpa(vm, &cv, param1, sizeof(cv)); } } else { pr_err("Post-launched VM%u chooses invalid pCPUs(0x%llx).", vmid, cv.cpu_affinity); } } } if (((ret != 0) || (cv.vmid == ACRN_INVALID_VMID)) && (!is_static_configured_vm(target_vm))) { memset(vm_config->name, 0U, MAX_VM_NAME_LEN); } return ret; } /** * @brief destroy virtual machine * * Destroy a virtual machine, it will pause target VM then shutdown it. * The function will return -1 if the target VM does not exist. * * @param target_vm Pointer to target VM data structure * * @return 0 on success, non-zero on error. */ int32_t hcall_destroy_vm(__unused struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, __unused uint64_t param2) { int32_t ret = -1; if (is_paused_vm(target_vm)) { /* TODO: check target_vm guest_flags */ ret = shutdown_vm(target_vm); } return ret; } /** * @brief start virtual machine * * Start a virtual machine, it will schedule target VM's vcpu to run. * The function will return -1 if the target VM does not exist or the * IOReq buffer page for the VM is not ready. * * @param target_vm Pointer to target VM data structure * * @return 0 on success, non-zero on error. */ int32_t hcall_start_vm(__unused struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, __unused uint64_t param2) { int32_t ret = -1; if ((is_created_vm(target_vm)) && (target_vm->sw.io_shared_page != NULL)) { /* TODO: check target_vm guest_flags */ start_vm(target_vm); ret = 0; } return ret; } /** * @brief pause virtual machine * * Pause a virtual machine, if the VM is already paused, the function * will return 0 directly for success. * The function will return -1 if the target VM does not exist. * * @param target_vm Pointer to target VM data structure * * @return 0 on success, non-zero on error. */ int32_t hcall_pause_vm(__unused struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, __unused uint64_t param2) { int32_t ret = -1; if (!is_poweroff_vm(target_vm)) { /* TODO: check target_vm guest_flags */ pause_vm(target_vm); ret = 0; } return ret; } /** * @brief reset virtual machine * * Reset a virtual machine, it will make target VM rerun from * pre-defined entry. Comparing to start vm, this function reset * each vcpu state and do some initialization for guest. * The function will return -1 if the target VM does not exist. * * @param target_vm Pointer to target VM data structure * * @return 0 on success, non-zero on error. */ int32_t hcall_reset_vm(__unused struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, __unused uint64_t param2) { int32_t ret = -1; if (is_paused_vm(target_vm)) { /* TODO: check target_vm guest_flags */ ret = reset_vm(target_vm, COLD_RESET); } return ret; } /** * @brief set vcpu regs * * Set the vcpu regs. It will set the vcpu init regs from DM. Now, * it's only applied to BSP. AP always uses fixed init regs. * The function will return -1 if the targat VM or BSP doesn't exist. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to * struct acrn_vcpu_regs * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_set_vcpu_regs(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; struct acrn_vcpu_regs vcpu_regs; struct acrn_vcpu *target_vcpu; int32_t ret = -1; /* Only allow setup init ctx while target_vm is inactive */ if ((!is_poweroff_vm(target_vm)) && (param2 != 0U) && (target_vm->state != VM_RUNNING)) { if (copy_from_gpa(vm, &vcpu_regs, param2, sizeof(vcpu_regs)) != 0) { } else if (vcpu_regs.vcpu_id >= MAX_VCPUS_PER_VM) { pr_err("%s: invalid vcpu_id for set_vcpu_regs\n", __func__); } else { target_vcpu = vcpu_from_vid(target_vm, vcpu_regs.vcpu_id); if (target_vcpu->state != VCPU_OFFLINE) { if (is_valid_cr0_cr4(vcpu_regs.vcpu_regs.cr0, vcpu_regs.vcpu_regs.cr4)) { set_vcpu_regs(target_vcpu, &(vcpu_regs.vcpu_regs)); ret = 0; } } } } return ret; } int32_t hcall_create_vcpu(__unused struct acrn_vcpu *vcpu, __unused struct acrn_vm *target_vm, __unused uint64_t param1, __unused uint64_t param2) { return 0; } /** * @brief set or clear IRQ line * * Set or clear a virtual IRQ line for a VM, which could be from ISA * or IOAPIC, normally it triggers an edge IRQ. * The function will return -1 if the target VM does not exist. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 info for irqline * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_set_irqline(__unused struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { uint32_t irq_pic; int32_t ret = -1; struct acrn_irqline_ops *ops = (struct acrn_irqline_ops *)¶m2; if (is_severity_pass(target_vm->vm_id) && !is_poweroff_vm(target_vm)) { if (ops->gsi < get_vm_gsicount(target_vm)) { if (ops->gsi < vpic_pincount()) { /* * IRQ line for 8254 timer is connected to * I/O APIC pin #2 but PIC pin #0,route GSI * number #2 to PIC IRQ #0. */ irq_pic = (ops->gsi == 2U) ? 0U : ops->gsi; vpic_set_irqline(vm_pic(target_vm), irq_pic, ops->op); } /* handle IOAPIC irqline */ vioapic_set_irqline_lock(target_vm, ops->gsi, ops->op); ret = 0; } } return ret; } /** * @brief inject MSI interrupt * * Inject a MSI interrupt for a VM. * The function will return -1 if the target VM does not exist. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to struct acrn_msi_entry * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_inject_msi(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -1; if (is_severity_pass(target_vm->vm_id) && !is_poweroff_vm(target_vm)) { struct acrn_msi_entry msi; if (copy_from_gpa(vm, &msi, param2, sizeof(msi)) == 0) { ret = vlapic_inject_msi(target_vm, msi.msi_addr, msi.msi_data); } } return ret; } /** * @brief set ioreq shared buffer * * Set the ioreq share buffer for a VM. * The function will return -1 if the target VM does not exist. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to buffer address * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_set_ioreq_buffer(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; uint64_t hpa; uint16_t i; int32_t ret = -1; if (is_created_vm(target_vm)) { uint64_t iobuf; if (copy_from_gpa(vm, &iobuf, param2, sizeof(iobuf)) == 0) { dev_dbg(DBG_LEVEL_HYCALL, "[%d] SET BUFFER=0x%p", target_vm->vm_id, iobuf); hpa = gpa2hpa(vm, iobuf); if (hpa == INVALID_HPA) { pr_err("%s,vm[%hu] gpa 0x%lx,GPA is unmapping.", __func__, vm->vm_id, iobuf); target_vm->sw.io_shared_page = NULL; } else { target_vm->sw.io_shared_page = hpa2hva(hpa); for (i = 0U; i < ACRN_IO_REQUEST_MAX; i++) { set_io_req_state(target_vm, i, ACRN_IOREQ_STATE_FREE); } ret = 0; } } } return ret; } /** * @brief Setup a share buffer for a VM. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param param1 guest physical address. This gpa points to * struct sbuf_setup_param * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_setup_sbuf(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; struct acrn_sbuf_param asp; uint64_t *hva; int ret = -1; if (copy_from_gpa(vm, &asp, param2, sizeof(asp)) == 0) { if (asp.gpa != 0U) { hva = (uint64_t *)gpa2hva(vm, asp.gpa); ret = sbuf_setup_common(target_vm, asp.cpu_id, asp.sbuf_id, hva); } } return ret; } int32_t hcall_asyncio_assign(__unused struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_asyncio_info asyncio_info; struct acrn_vm *vm = vcpu->vm; int ret = -1; if (copy_from_gpa(vm, &asyncio_info, param2, sizeof(asyncio_info)) == 0) { add_asyncio(target_vm, &asyncio_info); ret = 0; } return ret; } int32_t hcall_asyncio_deassign(__unused struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_asyncio_info asyncio_info; struct acrn_vm *vm = vcpu->vm; int ret = -1; if (copy_from_gpa(vm, &asyncio_info, param2, sizeof(asyncio_info)) == 0) { remove_asyncio(target_vm, &asyncio_info); ret = 0; } return ret; } /** * @brief notify request done * * Notify the requestor VCPU for the completion of an ioreq. * The function will return -1 if the target VM does not exist. * * @param target_vm Pointer to target VM data structure * @param param2 vcpu ID of the requestor * * @return 0 on success, non-zero on error. */ int32_t hcall_notify_ioreq_finish(__unused struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vcpu *target_vcpu; int32_t ret = -1; uint16_t vcpu_id = (uint16_t)param2; /* make sure we have set req_buf */ if (is_severity_pass(target_vm->vm_id) && (!is_poweroff_vm(target_vm)) && (target_vm->sw.io_shared_page != NULL)) { dev_dbg(DBG_LEVEL_HYCALL, "[%d] NOTIFY_FINISH for vcpu %d", target_vm->vm_id, vcpu_id); if (vcpu_id >= target_vm->hw.created_vcpus) { pr_err("%s, failed to get VCPU %d context from VM %d\n", __func__, vcpu_id, target_vm->vm_id); } else { target_vcpu = vcpu_from_vid(target_vm, vcpu_id); if (!target_vcpu->vm->sw.is_polling_ioreq) { signal_event(&target_vcpu->events[VCPU_EVENT_IOREQ]); } ret = 0; } } return ret; } /** *@pre is_service_vm(vm) *@pre gpa2hpa(vm, region->service_vm_gpa) != INVALID_HPA */ static void add_vm_memory_region(struct acrn_vm *vm, struct acrn_vm *target_vm, const struct vm_memory_region *region,uint64_t *pml4_page) { uint64_t prot = 0UL, base_paddr; uint64_t hpa = gpa2hpa(vm, region->service_vm_gpa); /* access right */ if ((region->prot & MEM_ACCESS_READ) != 0U) { prot |= EPT_RD; } if ((region->prot & MEM_ACCESS_WRITE) != 0U) { prot |= EPT_WR; } if ((region->prot & MEM_ACCESS_EXEC) != 0U) { prot |= EPT_EXE; } /* memory type */ if ((region->prot & MEM_TYPE_WB) != 0U) { prot |= EPT_WB; } else if ((region->prot & MEM_TYPE_WT) != 0U) { prot |= EPT_WT; } else if ((region->prot & MEM_TYPE_WC) != 0U) { prot |= EPT_WC; } else if ((region->prot & MEM_TYPE_WP) != 0U) { prot |= EPT_WP; } else { prot |= EPT_UNCACHED; } /* If Software SRAM is initialized, and HV received a request to map Software SRAM * area to guest, we should add EPT_WB flag to make Software SRAM effective. * TODO: We can enforce WB for any region has overlap with Software SRAM, for simplicity, * and leave it to Service VM to make sure it won't violate. */ if (is_software_sram_enabled()) { base_paddr = get_software_sram_base(); if ((hpa >= base_paddr) && ((hpa + region->size) <= (base_paddr + get_software_sram_size()))) { prot |= EPT_WB; } } /* create gpa to hpa EPT mapping */ ept_add_mr(target_vm, pml4_page, hpa, region->gpa, region->size, prot); } /** *@pre is_service_vm(vm) */ static int32_t set_vm_memory_region(struct acrn_vm *vm, struct acrn_vm *target_vm, const struct vm_memory_region *region) { uint64_t *pml4_page; int32_t ret = -EINVAL; if ((region->size & (PAGE_SIZE - 1UL)) == 0UL) { pml4_page = (uint64_t *)target_vm->arch_vm.nworld_eptp; if (region->type == MR_ADD) { /* if the GPA range is Service VM valid GPA or not */ if (ept_is_valid_mr(vm, region->service_vm_gpa, region->size)) { /* FIXME: how to filter the alias mapping ? */ add_vm_memory_region(vm, target_vm, region, pml4_page); ret = 0; } } else { if (ept_is_valid_mr(target_vm, region->gpa, region->size)) { ept_del_mr(target_vm, pml4_page, region->gpa, region->size); ret = 0; } } } dev_dbg((ret == 0) ? DBG_LEVEL_HYCALL : LOG_ERROR, "[vm%d] type=%d gpa=0x%x service_vm_gpa=0x%x sz=0x%x", target_vm->vm_id, region->type, region->gpa, region->service_vm_gpa, region->size); return ret; } /** * @brief setup ept memory mapping for multi regions * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param1 guest physical address. This gpa points to * struct set_memmaps * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_set_vm_memory_regions(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, uint64_t param1, __unused uint64_t param2) { struct acrn_vm *vm = vcpu->vm; struct set_regions regions; struct vm_memory_region mr; uint32_t idx; int32_t ret = -1; if (copy_from_gpa(vm, ®ions, param1, sizeof(regions)) == 0) { if (!is_poweroff_vm(target_vm) && (is_severity_pass(target_vm->vm_id) || (target_vm->state != VM_RUNNING))) { idx = 0U; while (idx < regions.mr_num) { if (copy_from_gpa(vm, &mr, regions.regions_gpa + idx * sizeof(mr), sizeof(mr)) != 0) { pr_err("%s: Copy mr entry fail from vm\n", __func__); break; } ret = set_vm_memory_region(vm, target_vm, &mr); if (ret < 0) { break; } idx++; } } else { pr_err("%p %s:target_vm is invalid or Targeting to service vm", target_vm, __func__); } } return ret; } /** *@pre is_service_vm(vm) */ static int32_t write_protect_page(struct acrn_vm *vm,const struct wp_data *wp) { uint64_t hpa, base_paddr; uint64_t prot_set; uint64_t prot_clr; int32_t ret = -EINVAL; if (is_severity_pass(vm->vm_id)) { if ((!mem_aligned_check(wp->gpa, PAGE_SIZE)) || (!ept_is_valid_mr(vm, wp->gpa, PAGE_SIZE))) { pr_err("%s,vm[%hu] gpa 0x%lx,GPA is invalid or not page size aligned.", __func__, vm->vm_id, wp->gpa); } else { hpa = gpa2hpa(vm, wp->gpa); if (hpa == INVALID_HPA) { pr_err("%s,vm[%hu] gpa 0x%lx,GPA is unmapping.", __func__, vm->vm_id, wp->gpa); } else { dev_dbg(DBG_LEVEL_HYCALL, "[vm%d] gpa=0x%x hpa=0x%x", vm->vm_id, wp->gpa, hpa); base_paddr = hva2hpa((void *)(get_hv_image_base())); if (((hpa <= base_paddr) && ((hpa + PAGE_SIZE) > base_paddr)) || ((hpa >= base_paddr) && (hpa < (base_paddr + get_hv_ram_size())))) { pr_err("%s: overlap the HV memory region.", __func__); } else { prot_set = (wp->set != 0U) ? 0UL : EPT_WR; prot_clr = (wp->set != 0U) ? EPT_WR : 0UL; ept_modify_mr(vm, (uint64_t *)vm->arch_vm.nworld_eptp, wp->gpa, PAGE_SIZE, prot_set, prot_clr); ret = 0; } } } } return ret; } /** * @brief change guest memory page write permission * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to * struct wp_data * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_write_protect_page(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -1; uint64_t wp_gpa = param2; if (!is_poweroff_vm(target_vm)) { struct wp_data wp; if (copy_from_gpa(vm, &wp, wp_gpa, sizeof(wp)) == 0) { ret = write_protect_page(target_vm, &wp); } } else { pr_err("%p %s: target_vm is invalid", target_vm, __func__); } return ret; } /** * @brief translate guest physical address to host physical address * * Translate guest physical address to host physical address for a VM. * The function will return -1 if the target VM does not exist. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to struct vm_gpa2hpa * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_gpa_to_hpa(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -1; struct vm_gpa2hpa v_gpa2hpa; (void)memset((void *)&v_gpa2hpa, 0U, sizeof(v_gpa2hpa)); if (!is_poweroff_vm(target_vm) && (copy_from_gpa(vm, &v_gpa2hpa, param2, sizeof(v_gpa2hpa)) == 0)) { v_gpa2hpa.hpa = gpa2hpa(target_vm, v_gpa2hpa.gpa); if (v_gpa2hpa.hpa == INVALID_HPA) { pr_err("%s,vm[%hu] gpa 0x%lx,GPA is unmapping.", __func__, target_vm->vm_id, v_gpa2hpa.gpa); } else { ret = copy_to_gpa(vm, &v_gpa2hpa, param2, sizeof(v_gpa2hpa)); } } else { pr_err("target_vm is invalid or HCALL gpa2hpa: Unable copy param from vm\n"); } return ret; } /** * @brief Assign one PCI dev to a VM. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to data structure of * acrn_pcidev including assign PCI device info * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_assign_pcidev(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -EINVAL; struct acrn_pcidev pcidev; /* We should only assign a device to a post-launched VM at creating time for safety, not runtime or other cases*/ if (is_created_vm(target_vm)) { if (copy_from_gpa(vm, &pcidev, param2, sizeof(pcidev)) == 0) { ret = vpci_assign_pcidev(target_vm, &pcidev); } } else { pr_err("%s, vm[%d] is not a postlaunched VM, or not in CREATED status to be assigned with a pcidev\n", __func__, vm->vm_id); } return ret; } /** * @brief Deassign one PCI dev from a VM. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to data structure of * acrn_pcidev including deassign PCI device info * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_deassign_pcidev(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -EINVAL; struct acrn_pcidev pcidev; /* We should only de-assign a device from a post-launched VM at creating/shutdown/reset time */ if ((is_paused_vm(target_vm) || is_created_vm(target_vm))) { if (copy_from_gpa(vm, &pcidev, param2, sizeof(pcidev)) == 0) { ret = vpci_deassign_pcidev(target_vm, &pcidev); } } else { pr_err("%s, vm[%d] is not a postlaunched VM, or not in PAUSED/CREATED status to be deassigned from a pcidev\n", __func__, vm->vm_id); } return ret; } /** * @brief Assign one MMIO dev to a VM. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to data structure of * acrn_mmiodev including assign MMIO device info * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_assign_mmiodev(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -EINVAL; struct acrn_mmiodev mmiodev; /* We should only assign a device to a post-launched VM at creating time for safety, not runtime or other cases*/ if (is_created_vm(target_vm)) { if (copy_from_gpa(vm, &mmiodev, param2, sizeof(mmiodev)) == 0) { ret = deassign_mmio_dev(vm, &mmiodev); if (ret == 0) { ret = assign_mmio_dev(target_vm, &mmiodev); } } } else { pr_err("vm[%d] %s failed!\n",target_vm->vm_id, __func__); } return ret; } /** * @brief Deassign one MMIO dev from a VM. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to data structure of * acrn_mmiodev including deassign MMIO device info * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_deassign_mmiodev(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -EINVAL; struct acrn_mmiodev mmiodev; /* We should only de-assign a device from a post-launched VM at creating/shutdown/reset time */ if ((is_paused_vm(target_vm) || is_created_vm(target_vm))) { if (copy_from_gpa(vm, &mmiodev, param2, sizeof(mmiodev)) == 0) { ret = deassign_mmio_dev(target_vm, &mmiodev); if (ret == 0) { ret = assign_mmio_dev(vm, &mmiodev); } } } else { pr_err("vm[%d] %s failed!\n",target_vm->vm_id, __func__); } return ret; } /** * @brief Set interrupt mapping info of ptdev. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to data structure of * hc_ptdev_irq including intr remapping info * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_set_ptdev_intr_info(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -1; if (is_created_vm(target_vm)) { struct hc_ptdev_irq irq; if (copy_from_gpa(vm, &irq, param2, sizeof(irq)) == 0) { if (irq.type == IRQ_INTX) { struct pci_vdev *vdev; union pci_bdf bdf = {.value = irq.virt_bdf}; struct acrn_vpci *vpci = &target_vm->vpci; spinlock_obtain(&vpci->lock); vdev = pci_find_vdev(vpci, bdf); spinlock_release(&vpci->lock); /* * TODO: Change the hc_ptdev_irq structure member names * virt_pin to virt_gsi * phys_pin to phys_gsi */ if ((vdev != NULL) && (vdev->pdev->bdf.value == irq.phys_bdf)) { if ((((!irq.intx.pic_pin) && (irq.intx.virt_pin < get_vm_gsicount(target_vm))) || ((irq.intx.pic_pin) && (irq.intx.virt_pin < vpic_pincount()))) && is_gsi_valid(irq.intx.phys_pin)) { ptirq_remove_intx_remapping(get_service_vm(), irq.intx.phys_pin, false, true); ret = ptirq_add_intx_remapping(target_vm, irq.intx.virt_pin, irq.intx.phys_pin, irq.intx.pic_pin); } else { pr_err("%s: Invalid phys pin or virt pin\n", __func__); } } } else { pr_err("%s: Invalid irq type: %u\n", __func__, irq.type); } } } return ret; } /** * @brief Clear interrupt mapping info of ptdev. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to data structure of * hc_ptdev_irq including intr remapping info * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_reset_ptdev_intr_info(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -1; if (is_created_vm(target_vm) || is_paused_vm(target_vm)) { struct hc_ptdev_irq irq; if (copy_from_gpa(vm, &irq, param2, sizeof(irq)) == 0) { if (irq.type == IRQ_INTX) { struct pci_vdev *vdev; union pci_bdf bdf = {.value = irq.virt_bdf}; struct acrn_vpci *vpci = &target_vm->vpci; spinlock_obtain(&vpci->lock); vdev = pci_find_vdev(vpci, bdf); spinlock_release(&vpci->lock); /* * TODO: Change the hc_ptdev_irq structure member names * virt_pin to virt_gsi * phys_pin to phys_gsi */ if ((vdev != NULL) && (vdev->pdev->bdf.value == irq.phys_bdf)) { if (((!irq.intx.pic_pin) && (irq.intx.virt_pin < get_vm_gsicount(target_vm))) || ((irq.intx.pic_pin) && (irq.intx.virt_pin < vpic_pincount()))) { ptirq_remove_intx_remapping(target_vm, irq.intx.virt_pin, irq.intx.pic_pin, false); ret = 0; } else { pr_err("%s: Invalid virt pin\n", __func__); } } } else { pr_err("%s: Invalid irq type: %u\n", __func__, irq.type); } } } return ret; } static bool is_pt_pstate(__unused const struct acrn_vm *vm) { /* Currently VM's CPU frequency is managed in hypervisor. So no pass through for all VMs. */ return false; } /** * @brief Get VCPU Power state. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param1 cmd to show get which VCPU power state data * @param param2 VCPU power state data * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_get_cpu_pm_state(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -1; uint64_t cmd = param1; if (is_created_vm(target_vm)) { switch (cmd & PMCMD_TYPE_MASK) { case ACRN_PMCMD_GET_PX_CNT: { uint8_t px_cnt; /* If the VM supports vHWP, then the guest is having continuous p-state. Thus it doesn't have a specific * px_cnt. The hypercall returns success and px_cnt = 0. * If the VM's p-state is hidden or hv doesn't have its p-state info, the hypercall returns -1. */ if (is_vhwp_configured(target_vm)) { px_cnt = 0U; } else if (!is_pt_pstate(target_vm)) { break; } else if (target_vm->pm.px_cnt == 0U) { break; } else { px_cnt = target_vm->pm.px_cnt; } ret = copy_to_gpa(vm, &px_cnt, param2, sizeof(px_cnt)); break; } case ACRN_PMCMD_GET_PX_DATA: { uint8_t pn; struct acrn_pstate_data *px_data; if (!is_pt_pstate(target_vm)) { break; } /* For now we put px data as per-vm, * If it is stored as per-cpu in the future, * we need to check PMCMD_VCPUID_MASK in cmd. */ if (target_vm->pm.px_cnt == 0U) { break; } pn = (uint8_t)((cmd & PMCMD_STATE_NUM_MASK) >> PMCMD_STATE_NUM_SHIFT); if (pn >= target_vm->pm.px_cnt) { break; } px_data = target_vm->pm.px_data + pn; ret = copy_to_gpa(vm, px_data, param2, sizeof(struct acrn_pstate_data)); break; } case ACRN_PMCMD_GET_CX_CNT: { ret = copy_to_gpa(vm, &(target_vm->pm.cx_cnt), param2, sizeof(target_vm->pm.cx_cnt)); break; } case ACRN_PMCMD_GET_CX_DATA: { uint8_t cx_idx; struct acrn_cstate_data *cx_data; if (target_vm->pm.cx_cnt == 0U) { break; } cx_idx = (uint8_t) ((cmd & PMCMD_STATE_NUM_MASK) >> PMCMD_STATE_NUM_SHIFT); if ((cx_idx == 0U) || (cx_idx > target_vm->pm.cx_cnt)) { break; } cx_data = target_vm->pm.cx_data + cx_idx; ret = copy_to_gpa(vm, cx_data, param2, sizeof(struct acrn_cstate_data)); break; } default: /* invalid command */ break; } } return ret; } /** * @brief Get VCPU a VM's interrupt count data. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param2 guest physical address. This gpa points to data structure of * acrn_intr_monitor * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_vm_intr_monitor(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t status = -EINVAL; struct acrn_intr_monitor *intr_hdr; uint64_t hpa; if (!is_poweroff_vm(target_vm)) { /* the param for this hypercall is page aligned */ hpa = gpa2hpa(vm, param2); if (hpa != INVALID_HPA) { intr_hdr = (struct acrn_intr_monitor *)hpa2hva(hpa); stac(); if (intr_hdr->buf_cnt <= (MAX_PTDEV_NUM * 2U)) { status = 0; switch (intr_hdr->cmd) { case INTR_CMD_GET_DATA: intr_hdr->buf_cnt = ptirq_get_intr_data(target_vm, intr_hdr->buffer, intr_hdr->buf_cnt); break; case INTR_CMD_DELAY_INT: /* buffer[0] is the delay time (in MS), if 0 to cancel delay */ target_vm->intr_inject_delay_delta = intr_hdr->buffer[0] * TICKS_PER_MS; break; default: /* if cmd wrong it goes here should not happen */ status = -EINVAL; break; } } clac(); } } return status; } /** * @brief set upcall notifier vector * * This is the API that helps to switch the notifer vecotr. If this API is * not called, the hypervisor will use the default notifier vector(0xF3) * to notify the Service VM kernel. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param param1 the expected notifier vector from guest * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_set_callback_vector(__unused struct acrn_vcpu *vcpu, __unused struct acrn_vm *target_vm, uint64_t param1, __unused uint64_t param2) { int32_t ret; if ((param1 > NR_MAX_VECTOR) || (param1 < VECTOR_DYNAMIC_START)) { pr_err("%s: Invalid passed vector\n", __func__); ret = -EINVAL; } else { set_hsm_notification_vector((uint32_t)param1); ret = 0; } return ret; } /* * @pre dev != NULL */ static struct emul_dev_ops *find_emul_dev_ops(struct acrn_vdev *dev) { struct emul_dev_ops *op = NULL; uint32_t i; for (i = 0U; i < ARRAY_SIZE(emul_dev_ops_tbl); i++) { if (emul_dev_ops_tbl[i].dev_id == dev->id.value) { op = &emul_dev_ops_tbl[i]; break; } } return op; } /** * @brief Add an emulated device in hypervisor. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param guest physical address. This gpa points to data structure of * acrn_vdev including information about PCI or legacy devices * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_add_vdev(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -EINVAL; struct acrn_vdev dev; struct emul_dev_ops *op; /* We should only create a device to a post-launched VM at creating time for safety, not runtime or other cases*/ if (is_created_vm(target_vm)) { if (copy_from_gpa(vm, &dev, param2, sizeof(dev)) == 0) { op = find_emul_dev_ops(&dev); if ((op != NULL) && (op->create != NULL)) { ret = op->create(target_vm, &dev); } } } else { pr_err("%s, vm[%d] is not a postlaunched VM, or not in CREATED status to create a vdev\n", __func__, target_vm->vm_id); } return ret; } /** * @brief Remove an emulated device in hypervisor. * * @param vcpu Pointer to vCPU that initiates the hypercall * @param target_vm Pointer to target VM data structure * @param param guest physical address. This gpa points to data structure of * acrn_vdev including information about PCI or legacy devices * * @pre is_service_vm(vcpu->vm) * @return 0 on success, non-zero on error. */ int32_t hcall_remove_vdev(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __unused uint64_t param1, uint64_t param2) { struct acrn_vm *vm = vcpu->vm; int32_t ret = -EINVAL; struct acrn_vdev dev; struct pci_vdev *vdev; struct emul_dev_ops *op; union pci_bdf bdf; /* We should only destroy a device to a post-launched VM at creating or pausing time for safety, not runtime or other cases*/ if (is_created_vm(target_vm) || is_paused_vm(target_vm)) { if (copy_from_gpa(vm, &dev, param2, sizeof(dev)) == 0) { op = find_emul_dev_ops(&dev); if (op != NULL) { bdf.value = (uint16_t) dev.slot; vdev = pci_find_vdev(&target_vm->vpci, bdf); if (vdev != NULL) { vdev->pci_dev_config->vbdf.value = UNASSIGNED_VBDF; if (op->destroy != NULL) { ret = op->destroy(vdev); } else { ret = 0; } } else { pr_warn("%s, failed to destroy emulated device %x:%x.%x\n", __func__, bdf.bits.b, bdf.bits.d, bdf.bits.f); } } } } else { pr_err("%s, vm[%d] is not a postlaunched VM, or not in CREATED/PAUSED status to destroy a vdev\n", __func__, target_vm->vm_id); } return ret; }