Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "VFIO support for PCI devices"
depends on PCI
config VFIO_PCI_CORE
tristate
select VFIO_VIRQFD
select IRQ_BYPASS_MANAGER
config VFIO_PCI_INTX
def_bool y if !S390
depends on VFIO_PCI_CORE
config VFIO_PCI
tristate "Generic VFIO support for any PCI device"
select VFIO_PCI_CORE
help
Support for the generic PCI VFIO bus driver which can connect any
PCI device to the VFIO framework.
If you don't know what to do here, say N.
if VFIO_PCI
config VFIO_PCI_VGA
bool "Generic VFIO PCI support for VGA devices"
depends on X86 && VGA_ARB
help
Support for VGA extension to VFIO PCI. This exposes an additional
region on VGA devices for accessing legacy VGA addresses used by
BIOS and generic video drivers.
If you don't know what to do here, say N.
config VFIO_PCI_IGD
bool "Generic VFIO PCI extensions for Intel graphics (GVT-d)"
depends on X86
default y
help
Support for Intel IGD specific extensions to enable direct
assignment to virtual machines. This includes exposing an IGD
specific firmware table and read-only copies of the host bridge
and LPC bridge config space.
To enable Intel IGD assignment through vfio-pci, say Y.
endif
config VFIO_PCI_ZDEV_KVM
bool "VFIO PCI extensions for s390x KVM passthrough"
depends on S390 && KVM
default y
help
Support s390x-specific extensions to enable support for enhancements
to KVM passthrough capabilities, such as interpretive execution of
zPCI instructions.
To enable s390x KVM vfio-pci extensions, say Y.
config VFIO_PCI_DMABUF
def_bool y if VFIO_PCI_CORE && PCI_P2PDMA && DMA_SHARED_BUFFER
source "drivers/vfio/pci/mlx5/Kconfig"
source "drivers/vfio/pci/ism/Kconfig"
source "drivers/vfio/pci/hisilicon/Kconfig"
source "drivers/vfio/pci/pds/Kconfig"
source "drivers/vfio/pci/virtio/Kconfig"
source "drivers/vfio/pci/nvgrace-gpu/Kconfig"
source "drivers/vfio/pci/qat/Kconfig"
source "drivers/vfio/pci/xe/Kconfig"
endmenu
+26
View File
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0-only
vfio-pci-core-y := vfio_pci_core.o vfio_pci_intrs.o vfio_pci_rdwr.o vfio_pci_config.o
vfio-pci-core-$(CONFIG_VFIO_PCI_ZDEV_KVM) += vfio_pci_zdev.o
vfio-pci-core-$(CONFIG_VFIO_PCI_DMABUF) += vfio_pci_dmabuf.o
obj-$(CONFIG_VFIO_PCI_CORE) += vfio-pci-core.o
vfio-pci-y := vfio_pci.o
vfio-pci-$(CONFIG_VFIO_PCI_IGD) += vfio_pci_igd.o
obj-$(CONFIG_VFIO_PCI) += vfio-pci.o
obj-$(CONFIG_MLX5_VFIO_PCI) += mlx5/
obj-$(CONFIG_ISM_VFIO_PCI) += ism/
obj-$(CONFIG_HISI_ACC_VFIO_PCI) += hisilicon/
obj-$(CONFIG_PDS_VFIO_PCI) += pds/
obj-$(CONFIG_VIRTIO_VFIO_PCI) += virtio/
obj-$(CONFIG_NVGRACE_GPU_VFIO_PCI) += nvgrace-gpu/
obj-$(CONFIG_QAT_VFIO_PCI) += qat/
obj-$(CONFIG_XE_VFIO_PCI) += xe/
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
config HISI_ACC_VFIO_PCI
tristate "VFIO support for HiSilicon ACC PCI devices"
depends on ARM64 || (COMPILE_TEST && 64BIT)
depends on PCI_MSI
depends on CRYPTO_DEV_HISI_QM
depends on CRYPTO_DEV_HISI_HPRE
depends on CRYPTO_DEV_HISI_SEC2
depends on CRYPTO_DEV_HISI_ZIP
select VFIO_PCI_CORE
help
This provides generic PCI support for HiSilicon ACC devices
using the VFIO framework.
If you don't know what to do here, say N.
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_HISI_ACC_VFIO_PCI) += hisi-acc-vfio-pci.o
hisi-acc-vfio-pci-y := hisi_acc_vfio_pci.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,165 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2021 HiSilicon Ltd. */
#ifndef HISI_ACC_VFIO_PCI_H
#define HISI_ACC_VFIO_PCI_H
#include <linux/hisi_acc_qm.h>
#define MB_POLL_PERIOD_US 10
#define MB_POLL_TIMEOUT_US 1000
#define QM_CACHE_WB_START 0x204
#define QM_CACHE_WB_DONE 0x208
#define QM_MB_CMD_PAUSE_QM 0xe
#define QM_ABNORMAL_INT_STATUS 0x100008
#define QM_IFC_INT_STATUS 0x0028
#define SEC_CORE_INT_STATUS 0x301008
#define HPRE_HAC_INT_STATUS 0x301800
#define HZIP_CORE_INT_STATUS 0x3010AC
#define QM_VFT_CFG_RDY 0x10006c
#define QM_VFT_CFG_OP_WR 0x100058
#define QM_VFT_CFG_TYPE 0x10005c
#define QM_VFT_CFG 0x100060
#define QM_VFT_CFG_OP_ENABLE 0x100054
#define QM_VFT_CFG_DATA_L 0x100064
#define QM_VFT_CFG_DATA_H 0x100068
#define ERROR_CHECK_TIMEOUT 100
#define CHECK_DELAY_TIME 100
#define QM_RESET_WAIT_TIMEOUT 60000
#define QM_SQC_VFT_BASE_SHIFT_V2 28
#define QM_SQC_VFT_BASE_MASK_V2 GENMASK(15, 0)
#define QM_SQC_VFT_NUM_SHIFT_V2 45
#define QM_SQC_VFT_NUM_MASK_V2 GENMASK(9, 0)
#define QM_MB_CMD_NOT_READY 0xffffffff
/* RW regs */
#define QM_REGS_MAX_LEN 7
#define QM_REG_ADDR_OFFSET 0x0004
#define QM_XQC_ADDR_OFFSET 32U
#define QM_XQC_ADDR_LOW 0x1
#define QM_XQC_ADDR_HIGH 0x2
#define QM_VF_AEQ_INT_MASK 0x0004
#define QM_VF_EQ_INT_MASK 0x000c
#define QM_IFC_INT_SOURCE_V 0x0020
#define QM_IFC_INT_MASK 0x0024
#define QM_IFC_INT_SET_V 0x002c
#define QM_QUE_ISO_CFG_V 0x0030
#define QM_PAGE_SIZE 0x0034
#define QM_EQC_VF_DW0 0X8000
#define QM_AEQC_VF_DW0 0X8020
#define QM_EQC_PF_DW0 0x1c00
#define QM_AEQC_PF_DW0 0x1c20
#define ACC_DRV_MAJOR_VER 1
#define ACC_DRV_MINOR_VER 0
#define ACC_DEV_MAGIC_V1 0XCDCDCDCDFEEDAACC
#define ACC_DEV_MAGIC_V2 0xAACCFEEDDECADEDE
#define QM_MIG_REGION_OFFSET 0x180000
#define QM_MIG_REGION_SIZE 0x2000
/**
* On HW_ACC_MIG_VF_CTRL mode, the configuration domain supporting live
* migration functionality is located in the latter 32KB of the VF's BAR2.
* The Guest is only provided with the first 32KB of the VF's BAR2.
* On HW_ACC_MIG_PF_CTRL mode, the configuration domain supporting live
* migration functionality is located in the PF's BAR2, and the entire 64KB
* of the VF's BAR2 is allocated to the Guest.
*/
enum hw_drv_mode {
HW_ACC_MIG_VF_CTRL = 0,
HW_ACC_MIG_PF_CTRL,
};
struct acc_vf_data {
#define QM_MATCH_SIZE offsetofend(struct acc_vf_data, qm_rsv_state)
/* QM match information */
u64 acc_magic;
u32 qp_num;
u32 dev_id;
u32 que_iso_cfg;
u32 qp_base;
u32 vf_qm_state;
/* QM reserved match information */
u16 major_ver;
u16 minor_ver;
u32 qm_rsv_state[2];
/* QM RW regs */
u32 aeq_int_mask;
u32 eq_int_mask;
u32 ifc_int_source;
u32 ifc_int_mask;
u32 ifc_int_set;
u32 page_size;
/* QM_EQC_DW has 7 regs */
u32 qm_eqc_dw[7];
/* QM_AEQC_DW has 7 regs */
u32 qm_aeqc_dw[7];
/* QM reserved 5 regs */
u32 qm_rsv_regs[5];
u32 padding;
/* QM memory init information */
u64 eqe_dma;
u64 aeqe_dma;
u64 sqc_dma;
u64 cqc_dma;
};
struct hisi_acc_vf_migration_file {
struct file *filp;
struct mutex lock;
bool disabled;
struct hisi_acc_vf_core_device *hisi_acc_vdev;
struct acc_vf_data vf_data;
size_t total_length;
};
struct hisi_acc_vf_core_device {
struct vfio_pci_core_device core_device;
u8 match_done;
bool set_reset_flag;
/*
* io_base is only valid when dev_opened is true,
* which is protected by open_mutex.
*/
bool dev_opened;
/* Ensure the accuracy of dev_opened operation */
struct mutex open_mutex;
/* For migration state */
struct mutex state_mutex;
enum vfio_device_mig_state mig_state;
struct pci_dev *pf_dev;
struct pci_dev *vf_dev;
struct hisi_qm *pf_qm;
struct hisi_qm vf_qm;
enum hw_drv_mode drv_mode;
/*
* vf_qm_state represents the QM_VF_STATE register value.
* It is set by Guest driver for the ACC VF dev indicating
* the driver has loaded and configured the dev correctly.
*/
u32 vf_qm_state;
int vf_id;
struct hisi_acc_vf_migration_file *resuming_migf;
struct hisi_acc_vf_migration_file *saving_migf;
/*
* It holds migration data corresponding to the last migration
* and is used by the debugfs interface to report it.
*/
struct hisi_acc_vf_migration_file *debug_migf;
};
#endif /* HISI_ACC_VFIO_PCI_H */
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
config ISM_VFIO_PCI
tristate "VFIO support for ISM devices"
depends on S390
select VFIO_PCI_CORE
help
This provides user space support for IBM Internal Shared Memory (ISM)
Adapter devices using the VFIO framework.
If you don't know what to do here, say N.
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_ISM_VFIO_PCI) += ism-vfio-pci.o
ism-vfio-pci-y := main.o
+408
View File
@@ -0,0 +1,408 @@
// SPDX-License-Identifier: GPL-2.0
/*
* vfio-ISM driver for s390
*
* Copyright IBM Corp.
*/
#include <linux/slab.h>
#include "../vfio_pci_priv.h"
#define ISM_VFIO_PCI_OFFSET_SHIFT 48
#define ISM_VFIO_PCI_OFFSET_TO_INDEX(off) ((off) >> ISM_VFIO_PCI_OFFSET_SHIFT)
#define ISM_VFIO_PCI_INDEX_TO_OFFSET(index) ((u64)(index) << ISM_VFIO_PCI_OFFSET_SHIFT)
#define ISM_VFIO_PCI_OFFSET_MASK (((u64)(1) << ISM_VFIO_PCI_OFFSET_SHIFT) - 1)
/*
* Use __zpci_load() to bypass automatic use of
* PCI MIO instructions which are not supported on ISM devices
*/
#define ISM_READ(size) \
static int ism_read##size(struct zpci_dev *zdev, int bar, \
size_t *filled, char __user *buf, \
loff_t off) \
{ \
u64 req, tmp; \
u##size val; \
int ret; \
\
req = ZPCI_CREATE_REQ(READ_ONCE(zdev->fh), bar, sizeof(val)); \
ret = __zpci_load(&tmp, req, off); \
if (ret) \
return ret; \
val = (u##size)tmp; \
if (copy_to_user(buf, &val, sizeof(val))) \
return -EFAULT; \
*filled = sizeof(val); \
return 0; \
}
ISM_READ(64);
ISM_READ(32);
ISM_READ(16);
ISM_READ(8);
struct ism_vfio_pci_core_device {
struct vfio_pci_core_device core_device;
struct kmem_cache *store_block_cache;
};
static int ism_vfio_pci_open_device(struct vfio_device *core_vdev)
{
struct ism_vfio_pci_core_device *ivpcd;
struct vfio_pci_core_device *vdev;
int ret;
ivpcd = container_of(core_vdev, struct ism_vfio_pci_core_device,
core_device.vdev);
vdev = &ivpcd->core_device;
ret = vfio_pci_core_enable(vdev);
if (ret)
return ret;
vfio_pci_core_finish_enable(vdev);
return 0;
}
/*
* ism_vfio_pci_do_io_r()
*
* On s390, kernel primitives such as ioread() and iowrite() are switched over
* from function-handle-based PCI load/stores instructions to PCI memory-I/O (MIO)
* loads/stores when these are available and not explicitly disabled. Since these
* instructions cannot be used with ISM devices, ensure that classic
* function-handle-based PCI instructions are used instead.
*/
static ssize_t ism_vfio_pci_do_io_r(struct vfio_pci_core_device *vdev,
char __user *buf, loff_t off, size_t count,
int bar)
{
struct zpci_dev *zdev = to_zpci(vdev->pdev);
ssize_t done = 0;
int ret;
while (count) {
size_t filled;
if (count >= 8 && IS_ALIGNED(off, 8)) {
ret = ism_read64(zdev, bar, &filled, buf, off);
if (ret)
return ret;
} else if (count >= 4 && IS_ALIGNED(off, 4)) {
ret = ism_read32(zdev, bar, &filled, buf, off);
if (ret)
return ret;
} else if (count >= 2 && IS_ALIGNED(off, 2)) {
ret = ism_read16(zdev, bar, &filled, buf, off);
if (ret)
return ret;
} else {
ret = ism_read8(zdev, bar, &filled, buf, off);
if (ret)
return ret;
}
count -= filled;
done += filled;
off += filled;
buf += filled;
}
return done;
}
/*
* ism_vfio_pci_do_io_w()
*
* Ensure that the PCI store block (PCISTB) instruction is used as required by the
* ISM device. The ISM device also uses a 256 TiB BAR 0 for write operations,
* which requires a 48bit region address space (ISM_VFIO_PCI_OFFSET_SHIFT).
*/
static ssize_t ism_vfio_pci_do_io_w(struct vfio_pci_core_device *vdev,
char __user *buf, loff_t off, size_t count,
int bar)
{
struct zpci_dev *zdev = to_zpci(vdev->pdev);
struct ism_vfio_pci_core_device *ivpcd;
ssize_t ret;
void *data;
u64 req;
if (count > zdev->maxstbl)
return -EINVAL;
if (((off % PAGE_SIZE) + count) > PAGE_SIZE)
return -EINVAL;
ivpcd = container_of(vdev, struct ism_vfio_pci_core_device,
core_device);
data = kmem_cache_alloc(ivpcd->store_block_cache, GFP_KERNEL);
if (!data)
return -ENOMEM;
if (copy_from_user(data, buf, count)) {
ret = -EFAULT;
goto out_free;
}
req = ZPCI_CREATE_REQ(READ_ONCE(zdev->fh), bar, count);
ret = __zpci_store_block(data, req, off);
if (ret)
goto out_free;
ret = count;
out_free:
kmem_cache_free(ivpcd->store_block_cache, data);
return ret;
}
static ssize_t ism_vfio_pci_bar_rw(struct vfio_pci_core_device *vdev,
char __user *buf, size_t count, loff_t *ppos,
bool iswrite)
{
int bar = ISM_VFIO_PCI_OFFSET_TO_INDEX(*ppos);
loff_t pos = *ppos & ISM_VFIO_PCI_OFFSET_MASK;
resource_size_t end;
ssize_t done = 0;
if (pci_resource_start(vdev->pdev, bar))
end = pci_resource_len(vdev->pdev, bar);
else
return -EINVAL;
if (pos >= end)
return -EINVAL;
count = min(count, (size_t)(end - pos));
if (iswrite)
done = ism_vfio_pci_do_io_w(vdev, buf, pos, count, bar);
else
done = ism_vfio_pci_do_io_r(vdev, buf, pos, count, bar);
if (done >= 0)
*ppos += done;
return done;
}
static ssize_t ism_vfio_pci_config_rw(struct vfio_pci_core_device *vdev,
char __user *buf, size_t count,
loff_t *ppos, bool iswrite)
{
loff_t pos = *ppos;
size_t done = 0;
int ret = 0;
pos &= ISM_VFIO_PCI_OFFSET_MASK;
while (count) {
/*
* zPCI must not use MIO instructions for config space access,
* so we can use common code path here.
*/
ret = vfio_pci_config_rw_single(vdev, buf, count, &pos, iswrite);
if (ret < 0)
return ret;
count -= ret;
done += ret;
buf += ret;
pos += ret;
}
*ppos += done;
return done;
}
static ssize_t ism_vfio_pci_rw(struct vfio_device *core_vdev, char __user *buf,
size_t count, loff_t *ppos, bool iswrite)
{
unsigned int index = ISM_VFIO_PCI_OFFSET_TO_INDEX(*ppos);
struct vfio_pci_core_device *vdev;
int ret;
vdev = container_of(core_vdev, struct vfio_pci_core_device, vdev);
if (!count)
return 0;
switch (index) {
case VFIO_PCI_CONFIG_REGION_INDEX:
ret = ism_vfio_pci_config_rw(vdev, buf, count, ppos, iswrite);
break;
case VFIO_PCI_BAR0_REGION_INDEX ... VFIO_PCI_BAR5_REGION_INDEX:
ret = ism_vfio_pci_bar_rw(vdev, buf, count, ppos, iswrite);
break;
default:
return -EINVAL;
}
return ret;
}
static ssize_t ism_vfio_pci_read(struct vfio_device *core_vdev,
char __user *buf, size_t count, loff_t *ppos)
{
return ism_vfio_pci_rw(core_vdev, buf, count, ppos, false);
}
static ssize_t ism_vfio_pci_write(struct vfio_device *core_vdev,
const char __user *buf, size_t count,
loff_t *ppos)
{
return ism_vfio_pci_rw(core_vdev, (char __user *)buf, count, ppos,
true);
}
static int ism_vfio_pci_ioctl_get_region_info(struct vfio_device *core_vdev,
struct vfio_region_info *info,
struct vfio_info_cap *caps)
{
struct vfio_pci_core_device *vdev =
container_of(core_vdev, struct vfio_pci_core_device, vdev);
struct pci_dev *pdev = vdev->pdev;
switch (info->index) {
case VFIO_PCI_CONFIG_REGION_INDEX:
info->offset = ISM_VFIO_PCI_INDEX_TO_OFFSET(info->index);
info->size = pdev->cfg_size;
info->flags = VFIO_REGION_INFO_FLAG_READ |
VFIO_REGION_INFO_FLAG_WRITE;
break;
case VFIO_PCI_BAR0_REGION_INDEX ... VFIO_PCI_BAR5_REGION_INDEX:
info->offset = ISM_VFIO_PCI_INDEX_TO_OFFSET(info->index);
info->size = pci_resource_len(pdev, info->index);
if (!info->size) {
info->flags = 0;
break;
}
info->flags = VFIO_REGION_INFO_FLAG_READ |
VFIO_REGION_INFO_FLAG_WRITE;
break;
default:
info->offset = 0;
info->size = 0;
info->flags = 0;
return -EINVAL;
}
return 0;
}
static int ism_vfio_pci_init_dev(struct vfio_device *core_vdev)
{
struct zpci_dev *zdev = to_zpci(to_pci_dev(core_vdev->dev));
struct ism_vfio_pci_core_device *ivpcd;
char cache_name[20];
int ret;
ivpcd = container_of(core_vdev, struct ism_vfio_pci_core_device,
core_device.vdev);
snprintf(cache_name, sizeof(cache_name), "ism_sb_fid_%08x", zdev->fid);
ivpcd->store_block_cache =
kmem_cache_create(cache_name, zdev->maxstbl,
(&(struct kmem_cache_args){
.align = PAGE_SIZE,
.useroffset = 0,
.usersize = zdev->maxstbl,
}),
(SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT));
if (!ivpcd->store_block_cache)
return -ENOMEM;
ret = vfio_pci_core_init_dev(core_vdev);
if (ret)
kmem_cache_destroy(ivpcd->store_block_cache);
return ret;
}
static void ism_vfio_pci_release_dev(struct vfio_device *core_vdev)
{
struct ism_vfio_pci_core_device *ivpcd = container_of(
core_vdev, struct ism_vfio_pci_core_device, core_device.vdev);
kmem_cache_destroy(ivpcd->store_block_cache);
vfio_pci_core_release_dev(core_vdev);
}
static const struct vfio_device_ops ism_pci_ops = {
.name = "ism-vfio-pci",
.init = ism_vfio_pci_init_dev,
.release = ism_vfio_pci_release_dev,
.open_device = ism_vfio_pci_open_device,
.close_device = vfio_pci_core_close_device,
.ioctl = vfio_pci_core_ioctl,
.get_region_info_caps = ism_vfio_pci_ioctl_get_region_info,
.device_feature = vfio_pci_core_ioctl_feature,
.read = ism_vfio_pci_read,
.write = ism_vfio_pci_write,
.request = vfio_pci_core_request,
.match = vfio_pci_core_match,
.match_token_uuid = vfio_pci_core_match_token_uuid,
.bind_iommufd = vfio_iommufd_physical_bind,
.unbind_iommufd = vfio_iommufd_physical_unbind,
.attach_ioas = vfio_iommufd_physical_attach_ioas,
.detach_ioas = vfio_iommufd_physical_detach_ioas,
};
static int ism_vfio_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct ism_vfio_pci_core_device *ivpcd;
int ret;
ivpcd = vfio_alloc_device(ism_vfio_pci_core_device, core_device.vdev,
&pdev->dev, &ism_pci_ops);
if (IS_ERR(ivpcd))
return PTR_ERR(ivpcd);
dev_set_drvdata(&pdev->dev, &ivpcd->core_device);
ret = vfio_pci_core_register_device(&ivpcd->core_device);
if (ret)
vfio_put_device(&ivpcd->core_device.vdev);
return ret;
}
static void ism_vfio_pci_remove(struct pci_dev *pdev)
{
struct vfio_pci_core_device *core_device;
struct ism_vfio_pci_core_device *ivpcd;
core_device = dev_get_drvdata(&pdev->dev);
ivpcd = container_of(core_device, struct ism_vfio_pci_core_device,
core_device);
vfio_pci_core_unregister_device(&ivpcd->core_device);
vfio_put_device(&ivpcd->core_device.vdev);
}
static const struct pci_device_id ism_device_table[] = {
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_IBM,
PCI_DEVICE_ID_IBM_ISM) },
{}
};
MODULE_DEVICE_TABLE(pci, ism_device_table);
static struct pci_driver ism_vfio_pci_driver = {
.name = KBUILD_MODNAME,
.id_table = ism_device_table,
.probe = ism_vfio_pci_probe,
.remove = ism_vfio_pci_remove,
.err_handler = &vfio_pci_core_err_handlers,
.driver_managed_dma = true,
};
module_pci_driver(ism_vfio_pci_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("vfio-pci variant driver for the IBM Internal Shared Memory (ISM) device");
MODULE_AUTHOR("IBM Corporation");
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
config MLX5_VFIO_PCI
tristate "VFIO support for MLX5 PCI devices"
depends on MLX5_CORE
select VFIO_PCI_CORE
select IOMMUFD_DRIVER
help
This provides migration support for MLX5 devices using the VFIO
framework.
If you don't know what to do here, say N.
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_MLX5_VFIO_PCI) += mlx5-vfio-pci.o
mlx5-vfio-pci-y := main.o cmd.o
File diff suppressed because it is too large Load Diff
+254
View File
@@ -0,0 +1,254 @@
/* SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB */
/*
* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*/
#ifndef MLX5_VFIO_CMD_H
#define MLX5_VFIO_CMD_H
#include <linux/kernel.h>
#include <linux/vfio_pci_core.h>
#include <linux/mlx5/driver.h>
#include <linux/mlx5/vport.h>
#include <linux/mlx5/cq.h>
#include <linux/mlx5/qp.h>
enum mlx5_vf_migf_state {
MLX5_MIGF_STATE_ERROR = 1,
MLX5_MIGF_STATE_PRE_COPY_ERROR,
MLX5_MIGF_STATE_PRE_COPY,
MLX5_MIGF_STATE_SAVE_STOP_COPY_CHUNK,
MLX5_MIGF_STATE_COMPLETE,
};
enum mlx5_vf_load_state {
MLX5_VF_LOAD_STATE_READ_HEADER,
MLX5_VF_LOAD_STATE_PREP_HEADER_DATA,
MLX5_VF_LOAD_STATE_READ_HEADER_DATA,
MLX5_VF_LOAD_STATE_PREP_IMAGE,
MLX5_VF_LOAD_STATE_READ_IMAGE,
MLX5_VF_LOAD_STATE_LOAD_IMAGE,
};
struct mlx5_vf_migration_tag_stop_copy_data {
__le64 stop_copy_size;
};
enum mlx5_vf_migf_header_flags {
MLX5_MIGF_HEADER_FLAGS_TAG_MANDATORY = 0,
MLX5_MIGF_HEADER_FLAGS_TAG_OPTIONAL = 1 << 0,
};
enum mlx5_vf_migf_header_tag {
MLX5_MIGF_HEADER_TAG_FW_DATA = 0,
MLX5_MIGF_HEADER_TAG_STOP_COPY_SIZE = 1 << 0,
};
struct mlx5_vf_migration_header {
__le64 record_size;
/* For future use in case we may need to change the kernel protocol */
__le32 flags; /* Use mlx5_vf_migf_header_flags */
__le32 tag; /* Use mlx5_vf_migf_header_tag */
__u8 data[]; /* Its size is given in the record_size */
};
struct mlx5_vhca_data_buffer {
struct page **page_list;
struct dma_iova_state state;
loff_t start_pos;
u64 length;
u32 npages;
u32 mkey;
u32 *mkey_in;
enum dma_data_direction dma_dir;
u8 stop_copy_chunk_num;
bool pre_copy_init_bytes_chunk;
struct list_head buf_elm;
struct mlx5_vf_migration_file *migf;
};
struct mlx5vf_async_data {
struct mlx5_async_work cb_work;
struct work_struct work;
struct mlx5_vhca_data_buffer *buf;
struct mlx5_vhca_data_buffer *header_buf;
int status;
u8 stop_copy_chunk:1;
void *out;
};
struct mlx5vf_save_work_data {
struct mlx5_vf_migration_file *migf;
size_t next_required_umem_size;
struct work_struct work;
u8 chunk_num;
};
#define MAX_NUM_CHUNKS 2
struct mlx5_vf_migration_file {
struct file *filp;
struct mutex lock;
enum mlx5_vf_migf_state state;
enum mlx5_vf_load_state load_state;
u32 pdn;
loff_t max_pos;
u64 record_size;
u32 record_tag;
u64 stop_copy_prep_size;
u64 pre_copy_initial_bytes;
u64 pre_copy_initial_bytes_start;
size_t next_required_umem_size;
u8 num_ready_chunks;
/* Upon chunk mode preserve another set of buffers for stop_copy phase */
struct mlx5_vhca_data_buffer *buf[MAX_NUM_CHUNKS];
struct mlx5_vhca_data_buffer *buf_header[MAX_NUM_CHUNKS];
struct mlx5vf_save_work_data save_data[MAX_NUM_CHUNKS];
spinlock_t list_lock;
struct list_head buf_list;
struct list_head avail_list;
struct mlx5vf_pci_core_device *mvdev;
wait_queue_head_t poll_wait;
struct completion save_comp;
struct mlx5_async_ctx async_ctx;
struct mlx5vf_async_data async_data;
u8 inflight_save:1;
};
struct mlx5_vhca_cq_buf {
struct mlx5_frag_buf_ctrl fbc;
struct mlx5_frag_buf frag_buf;
int cqe_size;
int nent;
};
struct mlx5_vhca_cq {
struct mlx5_vhca_cq_buf buf;
struct mlx5_db db;
struct mlx5_core_cq mcq;
size_t ncqe;
};
struct mlx5_vhca_recv_buf {
u32 npages;
struct page **page_list;
struct dma_iova_state state;
u32 next_rq_offset;
u32 *mkey_in;
u32 mkey;
};
struct mlx5_vhca_qp {
struct mlx5_frag_buf buf;
struct mlx5_db db;
struct mlx5_vhca_recv_buf recv_buf;
u32 tracked_page_size;
u32 max_msg_size;
u32 qpn;
struct {
unsigned int pc;
unsigned int cc;
unsigned int wqe_cnt;
__be32 *db;
struct mlx5_frag_buf_ctrl fbc;
} rq;
};
struct mlx5_vhca_page_tracker {
u32 id;
u32 pdn;
/* Flags modified at runtime - dedicated storage unit */
u8 is_err;
u8 object_changed;
int status;
struct mlx5_uars_page *uar;
struct mlx5_vhca_cq cq;
struct mlx5_vhca_qp *host_qp;
struct mlx5_vhca_qp *fw_qp;
struct mlx5_nb nb;
};
struct mlx5vf_pci_core_device {
struct vfio_pci_core_device core_device;
int vf_id;
u16 vhca_id;
/* Flags only modified on setup/release - bitfield ok */
u8 migrate_cap:1;
u8 chunk_mode:1;
u8 mig_state_cap:1;
/* Flags modified at runtime - dedicated storage unit */
u8 mdev_detach;
u8 log_active;
u8 deferred_reset;
struct completion tracker_comp;
/* protect migration state */
struct mutex state_mutex;
enum vfio_device_mig_state mig_state;
/* protect the reset_done flow */
spinlock_t reset_lock;
struct mlx5_vf_migration_file *resuming_migf;
struct mlx5_vf_migration_file *saving_migf;
struct mlx5_vhca_page_tracker tracker;
struct workqueue_struct *cb_wq;
struct notifier_block nb;
struct mlx5_core_dev *mdev;
};
enum {
MLX5VF_QUERY_INC = (1UL << 0),
MLX5VF_QUERY_FINAL = (1UL << 1),
MLX5VF_QUERY_CLEANUP = (1UL << 2),
};
int mlx5vf_cmd_suspend_vhca(struct mlx5vf_pci_core_device *mvdev, u16 op_mod);
int mlx5vf_cmd_resume_vhca(struct mlx5vf_pci_core_device *mvdev, u16 op_mod);
int mlx5vf_cmd_query_vhca_migration_state(struct mlx5vf_pci_core_device *mvdev,
size_t *state_size, u64 *total_size,
u8 *migration_state, u8 query_flags);
void mlx5vf_cmd_set_migratable(struct mlx5vf_pci_core_device *mvdev,
const struct vfio_migration_ops *mig_ops,
const struct vfio_log_ops *log_ops);
void mlx5vf_cmd_remove_migratable(struct mlx5vf_pci_core_device *mvdev);
void mlx5vf_cmd_close_migratable(struct mlx5vf_pci_core_device *mvdev);
int mlx5vf_cmd_save_vhca_state(struct mlx5vf_pci_core_device *mvdev,
struct mlx5_vf_migration_file *migf,
struct mlx5_vhca_data_buffer *buf, bool inc,
bool track);
int mlx5vf_cmd_load_vhca_state(struct mlx5vf_pci_core_device *mvdev,
struct mlx5_vf_migration_file *migf,
struct mlx5_vhca_data_buffer *buf);
int mlx5vf_cmd_alloc_pd(struct mlx5_vf_migration_file *migf);
void mlx5vf_cmd_dealloc_pd(struct mlx5_vf_migration_file *migf);
void mlx5fv_cmd_clean_migf_resources(struct mlx5_vf_migration_file *migf);
struct mlx5_vhca_data_buffer *
mlx5vf_alloc_data_buffer(struct mlx5_vf_migration_file *migf, u32 npages,
enum dma_data_direction dma_dir);
void mlx5vf_free_data_buffer(struct mlx5_vhca_data_buffer *buf);
struct mlx5_vhca_data_buffer *
mlx5vf_get_data_buffer(struct mlx5_vf_migration_file *migf, u32 npages,
enum dma_data_direction dma_dir);
void mlx5vf_put_data_buffer(struct mlx5_vhca_data_buffer *buf);
static inline struct page *
mlx5vf_get_migration_page(struct mlx5_vhca_data_buffer *buf,
unsigned long offset)
{
int page_entry = offset / PAGE_SIZE;
if (page_entry >= buf->npages)
return NULL;
return buf->page_list[page_entry];
}
void mlx5vf_state_mutex_unlock(struct mlx5vf_pci_core_device *mvdev);
void mlx5vf_disable_fds(struct mlx5vf_pci_core_device *mvdev,
enum mlx5_vf_migf_state *last_save_state);
void mlx5vf_mig_file_cleanup_cb(struct work_struct *_work);
void mlx5vf_mig_file_set_save_work(struct mlx5_vf_migration_file *migf,
u8 chunk_num, size_t next_required_umem_size);
int mlx5vf_start_page_tracker(struct vfio_device *vdev,
struct rb_root_cached *ranges, u32 nnodes, u64 *page_size);
int mlx5vf_stop_page_tracker(struct vfio_device *vdev);
int mlx5vf_tracker_read_and_clear(struct vfio_device *vdev, unsigned long iova,
unsigned long length, struct iova_bitmap *dirty);
#endif /* MLX5_VFIO_CMD_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
config NVGRACE_GPU_VFIO_PCI
tristate "VFIO support for the GPU in the NVIDIA Grace Hopper Superchip"
depends on ARM64 || (COMPILE_TEST && 64BIT)
select VFIO_PCI_CORE
help
VFIO support for the GPU in the NVIDIA Grace Hopper Superchip is
required to assign the GPU device to userspace using KVM/qemu/etc.
If you don't know what to do here, say N.
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_NVGRACE_GPU_VFIO_PCI) += nvgrace-gpu-vfio-pci.o
nvgrace-gpu-vfio-pci-y := main.o
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2023 Advanced Micro Devices, Inc.
config PDS_VFIO_PCI
tristate "VFIO support for PDS PCI devices"
depends on PDS_CORE && PCI_IOV
select VFIO_PCI_CORE
select IOMMUFD_DRIVER
help
This provides generic PCI support for PDS devices using the VFIO
framework.
More specific information on this driver can be
found in
<file:Documentation/networking/device_drivers/ethernet/amd/pds_vfio_pci.rst>.
To compile this driver as a module, choose M here. The module
will be called pds-vfio-pci.
If you don't know what to do here, say N.
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2023 Advanced Micro Devices, Inc.
obj-$(CONFIG_PDS_VFIO_PCI) += pds-vfio-pci.o
pds-vfio-pci-y := \
cmds.o \
dirty.o \
lm.o \
pci_drv.o \
vfio_dev.o
+510
View File
@@ -0,0 +1,510 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#include <linux/io.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/pds/pds_common.h>
#include <linux/pds/pds_core_if.h>
#include <linux/pds/pds_adminq.h>
#include "vfio_dev.h"
#include "cmds.h"
#define SUSPEND_TIMEOUT_S 5
#define SUSPEND_CHECK_INTERVAL_MS 1
static int pds_vfio_client_adminq_cmd(struct pds_vfio_pci_device *pds_vfio,
union pds_core_adminq_cmd *req,
union pds_core_adminq_comp *resp,
bool fast_poll)
{
struct pci_dev *pdev = pds_vfio_to_pci_dev(pds_vfio);
union pds_core_adminq_cmd cmd = {};
struct pdsc *pdsc;
int err;
/* Wrap the client request */
cmd.client_request.opcode = PDS_AQ_CMD_CLIENT_CMD;
cmd.client_request.client_id = cpu_to_le16(pds_vfio->client_id);
memcpy(cmd.client_request.client_cmd, req,
sizeof(cmd.client_request.client_cmd));
pdsc = pdsc_get_pf_struct(pdev);
if (IS_ERR(pdsc))
return PTR_ERR(pdsc);
err = pdsc_adminq_post(pdsc, &cmd, resp, fast_poll);
if (err && err != -EAGAIN)
dev_err(pds_vfio_to_dev(pds_vfio),
"client admin cmd failed: %pe\n", ERR_PTR(err));
return err;
}
int pds_vfio_register_client_cmd(struct pds_vfio_pci_device *pds_vfio)
{
struct pci_dev *pdev = pds_vfio_to_pci_dev(pds_vfio);
char devname[PDS_DEVNAME_LEN];
struct pdsc *pdsc;
int ci;
snprintf(devname, sizeof(devname), "%s.%d-%u", PDS_VFIO_LM_DEV_NAME,
pci_domain_nr(pdev->bus),
PCI_DEVID(pdev->bus->number, pdev->devfn));
pdsc = pdsc_get_pf_struct(pdev);
if (IS_ERR(pdsc))
return PTR_ERR(pdsc);
ci = pds_client_register(pdsc, devname);
if (ci < 0)
return ci;
pds_vfio->client_id = ci;
return 0;
}
void pds_vfio_unregister_client_cmd(struct pds_vfio_pci_device *pds_vfio)
{
struct pci_dev *pdev = pds_vfio_to_pci_dev(pds_vfio);
struct pdsc *pdsc;
int err;
pdsc = pdsc_get_pf_struct(pdev);
if (IS_ERR(pdsc))
return;
err = pds_client_unregister(pdsc, pds_vfio->client_id);
if (err)
dev_err(&pdev->dev, "unregister from DSC failed: %pe\n",
ERR_PTR(err));
pds_vfio->client_id = 0;
}
static int
pds_vfio_suspend_wait_device_cmd(struct pds_vfio_pci_device *pds_vfio, u8 type)
{
union pds_core_adminq_cmd cmd = {
.lm_suspend_status = {
.opcode = PDS_LM_CMD_SUSPEND_STATUS,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
.type = type,
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
unsigned long time_limit;
unsigned long time_start;
unsigned long time_done;
int err;
time_start = jiffies;
time_limit = time_start + HZ * SUSPEND_TIMEOUT_S;
do {
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, true);
if (err != -EAGAIN)
break;
msleep(SUSPEND_CHECK_INTERVAL_MS);
} while (time_before(jiffies, time_limit));
time_done = jiffies;
dev_dbg(dev, "%s: vf%u: Suspend comp received in %d msecs\n", __func__,
pds_vfio->vf_id, jiffies_to_msecs(time_done - time_start));
/* Check the results */
if (time_after_eq(time_done, time_limit)) {
dev_err(dev, "%s: vf%u: Suspend comp timeout\n", __func__,
pds_vfio->vf_id);
err = -ETIMEDOUT;
}
return err;
}
int pds_vfio_suspend_device_cmd(struct pds_vfio_pci_device *pds_vfio, u8 type)
{
union pds_core_adminq_cmd cmd = {
.lm_suspend = {
.opcode = PDS_LM_CMD_SUSPEND,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
.type = type,
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
dev_dbg(dev, "vf%u: Suspend device\n", pds_vfio->vf_id);
/*
* The initial suspend request to the firmware starts the device suspend
* operation and the firmware returns success if it's started
* successfully.
*/
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, true);
if (err) {
dev_err(dev, "vf%u: Suspend failed: %pe\n", pds_vfio->vf_id,
ERR_PTR(err));
return err;
}
/*
* The subsequent suspend status request(s) check if the firmware has
* completed the device suspend process.
*/
return pds_vfio_suspend_wait_device_cmd(pds_vfio, type);
}
int pds_vfio_resume_device_cmd(struct pds_vfio_pci_device *pds_vfio, u8 type)
{
union pds_core_adminq_cmd cmd = {
.lm_resume = {
.opcode = PDS_LM_CMD_RESUME,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
.type = type,
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
dev_dbg(dev, "vf%u: Resume device\n", pds_vfio->vf_id);
return pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, true);
}
int pds_vfio_get_lm_state_size_cmd(struct pds_vfio_pci_device *pds_vfio, u64 *size)
{
union pds_core_adminq_cmd cmd = {
.lm_state_size = {
.opcode = PDS_LM_CMD_STATE_SIZE,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
dev_dbg(dev, "vf%u: Get migration status\n", pds_vfio->vf_id);
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err)
return err;
*size = le64_to_cpu(comp.lm_state_size.size);
return 0;
}
static int pds_vfio_dma_map_lm_file(struct device *dev,
enum dma_data_direction dir,
struct pds_vfio_lm_file *lm_file)
{
struct pds_lm_sg_elem *sgl, *sge;
struct scatterlist *sg;
dma_addr_t sgl_addr;
size_t sgl_size;
int err;
int i;
if (!lm_file)
return -EINVAL;
/* dma map file pages */
err = dma_map_sgtable(dev, &lm_file->sg_table, dir, 0);
if (err)
return err;
lm_file->num_sge = lm_file->sg_table.nents;
/* alloc sgl */
sgl_size = lm_file->num_sge * sizeof(struct pds_lm_sg_elem);
sgl = kzalloc(sgl_size, GFP_KERNEL);
if (!sgl) {
err = -ENOMEM;
goto out_unmap_sgtable;
}
/* fill sgl */
sge = sgl;
for_each_sgtable_dma_sg(&lm_file->sg_table, sg, i) {
sge->addr = cpu_to_le64(sg_dma_address(sg));
sge->len = cpu_to_le32(sg_dma_len(sg));
dev_dbg(dev, "addr = %llx, len = %u\n", sge->addr, sge->len);
sge++;
}
sgl_addr = dma_map_single(dev, sgl, sgl_size, DMA_TO_DEVICE);
if (dma_mapping_error(dev, sgl_addr)) {
err = -EIO;
goto out_free_sgl;
}
lm_file->sgl = sgl;
lm_file->sgl_addr = sgl_addr;
return 0;
out_free_sgl:
kfree(sgl);
out_unmap_sgtable:
lm_file->num_sge = 0;
dma_unmap_sgtable(dev, &lm_file->sg_table, dir, 0);
return err;
}
static void pds_vfio_dma_unmap_lm_file(struct device *dev,
enum dma_data_direction dir,
struct pds_vfio_lm_file *lm_file)
{
if (!lm_file)
return;
/* free sgl */
if (lm_file->sgl) {
dma_unmap_single(dev, lm_file->sgl_addr,
lm_file->num_sge * sizeof(*lm_file->sgl),
DMA_TO_DEVICE);
kfree(lm_file->sgl);
lm_file->sgl = NULL;
lm_file->sgl_addr = DMA_MAPPING_ERROR;
lm_file->num_sge = 0;
}
/* dma unmap file pages */
dma_unmap_sgtable(dev, &lm_file->sg_table, dir, 0);
}
int pds_vfio_get_lm_state_cmd(struct pds_vfio_pci_device *pds_vfio)
{
union pds_core_adminq_cmd cmd = {
.lm_save = {
.opcode = PDS_LM_CMD_SAVE,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
},
};
struct pci_dev *pdev = pds_vfio_to_pci_dev(pds_vfio);
struct device *pdsc_dev = &pci_physfn(pdev)->dev;
union pds_core_adminq_comp comp = {};
struct pds_vfio_lm_file *lm_file;
int err;
dev_dbg(&pdev->dev, "vf%u: Get migration state\n", pds_vfio->vf_id);
lm_file = pds_vfio->save_file;
err = pds_vfio_dma_map_lm_file(pdsc_dev, DMA_FROM_DEVICE, lm_file);
if (err) {
dev_err(&pdev->dev, "failed to map save migration file: %pe\n",
ERR_PTR(err));
return err;
}
cmd.lm_save.sgl_addr = cpu_to_le64(lm_file->sgl_addr);
cmd.lm_save.num_sge = cpu_to_le32(lm_file->num_sge);
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err)
dev_err(&pdev->dev, "failed to get migration state: %pe\n",
ERR_PTR(err));
pds_vfio_dma_unmap_lm_file(pdsc_dev, DMA_FROM_DEVICE, lm_file);
return err;
}
int pds_vfio_set_lm_state_cmd(struct pds_vfio_pci_device *pds_vfio)
{
union pds_core_adminq_cmd cmd = {
.lm_restore = {
.opcode = PDS_LM_CMD_RESTORE,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
},
};
struct pci_dev *pdev = pds_vfio_to_pci_dev(pds_vfio);
struct device *pdsc_dev = &pci_physfn(pdev)->dev;
union pds_core_adminq_comp comp = {};
struct pds_vfio_lm_file *lm_file;
int err;
dev_dbg(&pdev->dev, "vf%u: Set migration state\n", pds_vfio->vf_id);
lm_file = pds_vfio->restore_file;
err = pds_vfio_dma_map_lm_file(pdsc_dev, DMA_TO_DEVICE, lm_file);
if (err) {
dev_err(&pdev->dev,
"failed to map restore migration file: %pe\n",
ERR_PTR(err));
return err;
}
cmd.lm_restore.sgl_addr = cpu_to_le64(lm_file->sgl_addr);
cmd.lm_restore.num_sge = cpu_to_le32(lm_file->num_sge);
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err)
dev_err(&pdev->dev, "failed to set migration state: %pe\n",
ERR_PTR(err));
pds_vfio_dma_unmap_lm_file(pdsc_dev, DMA_TO_DEVICE, lm_file);
return err;
}
void pds_vfio_send_host_vf_lm_status_cmd(struct pds_vfio_pci_device *pds_vfio,
enum pds_lm_host_vf_status vf_status)
{
union pds_core_adminq_cmd cmd = {
.lm_host_vf_status = {
.opcode = PDS_LM_CMD_HOST_VF_STATUS,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
.status = vf_status,
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
dev_dbg(dev, "vf%u: Set host VF LM status: %u", pds_vfio->vf_id,
vf_status);
if (vf_status != PDS_LM_STA_IN_PROGRESS &&
vf_status != PDS_LM_STA_NONE) {
dev_warn(dev, "Invalid host VF migration status, %d\n",
vf_status);
return;
}
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err)
dev_warn(dev, "failed to send host VF migration status: %pe\n",
ERR_PTR(err));
}
int pds_vfio_dirty_status_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 regions_dma, u8 *max_regions, u8 *num_regions)
{
union pds_core_adminq_cmd cmd = {
.lm_dirty_status = {
.opcode = PDS_LM_CMD_DIRTY_STATUS,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
dev_dbg(dev, "vf%u: Dirty status\n", pds_vfio->vf_id);
cmd.lm_dirty_status.regions_dma = cpu_to_le64(regions_dma);
cmd.lm_dirty_status.max_regions = *max_regions;
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err) {
dev_err(dev, "failed to get dirty status: %pe\n", ERR_PTR(err));
return err;
}
/* only support seq_ack approach for now */
if (!(le32_to_cpu(comp.lm_dirty_status.bmp_type_mask) &
BIT(PDS_LM_DIRTY_BMP_TYPE_SEQ_ACK))) {
dev_err(dev, "Dirty bitmap tracking SEQ_ACK not supported\n");
return -EOPNOTSUPP;
}
*num_regions = comp.lm_dirty_status.num_regions;
*max_regions = comp.lm_dirty_status.max_regions;
dev_dbg(dev,
"Page Tracking Status command successful, max_regions: %d, num_regions: %d, bmp_type: %s\n",
*max_regions, *num_regions, "PDS_LM_DIRTY_BMP_TYPE_SEQ_ACK");
return 0;
}
int pds_vfio_dirty_enable_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 regions_dma, u8 num_regions)
{
union pds_core_adminq_cmd cmd = {
.lm_dirty_enable = {
.opcode = PDS_LM_CMD_DIRTY_ENABLE,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
.regions_dma = cpu_to_le64(regions_dma),
.bmp_type = PDS_LM_DIRTY_BMP_TYPE_SEQ_ACK,
.num_regions = num_regions,
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err) {
dev_err(dev, "failed dirty tracking enable: %pe\n",
ERR_PTR(err));
return err;
}
return 0;
}
int pds_vfio_dirty_disable_cmd(struct pds_vfio_pci_device *pds_vfio)
{
union pds_core_adminq_cmd cmd = {
.lm_dirty_disable = {
.opcode = PDS_LM_CMD_DIRTY_DISABLE,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err || comp.lm_dirty_status.num_regions != 0) {
/* in case num_regions is still non-zero after disable */
err = err ? err : -EIO;
dev_err(dev,
"failed dirty tracking disable: %pe, num_regions %d\n",
ERR_PTR(err), comp.lm_dirty_status.num_regions);
return err;
}
return 0;
}
int pds_vfio_dirty_seq_ack_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 sgl_dma, u16 num_sge, u32 offset,
u32 total_len, bool read_seq)
{
const char *cmd_type_str = read_seq ? "read_seq" : "write_ack";
union pds_core_adminq_cmd cmd = {
.lm_dirty_seq_ack = {
.vf_id = cpu_to_le16(pds_vfio->vf_id),
.len_bytes = cpu_to_le32(total_len),
.off_bytes = cpu_to_le32(offset),
.sgl_addr = cpu_to_le64(sgl_dma),
.num_sge = cpu_to_le16(num_sge),
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
if (read_seq)
cmd.lm_dirty_seq_ack.opcode = PDS_LM_CMD_DIRTY_READ_SEQ;
else
cmd.lm_dirty_seq_ack.opcode = PDS_LM_CMD_DIRTY_WRITE_ACK;
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err) {
dev_err(dev, "failed cmd Page Tracking %s: %pe\n", cmd_type_str,
ERR_PTR(err));
return err;
}
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#ifndef _CMDS_H_
#define _CMDS_H_
int pds_vfio_register_client_cmd(struct pds_vfio_pci_device *pds_vfio);
void pds_vfio_unregister_client_cmd(struct pds_vfio_pci_device *pds_vfio);
int pds_vfio_suspend_device_cmd(struct pds_vfio_pci_device *pds_vfio, u8 type);
int pds_vfio_resume_device_cmd(struct pds_vfio_pci_device *pds_vfio, u8 type);
int pds_vfio_get_lm_state_size_cmd(struct pds_vfio_pci_device *pds_vfio, u64 *size);
int pds_vfio_get_lm_state_cmd(struct pds_vfio_pci_device *pds_vfio);
int pds_vfio_set_lm_state_cmd(struct pds_vfio_pci_device *pds_vfio);
void pds_vfio_send_host_vf_lm_status_cmd(struct pds_vfio_pci_device *pds_vfio,
enum pds_lm_host_vf_status vf_status);
int pds_vfio_dirty_status_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 regions_dma, u8 *max_regions,
u8 *num_regions);
int pds_vfio_dirty_enable_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 regions_dma, u8 num_regions);
int pds_vfio_dirty_disable_cmd(struct pds_vfio_pci_device *pds_vfio);
int pds_vfio_dirty_seq_ack_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 sgl_dma, u16 num_sge, u32 offset,
u32 total_len, bool read_seq);
#endif /* _CMDS_H_ */
+645
View File
@@ -0,0 +1,645 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#include <linux/interval_tree.h>
#include <linux/vfio.h>
#include <linux/vmalloc.h>
#include <linux/pds/pds_common.h>
#include <linux/pds/pds_core_if.h>
#include <linux/pds/pds_adminq.h>
#include "vfio_dev.h"
#include "cmds.h"
#include "dirty.h"
#define READ_SEQ true
#define WRITE_ACK false
bool pds_vfio_dirty_is_enabled(struct pds_vfio_pci_device *pds_vfio)
{
return pds_vfio->dirty.is_enabled;
}
void pds_vfio_dirty_set_enabled(struct pds_vfio_pci_device *pds_vfio)
{
pds_vfio->dirty.is_enabled = true;
}
void pds_vfio_dirty_set_disabled(struct pds_vfio_pci_device *pds_vfio)
{
pds_vfio->dirty.is_enabled = false;
}
static void
pds_vfio_print_guest_region_info(struct pds_vfio_pci_device *pds_vfio,
u8 max_regions)
{
int len = max_regions * sizeof(struct pds_lm_dirty_region_info);
struct pci_dev *pdev = pds_vfio->vfio_coredev.pdev;
struct device *pdsc_dev = &pci_physfn(pdev)->dev;
struct pds_lm_dirty_region_info *region_info;
dma_addr_t regions_dma;
u8 num_regions;
int err;
region_info = kzalloc_objs(struct pds_lm_dirty_region_info, max_regions);
if (!region_info)
return;
regions_dma =
dma_map_single(pdsc_dev, region_info, len, DMA_FROM_DEVICE);
if (dma_mapping_error(pdsc_dev, regions_dma))
goto out_free_region_info;
err = pds_vfio_dirty_status_cmd(pds_vfio, regions_dma, &max_regions,
&num_regions);
dma_unmap_single(pdsc_dev, regions_dma, len, DMA_FROM_DEVICE);
if (err)
goto out_free_region_info;
for (unsigned int i = 0; i < num_regions; i++)
dev_dbg(&pdev->dev,
"region_info[%d]: dma_base 0x%llx page_count %u page_size_log2 %u\n",
i, le64_to_cpu(region_info[i].dma_base),
le32_to_cpu(region_info[i].page_count),
region_info[i].page_size_log2);
out_free_region_info:
kfree(region_info);
}
static int pds_vfio_dirty_alloc_bitmaps(struct pds_vfio_region *region,
unsigned long bytes)
{
unsigned long *host_seq_bmp, *host_ack_bmp;
host_seq_bmp = vzalloc(bytes);
if (!host_seq_bmp)
return -ENOMEM;
host_ack_bmp = vzalloc(bytes);
if (!host_ack_bmp) {
vfree(host_seq_bmp);
return -ENOMEM;
}
region->host_seq = host_seq_bmp;
region->host_ack = host_ack_bmp;
region->bmp_bytes = bytes;
return 0;
}
static void pds_vfio_dirty_free_bitmaps(struct pds_vfio_dirty *dirty)
{
if (!dirty->regions)
return;
for (int i = 0; i < dirty->num_regions; i++) {
struct pds_vfio_region *region = &dirty->regions[i];
vfree(region->host_seq);
vfree(region->host_ack);
region->host_seq = NULL;
region->host_ack = NULL;
region->bmp_bytes = 0;
}
}
static void __pds_vfio_dirty_free_sgl(struct pds_vfio_pci_device *pds_vfio,
struct pds_vfio_region *region)
{
struct pci_dev *pdev = pds_vfio->vfio_coredev.pdev;
struct device *pdsc_dev = &pci_physfn(pdev)->dev;
dma_unmap_single(pdsc_dev, region->sgl_addr,
region->num_sge * sizeof(struct pds_lm_sg_elem),
DMA_BIDIRECTIONAL);
kfree(region->sgl);
region->num_sge = 0;
region->sgl = NULL;
region->sgl_addr = 0;
}
static void pds_vfio_dirty_free_sgl(struct pds_vfio_pci_device *pds_vfio)
{
struct pds_vfio_dirty *dirty = &pds_vfio->dirty;
if (!dirty->regions)
return;
for (int i = 0; i < dirty->num_regions; i++) {
struct pds_vfio_region *region = &dirty->regions[i];
if (region->sgl)
__pds_vfio_dirty_free_sgl(pds_vfio, region);
}
}
static int pds_vfio_dirty_alloc_sgl(struct pds_vfio_pci_device *pds_vfio,
struct pds_vfio_region *region,
u32 page_count)
{
struct pci_dev *pdev = pds_vfio->vfio_coredev.pdev;
struct device *pdsc_dev = &pci_physfn(pdev)->dev;
struct pds_lm_sg_elem *sgl;
dma_addr_t sgl_addr;
size_t sgl_size;
u32 max_sge;
max_sge = DIV_ROUND_UP(page_count, PAGE_SIZE * 8);
sgl_size = max_sge * sizeof(struct pds_lm_sg_elem);
sgl = kzalloc(sgl_size, GFP_KERNEL);
if (!sgl)
return -ENOMEM;
sgl_addr = dma_map_single(pdsc_dev, sgl, sgl_size, DMA_BIDIRECTIONAL);
if (dma_mapping_error(pdsc_dev, sgl_addr)) {
kfree(sgl);
return -EIO;
}
region->sgl = sgl;
region->num_sge = max_sge;
region->sgl_addr = sgl_addr;
return 0;
}
static void pds_vfio_dirty_free_regions(struct pds_vfio_dirty *dirty)
{
vfree(dirty->regions);
dirty->regions = NULL;
dirty->num_regions = 0;
}
static int pds_vfio_dirty_alloc_regions(struct pds_vfio_pci_device *pds_vfio,
struct pds_lm_dirty_region_info *region_info,
u64 region_page_size, u8 num_regions)
{
struct pci_dev *pdev = pds_vfio->vfio_coredev.pdev;
struct pds_vfio_dirty *dirty = &pds_vfio->dirty;
u32 dev_bmp_offset_byte = 0;
int err;
dirty->regions = vcalloc(num_regions, sizeof(struct pds_vfio_region));
if (!dirty->regions)
return -ENOMEM;
dirty->num_regions = num_regions;
for (int i = 0; i < num_regions; i++) {
struct pds_lm_dirty_region_info *ri = &region_info[i];
struct pds_vfio_region *region = &dirty->regions[i];
u64 region_size, region_start;
u32 page_count;
/* page_count might be adjusted by the device */
page_count = le32_to_cpu(ri->page_count);
region_start = le64_to_cpu(ri->dma_base);
region_size = page_count * region_page_size;
err = pds_vfio_dirty_alloc_bitmaps(region,
page_count / BITS_PER_BYTE);
if (err) {
dev_err(&pdev->dev, "Failed to alloc dirty bitmaps: %pe\n",
ERR_PTR(err));
goto out_free_regions;
}
err = pds_vfio_dirty_alloc_sgl(pds_vfio, region, page_count);
if (err) {
dev_err(&pdev->dev, "Failed to alloc dirty sg lists: %pe\n",
ERR_PTR(err));
goto out_free_regions;
}
region->size = region_size;
region->start = region_start;
region->page_size = region_page_size;
region->dev_bmp_offset_start_byte = dev_bmp_offset_byte;
dev_bmp_offset_byte += page_count / BITS_PER_BYTE;
if (dev_bmp_offset_byte % BITS_PER_BYTE) {
dev_err(&pdev->dev, "Device bitmap offset is mis-aligned\n");
err = -EINVAL;
goto out_free_regions;
}
}
return 0;
out_free_regions:
pds_vfio_dirty_free_bitmaps(dirty);
pds_vfio_dirty_free_sgl(pds_vfio);
pds_vfio_dirty_free_regions(dirty);
return err;
}
static int pds_vfio_dirty_enable(struct pds_vfio_pci_device *pds_vfio,
struct rb_root_cached *ranges, u32 nnodes,
u64 *page_size)
{
struct pci_dev *pdev = pds_vfio->vfio_coredev.pdev;
struct device *pdsc_dev = &pci_physfn(pdev)->dev;
struct pds_lm_dirty_region_info *region_info;
struct interval_tree_node *node = NULL;
u64 region_page_size = *page_size;
u8 max_regions = 0, num_regions;
dma_addr_t regions_dma = 0;
u32 num_ranges = nnodes;
int err;
u16 len;
dev_dbg(&pdev->dev, "vf%u: Start dirty page tracking\n",
pds_vfio->vf_id);
if (pds_vfio_dirty_is_enabled(pds_vfio))
return -EINVAL;
/* find if dirty tracking is disabled, i.e. num_regions == 0 */
err = pds_vfio_dirty_status_cmd(pds_vfio, 0, &max_regions,
&num_regions);
if (err < 0) {
dev_err(&pdev->dev, "Failed to get dirty status, err %pe\n",
ERR_PTR(err));
return err;
} else if (num_regions) {
dev_err(&pdev->dev,
"Dirty tracking already enabled for %d regions\n",
num_regions);
return -EEXIST;
} else if (!max_regions) {
dev_err(&pdev->dev,
"Device doesn't support dirty tracking, max_regions %d\n",
max_regions);
return -EOPNOTSUPP;
}
if (num_ranges > max_regions) {
vfio_combine_iova_ranges(ranges, nnodes, max_regions);
num_ranges = max_regions;
}
region_info = kzalloc_objs(*region_info, num_ranges);
if (!region_info)
return -ENOMEM;
len = num_ranges * sizeof(*region_info);
node = interval_tree_iter_first(ranges, 0, ULONG_MAX);
if (!node) {
err = -EINVAL;
goto out_free_region_info;
}
for (int i = 0; i < num_ranges; i++) {
struct pds_lm_dirty_region_info *ri = &region_info[i];
u64 region_size = node->last - node->start + 1;
u64 region_start = node->start;
u32 page_count;
page_count = DIV_ROUND_UP(region_size, region_page_size);
ri->dma_base = cpu_to_le64(region_start);
ri->page_count = cpu_to_le32(page_count);
ri->page_size_log2 = ilog2(region_page_size);
dev_dbg(&pdev->dev,
"region_info[%d]: region_start 0x%llx region_end 0x%lx region_size 0x%llx page_count %u page_size %llu\n",
i, region_start, node->last, region_size, page_count,
region_page_size);
node = interval_tree_iter_next(node, 0, ULONG_MAX);
}
regions_dma = dma_map_single(pdsc_dev, (void *)region_info, len,
DMA_BIDIRECTIONAL);
if (dma_mapping_error(pdsc_dev, regions_dma)) {
err = -ENOMEM;
goto out_free_region_info;
}
err = pds_vfio_dirty_enable_cmd(pds_vfio, regions_dma, num_ranges);
dma_unmap_single(pdsc_dev, regions_dma, len, DMA_BIDIRECTIONAL);
if (err)
goto out_free_region_info;
err = pds_vfio_dirty_alloc_regions(pds_vfio, region_info,
region_page_size, num_ranges);
if (err) {
dev_err(&pdev->dev,
"Failed to allocate %d regions for tracking dirty regions: %pe\n",
num_regions, ERR_PTR(err));
goto out_dirty_disable;
}
pds_vfio_dirty_set_enabled(pds_vfio);
pds_vfio_print_guest_region_info(pds_vfio, max_regions);
kfree(region_info);
return 0;
out_dirty_disable:
pds_vfio_dirty_disable_cmd(pds_vfio);
out_free_region_info:
kfree(region_info);
return err;
}
void pds_vfio_dirty_disable(struct pds_vfio_pci_device *pds_vfio, bool send_cmd)
{
if (pds_vfio_dirty_is_enabled(pds_vfio)) {
pds_vfio_dirty_set_disabled(pds_vfio);
if (send_cmd)
pds_vfio_dirty_disable_cmd(pds_vfio);
pds_vfio_dirty_free_sgl(pds_vfio);
pds_vfio_dirty_free_bitmaps(&pds_vfio->dirty);
pds_vfio_dirty_free_regions(&pds_vfio->dirty);
}
if (send_cmd)
pds_vfio_send_host_vf_lm_status_cmd(pds_vfio, PDS_LM_STA_NONE);
}
static int pds_vfio_dirty_seq_ack(struct pds_vfio_pci_device *pds_vfio,
struct pds_vfio_region *region,
unsigned long *seq_ack_bmp, u32 offset,
u32 bmp_bytes, bool read_seq)
{
const char *bmp_type_str = read_seq ? "read_seq" : "write_ack";
u8 dma_dir = read_seq ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
struct pci_dev *pdev = pds_vfio->vfio_coredev.pdev;
struct device *pdsc_dev = &pci_physfn(pdev)->dev;
unsigned long long npages;
struct sg_table sg_table;
struct scatterlist *sg;
struct page **pages;
u32 page_offset;
const void *bmp;
size_t size;
u16 num_sge;
int err;
int i;
bmp = (void *)((u64)seq_ack_bmp + offset);
page_offset = offset_in_page(bmp);
bmp -= page_offset;
/*
* Start and end of bitmap section to seq/ack might not be page
* aligned, so use the page_offset to account for that so there
* will be enough pages to represent the bmp_bytes
*/
npages = DIV_ROUND_UP_ULL(bmp_bytes + page_offset, PAGE_SIZE);
pages = kmalloc_objs(*pages, npages);
if (!pages)
return -ENOMEM;
for (unsigned long long i = 0; i < npages; i++) {
struct page *page = vmalloc_to_page(bmp);
if (!page) {
err = -EFAULT;
goto out_free_pages;
}
pages[i] = page;
bmp += PAGE_SIZE;
}
err = sg_alloc_table_from_pages(&sg_table, pages, npages, page_offset,
bmp_bytes, GFP_KERNEL);
if (err)
goto out_free_pages;
err = dma_map_sgtable(pdsc_dev, &sg_table, dma_dir, 0);
if (err)
goto out_free_sg_table;
for_each_sgtable_dma_sg(&sg_table, sg, i) {
struct pds_lm_sg_elem *sg_elem = &region->sgl[i];
sg_elem->addr = cpu_to_le64(sg_dma_address(sg));
sg_elem->len = cpu_to_le32(sg_dma_len(sg));
}
num_sge = sg_table.nents;
size = num_sge * sizeof(struct pds_lm_sg_elem);
offset += region->dev_bmp_offset_start_byte;
dma_sync_single_for_device(pdsc_dev, region->sgl_addr, size, dma_dir);
err = pds_vfio_dirty_seq_ack_cmd(pds_vfio, region->sgl_addr, num_sge,
offset, bmp_bytes, read_seq);
if (err)
dev_err(&pdev->dev,
"Dirty bitmap %s failed offset %u bmp_bytes %u num_sge %u DMA 0x%llx: %pe\n",
bmp_type_str, offset, bmp_bytes,
num_sge, region->sgl_addr, ERR_PTR(err));
dma_sync_single_for_cpu(pdsc_dev, region->sgl_addr, size, dma_dir);
dma_unmap_sgtable(pdsc_dev, &sg_table, dma_dir, 0);
out_free_sg_table:
sg_free_table(&sg_table);
out_free_pages:
kfree(pages);
return err;
}
static int pds_vfio_dirty_write_ack(struct pds_vfio_pci_device *pds_vfio,
struct pds_vfio_region *region,
u32 offset, u32 len)
{
return pds_vfio_dirty_seq_ack(pds_vfio, region, region->host_ack,
offset, len, WRITE_ACK);
}
static int pds_vfio_dirty_read_seq(struct pds_vfio_pci_device *pds_vfio,
struct pds_vfio_region *region,
u32 offset, u32 len)
{
return pds_vfio_dirty_seq_ack(pds_vfio, region, region->host_seq,
offset, len, READ_SEQ);
}
static int pds_vfio_dirty_process_bitmaps(struct pds_vfio_pci_device *pds_vfio,
struct pds_vfio_region *region,
struct iova_bitmap *dirty_bitmap,
u32 bmp_offset, u32 len_bytes)
{
u64 page_size = region->page_size;
u64 region_start = region->start;
u32 bmp_offset_bit;
__le64 *seq, *ack;
int dword_count;
dword_count = len_bytes / sizeof(u64);
seq = (__le64 *)((u64)region->host_seq + bmp_offset);
ack = (__le64 *)((u64)region->host_ack + bmp_offset);
bmp_offset_bit = bmp_offset * 8;
for (int i = 0; i < dword_count; i++) {
u64 xor = le64_to_cpu(seq[i]) ^ le64_to_cpu(ack[i]);
/* prepare for next write_ack call */
ack[i] = seq[i];
for (u8 bit_i = 0; bit_i < BITS_PER_TYPE(u64); ++bit_i) {
if (xor & BIT(bit_i)) {
u64 abs_bit_i = bmp_offset_bit +
i * BITS_PER_TYPE(u64) + bit_i;
u64 addr = abs_bit_i * page_size + region_start;
iova_bitmap_set(dirty_bitmap, addr, page_size);
}
}
}
return 0;
}
static struct pds_vfio_region *
pds_vfio_get_region(struct pds_vfio_pci_device *pds_vfio, unsigned long iova)
{
struct pds_vfio_dirty *dirty = &pds_vfio->dirty;
for (int i = 0; i < dirty->num_regions; i++) {
struct pds_vfio_region *region = &dirty->regions[i];
if (iova >= region->start &&
iova < (region->start + region->size))
return region;
}
return NULL;
}
static int pds_vfio_dirty_sync(struct pds_vfio_pci_device *pds_vfio,
struct iova_bitmap *dirty_bitmap,
unsigned long iova, unsigned long length)
{
struct device *dev = &pds_vfio->vfio_coredev.pdev->dev;
struct pds_vfio_region *region;
u64 bmp_offset, bmp_bytes;
u64 bitmap_size, pages;
int err;
dev_dbg(dev, "vf%u: Get dirty page bitmap\n", pds_vfio->vf_id);
if (!pds_vfio_dirty_is_enabled(pds_vfio)) {
dev_err(dev, "vf%u: Sync failed, dirty tracking is disabled\n",
pds_vfio->vf_id);
return -EINVAL;
}
region = pds_vfio_get_region(pds_vfio, iova);
if (!region) {
dev_err(dev, "vf%u: Failed to find region that contains iova 0x%lx length 0x%lx\n",
pds_vfio->vf_id, iova, length);
return -EINVAL;
}
pages = DIV_ROUND_UP(length, region->page_size);
bitmap_size =
round_up(pages, sizeof(u64) * BITS_PER_BYTE) / BITS_PER_BYTE;
dev_dbg(dev,
"vf%u: iova 0x%lx length %lu page_size %llu pages %llu bitmap_size %llu\n",
pds_vfio->vf_id, iova, length, region->page_size,
pages, bitmap_size);
if (!length || ((iova - region->start + length) > region->size)) {
dev_err(dev, "Invalid iova 0x%lx and/or length 0x%lx to sync\n",
iova, length);
return -EINVAL;
}
/* bitmap is modified in 64 bit chunks */
bmp_bytes = ALIGN(DIV_ROUND_UP(length / region->page_size,
sizeof(u64)), sizeof(u64));
if (bmp_bytes != bitmap_size) {
dev_err(dev,
"Calculated bitmap bytes %llu not equal to bitmap size %llu\n",
bmp_bytes, bitmap_size);
return -EINVAL;
}
if (bmp_bytes > region->bmp_bytes) {
dev_err(dev,
"Calculated bitmap bytes %llu larger than region's cached bmp_bytes %llu\n",
bmp_bytes, region->bmp_bytes);
return -EINVAL;
}
bmp_offset = DIV_ROUND_UP((iova - region->start) /
region->page_size, sizeof(u64));
dev_dbg(dev,
"Syncing dirty bitmap, iova 0x%lx length 0x%lx, bmp_offset %llu bmp_bytes %llu\n",
iova, length, bmp_offset, bmp_bytes);
err = pds_vfio_dirty_read_seq(pds_vfio, region, bmp_offset, bmp_bytes);
if (err)
return err;
err = pds_vfio_dirty_process_bitmaps(pds_vfio, region, dirty_bitmap,
bmp_offset, bmp_bytes);
if (err)
return err;
err = pds_vfio_dirty_write_ack(pds_vfio, region, bmp_offset, bmp_bytes);
if (err)
return err;
return 0;
}
int pds_vfio_dma_logging_report(struct vfio_device *vdev, unsigned long iova,
unsigned long length, struct iova_bitmap *dirty)
{
struct pds_vfio_pci_device *pds_vfio =
container_of(vdev, struct pds_vfio_pci_device,
vfio_coredev.vdev);
int err;
mutex_lock(&pds_vfio->state_mutex);
err = pds_vfio_dirty_sync(pds_vfio, dirty, iova, length);
mutex_unlock(&pds_vfio->state_mutex);
return err;
}
int pds_vfio_dma_logging_start(struct vfio_device *vdev,
struct rb_root_cached *ranges, u32 nnodes,
u64 *page_size)
{
struct pds_vfio_pci_device *pds_vfio =
container_of(vdev, struct pds_vfio_pci_device,
vfio_coredev.vdev);
int err;
mutex_lock(&pds_vfio->state_mutex);
pds_vfio_send_host_vf_lm_status_cmd(pds_vfio, PDS_LM_STA_IN_PROGRESS);
err = pds_vfio_dirty_enable(pds_vfio, ranges, nnodes, page_size);
mutex_unlock(&pds_vfio->state_mutex);
return err;
}
int pds_vfio_dma_logging_stop(struct vfio_device *vdev)
{
struct pds_vfio_pci_device *pds_vfio =
container_of(vdev, struct pds_vfio_pci_device,
vfio_coredev.vdev);
mutex_lock(&pds_vfio->state_mutex);
pds_vfio_dirty_disable(pds_vfio, true);
mutex_unlock(&pds_vfio->state_mutex);
return 0;
}
+41
View File
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#ifndef _DIRTY_H_
#define _DIRTY_H_
struct pds_vfio_region {
unsigned long *host_seq;
unsigned long *host_ack;
u64 bmp_bytes;
u64 size;
u64 start;
u64 page_size;
struct pds_lm_sg_elem *sgl;
dma_addr_t sgl_addr;
u32 dev_bmp_offset_start_byte;
u16 num_sge;
};
struct pds_vfio_dirty {
struct pds_vfio_region *regions;
u8 num_regions;
bool is_enabled;
};
struct pds_vfio_pci_device;
bool pds_vfio_dirty_is_enabled(struct pds_vfio_pci_device *pds_vfio);
void pds_vfio_dirty_set_enabled(struct pds_vfio_pci_device *pds_vfio);
void pds_vfio_dirty_set_disabled(struct pds_vfio_pci_device *pds_vfio);
void pds_vfio_dirty_disable(struct pds_vfio_pci_device *pds_vfio,
bool send_cmd);
int pds_vfio_dma_logging_report(struct vfio_device *vdev, unsigned long iova,
unsigned long length,
struct iova_bitmap *dirty);
int pds_vfio_dma_logging_start(struct vfio_device *vdev,
struct rb_root_cached *ranges, u32 nnodes,
u64 *page_size);
int pds_vfio_dma_logging_stop(struct vfio_device *vdev);
#endif /* _DIRTY_H_ */
+444
View File
@@ -0,0 +1,444 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#include <linux/anon_inodes.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/highmem.h>
#include <linux/vfio.h>
#include <linux/vfio_pci_core.h>
#include "vfio_dev.h"
#include "cmds.h"
static struct pds_vfio_lm_file *
pds_vfio_get_lm_file(const struct file_operations *fops, int flags, u64 size)
{
struct pds_vfio_lm_file *lm_file = NULL;
unsigned long long npages;
struct page **pages;
void *page_mem;
const void *p;
if (!size)
return NULL;
/* Alloc file structure */
lm_file = kzalloc_obj(*lm_file);
if (!lm_file)
return NULL;
/* Create file */
lm_file->filep =
anon_inode_getfile("pds_vfio_lm", fops, lm_file, flags);
if (IS_ERR(lm_file->filep))
goto out_free_file;
stream_open(lm_file->filep->f_inode, lm_file->filep);
mutex_init(&lm_file->lock);
/* prevent file from being released before we are done with it */
get_file(lm_file->filep);
/* Allocate memory for file pages */
npages = DIV_ROUND_UP_ULL(size, PAGE_SIZE);
pages = kmalloc_objs(*pages, npages);
if (!pages)
goto out_put_file;
page_mem = kvzalloc(ALIGN(size, PAGE_SIZE), GFP_KERNEL);
if (!page_mem)
goto out_free_pages_array;
p = page_mem - offset_in_page(page_mem);
for (unsigned long long i = 0; i < npages; i++) {
if (is_vmalloc_addr(p))
pages[i] = vmalloc_to_page(p);
else
pages[i] = kmap_to_page((void *)p);
if (!pages[i])
goto out_free_page_mem;
p += PAGE_SIZE;
}
/* Create scatterlist of file pages to use for DMA mapping later */
if (sg_alloc_table_from_pages(&lm_file->sg_table, pages, npages, 0,
size, GFP_KERNEL))
goto out_free_page_mem;
lm_file->size = size;
lm_file->pages = pages;
lm_file->npages = npages;
lm_file->page_mem = page_mem;
lm_file->alloc_size = npages * PAGE_SIZE;
return lm_file;
out_free_page_mem:
kvfree(page_mem);
out_free_pages_array:
kfree(pages);
out_put_file:
fput(lm_file->filep);
mutex_destroy(&lm_file->lock);
out_free_file:
kfree(lm_file);
return NULL;
}
static void pds_vfio_put_lm_file(struct pds_vfio_lm_file *lm_file)
{
mutex_lock(&lm_file->lock);
lm_file->disabled = true;
lm_file->size = 0;
lm_file->alloc_size = 0;
lm_file->filep->f_pos = 0;
/* Free scatter list of file pages */
sg_free_table(&lm_file->sg_table);
kvfree(lm_file->page_mem);
lm_file->page_mem = NULL;
kfree(lm_file->pages);
lm_file->pages = NULL;
mutex_unlock(&lm_file->lock);
/* allow file to be released since we are done with it */
fput(lm_file->filep);
}
void pds_vfio_put_save_file(struct pds_vfio_pci_device *pds_vfio)
{
if (!pds_vfio->save_file)
return;
pds_vfio_put_lm_file(pds_vfio->save_file);
pds_vfio->save_file = NULL;
}
void pds_vfio_put_restore_file(struct pds_vfio_pci_device *pds_vfio)
{
if (!pds_vfio->restore_file)
return;
pds_vfio_put_lm_file(pds_vfio->restore_file);
pds_vfio->restore_file = NULL;
}
static struct page *pds_vfio_get_file_page(struct pds_vfio_lm_file *lm_file,
unsigned long offset)
{
unsigned long cur_offset = 0;
struct scatterlist *sg;
unsigned int i;
/* All accesses are sequential */
if (offset < lm_file->last_offset || !lm_file->last_offset_sg) {
lm_file->last_offset = 0;
lm_file->last_offset_sg = lm_file->sg_table.sgl;
lm_file->sg_last_entry = 0;
}
cur_offset = lm_file->last_offset;
for_each_sg(lm_file->last_offset_sg, sg,
lm_file->sg_table.orig_nents - lm_file->sg_last_entry, i) {
if (offset < sg->length + cur_offset) {
lm_file->last_offset_sg = sg;
lm_file->sg_last_entry += i;
lm_file->last_offset = cur_offset;
return sg_page(sg) + (offset - cur_offset) / PAGE_SIZE;
}
cur_offset += sg->length;
}
return NULL;
}
static int pds_vfio_release_file(struct inode *inode, struct file *filp)
{
struct pds_vfio_lm_file *lm_file = filp->private_data;
mutex_lock(&lm_file->lock);
lm_file->filep->f_pos = 0;
lm_file->size = 0;
mutex_unlock(&lm_file->lock);
mutex_destroy(&lm_file->lock);
kfree(lm_file);
return 0;
}
static ssize_t pds_vfio_save_read(struct file *filp, char __user *buf,
size_t len, loff_t *pos)
{
struct pds_vfio_lm_file *lm_file = filp->private_data;
ssize_t done = 0;
if (pos)
return -ESPIPE;
pos = &filp->f_pos;
mutex_lock(&lm_file->lock);
if (lm_file->disabled) {
done = -ENODEV;
goto out_unlock;
}
if (*pos > lm_file->size) {
done = -EINVAL;
goto out_unlock;
}
len = min_t(size_t, lm_file->size - *pos, len);
while (len) {
size_t page_offset;
struct page *page;
size_t page_len;
u8 *from_buff;
int err;
page_offset = (*pos) % PAGE_SIZE;
page = pds_vfio_get_file_page(lm_file, *pos - page_offset);
if (!page) {
if (done == 0)
done = -EINVAL;
goto out_unlock;
}
page_len = min_t(size_t, len, PAGE_SIZE - page_offset);
from_buff = kmap_local_page(page);
err = copy_to_user(buf, from_buff + page_offset, page_len);
kunmap_local(from_buff);
if (err) {
done = -EFAULT;
goto out_unlock;
}
*pos += page_len;
len -= page_len;
done += page_len;
buf += page_len;
}
out_unlock:
mutex_unlock(&lm_file->lock);
return done;
}
static const struct file_operations pds_vfio_save_fops = {
.owner = THIS_MODULE,
.read = pds_vfio_save_read,
.release = pds_vfio_release_file,
};
static int pds_vfio_get_save_file(struct pds_vfio_pci_device *pds_vfio)
{
struct device *dev = &pds_vfio->vfio_coredev.pdev->dev;
struct pds_vfio_lm_file *lm_file;
u64 size;
int err;
/* Get live migration state size in this state */
err = pds_vfio_get_lm_state_size_cmd(pds_vfio, &size);
if (err) {
dev_err(dev, "failed to get save status: %pe\n", ERR_PTR(err));
return err;
}
dev_dbg(dev, "save status, size = %lld\n", size);
if (!size) {
dev_err(dev, "invalid state size\n");
return -EIO;
}
lm_file = pds_vfio_get_lm_file(&pds_vfio_save_fops, O_RDONLY, size);
if (!lm_file) {
dev_err(dev, "failed to create save file\n");
return -ENOENT;
}
dev_dbg(dev, "size = %lld, alloc_size = %lld, npages = %lld\n",
lm_file->size, lm_file->alloc_size, lm_file->npages);
pds_vfio->save_file = lm_file;
return 0;
}
static ssize_t pds_vfio_restore_write(struct file *filp, const char __user *buf,
size_t len, loff_t *pos)
{
struct pds_vfio_lm_file *lm_file = filp->private_data;
loff_t requested_length;
ssize_t done = 0;
if (pos)
return -ESPIPE;
pos = &filp->f_pos;
if (*pos < 0 ||
check_add_overflow((loff_t)len, *pos, &requested_length))
return -EINVAL;
mutex_lock(&lm_file->lock);
if (lm_file->disabled) {
done = -ENODEV;
goto out_unlock;
}
while (len) {
size_t page_offset;
struct page *page;
size_t page_len;
u8 *to_buff;
int err;
page_offset = (*pos) % PAGE_SIZE;
page = pds_vfio_get_file_page(lm_file, *pos - page_offset);
if (!page) {
if (done == 0)
done = -EINVAL;
goto out_unlock;
}
page_len = min_t(size_t, len, PAGE_SIZE - page_offset);
to_buff = kmap_local_page(page);
err = copy_from_user(to_buff + page_offset, buf, page_len);
kunmap_local(to_buff);
if (err) {
done = -EFAULT;
goto out_unlock;
}
*pos += page_len;
len -= page_len;
done += page_len;
buf += page_len;
lm_file->size += page_len;
}
out_unlock:
mutex_unlock(&lm_file->lock);
return done;
}
static const struct file_operations pds_vfio_restore_fops = {
.owner = THIS_MODULE,
.write = pds_vfio_restore_write,
.release = pds_vfio_release_file,
};
static int pds_vfio_get_restore_file(struct pds_vfio_pci_device *pds_vfio)
{
struct device *dev = &pds_vfio->vfio_coredev.pdev->dev;
struct pds_vfio_lm_file *lm_file;
u64 size;
size = sizeof(union pds_lm_dev_state);
dev_dbg(dev, "restore status, size = %lld\n", size);
if (!size) {
dev_err(dev, "invalid state size");
return -EIO;
}
lm_file = pds_vfio_get_lm_file(&pds_vfio_restore_fops, O_WRONLY, size);
if (!lm_file) {
dev_err(dev, "failed to create restore file");
return -ENOENT;
}
pds_vfio->restore_file = lm_file;
return 0;
}
struct file *
pds_vfio_step_device_state_locked(struct pds_vfio_pci_device *pds_vfio,
enum vfio_device_mig_state next)
{
enum vfio_device_mig_state cur = pds_vfio->state;
int err;
if (cur == VFIO_DEVICE_STATE_STOP && next == VFIO_DEVICE_STATE_STOP_COPY) {
err = pds_vfio_get_save_file(pds_vfio);
if (err)
return ERR_PTR(err);
err = pds_vfio_get_lm_state_cmd(pds_vfio);
if (err) {
pds_vfio_put_save_file(pds_vfio);
return ERR_PTR(err);
}
return pds_vfio->save_file->filep;
}
if (cur == VFIO_DEVICE_STATE_STOP_COPY && next == VFIO_DEVICE_STATE_STOP) {
pds_vfio_put_save_file(pds_vfio);
pds_vfio_dirty_disable(pds_vfio, true);
return NULL;
}
if (cur == VFIO_DEVICE_STATE_STOP && next == VFIO_DEVICE_STATE_RESUMING) {
err = pds_vfio_get_restore_file(pds_vfio);
if (err)
return ERR_PTR(err);
return pds_vfio->restore_file->filep;
}
if (cur == VFIO_DEVICE_STATE_RESUMING && next == VFIO_DEVICE_STATE_STOP) {
err = pds_vfio_set_lm_state_cmd(pds_vfio);
if (err)
return ERR_PTR(err);
pds_vfio_put_restore_file(pds_vfio);
return NULL;
}
if (cur == VFIO_DEVICE_STATE_RUNNING && next == VFIO_DEVICE_STATE_RUNNING_P2P) {
pds_vfio_send_host_vf_lm_status_cmd(pds_vfio,
PDS_LM_STA_IN_PROGRESS);
err = pds_vfio_suspend_device_cmd(pds_vfio,
PDS_LM_SUSPEND_RESUME_TYPE_P2P);
if (err)
return ERR_PTR(err);
return NULL;
}
if (cur == VFIO_DEVICE_STATE_RUNNING_P2P && next == VFIO_DEVICE_STATE_RUNNING) {
err = pds_vfio_resume_device_cmd(pds_vfio,
PDS_LM_SUSPEND_RESUME_TYPE_FULL);
if (err)
return ERR_PTR(err);
pds_vfio_send_host_vf_lm_status_cmd(pds_vfio, PDS_LM_STA_NONE);
return NULL;
}
if (cur == VFIO_DEVICE_STATE_STOP && next == VFIO_DEVICE_STATE_RUNNING_P2P) {
err = pds_vfio_resume_device_cmd(pds_vfio,
PDS_LM_SUSPEND_RESUME_TYPE_P2P);
if (err)
return ERR_PTR(err);
return NULL;
}
if (cur == VFIO_DEVICE_STATE_RUNNING_P2P && next == VFIO_DEVICE_STATE_STOP) {
err = pds_vfio_suspend_device_cmd(pds_vfio,
PDS_LM_SUSPEND_RESUME_TYPE_FULL);
if (err)
return ERR_PTR(err);
return NULL;
}
return ERR_PTR(-EINVAL);
}
+42
View File
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#ifndef _LM_H_
#define _LM_H_
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/scatterlist.h>
#include <linux/types.h>
#include <linux/pds/pds_common.h>
#include <linux/pds/pds_adminq.h>
struct pds_vfio_lm_file {
struct file *filep;
struct mutex lock; /* protect live migration data file */
u64 size; /* Size with valid data */
u64 alloc_size; /* Total allocated size. Always >= len */
void *page_mem; /* memory allocated for pages */
struct page **pages; /* Backing pages for file */
unsigned long long npages;
struct sg_table sg_table; /* SG table for backing pages */
struct pds_lm_sg_elem *sgl; /* DMA mapping */
dma_addr_t sgl_addr;
u16 num_sge;
struct scatterlist *last_offset_sg; /* Iterator */
unsigned int sg_last_entry;
unsigned long last_offset;
bool disabled;
};
struct pds_vfio_pci_device;
struct file *
pds_vfio_step_device_state_locked(struct pds_vfio_pci_device *pds_vfio,
enum vfio_device_mig_state next);
void pds_vfio_put_save_file(struct pds_vfio_pci_device *pds_vfio);
void pds_vfio_put_restore_file(struct pds_vfio_pci_device *pds_vfio);
#endif /* _LM_H_ */
+193
View File
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/types.h>
#include <linux/vfio.h>
#include <linux/pds/pds_common.h>
#include <linux/pds/pds_core_if.h>
#include <linux/pds/pds_adminq.h>
#include "vfio_dev.h"
#include "pci_drv.h"
#include "cmds.h"
#define PDS_VFIO_DRV_DESCRIPTION "AMD/Pensando VFIO Device Driver"
#define PCI_VENDOR_ID_PENSANDO 0x1dd8
static void pds_vfio_recovery(struct pds_vfio_pci_device *pds_vfio)
{
/*
* Documentation states that the kernel migration driver must not
* generate asynchronous device state transitions outside of
* manipulation by the user or the VFIO_DEVICE_RESET ioctl.
*
* Since recovery is an asynchronous event received from the device,
* initiate a reset in the following situations:
* 1. Migration is in progress, which will cause the next step of
* the migration to fail.
* 2. If the device is in a state that will be set to
* VFIO_DEVICE_STATE_RUNNING on the next action (i.e. VM is
* shutdown and device is in VFIO_DEVICE_STATE_STOP).
*/
mutex_lock(&pds_vfio->state_mutex);
if ((pds_vfio->state != VFIO_DEVICE_STATE_RUNNING &&
pds_vfio->state != VFIO_DEVICE_STATE_ERROR) ||
(pds_vfio->state == VFIO_DEVICE_STATE_RUNNING &&
pds_vfio_dirty_is_enabled(pds_vfio)))
pds_vfio_reset(pds_vfio, VFIO_DEVICE_STATE_ERROR);
mutex_unlock(&pds_vfio->state_mutex);
}
static int pds_vfio_pci_notify_handler(struct notifier_block *nb,
unsigned long ecode, void *data)
{
struct pds_vfio_pci_device *pds_vfio =
container_of(nb, struct pds_vfio_pci_device, nb);
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_notifyq_comp *event = data;
dev_dbg(dev, "%s: event code %lu\n", __func__, ecode);
/*
* We don't need to do anything for RESET state==0 as there is no notify
* or feedback mechanism available, and it is possible that we won't
* even see a state==0 event since the pds_core recovery is pending.
*
* Any requests from VFIO while state==0 will fail, which will return
* error and may cause migration to fail.
*/
if (ecode == PDS_EVENT_RESET) {
dev_info(dev, "%s: PDS_EVENT_RESET event received, state==%d\n",
__func__, event->reset.state);
/*
* pds_core device finished recovery and sent us the
* notification (state == 1) to allow us to recover
*/
if (event->reset.state == 1)
pds_vfio_recovery(pds_vfio);
}
return 0;
}
static int
pds_vfio_pci_register_event_handler(struct pds_vfio_pci_device *pds_vfio)
{
struct device *dev = pds_vfio_to_dev(pds_vfio);
struct notifier_block *nb = &pds_vfio->nb;
int err;
if (!nb->notifier_call) {
nb->notifier_call = pds_vfio_pci_notify_handler;
err = pdsc_register_notify(nb);
if (err) {
nb->notifier_call = NULL;
dev_err(dev,
"failed to register pds event handler: %pe\n",
ERR_PTR(err));
return -EINVAL;
}
dev_dbg(dev, "pds event handler registered\n");
}
return 0;
}
static void
pds_vfio_pci_unregister_event_handler(struct pds_vfio_pci_device *pds_vfio)
{
if (pds_vfio->nb.notifier_call) {
pdsc_unregister_notify(&pds_vfio->nb);
pds_vfio->nb.notifier_call = NULL;
}
}
static int pds_vfio_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct pds_vfio_pci_device *pds_vfio;
int err;
pds_vfio = vfio_alloc_device(pds_vfio_pci_device, vfio_coredev.vdev,
&pdev->dev, pds_vfio_ops_info());
if (IS_ERR(pds_vfio))
return PTR_ERR(pds_vfio);
dev_set_drvdata(&pdev->dev, &pds_vfio->vfio_coredev);
err = vfio_pci_core_register_device(&pds_vfio->vfio_coredev);
if (err)
goto out_put_vdev;
err = pds_vfio_register_client_cmd(pds_vfio);
if (err) {
dev_err(&pdev->dev, "failed to register as client: %pe\n",
ERR_PTR(err));
goto out_unregister_coredev;
}
err = pds_vfio_pci_register_event_handler(pds_vfio);
if (err)
goto out_unregister_client;
return 0;
out_unregister_client:
pds_vfio_unregister_client_cmd(pds_vfio);
out_unregister_coredev:
vfio_pci_core_unregister_device(&pds_vfio->vfio_coredev);
out_put_vdev:
vfio_put_device(&pds_vfio->vfio_coredev.vdev);
return err;
}
static void pds_vfio_pci_remove(struct pci_dev *pdev)
{
struct pds_vfio_pci_device *pds_vfio = pds_vfio_pci_drvdata(pdev);
pds_vfio_pci_unregister_event_handler(pds_vfio);
pds_vfio_unregister_client_cmd(pds_vfio);
vfio_pci_core_unregister_device(&pds_vfio->vfio_coredev);
vfio_put_device(&pds_vfio->vfio_coredev.vdev);
}
static const struct pci_device_id pds_vfio_pci_table[] = {
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_PENSANDO, 0x1003) }, /* Ethernet VF */
{ 0, }
};
MODULE_DEVICE_TABLE(pci, pds_vfio_pci_table);
static void pds_vfio_pci_aer_reset_done(struct pci_dev *pdev)
{
struct pds_vfio_pci_device *pds_vfio = pds_vfio_pci_drvdata(pdev);
mutex_lock(&pds_vfio->state_mutex);
pds_vfio_reset(pds_vfio, VFIO_DEVICE_STATE_RUNNING);
mutex_unlock(&pds_vfio->state_mutex);
}
static const struct pci_error_handlers pds_vfio_pci_err_handlers = {
.reset_done = pds_vfio_pci_aer_reset_done,
.error_detected = vfio_pci_core_aer_err_detected,
};
static struct pci_driver pds_vfio_pci_driver = {
.name = KBUILD_MODNAME,
.id_table = pds_vfio_pci_table,
.probe = pds_vfio_pci_probe,
.remove = pds_vfio_pci_remove,
.err_handler = &pds_vfio_pci_err_handlers,
.driver_managed_dma = true,
};
module_pci_driver(pds_vfio_pci_driver);
MODULE_IMPORT_NS("IOMMUFD");
MODULE_DESCRIPTION(PDS_VFIO_DRV_DESCRIPTION);
MODULE_AUTHOR("Brett Creeley <brett.creeley@amd.com>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#ifndef _PCI_DRV_H
#define _PCI_DRV_H
#include <linux/pci.h>
#endif /* _PCI_DRV_H */
+215
View File
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#include <linux/vfio.h>
#include <linux/vfio_pci_core.h>
#include "lm.h"
#include "dirty.h"
#include "vfio_dev.h"
struct pci_dev *pds_vfio_to_pci_dev(struct pds_vfio_pci_device *pds_vfio)
{
return pds_vfio->vfio_coredev.pdev;
}
struct device *pds_vfio_to_dev(struct pds_vfio_pci_device *pds_vfio)
{
return &pds_vfio_to_pci_dev(pds_vfio)->dev;
}
struct pds_vfio_pci_device *pds_vfio_pci_drvdata(struct pci_dev *pdev)
{
struct vfio_pci_core_device *core_device = dev_get_drvdata(&pdev->dev);
return container_of(core_device, struct pds_vfio_pci_device,
vfio_coredev);
}
void pds_vfio_reset(struct pds_vfio_pci_device *pds_vfio,
enum vfio_device_mig_state state)
{
pds_vfio_put_restore_file(pds_vfio);
pds_vfio_put_save_file(pds_vfio);
if (state == VFIO_DEVICE_STATE_ERROR)
pds_vfio_dirty_disable(pds_vfio, false);
pds_vfio->state = state;
}
static struct file *
pds_vfio_set_device_state(struct vfio_device *vdev,
enum vfio_device_mig_state new_state)
{
struct pds_vfio_pci_device *pds_vfio =
container_of(vdev, struct pds_vfio_pci_device,
vfio_coredev.vdev);
struct file *res = NULL;
mutex_lock(&pds_vfio->state_mutex);
/*
* only way to transition out of VFIO_DEVICE_STATE_ERROR is via
* VFIO_DEVICE_RESET, so prevent the state machine from running since
* vfio_mig_get_next_state() will throw a WARN_ON() when transitioning
* from VFIO_DEVICE_STATE_ERROR to any other state
*/
while (pds_vfio->state != VFIO_DEVICE_STATE_ERROR &&
new_state != pds_vfio->state) {
enum vfio_device_mig_state next_state;
int err = vfio_mig_get_next_state(vdev, pds_vfio->state,
new_state, &next_state);
if (err) {
res = ERR_PTR(err);
break;
}
res = pds_vfio_step_device_state_locked(pds_vfio, next_state);
if (IS_ERR(res))
break;
pds_vfio->state = next_state;
if (WARN_ON(res && new_state != pds_vfio->state)) {
res = ERR_PTR(-EINVAL);
break;
}
}
mutex_unlock(&pds_vfio->state_mutex);
if (pds_vfio->state == VFIO_DEVICE_STATE_ERROR)
res = ERR_PTR(-EIO);
return res;
}
static int pds_vfio_get_device_state(struct vfio_device *vdev,
enum vfio_device_mig_state *current_state)
{
struct pds_vfio_pci_device *pds_vfio =
container_of(vdev, struct pds_vfio_pci_device,
vfio_coredev.vdev);
mutex_lock(&pds_vfio->state_mutex);
*current_state = pds_vfio->state;
mutex_unlock(&pds_vfio->state_mutex);
return 0;
}
static int pds_vfio_get_device_state_size(struct vfio_device *vdev,
unsigned long *stop_copy_length)
{
*stop_copy_length = PDS_LM_DEVICE_STATE_LENGTH;
return 0;
}
static const struct vfio_migration_ops pds_vfio_lm_ops = {
.migration_set_state = pds_vfio_set_device_state,
.migration_get_state = pds_vfio_get_device_state,
.migration_get_data_size = pds_vfio_get_device_state_size
};
static const struct vfio_log_ops pds_vfio_log_ops = {
.log_start = pds_vfio_dma_logging_start,
.log_stop = pds_vfio_dma_logging_stop,
.log_read_and_clear = pds_vfio_dma_logging_report,
};
static int pds_vfio_init_device(struct vfio_device *vdev)
{
struct pds_vfio_pci_device *pds_vfio =
container_of(vdev, struct pds_vfio_pci_device,
vfio_coredev.vdev);
struct pci_dev *pdev = to_pci_dev(vdev->dev);
int err, vf_id, pci_id;
vf_id = pci_iov_vf_id(pdev);
if (vf_id < 0)
return vf_id;
err = vfio_pci_core_init_dev(vdev);
if (err)
return err;
pds_vfio->vf_id = vf_id;
mutex_init(&pds_vfio->state_mutex);
vdev->migration_flags = VFIO_MIGRATION_STOP_COPY | VFIO_MIGRATION_P2P;
vdev->mig_ops = &pds_vfio_lm_ops;
vdev->log_ops = &pds_vfio_log_ops;
pci_id = PCI_DEVID(pdev->bus->number, pdev->devfn);
dev_dbg(&pdev->dev,
"%s: PF %#04x VF %#04x vf_id %d domain %d pds_vfio %p\n",
__func__, pci_dev_id(pci_physfn(pdev)), pci_id, vf_id,
pci_domain_nr(pdev->bus), pds_vfio);
return 0;
}
static void pds_vfio_release_device(struct vfio_device *vdev)
{
struct pds_vfio_pci_device *pds_vfio =
container_of(vdev, struct pds_vfio_pci_device,
vfio_coredev.vdev);
mutex_destroy(&pds_vfio->state_mutex);
vfio_pci_core_release_dev(vdev);
}
static int pds_vfio_open_device(struct vfio_device *vdev)
{
struct pds_vfio_pci_device *pds_vfio =
container_of(vdev, struct pds_vfio_pci_device,
vfio_coredev.vdev);
int err;
err = vfio_pci_core_enable(&pds_vfio->vfio_coredev);
if (err)
return err;
pds_vfio->state = VFIO_DEVICE_STATE_RUNNING;
vfio_pci_core_finish_enable(&pds_vfio->vfio_coredev);
return 0;
}
static void pds_vfio_close_device(struct vfio_device *vdev)
{
struct pds_vfio_pci_device *pds_vfio =
container_of(vdev, struct pds_vfio_pci_device,
vfio_coredev.vdev);
mutex_lock(&pds_vfio->state_mutex);
pds_vfio_put_restore_file(pds_vfio);
pds_vfio_put_save_file(pds_vfio);
pds_vfio_dirty_disable(pds_vfio, true);
mutex_unlock(&pds_vfio->state_mutex);
vfio_pci_core_close_device(vdev);
}
static const struct vfio_device_ops pds_vfio_ops = {
.name = "pds-vfio",
.init = pds_vfio_init_device,
.release = pds_vfio_release_device,
.open_device = pds_vfio_open_device,
.close_device = pds_vfio_close_device,
.ioctl = vfio_pci_core_ioctl,
.get_region_info_caps = vfio_pci_ioctl_get_region_info,
.device_feature = vfio_pci_core_ioctl_feature,
.read = vfio_pci_core_read,
.write = vfio_pci_core_write,
.mmap = vfio_pci_core_mmap,
.request = vfio_pci_core_request,
.match = vfio_pci_core_match,
.match_token_uuid = vfio_pci_core_match_token_uuid,
.bind_iommufd = vfio_iommufd_physical_bind,
.unbind_iommufd = vfio_iommufd_physical_unbind,
.attach_ioas = vfio_iommufd_physical_attach_ioas,
.detach_ioas = vfio_iommufd_physical_detach_ioas,
};
const struct vfio_device_ops *pds_vfio_ops_info(void)
{
return &pds_vfio_ops;
}
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#ifndef _VFIO_DEV_H_
#define _VFIO_DEV_H_
#include <linux/pci.h>
#include <linux/vfio_pci_core.h>
#include "dirty.h"
#include "lm.h"
struct pds_vfio_pci_device {
struct vfio_pci_core_device vfio_coredev;
struct pds_vfio_lm_file *save_file;
struct pds_vfio_lm_file *restore_file;
struct pds_vfio_dirty dirty;
struct mutex state_mutex; /* protect migration state */
enum vfio_device_mig_state state;
struct notifier_block nb;
int vf_id;
u16 client_id;
};
const struct vfio_device_ops *pds_vfio_ops_info(void);
struct pds_vfio_pci_device *pds_vfio_pci_drvdata(struct pci_dev *pdev);
void pds_vfio_reset(struct pds_vfio_pci_device *pds_vfio,
enum vfio_device_mig_state state);
struct pci_dev *pds_vfio_to_pci_dev(struct pds_vfio_pci_device *pds_vfio);
struct device *pds_vfio_to_dev(struct pds_vfio_pci_device *pds_vfio);
#endif /* _VFIO_DEV_H_ */
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
config QAT_VFIO_PCI
tristate "VFIO support for QAT VF PCI devices"
select VFIO_PCI_CORE
depends on CRYPTO_DEV_QAT_4XXX || CRYPTO_DEV_QAT_420XX || CRYPTO_DEV_QAT_6XXX
help
This provides migration support for Intel(R) QAT Virtual Function
using the VFIO framework.
To compile this as a module, choose M here: the module
will be called qat_vfio_pci. If you don't know what to do here,
say N.
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_QAT_VFIO_PCI) += qat_vfio_pci.o
qat_vfio_pci-y := main.o
+705
View File
@@ -0,0 +1,705 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2024 Intel Corporation */
#include <linux/anon_inodes.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/file.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/vfio_pci_core.h>
#include <linux/qat/qat_mig_dev.h>
/*
* The migration data of each Intel QAT VF device is encapsulated into a
* 4096 bytes block. The data consists of two parts.
* The first is a pre-configured set of attributes of the VF being migrated,
* which are only set when it is created. This can be migrated during pre-copy
* stage and used for a device compatibility check.
* The second is the VF state. This includes the required MMIO regions and
* the shadow states maintained by the QAT PF driver. This part can only be
* saved when the VF is fully quiesced and be migrated during stop-copy stage.
* Both these 2 parts of data are saved in hierarchical structures including
* a preamble section and several raw state sections.
* When the pre-configured part of the migration data is fully retrieved from
* user space, the preamble section are used to validate the correctness of
* the data blocks and check the version compatibility. The raw state sections
* are then used to do a device compatibility check.
* When the device transits from RESUMING state, the VF states are extracted
* from the raw state sections of the VF state part of the migration data and
* then loaded into the device.
*/
struct qat_vf_migration_file {
struct file *filp;
/* protects migration region context */
struct mutex lock;
bool disabled;
struct qat_vf_core_device *qat_vdev;
ssize_t filled_size;
};
struct qat_vf_core_device {
struct vfio_pci_core_device core_device;
struct qat_mig_dev *mdev;
/* protects migration state */
struct mutex state_mutex;
enum vfio_device_mig_state mig_state;
struct qat_vf_migration_file *resuming_migf;
struct qat_vf_migration_file *saving_migf;
};
static int qat_vf_pci_open_device(struct vfio_device *core_vdev)
{
struct qat_vf_core_device *qat_vdev =
container_of(core_vdev, struct qat_vf_core_device,
core_device.vdev);
struct vfio_pci_core_device *vdev = &qat_vdev->core_device;
int ret;
ret = vfio_pci_core_enable(vdev);
if (ret)
return ret;
ret = qat_vfmig_open(qat_vdev->mdev);
if (ret) {
vfio_pci_core_disable(vdev);
return ret;
}
qat_vdev->mig_state = VFIO_DEVICE_STATE_RUNNING;
vfio_pci_core_finish_enable(vdev);
return 0;
}
static void qat_vf_disable_fd(struct qat_vf_migration_file *migf)
{
mutex_lock(&migf->lock);
migf->disabled = true;
migf->filp->f_pos = 0;
migf->filled_size = 0;
mutex_unlock(&migf->lock);
}
static void qat_vf_disable_fds(struct qat_vf_core_device *qat_vdev)
{
if (qat_vdev->resuming_migf) {
qat_vf_disable_fd(qat_vdev->resuming_migf);
fput(qat_vdev->resuming_migf->filp);
qat_vdev->resuming_migf = NULL;
}
if (qat_vdev->saving_migf) {
qat_vf_disable_fd(qat_vdev->saving_migf);
fput(qat_vdev->saving_migf->filp);
qat_vdev->saving_migf = NULL;
}
}
static void qat_vf_pci_close_device(struct vfio_device *core_vdev)
{
struct qat_vf_core_device *qat_vdev = container_of(core_vdev,
struct qat_vf_core_device, core_device.vdev);
qat_vfmig_close(qat_vdev->mdev);
qat_vf_disable_fds(qat_vdev);
vfio_pci_core_close_device(core_vdev);
}
static long qat_vf_precopy_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct qat_vf_migration_file *migf = filp->private_data;
struct qat_vf_core_device *qat_vdev = migf->qat_vdev;
struct qat_mig_dev *mig_dev = qat_vdev->mdev;
struct vfio_precopy_info info;
loff_t *pos = &filp->f_pos;
int ret = 0;
ret = vfio_check_precopy_ioctl(&qat_vdev->core_device.vdev, cmd, arg,
&info);
if (ret)
return ret;
mutex_lock(&qat_vdev->state_mutex);
if (qat_vdev->mig_state != VFIO_DEVICE_STATE_PRE_COPY &&
qat_vdev->mig_state != VFIO_DEVICE_STATE_PRE_COPY_P2P) {
mutex_unlock(&qat_vdev->state_mutex);
return -EINVAL;
}
mutex_lock(&migf->lock);
if (migf->disabled) {
ret = -ENODEV;
goto out;
}
if (*pos > mig_dev->setup_size) {
ret = -EINVAL;
goto out;
}
info.dirty_bytes = 0;
info.initial_bytes = mig_dev->setup_size - *pos;
out:
mutex_unlock(&migf->lock);
mutex_unlock(&qat_vdev->state_mutex);
if (ret)
return ret;
return copy_to_user((void __user *)arg, &info,
offsetofend(struct vfio_precopy_info, dirty_bytes)) ? -EFAULT : 0;
}
static ssize_t qat_vf_save_read(struct file *filp, char __user *buf,
size_t len, loff_t *pos)
{
struct qat_vf_migration_file *migf = filp->private_data;
struct qat_mig_dev *mig_dev = migf->qat_vdev->mdev;
ssize_t done = 0;
loff_t *offs;
int ret;
if (pos)
return -ESPIPE;
offs = &filp->f_pos;
mutex_lock(&migf->lock);
if (*offs > migf->filled_size || *offs < 0) {
done = -EINVAL;
goto out_unlock;
}
if (migf->disabled) {
done = -ENODEV;
goto out_unlock;
}
len = min_t(size_t, migf->filled_size - *offs, len);
if (len) {
ret = copy_to_user(buf, mig_dev->state + *offs, len);
if (ret) {
done = -EFAULT;
goto out_unlock;
}
*offs += len;
done = len;
}
out_unlock:
mutex_unlock(&migf->lock);
return done;
}
static int qat_vf_release_file(struct inode *inode, struct file *filp)
{
struct qat_vf_migration_file *migf = filp->private_data;
qat_vf_disable_fd(migf);
mutex_destroy(&migf->lock);
kfree(migf);
return 0;
}
static const struct file_operations qat_vf_save_fops = {
.owner = THIS_MODULE,
.read = qat_vf_save_read,
.unlocked_ioctl = qat_vf_precopy_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.release = qat_vf_release_file,
};
static int qat_vf_save_state(struct qat_vf_core_device *qat_vdev,
struct qat_vf_migration_file *migf)
{
int ret;
ret = qat_vfmig_save_state(qat_vdev->mdev);
if (ret)
return ret;
migf->filled_size = qat_vdev->mdev->state_size;
return 0;
}
static int qat_vf_save_setup(struct qat_vf_core_device *qat_vdev,
struct qat_vf_migration_file *migf)
{
int ret;
ret = qat_vfmig_save_setup(qat_vdev->mdev);
if (ret)
return ret;
migf->filled_size = qat_vdev->mdev->setup_size;
return 0;
}
/*
* Allocate a file handler for user space and then save the migration data for
* the device being migrated. If this is called in the pre-copy stage, save the
* pre-configured device data. Otherwise, if this is called in the stop-copy
* stage, save the device state. In both cases, update the data size which can
* then be read from user space.
*/
static struct qat_vf_migration_file *
qat_vf_save_device_data(struct qat_vf_core_device *qat_vdev, bool pre_copy)
{
struct qat_vf_migration_file *migf;
int ret;
migf = kzalloc_obj(*migf);
if (!migf)
return ERR_PTR(-ENOMEM);
migf->filp = anon_inode_getfile("qat_vf_mig", &qat_vf_save_fops,
migf, O_RDONLY);
ret = PTR_ERR_OR_ZERO(migf->filp);
if (ret) {
kfree(migf);
return ERR_PTR(ret);
}
stream_open(migf->filp->f_inode, migf->filp);
mutex_init(&migf->lock);
if (pre_copy)
ret = qat_vf_save_setup(qat_vdev, migf);
else
ret = qat_vf_save_state(qat_vdev, migf);
if (ret) {
fput(migf->filp);
return ERR_PTR(ret);
}
migf->qat_vdev = qat_vdev;
return migf;
}
static ssize_t qat_vf_resume_write(struct file *filp, const char __user *buf,
size_t len, loff_t *pos)
{
struct qat_vf_migration_file *migf = filp->private_data;
struct qat_mig_dev *mig_dev = migf->qat_vdev->mdev;
loff_t end, *offs;
ssize_t done = 0;
int ret;
if (pos)
return -ESPIPE;
offs = &filp->f_pos;
mutex_lock(&migf->lock);
if (*offs < 0 || check_add_overflow(len, *offs, &end)) {
done = -EOVERFLOW;
goto out_unlock;
}
if (end > mig_dev->state_size) {
done = -ENOMEM;
goto out_unlock;
}
if (migf->disabled) {
done = -ENODEV;
goto out_unlock;
}
ret = copy_from_user(mig_dev->state + *offs, buf, len);
if (ret) {
done = -EFAULT;
goto out_unlock;
}
*offs += len;
migf->filled_size += len;
/*
* Load the pre-configured device data first to check if the target
* device is compatible with the source device.
*/
ret = qat_vfmig_load_setup(mig_dev, migf->filled_size);
if (ret && ret != -EAGAIN) {
done = ret;
goto out_unlock;
}
done = len;
out_unlock:
mutex_unlock(&migf->lock);
return done;
}
static const struct file_operations qat_vf_resume_fops = {
.owner = THIS_MODULE,
.write = qat_vf_resume_write,
.release = qat_vf_release_file,
};
static struct qat_vf_migration_file *
qat_vf_resume_device_data(struct qat_vf_core_device *qat_vdev)
{
struct qat_vf_migration_file *migf;
int ret;
migf = kzalloc_obj(*migf);
if (!migf)
return ERR_PTR(-ENOMEM);
migf->filp = anon_inode_getfile("qat_vf_mig", &qat_vf_resume_fops, migf, O_WRONLY);
ret = PTR_ERR_OR_ZERO(migf->filp);
if (ret) {
kfree(migf);
return ERR_PTR(ret);
}
migf->qat_vdev = qat_vdev;
migf->filled_size = 0;
stream_open(migf->filp->f_inode, migf->filp);
mutex_init(&migf->lock);
return migf;
}
static int qat_vf_load_device_data(struct qat_vf_core_device *qat_vdev)
{
return qat_vfmig_load_state(qat_vdev->mdev);
}
static struct file *qat_vf_pci_step_device_state(struct qat_vf_core_device *qat_vdev, u32 new)
{
u32 cur = qat_vdev->mig_state;
int ret;
/*
* As the device is not capable of just stopping P2P DMAs, suspend the
* device completely once any of the P2P states are reached.
* When it is suspended, all its MMIO registers can still be operated
* correctly, jobs submitted through ring are queued while no jobs are
* processed by the device. The MMIO states can be safely migrated to
* the target VF during stop-copy stage and restored correctly in the
* target VF. All queued jobs can be resumed then.
*/
if ((cur == VFIO_DEVICE_STATE_RUNNING && new == VFIO_DEVICE_STATE_RUNNING_P2P) ||
(cur == VFIO_DEVICE_STATE_PRE_COPY && new == VFIO_DEVICE_STATE_PRE_COPY_P2P)) {
ret = qat_vfmig_suspend(qat_vdev->mdev);
if (ret)
return ERR_PTR(ret);
return NULL;
}
if ((cur == VFIO_DEVICE_STATE_RUNNING_P2P && new == VFIO_DEVICE_STATE_RUNNING) ||
(cur == VFIO_DEVICE_STATE_PRE_COPY_P2P && new == VFIO_DEVICE_STATE_PRE_COPY)) {
qat_vfmig_resume(qat_vdev->mdev);
return NULL;
}
if ((cur == VFIO_DEVICE_STATE_RUNNING_P2P && new == VFIO_DEVICE_STATE_STOP) ||
(cur == VFIO_DEVICE_STATE_STOP && new == VFIO_DEVICE_STATE_RUNNING_P2P))
return NULL;
if (cur == VFIO_DEVICE_STATE_STOP && new == VFIO_DEVICE_STATE_STOP_COPY) {
struct qat_vf_migration_file *migf;
migf = qat_vf_save_device_data(qat_vdev, false);
if (IS_ERR(migf))
return ERR_CAST(migf);
get_file(migf->filp);
qat_vdev->saving_migf = migf;
return migf->filp;
}
if (cur == VFIO_DEVICE_STATE_STOP && new == VFIO_DEVICE_STATE_RESUMING) {
struct qat_vf_migration_file *migf;
migf = qat_vf_resume_device_data(qat_vdev);
if (IS_ERR(migf))
return ERR_CAST(migf);
get_file(migf->filp);
qat_vdev->resuming_migf = migf;
return migf->filp;
}
if ((cur == VFIO_DEVICE_STATE_STOP_COPY && new == VFIO_DEVICE_STATE_STOP) ||
(cur == VFIO_DEVICE_STATE_PRE_COPY && new == VFIO_DEVICE_STATE_RUNNING) ||
(cur == VFIO_DEVICE_STATE_PRE_COPY_P2P && new == VFIO_DEVICE_STATE_RUNNING_P2P)) {
qat_vf_disable_fds(qat_vdev);
return NULL;
}
if ((cur == VFIO_DEVICE_STATE_RUNNING && new == VFIO_DEVICE_STATE_PRE_COPY) ||
(cur == VFIO_DEVICE_STATE_RUNNING_P2P && new == VFIO_DEVICE_STATE_PRE_COPY_P2P)) {
struct qat_vf_migration_file *migf;
migf = qat_vf_save_device_data(qat_vdev, true);
if (IS_ERR(migf))
return ERR_CAST(migf);
get_file(migf->filp);
qat_vdev->saving_migf = migf;
return migf->filp;
}
if (cur == VFIO_DEVICE_STATE_PRE_COPY_P2P && new == VFIO_DEVICE_STATE_STOP_COPY) {
struct qat_vf_migration_file *migf = qat_vdev->saving_migf;
if (!migf)
return ERR_PTR(-EINVAL);
ret = qat_vf_save_state(qat_vdev, migf);
if (ret)
return ERR_PTR(ret);
return NULL;
}
if (cur == VFIO_DEVICE_STATE_RESUMING && new == VFIO_DEVICE_STATE_STOP) {
ret = qat_vf_load_device_data(qat_vdev);
if (ret)
return ERR_PTR(ret);
qat_vf_disable_fds(qat_vdev);
return NULL;
}
/* vfio_mig_get_next_state() does not use arcs other than the above */
WARN_ON(true);
return ERR_PTR(-EINVAL);
}
static void qat_vf_reset_done(struct qat_vf_core_device *qat_vdev)
{
qat_vdev->mig_state = VFIO_DEVICE_STATE_RUNNING;
qat_vfmig_reset(qat_vdev->mdev);
qat_vf_disable_fds(qat_vdev);
}
static struct file *qat_vf_pci_set_device_state(struct vfio_device *vdev,
enum vfio_device_mig_state new_state)
{
struct qat_vf_core_device *qat_vdev = container_of(vdev,
struct qat_vf_core_device, core_device.vdev);
enum vfio_device_mig_state next_state;
struct file *res = NULL;
int ret;
mutex_lock(&qat_vdev->state_mutex);
while (new_state != qat_vdev->mig_state) {
ret = vfio_mig_get_next_state(vdev, qat_vdev->mig_state,
new_state, &next_state);
if (ret) {
res = ERR_PTR(ret);
break;
}
res = qat_vf_pci_step_device_state(qat_vdev, next_state);
if (IS_ERR(res))
break;
qat_vdev->mig_state = next_state;
if (WARN_ON(res && new_state != qat_vdev->mig_state)) {
fput(res);
res = ERR_PTR(-EINVAL);
break;
}
}
mutex_unlock(&qat_vdev->state_mutex);
return res;
}
static int qat_vf_pci_get_device_state(struct vfio_device *vdev,
enum vfio_device_mig_state *curr_state)
{
struct qat_vf_core_device *qat_vdev = container_of(vdev,
struct qat_vf_core_device, core_device.vdev);
mutex_lock(&qat_vdev->state_mutex);
*curr_state = qat_vdev->mig_state;
mutex_unlock(&qat_vdev->state_mutex);
return 0;
}
static int qat_vf_pci_get_data_size(struct vfio_device *vdev,
unsigned long *stop_copy_length)
{
struct qat_vf_core_device *qat_vdev = container_of(vdev,
struct qat_vf_core_device, core_device.vdev);
mutex_lock(&qat_vdev->state_mutex);
*stop_copy_length = qat_vdev->mdev->state_size;
mutex_unlock(&qat_vdev->state_mutex);
return 0;
}
static const struct vfio_migration_ops qat_vf_pci_mig_ops = {
.migration_set_state = qat_vf_pci_set_device_state,
.migration_get_state = qat_vf_pci_get_device_state,
.migration_get_data_size = qat_vf_pci_get_data_size,
};
static void qat_vf_pci_release_dev(struct vfio_device *core_vdev)
{
struct qat_vf_core_device *qat_vdev = container_of(core_vdev,
struct qat_vf_core_device, core_device.vdev);
qat_vfmig_cleanup(qat_vdev->mdev);
qat_vfmig_destroy(qat_vdev->mdev);
mutex_destroy(&qat_vdev->state_mutex);
vfio_pci_core_release_dev(core_vdev);
}
static int qat_vf_pci_init_dev(struct vfio_device *core_vdev)
{
struct qat_vf_core_device *qat_vdev = container_of(core_vdev,
struct qat_vf_core_device, core_device.vdev);
struct qat_mig_dev *mdev;
struct pci_dev *parent;
int ret, vf_id;
core_vdev->migration_flags = VFIO_MIGRATION_STOP_COPY | VFIO_MIGRATION_P2P |
VFIO_MIGRATION_PRE_COPY;
core_vdev->mig_ops = &qat_vf_pci_mig_ops;
ret = vfio_pci_core_init_dev(core_vdev);
if (ret)
return ret;
mutex_init(&qat_vdev->state_mutex);
parent = pci_physfn(qat_vdev->core_device.pdev);
vf_id = pci_iov_vf_id(qat_vdev->core_device.pdev);
if (vf_id < 0) {
ret = -ENODEV;
goto err_rel;
}
mdev = qat_vfmig_create(parent, vf_id);
if (IS_ERR(mdev)) {
ret = PTR_ERR(mdev);
goto err_rel;
}
ret = qat_vfmig_init(mdev);
if (ret)
goto err_destroy;
qat_vdev->mdev = mdev;
return 0;
err_destroy:
qat_vfmig_destroy(mdev);
err_rel:
vfio_pci_core_release_dev(core_vdev);
return ret;
}
static const struct vfio_device_ops qat_vf_pci_ops = {
.name = "qat-vf-vfio-pci",
.init = qat_vf_pci_init_dev,
.release = qat_vf_pci_release_dev,
.open_device = qat_vf_pci_open_device,
.close_device = qat_vf_pci_close_device,
.ioctl = vfio_pci_core_ioctl,
.get_region_info_caps = vfio_pci_ioctl_get_region_info,
.read = vfio_pci_core_read,
.write = vfio_pci_core_write,
.mmap = vfio_pci_core_mmap,
.request = vfio_pci_core_request,
.match = vfio_pci_core_match,
.match_token_uuid = vfio_pci_core_match_token_uuid,
.bind_iommufd = vfio_iommufd_physical_bind,
.unbind_iommufd = vfio_iommufd_physical_unbind,
.attach_ioas = vfio_iommufd_physical_attach_ioas,
.detach_ioas = vfio_iommufd_physical_detach_ioas,
};
static struct qat_vf_core_device *qat_vf_drvdata(struct pci_dev *pdev)
{
struct vfio_pci_core_device *core_device = pci_get_drvdata(pdev);
return container_of(core_device, struct qat_vf_core_device, core_device);
}
static void qat_vf_pci_aer_reset_done(struct pci_dev *pdev)
{
struct qat_vf_core_device *qat_vdev = qat_vf_drvdata(pdev);
if (!qat_vdev->mdev)
return;
mutex_lock(&qat_vdev->state_mutex);
qat_vf_reset_done(qat_vdev);
mutex_unlock(&qat_vdev->state_mutex);
}
static int
qat_vf_vfio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct device *dev = &pdev->dev;
struct qat_vf_core_device *qat_vdev;
int ret;
qat_vdev = vfio_alloc_device(qat_vf_core_device, core_device.vdev, dev, &qat_vf_pci_ops);
if (IS_ERR(qat_vdev))
return PTR_ERR(qat_vdev);
pci_set_drvdata(pdev, &qat_vdev->core_device);
ret = vfio_pci_core_register_device(&qat_vdev->core_device);
if (ret)
goto out_put_device;
return 0;
out_put_device:
vfio_put_device(&qat_vdev->core_device.vdev);
return ret;
}
static void qat_vf_vfio_pci_remove(struct pci_dev *pdev)
{
struct qat_vf_core_device *qat_vdev = qat_vf_drvdata(pdev);
vfio_pci_core_unregister_device(&qat_vdev->core_device);
vfio_put_device(&qat_vdev->core_device.vdev);
}
static const struct pci_device_id qat_vf_vfio_pci_table[] = {
/* Intel QAT GEN4 4xxx VF device */
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_INTEL, 0x4941) },
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_INTEL, 0x4943) },
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_INTEL, 0x4945) },
/* Intel QAT GEN5 420xx VF device */
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_INTEL, 0x4947) },
/* Intel QAT GEN6 6xxx VF device */
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_INTEL, 0x4949) },
{}
};
MODULE_DEVICE_TABLE(pci, qat_vf_vfio_pci_table);
static const struct pci_error_handlers qat_vf_err_handlers = {
.reset_done = qat_vf_pci_aer_reset_done,
.error_detected = vfio_pci_core_aer_err_detected,
};
static struct pci_driver qat_vf_vfio_pci_driver = {
.name = "qat_vfio_pci",
.id_table = qat_vf_vfio_pci_table,
.probe = qat_vf_vfio_pci_probe,
.remove = qat_vf_vfio_pci_remove,
.err_handler = &qat_vf_err_handlers,
.driver_managed_dma = true,
};
module_pci_driver(qat_vf_vfio_pci_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Xin Zeng <xin.zeng@intel.com>");
MODULE_DESCRIPTION("QAT VFIO PCI - VFIO PCI driver with live migration support for Intel(R) QAT device family");
MODULE_IMPORT_NS("CRYPTO_QAT");
+98
View File
@@ -0,0 +1,98 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* VFIO PCI mmap/mmap_fault tracepoints
*
* Copyright (C) 2018 IBM Corp. All rights reserved.
* Author: Alexey Kardashevskiy <aik@ozlabs.ru>
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM vfio_pci
#if !defined(_TRACE_VFIO_PCI_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_VFIO_PCI_H
#include <linux/tracepoint.h>
TRACE_EVENT(vfio_pci_nvgpu_mmap_fault,
TP_PROTO(struct pci_dev *pdev, unsigned long hpa, unsigned long ua,
vm_fault_t ret),
TP_ARGS(pdev, hpa, ua, ret),
TP_STRUCT__entry(
__field(const char *, name)
__field(unsigned long, hpa)
__field(unsigned long, ua)
__field(int, ret)
),
TP_fast_assign(
__entry->name = dev_name(&pdev->dev),
__entry->hpa = hpa;
__entry->ua = ua;
__entry->ret = ret;
),
TP_printk("%s: %lx -> %lx ret=%d", __entry->name, __entry->hpa,
__entry->ua, __entry->ret)
);
TRACE_EVENT(vfio_pci_nvgpu_mmap,
TP_PROTO(struct pci_dev *pdev, unsigned long hpa, unsigned long ua,
unsigned long size, int ret),
TP_ARGS(pdev, hpa, ua, size, ret),
TP_STRUCT__entry(
__field(const char *, name)
__field(unsigned long, hpa)
__field(unsigned long, ua)
__field(unsigned long, size)
__field(int, ret)
),
TP_fast_assign(
__entry->name = dev_name(&pdev->dev),
__entry->hpa = hpa;
__entry->ua = ua;
__entry->size = size;
__entry->ret = ret;
),
TP_printk("%s: %lx -> %lx size=%lx ret=%d", __entry->name, __entry->hpa,
__entry->ua, __entry->size, __entry->ret)
);
TRACE_EVENT(vfio_pci_npu2_mmap,
TP_PROTO(struct pci_dev *pdev, unsigned long hpa, unsigned long ua,
unsigned long size, int ret),
TP_ARGS(pdev, hpa, ua, size, ret),
TP_STRUCT__entry(
__field(const char *, name)
__field(unsigned long, hpa)
__field(unsigned long, ua)
__field(unsigned long, size)
__field(int, ret)
),
TP_fast_assign(
__entry->name = dev_name(&pdev->dev),
__entry->hpa = hpa;
__entry->ua = ua;
__entry->size = size;
__entry->ret = ret;
),
TP_printk("%s: %lx -> %lx size=%lx ret=%d", __entry->name, __entry->hpa,
__entry->ua, __entry->size, __entry->ret)
);
#endif /* _TRACE_VFIO_PCI_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../drivers/vfio/pci
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
/* This part must be outside protection */
#include <trace/define_trace.h>
+303
View File
@@ -0,0 +1,303 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved
*
* Copyright (C) 2012 Red Hat, Inc. All rights reserved.
* Author: Alex Williamson <alex.williamson@redhat.com>
*
* Derived from original vfio:
* Copyright 2010 Cisco Systems, Inc. All rights reserved.
* Author: Tom Lyon, pugs@cisco.com
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/eventfd.h>
#include <linux/file.h>
#include <linux/interrupt.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include "vfio_pci_priv.h"
#define DRIVER_AUTHOR "Alex Williamson <alex.williamson@redhat.com>"
#define DRIVER_DESC "VFIO PCI - User Level meta-driver"
static char ids[1024] __initdata;
module_param_string(ids, ids, sizeof(ids), 0);
MODULE_PARM_DESC(ids, "Initial PCI IDs to add to the vfio driver, format is \"vendor:device[:subvendor[:subdevice[:class[:class_mask]]]]\" and multiple comma separated entries can be specified");
static bool nointxmask;
module_param_named(nointxmask, nointxmask, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(nointxmask,
"Disable support for PCI 2.3 style INTx masking. If this resolves problems for specific devices, report lspci -vvvxxx to linux-pci@vger.kernel.org so the device can be fixed automatically via the broken_intx_masking flag.");
#ifdef CONFIG_VFIO_PCI_VGA
static bool disable_vga;
module_param(disable_vga, bool, S_IRUGO);
MODULE_PARM_DESC(disable_vga, "Disable VGA resource access through vfio-pci");
#endif
static bool disable_idle_d3;
module_param(disable_idle_d3, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(disable_idle_d3,
"Disable using the PCI D3 low power state for idle, unused devices");
static bool enable_sriov;
#ifdef CONFIG_PCI_IOV
module_param(enable_sriov, bool, 0644);
MODULE_PARM_DESC(enable_sriov, "Enable support for SR-IOV configuration. Enabling SR-IOV on a PF typically requires support of the userspace PF driver, enabling VFs without such support may result in non-functional VFs or PF.");
#endif
static bool disable_denylist;
module_param(disable_denylist, bool, 0444);
MODULE_PARM_DESC(disable_denylist, "Disable use of device denylist. Disabling the denylist allows binding to devices with known errata that may lead to exploitable stability or security issues when accessed by untrusted users.");
static bool vfio_pci_dev_in_denylist(struct pci_dev *pdev)
{
switch (pdev->vendor) {
case PCI_VENDOR_ID_INTEL:
switch (pdev->device) {
case PCI_DEVICE_ID_INTEL_QAT_C3XXX:
case PCI_DEVICE_ID_INTEL_QAT_C3XXX_VF:
case PCI_DEVICE_ID_INTEL_QAT_C62X:
case PCI_DEVICE_ID_INTEL_QAT_C62X_VF:
case PCI_DEVICE_ID_INTEL_QAT_DH895XCC:
case PCI_DEVICE_ID_INTEL_QAT_DH895XCC_VF:
case PCI_DEVICE_ID_INTEL_DSA_SPR0:
case PCI_DEVICE_ID_INTEL_IAX_SPR0:
return true;
default:
return false;
}
}
return false;
}
static bool vfio_pci_is_denylisted(struct pci_dev *pdev)
{
if (!vfio_pci_dev_in_denylist(pdev))
return false;
if (disable_denylist) {
pci_warn(pdev,
"device denylist disabled - allowing device %04x:%04x.\n",
pdev->vendor, pdev->device);
return false;
}
pci_warn(pdev, "%04x:%04x exists in vfio-pci device denylist, driver probing disallowed.\n",
pdev->vendor, pdev->device);
return true;
}
static int vfio_pci_open_device(struct vfio_device *core_vdev)
{
struct vfio_pci_core_device *vdev =
container_of(core_vdev, struct vfio_pci_core_device, vdev);
struct pci_dev *pdev = vdev->pdev;
int ret;
ret = vfio_pci_core_enable(vdev);
if (ret)
return ret;
if (vfio_pci_is_intel_display(pdev)) {
ret = vfio_pci_igd_init(vdev);
if (ret && ret != -ENODEV) {
pci_warn(pdev, "Failed to setup Intel IGD regions\n");
vfio_pci_core_disable(vdev);
return ret;
}
}
vfio_pci_core_finish_enable(vdev);
return 0;
}
static int vfio_pci_init_dev(struct vfio_device *core_vdev)
{
struct vfio_pci_core_device *vdev =
container_of(core_vdev, struct vfio_pci_core_device, vdev);
/*
* These behaviors originated in vfio-pci and moved into
* vfio-pci-core when the driver was split; vfio-pci remains the
* only driver that toggles them. Latch our module parameters per
* device at init time so that later parameter changes do not
* affect already-initialized devices.
*/
vdev->nointxmask = nointxmask;
vdev->disable_idle_d3 = disable_idle_d3;
#ifdef CONFIG_VFIO_PCI_VGA
vdev->disable_vga = disable_vga;
#endif
return vfio_pci_core_init_dev(core_vdev);
}
static const struct vfio_device_ops vfio_pci_ops = {
.name = "vfio-pci",
.init = vfio_pci_init_dev,
.release = vfio_pci_core_release_dev,
.open_device = vfio_pci_open_device,
.close_device = vfio_pci_core_close_device,
.ioctl = vfio_pci_core_ioctl,
.get_region_info_caps = vfio_pci_ioctl_get_region_info,
.device_feature = vfio_pci_core_ioctl_feature,
.read = vfio_pci_core_read,
.write = vfio_pci_core_write,
.mmap = vfio_pci_core_mmap,
.request = vfio_pci_core_request,
.match = vfio_pci_core_match,
.match_token_uuid = vfio_pci_core_match_token_uuid,
.bind_iommufd = vfio_iommufd_physical_bind,
.unbind_iommufd = vfio_iommufd_physical_unbind,
.attach_ioas = vfio_iommufd_physical_attach_ioas,
.detach_ioas = vfio_iommufd_physical_detach_ioas,
.pasid_attach_ioas = vfio_iommufd_physical_pasid_attach_ioas,
.pasid_detach_ioas = vfio_iommufd_physical_pasid_detach_ioas,
};
static const struct vfio_pci_device_ops vfio_pci_dev_ops = {
.get_dmabuf_phys = vfio_pci_core_get_dmabuf_phys,
};
static int vfio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct vfio_pci_core_device *vdev;
int ret;
if (vfio_pci_is_denylisted(pdev))
return -EINVAL;
vdev = vfio_alloc_device(vfio_pci_core_device, vdev, &pdev->dev,
&vfio_pci_ops);
if (IS_ERR(vdev))
return PTR_ERR(vdev);
dev_set_drvdata(&pdev->dev, vdev);
vdev->pci_ops = &vfio_pci_dev_ops;
ret = vfio_pci_core_register_device(vdev);
if (ret)
goto out_put_vdev;
return 0;
out_put_vdev:
vfio_put_device(&vdev->vdev);
return ret;
}
static void vfio_pci_remove(struct pci_dev *pdev)
{
struct vfio_pci_core_device *vdev = dev_get_drvdata(&pdev->dev);
vfio_pci_core_unregister_device(vdev);
vfio_put_device(&vdev->vdev);
}
static int vfio_pci_sriov_configure(struct pci_dev *pdev, int nr_virtfn)
{
struct vfio_pci_core_device *vdev = dev_get_drvdata(&pdev->dev);
if (!enable_sriov)
return -ENOENT;
return vfio_pci_core_sriov_configure(vdev, nr_virtfn);
}
static const struct pci_device_id vfio_pci_table[] = {
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_ANY_ID, PCI_ANY_ID) }, /* match all by default */
{}
};
MODULE_DEVICE_TABLE(pci, vfio_pci_table);
static struct pci_driver vfio_pci_driver = {
.name = "vfio-pci",
.id_table = vfio_pci_table,
.probe = vfio_pci_probe,
.remove = vfio_pci_remove,
.sriov_configure = vfio_pci_sriov_configure,
.err_handler = &vfio_pci_core_err_handlers,
.driver_managed_dma = true,
};
static void __init vfio_pci_fill_ids(void)
{
char *p, *id;
int rc;
/* no ids passed actually */
if (ids[0] == '\0')
return;
/* add ids specified in the module parameter */
p = ids;
while ((id = strsep(&p, ","))) {
unsigned int vendor, device, subvendor = PCI_ANY_ID,
subdevice = PCI_ANY_ID, class = 0, class_mask = 0;
int fields;
if (!strlen(id))
continue;
fields = sscanf(id, "%x:%x:%x:%x:%x:%x",
&vendor, &device, &subvendor, &subdevice,
&class, &class_mask);
if (fields < 2) {
pr_warn("invalid id string \"%s\"\n", id);
continue;
}
rc = pci_add_dynid(&vfio_pci_driver, vendor, device,
subvendor, subdevice, class, class_mask, 0);
if (rc)
pr_warn("failed to add dynamic id [%04x:%04x[%04x:%04x]] class %#08x/%08x (%d)\n",
vendor, device, subvendor, subdevice,
class, class_mask, rc);
else
pr_info("add [%04x:%04x[%04x:%04x]] class %#08x/%08x\n",
vendor, device, subvendor, subdevice,
class, class_mask);
}
}
static int __init vfio_pci_init(void)
{
int ret;
/* Register and scan for devices */
ret = pci_register_driver(&vfio_pci_driver);
if (ret)
return ret;
vfio_pci_fill_ids();
if (disable_denylist)
pr_warn("device denylist disabled.\n");
return 0;
}
module_init(vfio_pci_init);
static void __exit vfio_pci_cleanup(void)
{
pci_unregister_driver(&vfio_pci_driver);
}
module_exit(vfio_pci_cleanup);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,406 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES.
*/
#include <linux/dma-buf-mapping.h>
#include <linux/pci-p2pdma.h>
#include <linux/dma-resv.h>
#include "vfio_pci_priv.h"
MODULE_IMPORT_NS("DMA_BUF");
struct vfio_pci_dma_buf {
struct dma_buf *dmabuf;
struct vfio_pci_core_device *vdev;
struct list_head dmabufs_elm;
size_t size;
struct phys_vec *phys_vec;
struct p2pdma_provider *provider;
u32 nr_ranges;
struct kref kref;
struct completion comp;
u8 revoked : 1;
};
static int vfio_pci_dma_buf_attach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attachment)
{
struct vfio_pci_dma_buf *priv = dmabuf->priv;
if (!attachment->peer2peer)
return -EOPNOTSUPP;
if (priv->revoked)
return -ENODEV;
if (!dma_buf_attach_revocable(attachment))
return -EOPNOTSUPP;
return 0;
}
static void vfio_pci_dma_buf_done(struct kref *kref)
{
struct vfio_pci_dma_buf *priv =
container_of(kref, struct vfio_pci_dma_buf, kref);
complete(&priv->comp);
}
static struct sg_table *
vfio_pci_dma_buf_map(struct dma_buf_attachment *attachment,
enum dma_data_direction dir)
{
struct vfio_pci_dma_buf *priv = attachment->dmabuf->priv;
struct sg_table *ret;
dma_resv_assert_held(priv->dmabuf->resv);
if (priv->revoked)
return ERR_PTR(-ENODEV);
ret = dma_buf_phys_vec_to_sgt(attachment, priv->provider,
priv->phys_vec, priv->nr_ranges,
priv->size, dir);
if (IS_ERR(ret))
return ret;
kref_get(&priv->kref);
return ret;
}
static void vfio_pci_dma_buf_unmap(struct dma_buf_attachment *attachment,
struct sg_table *sgt,
enum dma_data_direction dir)
{
struct vfio_pci_dma_buf *priv = attachment->dmabuf->priv;
dma_resv_assert_held(priv->dmabuf->resv);
dma_buf_free_sgt(attachment, sgt, dir);
kref_put(&priv->kref, vfio_pci_dma_buf_done);
}
static void vfio_pci_dma_buf_release(struct dma_buf *dmabuf)
{
struct vfio_pci_dma_buf *priv = dmabuf->priv;
/*
* Either this or vfio_pci_dma_buf_cleanup() will remove from the list.
* The refcount prevents both.
*/
if (priv->vdev) {
down_write(&priv->vdev->memory_lock);
list_del_init(&priv->dmabufs_elm);
up_write(&priv->vdev->memory_lock);
vfio_device_put_registration(&priv->vdev->vdev);
}
kfree(priv->phys_vec);
kfree(priv);
}
static const struct dma_buf_ops vfio_pci_dmabuf_ops = {
.attach = vfio_pci_dma_buf_attach,
.map_dma_buf = vfio_pci_dma_buf_map,
.unmap_dma_buf = vfio_pci_dma_buf_unmap,
.release = vfio_pci_dma_buf_release,
};
/*
* This is a temporary "private interconnect" between VFIO DMABUF and iommufd.
* It allows the two co-operating drivers to exchange the physical address of
* the BAR. This is to be replaced with a formal DMABUF system for negotiated
* interconnect types.
*
* If this function succeeds the following are true:
* - There is one physical range and it is pointing to MMIO
* - When move_notify is called it means revoke, not move, vfio_dma_buf_map
* will fail if it is currently revoked
*/
int vfio_pci_dma_buf_iommufd_map(struct dma_buf_attachment *attachment,
struct phys_vec *phys)
{
struct vfio_pci_dma_buf *priv;
dma_resv_assert_held(attachment->dmabuf->resv);
if (attachment->dmabuf->ops != &vfio_pci_dmabuf_ops)
return -EOPNOTSUPP;
priv = attachment->dmabuf->priv;
if (priv->revoked)
return -ENODEV;
/* More than one range to iommufd will require proper DMABUF support */
if (priv->nr_ranges != 1)
return -EOPNOTSUPP;
*phys = priv->phys_vec[0];
return 0;
}
EXPORT_SYMBOL_FOR_MODULES(vfio_pci_dma_buf_iommufd_map, "iommufd");
int vfio_pci_core_fill_phys_vec(struct phys_vec *phys_vec,
struct vfio_region_dma_range *dma_ranges,
size_t nr_ranges, phys_addr_t start,
phys_addr_t len)
{
phys_addr_t max_addr;
unsigned int i;
max_addr = start + len;
for (i = 0; i < nr_ranges; i++) {
phys_addr_t end;
if (!dma_ranges[i].length)
return -EINVAL;
if (check_add_overflow(start, dma_ranges[i].offset,
&phys_vec[i].paddr) ||
check_add_overflow(phys_vec[i].paddr,
dma_ranges[i].length, &end))
return -EOVERFLOW;
if (end > max_addr)
return -EINVAL;
phys_vec[i].len = dma_ranges[i].length;
}
return 0;
}
EXPORT_SYMBOL_GPL(vfio_pci_core_fill_phys_vec);
int vfio_pci_core_get_dmabuf_phys(struct vfio_pci_core_device *vdev,
struct p2pdma_provider **provider,
unsigned int region_index,
struct phys_vec *phys_vec,
struct vfio_region_dma_range *dma_ranges,
size_t nr_ranges)
{
struct pci_dev *pdev = vdev->pdev;
*provider = pcim_p2pdma_provider(pdev, region_index);
if (!*provider)
return -EINVAL;
return vfio_pci_core_fill_phys_vec(
phys_vec, dma_ranges, nr_ranges,
pci_resource_start(pdev, region_index),
pci_resource_len(pdev, region_index));
}
EXPORT_SYMBOL_GPL(vfio_pci_core_get_dmabuf_phys);
static int validate_dmabuf_input(struct vfio_device_feature_dma_buf *dma_buf,
struct vfio_region_dma_range *dma_ranges,
size_t *lengthp)
{
size_t length = 0;
u32 i;
for (i = 0; i < dma_buf->nr_ranges; i++) {
u64 offset = dma_ranges[i].offset;
u64 len = dma_ranges[i].length;
if (!len || !PAGE_ALIGNED(offset) || !PAGE_ALIGNED(len))
return -EINVAL;
if (check_add_overflow(length, len, &length))
return -EINVAL;
}
/*
* dma_iova_try_alloc() will WARN on if userspace proposes a size that
* is too big, eg with lots of ranges.
*/
if ((u64)(length) & DMA_IOVA_USE_SWIOTLB)
return -EINVAL;
*lengthp = length;
return 0;
}
int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags,
struct vfio_device_feature_dma_buf __user *arg,
size_t argsz)
{
struct vfio_device_feature_dma_buf get_dma_buf = {};
struct vfio_region_dma_range *dma_ranges;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
struct vfio_pci_dma_buf *priv;
size_t length;
int ret;
if (!vdev->pci_ops || !vdev->pci_ops->get_dmabuf_phys)
return -EOPNOTSUPP;
ret = vfio_check_feature(flags, argsz, VFIO_DEVICE_FEATURE_GET,
sizeof(get_dma_buf));
if (ret != 1)
return ret;
if (copy_from_user(&get_dma_buf, arg, sizeof(get_dma_buf)))
return -EFAULT;
if (!get_dma_buf.nr_ranges || get_dma_buf.flags)
return -EINVAL;
/*
* For PCI the region_index is the BAR number like everything
* else. Check that PCI resources have been claimed for it.
*/
if (get_dma_buf.region_index >= VFIO_PCI_ROM_REGION_INDEX ||
IS_ERR(vfio_pci_core_get_iomap(vdev, get_dma_buf.region_index)))
return -ENODEV;
dma_ranges = memdup_array_user(&arg->dma_ranges, get_dma_buf.nr_ranges,
sizeof(*dma_ranges));
if (IS_ERR(dma_ranges))
return PTR_ERR(dma_ranges);
ret = validate_dmabuf_input(&get_dma_buf, dma_ranges, &length);
if (ret)
goto err_free_ranges;
priv = kzalloc_obj(*priv);
if (!priv) {
ret = -ENOMEM;
goto err_free_ranges;
}
priv->phys_vec = kzalloc_objs(*priv->phys_vec, get_dma_buf.nr_ranges);
if (!priv->phys_vec) {
ret = -ENOMEM;
goto err_free_priv;
}
priv->vdev = vdev;
priv->nr_ranges = get_dma_buf.nr_ranges;
priv->size = length;
ret = vdev->pci_ops->get_dmabuf_phys(vdev, &priv->provider,
get_dma_buf.region_index,
priv->phys_vec, dma_ranges,
priv->nr_ranges);
if (ret)
goto err_free_phys;
kfree(dma_ranges);
dma_ranges = NULL;
if (!vfio_device_try_get_registration(&vdev->vdev)) {
ret = -ENODEV;
goto err_free_phys;
}
exp_info.ops = &vfio_pci_dmabuf_ops;
exp_info.size = priv->size;
exp_info.flags = get_dma_buf.open_flags;
exp_info.priv = priv;
priv->dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(priv->dmabuf)) {
ret = PTR_ERR(priv->dmabuf);
goto err_dev_put;
}
kref_init(&priv->kref);
init_completion(&priv->comp);
/* dma_buf_put() now frees priv */
INIT_LIST_HEAD(&priv->dmabufs_elm);
down_write(&vdev->memory_lock);
dma_resv_lock(priv->dmabuf->resv, NULL);
priv->revoked = !__vfio_pci_memory_enabled(vdev);
list_add_tail(&priv->dmabufs_elm, &vdev->dmabufs);
dma_resv_unlock(priv->dmabuf->resv);
up_write(&vdev->memory_lock);
/*
* dma_buf_fd() consumes the reference, when the file closes the dmabuf
* will be released.
*/
ret = dma_buf_fd(priv->dmabuf, get_dma_buf.open_flags);
if (ret < 0)
dma_buf_put(priv->dmabuf);
return ret;
err_dev_put:
vfio_device_put_registration(&vdev->vdev);
err_free_phys:
kfree(priv->phys_vec);
err_free_priv:
kfree(priv);
err_free_ranges:
kfree(dma_ranges);
return ret;
}
void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked)
{
struct vfio_pci_dma_buf *priv;
struct vfio_pci_dma_buf *tmp;
lockdep_assert_held_write(&vdev->memory_lock);
list_for_each_entry_safe(priv, tmp, &vdev->dmabufs, dmabufs_elm) {
if (!get_file_active(&priv->dmabuf->file))
continue;
if (priv->revoked != revoked) {
dma_resv_lock(priv->dmabuf->resv, NULL);
if (revoked)
priv->revoked = true;
dma_buf_invalidate_mappings(priv->dmabuf);
dma_resv_wait_timeout(priv->dmabuf->resv,
DMA_RESV_USAGE_BOOKKEEP, false,
MAX_SCHEDULE_TIMEOUT);
dma_resv_unlock(priv->dmabuf->resv);
if (revoked) {
kref_put(&priv->kref, vfio_pci_dma_buf_done);
wait_for_completion(&priv->comp);
/*
* Re-arm the registered kref reference and the
* completion so the post-revoke state matches the
* post-creation state. An un-revoke followed by a
* new mapping needs the kref to be non-zero before
* kref_get(), and vfio_pci_dma_buf_cleanup()
* delegates its drain back through this revoke
* path on a possibly-already-revoked dma-buf.
*/
kref_init(&priv->kref);
reinit_completion(&priv->comp);
} else {
dma_resv_lock(priv->dmabuf->resv, NULL);
priv->revoked = false;
dma_resv_unlock(priv->dmabuf->resv);
}
}
fput(priv->dmabuf->file);
}
}
void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev)
{
struct vfio_pci_dma_buf *priv;
struct vfio_pci_dma_buf *tmp;
down_write(&vdev->memory_lock);
/*
* Drain any active mappings via the revoke path. The move is
* idempotent for dma-bufs already in the revoked state and
* leaves every priv with the kref re-armed and the completion
* ready, so cleanup itself does not need to participate in kref
* bookkeeping.
*/
vfio_pci_dma_buf_move(vdev, true);
list_for_each_entry_safe(priv, tmp, &vdev->dmabufs, dmabufs_elm) {
if (!get_file_active(&priv->dmabuf->file))
continue;
list_del_init(&priv->dmabufs_elm);
priv->vdev = NULL;
vfio_device_put_registration(&vdev->vdev);
fput(priv->dmabuf->file);
}
up_write(&vdev->memory_lock);
}
+456
View File
@@ -0,0 +1,456 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* VFIO PCI Intel Graphics support
*
* Copyright (C) 2016 Red Hat, Inc. All rights reserved.
* Author: Alex Williamson <alex.williamson@redhat.com>
*
* Register a device specific region through which to provide read-only
* access to the Intel IGD opregion. The register defining the opregion
* address is also virtualized to prevent user modification.
*/
#include <linux/io.h>
#include <linux/pci.h>
#include <linux/uaccess.h>
#include <linux/vfio.h>
#include "vfio_pci_priv.h"
#define OPREGION_SIGNATURE "IntelGraphicsMem"
#define OPREGION_SIZE (8 * 1024)
#define OPREGION_PCI_ADDR 0xfc
#define OPREGION_RVDA 0x3ba
#define OPREGION_RVDS 0x3c2
#define OPREGION_VERSION 0x16
struct igd_opregion_vbt {
void *opregion;
void *vbt_ex;
};
/**
* igd_opregion_shift_copy() - Copy OpRegion to user buffer and shift position.
* @dst: User buffer ptr to copy to.
* @off: Offset to user buffer ptr. Increased by bytes on return.
* @src: Source buffer to copy from.
* @pos: Increased by bytes on return.
* @remaining: Decreased by bytes on return.
* @bytes: Bytes to copy and adjust off, pos and remaining.
*
* Copy OpRegion to offset from specific source ptr and shift the offset.
*
* Return: 0 on success, -EFAULT otherwise.
*
*/
static inline unsigned long igd_opregion_shift_copy(char __user *dst,
loff_t *off,
void *src,
loff_t *pos,
size_t *remaining,
size_t bytes)
{
if (copy_to_user(dst + (*off), src, bytes))
return -EFAULT;
*off += bytes;
*pos += bytes;
*remaining -= bytes;
return 0;
}
static ssize_t vfio_pci_igd_rw(struct vfio_pci_core_device *vdev,
char __user *buf, size_t count, loff_t *ppos,
bool iswrite)
{
unsigned int i = VFIO_PCI_OFFSET_TO_INDEX(*ppos) - VFIO_PCI_NUM_REGIONS;
struct igd_opregion_vbt *opregionvbt = vdev->region[i].data;
loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK, off = 0;
size_t remaining;
if (pos >= vdev->region[i].size || iswrite)
return -EINVAL;
count = min_t(size_t, count, vdev->region[i].size - pos);
remaining = count;
/* Copy until OpRegion version */
if (remaining && pos < OPREGION_VERSION) {
size_t bytes = min_t(size_t, remaining, OPREGION_VERSION - pos);
if (igd_opregion_shift_copy(buf, &off,
opregionvbt->opregion + pos, &pos,
&remaining, bytes))
return -EFAULT;
}
/* Copy patched (if necessary) OpRegion version */
if (remaining && pos < OPREGION_VERSION + sizeof(__le16)) {
size_t bytes = min_t(size_t, remaining,
OPREGION_VERSION + sizeof(__le16) - pos);
__le16 version = *(__le16 *)(opregionvbt->opregion +
OPREGION_VERSION);
/* Patch to 2.1 if OpRegion 2.0 has extended VBT */
if (le16_to_cpu(version) == 0x0200 && opregionvbt->vbt_ex)
version = cpu_to_le16(0x0201);
if (igd_opregion_shift_copy(buf, &off,
(u8 *)&version +
(pos - OPREGION_VERSION),
&pos, &remaining, bytes))
return -EFAULT;
}
/* Copy until RVDA */
if (remaining && pos < OPREGION_RVDA) {
size_t bytes = min_t(size_t, remaining, OPREGION_RVDA - pos);
if (igd_opregion_shift_copy(buf, &off,
opregionvbt->opregion + pos, &pos,
&remaining, bytes))
return -EFAULT;
}
/* Copy modified (if necessary) RVDA */
if (remaining && pos < OPREGION_RVDA + sizeof(__le64)) {
size_t bytes = min_t(size_t, remaining,
OPREGION_RVDA + sizeof(__le64) - pos);
__le64 rvda = cpu_to_le64(opregionvbt->vbt_ex ?
OPREGION_SIZE : 0);
if (igd_opregion_shift_copy(buf, &off,
(u8 *)&rvda + (pos - OPREGION_RVDA),
&pos, &remaining, bytes))
return -EFAULT;
}
/* Copy the rest of OpRegion */
if (remaining && pos < OPREGION_SIZE) {
size_t bytes = min_t(size_t, remaining, OPREGION_SIZE - pos);
if (igd_opregion_shift_copy(buf, &off,
opregionvbt->opregion + pos, &pos,
&remaining, bytes))
return -EFAULT;
}
/* Copy extended VBT if exists */
if (remaining &&
copy_to_user(buf + off, opregionvbt->vbt_ex + (pos - OPREGION_SIZE),
remaining))
return -EFAULT;
*ppos += count;
return count;
}
static void vfio_pci_igd_release(struct vfio_pci_core_device *vdev,
struct vfio_pci_region *region)
{
struct igd_opregion_vbt *opregionvbt = region->data;
if (opregionvbt->vbt_ex)
memunmap(opregionvbt->vbt_ex);
memunmap(opregionvbt->opregion);
kfree(opregionvbt);
}
static const struct vfio_pci_regops vfio_pci_igd_regops = {
.rw = vfio_pci_igd_rw,
.release = vfio_pci_igd_release,
};
static int vfio_pci_igd_opregion_init(struct vfio_pci_core_device *vdev)
{
__le32 *dwordp = (__le32 *)(vdev->vconfig + OPREGION_PCI_ADDR);
u32 addr, size;
struct igd_opregion_vbt *opregionvbt;
int ret;
u16 version;
ret = pci_read_config_dword(vdev->pdev, OPREGION_PCI_ADDR, &addr);
if (ret)
return ret;
if (!addr || !(~addr))
return -ENODEV;
opregionvbt = kzalloc_obj(*opregionvbt, GFP_KERNEL_ACCOUNT);
if (!opregionvbt)
return -ENOMEM;
opregionvbt->opregion = memremap(addr, OPREGION_SIZE, MEMREMAP_WB);
if (!opregionvbt->opregion) {
kfree(opregionvbt);
return -ENOMEM;
}
if (memcmp(opregionvbt->opregion, OPREGION_SIGNATURE, 16)) {
memunmap(opregionvbt->opregion);
kfree(opregionvbt);
return -EINVAL;
}
size = le32_to_cpu(*(__le32 *)(opregionvbt->opregion + 16));
if (!size) {
memunmap(opregionvbt->opregion);
kfree(opregionvbt);
return -EINVAL;
}
size *= 1024; /* In KB */
/*
* OpRegion and VBT:
* When VBT data doesn't exceed 6KB, it's stored in Mailbox #4.
* When VBT data exceeds 6KB size, Mailbox #4 is no longer large enough
* to hold the VBT data, the Extended VBT region is introduced since
* OpRegion 2.0 to hold the VBT data. Since OpRegion 2.0, RVDA/RVDS are
* introduced to define the extended VBT data location and size.
* OpRegion 2.0: RVDA defines the absolute physical address of the
* extended VBT data, RVDS defines the VBT data size.
* OpRegion 2.1 and above: RVDA defines the relative address of the
* extended VBT data to OpRegion base, RVDS defines the VBT data size.
*
* Due to the RVDA definition diff in OpRegion VBT (also the only diff
* between 2.0 and 2.1), exposing OpRegion and VBT as a contiguous range
* for OpRegion 2.0 and above makes it possible to support the
* non-contiguous VBT through a single vfio region. From r/w ops view,
* only contiguous VBT after OpRegion with version 2.1+ is exposed,
* regardless the host OpRegion is 2.0 or non-contiguous 2.1+. The r/w
* ops will on-the-fly shift the actural offset into VBT so that data at
* correct position can be returned to the requester.
*/
version = le16_to_cpu(*(__le16 *)(opregionvbt->opregion +
OPREGION_VERSION));
if (version >= 0x0200) {
u64 rvda = le64_to_cpu(*(__le64 *)(opregionvbt->opregion +
OPREGION_RVDA));
u32 rvds = le32_to_cpu(*(__le32 *)(opregionvbt->opregion +
OPREGION_RVDS));
/* The extended VBT is valid only when RVDA/RVDS are non-zero */
if (rvda && rvds) {
size += rvds;
/*
* Extended VBT location by RVDA:
* Absolute physical addr for 2.0.
* Relative addr to OpRegion header for 2.1+.
*/
if (version == 0x0200)
addr = rvda;
else
addr += rvda;
opregionvbt->vbt_ex = memremap(addr, rvds, MEMREMAP_WB);
if (!opregionvbt->vbt_ex) {
memunmap(opregionvbt->opregion);
kfree(opregionvbt);
return -ENOMEM;
}
}
}
ret = vfio_pci_core_register_dev_region(vdev,
PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE,
VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION, &vfio_pci_igd_regops,
size, VFIO_REGION_INFO_FLAG_READ, opregionvbt);
if (ret) {
if (opregionvbt->vbt_ex)
memunmap(opregionvbt->vbt_ex);
memunmap(opregionvbt->opregion);
kfree(opregionvbt);
return ret;
}
/* Fill vconfig with the hw value and virtualize register */
*dwordp = cpu_to_le32(addr);
memset(vdev->pci_config_map + OPREGION_PCI_ADDR,
PCI_CAP_ID_INVALID_VIRT, 4);
return ret;
}
static ssize_t vfio_pci_igd_cfg_rw(struct vfio_pci_core_device *vdev,
char __user *buf, size_t count, loff_t *ppos,
bool iswrite)
{
unsigned int i = VFIO_PCI_OFFSET_TO_INDEX(*ppos) - VFIO_PCI_NUM_REGIONS;
struct pci_dev *pdev = vdev->region[i].data;
loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK;
size_t size;
int ret;
if (pos >= vdev->region[i].size || iswrite)
return -EINVAL;
size = count = min(count, (size_t)(vdev->region[i].size - pos));
if ((pos & 1) && size) {
u8 val;
ret = pci_user_read_config_byte(pdev, pos, &val);
if (ret)
return ret;
if (copy_to_user(buf + count - size, &val, 1))
return -EFAULT;
pos++;
size--;
}
if ((pos & 3) && size > 2) {
u16 val;
__le16 lval;
ret = pci_user_read_config_word(pdev, pos, &val);
if (ret)
return ret;
lval = cpu_to_le16(val);
if (copy_to_user(buf + count - size, &lval, 2))
return -EFAULT;
pos += 2;
size -= 2;
}
while (size > 3) {
u32 val;
__le32 lval;
ret = pci_user_read_config_dword(pdev, pos, &val);
if (ret)
return ret;
lval = cpu_to_le32(val);
if (copy_to_user(buf + count - size, &lval, 4))
return -EFAULT;
pos += 4;
size -= 4;
}
while (size >= 2) {
u16 val;
__le16 lval;
ret = pci_user_read_config_word(pdev, pos, &val);
if (ret)
return ret;
lval = cpu_to_le16(val);
if (copy_to_user(buf + count - size, &lval, 2))
return -EFAULT;
pos += 2;
size -= 2;
}
while (size) {
u8 val;
ret = pci_user_read_config_byte(pdev, pos, &val);
if (ret)
return ret;
if (copy_to_user(buf + count - size, &val, 1))
return -EFAULT;
pos++;
size--;
}
*ppos += count;
return count;
}
static void vfio_pci_igd_cfg_release(struct vfio_pci_core_device *vdev,
struct vfio_pci_region *region)
{
struct pci_dev *pdev = region->data;
pci_dev_put(pdev);
}
static const struct vfio_pci_regops vfio_pci_igd_cfg_regops = {
.rw = vfio_pci_igd_cfg_rw,
.release = vfio_pci_igd_cfg_release,
};
static int vfio_pci_igd_cfg_init(struct vfio_pci_core_device *vdev)
{
struct pci_dev *host_bridge, *lpc_bridge;
int ret;
host_bridge = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
if (!host_bridge)
return -ENODEV;
if (host_bridge->vendor != PCI_VENDOR_ID_INTEL ||
host_bridge->class != (PCI_CLASS_BRIDGE_HOST << 8)) {
pci_dev_put(host_bridge);
return -EINVAL;
}
ret = vfio_pci_core_register_dev_region(vdev,
PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE,
VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG,
&vfio_pci_igd_cfg_regops, host_bridge->cfg_size,
VFIO_REGION_INFO_FLAG_READ, host_bridge);
if (ret) {
pci_dev_put(host_bridge);
return ret;
}
lpc_bridge = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x1f, 0));
if (!lpc_bridge)
return -ENODEV;
if (lpc_bridge->vendor != PCI_VENDOR_ID_INTEL ||
lpc_bridge->class != (PCI_CLASS_BRIDGE_ISA << 8)) {
pci_dev_put(lpc_bridge);
return -EINVAL;
}
ret = vfio_pci_core_register_dev_region(vdev,
PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE,
VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG,
&vfio_pci_igd_cfg_regops, lpc_bridge->cfg_size,
VFIO_REGION_INFO_FLAG_READ, lpc_bridge);
if (ret) {
pci_dev_put(lpc_bridge);
return ret;
}
return 0;
}
bool vfio_pci_is_intel_display(struct pci_dev *pdev)
{
return (pdev->vendor == PCI_VENDOR_ID_INTEL) && pci_is_display(pdev);
}
int vfio_pci_igd_init(struct vfio_pci_core_device *vdev)
{
int ret;
ret = vfio_pci_igd_opregion_init(vdev);
if (ret)
return ret;
ret = vfio_pci_igd_cfg_init(vdev);
if (ret)
return ret;
return 0;
}
@@ -0,0 +1,880 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* VFIO PCI interrupt handling
*
* Copyright (C) 2012 Red Hat, Inc. All rights reserved.
* Author: Alex Williamson <alex.williamson@redhat.com>
*
* Derived from original vfio:
* Copyright 2010 Cisco Systems, Inc. All rights reserved.
* Author: Tom Lyon, pugs@cisco.com
*/
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/eventfd.h>
#include <linux/msi.h>
#include <linux/pci.h>
#include <linux/file.h>
#include <linux/vfio.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include "vfio_pci_priv.h"
struct vfio_pci_irq_ctx {
struct vfio_pci_core_device *vdev;
struct eventfd_ctx *trigger;
struct virqfd *unmask;
struct virqfd *mask;
char *name;
bool masked;
struct irq_bypass_producer producer;
};
static bool irq_is(struct vfio_pci_core_device *vdev, int type)
{
return vdev->irq_type == type;
}
static bool is_intx(struct vfio_pci_core_device *vdev)
{
return vdev->irq_type == VFIO_PCI_INTX_IRQ_INDEX;
}
static bool is_irq_none(struct vfio_pci_core_device *vdev)
{
return !(vdev->irq_type == VFIO_PCI_INTX_IRQ_INDEX ||
vdev->irq_type == VFIO_PCI_MSI_IRQ_INDEX ||
vdev->irq_type == VFIO_PCI_MSIX_IRQ_INDEX);
}
static
struct vfio_pci_irq_ctx *vfio_irq_ctx_get(struct vfio_pci_core_device *vdev,
unsigned long index)
{
return xa_load(&vdev->ctx, index);
}
static void vfio_irq_ctx_free(struct vfio_pci_core_device *vdev,
struct vfio_pci_irq_ctx *ctx, unsigned long index)
{
xa_erase(&vdev->ctx, index);
kfree(ctx);
}
static struct vfio_pci_irq_ctx *
vfio_irq_ctx_alloc(struct vfio_pci_core_device *vdev, unsigned long index)
{
struct vfio_pci_irq_ctx *ctx;
int ret;
ctx = kzalloc_obj(*ctx, GFP_KERNEL_ACCOUNT);
if (!ctx)
return NULL;
ret = xa_insert(&vdev->ctx, index, ctx, GFP_KERNEL_ACCOUNT);
if (ret) {
kfree(ctx);
return NULL;
}
return ctx;
}
/*
* INTx
*/
static void vfio_send_intx_eventfd(void *opaque, void *data)
{
struct vfio_pci_core_device *vdev = opaque;
if (likely(is_intx(vdev) && !vdev->virq_disabled)) {
struct vfio_pci_irq_ctx *ctx = data;
struct eventfd_ctx *trigger = READ_ONCE(ctx->trigger);
if (likely(trigger))
eventfd_signal(trigger);
}
}
/* Returns true if the INTx vfio_pci_irq_ctx.masked value is changed. */
static bool __vfio_pci_intx_mask(struct vfio_pci_core_device *vdev)
{
struct pci_dev *pdev = vdev->pdev;
struct vfio_pci_irq_ctx *ctx;
unsigned long flags;
bool masked_changed = false;
lockdep_assert_held(&vdev->igate);
spin_lock_irqsave(&vdev->irqlock, flags);
/*
* Masking can come from interrupt, ioctl, or config space
* via INTx disable. The latter means this can get called
* even when not using intx delivery. In this case, just
* try to have the physical bit follow the virtual bit.
*/
if (unlikely(!is_intx(vdev))) {
if (vdev->pci_2_3)
pci_intx(pdev, 0);
goto out_unlock;
}
ctx = vfio_irq_ctx_get(vdev, 0);
if (WARN_ON_ONCE(!ctx))
goto out_unlock;
if (!ctx->masked) {
/*
* Can't use check_and_mask here because we always want to
* mask, not just when something is pending.
*/
if (vdev->pci_2_3)
pci_intx(pdev, 0);
else
disable_irq_nosync(pdev->irq);
ctx->masked = true;
masked_changed = true;
}
out_unlock:
spin_unlock_irqrestore(&vdev->irqlock, flags);
return masked_changed;
}
bool vfio_pci_intx_mask(struct vfio_pci_core_device *vdev)
{
bool mask_changed;
mutex_lock(&vdev->igate);
mask_changed = __vfio_pci_intx_mask(vdev);
mutex_unlock(&vdev->igate);
return mask_changed;
}
/*
* If this is triggered by an eventfd, we can't call eventfd_signal
* or else we'll deadlock on the eventfd wait queue. Return >0 when
* a signal is necessary, which can then be handled via a work queue
* or directly depending on the caller.
*/
static int vfio_pci_intx_unmask_handler(void *opaque, void *data)
{
struct vfio_pci_core_device *vdev = opaque;
struct pci_dev *pdev = vdev->pdev;
struct vfio_pci_irq_ctx *ctx = data;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&vdev->irqlock, flags);
/*
* Unmasking comes from ioctl or config, so again, have the
* physical bit follow the virtual even when not using INTx.
*/
if (unlikely(!is_intx(vdev))) {
if (vdev->pci_2_3)
pci_intx(pdev, 1);
goto out_unlock;
}
if (ctx->masked && !vdev->virq_disabled) {
/*
* A pending interrupt here would immediately trigger,
* but we can avoid that overhead by just re-sending
* the interrupt to the user.
*/
if (vdev->pci_2_3) {
if (!pci_check_and_unmask_intx(pdev))
ret = 1;
} else
enable_irq(pdev->irq);
ctx->masked = (ret > 0);
}
out_unlock:
spin_unlock_irqrestore(&vdev->irqlock, flags);
return ret;
}
static void __vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev)
{
struct vfio_pci_irq_ctx *ctx = vfio_irq_ctx_get(vdev, 0);
lockdep_assert_held(&vdev->igate);
if (vfio_pci_intx_unmask_handler(vdev, ctx) > 0)
vfio_send_intx_eventfd(vdev, ctx);
}
void vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev)
{
mutex_lock(&vdev->igate);
__vfio_pci_intx_unmask(vdev);
mutex_unlock(&vdev->igate);
}
static irqreturn_t vfio_intx_handler(int irq, void *dev_id)
{
struct vfio_pci_irq_ctx *ctx = dev_id;
struct vfio_pci_core_device *vdev = ctx->vdev;
unsigned long flags;
int ret = IRQ_NONE;
spin_lock_irqsave(&vdev->irqlock, flags);
if (!vdev->pci_2_3) {
disable_irq_nosync(vdev->pdev->irq);
ctx->masked = true;
ret = IRQ_HANDLED;
} else if (!ctx->masked && /* may be shared */
pci_check_and_mask_intx(vdev->pdev)) {
ctx->masked = true;
ret = IRQ_HANDLED;
}
spin_unlock_irqrestore(&vdev->irqlock, flags);
if (ret == IRQ_HANDLED)
vfio_send_intx_eventfd(vdev, ctx);
return ret;
}
static int vfio_intx_enable(struct vfio_pci_core_device *vdev,
struct eventfd_ctx *trigger)
{
struct pci_dev *pdev = vdev->pdev;
struct vfio_pci_irq_ctx *ctx;
unsigned long irqflags;
char *name;
int ret;
if (!is_irq_none(vdev))
return -EINVAL;
if (!pdev->irq || pdev->irq == IRQ_NOTCONNECTED)
return -ENODEV;
name = kasprintf(GFP_KERNEL_ACCOUNT, "vfio-intx(%s)", pci_name(pdev));
if (!name)
return -ENOMEM;
ctx = vfio_irq_ctx_alloc(vdev, 0);
if (!ctx) {
kfree(name);
return -ENOMEM;
}
ctx->name = name;
ctx->trigger = trigger;
ctx->vdev = vdev;
/*
* Fill the initial masked state based on virq_disabled. After
* enable, changing the DisINTx bit in vconfig directly changes INTx
* masking. igate prevents races during setup, once running masked
* is protected via irqlock.
*
* Devices supporting DisINTx also reflect the current mask state in
* the physical DisINTx bit, which is not affected during IRQ setup.
*
* Devices without DisINTx support require an exclusive interrupt.
* IRQ masking is performed at the IRQ chip. Again, igate protects
* against races during setup and IRQ handlers and irqfds are not
* yet active, therefore masked is stable and can be used to
* conditionally auto-enable the IRQ.
*
* irq_type must be stable while the IRQ handler is registered,
* therefore it must be set before request_irq().
*/
ctx->masked = vdev->virq_disabled;
if (vdev->pci_2_3) {
pci_intx(pdev, !ctx->masked);
irqflags = IRQF_SHARED;
} else {
irqflags = ctx->masked ? IRQF_NO_AUTOEN : 0;
}
vdev->irq_type = VFIO_PCI_INTX_IRQ_INDEX;
if (!vdev->pci_2_3)
irq_set_status_flags(pdev->irq, IRQ_DISABLE_UNLAZY);
ret = request_irq(pdev->irq, vfio_intx_handler,
irqflags, ctx->name, ctx);
if (ret) {
if (!vdev->pci_2_3)
irq_clear_status_flags(pdev->irq, IRQ_DISABLE_UNLAZY);
vdev->irq_type = VFIO_PCI_NUM_IRQS;
kfree(name);
vfio_irq_ctx_free(vdev, ctx, 0);
return ret;
}
return 0;
}
static int vfio_intx_set_signal(struct vfio_pci_core_device *vdev,
struct eventfd_ctx *trigger)
{
struct pci_dev *pdev = vdev->pdev;
struct vfio_pci_irq_ctx *ctx;
struct eventfd_ctx *old;
ctx = vfio_irq_ctx_get(vdev, 0);
if (WARN_ON_ONCE(!ctx))
return -EINVAL;
old = ctx->trigger;
WRITE_ONCE(ctx->trigger, trigger);
/* Releasing an old ctx requires synchronizing in-flight users */
if (old) {
synchronize_irq(pdev->irq);
vfio_virqfd_flush_thread(&ctx->unmask);
eventfd_ctx_put(old);
}
return 0;
}
static void vfio_intx_disable(struct vfio_pci_core_device *vdev)
{
struct pci_dev *pdev = vdev->pdev;
struct vfio_pci_irq_ctx *ctx;
ctx = vfio_irq_ctx_get(vdev, 0);
WARN_ON_ONCE(!ctx);
if (ctx) {
vfio_virqfd_disable(&ctx->unmask);
vfio_virqfd_disable(&ctx->mask);
free_irq(pdev->irq, ctx);
if (!vdev->pci_2_3)
irq_clear_status_flags(pdev->irq, IRQ_DISABLE_UNLAZY);
if (ctx->trigger)
eventfd_ctx_put(ctx->trigger);
kfree(ctx->name);
vfio_irq_ctx_free(vdev, ctx, 0);
}
vdev->irq_type = VFIO_PCI_NUM_IRQS;
}
/*
* MSI/MSI-X
*/
static irqreturn_t vfio_msihandler(int irq, void *arg)
{
struct eventfd_ctx *trigger = arg;
eventfd_signal(trigger);
return IRQ_HANDLED;
}
static int vfio_msi_enable(struct vfio_pci_core_device *vdev, int nvec, bool msix)
{
struct pci_dev *pdev = vdev->pdev;
unsigned int flag = msix ? PCI_IRQ_MSIX : PCI_IRQ_MSI;
int ret;
u16 cmd;
if (!is_irq_none(vdev))
return -EINVAL;
/* return the number of supported vectors if we can't get all: */
cmd = vfio_pci_memory_lock_and_enable(vdev);
ret = pci_alloc_irq_vectors(pdev, 1, nvec, flag);
if (ret < nvec) {
if (ret > 0)
pci_free_irq_vectors(pdev);
vfio_pci_memory_unlock_and_restore(vdev, cmd);
return ret;
}
vfio_pci_memory_unlock_and_restore(vdev, cmd);
vdev->irq_type = msix ? VFIO_PCI_MSIX_IRQ_INDEX :
VFIO_PCI_MSI_IRQ_INDEX;
if (!msix) {
/*
* Compute the virtual hardware field for max msi vectors -
* it is the log base 2 of the number of vectors.
*/
vdev->msi_qmax = fls(nvec * 2 - 1) - 1;
}
return 0;
}
/*
* vfio_msi_alloc_irq() returns the Linux IRQ number of an MSI or MSI-X device
* interrupt vector. If a Linux IRQ number is not available then a new
* interrupt is allocated if dynamic MSI-X is supported.
*
* Where is vfio_msi_free_irq()? Allocated interrupts are maintained,
* essentially forming a cache that subsequent allocations can draw from.
* Interrupts are freed using pci_free_irq_vectors() when MSI/MSI-X is
* disabled.
*/
static int vfio_msi_alloc_irq(struct vfio_pci_core_device *vdev,
unsigned int vector, bool msix)
{
struct pci_dev *pdev = vdev->pdev;
struct msi_map map;
int irq;
u16 cmd;
irq = pci_irq_vector(pdev, vector);
if (WARN_ON_ONCE(irq == 0))
return -EINVAL;
if (irq > 0 || !msix || !vdev->has_dyn_msix)
return irq;
cmd = vfio_pci_memory_lock_and_enable(vdev);
map = pci_msix_alloc_irq_at(pdev, vector, NULL);
vfio_pci_memory_unlock_and_restore(vdev, cmd);
return map.index < 0 ? map.index : map.virq;
}
static int vfio_msi_set_vector_signal(struct vfio_pci_core_device *vdev,
unsigned int vector, int fd, bool msix)
{
struct pci_dev *pdev = vdev->pdev;
struct vfio_pci_irq_ctx *ctx;
struct eventfd_ctx *trigger;
int irq = -EINVAL, ret;
u16 cmd;
ctx = vfio_irq_ctx_get(vdev, vector);
if (ctx) {
irq_bypass_unregister_producer(&ctx->producer);
irq = pci_irq_vector(pdev, vector);
cmd = vfio_pci_memory_lock_and_enable(vdev);
free_irq(irq, ctx->trigger);
vfio_pci_memory_unlock_and_restore(vdev, cmd);
/* Interrupt stays allocated, will be freed at MSI-X disable. */
kfree(ctx->name);
eventfd_ctx_put(ctx->trigger);
vfio_irq_ctx_free(vdev, ctx, vector);
}
if (fd < 0)
return 0;
if (irq == -EINVAL) {
/* Interrupt stays allocated, will be freed at MSI-X disable. */
irq = vfio_msi_alloc_irq(vdev, vector, msix);
if (irq < 0)
return irq;
}
ctx = vfio_irq_ctx_alloc(vdev, vector);
if (!ctx)
return -ENOMEM;
ctx->name = kasprintf(GFP_KERNEL_ACCOUNT, "vfio-msi%s[%d](%s)",
msix ? "x" : "", vector, pci_name(pdev));
if (!ctx->name) {
ret = -ENOMEM;
goto out_free_ctx;
}
trigger = eventfd_ctx_fdget(fd);
if (IS_ERR(trigger)) {
ret = PTR_ERR(trigger);
goto out_free_name;
}
/*
* If the vector was previously allocated, refresh the on-device
* message data before enabling in case it had been cleared or
* corrupted (e.g. due to backdoor resets) since writing.
*/
cmd = vfio_pci_memory_lock_and_enable(vdev);
if (msix) {
struct msi_msg msg;
get_cached_msi_msg(irq, &msg);
pci_write_msi_msg(irq, &msg);
}
ret = request_irq(irq, vfio_msihandler, 0, ctx->name, trigger);
vfio_pci_memory_unlock_and_restore(vdev, cmd);
if (ret)
goto out_put_eventfd_ctx;
ret = irq_bypass_register_producer(&ctx->producer, trigger, irq);
if (unlikely(ret)) {
dev_info(&pdev->dev,
"irq bypass producer (eventfd %p) registration fails: %d\n",
trigger, ret);
}
ctx->trigger = trigger;
return 0;
out_put_eventfd_ctx:
eventfd_ctx_put(trigger);
out_free_name:
kfree(ctx->name);
out_free_ctx:
vfio_irq_ctx_free(vdev, ctx, vector);
return ret;
}
static int vfio_msi_set_block(struct vfio_pci_core_device *vdev, unsigned start,
unsigned count, int32_t *fds, bool msix)
{
unsigned int i, j;
int ret = 0;
for (i = 0, j = start; i < count && !ret; i++, j++) {
int fd = fds ? fds[i] : -1;
ret = vfio_msi_set_vector_signal(vdev, j, fd, msix);
}
if (ret) {
for (i = start; i < j; i++)
vfio_msi_set_vector_signal(vdev, i, -1, msix);
}
return ret;
}
static void vfio_msi_disable(struct vfio_pci_core_device *vdev, bool msix)
{
struct pci_dev *pdev = vdev->pdev;
struct vfio_pci_irq_ctx *ctx;
unsigned long i;
u16 cmd;
xa_for_each(&vdev->ctx, i, ctx) {
vfio_virqfd_disable(&ctx->unmask);
vfio_virqfd_disable(&ctx->mask);
vfio_msi_set_vector_signal(vdev, i, -1, msix);
}
cmd = vfio_pci_memory_lock_and_enable(vdev);
pci_free_irq_vectors(pdev);
vfio_pci_memory_unlock_and_restore(vdev, cmd);
/*
* Both disable paths above use pci_intx_for_msi() to clear DisINTx
* via their shutdown paths. Restore for NoINTx devices.
*/
if (vdev->nointx)
pci_intx(pdev, 0);
vdev->irq_type = VFIO_PCI_NUM_IRQS;
}
/*
* IOCTL support
*/
static int vfio_pci_set_intx_unmask(struct vfio_pci_core_device *vdev,
unsigned index, unsigned start,
unsigned count, uint32_t flags, void *data)
{
if (!is_intx(vdev) || start != 0 || count != 1)
return -EINVAL;
if (flags & VFIO_IRQ_SET_DATA_NONE) {
__vfio_pci_intx_unmask(vdev);
} else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
uint8_t unmask = *(uint8_t *)data;
if (unmask)
__vfio_pci_intx_unmask(vdev);
} else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
struct vfio_pci_irq_ctx *ctx = vfio_irq_ctx_get(vdev, 0);
int32_t fd = *(int32_t *)data;
if (WARN_ON_ONCE(!ctx))
return -EINVAL;
if (fd >= 0)
return vfio_virqfd_enable((void *) vdev,
vfio_pci_intx_unmask_handler,
vfio_send_intx_eventfd, ctx,
&ctx->unmask, fd);
vfio_virqfd_disable(&ctx->unmask);
}
return 0;
}
static int vfio_pci_set_intx_mask(struct vfio_pci_core_device *vdev,
unsigned index, unsigned start,
unsigned count, uint32_t flags, void *data)
{
if (!is_intx(vdev) || start != 0 || count != 1)
return -EINVAL;
if (flags & VFIO_IRQ_SET_DATA_NONE) {
__vfio_pci_intx_mask(vdev);
} else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
uint8_t mask = *(uint8_t *)data;
if (mask)
__vfio_pci_intx_mask(vdev);
} else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
return -ENOTTY; /* XXX implement me */
}
return 0;
}
static int vfio_pci_set_intx_trigger(struct vfio_pci_core_device *vdev,
unsigned index, unsigned start,
unsigned count, uint32_t flags, void *data)
{
if (is_intx(vdev) && !count && (flags & VFIO_IRQ_SET_DATA_NONE)) {
vfio_intx_disable(vdev);
return 0;
}
if (!(is_intx(vdev) || is_irq_none(vdev)) || start != 0 || count != 1)
return -EINVAL;
if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
struct eventfd_ctx *trigger = NULL;
int32_t fd = *(int32_t *)data;
int ret;
if (fd >= 0) {
trigger = eventfd_ctx_fdget(fd);
if (IS_ERR(trigger))
return PTR_ERR(trigger);
}
if (is_intx(vdev))
ret = vfio_intx_set_signal(vdev, trigger);
else
ret = vfio_intx_enable(vdev, trigger);
if (ret && trigger)
eventfd_ctx_put(trigger);
return ret;
}
if (!is_intx(vdev))
return -EINVAL;
if (flags & VFIO_IRQ_SET_DATA_NONE) {
vfio_send_intx_eventfd(vdev, vfio_irq_ctx_get(vdev, 0));
} else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
uint8_t trigger = *(uint8_t *)data;
if (trigger)
vfio_send_intx_eventfd(vdev, vfio_irq_ctx_get(vdev, 0));
}
return 0;
}
static int vfio_pci_set_msi_trigger(struct vfio_pci_core_device *vdev,
unsigned index, unsigned start,
unsigned count, uint32_t flags, void *data)
{
struct vfio_pci_irq_ctx *ctx;
unsigned int i;
bool msix = (index == VFIO_PCI_MSIX_IRQ_INDEX);
if (irq_is(vdev, index) && !count && (flags & VFIO_IRQ_SET_DATA_NONE)) {
vfio_msi_disable(vdev, msix);
return 0;
}
if (!(irq_is(vdev, index) || is_irq_none(vdev)))
return -EINVAL;
if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
int32_t *fds = data;
int ret;
if (vdev->irq_type == index)
return vfio_msi_set_block(vdev, start, count,
fds, msix);
ret = vfio_msi_enable(vdev, start + count, msix);
if (ret)
return ret;
ret = vfio_msi_set_block(vdev, start, count, fds, msix);
if (ret)
vfio_msi_disable(vdev, msix);
return ret;
}
if (!irq_is(vdev, index))
return -EINVAL;
for (i = start; i < start + count; i++) {
ctx = vfio_irq_ctx_get(vdev, i);
if (!ctx)
continue;
if (flags & VFIO_IRQ_SET_DATA_NONE) {
eventfd_signal(ctx->trigger);
} else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
uint8_t *bools = data;
if (bools[i - start])
eventfd_signal(ctx->trigger);
}
}
return 0;
}
static int vfio_pci_set_ctx_trigger_single(struct vfio_pci_core_device *vdev,
struct vfio_pci_eventfd __rcu **peventfd,
unsigned int count, uint32_t flags,
void *data)
{
/* DATA_NONE/DATA_BOOL enables loopback testing */
if (flags & VFIO_IRQ_SET_DATA_NONE) {
struct vfio_pci_eventfd *eventfd;
eventfd = rcu_dereference_protected(*peventfd,
lockdep_is_held(&vdev->igate));
if (!eventfd)
return -EINVAL;
if (count) {
eventfd_signal(eventfd->ctx);
return 0;
}
return vfio_pci_eventfd_replace_locked(vdev, peventfd, NULL);
} else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
uint8_t trigger;
if (!count)
return -EINVAL;
trigger = *(uint8_t *)data;
if (trigger) {
struct vfio_pci_eventfd *eventfd =
rcu_dereference_protected(*peventfd,
lockdep_is_held(&vdev->igate));
if (eventfd)
eventfd_signal(eventfd->ctx);
}
return 0;
} else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
int32_t fd;
if (!count)
return -EINVAL;
fd = *(int32_t *)data;
if (fd == -1) {
return vfio_pci_eventfd_replace_locked(vdev,
peventfd, NULL);
} else if (fd >= 0) {
struct eventfd_ctx *efdctx;
int ret;
efdctx = eventfd_ctx_fdget(fd);
if (IS_ERR(efdctx))
return PTR_ERR(efdctx);
ret = vfio_pci_eventfd_replace_locked(vdev,
peventfd, efdctx);
if (ret)
eventfd_ctx_put(efdctx);
return ret;
}
}
return -EINVAL;
}
static int vfio_pci_set_err_trigger(struct vfio_pci_core_device *vdev,
unsigned index, unsigned start,
unsigned count, uint32_t flags, void *data)
{
if (index != VFIO_PCI_ERR_IRQ_INDEX || start != 0 || count > 1)
return -EINVAL;
return vfio_pci_set_ctx_trigger_single(vdev, &vdev->err_trigger,
count, flags, data);
}
static int vfio_pci_set_req_trigger(struct vfio_pci_core_device *vdev,
unsigned index, unsigned start,
unsigned count, uint32_t flags, void *data)
{
if (index != VFIO_PCI_REQ_IRQ_INDEX || start != 0 || count > 1)
return -EINVAL;
return vfio_pci_set_ctx_trigger_single(vdev, &vdev->req_trigger,
count, flags, data);
}
int vfio_pci_set_irqs_ioctl(struct vfio_pci_core_device *vdev, uint32_t flags,
unsigned index, unsigned start, unsigned count,
void *data)
{
int (*func)(struct vfio_pci_core_device *vdev, unsigned index,
unsigned start, unsigned count, uint32_t flags,
void *data) = NULL;
switch (index) {
case VFIO_PCI_INTX_IRQ_INDEX:
switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) {
case VFIO_IRQ_SET_ACTION_MASK:
func = vfio_pci_set_intx_mask;
break;
case VFIO_IRQ_SET_ACTION_UNMASK:
func = vfio_pci_set_intx_unmask;
break;
case VFIO_IRQ_SET_ACTION_TRIGGER:
func = vfio_pci_set_intx_trigger;
break;
}
break;
case VFIO_PCI_MSI_IRQ_INDEX:
case VFIO_PCI_MSIX_IRQ_INDEX:
switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) {
case VFIO_IRQ_SET_ACTION_MASK:
case VFIO_IRQ_SET_ACTION_UNMASK:
/* XXX Need masking support exported */
break;
case VFIO_IRQ_SET_ACTION_TRIGGER:
func = vfio_pci_set_msi_trigger;
break;
}
break;
case VFIO_PCI_ERR_IRQ_INDEX:
switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) {
case VFIO_IRQ_SET_ACTION_TRIGGER:
if (pci_is_pcie(vdev->pdev))
func = vfio_pci_set_err_trigger;
break;
}
break;
case VFIO_PCI_REQ_IRQ_INDEX:
switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) {
case VFIO_IRQ_SET_ACTION_TRIGGER:
func = vfio_pci_set_req_trigger;
break;
}
break;
}
if (!func)
return -ENOTTY;
return func(vdev, index, start, count, flags, data);
}
@@ -0,0 +1,140 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef VFIO_PCI_PRIV_H
#define VFIO_PCI_PRIV_H
#include <linux/vfio_pci_core.h>
/* Special capability IDs predefined access */
#define PCI_CAP_ID_INVALID 0xFF /* default raw access */
#define PCI_CAP_ID_INVALID_VIRT 0xFE /* default virt access */
/* Cap maximum number of ioeventfds per device (arbitrary) */
#define VFIO_PCI_IOEVENTFD_MAX 1000
struct vfio_pci_ioeventfd {
struct list_head next;
struct vfio_pci_core_device *vdev;
struct virqfd *virqfd;
void __iomem *addr;
uint64_t data;
loff_t pos;
int bar;
int count;
bool test_mem;
};
bool vfio_pci_intx_mask(struct vfio_pci_core_device *vdev);
void vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev);
int vfio_pci_eventfd_replace_locked(struct vfio_pci_core_device *vdev,
struct vfio_pci_eventfd __rcu **peventfd,
struct eventfd_ctx *ctx);
int vfio_pci_set_irqs_ioctl(struct vfio_pci_core_device *vdev, uint32_t flags,
unsigned index, unsigned start, unsigned count,
void *data);
ssize_t vfio_pci_config_rw(struct vfio_pci_core_device *vdev, char __user *buf,
size_t count, loff_t *ppos, bool iswrite);
ssize_t vfio_pci_config_rw_single(struct vfio_pci_core_device *vdev,
char __user *buf, size_t count, loff_t *ppos,
bool iswrite);
ssize_t vfio_pci_bar_rw(struct vfio_pci_core_device *vdev, char __user *buf,
size_t count, loff_t *ppos, bool iswrite);
#ifdef CONFIG_VFIO_PCI_VGA
ssize_t vfio_pci_vga_rw(struct vfio_pci_core_device *vdev, char __user *buf,
size_t count, loff_t *ppos, bool iswrite);
#else
static inline ssize_t vfio_pci_vga_rw(struct vfio_pci_core_device *vdev,
char __user *buf, size_t count,
loff_t *ppos, bool iswrite)
{
return -EINVAL;
}
#endif
int vfio_pci_ioeventfd(struct vfio_pci_core_device *vdev, loff_t offset,
uint64_t data, int count, int fd);
int vfio_pci_init_perm_bits(void);
void vfio_pci_uninit_perm_bits(void);
int vfio_config_init(struct vfio_pci_core_device *vdev);
void vfio_config_free(struct vfio_pci_core_device *vdev);
int vfio_pci_set_power_state(struct vfio_pci_core_device *vdev,
pci_power_t state);
void vfio_pci_zap_and_down_write_memory_lock(struct vfio_pci_core_device *vdev);
u16 vfio_pci_memory_lock_and_enable(struct vfio_pci_core_device *vdev);
void vfio_pci_memory_unlock_and_restore(struct vfio_pci_core_device *vdev,
u16 cmd);
#ifdef CONFIG_VFIO_PCI_IGD
bool vfio_pci_is_intel_display(struct pci_dev *pdev);
int vfio_pci_igd_init(struct vfio_pci_core_device *vdev);
#else
static inline bool vfio_pci_is_intel_display(struct pci_dev *pdev)
{
return false;
}
static inline int vfio_pci_igd_init(struct vfio_pci_core_device *vdev)
{
return -ENODEV;
}
#endif
#ifdef CONFIG_VFIO_PCI_ZDEV_KVM
int vfio_pci_info_zdev_add_caps(struct vfio_pci_core_device *vdev,
struct vfio_info_cap *caps);
int vfio_pci_zdev_open_device(struct vfio_pci_core_device *vdev);
void vfio_pci_zdev_close_device(struct vfio_pci_core_device *vdev);
#else
static inline int vfio_pci_info_zdev_add_caps(struct vfio_pci_core_device *vdev,
struct vfio_info_cap *caps)
{
return -ENODEV;
}
static inline int vfio_pci_zdev_open_device(struct vfio_pci_core_device *vdev)
{
return 0;
}
static inline void vfio_pci_zdev_close_device(struct vfio_pci_core_device *vdev)
{}
#endif
static inline bool vfio_pci_is_vga(struct pci_dev *pdev)
{
return (pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA;
}
#ifdef CONFIG_VFIO_PCI_DMABUF
int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags,
struct vfio_device_feature_dma_buf __user *arg,
size_t argsz);
void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev);
void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked);
#else
static inline int
vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags,
struct vfio_device_feature_dma_buf __user *arg,
size_t argsz)
{
return -ENOTTY;
}
static inline void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev)
{
}
static inline void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev,
bool revoked)
{
}
#endif
#endif
@@ -0,0 +1,490 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* VFIO PCI I/O Port & MMIO access
*
* Copyright (C) 2012 Red Hat, Inc. All rights reserved.
* Author: Alex Williamson <alex.williamson@redhat.com>
*
* Derived from original vfio:
* Copyright 2010 Cisco Systems, Inc. All rights reserved.
* Author: Tom Lyon, pugs@cisco.com
*/
#include <linux/fs.h>
#include <linux/pci.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/vfio.h>
#include <linux/vgaarb.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include "vfio_pci_priv.h"
#ifdef __LITTLE_ENDIAN
#define vfio_ioread64 ioread64
#define vfio_iowrite64 iowrite64
#define vfio_ioread32 ioread32
#define vfio_iowrite32 iowrite32
#define vfio_ioread16 ioread16
#define vfio_iowrite16 iowrite16
#else
#define vfio_ioread64 ioread64be
#define vfio_iowrite64 iowrite64be
#define vfio_ioread32 ioread32be
#define vfio_iowrite32 iowrite32be
#define vfio_ioread16 ioread16be
#define vfio_iowrite16 iowrite16be
#endif
#define vfio_ioread8 ioread8
#define vfio_iowrite8 iowrite8
#define VFIO_IOWRITE(size) \
int vfio_pci_core_iowrite##size(struct vfio_pci_core_device *vdev, \
bool test_mem, u##size val, void __iomem *io) \
{ \
if (test_mem) { \
down_read(&vdev->memory_lock); \
if (!__vfio_pci_memory_enabled(vdev)) { \
up_read(&vdev->memory_lock); \
return -EIO; \
} \
} \
\
vfio_iowrite##size(val, io); \
\
if (test_mem) \
up_read(&vdev->memory_lock); \
\
return 0; \
} \
EXPORT_SYMBOL_GPL(vfio_pci_core_iowrite##size);
VFIO_IOWRITE(8)
VFIO_IOWRITE(16)
VFIO_IOWRITE(32)
VFIO_IOWRITE(64)
#define VFIO_IOREAD(size) \
int vfio_pci_core_ioread##size(struct vfio_pci_core_device *vdev, \
bool test_mem, u##size *val, void __iomem *io) \
{ \
if (test_mem) { \
down_read(&vdev->memory_lock); \
if (!__vfio_pci_memory_enabled(vdev)) { \
up_read(&vdev->memory_lock); \
return -EIO; \
} \
} \
\
*val = vfio_ioread##size(io); \
\
if (test_mem) \
up_read(&vdev->memory_lock); \
\
return 0; \
} \
EXPORT_SYMBOL_GPL(vfio_pci_core_ioread##size);
VFIO_IOREAD(8)
VFIO_IOREAD(16)
VFIO_IOREAD(32)
VFIO_IOREAD(64)
#define VFIO_IORDWR(size) \
static int vfio_pci_iordwr##size(struct vfio_pci_core_device *vdev,\
bool iswrite, bool test_mem, \
void __iomem *io, char __user *buf, \
loff_t off, size_t *filled) \
{ \
u##size val; \
int ret; \
\
if (iswrite) { \
if (copy_from_user(&val, buf, sizeof(val))) \
return -EFAULT; \
\
ret = vfio_pci_core_iowrite##size(vdev, test_mem, \
val, io + off); \
if (ret) \
return ret; \
} else { \
ret = vfio_pci_core_ioread##size(vdev, test_mem, \
&val, io + off); \
if (ret) \
return ret; \
\
if (copy_to_user(buf, &val, sizeof(val))) \
return -EFAULT; \
} \
\
*filled = sizeof(val); \
return 0; \
} \
VFIO_IORDWR(8)
VFIO_IORDWR(16)
VFIO_IORDWR(32)
VFIO_IORDWR(64)
/*
* Read or write from an __iomem region (MMIO or I/O port) with an excluded
* range which is inaccessible. The excluded range drops writes and fills
* reads with -1. This is intended for handling MSI-X vector tables and
* leftover space for ROM BARs.
*/
ssize_t vfio_pci_core_do_io_rw(struct vfio_pci_core_device *vdev, bool test_mem,
void __iomem *io, char __user *buf,
loff_t off, size_t count, size_t x_start,
size_t x_end, bool iswrite,
enum vfio_pci_io_width max_width)
{
ssize_t done = 0;
int ret;
while (count) {
size_t fillable, filled;
if (off < x_start)
fillable = min(count, (size_t)(x_start - off));
else if (off >= x_end)
fillable = count;
else
fillable = 0;
if (fillable >= 8 && !(off % 8) && max_width >= 8) {
ret = vfio_pci_iordwr64(vdev, iswrite, test_mem,
io, buf, off, &filled);
if (ret)
return ret;
} else if (fillable >= 4 && !(off % 4) && max_width >= 4) {
ret = vfio_pci_iordwr32(vdev, iswrite, test_mem,
io, buf, off, &filled);
if (ret)
return ret;
} else if (fillable >= 2 && !(off % 2) && max_width >= 2) {
ret = vfio_pci_iordwr16(vdev, iswrite, test_mem,
io, buf, off, &filled);
if (ret)
return ret;
} else if (fillable) {
ret = vfio_pci_iordwr8(vdev, iswrite, test_mem,
io, buf, off, &filled);
if (ret)
return ret;
} else {
/* Fill reads with -1, drop writes */
filled = min(count, (size_t)(x_end - off));
if (!iswrite) {
u8 val = 0xFF;
size_t i;
for (i = 0; i < filled; i++)
if (copy_to_user(buf + i, &val, 1))
return -EFAULT;
}
}
count -= filled;
done += filled;
off += filled;
buf += filled;
}
return done;
}
EXPORT_SYMBOL_GPL(vfio_pci_core_do_io_rw);
ssize_t vfio_pci_bar_rw(struct vfio_pci_core_device *vdev, char __user *buf,
size_t count, loff_t *ppos, bool iswrite)
{
struct pci_dev *pdev = vdev->pdev;
loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK;
int bar = VFIO_PCI_OFFSET_TO_INDEX(*ppos);
size_t x_start = 0, x_end = 0;
resource_size_t end;
void __iomem *io;
struct resource *res = &vdev->pdev->resource[bar];
ssize_t done;
enum vfio_pci_io_width max_width = VFIO_PCI_IO_WIDTH_8;
if (pci_resource_start(pdev, bar))
end = pci_resource_len(pdev, bar);
else if (bar == PCI_ROM_RESOURCE && pdev->rom && pdev->romlen)
end = roundup_pow_of_two(pdev->romlen);
else
return -EINVAL;
if (pos >= end)
return -EINVAL;
count = min(count, (size_t)(end - pos));
if (bar == PCI_ROM_RESOURCE) {
/*
* The ROM can fill less space than the BAR, so we start the
* excluded range at the end of the actual ROM. This makes
* filling large ROM BARs much faster.
*/
if (pci_resource_start(pdev, bar)) {
io = pci_map_rom(pdev, &x_start);
} else {
io = ioremap(pdev->rom, pdev->romlen);
x_start = pdev->romlen;
}
if (!io)
return -ENOMEM;
x_end = end;
/*
* Certain devices (e.g. Intel X710) don't support qword
* access to the ROM bar. Otherwise PCI AER errors might be
* triggered.
*
* Disable qword access to the ROM bar universally, which
* worked reliably for years before qword access is enabled.
*/
max_width = VFIO_PCI_IO_WIDTH_4;
} else {
io = vfio_pci_core_get_iomap(vdev, bar);
if (IS_ERR(io)) {
done = PTR_ERR(io);
goto out;
}
}
if (bar == vdev->msix_bar) {
x_start = vdev->msix_offset;
x_end = vdev->msix_offset + vdev->msix_size;
}
done = vfio_pci_core_do_io_rw(vdev, res->flags & IORESOURCE_MEM, io, buf, pos,
count, x_start, x_end, iswrite, max_width);
if (done >= 0)
*ppos += done;
if (bar == PCI_ROM_RESOURCE) {
if (pci_resource_start(pdev, bar))
pci_unmap_rom(pdev, io);
else
iounmap(io);
}
out:
return done;
}
#ifdef CONFIG_VFIO_PCI_VGA
ssize_t vfio_pci_vga_rw(struct vfio_pci_core_device *vdev, char __user *buf,
size_t count, loff_t *ppos, bool iswrite)
{
int ret;
loff_t off, pos = *ppos & VFIO_PCI_OFFSET_MASK;
void __iomem *iomem = NULL;
unsigned int rsrc;
bool is_ioport;
ssize_t done;
if (!vdev->has_vga)
return -EINVAL;
if (pos > 0xbfffful)
return -EINVAL;
switch ((u32)pos) {
case 0xa0000 ... 0xbffff:
count = min(count, (size_t)(0xc0000 - pos));
iomem = ioremap(0xa0000, 0xbffff - 0xa0000 + 1);
off = pos - 0xa0000;
rsrc = VGA_RSRC_LEGACY_MEM;
is_ioport = false;
break;
case 0x3b0 ... 0x3bb:
count = min(count, (size_t)(0x3bc - pos));
iomem = ioport_map(0x3b0, 0x3bb - 0x3b0 + 1);
off = pos - 0x3b0;
rsrc = VGA_RSRC_LEGACY_IO;
is_ioport = true;
break;
case 0x3c0 ... 0x3df:
count = min(count, (size_t)(0x3e0 - pos));
iomem = ioport_map(0x3c0, 0x3df - 0x3c0 + 1);
off = pos - 0x3c0;
rsrc = VGA_RSRC_LEGACY_IO;
is_ioport = true;
break;
default:
return -EINVAL;
}
if (!iomem)
return -ENOMEM;
ret = vga_get_interruptible(vdev->pdev, rsrc);
if (ret) {
is_ioport ? ioport_unmap(iomem) : iounmap(iomem);
return ret;
}
/*
* VGA MMIO is a legacy, non-BAR resource that hopefully allows
* probing, so we don't currently worry about access in relation
* to the memory enable bit in the command register.
*/
done = vfio_pci_core_do_io_rw(vdev, false, iomem, buf, off, count,
0, 0, iswrite, VFIO_PCI_IO_WIDTH_4);
vga_put(vdev->pdev, rsrc);
is_ioport ? ioport_unmap(iomem) : iounmap(iomem);
if (done >= 0)
*ppos += done;
return done;
}
#endif
static void vfio_pci_ioeventfd_do_write(struct vfio_pci_ioeventfd *ioeventfd,
bool test_mem)
{
switch (ioeventfd->count) {
case 1:
vfio_pci_core_iowrite8(ioeventfd->vdev, test_mem,
ioeventfd->data, ioeventfd->addr);
break;
case 2:
vfio_pci_core_iowrite16(ioeventfd->vdev, test_mem,
ioeventfd->data, ioeventfd->addr);
break;
case 4:
vfio_pci_core_iowrite32(ioeventfd->vdev, test_mem,
ioeventfd->data, ioeventfd->addr);
break;
case 8:
vfio_pci_core_iowrite64(ioeventfd->vdev, test_mem,
ioeventfd->data, ioeventfd->addr);
break;
}
}
static int vfio_pci_ioeventfd_handler(void *opaque, void *unused)
{
struct vfio_pci_ioeventfd *ioeventfd = opaque;
struct vfio_pci_core_device *vdev = ioeventfd->vdev;
if (ioeventfd->test_mem) {
if (!down_read_trylock(&vdev->memory_lock))
return 1; /* Lock contended, use thread */
if (!__vfio_pci_memory_enabled(vdev)) {
up_read(&vdev->memory_lock);
return 0;
}
}
vfio_pci_ioeventfd_do_write(ioeventfd, false);
if (ioeventfd->test_mem)
up_read(&vdev->memory_lock);
return 0;
}
static void vfio_pci_ioeventfd_thread(void *opaque, void *unused)
{
struct vfio_pci_ioeventfd *ioeventfd = opaque;
vfio_pci_ioeventfd_do_write(ioeventfd, ioeventfd->test_mem);
}
int vfio_pci_ioeventfd(struct vfio_pci_core_device *vdev, loff_t offset,
uint64_t data, int count, int fd)
{
struct pci_dev *pdev = vdev->pdev;
loff_t pos = offset & VFIO_PCI_OFFSET_MASK;
int ret, bar = VFIO_PCI_OFFSET_TO_INDEX(offset);
struct vfio_pci_ioeventfd *ioeventfd;
void __iomem *io;
/* Only support ioeventfds into BARs */
if (bar > VFIO_PCI_BAR5_REGION_INDEX)
return -EINVAL;
if (pos + count > pci_resource_len(pdev, bar))
return -EINVAL;
/* Disallow ioeventfds working around MSI-X table writes */
if (bar == vdev->msix_bar &&
!(pos + count <= vdev->msix_offset ||
pos >= vdev->msix_offset + vdev->msix_size))
return -EINVAL;
if (count == 8)
return -EINVAL;
io = vfio_pci_core_get_iomap(vdev, bar);
if (IS_ERR(io))
return PTR_ERR(io);
mutex_lock(&vdev->ioeventfds_lock);
list_for_each_entry(ioeventfd, &vdev->ioeventfds_list, next) {
if (ioeventfd->pos == pos && ioeventfd->bar == bar &&
ioeventfd->data == data && ioeventfd->count == count) {
if (fd == -1) {
vfio_virqfd_disable(&ioeventfd->virqfd);
list_del(&ioeventfd->next);
vdev->ioeventfds_nr--;
kfree(ioeventfd);
ret = 0;
} else
ret = -EEXIST;
goto out_unlock;
}
}
if (fd < 0) {
ret = -ENODEV;
goto out_unlock;
}
if (vdev->ioeventfds_nr >= VFIO_PCI_IOEVENTFD_MAX) {
ret = -ENOSPC;
goto out_unlock;
}
ioeventfd = kzalloc_obj(*ioeventfd, GFP_KERNEL_ACCOUNT);
if (!ioeventfd) {
ret = -ENOMEM;
goto out_unlock;
}
ioeventfd->vdev = vdev;
ioeventfd->addr = io + pos;
ioeventfd->data = data;
ioeventfd->pos = pos;
ioeventfd->bar = bar;
ioeventfd->count = count;
ioeventfd->test_mem = vdev->pdev->resource[bar].flags & IORESOURCE_MEM;
ret = vfio_virqfd_enable(ioeventfd, vfio_pci_ioeventfd_handler,
vfio_pci_ioeventfd_thread, NULL,
&ioeventfd->virqfd, fd);
if (ret) {
kfree(ioeventfd);
goto out_unlock;
}
list_add(&ioeventfd->next, &vdev->ioeventfds_list);
vdev->ioeventfds_nr++;
out_unlock:
mutex_unlock(&vdev->ioeventfds_lock);
return ret;
}
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* VFIO ZPCI devices support
*
* Copyright (C) IBM Corp. 2020. All rights reserved.
* Author(s): Pierre Morel <pmorel@linux.ibm.com>
* Matthew Rosato <mjrosato@linux.ibm.com>
*/
#include <linux/io.h>
#include <linux/pci.h>
#include <linux/uaccess.h>
#include <linux/vfio.h>
#include <linux/vfio_zdev.h>
#include <linux/kvm_host.h>
#include <asm/pci_clp.h>
#include <asm/pci_io.h>
#include "vfio_pci_priv.h"
/*
* Add the Base PCI Function information to the device info region.
*/
static int zpci_base_cap(struct zpci_dev *zdev, struct vfio_info_cap *caps)
{
struct vfio_device_info_cap_zpci_base cap = {
.header.id = VFIO_DEVICE_INFO_CAP_ZPCI_BASE,
.header.version = 2,
.start_dma = zdev->start_dma,
.end_dma = zdev->end_dma,
.pchid = zdev->pchid,
.vfn = zdev->vfn,
.fmb_length = zdev->fmb_length,
.pft = zdev->pft,
.gid = zdev->pfgid,
.fh = zdev->fh
};
return vfio_info_add_capability(caps, &cap.header, sizeof(cap));
}
/*
* Add the Base PCI Function Group information to the device info region.
*/
static int zpci_group_cap(struct zpci_dev *zdev, struct vfio_info_cap *caps)
{
struct vfio_device_info_cap_zpci_group cap = {
.header.id = VFIO_DEVICE_INFO_CAP_ZPCI_GROUP,
.header.version = 2,
.dasm = zdev->dma_mask,
.msi_addr = zdev->msi_addr,
.flags = VFIO_DEVICE_INFO_ZPCI_FLAG_REFRESH,
.mui = zdev->fmb_update,
.noi = zdev->max_msi,
.maxstbl = ZPCI_MAX_WRITE_SIZE,
.version = zdev->version,
.reserved = 0,
.imaxstbl = zdev->maxstbl
};
return vfio_info_add_capability(caps, &cap.header, sizeof(cap));
}
/*
* Add the device utility string to the device info region.
*/
static int zpci_util_cap(struct zpci_dev *zdev, struct vfio_info_cap *caps)
{
struct vfio_device_info_cap_zpci_util *cap;
int cap_size = sizeof(*cap) + CLP_UTIL_STR_LEN;
int ret;
cap = kmalloc(cap_size, GFP_KERNEL);
if (!cap)
return -ENOMEM;
cap->header.id = VFIO_DEVICE_INFO_CAP_ZPCI_UTIL;
cap->header.version = 1;
cap->size = CLP_UTIL_STR_LEN;
memcpy(cap->util_str, zdev->util_str, cap->size);
ret = vfio_info_add_capability(caps, &cap->header, cap_size);
kfree(cap);
return ret;
}
/*
* Add the function path string to the device info region.
*/
static int zpci_pfip_cap(struct zpci_dev *zdev, struct vfio_info_cap *caps)
{
struct vfio_device_info_cap_zpci_pfip *cap;
int cap_size = sizeof(*cap) + CLP_PFIP_NR_SEGMENTS;
int ret;
cap = kmalloc(cap_size, GFP_KERNEL);
if (!cap)
return -ENOMEM;
cap->header.id = VFIO_DEVICE_INFO_CAP_ZPCI_PFIP;
cap->header.version = 1;
cap->size = CLP_PFIP_NR_SEGMENTS;
memcpy(cap->pfip, zdev->pfip, cap->size);
ret = vfio_info_add_capability(caps, &cap->header, cap_size);
kfree(cap);
return ret;
}
/*
* Add all supported capabilities to the VFIO_DEVICE_GET_INFO capability chain.
*/
int vfio_pci_info_zdev_add_caps(struct vfio_pci_core_device *vdev,
struct vfio_info_cap *caps)
{
struct zpci_dev *zdev = to_zpci(vdev->pdev);
int ret;
if (!zdev)
return -ENODEV;
ret = zpci_base_cap(zdev, caps);
if (ret)
return ret;
ret = zpci_group_cap(zdev, caps);
if (ret)
return ret;
if (zdev->util_str_avail) {
ret = zpci_util_cap(zdev, caps);
if (ret)
return ret;
}
ret = zpci_pfip_cap(zdev, caps);
return ret;
}
int vfio_pci_zdev_open_device(struct vfio_pci_core_device *vdev)
{
struct zpci_dev *zdev = to_zpci(vdev->pdev);
if (!zdev)
return -ENODEV;
if (!vdev->vdev.kvm)
return 0;
if (zpci_kvm_hook.kvm_register)
return zpci_kvm_hook.kvm_register(zdev, vdev->vdev.kvm);
return -ENOENT;
}
void vfio_pci_zdev_close_device(struct vfio_pci_core_device *vdev)
{
struct zpci_dev *zdev = to_zpci(vdev->pdev);
if (!zdev || !vdev->vdev.kvm)
return;
if (zpci_kvm_hook.kvm_unregister)
zpci_kvm_hook.kvm_unregister(zdev);
}
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0-only
config VIRTIO_VFIO_PCI
tristate "VFIO support for VIRTIO PCI VF devices"
depends on VIRTIO_PCI
select VFIO_PCI_CORE
help
This provides migration support for VIRTIO NET and BLOCK PCI VF
devices using the VFIO framework. Migration support requires the
SR-IOV PF device to support specific VIRTIO extensions,
otherwise this driver provides no additional functionality
beyond vfio-pci.
Migration support in this driver relies on dirty page tracking
provided by the IOMMU hardware and exposed through IOMMUFD, any
other use cases are dis-recommended.
If you don't know what to do here, say N.
config VIRTIO_VFIO_PCI_ADMIN_LEGACY
bool "Legacy I/O support for VIRTIO NET PCI VF devices"
depends on VIRTIO_VFIO_PCI && VIRTIO_PCI_ADMIN_LEGACY
default y
help
This extends the virtio-vfio-pci driver to support legacy I/O
access, allowing use of legacy virtio drivers with VIRTIO NET
PCI VF devices. Legacy I/O support requires the SR-IOV PF
device to support and enable specific VIRTIO extensions,
otherwise this driver provides no additional functionality
beyond vfio-pci.
If you don't know what to do here, say N.
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_VIRTIO_VFIO_PCI) += virtio-vfio-pci.o
virtio-vfio-pci-y := main.o migrate.o
virtio-vfio-pci-$(CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY) += legacy_io.o
@@ -0,0 +1,126 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef VIRTIO_VFIO_COMMON_H
#define VIRTIO_VFIO_COMMON_H
#include <linux/kernel.h>
#include <linux/virtio.h>
#include <linux/vfio_pci_core.h>
#include <linux/virtio_pci.h>
enum virtiovf_migf_state {
VIRTIOVF_MIGF_STATE_ERROR = 1,
VIRTIOVF_MIGF_STATE_PRECOPY = 2,
VIRTIOVF_MIGF_STATE_COMPLETE = 3,
};
enum virtiovf_load_state {
VIRTIOVF_LOAD_STATE_READ_HEADER,
VIRTIOVF_LOAD_STATE_PREP_HEADER_DATA,
VIRTIOVF_LOAD_STATE_READ_HEADER_DATA,
VIRTIOVF_LOAD_STATE_PREP_CHUNK,
VIRTIOVF_LOAD_STATE_READ_CHUNK,
VIRTIOVF_LOAD_STATE_LOAD_CHUNK,
};
struct virtiovf_data_buffer {
struct sg_append_table table;
loff_t start_pos;
u64 length;
u64 allocated_length;
struct list_head buf_elm;
u8 include_header_object:1;
struct virtiovf_migration_file *migf;
/* Optimize virtiovf_get_migration_page() for sequential access */
struct scatterlist *last_offset_sg;
unsigned int sg_last_entry;
unsigned long last_offset;
};
enum virtiovf_migf_header_flags {
VIRTIOVF_MIGF_HEADER_FLAGS_TAG_MANDATORY = 0,
VIRTIOVF_MIGF_HEADER_FLAGS_TAG_OPTIONAL = 1 << 0,
};
enum virtiovf_migf_header_tag {
VIRTIOVF_MIGF_HEADER_TAG_DEVICE_DATA = 0,
};
struct virtiovf_migration_header {
__le64 record_size;
/* For future use in case we may need to change the kernel protocol */
__le32 flags; /* Use virtiovf_migf_header_flags */
__le32 tag; /* Use virtiovf_migf_header_tag */
__u8 data[]; /* Its size is given in the record_size */
};
struct virtiovf_migration_file {
struct file *filp;
/* synchronize access to the file state */
struct mutex lock;
loff_t max_pos;
u64 pre_copy_initial_bytes;
struct ratelimit_state pre_copy_rl_state;
u64 record_size;
u32 record_tag;
u8 has_obj_id:1;
u32 obj_id;
enum virtiovf_migf_state state;
enum virtiovf_load_state load_state;
/* synchronize access to the lists */
struct mutex list_lock;
struct list_head buf_list;
struct list_head avail_list;
struct virtiovf_data_buffer *buf;
struct virtiovf_data_buffer *buf_header;
struct virtiovf_pci_core_device *virtvdev;
};
struct virtiovf_pci_core_device {
struct vfio_pci_core_device core_device;
#ifdef CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY
u8 *bar0_virtual_buf;
/* synchronize access to the virtual buf */
struct mutex bar_mutex;
void __iomem *notify_addr;
u64 notify_offset;
__le32 pci_base_addr_0;
__le16 pci_cmd;
u8 bar0_virtual_buf_size;
u8 notify_bar;
#endif
/* LM related */
u8 migrate_cap:1;
u8 deferred_reset:1;
/* protect migration state */
struct mutex state_mutex;
enum vfio_device_mig_state mig_state;
/* protect the reset_done flow */
spinlock_t reset_lock;
struct virtiovf_migration_file *resuming_migf;
struct virtiovf_migration_file *saving_migf;
};
void virtiovf_set_migratable(struct virtiovf_pci_core_device *virtvdev);
void virtiovf_open_migration(struct virtiovf_pci_core_device *virtvdev);
void virtiovf_close_migration(struct virtiovf_pci_core_device *virtvdev);
void virtiovf_migration_reset_done(struct pci_dev *pdev);
#ifdef CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY
int virtiovf_open_legacy_io(struct virtiovf_pci_core_device *virtvdev);
int virtiovf_pci_ioctl_get_region_info(struct vfio_device *core_vdev,
struct vfio_region_info *info,
struct vfio_info_cap *caps);
ssize_t virtiovf_pci_core_write(struct vfio_device *core_vdev,
const char __user *buf, size_t count,
loff_t *ppos);
ssize_t virtiovf_pci_core_read(struct vfio_device *core_vdev, char __user *buf,
size_t count, loff_t *ppos);
bool virtiovf_support_legacy_io(struct pci_dev *pdev);
int virtiovf_init_legacy_io(struct virtiovf_pci_core_device *virtvdev);
void virtiovf_release_legacy_io(struct virtiovf_pci_core_device *virtvdev);
void virtiovf_legacy_io_reset_done(struct pci_dev *pdev);
#endif
#endif /* VIRTIO_VFIO_COMMON_H */
@@ -0,0 +1,396 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/vfio.h>
#include <linux/vfio_pci_core.h>
#include <linux/virtio_pci.h>
#include <linux/virtio_net.h>
#include <linux/virtio_pci_admin.h>
#include "common.h"
static int
virtiovf_issue_legacy_rw_cmd(struct virtiovf_pci_core_device *virtvdev,
loff_t pos, char __user *buf,
size_t count, bool read)
{
bool msix_enabled =
(virtvdev->core_device.irq_type == VFIO_PCI_MSIX_IRQ_INDEX);
struct pci_dev *pdev = virtvdev->core_device.pdev;
u8 *bar0_buf = virtvdev->bar0_virtual_buf;
bool common;
u8 offset;
int ret;
common = pos < VIRTIO_PCI_CONFIG_OFF(msix_enabled);
/* offset within the relevant configuration area */
offset = common ? pos : pos - VIRTIO_PCI_CONFIG_OFF(msix_enabled);
guard(mutex)(&virtvdev->bar_mutex);
if (read) {
if (common)
ret = virtio_pci_admin_legacy_common_io_read(pdev, offset,
count, bar0_buf + pos);
else
ret = virtio_pci_admin_legacy_device_io_read(pdev, offset,
count, bar0_buf + pos);
if (ret)
return ret;
if (copy_to_user(buf, bar0_buf + pos, count))
return -EFAULT;
} else {
if (copy_from_user(bar0_buf + pos, buf, count))
return -EFAULT;
if (common)
ret = virtio_pci_admin_legacy_common_io_write(pdev, offset,
count, bar0_buf + pos);
else
ret = virtio_pci_admin_legacy_device_io_write(pdev, offset,
count, bar0_buf + pos);
}
return ret;
}
static int
virtiovf_pci_bar0_rw(struct virtiovf_pci_core_device *virtvdev,
loff_t pos, char __user *buf,
size_t count, bool read)
{
struct vfio_pci_core_device *core_device = &virtvdev->core_device;
struct pci_dev *pdev = core_device->pdev;
u16 queue_notify;
int ret;
if (!(le16_to_cpu(virtvdev->pci_cmd) & PCI_COMMAND_IO))
return -EIO;
if (pos + count > virtvdev->bar0_virtual_buf_size)
return -EINVAL;
ret = pm_runtime_resume_and_get(&pdev->dev);
if (ret) {
pci_info_ratelimited(pdev, "runtime resume failed %d\n", ret);
return -EIO;
}
switch (pos) {
case VIRTIO_PCI_QUEUE_NOTIFY:
if (count != sizeof(queue_notify)) {
ret = -EINVAL;
goto end;
}
if (read) {
ret = vfio_pci_core_ioread16(core_device, true, &queue_notify,
virtvdev->notify_addr);
if (ret)
goto end;
if (copy_to_user(buf, &queue_notify,
sizeof(queue_notify))) {
ret = -EFAULT;
goto end;
}
} else {
if (copy_from_user(&queue_notify, buf, count)) {
ret = -EFAULT;
goto end;
}
ret = vfio_pci_core_iowrite16(core_device, true, queue_notify,
virtvdev->notify_addr);
}
break;
default:
ret = virtiovf_issue_legacy_rw_cmd(virtvdev, pos, buf, count,
read);
}
end:
pm_runtime_put(&pdev->dev);
return ret ? ret : count;
}
static ssize_t virtiovf_pci_read_config(struct vfio_device *core_vdev,
char __user *buf, size_t count,
loff_t *ppos)
{
struct virtiovf_pci_core_device *virtvdev = container_of(
core_vdev, struct virtiovf_pci_core_device, core_device.vdev);
loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK;
size_t register_offset;
loff_t copy_offset;
size_t copy_count;
__le32 val32;
__le16 val16;
u8 val8;
int ret;
ret = vfio_pci_core_read(core_vdev, buf, count, ppos);
if (ret < 0)
return ret;
if (vfio_pci_core_range_intersect_range(pos, count, PCI_DEVICE_ID,
sizeof(val16), &copy_offset,
&copy_count, &register_offset)) {
val16 = cpu_to_le16(VIRTIO_TRANS_ID_NET);
if (copy_to_user(buf + copy_offset, (void *)&val16 + register_offset, copy_count))
return -EFAULT;
}
if ((le16_to_cpu(virtvdev->pci_cmd) & PCI_COMMAND_IO) &&
vfio_pci_core_range_intersect_range(pos, count, PCI_COMMAND,
sizeof(val16), &copy_offset,
&copy_count, &register_offset)) {
if (copy_from_user((void *)&val16 + register_offset, buf + copy_offset,
copy_count))
return -EFAULT;
val16 |= cpu_to_le16(PCI_COMMAND_IO);
if (copy_to_user(buf + copy_offset, (void *)&val16 + register_offset,
copy_count))
return -EFAULT;
}
if (vfio_pci_core_range_intersect_range(pos, count, PCI_REVISION_ID,
sizeof(val8), &copy_offset,
&copy_count, &register_offset)) {
/* Transional needs to have revision 0 */
val8 = 0;
if (copy_to_user(buf + copy_offset, &val8, copy_count))
return -EFAULT;
}
if (vfio_pci_core_range_intersect_range(pos, count, PCI_BASE_ADDRESS_0,
sizeof(val32), &copy_offset,
&copy_count, &register_offset)) {
u32 bar_mask = ~(virtvdev->bar0_virtual_buf_size - 1);
u32 pci_base_addr_0 = le32_to_cpu(virtvdev->pci_base_addr_0);
val32 = cpu_to_le32((pci_base_addr_0 & bar_mask) | PCI_BASE_ADDRESS_SPACE_IO);
if (copy_to_user(buf + copy_offset, (void *)&val32 + register_offset, copy_count))
return -EFAULT;
}
if (vfio_pci_core_range_intersect_range(pos, count, PCI_SUBSYSTEM_ID,
sizeof(val16), &copy_offset,
&copy_count, &register_offset)) {
/*
* Transitional devices use the PCI subsystem device id as
* virtio device id, same as legacy driver always did.
*/
val16 = cpu_to_le16(VIRTIO_ID_NET);
if (copy_to_user(buf + copy_offset, (void *)&val16 + register_offset,
copy_count))
return -EFAULT;
}
if (vfio_pci_core_range_intersect_range(pos, count, PCI_SUBSYSTEM_VENDOR_ID,
sizeof(val16), &copy_offset,
&copy_count, &register_offset)) {
val16 = cpu_to_le16(PCI_VENDOR_ID_REDHAT_QUMRANET);
if (copy_to_user(buf + copy_offset, (void *)&val16 + register_offset,
copy_count))
return -EFAULT;
}
return count;
}
ssize_t virtiovf_pci_core_read(struct vfio_device *core_vdev, char __user *buf,
size_t count, loff_t *ppos)
{
struct virtiovf_pci_core_device *virtvdev = container_of(
core_vdev, struct virtiovf_pci_core_device, core_device.vdev);
unsigned int index = VFIO_PCI_OFFSET_TO_INDEX(*ppos);
loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK;
if (!count)
return 0;
if (index == VFIO_PCI_CONFIG_REGION_INDEX)
return virtiovf_pci_read_config(core_vdev, buf, count, ppos);
if (index == VFIO_PCI_BAR0_REGION_INDEX)
return virtiovf_pci_bar0_rw(virtvdev, pos, buf, count, true);
return vfio_pci_core_read(core_vdev, buf, count, ppos);
}
static ssize_t virtiovf_pci_write_config(struct vfio_device *core_vdev,
const char __user *buf, size_t count,
loff_t *ppos)
{
struct virtiovf_pci_core_device *virtvdev = container_of(
core_vdev, struct virtiovf_pci_core_device, core_device.vdev);
loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK;
size_t register_offset;
loff_t copy_offset;
size_t copy_count;
if (vfio_pci_core_range_intersect_range(pos, count, PCI_COMMAND,
sizeof(virtvdev->pci_cmd),
&copy_offset, &copy_count,
&register_offset)) {
if (copy_from_user((void *)&virtvdev->pci_cmd + register_offset,
buf + copy_offset,
copy_count))
return -EFAULT;
}
if (vfio_pci_core_range_intersect_range(pos, count, PCI_BASE_ADDRESS_0,
sizeof(virtvdev->pci_base_addr_0),
&copy_offset, &copy_count,
&register_offset)) {
if (copy_from_user((void *)&virtvdev->pci_base_addr_0 + register_offset,
buf + copy_offset,
copy_count))
return -EFAULT;
}
return vfio_pci_core_write(core_vdev, buf, count, ppos);
}
ssize_t virtiovf_pci_core_write(struct vfio_device *core_vdev, const char __user *buf,
size_t count, loff_t *ppos)
{
struct virtiovf_pci_core_device *virtvdev = container_of(
core_vdev, struct virtiovf_pci_core_device, core_device.vdev);
unsigned int index = VFIO_PCI_OFFSET_TO_INDEX(*ppos);
loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK;
if (!count)
return 0;
if (index == VFIO_PCI_CONFIG_REGION_INDEX)
return virtiovf_pci_write_config(core_vdev, buf, count, ppos);
if (index == VFIO_PCI_BAR0_REGION_INDEX)
return virtiovf_pci_bar0_rw(virtvdev, pos, (char __user *)buf, count, false);
return vfio_pci_core_write(core_vdev, buf, count, ppos);
}
int virtiovf_pci_ioctl_get_region_info(struct vfio_device *core_vdev,
struct vfio_region_info *info,
struct vfio_info_cap *caps)
{
struct virtiovf_pci_core_device *virtvdev = container_of(
core_vdev, struct virtiovf_pci_core_device, core_device.vdev);
if (info->index != VFIO_PCI_BAR0_REGION_INDEX)
return vfio_pci_ioctl_get_region_info(core_vdev, info, caps);
info->offset = VFIO_PCI_INDEX_TO_OFFSET(info->index);
info->size = virtvdev->bar0_virtual_buf_size;
info->flags = VFIO_REGION_INFO_FLAG_READ | VFIO_REGION_INFO_FLAG_WRITE;
return 0;
}
static int virtiovf_set_notify_addr(struct virtiovf_pci_core_device *virtvdev)
{
struct vfio_pci_core_device *core_device = &virtvdev->core_device;
void __iomem *io;
/*
* Setup the BAR where the 'notify' exists to be used by vfio as well
* This will let us mmap it only once and use it when needed.
*/
io = vfio_pci_core_get_iomap(core_device,
virtvdev->notify_bar);
if (IS_ERR(io))
return PTR_ERR(io);
virtvdev->notify_addr = io + virtvdev->notify_offset;
return 0;
}
int virtiovf_open_legacy_io(struct virtiovf_pci_core_device *virtvdev)
{
if (!virtvdev->bar0_virtual_buf)
return 0;
/*
* Upon close_device() the vfio_pci_core_disable() is called
* and will close all the previous mmaps, so it seems that the
* valid life cycle for the 'notify' addr is per open/close.
*/
return virtiovf_set_notify_addr(virtvdev);
}
static int virtiovf_get_device_config_size(unsigned short device)
{
/* Network card */
return offsetofend(struct virtio_net_config, status);
}
static int virtiovf_read_notify_info(struct virtiovf_pci_core_device *virtvdev)
{
u64 offset;
int ret;
u8 bar;
ret = virtio_pci_admin_legacy_io_notify_info(virtvdev->core_device.pdev,
VIRTIO_ADMIN_CMD_NOTIFY_INFO_FLAGS_OWNER_MEM,
&bar, &offset);
if (ret)
return ret;
virtvdev->notify_bar = bar;
virtvdev->notify_offset = offset;
return 0;
}
static bool virtiovf_bar0_exists(struct pci_dev *pdev)
{
struct resource *res = pdev->resource;
return res->flags;
}
bool virtiovf_support_legacy_io(struct pci_dev *pdev)
{
/* For now, the legacy IO functionality is supported only for virtio-net */
return pdev->device == 0x1041 && virtio_pci_admin_has_legacy_io(pdev) &&
!virtiovf_bar0_exists(pdev);
}
int virtiovf_init_legacy_io(struct virtiovf_pci_core_device *virtvdev)
{
struct pci_dev *pdev = virtvdev->core_device.pdev;
int ret;
ret = virtiovf_read_notify_info(virtvdev);
if (ret)
return ret;
virtvdev->bar0_virtual_buf_size = VIRTIO_PCI_CONFIG_OFF(true) +
virtiovf_get_device_config_size(pdev->device);
BUILD_BUG_ON(!is_power_of_2(virtvdev->bar0_virtual_buf_size));
virtvdev->bar0_virtual_buf = kzalloc(virtvdev->bar0_virtual_buf_size,
GFP_KERNEL);
if (!virtvdev->bar0_virtual_buf)
return -ENOMEM;
mutex_init(&virtvdev->bar_mutex);
return 0;
}
void virtiovf_release_legacy_io(struct virtiovf_pci_core_device *virtvdev)
{
kfree(virtvdev->bar0_virtual_buf);
}
void virtiovf_legacy_io_reset_done(struct pci_dev *pdev)
{
struct virtiovf_pci_core_device *virtvdev = dev_get_drvdata(&pdev->dev);
virtvdev->pci_cmd = 0;
}
+231
View File
@@ -0,0 +1,231 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/vfio.h>
#include <linux/vfio_pci_core.h>
#include <linux/virtio_pci.h>
#include <linux/virtio_net.h>
#include <linux/virtio_pci_admin.h>
#include "common.h"
static int virtiovf_pci_open_device(struct vfio_device *core_vdev)
{
struct virtiovf_pci_core_device *virtvdev = container_of(core_vdev,
struct virtiovf_pci_core_device, core_device.vdev);
struct vfio_pci_core_device *vdev = &virtvdev->core_device;
int ret;
ret = vfio_pci_core_enable(vdev);
if (ret)
return ret;
#ifdef CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY
ret = virtiovf_open_legacy_io(virtvdev);
if (ret) {
vfio_pci_core_disable(vdev);
return ret;
}
#endif
virtiovf_open_migration(virtvdev);
vfio_pci_core_finish_enable(vdev);
return 0;
}
static void virtiovf_pci_close_device(struct vfio_device *core_vdev)
{
struct virtiovf_pci_core_device *virtvdev = container_of(core_vdev,
struct virtiovf_pci_core_device, core_device.vdev);
virtiovf_close_migration(virtvdev);
vfio_pci_core_close_device(core_vdev);
}
#ifdef CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY
static int virtiovf_pci_init_device(struct vfio_device *core_vdev)
{
struct virtiovf_pci_core_device *virtvdev = container_of(core_vdev,
struct virtiovf_pci_core_device, core_device.vdev);
int ret;
ret = vfio_pci_core_init_dev(core_vdev);
if (ret)
return ret;
/*
* The vfio_device_ops.init() callback is set to virtiovf_pci_init_device()
* only when legacy I/O is supported. Now, let's initialize it.
*/
return virtiovf_init_legacy_io(virtvdev);
}
#endif
static void virtiovf_pci_core_release_dev(struct vfio_device *core_vdev)
{
#ifdef CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY
struct virtiovf_pci_core_device *virtvdev = container_of(core_vdev,
struct virtiovf_pci_core_device, core_device.vdev);
virtiovf_release_legacy_io(virtvdev);
#endif
vfio_pci_core_release_dev(core_vdev);
}
static const struct vfio_device_ops virtiovf_vfio_pci_lm_ops = {
.name = "virtio-vfio-pci-lm",
.init = vfio_pci_core_init_dev,
.release = virtiovf_pci_core_release_dev,
.open_device = virtiovf_pci_open_device,
.close_device = virtiovf_pci_close_device,
.ioctl = vfio_pci_core_ioctl,
.get_region_info_caps = vfio_pci_ioctl_get_region_info,
.device_feature = vfio_pci_core_ioctl_feature,
.read = vfio_pci_core_read,
.write = vfio_pci_core_write,
.mmap = vfio_pci_core_mmap,
.request = vfio_pci_core_request,
.match = vfio_pci_core_match,
.match_token_uuid = vfio_pci_core_match_token_uuid,
.bind_iommufd = vfio_iommufd_physical_bind,
.unbind_iommufd = vfio_iommufd_physical_unbind,
.attach_ioas = vfio_iommufd_physical_attach_ioas,
.detach_ioas = vfio_iommufd_physical_detach_ioas,
};
#ifdef CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY
static const struct vfio_device_ops virtiovf_vfio_pci_tran_lm_ops = {
.name = "virtio-vfio-pci-trans-lm",
.init = virtiovf_pci_init_device,
.release = virtiovf_pci_core_release_dev,
.open_device = virtiovf_pci_open_device,
.close_device = virtiovf_pci_close_device,
.ioctl = vfio_pci_core_ioctl,
.get_region_info_caps = virtiovf_pci_ioctl_get_region_info,
.device_feature = vfio_pci_core_ioctl_feature,
.read = virtiovf_pci_core_read,
.write = virtiovf_pci_core_write,
.mmap = vfio_pci_core_mmap,
.request = vfio_pci_core_request,
.match = vfio_pci_core_match,
.match_token_uuid = vfio_pci_core_match_token_uuid,
.bind_iommufd = vfio_iommufd_physical_bind,
.unbind_iommufd = vfio_iommufd_physical_unbind,
.attach_ioas = vfio_iommufd_physical_attach_ioas,
.detach_ioas = vfio_iommufd_physical_detach_ioas,
};
#endif
static const struct vfio_device_ops virtiovf_vfio_pci_ops = {
.name = "virtio-vfio-pci",
.init = vfio_pci_core_init_dev,
.release = vfio_pci_core_release_dev,
.open_device = virtiovf_pci_open_device,
.close_device = vfio_pci_core_close_device,
.ioctl = vfio_pci_core_ioctl,
.get_region_info_caps = vfio_pci_ioctl_get_region_info,
.device_feature = vfio_pci_core_ioctl_feature,
.read = vfio_pci_core_read,
.write = vfio_pci_core_write,
.mmap = vfio_pci_core_mmap,
.request = vfio_pci_core_request,
.match = vfio_pci_core_match,
.match_token_uuid = vfio_pci_core_match_token_uuid,
.bind_iommufd = vfio_iommufd_physical_bind,
.unbind_iommufd = vfio_iommufd_physical_unbind,
.attach_ioas = vfio_iommufd_physical_attach_ioas,
.detach_ioas = vfio_iommufd_physical_detach_ioas,
};
static int virtiovf_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
const struct vfio_device_ops *ops = &virtiovf_vfio_pci_ops;
struct virtiovf_pci_core_device *virtvdev;
bool sup_legacy_io = false;
bool sup_lm = false;
int ret;
if (pdev->is_virtfn) {
#ifdef CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY
sup_legacy_io = virtiovf_support_legacy_io(pdev);
if (sup_legacy_io)
ops = &virtiovf_vfio_pci_tran_lm_ops;
#endif
sup_lm = virtio_pci_admin_has_dev_parts(pdev);
if (sup_lm && !sup_legacy_io)
ops = &virtiovf_vfio_pci_lm_ops;
}
virtvdev = vfio_alloc_device(virtiovf_pci_core_device, core_device.vdev,
&pdev->dev, ops);
if (IS_ERR(virtvdev))
return PTR_ERR(virtvdev);
if (sup_lm)
virtiovf_set_migratable(virtvdev);
dev_set_drvdata(&pdev->dev, &virtvdev->core_device);
ret = vfio_pci_core_register_device(&virtvdev->core_device);
if (ret)
goto out;
return 0;
out:
vfio_put_device(&virtvdev->core_device.vdev);
return ret;
}
static void virtiovf_pci_remove(struct pci_dev *pdev)
{
struct virtiovf_pci_core_device *virtvdev = dev_get_drvdata(&pdev->dev);
vfio_pci_core_unregister_device(&virtvdev->core_device);
vfio_put_device(&virtvdev->core_device.vdev);
}
static const struct pci_device_id virtiovf_pci_table[] = {
/* Only virtio-net and virtio-block are supported/tested so far */
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_REDHAT_QUMRANET, 0x1041) },
{ PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_REDHAT_QUMRANET, 0x1042) },
{}
};
MODULE_DEVICE_TABLE(pci, virtiovf_pci_table);
static void virtiovf_pci_aer_reset_done(struct pci_dev *pdev)
{
#ifdef CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY
virtiovf_legacy_io_reset_done(pdev);
#endif
virtiovf_migration_reset_done(pdev);
}
static const struct pci_error_handlers virtiovf_err_handlers = {
.reset_done = virtiovf_pci_aer_reset_done,
.error_detected = vfio_pci_core_aer_err_detected,
};
static struct pci_driver virtiovf_pci_driver = {
.name = KBUILD_MODNAME,
.id_table = virtiovf_pci_table,
.probe = virtiovf_pci_probe,
.remove = virtiovf_pci_remove,
.err_handler = &virtiovf_err_handlers,
.driver_managed_dma = true,
};
module_pci_driver(virtiovf_pci_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yishai Hadas <yishaih@nvidia.com>");
MODULE_DESCRIPTION(
"VIRTIO VFIO PCI - User Level meta-driver for VIRTIO NET and BLOCK devices");
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
config XE_VFIO_PCI
tristate "VFIO support for Intel Graphics"
depends on DRM_XE && PCI_IOV
select VFIO_PCI_CORE
help
This option enables device specific VFIO driver variant for Intel Graphics.
In addition to generic VFIO PCI functionality, it implements VFIO
migration uAPI allowing userspace to enable migration for
Intel Graphics SR-IOV Virtual Functions supported by the Xe driver.
If you don't know what to do here, say N.
+3
View File
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_XE_VFIO_PCI) += xe-vfio-pci.o
xe-vfio-pci-y := main.o
+596
View File
@@ -0,0 +1,596 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright © 2025 Intel Corporation
*/
#include <linux/anon_inodes.h>
#include <linux/delay.h>
#include <linux/file.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include <linux/vfio.h>
#include <linux/vfio_pci_core.h>
#include <drm/intel/xe_sriov_vfio.h>
#include <drm/intel/pciids.h>
struct xe_vfio_pci_migration_file {
struct file *filp;
/* serializes accesses to migration data */
struct mutex lock;
struct xe_vfio_pci_core_device *xe_vdev;
u8 disabled:1;
};
struct xe_vfio_pci_core_device {
struct vfio_pci_core_device core_device;
struct xe_device *xe;
/* PF internal control uses vfid index starting from 1 */
unsigned int vfid;
u8 deferred_reset:1;
/* protects migration state */
struct mutex state_mutex;
enum vfio_device_mig_state mig_state;
/* protects the reset_done flow */
spinlock_t reset_lock;
struct xe_vfio_pci_migration_file *migf;
};
#define xe_vdev_to_dev(xe_vdev) (&(xe_vdev)->core_device.pdev->dev)
static void xe_vfio_pci_disable_file(struct xe_vfio_pci_migration_file *migf)
{
mutex_lock(&migf->lock);
migf->disabled = true;
mutex_unlock(&migf->lock);
}
static void xe_vfio_pci_put_file(struct xe_vfio_pci_core_device *xe_vdev)
{
xe_vfio_pci_disable_file(xe_vdev->migf);
fput(xe_vdev->migf->filp);
xe_vdev->migf = NULL;
}
static void xe_vfio_pci_reset(struct xe_vfio_pci_core_device *xe_vdev)
{
if (xe_vdev->migf)
xe_vfio_pci_put_file(xe_vdev);
xe_vdev->mig_state = VFIO_DEVICE_STATE_RUNNING;
}
static void xe_vfio_pci_state_mutex_lock(struct xe_vfio_pci_core_device *xe_vdev)
{
mutex_lock(&xe_vdev->state_mutex);
}
/*
* This function is called in all state_mutex unlock cases to
* handle a 'deferred_reset' if exists.
*/
static void xe_vfio_pci_state_mutex_unlock(struct xe_vfio_pci_core_device *xe_vdev)
{
again:
spin_lock(&xe_vdev->reset_lock);
if (xe_vdev->deferred_reset) {
xe_vdev->deferred_reset = false;
spin_unlock(&xe_vdev->reset_lock);
xe_vfio_pci_reset(xe_vdev);
goto again;
}
mutex_unlock(&xe_vdev->state_mutex);
spin_unlock(&xe_vdev->reset_lock);
}
static void xe_vfio_pci_reset_prepare(struct pci_dev *pdev)
{
struct xe_vfio_pci_core_device *xe_vdev = pci_get_drvdata(pdev);
int ret;
if (!pdev->is_virtfn)
return;
ret = xe_sriov_vfio_flr_prepare(xe_vdev->xe, xe_vdev->vfid);
if (ret)
dev_err(&pdev->dev, "Failed to prepare FLR: %d\n", ret);
}
static void xe_vfio_pci_reset_done(struct pci_dev *pdev)
{
struct xe_vfio_pci_core_device *xe_vdev = pci_get_drvdata(pdev);
int ret;
if (!pdev->is_virtfn)
return;
/*
* VF FLR requires additional processing done by PF driver.
* The processing is done after FLR is already finished from PCIe
* perspective.
* In order to avoid a scenario where VF is used while PF processing
* is still in progress, additional synchronization point is needed.
*/
ret = xe_sriov_vfio_wait_flr_done(xe_vdev->xe, xe_vdev->vfid);
if (ret)
dev_err(&pdev->dev, "Failed to wait for FLR: %d\n", ret);
if (!xe_vdev->vfid)
return;
/*
* As the higher VFIO layers are holding locks across reset and using
* those same locks with the mm_lock we need to prevent ABBA deadlock
* with the state_mutex and mm_lock.
* In case the state_mutex was taken already we defer the cleanup work
* to the unlock flow of the other running context.
*/
spin_lock(&xe_vdev->reset_lock);
xe_vdev->deferred_reset = true;
if (!mutex_trylock(&xe_vdev->state_mutex)) {
spin_unlock(&xe_vdev->reset_lock);
return;
}
spin_unlock(&xe_vdev->reset_lock);
xe_vfio_pci_state_mutex_unlock(xe_vdev);
}
static const struct pci_error_handlers xe_vfio_pci_err_handlers = {
.reset_prepare = xe_vfio_pci_reset_prepare,
.reset_done = xe_vfio_pci_reset_done,
.error_detected = vfio_pci_core_aer_err_detected,
};
static int xe_vfio_pci_open_device(struct vfio_device *core_vdev)
{
struct xe_vfio_pci_core_device *xe_vdev =
container_of(core_vdev, struct xe_vfio_pci_core_device, core_device.vdev);
struct vfio_pci_core_device *vdev = &xe_vdev->core_device;
int ret;
ret = vfio_pci_core_enable(vdev);
if (ret)
return ret;
xe_vdev->mig_state = VFIO_DEVICE_STATE_RUNNING;
vfio_pci_core_finish_enable(vdev);
return 0;
}
static void xe_vfio_pci_close_device(struct vfio_device *core_vdev)
{
struct xe_vfio_pci_core_device *xe_vdev =
container_of(core_vdev, struct xe_vfio_pci_core_device, core_device.vdev);
xe_vfio_pci_state_mutex_lock(xe_vdev);
xe_vfio_pci_reset(xe_vdev);
xe_vfio_pci_state_mutex_unlock(xe_vdev);
vfio_pci_core_close_device(core_vdev);
}
static int xe_vfio_pci_release_file(struct inode *inode, struct file *filp)
{
struct xe_vfio_pci_migration_file *migf = filp->private_data;
mutex_destroy(&migf->lock);
kfree(migf);
return 0;
}
static ssize_t xe_vfio_pci_save_read(struct file *filp, char __user *buf, size_t len, loff_t *pos)
{
struct xe_vfio_pci_migration_file *migf = filp->private_data;
ssize_t ret;
if (pos)
return -ESPIPE;
mutex_lock(&migf->lock);
if (migf->disabled) {
mutex_unlock(&migf->lock);
return -ENODEV;
}
ret = xe_sriov_vfio_data_read(migf->xe_vdev->xe, migf->xe_vdev->vfid, buf, len);
mutex_unlock(&migf->lock);
return ret;
}
static const struct file_operations xe_vfio_pci_save_fops = {
.owner = THIS_MODULE,
.read = xe_vfio_pci_save_read,
.release = xe_vfio_pci_release_file,
.llseek = noop_llseek,
};
static ssize_t xe_vfio_pci_resume_write(struct file *filp, const char __user *buf,
size_t len, loff_t *pos)
{
struct xe_vfio_pci_migration_file *migf = filp->private_data;
ssize_t ret;
if (pos)
return -ESPIPE;
mutex_lock(&migf->lock);
if (migf->disabled) {
mutex_unlock(&migf->lock);
return -ENODEV;
}
ret = xe_sriov_vfio_data_write(migf->xe_vdev->xe, migf->xe_vdev->vfid, buf, len);
mutex_unlock(&migf->lock);
return ret;
}
static const struct file_operations xe_vfio_pci_resume_fops = {
.owner = THIS_MODULE,
.write = xe_vfio_pci_resume_write,
.release = xe_vfio_pci_release_file,
.llseek = noop_llseek,
};
static const char *vfio_dev_state_str(u32 state)
{
switch (state) {
case VFIO_DEVICE_STATE_RUNNING: return "running";
case VFIO_DEVICE_STATE_RUNNING_P2P: return "running_p2p";
case VFIO_DEVICE_STATE_STOP_COPY: return "stopcopy";
case VFIO_DEVICE_STATE_STOP: return "stop";
case VFIO_DEVICE_STATE_RESUMING: return "resuming";
case VFIO_DEVICE_STATE_ERROR: return "error";
default: return "";
}
}
enum xe_vfio_pci_file_type {
XE_VFIO_FILE_SAVE = 0,
XE_VFIO_FILE_RESUME,
};
static struct xe_vfio_pci_migration_file *
xe_vfio_pci_alloc_file(struct xe_vfio_pci_core_device *xe_vdev,
enum xe_vfio_pci_file_type type)
{
struct xe_vfio_pci_migration_file *migf;
const struct file_operations *fops;
int flags;
int ret;
migf = kzalloc_obj(*migf, GFP_KERNEL_ACCOUNT);
if (!migf)
return ERR_PTR(-ENOMEM);
fops = type == XE_VFIO_FILE_SAVE ? &xe_vfio_pci_save_fops : &xe_vfio_pci_resume_fops;
flags = type == XE_VFIO_FILE_SAVE ? O_RDONLY : O_WRONLY;
migf->filp = anon_inode_getfile("xe_vfio_mig", fops, migf, flags);
if (IS_ERR(migf->filp)) {
ret = PTR_ERR(migf->filp);
kfree(migf);
return ERR_PTR(ret);
}
mutex_init(&migf->lock);
migf->xe_vdev = xe_vdev;
xe_vdev->migf = migf;
stream_open(migf->filp->f_inode, migf->filp);
return migf;
}
static struct file *
xe_vfio_set_state(struct xe_vfio_pci_core_device *xe_vdev, u32 new)
{
u32 cur = xe_vdev->mig_state;
int ret;
dev_dbg(xe_vdev_to_dev(xe_vdev),
"state: %s->%s\n", vfio_dev_state_str(cur), vfio_dev_state_str(new));
/*
* "STOP" handling is reused for "RUNNING_P2P", as the device doesn't
* have the capability to selectively block outgoing p2p DMA transfers.
* While the device is allowing BAR accesses when the VF is stopped, it
* is not processing any new workload requests, effectively stopping
* any outgoing DMA transfers (not just p2p).
* Any VRAM / MMIO accesses occurring during "RUNNING_P2P" are kept and
* will be migrated to target VF during stop-copy.
*/
if (cur == VFIO_DEVICE_STATE_RUNNING && new == VFIO_DEVICE_STATE_RUNNING_P2P) {
ret = xe_sriov_vfio_suspend_device(xe_vdev->xe, xe_vdev->vfid);
if (ret)
goto err;
return NULL;
}
if ((cur == VFIO_DEVICE_STATE_RUNNING_P2P && new == VFIO_DEVICE_STATE_STOP) ||
(cur == VFIO_DEVICE_STATE_STOP && new == VFIO_DEVICE_STATE_RUNNING_P2P))
return NULL;
if (cur == VFIO_DEVICE_STATE_RUNNING_P2P && new == VFIO_DEVICE_STATE_RUNNING) {
ret = xe_sriov_vfio_resume_device(xe_vdev->xe, xe_vdev->vfid);
if (ret)
goto err;
return NULL;
}
if (cur == VFIO_DEVICE_STATE_STOP && new == VFIO_DEVICE_STATE_STOP_COPY) {
struct xe_vfio_pci_migration_file *migf;
migf = xe_vfio_pci_alloc_file(xe_vdev, XE_VFIO_FILE_SAVE);
if (IS_ERR(migf)) {
ret = PTR_ERR(migf);
goto err;
}
get_file(migf->filp);
ret = xe_sriov_vfio_stop_copy_enter(xe_vdev->xe, xe_vdev->vfid);
if (ret) {
fput(migf->filp);
goto err;
}
return migf->filp;
}
if (cur == VFIO_DEVICE_STATE_STOP_COPY && new == VFIO_DEVICE_STATE_STOP) {
if (xe_vdev->migf)
xe_vfio_pci_put_file(xe_vdev);
ret = xe_sriov_vfio_stop_copy_exit(xe_vdev->xe, xe_vdev->vfid);
if (ret)
goto err;
return NULL;
}
if (cur == VFIO_DEVICE_STATE_STOP && new == VFIO_DEVICE_STATE_RESUMING) {
struct xe_vfio_pci_migration_file *migf;
migf = xe_vfio_pci_alloc_file(xe_vdev, XE_VFIO_FILE_RESUME);
if (IS_ERR(migf)) {
ret = PTR_ERR(migf);
goto err;
}
get_file(migf->filp);
ret = xe_sriov_vfio_resume_data_enter(xe_vdev->xe, xe_vdev->vfid);
if (ret) {
fput(migf->filp);
goto err;
}
return migf->filp;
}
if (cur == VFIO_DEVICE_STATE_RESUMING && new == VFIO_DEVICE_STATE_STOP) {
if (xe_vdev->migf)
xe_vfio_pci_put_file(xe_vdev);
ret = xe_sriov_vfio_resume_data_exit(xe_vdev->xe, xe_vdev->vfid);
if (ret)
goto err;
return NULL;
}
WARN(true, "Unknown state transition %d->%d", cur, new);
return ERR_PTR(-EINVAL);
err:
dev_dbg(xe_vdev_to_dev(xe_vdev),
"Failed to transition state: %s->%s err=%d\n",
vfio_dev_state_str(cur), vfio_dev_state_str(new), ret);
return ERR_PTR(ret);
}
static struct file *
xe_vfio_pci_set_device_state(struct vfio_device *core_vdev,
enum vfio_device_mig_state new_state)
{
struct xe_vfio_pci_core_device *xe_vdev =
container_of(core_vdev, struct xe_vfio_pci_core_device, core_device.vdev);
enum vfio_device_mig_state next_state;
struct file *f = NULL;
int ret;
xe_vfio_pci_state_mutex_lock(xe_vdev);
while (new_state != xe_vdev->mig_state) {
ret = vfio_mig_get_next_state(core_vdev, xe_vdev->mig_state,
new_state, &next_state);
if (ret) {
xe_sriov_vfio_error(xe_vdev->xe, xe_vdev->vfid);
f = ERR_PTR(ret);
break;
}
f = xe_vfio_set_state(xe_vdev, next_state);
if (IS_ERR(f))
break;
xe_vdev->mig_state = next_state;
/* Multiple state transitions with non-NULL file in the middle */
if (f && new_state != xe_vdev->mig_state) {
fput(f);
f = ERR_PTR(-EINVAL);
break;
}
}
xe_vfio_pci_state_mutex_unlock(xe_vdev);
return f;
}
static int xe_vfio_pci_get_device_state(struct vfio_device *core_vdev,
enum vfio_device_mig_state *curr_state)
{
struct xe_vfio_pci_core_device *xe_vdev =
container_of(core_vdev, struct xe_vfio_pci_core_device, core_device.vdev);
xe_vfio_pci_state_mutex_lock(xe_vdev);
*curr_state = xe_vdev->mig_state;
xe_vfio_pci_state_mutex_unlock(xe_vdev);
return 0;
}
static int xe_vfio_pci_get_data_size(struct vfio_device *vdev,
unsigned long *stop_copy_length)
{
struct xe_vfio_pci_core_device *xe_vdev =
container_of(vdev, struct xe_vfio_pci_core_device, core_device.vdev);
xe_vfio_pci_state_mutex_lock(xe_vdev);
*stop_copy_length = xe_sriov_vfio_stop_copy_size(xe_vdev->xe, xe_vdev->vfid);
xe_vfio_pci_state_mutex_unlock(xe_vdev);
return 0;
}
static const struct vfio_migration_ops xe_vfio_pci_migration_ops = {
.migration_set_state = xe_vfio_pci_set_device_state,
.migration_get_state = xe_vfio_pci_get_device_state,
.migration_get_data_size = xe_vfio_pci_get_data_size,
};
static void xe_vfio_pci_migration_init(struct xe_vfio_pci_core_device *xe_vdev)
{
struct vfio_device *core_vdev = &xe_vdev->core_device.vdev;
if (!xe_sriov_vfio_migration_supported(xe_vdev->xe))
return;
core_vdev->migration_flags = VFIO_MIGRATION_STOP_COPY | VFIO_MIGRATION_P2P;
core_vdev->mig_ops = &xe_vfio_pci_migration_ops;
}
static int xe_vfio_pci_vf_init(struct xe_vfio_pci_core_device *xe_vdev)
{
struct vfio_device *core_vdev = &xe_vdev->core_device.vdev;
struct pci_dev *pdev = to_pci_dev(core_vdev->dev);
struct xe_device *xe = xe_sriov_vfio_get_pf(pdev);
if (!pdev->is_virtfn)
return 0;
if (!xe)
return -ENODEV;
xe_vdev->xe = xe;
/* PF internal control uses vfid index starting from 1 */
xe_vdev->vfid = pci_iov_vf_id(pdev) + 1;
xe_vfio_pci_migration_init(xe_vdev);
return 0;
}
static int xe_vfio_pci_init_dev(struct vfio_device *core_vdev)
{
struct xe_vfio_pci_core_device *xe_vdev =
container_of(core_vdev, struct xe_vfio_pci_core_device, core_device.vdev);
int ret;
mutex_init(&xe_vdev->state_mutex);
spin_lock_init(&xe_vdev->reset_lock);
ret = xe_vfio_pci_vf_init(xe_vdev);
if (ret)
return ret;
return vfio_pci_core_init_dev(core_vdev);
}
static void xe_vfio_pci_release_dev(struct vfio_device *core_vdev)
{
struct xe_vfio_pci_core_device *xe_vdev =
container_of(core_vdev, struct xe_vfio_pci_core_device, core_device.vdev);
mutex_destroy(&xe_vdev->state_mutex);
vfio_pci_core_release_dev(core_vdev);
}
static const struct vfio_device_ops xe_vfio_pci_ops = {
.name = "xe-vfio-pci",
.init = xe_vfio_pci_init_dev,
.release = xe_vfio_pci_release_dev,
.open_device = xe_vfio_pci_open_device,
.close_device = xe_vfio_pci_close_device,
.ioctl = vfio_pci_core_ioctl,
.get_region_info_caps = vfio_pci_ioctl_get_region_info,
.device_feature = vfio_pci_core_ioctl_feature,
.read = vfio_pci_core_read,
.write = vfio_pci_core_write,
.mmap = vfio_pci_core_mmap,
.request = vfio_pci_core_request,
.match = vfio_pci_core_match,
.match_token_uuid = vfio_pci_core_match_token_uuid,
.bind_iommufd = vfio_iommufd_physical_bind,
.unbind_iommufd = vfio_iommufd_physical_unbind,
.attach_ioas = vfio_iommufd_physical_attach_ioas,
.detach_ioas = vfio_iommufd_physical_detach_ioas,
};
static int xe_vfio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct xe_vfio_pci_core_device *xe_vdev;
int ret;
xe_vdev = vfio_alloc_device(xe_vfio_pci_core_device, core_device.vdev, &pdev->dev,
&xe_vfio_pci_ops);
if (IS_ERR(xe_vdev))
return PTR_ERR(xe_vdev);
dev_set_drvdata(&pdev->dev, &xe_vdev->core_device);
ret = vfio_pci_core_register_device(&xe_vdev->core_device);
if (ret) {
vfio_put_device(&xe_vdev->core_device.vdev);
return ret;
}
return 0;
}
static void xe_vfio_pci_remove(struct pci_dev *pdev)
{
struct xe_vfio_pci_core_device *xe_vdev = pci_get_drvdata(pdev);
vfio_pci_core_unregister_device(&xe_vdev->core_device);
vfio_put_device(&xe_vdev->core_device.vdev);
}
#define INTEL_PCI_VFIO_DEVICE(_id) { \
PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_INTEL, (_id)) \
}
static const struct pci_device_id xe_vfio_pci_table[] = {
INTEL_PTL_IDS(INTEL_PCI_VFIO_DEVICE),
INTEL_WCL_IDS(INTEL_PCI_VFIO_DEVICE),
INTEL_BMG_IDS(INTEL_PCI_VFIO_DEVICE),
{}
};
MODULE_DEVICE_TABLE(pci, xe_vfio_pci_table);
static struct pci_driver xe_vfio_pci_driver = {
.name = "xe-vfio-pci",
.id_table = xe_vfio_pci_table,
.probe = xe_vfio_pci_probe,
.remove = xe_vfio_pci_remove,
.err_handler = &xe_vfio_pci_err_handlers,
.driver_managed_dma = true,
};
module_pci_driver(xe_vfio_pci_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Michał Winiarski <michal.winiarski@intel.com>");
MODULE_DESCRIPTION("VFIO PCI driver with migration support for Intel Graphics");