Commit 40bcdb12 authored by Yishai Hadas's avatar Yishai Hadas Committed by Alex Williamson
Browse files

vfio/virtio: Enable live migration once VIRTIO_PCI was configured



Now that the driver supports live migration, only the legacy IO
functionality depends on config VIRTIO_PCI_ADMIN_LEGACY.

As part of that we introduce a bool configuration option as a sub menu
under the driver's main live migration feature named
VIRTIO_VFIO_PCI_ADMIN_LEGACY, to control the legacy IO functionality.

This will let users configuring the kernel, know which features from the
description might be available in the resulting driver.

As of that, move the legacy IO into a separate file to be compiled only
once CONFIG_VIRTIO_VFIO_PCI_ADMIN_LEGACY was configured and let the live
migration depends only on VIRTIO_PCI.

Signed-off-by: default avatarYishai Hadas <yishaih@nvidia.com>
Link: https://lore.kernel.org/r/20241113115200.209269-8-yishaih@nvidia.com


Signed-off-by: default avatarAlex Williamson <alex.williamson@redhat.com>
parent 6cea64b1
Loading
Loading
Loading
Loading
+29 −13
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0-only
config VIRTIO_VFIO_PCI
        tristate "VFIO support for VIRTIO NET PCI devices"
        depends on VIRTIO_PCI && VIRTIO_PCI_ADMIN_LEGACY
	tristate "VFIO support for VIRTIO NET PCI VF devices"
	depends on VIRTIO_PCI
	select VFIO_PCI_CORE
	help
          This provides support for exposing VIRTIO NET VF devices which support
          legacy IO access, using the VFIO framework that can work with a legacy
          virtio driver in the guest.
          Based on PCIe spec, VFs do not support I/O Space.
          As of that this driver emulates I/O BAR in software to let a VF be
          seen as a transitional device by its users and let it work with
          a legacy driver.
	  This provides migration support for VIRTIO NET 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.
+1 −0
Original line number Diff line number Diff line
# 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
+19 −0
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ struct virtiovf_migration_file {

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;
@@ -87,6 +88,7 @@ struct virtiovf_pci_core_device {
	__le16 pci_cmd;
	u8 bar0_virtual_buf_size;
	u8 notify_bar;
#endif

	/* LM related */
	u8 migrate_cap:1;
@@ -105,4 +107,21 @@ 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);
long virtiovf_vfio_pci_core_ioctl(struct vfio_device *core_vdev,
				  unsigned int cmd, unsigned long arg);
int virtiovf_pci_ioctl_get_region_info(struct vfio_device *core_vdev,
				       unsigned int cmd, unsigned long arg);
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 */
+418 −0
Original line number Diff line number Diff line
// 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);
	mutex_lock(&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)
			goto out;
		if (copy_to_user(buf, bar0_buf + pos, count))
			ret = -EFAULT;
	} else {
		if (copy_from_user(bar0_buf + pos, buf, count)) {
			ret = -EFAULT;
			goto out;
		}

		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);
	}
out:
	mutex_unlock(&virtvdev->bar_mutex);
	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,
				       unsigned int cmd, unsigned long arg)
{
	struct virtiovf_pci_core_device *virtvdev = container_of(
		core_vdev, struct virtiovf_pci_core_device, core_device.vdev);
	unsigned long minsz = offsetofend(struct vfio_region_info, offset);
	void __user *uarg = (void __user *)arg;
	struct vfio_region_info info = {};

	if (copy_from_user(&info, uarg, minsz))
		return -EFAULT;

	if (info.argsz < minsz)
		return -EINVAL;

	switch (info.index) {
	case VFIO_PCI_BAR0_REGION_INDEX:
		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 copy_to_user(uarg, &info, minsz) ? -EFAULT : 0;
	default:
		return vfio_pci_core_ioctl(core_vdev, cmd, arg);
	}
}

long virtiovf_vfio_pci_core_ioctl(struct vfio_device *core_vdev, unsigned int cmd,
				  unsigned long arg)
{
	switch (cmd) {
	case VFIO_DEVICE_GET_REGION_INFO:
		return virtiovf_pci_ioctl_get_region_info(core_vdev, cmd, arg);
	default:
		return vfio_pci_core_ioctl(core_vdev, cmd, arg);
	}
}

static int virtiovf_set_notify_addr(struct virtiovf_pci_core_device *virtvdev)
{
	struct vfio_pci_core_device *core_device = &virtvdev->core_device;
	int ret;

	/*
	 * 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.
	 */
	ret = vfio_pci_core_setup_barmap(core_device,
					 virtvdev->notify_bar);
	if (ret)
		return ret;

	virtvdev->notify_addr = core_device->barmap[virtvdev->notify_bar] +
			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)
{
	return 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;
}
+22 −388

File changed.

Preview size limit exceeded, changes collapsed.