acrn-hypervisor/hypervisor/dm/vpci/vpci.c
Li Fei1 572f755037 hv: vm: refine the devices unregistration sequence of vm shutdown
Conceptually, the devices unregistration sequence of the shutdown process should be
opposite to create.

Tracked-On: #4550
Signed-off-by: Li Fei1 <fei1.li@intel.com>
2020-04-08 10:13:37 +08:00

850 lines
25 KiB
C

/*-
* Copyright (c) 2011 NetApp, Inc.
* Copyright (c) 2018 Intel Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``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 NETAPP, INC 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.
*
* $FreeBSD$
*/
#include <errno.h>
#include <ptdev.h>
#include <vm.h>
#include <vtd.h>
#include <io.h>
#include <mmu.h>
#include <logmsg.h>
#include "vpci_priv.h"
#include "pci_dev.h"
static void vpci_init_vdevs(struct acrn_vm *vm);
static void deinit_prelaunched_vm_vpci(struct acrn_vm *vm);
static void deinit_postlaunched_vm_vpci(struct acrn_vm *vm);
static int32_t vpci_read_cfg(struct acrn_vpci *vpci, union pci_bdf bdf, uint32_t offset, uint32_t bytes, uint32_t *val);
static int32_t vpci_write_cfg(struct acrn_vpci *vpci, union pci_bdf bdf, uint32_t offset, uint32_t bytes, uint32_t val);
static struct pci_vdev *find_available_vdev(struct acrn_vpci *vpci, union pci_bdf bdf);
/**
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
*/
static bool vpci_pio_cfgaddr_read(struct acrn_vcpu *vcpu, uint16_t addr, size_t bytes)
{
uint32_t val = ~0U;
struct acrn_vpci *vpci = &vcpu->vm->vpci;
union pci_cfg_addr_reg *cfg_addr = &vpci->addr;
struct pio_request *pio_req = &vcpu->req.reqs.pio;
if ((addr == (uint16_t)PCI_CONFIG_ADDR) && (bytes == 4U)) {
val = cfg_addr->value;
}
pio_req->value = val;
return true;
}
/**
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
*
* @retval true on success.
* @retval false. (ACRN will deliver this IO request to DM to handle for post-launched VM)
*/
static bool vpci_pio_cfgaddr_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t bytes, uint32_t val)
{
bool ret = true;
struct acrn_vpci *vpci = &vcpu->vm->vpci;
union pci_cfg_addr_reg *cfg_addr = &vpci->addr;
union pci_bdf vbdf;
if ((addr == (uint16_t)PCI_CONFIG_ADDR) && (bytes == 4U)) {
/* unmask reserved fields: BITs 24-30 and BITs 0-1 */
cfg_addr->value = val & (~0x7f000003U);
if (is_postlaunched_vm(vcpu->vm)) {
const struct pci_vdev *vdev;
vbdf.value = cfg_addr->bits.bdf;
vdev = find_available_vdev(vpci, vbdf);
/* For post-launched VM, ACRN HV will only handle PT device,
* all virtual PCI device and QUIRK PT device
* still need to deliver to ACRN DM to handle.
*/
if ((vdev == NULL) || is_quirk_ptdev(vdev)) {
ret = false;
}
}
}
return ret;
}
static inline bool vpci_is_valid_access_offset(uint32_t offset, uint32_t bytes)
{
return ((offset & (bytes - 1U)) == 0U);
}
static inline bool vpci_is_valid_access_byte(uint32_t bytes)
{
return ((bytes == 1U) || (bytes == 2U) || (bytes == 4U));
}
static inline bool vpci_is_valid_access(uint32_t offset, uint32_t bytes)
{
return (vpci_is_valid_access_byte(bytes) && vpci_is_valid_access_offset(offset, bytes));
}
/**
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
* @pre vcpu->vm->vm_id < CONFIG_MAX_VM_NUM
* @pre (get_vm_config(vcpu->vm->vm_id)->load_order == PRE_LAUNCHED_VM)
* || (get_vm_config(vcpu->vm->vm_id)->load_order == SOS_VM)
*
* @retval true on success.
* @retval false. (ACRN will deliver this IO request to DM to handle for post-launched VM)
*/
static bool vpci_pio_cfgdata_read(struct acrn_vcpu *vcpu, uint16_t addr, size_t bytes)
{
int32_t ret = 0;
struct acrn_vm *vm = vcpu->vm;
struct acrn_vpci *vpci = &vm->vpci;
union pci_cfg_addr_reg cfg_addr;
union pci_bdf bdf;
uint16_t offset = addr - PCI_CONFIG_DATA;
uint32_t val = ~0U;
struct pio_request *pio_req = &vcpu->req.reqs.pio;
cfg_addr.value = atomic_readandclear32(&vpci->addr.value);
if (cfg_addr.bits.enable != 0U) {
if (vpci_is_valid_access(cfg_addr.bits.reg_num + offset, bytes)) {
bdf.value = cfg_addr.bits.bdf;
ret = vpci_read_cfg(vpci, bdf, cfg_addr.bits.reg_num + offset, bytes, &val);
}
}
pio_req->value = val;
return (ret == 0);
}
/**
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
* @pre vcpu->vm->vm_id < CONFIG_MAX_VM_NUM
* @pre (get_vm_config(vcpu->vm->vm_id)->load_order == PRE_LAUNCHED_VM)
* || (get_vm_config(vcpu->vm->vm_id)->load_order == SOS_VM)
*
* @retval true on success.
* @retval false. (ACRN will deliver this IO request to DM to handle for post-launched VM)
*/
static bool vpci_pio_cfgdata_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t bytes, uint32_t val)
{
int32_t ret = 0;
struct acrn_vm *vm = vcpu->vm;
struct acrn_vpci *vpci = &vm->vpci;
union pci_cfg_addr_reg cfg_addr;
union pci_bdf bdf;
uint16_t offset = addr - PCI_CONFIG_DATA;
cfg_addr.value = atomic_readandclear32(&vpci->addr.value);
if (cfg_addr.bits.enable != 0U) {
if (vpci_is_valid_access(cfg_addr.bits.reg_num + offset, bytes)) {
bdf.value = cfg_addr.bits.bdf;
ret = vpci_write_cfg(vpci, bdf, cfg_addr.bits.reg_num + offset, bytes, val);
}
}
return (ret == 0);
}
/**
* @pre io_req != NULL && private_data != NULL
*
* @retval 0 on success.
* @retval other on false. (ACRN will deliver this MMIO request to DM to handle for post-launched VM)
*/
static int32_t vpci_mmio_cfg_access(struct io_request *io_req, void *private_data)
{
int32_t ret = 0;
struct mmio_request *mmio = &io_req->reqs.mmio;
struct acrn_vpci *vpci = (struct acrn_vpci *)private_data;
uint64_t pci_mmcofg_base = vpci->pci_mmcfg_base;
uint64_t address = mmio->address;
uint32_t reg_num = (uint32_t)(address & 0xfffUL);
union pci_bdf bdf;
/**
* Enhanced Configuration Address Mapping
* A[(20+n-1):20] Bus Number 1 ≤ n ≤ 8
* A[19:15] Device Number
* A[14:12] Function Number
* A[11:8] Extended Register Number
* A[7:2] Register Number
* A[1:0] Along with size of the access, used to generate Byte Enables
*/
bdf.value = (uint16_t)((address - pci_mmcofg_base) >> 12U);
if (mmio->direction == REQUEST_READ) {
if (!is_plat_hidden_pdev(bdf)) {
ret = vpci_read_cfg(vpci, bdf, reg_num, (uint32_t)mmio->size, (uint32_t *)&mmio->value);
} else {
/* expose and pass through platform hidden devices to SOS */
mmio->value = (uint64_t)pci_pdev_read_cfg(bdf, reg_num, (uint32_t)mmio->size);
}
} else {
if (!is_plat_hidden_pdev(bdf)) {
ret = vpci_write_cfg(vpci, bdf, reg_num, (uint32_t)mmio->size, (uint32_t)mmio->value);
} else {
/* expose and pass through platform hidden devices to SOS */
pci_pdev_write_cfg(bdf, reg_num, (uint32_t)mmio->size, (uint32_t)mmio->value);
}
}
return ret;
}
/**
* @pre vm != NULL
* @pre vm->vm_id < CONFIG_MAX_VM_NUM
*/
void init_vpci(struct acrn_vm *vm)
{
struct vm_io_range pci_cfgaddr_range = {
.base = PCI_CONFIG_ADDR,
.len = 1U
};
struct vm_io_range pci_cfgdata_range = {
.base = PCI_CONFIG_DATA,
.len = 4U
};
struct acrn_vm_config *vm_config;
uint64_t pci_mmcfg_base;
vm->iommu = create_iommu_domain(vm->vm_id, hva2hpa(vm->arch_vm.nworld_eptp), 48U);
/* Build up vdev list for vm */
vpci_init_vdevs(vm);
vm_config = get_vm_config(vm->vm_id);
if (vm_config->load_order != PRE_LAUNCHED_VM) {
/* PCI MMCONFIG for post-launched VM is fixed to 0xE0000000 */
pci_mmcfg_base = (vm_config->load_order == SOS_VM) ? get_mmcfg_base() : 0xE0000000UL;
vm->vpci.pci_mmcfg_base = pci_mmcfg_base;
register_mmio_emulation_handler(vm, vpci_mmio_cfg_access,
pci_mmcfg_base, pci_mmcfg_base + PCI_MMCONFIG_SIZE, &vm->vpci, false);
}
/* Intercept and handle I/O ports CF8h */
register_pio_emulation_handler(vm, PCI_CFGADDR_PIO_IDX, &pci_cfgaddr_range,
vpci_pio_cfgaddr_read, vpci_pio_cfgaddr_write);
/* Intercept and handle I/O ports CFCh -- CFFh */
register_pio_emulation_handler(vm, PCI_CFGDATA_PIO_IDX, &pci_cfgdata_range,
vpci_pio_cfgdata_read, vpci_pio_cfgdata_write);
spinlock_init(&vm->vpci.lock);
}
/**
* @pre vm != NULL
* @pre vm->vm_id < CONFIG_MAX_VM_NUM
*/
void deinit_vpci(struct acrn_vm *vm)
{
struct acrn_vm_config *vm_config;
vm_config = get_vm_config(vm->vm_id);
switch (vm_config->load_order) {
case PRE_LAUNCHED_VM:
case SOS_VM:
/* deinit function for both SOS and pre-launched VMs (consider sos also as pre-launched) */
deinit_prelaunched_vm_vpci(vm);
break;
case POST_LAUNCHED_VM:
deinit_postlaunched_vm_vpci(vm);
break;
default:
/* Unsupported VM type - Do nothing */
break;
}
ptdev_release_all_entries(vm);
/* Free iommu */
destroy_iommu_domain(vm->iommu);
}
/**
* @pre vdev != NULL
* @pre vdev->vpci != NULL
* @pre vdev->vpci->vm != NULL
* @pre vdev->vpci->vm->iommu != NULL
*/
static void assign_vdev_pt_iommu_domain(struct pci_vdev *vdev)
{
int32_t ret;
struct acrn_vm *vm = vpci2vm(vdev->vpci);
ret = move_pt_device(NULL, vm->iommu, (uint8_t)vdev->pdev->bdf.bits.b,
(uint8_t)(vdev->pdev->bdf.value & 0xFFU));
if (ret != 0) {
panic("failed to assign iommu device!");
}
}
/**
* @pre vdev != NULL
* @pre vdev->vpci != NULL
* @pre vdev->vpci->vm != NULL
* @pre vdev->vpci->vm->iommu != NULL
*/
static void remove_vdev_pt_iommu_domain(const struct pci_vdev *vdev)
{
int32_t ret;
const struct acrn_vm *vm = vpci2vm(vdev->vpci);
ret = move_pt_device(vm->iommu, NULL, (uint8_t)vdev->pdev->bdf.bits.b,
(uint8_t)(vdev->pdev->bdf.value & 0xFFU));
if (ret != 0) {
/*
*TODO
* panic needs to be removed here
* Currently unassign_pt_device can fail for multiple reasons
* Once all the reasons and methods to avoid them can be made sure
* panic here is not necessary.
*/
panic("failed to unassign iommu device!");
}
}
/**
* @brief Find an available vdev structure with BDF from a specified vpci structure.
* If the vdev's vpci is the same as the specified vpci, the vdev is available.
* If the vdev's vpci is not the same as the specified vpci, the vdev has already
* been assigned and it is unavailable for SOS.
* If the vdev's vpci is NULL, the vdev is a orphan/zombie instance, it can't
* be accessed by any vpci.
*
* @param vpci Pointer to a specified vpci structure
* @param bdf Indicate the vdev's BDF
*
* @pre vpci != NULL
* @pre vpci->vm != NULL
*
* @return Return a available vdev instance, otherwise return NULL
*/
static struct pci_vdev *find_available_vdev(struct acrn_vpci *vpci, union pci_bdf bdf)
{
struct pci_vdev *vdev = pci_find_vdev(vpci, bdf);
if ((vdev != NULL) && (vdev->vpci != vpci)) {
/* In the case a device is assigned to a UOS and is not in a zombie state */
if ((vdev->new_owner != NULL) && (vdev->new_owner->vpci != NULL)) {
/* the SOS is able to access, if and only if the SOS has higher severity than the UOS. */
if (get_vm_severity(vpci2vm(vpci)->vm_id) <
get_vm_severity(vpci2vm(vdev->new_owner->vpci)->vm_id)) {
vdev = NULL;
}
} else {
vdev = NULL;
}
}
return vdev;
}
static void vpci_init_pt_dev(struct pci_vdev *vdev)
{
/*
* Here init_vdev_pt() needs to be called after init_vmsix() for the following reason:
* init_vdev_pt() will indirectly call has_msix_cap(), which
* requires init_vmsix() to be called first.
*/
init_vmsi(vdev);
init_vmsix(vdev);
init_vsriov(vdev);
init_vdev_pt(vdev, false);
assign_vdev_pt_iommu_domain(vdev);
}
static void vpci_deinit_pt_dev(struct pci_vdev *vdev)
{
deinit_vdev_pt(vdev);
remove_vdev_pt_iommu_domain(vdev);
deinit_vmsix(vdev);
deinit_vmsi(vdev);
}
struct cfg_header_perm {
/* For each 4-byte register defined in PCI config space header,
* there is one bit dedicated for it in pt_mask and ro_mask.
* For example, bit 0 for CFG Vendor ID and Device ID register,
* Bit 1 for CFG register Command and Status register, and so on.
*
* For each mask, only low 16-bits takes effect.
*
* If bit x is set the pt_mask, it indicates that the corresponding 4 Bytes register
* for bit x is pass through to guest. Otherwise, it's virtualized.
*
* If bit x is set the ro_mask, it indicates that the corresponding 4 Bytes register
* for bit x is read-only. Otherwise, it's writable.
*/
uint32_t pt_mask;
uint32_t ro_mask;
};
static const struct cfg_header_perm cfg_hdr_perm = {
/* Only Command (0x04-0x05) and Status (0x06-0x07) Registers are pass through */
.pt_mask = 0x0002U,
/* Command (0x04-0x05) and Status (0x06-0x07) Registers and
* Base Address Registers (0x10-0x27) are writable */
.ro_mask = (uint16_t)~0x03f2U
};
/*
* @pre offset + bytes < PCI_CFG_HEADER_LENGTH
*/
static void read_cfg_header(const struct pci_vdev *vdev,
uint32_t offset, uint32_t bytes, uint32_t *val)
{
if (vbar_access(vdev, offset)) {
/* bar access must be 4 bytes and offset must also be 4 bytes aligned */
if ((bytes == 4U) && ((offset & 0x3U) == 0U)) {
*val = pci_vdev_read_vbar(vdev, pci_bar_index(offset));
} else {
*val = ~0U;
}
} else {
if (bitmap32_test(((uint16_t)offset) >> 2U, &cfg_hdr_perm.pt_mask)) {
*val = pci_pdev_read_cfg(vdev->pdev->bdf, offset, bytes);
/* MSE(Memory Space Enable) bit always be set for an assigned VF */
if ((vdev->phyfun != NULL) && (offset == PCIR_COMMAND) &&
(vdev->vpci != vdev->phyfun->vpci)) {
*val |= PCIM_CMD_MEMEN;
}
} else {
*val = pci_vdev_read_vcfg(vdev, offset, bytes);
}
}
}
/*
* @pre offset + bytes < PCI_CFG_HEADER_LENGTH
*/
static void write_cfg_header(struct pci_vdev *vdev,
uint32_t offset, uint32_t bytes, uint32_t val)
{
if (vbar_access(vdev, offset)) {
/* bar write access must be 4 bytes and offset must also be 4 bytes aligned */
if ((bytes == 4U) && ((offset & 0x3U) == 0U)) {
vdev_pt_write_vbar(vdev, pci_bar_index(offset), val);
}
} else {
if (offset == PCIR_COMMAND) {
#define PCIM_SPACE_EN (PCIM_CMD_PORTEN | PCIM_CMD_MEMEN)
uint16_t phys_cmd = (uint16_t)pci_pdev_read_cfg(vdev->pdev->bdf, PCIR_COMMAND, 2U);
/* check whether need to restore BAR because some kind of reset */
if (((phys_cmd & PCIM_SPACE_EN) == 0U) && ((val & PCIM_SPACE_EN) != 0U) &&
pdev_need_bar_restore(vdev->pdev)) {
pdev_restore_bar(vdev->pdev);
}
}
if (!bitmap32_test(((uint16_t)offset) >> 2U, &cfg_hdr_perm.ro_mask)) {
if (bitmap32_test(((uint16_t)offset) >> 2U, &cfg_hdr_perm.pt_mask)) {
pci_pdev_write_cfg(vdev->pdev->bdf, offset, bytes, val);
} else {
pci_vdev_write_vcfg(vdev, offset, bytes, val);
}
}
}
}
static int32_t write_pt_dev_cfg(struct pci_vdev *vdev, uint32_t offset,
uint32_t bytes, uint32_t val)
{
int32_t ret = 0;
if (cfg_header_access(offset)) {
write_cfg_header(vdev, offset, bytes, val);
} else if (msicap_access(vdev, offset)) {
write_vmsi_cfg(vdev, offset, bytes, val);
} else if (msixcap_access(vdev, offset)) {
write_vmsix_cfg(vdev, offset, bytes, val);
} else if (sriovcap_access(vdev, offset)) {
write_sriov_cap_reg(vdev, offset, bytes, val);
} else {
if (!is_quirk_ptdev(vdev)) {
/* passthru to physical device */
pci_pdev_write_cfg(vdev->pdev->bdf, offset, bytes, val);
} else {
ret = -ENODEV;
}
}
return ret;
}
static int32_t read_pt_dev_cfg(const struct pci_vdev *vdev, uint32_t offset,
uint32_t bytes, uint32_t *val)
{
int32_t ret = 0;
if (cfg_header_access(offset)) {
read_cfg_header(vdev, offset, bytes, val);
} else if (msicap_access(vdev, offset)) {
read_vmsi_cfg(vdev, offset, bytes, val);
} else if (msixcap_access(vdev, offset)) {
read_vmsix_cfg(vdev, offset, bytes, val);
} else if (sriovcap_access(vdev, offset)) {
read_sriov_cap_reg(vdev, offset, bytes, val);
} else {
if (!is_quirk_ptdev(vdev)) {
/* passthru to physical device */
*val = pci_pdev_read_cfg(vdev->pdev->bdf, offset, bytes);
} else {
ret = -ENODEV;
}
}
return ret;
}
static const struct pci_vdev_ops pci_pt_dev_ops = {
.init_vdev = vpci_init_pt_dev,
.deinit_vdev = vpci_deinit_pt_dev,
.write_vdev_cfg = write_pt_dev_cfg,
.read_vdev_cfg = read_pt_dev_cfg,
};
/**
* @pre vpci != NULL
*/
static int32_t vpci_read_cfg(struct acrn_vpci *vpci, union pci_bdf bdf,
uint32_t offset, uint32_t bytes, uint32_t *val)
{
int32_t ret = 0;
struct pci_vdev *vdev;
spinlock_obtain(&vpci->lock);
vdev = find_available_vdev(vpci, bdf);
if (vdev != NULL) {
ret = vdev->vdev_ops->read_vdev_cfg(vdev, offset, bytes, val);
} else {
if (is_postlaunched_vm(vpci2vm(vpci))) {
ret = -ENODEV;
}
}
spinlock_release(&vpci->lock);
return ret;
}
/**
* @pre vpci != NULL
*/
static int32_t vpci_write_cfg(struct acrn_vpci *vpci, union pci_bdf bdf,
uint32_t offset, uint32_t bytes, uint32_t val)
{
int32_t ret = 0;
struct pci_vdev *vdev;
spinlock_obtain(&vpci->lock);
vdev = find_available_vdev(vpci, bdf);
if (vdev != NULL) {
ret = vdev->vdev_ops->write_vdev_cfg(vdev, offset, bytes, val);
} else {
if (!is_postlaunched_vm(vpci2vm(vpci))) {
pr_acrnlog("%s %x:%x.%x not found! off: 0x%x, val: 0x%x\n", __func__,
bdf.bits.b, bdf.bits.d, bdf.bits.f, offset, val);
} else {
ret = -ENODEV;
}
}
spinlock_release(&vpci->lock);
return ret;
}
/**
* @brief Initialize a vdev structure.
*
* The function vpci_init_vdev is used to initialize a vdev structure with a PCI device configuration(dev_config)
* on a specified vPCI bus(vpci). If the function vpci_init_vdev initializes a SRIOV Virtual Function(VF) vdev structure,
* the parameter parent_pf_vdev is the VF associated Physical Function(PF) vdev structure, otherwise the parameter parent_pf_vdev is NULL.
* The caller of the function vpci_init_vdev should guarantee execution atomically.
*
* @param vpci Pointer to a vpci structure
* @param dev_config Pointer to a dev_config structure of the vdev
* @param parent_pf_vdev If the parameter def_config points to a SRIOV VF vdev, this parameter parent_pf_vdev indicates the parent PF vdev.
* Otherwise, it is NULL.
*
* @pre vpci != NULL
* @pre vpci.pci_vdev_cnt <= CONFIG_MAX_PCI_DEV_NUM
*
* @return If there's a successfully initialized vdev structure return it, otherwise return NULL;
*/
struct pci_vdev *vpci_init_vdev(struct acrn_vpci *vpci, struct acrn_vm_pci_dev_config *dev_config, struct pci_vdev *parent_pf_vdev)
{
struct pci_vdev *vdev = &vpci->pci_vdevs[vpci->pci_vdev_cnt];
vpci->pci_vdev_cnt++;
vdev->vpci = vpci;
vdev->bdf.value = dev_config->vbdf.value;
vdev->pdev = dev_config->pdev;
vdev->pci_dev_config = dev_config;
vdev->phyfun = parent_pf_vdev;
if (dev_config->vdev_ops != NULL) {
vdev->vdev_ops = dev_config->vdev_ops;
} else {
if (vdev->pdev->hdr_type == PCIM_HDRTYPE_BRIDGE) {
vdev->vdev_ops = &vpci_bridge_ops;
} else {
vdev->vdev_ops = &pci_pt_dev_ops;
}
ASSERT(dev_config->emu_type == PCI_DEV_TYPE_PTDEV,
"Only PCI_DEV_TYPE_PTDEV could not configure vdev_ops");
ASSERT(dev_config->pdev != NULL, "PCI PTDev is not present on platform!");
}
vdev->vdev_ops->init_vdev(vdev);
return vdev;
}
/**
* @pre vm != NULL
*/
static void vpci_init_vdevs(struct acrn_vm *vm)
{
uint32_t idx;
struct acrn_vpci *vpci = &(vm->vpci);
const struct acrn_vm_config *vm_config = get_vm_config(vpci2vm(vpci)->vm_id);
for (idx = 0U; idx < vm_config->pci_dev_num; idx++) {
(void)vpci_init_vdev(vpci, &vm_config->pci_devs[idx], NULL);
}
}
/**
* @pre vm != NULL
* @pre vm->vpci.pci_vdev_cnt <= CONFIG_MAX_PCI_DEV_NUM
* @pre is_sos_vm(vm) || is_prelaunched_vm(vm)
*/
static void deinit_prelaunched_vm_vpci(struct acrn_vm *vm)
{
struct pci_vdev *vdev;
uint32_t i;
for (i = 0U; i < vm->vpci.pci_vdev_cnt; i++) {
vdev = (struct pci_vdev *) &(vm->vpci.pci_vdevs[i]);
/* Only deinit the VM's own devices */
if (is_own_device(vm, vdev)) {
vdev->vdev_ops->deinit_vdev(vdev);
}
}
}
/**
* @pre vm != NULL && Pointer vm shall point to SOS_VM
* @pre vm->vpci.pci_vdev_cnt <= CONFIG_MAX_PCI_DEV_NUM
* @pre is_postlaunched_vm(vm) == true
*/
static void deinit_postlaunched_vm_vpci(struct acrn_vm *vm)
{
struct acrn_vm *sos_vm;
uint32_t i;
struct pci_vdev *vdev, *target_vdev;
int32_t ret;
/* PCI resources
* 1) IOMMU domain switch
* 2) Relese UOS MSI host IRQ/IRTE
* 3) Update vdev info in SOS vdev
* Cleanup mentioned above is taken care when DM releases UOS resources
* during a UOS reboot or shutdown
* In the following cases, where DM does not get chance to cleanup
* 1) DM crash/segfault
* 2) SOS triple fault/hang
* 3) SOS reboot before shutting down POST_LAUNCHED_VMs
* ACRN must cleanup
*/
sos_vm = get_sos_vm();
spinlock_obtain(&sos_vm->vpci.lock);
for (i = 0U; i < sos_vm->vpci.pci_vdev_cnt; i++) {
vdev = (struct pci_vdev *)&(sos_vm->vpci.pci_vdevs[i]);
/* Only deinit the VM's own devices */
if (is_own_device(vm, vdev)) {
spinlock_obtain(&vm->vpci.lock);
target_vdev = vdev->new_owner;
ret = move_pt_device(vm->iommu, sos_vm->iommu, (uint8_t)target_vdev->pdev->bdf.bits.b,
(uint8_t)(target_vdev->pdev->bdf.value & 0xFFU));
if (ret != 0) {
panic("failed to assign iommu device!");
}
deinit_vmsi(target_vdev);
deinit_vmsix(target_vdev);
/* Move vdev pointers back to SOS*/
vdev->vpci = (struct acrn_vpci *) &sos_vm->vpci;
vdev->new_owner = NULL;
spinlock_release(&vm->vpci.lock);
}
}
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);
/*
* TODO We didn't support SR-IOV capability of PF in UOS for now, we should
* hide the SR-IOV capability if we pass through the PF to a UOS.
*
* For now, we don't support assignment of PF to a UOS.
*/
if ((vdev_in_sos != NULL) && is_own_device(sos_vm, vdev_in_sos) && (vdev_in_sos->pdev != NULL) && (!has_sriov_cap(vdev_in_sos))) {
/* 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, vdev_in_sos->phyfun);
pci_vdev_write_vcfg(vdev, PCIR_INTERRUPT_LINE, 1U, pcidev->intr_line);
pci_vdev_write_vcfg(vdev, PCIR_INTERRUPT_PIN, 1U, pcidev->intr_pin);
for (idx = 0U; idx < vdev->nr_bars; idx++) {
/* VF is assigned to a UOS */
if (vdev->phyfun != NULL) {
vdev->vbars[idx] = vdev_in_sos->vbars[idx];
if (has_msix_cap(vdev) && (idx == vdev->msix.table_bar)) {
vdev->msix.mmio_hpa = vdev->vbars[idx].base_hpa;
vdev->msix.mmio_size = vdev->vbars[idx].size;
}
}
pci_vdev_write_vbar(vdev, idx, pcidev->bar[idx]);
}
vdev->flags |= pcidev->type;
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) && is_own_device(tgt_vm, vdev_in_sos) && (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;
}