diff --git a/hypervisor/arch/x86/guest/vmcall.c b/hypervisor/arch/x86/guest/vmcall.c index 9c9dd9099..ee9ca30d9 100644 --- a/hypervisor/arch/x86/guest/vmcall.c +++ b/hypervisor/arch/x86/guest/vmcall.c @@ -181,6 +181,20 @@ static int32_t dispatch_sos_hypercall(const struct acrn_vcpu *vcpu) } break; + case HC_ASSIGN_PCIDEV: + /* param1: relative vmid to sos, vm_id: absolute vmid */ + if (vmid_is_valid) { + ret = hcall_assign_pcidev(sos_vm, vm_id, param2); + } + break; + + case HC_DEASSIGN_PCIDEV: + /* param1: relative vmid to sos, vm_id: absolute vmid */ + if (vmid_is_valid) { + ret = hcall_deassign_pcidev(sos_vm, vm_id, param2); + } + break; + case HC_SET_PTDEV_INTR_INFO: /* param1: relative vmid to sos, vm_id: absolute vmid */ if (vmid_is_valid) { diff --git a/hypervisor/arch/x86/vtd.c b/hypervisor/arch/x86/vtd.c index 5a7a2113c..bc80ba2a7 100644 --- a/hypervisor/arch/x86/vtd.c +++ b/hypervisor/arch/x86/vtd.c @@ -1046,7 +1046,7 @@ static void dmar_resume(struct dmar_drhd_rt *dmar_unit) dmar_enable_intr_remapping(dmar_unit); } -static int32_t add_iommu_device(struct iommu_domain *domain, uint8_t bus, uint8_t devfun) +static int32_t iommu_attach_device(struct iommu_domain *domain, uint8_t bus, uint8_t devfun) { struct dmar_drhd_rt *dmar_unit; struct dmar_entry *root_table; @@ -1161,7 +1161,7 @@ static int32_t add_iommu_device(struct iommu_domain *domain, uint8_t bus, uint8_ return ret; } -static int32_t remove_iommu_device(const struct iommu_domain *domain, uint8_t bus, uint8_t devfun) +static int32_t iommu_detach_device(const struct iommu_domain *domain, uint8_t bus, uint8_t devfun) { struct dmar_drhd_rt *dmar_unit; struct dmar_entry *root_table; @@ -1309,11 +1309,11 @@ int32_t move_pt_device(const struct iommu_domain *from_domain, struct iommu_doma if (bus_local < CONFIG_IOMMU_BUS_NUM) { if (from_domain != NULL) { - status = remove_iommu_device(from_domain, bus, devfun); + status = iommu_detach_device(from_domain, bus, devfun); } if ((status == 0) && (to_domain != NULL)) { - status = add_iommu_device(to_domain, bus, devfun); + status = iommu_attach_device(to_domain, bus, devfun); } } else { status = -EINVAL; diff --git a/hypervisor/common/hypercall.c b/hypervisor/common/hypercall.c index 69c225593..5e24a3091 100644 --- a/hypervisor/common/hypercall.c +++ b/hypervisor/common/hypercall.c @@ -882,6 +882,66 @@ int32_t hcall_deassign_ptdev(struct acrn_vm *vm, uint16_t vmid, uint64_t param) return ret; } +/** + * @brief Assign one PCI dev to a VM. + * + * @param vm Pointer to VM data structure + * @param vmid ID of the VM + * @param param guest physical address. This gpa points to data structure of + * acrn_assign_pcidev including assign PCI device info + * + * @pre Pointer vm shall point to SOS_VM + * @return 0 on success, non-zero on error. + */ +int32_t hcall_assign_pcidev(struct acrn_vm *vm, uint16_t vmid, uint64_t param) +{ + int32_t ret = -EINVAL; + struct acrn_assign_pcidev pcidev; + struct acrn_vm *target_vm = get_vm_from_vmid(vmid); + + if (!is_poweroff_vm(target_vm) && is_postlaunched_vm(target_vm)) { + if (copy_from_gpa(vm, &pcidev, param, sizeof(pcidev)) != 0) { + pr_err("%s: Unable copy param to vm\n", __func__); + } else { + ret = vpci_assign_pcidev(target_vm, &pcidev); + } + } else { + pr_err("%s, vm[%d] is invalid\n", __func__, vm->vm_id); + } + + return ret; +} + +/** + * @brief Deassign one PCI dev from a VM. + * + * @param vm Pointer to VM data structure + * @param vmid ID of the VM + * @param param guest physical address. This gpa points to data structure of + * acrn_assign_pcidev including deassign PCI device info + * + * @pre Pointer vm shall point to SOS_VM + * @return 0 on success, non-zero on error. + */ +int32_t hcall_deassign_pcidev(struct acrn_vm *vm, uint16_t vmid, uint64_t param) +{ + int32_t ret = -EINVAL; + struct acrn_assign_pcidev pcidev; + struct acrn_vm *target_vm = get_vm_from_vmid(vmid); + + if (!is_poweroff_vm(target_vm) && is_postlaunched_vm(target_vm)) { + if (copy_from_gpa(vm, &pcidev, param, sizeof(pcidev)) != 0) { + pr_err("%s: Unable copy param to vm\n", __func__); + } else { + ret = vpci_deassign_pcidev(target_vm, &pcidev); + } + } else { + pr_err("%s, vm[%d] is invalid\n", __func__, vm->vm_id); + } + + return ret; +} + /** * @brief Set interrupt mapping info of ptdev. * diff --git a/hypervisor/dm/vpci/vpci.c b/hypervisor/dm/vpci/vpci.c index cb0c37f41..4b6741ca7 100644 --- a/hypervisor/dm/vpci/vpci.c +++ b/hypervisor/dm/vpci/vpci.c @@ -27,6 +27,7 @@ * $FreeBSD$ */ +#include #include #include #include @@ -301,7 +302,7 @@ void vpci_cleanup(struct acrn_vm *vm) * @pre vdev->vpci->vm != NULL * @pre vdev->vpci->vm->iommu != NULL */ -static void assign_vdev_pt_iommu_domain(const struct pci_vdev *vdev) +static void assign_vdev_pt_iommu_domain(struct pci_vdev *vdev) { int32_t ret; struct acrn_vm *vm = vdev->vpci->vm; @@ -322,7 +323,7 @@ static void assign_vdev_pt_iommu_domain(const struct pci_vdev *vdev) static void remove_vdev_pt_iommu_domain(const struct pci_vdev *vdev) { int32_t ret; - struct acrn_vm *vm = vdev->vpci->vm; + const struct acrn_vm *vm = vdev->vpci->vm; ret = move_pt_device(vm->iommu, NULL, (uint8_t)vdev->pdev->bdf.bits.b, (uint8_t)(vdev->pdev->bdf.value & 0xFFU)); @@ -464,7 +465,7 @@ static void write_cfg(struct acrn_vpci *vpci, union pci_bdf bdf, * @pre vpci != NULL * @pre vpci.pci_vdev_cnt <= CONFIG_MAX_PCI_DEV_NUM */ -static void vpci_init_vdev(struct acrn_vpci *vpci, struct acrn_vm_pci_dev_config *dev_config) +static struct pci_vdev *vpci_init_vdev(struct acrn_vpci *vpci, struct acrn_vm_pci_dev_config *dev_config) { struct pci_vdev *vdev = &vpci->pci_vdevs[vpci->pci_vdev_cnt]; @@ -484,6 +485,8 @@ static void vpci_init_vdev(struct acrn_vpci *vpci, struct acrn_vm_pci_dev_config } vdev->vdev_ops->init_vdev(vdev); + + return vdev; } /** @@ -496,7 +499,7 @@ static void vpci_init_vdevs(struct acrn_vm *vm) const struct acrn_vm_config *vm_config = get_vm_config(vpci->vm->vm_id); for (idx = 0U; idx < vm_config->pci_dev_num; idx++) { - vpci_init_vdev(vpci, &vm_config->pci_devs[idx]); + (void)vpci_init_vdev(vpci, &vm_config->pci_devs[idx]); } } @@ -630,3 +633,112 @@ void vpci_reset_ptdev_intr_info(struct acrn_vm *target_vm, uint16_t vbdf, uint16 } spinlock_release(&sos_vm->vpci.lock); } + +/** + * @brief assign a PCI device from SOS to target post-launched VM. + * + * @pre tgt_vm != NULL + * @pre pcidev != NULL + */ +int32_t vpci_assign_pcidev(struct acrn_vm *tgt_vm, struct acrn_assign_pcidev *pcidev) +{ + int32_t ret = 0; + uint32_t idx; + struct pci_vdev *vdev_in_sos, *vdev; + struct acrn_vpci *vpci; + union pci_bdf bdf; + struct acrn_vm *sos_vm; + + bdf.value = pcidev->phys_bdf; + sos_vm = get_sos_vm(); + spinlock_obtain(&sos_vm->vpci.lock); + vdev_in_sos = pci_find_vdev(&sos_vm->vpci, bdf); + if ((vdev_in_sos != NULL) && (vdev_in_sos->vpci->vm == sos_vm) && (vdev_in_sos->pdev != NULL)) { + /* ToDo: Each PT device must support one type reset */ + if (!vdev_in_sos->pdev->has_pm_reset && !vdev_in_sos->pdev->has_flr && + !vdev_in_sos->pdev->has_af_flr) { + pr_fatal("%s %x:%x.%x not support FLR or not support PM reset\n", + __func__, bdf.bits.b, bdf.bits.d, bdf.bits.f); + } else { + /* DM will reset this device before assigning it */ + pdev_restore_bar(vdev_in_sos->pdev); + } + + remove_vdev_pt_iommu_domain(vdev_in_sos); + if (ret == 0) { + vpci = &(tgt_vm->vpci); + vdev_in_sos->vpci = vpci; + + spinlock_obtain(&tgt_vm->vpci.lock); + vdev = vpci_init_vdev(vpci, vdev_in_sos->pci_dev_config); + pci_vdev_write_cfg_u8(vdev, PCIR_INTERRUPT_LINE, pcidev->intr_line); + pci_vdev_write_cfg_u8(vdev, PCIR_INTERRUPT_PIN, pcidev->intr_pin); + for (idx = 0U; idx < vdev->nr_bars; idx++) { + pci_vdev_write_bar(vdev, idx, pcidev->bar[idx]); + } + + vdev->bdf.value = pcidev->virt_bdf; + spinlock_release(&tgt_vm->vpci.lock); + vdev_in_sos->new_owner = vdev; + } + } else { + pr_fatal("%s, can't find PCI device %x:%x.%x for vm[%d] %x:%x.%x\n", __func__, + pcidev->phys_bdf >> 8U, (pcidev->phys_bdf >> 3U) & 0x1fU, pcidev->phys_bdf & 0x7U, + tgt_vm->vm_id, + pcidev->virt_bdf >> 8U, (pcidev->virt_bdf >> 3U) & 0x1fU, pcidev->virt_bdf & 0x7U); + ret = -ENODEV; + } + spinlock_release(&sos_vm->vpci.lock); + + return ret; +} + +/** + * @brief deassign a PCI device from target post-launched VM to SOS. + * + * @pre tgt_vm != NULL + * @pre pcidev != NULL + */ +int32_t vpci_deassign_pcidev(struct acrn_vm *tgt_vm, struct acrn_assign_pcidev *pcidev) +{ + int32_t ret = 0; + struct pci_vdev *vdev_in_sos, *vdev; + union pci_bdf bdf; + struct acrn_vm *sos_vm; + + bdf.value = pcidev->phys_bdf; + sos_vm = get_sos_vm(); + spinlock_obtain(&sos_vm->vpci.lock); + vdev_in_sos = pci_find_vdev(&sos_vm->vpci, bdf); + if ((vdev_in_sos != NULL) && (vdev_in_sos->vpci->vm == tgt_vm) && (vdev_in_sos->pdev != NULL)) { + vdev = vdev_in_sos->new_owner; + + spinlock_obtain(&tgt_vm->vpci.lock); + + if (vdev != NULL) { + ret = move_pt_device(tgt_vm->iommu, sos_vm->iommu, (uint8_t)(pcidev->phys_bdf >> 8U), + (uint8_t)(pcidev->phys_bdf & 0xffU)); + if (ret != 0) { + panic("failed to assign iommu device!"); + } + + deinit_vmsi(vdev); + + deinit_vmsix(vdev); + + } + spinlock_release(&tgt_vm->vpci.lock); + + vdev_in_sos->vpci = &sos_vm->vpci; + vdev_in_sos->new_owner = NULL; + } else { + pr_fatal("%s, can't find PCI device %x:%x.%x for vm[%d] %x:%x.%x\n", __func__, + pcidev->phys_bdf >> 8U, (pcidev->phys_bdf >> 3U) & 0x1fU, pcidev->phys_bdf & 0x7U, + tgt_vm->vm_id, + pcidev->virt_bdf >> 8U, (pcidev->virt_bdf >> 3U) & 0x1fU, pcidev->virt_bdf & 0x7U); + ret = -ENODEV; + } + spinlock_release(&sos_vm->vpci.lock); + + return ret; +} diff --git a/hypervisor/include/common/hypercall.h b/hypervisor/include/common/hypercall.h index 2bcc705d2..8ae7a3389 100644 --- a/hypervisor/include/common/hypercall.h +++ b/hypervisor/include/common/hypercall.h @@ -272,6 +272,32 @@ int32_t hcall_assign_ptdev(struct acrn_vm *vm, uint16_t vmid, uint64_t param); */ int32_t hcall_deassign_ptdev(struct acrn_vm *vm, uint16_t vmid, uint64_t param); +/** + * @brief Assign one PCI dev to VM. + * + * @param vm Pointer to VM data structure + * @param vmid ID of the VM + * @param param guest physical address. This gpa points to data structure of + * acrn_assign_pcidev including assign PCI device info + * + * @pre Pointer vm shall point to SOS_VM + * @return 0 on success, non-zero on error. + */ +int32_t hcall_assign_pcidev(struct acrn_vm *vm, uint16_t vmid, uint64_t param); + +/** + * @brief Deassign one PCI dev to VM. + * + * @param vm Pointer to VM data structure + * @param vmid ID of the VM + * @param param guest physical address. This gpa points to data structure of + * acrn_assign_pcidev including deassign PCI device info + * + * @pre Pointer vm shall point to SOS_VM + * @return 0 on success, non-zero on error. + */ +int32_t hcall_deassign_pcidev(struct acrn_vm *vm, uint16_t vmid, uint64_t param); + /** * @brief Set interrupt mapping info of ptdev. * diff --git a/hypervisor/include/dm/vpci.h b/hypervisor/include/dm/vpci.h index 02f4f351b..f0ce6ba12 100644 --- a/hypervisor/include/dm/vpci.h +++ b/hypervisor/include/dm/vpci.h @@ -134,5 +134,8 @@ void vpci_cleanup(struct acrn_vm *vm); struct pci_vdev *pci_find_vdev(struct acrn_vpci *vpci, union pci_bdf vbdf); void vpci_set_ptdev_intr_info(struct acrn_vm *target_vm, uint16_t vbdf, uint16_t pbdf); void vpci_reset_ptdev_intr_info(struct acrn_vm *target_vm, uint16_t vbdf, uint16_t pbdf); +struct acrn_assign_pcidev; +int32_t vpci_assign_pcidev(struct acrn_vm *tgt_vm, struct acrn_assign_pcidev *pcidev); +int32_t vpci_deassign_pcidev(struct acrn_vm *tgt_vm, struct acrn_assign_pcidev *pcidev); #endif /* VPCI_H_ */ diff --git a/hypervisor/include/hw/pci.h b/hypervisor/include/hw/pci.h index 3230fbfe2..6f9d9e0ac 100644 --- a/hypervisor/include/hw/pci.h +++ b/hypervisor/include/hw/pci.h @@ -90,6 +90,8 @@ #define PCIR_CAP_PTR_CARDBUS 0x14U #define PCI_BASE_ADDRESS_MEM_MASK (~0x0fUL) #define PCI_BASE_ADDRESS_IO_MASK (~0x03UL) +#define PCIR_INTERRUPT_LINE 0x3cU +#define PCIR_INTERRUPT_PIN 0x3dU /* config registers for header type 1 (PCI-to-PCI bridge) devices */ #define PCIR_PRIBUS_1 0x18U diff --git a/hypervisor/include/public/acrn_hv_defs.h b/hypervisor/include/public/acrn_hv_defs.h index 1c5b1ab07..b2f94048a 100644 --- a/hypervisor/include/public/acrn_hv_defs.h +++ b/hypervisor/include/public/acrn_hv_defs.h @@ -64,6 +64,8 @@ #define HC_VM_PCI_MSIX_REMAP BASE_HC_ID(HC_ID, HC_ID_PCI_BASE + 0x02UL) #define HC_SET_PTDEV_INTR_INFO BASE_HC_ID(HC_ID, HC_ID_PCI_BASE + 0x03UL) #define HC_RESET_PTDEV_INTR_INFO BASE_HC_ID(HC_ID, HC_ID_PCI_BASE + 0x04UL) +#define HC_ASSIGN_PCIDEV BASE_HC_ID(HC_ID, HC_ID_PCI_BASE + 0x05UL) +#define HC_DEASSIGN_PCIDEV BASE_HC_ID(HC_ID, HC_ID_PCI_BASE + 0x06UL) /* DEBUG */ #define HC_ID_DBG_BASE 0x60UL @@ -271,6 +273,39 @@ struct hc_ptdev_irq { } is; /* irq source */ } __aligned(8); +/** + * @brief Info to assign or deassign PCI for a VM + * + * the parameter for HC_ASSIGN_PCIDEV or HC_DEASSIGN_PCIDEV hypercall + */ +struct acrn_assign_pcidev { + /** reversed for externed compatibility */ + uint32_t rsvd1; + + /** virtual BDF# of the pass-through PCI device */ + uint16_t virt_bdf; + + /** physical BDF# of the pass-through PCI device */ + uint16_t phys_bdf; + + /** the PCI Interrupt Line, initialized by ACRN-DM, which is RO and + * ideally not used for pass-through MSI/MSI-x devices. + */ + uint8_t intr_line; + + /** the PCI Interrupt Pin, initialized by ACRN-DM, which is RO and + * ideally not used for pass-through MSI/MSI-x devices. + */ + uint8_t intr_pin; + + /** the base address of the PCI BAR, initialized by ACRN-DM. */ + uint32_t bar[6]; + + /** reserved for extension */ + uint32_t rsvd2[6]; + +} __attribute__((aligned(8))); + /** * Hypervisor api version info, return it for HC_GET_API_VERSION hypercall */