From b138dd1eafbded3801287781a5d1242a6f4f4dd8 Mon Sep 17 00:00:00 2001 From: yichongt Date: Fri, 28 Jun 2024 10:51:50 +0800 Subject: [PATCH] hv: pio_region: add hypercalls to support pio device pass through Add two hypercalls HC_ASSIGN_PIO_REGION and HC_DEASSIGN_PIO_REGION to support PIO device pass through for post-launched VM. Add has_direct_pio_access function to check the pio access state of VM before allow/deny guest pio access. Align querying, setting and clearing interfaces of VM io.bitmap with general bitmap operation function. Tracked-On: #8635 Signed-off-by: Yichong Tang Reviewed-by: Junjie Mao --- hypervisor/arch/x86/guest/vmcall.c | 4 ++ hypervisor/arch/x86/guest/vmx_io.c | 32 ++++++++- hypervisor/common/hypercall.c | 72 +++++++++++++++++++ .../include/arch/x86/asm/guest/vmx_io.h | 12 ++++ hypervisor/include/common/hypercall.h | 28 ++++++++ hypervisor/include/public/acrn_common.h | 13 ++++ hypervisor/include/public/acrn_hv_defs.h | 2 + 7 files changed, 161 insertions(+), 2 deletions(-) diff --git a/hypervisor/arch/x86/guest/vmcall.c b/hypervisor/arch/x86/guest/vmcall.c index e7ad68940..9985a7e17 100644 --- a/hypervisor/arch/x86/guest/vmcall.c +++ b/hypervisor/arch/x86/guest/vmcall.c @@ -82,6 +82,10 @@ static const struct hc_dispatch hc_dispatch_table[] = { .handler = hcall_add_vdev}, [HC_IDX(HC_REMOVE_VDEV)] = { .handler = hcall_remove_vdev}, + [HC_IDX(HC_ASSIGN_PIO_REGION)] = { + .handler = hcall_assign_pio_region}, + [HC_IDX(HC_DEASSIGN_PIO_REGION)] = { + .handler = hcall_deassign_pio_region}, [HC_IDX(HC_SET_PTDEV_INTR_INFO)] = { .handler = hcall_set_ptdev_intr_info}, [HC_IDX(HC_RESET_PTDEV_INTR_INFO)] = { diff --git a/hypervisor/arch/x86/guest/vmx_io.c b/hypervisor/arch/x86/guest/vmx_io.c index c9580bada..9f6f84020 100644 --- a/hypervisor/arch/x86/guest/vmx_io.c +++ b/hypervisor/arch/x86/guest/vmx_io.c @@ -204,7 +204,7 @@ void allow_guest_pio_access(struct acrn_vm *vm, uint16_t port_address, b = (uint32_t *)vm->arch_vm.io_bitmap; for (i = 0U; i < nbytes; i++) { - b[address >> 5U] &= ~(1U << (address & 0x1fU)); + bitmap32_clear_nolock(address & 0x1fU, &b[address >> 5U]); address++; } } @@ -218,7 +218,35 @@ void deny_guest_pio_access(struct acrn_vm *vm, uint16_t port_address, b = (uint32_t *)vm->arch_vm.io_bitmap; for (i = 0U; i < nbytes; i++) { - b[address >> 5U] |= (1U << (address & 0x1fU)); + bitmap32_set_nolock(address & 0x1fU, &b[address >> 5U]); address++; } } + +/** + * @brief Check if a VM has full access to a port I/O range + * + * This API check if given \p vm has direct access to the port I/O space + * starting from \p port_address to \p port_address + \p nbytes - 1. + * + * @param vm The VM whose port I/O access permissions is to be checked + * @param port_address The start address of the port I/O range + * @param nbytes The size of the range, in bytes + */ +bool has_direct_pio_access(struct acrn_vm *vm, uint16_t port_address, uint32_t nbytes) +{ + uint16_t address = port_address; + uint32_t *b; + uint32_t i; + bool ret = true; + + b = (uint32_t *)vm->arch_vm.io_bitmap; + for (i = 0U; i < nbytes; i++) { + if (bitmap32_test(address & 0x1fU, &b[address >> 5U])) { + ret = false; + break; + } + address++; + } + return ret; +} \ No newline at end of file diff --git a/hypervisor/common/hypercall.c b/hypervisor/common/hypercall.c index 5d60cc5cf..03e2a715a 100644 --- a/hypervisor/common/hypercall.c +++ b/hypervisor/common/hypercall.c @@ -1354,3 +1354,75 @@ int32_t hcall_remove_vdev(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, __u } return ret; } + +/** + * @brief Assign PIO device resource 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_pio_region including assign PIO resource info + * + * @pre is_service_vm(vcpu->vm) + * @return 0 on success, non-zero on error. + */ +int32_t hcall_assign_pio_region(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_pio_region pio_region; + + /* 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, &pio_region, param2, sizeof(pio_region)) == 0) { + if (has_direct_pio_access(vm, pio_region.res.port_address, pio_region.res.size)) { + deny_guest_pio_access(vm, pio_region.res.port_address, pio_region.res.size); + allow_guest_pio_access(target_vm, pio_region.res.port_address, pio_region.res.size); + ret = 0; + } else { + pr_err("vm[%d] %s failed: pio region not fully accessible for vm[%d].\n",target_vm->vm_id, __func__, vm->vm_id); + } + } + } else { + pr_err("vm[%d] %s failed: target vm not created.\n",target_vm->vm_id, __func__); + } + + return ret; +} + +/** + * @brief Deassign PIO device resource 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_pio_region including deassign PIO device info + * + * @pre is_service_vm(vcpu->vm) + * @return 0 on success, non-zero on error. + */ +int32_t hcall_deassign_pio_region(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_pio_region pio_region; + + /* 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, &pio_region, param2, sizeof(pio_region)) == 0) { + if (has_direct_pio_access(target_vm, pio_region.res.port_address, pio_region.res.size)) { + deny_guest_pio_access(target_vm, pio_region.res.port_address, pio_region.res.size); + allow_guest_pio_access(vm, pio_region.res.port_address, pio_region.res.size); + ret = 0; + } else { + pr_err("vm[%d] %s failed: pio region not fully accessible for vm[%d].\n",vm->vm_id, __func__, target_vm->vm_id); + } + } + } else { + pr_err("vm[%d] %s failed: target vm not created or not paused.\n",target_vm->vm_id, __func__); + } + + return ret; +} diff --git a/hypervisor/include/arch/x86/asm/guest/vmx_io.h b/hypervisor/include/arch/x86/asm/guest/vmx_io.h index c54919632..b8e35b139 100644 --- a/hypervisor/include/arch/x86/asm/guest/vmx_io.h +++ b/hypervisor/include/arch/x86/asm/guest/vmx_io.h @@ -80,6 +80,18 @@ void allow_guest_pio_access(struct acrn_vm *vm, uint16_t port_address, uint32_ */ void deny_guest_pio_access(struct acrn_vm *vm, uint16_t port_address, uint32_t nbytes); +/** + * @brief Check if a VM has full access to a port I/O range + * + * This API check if given \p vm has direct access to the port I/O space + * starting from \p port_address to \p port_address + \p nbytes - 1. + * + * @param vm The VM whose port I/O access permissions is to be checked + * @param port_address The start address of the port I/O range + * @param nbytes The size of the range, in bytes + */ +bool has_direct_pio_access(struct acrn_vm *vm, uint16_t port_address, uint32_t nbytes); + /** * @brief Fire HSM interrupt to Service VM */ diff --git a/hypervisor/include/common/hypercall.h b/hypervisor/include/common/hypercall.h index c8f8a9831..31af60a48 100644 --- a/hypervisor/include/common/hypercall.h +++ b/hypervisor/include/common/hypercall.h @@ -346,6 +346,34 @@ int32_t hcall_add_vdev(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, uint64 */ int32_t hcall_remove_vdev(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, uint64_t param1, uint64_t param2); +/** + * @brief Assign PIO device resource to VM. + * + * @param vcpu Pointer to vCPU that initiates the hypercall + * @param target_vm Pointer to target VM data structure + * @param param1 not used + * @param param2 guest physical address. This gpa points to data structure of + * acrn_mmiores including assign PIO device resource info + * + * @pre is_service_vm(vcpu->vm) + * @return 0 on success, non-zero on error. + */ +int32_t hcall_assign_pio_region(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, uint64_t param1, uint64_t param2); + +/** + * @brief Deassign PIO device resource from VM. + * + * @param vcpu Pointer to vCPU that initiates the hypercall + * @param target_vm Pointer to target VM data structure + * @param param1 not used + * @param param2 guest physical address. This gpa points to data structure of + * acrn_mmiores including assign PIO device resource info + * + * @pre is_service_vm(vcpu->vm) + * @return 0 on success, non-zero on error. + */ +int32_t hcall_deassign_pio_region(struct acrn_vcpu *vcpu, struct acrn_vm *target_vm, uint64_t param1, uint64_t param2); + /** * @brief Set interrupt mapping info of ptdev. * diff --git a/hypervisor/include/public/acrn_common.h b/hypervisor/include/public/acrn_common.h index 3990e2fc8..e030f7191 100644 --- a/hypervisor/include/public/acrn_common.h +++ b/hypervisor/include/public/acrn_common.h @@ -707,6 +707,19 @@ struct acrn_mmiodev { } res[MMIODEV_RES_NUM]; }; +/** + * @brief Info to assign or deassign a PIO device for a VM + */ +struct acrn_pio_region { + char name[8]; + struct acrn_piores { + /** the io port address for PIO device */ + uint16_t port_address; + /** the size of the io port for the PIO resource */ + uint16_t size; + } res; +}; + /** * @brief Info to create or destroy a virtual PCI or legacy device for a VM * diff --git a/hypervisor/include/public/acrn_hv_defs.h b/hypervisor/include/public/acrn_hv_defs.h index 9d9bc2fb7..034ad87c4 100644 --- a/hypervisor/include/public/acrn_hv_defs.h +++ b/hypervisor/include/public/acrn_hv_defs.h @@ -74,6 +74,8 @@ #define HC_DEASSIGN_MMIODEV BASE_HC_ID(HC_ID, HC_ID_PCI_BASE + 0x08UL) #define HC_ADD_VDEV BASE_HC_ID(HC_ID, HC_ID_PCI_BASE + 0x09UL) #define HC_REMOVE_VDEV BASE_HC_ID(HC_ID, HC_ID_PCI_BASE + 0x0AUL) +#define HC_ASSIGN_PIO_REGION BASE_HC_ID(HC_ID, HC_ID_PCI_BASE + 0x0BUL) +#define HC_DEASSIGN_PIO_REGION BASE_HC_ID(HC_ID, HC_ID_PCI_BASE + 0x0CUL) /* DEBUG */ #define HC_ID_DBG_BASE 0x60UL