mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-05-08 08:26:55 +00:00
In current design, when pass-thru dev, for the PIO bar, need to ensure the guest PIO start address equals to host PIO start address. Then set the VMCS io bitmap to pass-thru the corresponding port io to guest for performance. But malicious guest may reprogram the PIO bar, then hv will pass-thru the reprogramed PIO address to guest. This isn't safe behavior. Here only pass-thru the host physical device PIO to guest. If guest regrogram the PIO bar, just update the virtual bar only. Currently, we don't support PIO bar reprogramming, if guest reprogram the PIO bar, guest should take responsibility itself When init the pass-thru dev PIO bars, set the VMCS io bitmap. setup_io_bitmap is called before init pass-thru dev to initiailize the io bitmap, so don't need to call deny_guest_pio_access when deinit pass-thru dev. v1 -> v2: * set the VMCS io bitmap when init pass-thru devices to migrate redoing allow_guest_pio_access()/deny_guest_pio_access(). Tracked-On: #6508 Signed-off-by: Liu,Junming <junming.liu@intel.com>
872 lines
26 KiB
C
872 lines
26 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 <asm/guest/vm.h>
|
|
#include <asm/vtd.h>
|
|
#include <asm/io.h>
|
|
#include <asm/mmu.h>
|
|
#include <vacpi.h>
|
|
#include <logmsg.h>
|
|
#include "vpci_priv.h"
|
|
#include <asm/pci_dev.h>
|
|
#include <hash.h>
|
|
#include <board_info.h>
|
|
|
|
|
|
static void vpci_init_vdevs(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 acrn_pio_request *pio_req = &vcpu->req.reqs.pio_request;
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @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 acrn_pio_request *pio_req = &vcpu->req.reqs.pio_request;
|
|
|
|
cfg_addr.value = atomic_readandclear32(&vpci->addr.value);
|
|
if (cfg_addr.bits.enable != 0U) {
|
|
if (pci_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 (pci_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 acrn_mmio_request *mmio = &io_req->reqs.mmio_request;
|
|
struct acrn_vpci *vpci = (struct acrn_vpci *)private_data;
|
|
uint64_t pci_mmcofg_base = vpci->pci_mmcfg.address;
|
|
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 == ACRN_IOREQ_DIR_READ) {
|
|
ret = vpci_read_cfg(vpci, bdf, reg_num, (uint32_t)mmio->size, (uint32_t *)&mmio->value);
|
|
} else {
|
|
ret = vpci_write_cfg(vpci, 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;
|
|
struct pci_mmcfg_region *pci_mmcfg;
|
|
|
|
vm->iommu = create_iommu_domain(vm->vm_id, hva2hpa(vm->arch_vm.nworld_eptp), 48U);
|
|
|
|
vm_config = get_vm_config(vm->vm_id);
|
|
/* virtual PCI MMCONFIG for SOS is same with the physical value */
|
|
if (vm_config->load_order == SOS_VM) {
|
|
pci_mmcfg = get_mmcfg_region();
|
|
vm->vpci.pci_mmcfg = *pci_mmcfg;
|
|
vm->vpci.res32.start = MMIO32_START;
|
|
vm->vpci.res32.end = MMIO32_END;
|
|
vm->vpci.res64.start = MMIO64_START;
|
|
vm->vpci.res64.end = MMIO64_END;
|
|
} else {
|
|
vm->vpci.pci_mmcfg.address = UOS_VIRT_PCI_MMCFG_BASE;
|
|
vm->vpci.pci_mmcfg.start_bus = UOS_VIRT_PCI_MMCFG_START_BUS;
|
|
vm->vpci.pci_mmcfg.end_bus = UOS_VIRT_PCI_MMCFG_END_BUS;
|
|
vm->vpci.res32.start = UOS_VIRT_PCI_MEMBASE32;
|
|
vm->vpci.res32.end = UOS_VIRT_PCI_MEMLIMIT32;
|
|
vm->vpci.res64.start = UOS_VIRT_PCI_MEMBASE64;
|
|
vm->vpci.res64.end = UOS_VIRT_PCI_MEMLIMIT64;
|
|
}
|
|
|
|
/* Build up vdev list for vm */
|
|
vpci_init_vdevs(vm);
|
|
|
|
register_mmio_emulation_handler(vm, vpci_mmio_cfg_access, vm->vpci.pci_mmcfg.address,
|
|
vm->vpci.pci_mmcfg.address + get_pci_mmcfg_size(&vm->vpci.pci_mmcfg), &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 pci_vdev *vdev, *parent_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 (vdev->user == vdev) {
|
|
parent_vdev = vdev->parent_user;
|
|
|
|
vdev->vdev_ops->deinit_vdev(vdev);
|
|
|
|
if (parent_vdev != NULL) {
|
|
spinlock_obtain(&parent_vdev->vpci->lock);
|
|
parent_vdev->vdev_ops->init_vdev(parent_vdev);
|
|
spinlock_release(&parent_vdev->vpci->lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
ptdev_release_all_entries(vm);
|
|
(void)memset(&vm->vpci, 0U, sizeof(struct acrn_vpci));
|
|
|
|
/* Free iommu */
|
|
destroy_iommu_domain(vm->iommu);
|
|
}
|
|
|
|
/**
|
|
* @pre vdev != NULL
|
|
* @pre vdev->vpci != NULL
|
|
* @pre vpci2vm(vdev->vpci)->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 vpci2vm(vdev->vpci)->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
|
|
*
|
|
* @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->user != vdev)) {
|
|
if (vdev->user != 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->user->vpci)->vm_id)) {
|
|
vdev = NULL;
|
|
}
|
|
} else {
|
|
vdev = NULL;
|
|
}
|
|
}
|
|
|
|
return vdev;
|
|
}
|
|
|
|
static void vpci_init_pt_dev(struct pci_vdev *vdev)
|
|
{
|
|
vdev->parent_user = NULL;
|
|
vdev->user = vdev;
|
|
|
|
/*
|
|
* Here init_vdev_pt() needs to be called after init_vmsix_pt() for the following reason:
|
|
* init_vdev_pt() will indirectly call has_msix_cap(), which
|
|
* requires init_vmsix_pt() to be called first.
|
|
*/
|
|
init_vmsi(vdev);
|
|
init_vmsix_pt(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_pt(vdev);
|
|
deinit_vmsi(vdev);
|
|
|
|
vdev->user = NULL;
|
|
vdev->parent_user = NULL;
|
|
}
|
|
|
|
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_vcfg(vdev, offset, bytes);
|
|
} else {
|
|
*val = ~0U;
|
|
}
|
|
} else {
|
|
/* ToDo: add cfg_hdr_perm for Type 1 device */
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* ToDo: add cfg_hdr_perm for Type 1 device */
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* According to PCIe Spec, for a RW register bits, If the optional feature
|
|
* that is associated with the bits is not implemented, the bits are permitted
|
|
* to be hardwired to 0b. However Zephyr would use INTx Line Register as writable
|
|
* even this PCI device has no INTx, so emulate INTx Line Register as writable.
|
|
*/
|
|
if (offset == PCIR_INTERRUPT_LINE) {
|
|
val &= 0xfU;
|
|
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_cap_reg(vdev, offset, bytes, val);
|
|
} else if (msixcap_access(vdev, offset)) {
|
|
if (vdev->msix.is_vmsix_on_msi) {
|
|
write_vmsix_cap_reg_on_msi(vdev, offset, bytes, val);
|
|
} else {
|
|
write_pt_vmsix_cap_reg(vdev, offset, bytes, val);
|
|
}
|
|
} else if (sriovcap_access(vdev, offset)) {
|
|
write_sriov_cap_reg(vdev, offset, bytes, val);
|
|
} else {
|
|
if (offset != vdev->pdev->sriov.pre_pos) {
|
|
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) || msixcap_access(vdev, offset)) {
|
|
*val = pci_vdev_read_vcfg(vdev, offset, bytes);
|
|
} else if (sriovcap_access(vdev, offset)) {
|
|
read_sriov_cap_reg(vdev, offset, bytes, val);
|
|
} else {
|
|
if ((offset == vdev->pdev->sriov.pre_pos) && (vdev->pdev->sriov.hide_sriov)) {
|
|
*val = pci_vdev_read_vcfg(vdev, offset, bytes);
|
|
} 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;
|
|
} else if (is_plat_hidden_pdev(bdf)) {
|
|
/* expose and pass through platform hidden devices */
|
|
*val = pci_pdev_read_cfg(bdf, offset, bytes);
|
|
} else {
|
|
/* no action: e.g., PCI scan */
|
|
}
|
|
}
|
|
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))) {
|
|
ret = -ENODEV;
|
|
} else if (is_plat_hidden_pdev(bdf)) {
|
|
/* expose and pass through platform hidden devices */
|
|
pci_pdev_write_cfg(bdf, offset, bytes, val);
|
|
} else {
|
|
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);
|
|
}
|
|
}
|
|
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;
|
|
|
|
hlist_add_head(&vdev->link, &vpci->vdevs_hlist_heads[hash64(dev_config->vbdf.value, VDEV_LIST_HASHBITS)]);
|
|
if (dev_config->vdev_ops != NULL) {
|
|
vdev->vdev_ops = dev_config->vdev_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++) {
|
|
/* the vdev whose vBDF is unassigned will be created by hypercall */
|
|
if ((!is_postlaunched_vm(vm)) || (vm_config->pci_devs[idx].vbdf.value != UNASSIGNED_VBDF)) {
|
|
(void)vpci_init_vdev(vpci, &vm_config->pci_devs[idx], NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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_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->user == vdev_in_sos) &&
|
|
(vdev_in_sos->pdev != NULL) &&
|
|
!is_host_bridge(vdev_in_sos->pdev) && !is_bridge(vdev_in_sos->pdev)) {
|
|
|
|
/* 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);
|
|
}
|
|
|
|
vdev_in_sos->vdev_ops->deinit_vdev(vdev_in_sos);
|
|
|
|
vpci = &(tgt_vm->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;
|
|
/*We should re-add the vdev to hashlist since its vbdf has changed */
|
|
hlist_del(&vdev->link);
|
|
hlist_add_head(&vdev->link, &vpci->vdevs_hlist_heads[hash64(vdev->bdf.value, VDEV_LIST_HASHBITS)]);
|
|
vdev->parent_user = vdev_in_sos;
|
|
spinlock_release(&tgt_vm->vpci.lock);
|
|
vdev_in_sos->user = 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_pcidev *pcidev)
|
|
{
|
|
int32_t ret = 0;
|
|
struct pci_vdev *parent_vdev, *vdev;
|
|
union pci_bdf bdf;
|
|
|
|
bdf.value = pcidev->virt_bdf;
|
|
vdev = pci_find_vdev(&tgt_vm->vpci, bdf);
|
|
if ((vdev != NULL) && (vdev->user == vdev) && (vdev->pdev != NULL) &&
|
|
(vdev->pdev->bdf.value == pcidev->phys_bdf)) {
|
|
parent_vdev = vdev->parent_user;
|
|
|
|
vdev->vdev_ops->deinit_vdev(vdev);
|
|
|
|
if (parent_vdev != NULL) {
|
|
spinlock_obtain(&parent_vdev->vpci->lock);
|
|
parent_vdev->vdev_ops->init_vdev(parent_vdev);
|
|
spinlock_release(&parent_vdev->vpci->lock);
|
|
}
|
|
} 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;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void vpci_update_one_vbar(struct pci_vdev *vdev, uint32_t bar_idx, uint32_t val,
|
|
map_pcibar map_cb, unmap_pcibar unmap_cb)
|
|
{
|
|
struct pci_vbar *vbar = &vdev->vbars[bar_idx];
|
|
uint32_t update_idx = bar_idx;
|
|
|
|
if (vbar->is_mem64hi) {
|
|
update_idx -= 1U;
|
|
}
|
|
if (unmap_cb != NULL) {
|
|
unmap_cb(vdev, update_idx);
|
|
}
|
|
pci_vdev_write_vbar(vdev, bar_idx, val);
|
|
if ((map_cb != NULL) && (vdev->vbars[update_idx].base_gpa != 0UL)) {
|
|
map_cb(vdev, update_idx);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @brief Add emulated legacy PCI capability support for virtual PCI device
|
|
*
|
|
* @param vdev Pointer to vdev data structure
|
|
* @param capdata Pointer to buffer that holds the capability data to be added.
|
|
* @param caplen Length of buffer that holds the capability data to be added.
|
|
*
|
|
* @pre vdev != NULL
|
|
* @pre vdev->vpci != NULL
|
|
*
|
|
* @return None
|
|
*/
|
|
uint32_t vpci_add_capability(struct pci_vdev *vdev, uint8_t *capdata, uint8_t caplen)
|
|
{
|
|
#define CAP_START_OFFSET PCI_CFG_HEADER_LENGTH
|
|
|
|
uint8_t capoff, reallen;
|
|
uint16_t sts;
|
|
uint32_t ret = 0U;
|
|
|
|
reallen = roundup(caplen, 4U); /* dword aligned */
|
|
|
|
sts = pci_vdev_read_vcfg(vdev, PCIR_STATUS, 2U);
|
|
if ((sts & PCIM_STATUS_CAPPRESENT) == 0U) {
|
|
capoff = CAP_START_OFFSET;
|
|
} else {
|
|
capoff = vdev->free_capoff;
|
|
}
|
|
|
|
/* Check if we have enough space */
|
|
if (((uint16_t)capoff + reallen) <= PCI_CONFIG_SPACE_SIZE) {
|
|
/* Set the previous capability pointer */
|
|
if ((sts & PCIM_STATUS_CAPPRESENT) == 0U) {
|
|
pci_vdev_write_vcfg(vdev, PCIR_CAP_PTR, 1U, capoff);
|
|
pci_vdev_write_vcfg(vdev, PCIR_STATUS, 2U, sts|PCIM_STATUS_CAPPRESENT);
|
|
} else {
|
|
pci_vdev_write_vcfg(vdev, vdev->prev_capoff + 1U, 1U, capoff);
|
|
}
|
|
|
|
/* Copy the capability */
|
|
(void)memcpy_s((void *)&vdev->cfgdata.data_8[capoff], caplen, (void *)capdata, caplen);
|
|
|
|
/* Set the next capability pointer */
|
|
pci_vdev_write_vcfg(vdev, capoff + 1U, 1U, 0U);
|
|
|
|
vdev->prev_capoff = capoff;
|
|
vdev->free_capoff = capoff + reallen;
|
|
ret = capoff;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool vpci_vmsix_enabled(const struct pci_vdev *vdev)
|
|
{
|
|
uint32_t msgctrl;
|
|
bool ret = false;
|
|
|
|
if (vdev->msix.capoff != 0U) {
|
|
msgctrl = pci_vdev_read_vcfg(vdev, vdev->msix.capoff + PCIR_MSIX_CTRL, 2U);
|
|
if (((msgctrl & PCIM_MSIXCTRL_MSIX_ENABLE) != 0U) &&
|
|
((msgctrl & PCIM_MSIXCTRL_FUNCTION_MASK) == 0U)) {
|
|
ret = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|