mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-10-26 10:57:38 +08:00
Pull VFIO updates from Alex Williamson: - Fix missing selftest <libgen.h> include for basename() to support non-glibc builds (Hisam Mehboob) - Add vfio selftest documentation covering the libvfio helper library and guidance for developers to aid in adding and understanding the framework (David Matlack) - Fix vfio cdev open path to prevent a second open from clobbering the vf_token or clearing the KVM reference. Add a selftest to avoid future recurrences (Alex Williamson) - Extend vfio selftest mix_and_match timeouts, in particular to account for systems where generated faults can induce heavy SMM load, delaying internal timeouts. Also extend timeout for execution via runner to account for various tests exceeding default 45s timeout (Alex Williamson) - Add tracking for non-reserved mappings into a vfio_dma object in order to optimize the tear-down paths where per-page tests can be entirely skipped. This resolves a soft lockup for large memory GPUs (Samuel Crossley) - Add vfio selftest driver for mlx5, providing another target for exercising DMA and interrupts against a physical device (Jason Gunthorpe) - Hardening against read-only region promotion, preserving vm_pgoff for unmap_mapping_range(), and reject non-shared MMIO mmaps as necessary through vfio-platform, vfio-fsl-mc, and vfio-cdx. These prevent future issues, though none are currently reachable (Abdifatah Suruur) - Fix 8-byte ioeventfd support, removing a static error test that was erroneously left in place when generalizing 8-byte io accesses (Li RongQing) - Fix missed RCU drain on module exit (Jiale Yao) - Fix off-by-one boundary check in migration support within the mlx5 vfio-pci variant driver (Seongjun Hong) - Remove drvdata usage convention across vfio-pci-core, allowing variant drivers to freely create their own conventions and enabling a future Rust-based variant driver. A DRM topic branch is also merged in support of this effort, modifying the vga arbiter API to more cleanly remove the drvdata dependency (Simon Song) - Fix hisi_acc vfio-pci variant driver to avoid NULL pointer accesses when bound to unsupported devices and also disabling migration support when running on 64K host page size due to BAR mapping constraints (Longfang Liu) - Add device IDs for Crescent Island and Nova Lake devices to the Xe vfio-pci variant driver (Satyanarayana K V P) - Fix fallback mode in nvgrace-gpu variant driver to use symmetric open and close handlers to avoid reported use-after-free issue. Remove the resulting unnecessary test in the now single-purpose open function (Andrea Parri) * tag 'vfio-v7.4-rc1' of https://github.com/awilliam/linux-vfio: (35 commits) vfio/nvgrace-gpu: Drop the usemem length check in open_device vfio/nvgrace-gpu: Use a core-only open_device in fallback mode vfio/xe: Add CRI and NVL device IDs hisi_acc_vfio_pci: reject live migration on 64KB page with QM_HW_V3 hardware hisi_acc_vfio_pci: fix NULL dereference in reset_prepare on PF passthrough vfio/pci: Remove the core dependency on driver data vfio/mlx5: Fix boundary check in set_report_output() vfio/pci: Drain eventfd RCU callbacks on module exit PCI/VGA: pass a data pointer to be used in the decode callback (v2) vfio/pci: Restore 8-byte ioeventfd support vfio/cdx: reject non-shared MMIO mmaps vfio/cdx: keep logical vm_pgoff in MMIO region mmap vfio/fsl-mc: keep logical vm_pgoff in MMIO region mmap vfio/platform: keep logical vm_pgoff in MMIO region mmap vfio/cdx: prevent read-only region mappings from becoming writable vfio/fsl-mc: prevent read-only region mappings from becoming writable vfio/platform: prevent read-only region mappings from becoming writable vfio: selftests: Verify a failed second open preserves the vf_token vfio: selftests: mlx5 driver - add send_msi support vfio: selftests: Add mlx5 driver - data path and memcpy ops ...
327 lines
7.8 KiB
C
327 lines
7.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2023 Intel Corporation.
|
|
*/
|
|
#include <linux/vfio.h>
|
|
#include <linux/iommufd.h>
|
|
|
|
#include "vfio.h"
|
|
|
|
static dev_t device_devt;
|
|
|
|
void vfio_init_device_cdev(struct vfio_device *device)
|
|
{
|
|
if (vfio_device_is_noiommu(device) &&
|
|
!IS_ENABLED(CONFIG_IOMMUFD_NOIOMMU))
|
|
return;
|
|
|
|
device->device.devt = MKDEV(MAJOR(device_devt), device->index);
|
|
cdev_init(&device->cdev, &vfio_device_fops);
|
|
device->cdev.owner = THIS_MODULE;
|
|
}
|
|
|
|
/*
|
|
* device access via the fd opened by this function is blocked until
|
|
* .open_device() is called successfully during BIND_IOMMUFD.
|
|
*/
|
|
int vfio_device_fops_cdev_open(struct inode *inode, struct file *filep)
|
|
{
|
|
struct vfio_device *device = container_of(inode->i_cdev,
|
|
struct vfio_device, cdev);
|
|
struct vfio_device_file *df;
|
|
int ret;
|
|
|
|
/* Paired with the put in vfio_device_fops_release() */
|
|
if (!vfio_device_try_get_registration(device))
|
|
return -ENODEV;
|
|
|
|
if (vfio_device_is_noiommu(device) && !capable(CAP_SYS_RAWIO)) {
|
|
ret = -EPERM;
|
|
goto err_put_registration;
|
|
}
|
|
|
|
df = vfio_allocate_device_file(device);
|
|
if (IS_ERR(df)) {
|
|
ret = PTR_ERR(df);
|
|
goto err_put_registration;
|
|
}
|
|
|
|
filep->private_data = df;
|
|
|
|
/*
|
|
* Use the pseudo fs inode on the device to link all mmaps
|
|
* to the same address space, allowing us to unmap all vmas
|
|
* associated to this device using unmap_mapping_range().
|
|
*/
|
|
filep->f_mapping = device->inode->i_mapping;
|
|
|
|
return 0;
|
|
|
|
err_put_registration:
|
|
vfio_device_put_registration(device);
|
|
return ret;
|
|
}
|
|
|
|
static void vfio_df_get_kvm_safe(struct vfio_device_file *df)
|
|
{
|
|
spin_lock(&df->kvm_ref_lock);
|
|
vfio_device_get_kvm(df->device, df->kvm);
|
|
spin_unlock(&df->kvm_ref_lock);
|
|
}
|
|
|
|
static int vfio_df_check_token(struct vfio_device *device,
|
|
const struct vfio_device_bind_iommufd *bind)
|
|
{
|
|
uuid_t uuid;
|
|
|
|
if (!device->ops->match_token_uuid) {
|
|
if (bind->flags & VFIO_DEVICE_BIND_FLAG_TOKEN)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
if (!(bind->flags & VFIO_DEVICE_BIND_FLAG_TOKEN))
|
|
return device->ops->match_token_uuid(device, NULL);
|
|
|
|
if (copy_from_user(&uuid, u64_to_user_ptr(bind->token_uuid_ptr),
|
|
sizeof(uuid)))
|
|
return -EFAULT;
|
|
return device->ops->match_token_uuid(device, &uuid);
|
|
}
|
|
|
|
long vfio_df_ioctl_bind_iommufd(struct vfio_device_file *df,
|
|
struct vfio_device_bind_iommufd __user *arg)
|
|
{
|
|
const u32 VALID_FLAGS = VFIO_DEVICE_BIND_FLAG_TOKEN;
|
|
struct vfio_device *device = df->device;
|
|
struct vfio_device_bind_iommufd bind;
|
|
unsigned long minsz;
|
|
u32 user_size;
|
|
int ret;
|
|
|
|
static_assert(__same_type(arg->out_devid, df->devid));
|
|
|
|
minsz = offsetofend(struct vfio_device_bind_iommufd, out_devid);
|
|
|
|
ret = get_user(user_size, &arg->argsz);
|
|
if (ret)
|
|
return ret;
|
|
if (user_size < minsz)
|
|
return -EINVAL;
|
|
ret = copy_struct_from_user(&bind, sizeof(bind), arg, user_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (bind.iommufd < 0 || bind.flags & ~VALID_FLAGS)
|
|
return -EINVAL;
|
|
|
|
/* BIND_IOMMUFD only allowed for cdev fds */
|
|
if (df->group)
|
|
return -EINVAL;
|
|
|
|
ret = vfio_device_block_group(device);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mutex_lock(&device->dev_set->lock);
|
|
/* one device cannot be bound twice */
|
|
if (df->access_granted) {
|
|
ret = -EINVAL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
/*
|
|
* The cdev path allows only a single open. Reject a second open here,
|
|
* before the VF token and device->kvm updates below would clobber the
|
|
* current opener's state on a bind that cannot complete. Return -EBUSY
|
|
* rather than -EINVAL since a delayed release of the prior opener can
|
|
* make this transient.
|
|
*/
|
|
if (device->open_count) {
|
|
ret = -EBUSY;
|
|
goto out_unlock;
|
|
}
|
|
|
|
ret = vfio_df_check_token(device, &bind);
|
|
if (ret)
|
|
goto out_unlock;
|
|
|
|
df->iommufd = iommufd_ctx_from_fd(bind.iommufd);
|
|
if (IS_ERR(df->iommufd)) {
|
|
ret = PTR_ERR(df->iommufd);
|
|
df->iommufd = NULL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
/*
|
|
* Before the device open, get the KVM pointer currently
|
|
* associated with the device file (if there is) and obtain
|
|
* a reference. This reference is held until device closed.
|
|
* Save the pointer in the device for use by drivers.
|
|
*/
|
|
vfio_df_get_kvm_safe(df);
|
|
|
|
ret = vfio_df_open(df);
|
|
if (ret)
|
|
goto out_put_kvm;
|
|
|
|
ret = copy_to_user(&arg->out_devid, &df->devid,
|
|
sizeof(df->devid)) ? -EFAULT : 0;
|
|
if (ret)
|
|
goto out_close_device;
|
|
|
|
device->cdev_opened = true;
|
|
/*
|
|
* Paired with smp_load_acquire() in vfio_device_fops::ioctl/
|
|
* read/write/mmap
|
|
*/
|
|
smp_store_release(&df->access_granted, true);
|
|
mutex_unlock(&device->dev_set->lock);
|
|
return 0;
|
|
|
|
out_close_device:
|
|
vfio_df_close(df);
|
|
out_put_kvm:
|
|
vfio_device_put_kvm(device);
|
|
iommufd_ctx_put(df->iommufd);
|
|
df->iommufd = NULL;
|
|
out_unlock:
|
|
mutex_unlock(&device->dev_set->lock);
|
|
vfio_device_unblock_group(device);
|
|
return ret;
|
|
}
|
|
|
|
void vfio_df_unbind_iommufd(struct vfio_device_file *df)
|
|
{
|
|
struct vfio_device *device = df->device;
|
|
|
|
/*
|
|
* In the time of close, there is no contention with another one
|
|
* changing this flag. So read df->access_granted without lock
|
|
* and no smp_load_acquire() is ok.
|
|
*/
|
|
if (!df->access_granted)
|
|
return;
|
|
|
|
mutex_lock(&device->dev_set->lock);
|
|
vfio_df_close(df);
|
|
vfio_device_put_kvm(device);
|
|
iommufd_ctx_put(df->iommufd);
|
|
device->cdev_opened = false;
|
|
mutex_unlock(&device->dev_set->lock);
|
|
vfio_device_unblock_group(device);
|
|
}
|
|
|
|
int vfio_df_ioctl_attach_pt(struct vfio_device_file *df,
|
|
struct vfio_device_attach_iommufd_pt __user *arg)
|
|
{
|
|
struct vfio_device_attach_iommufd_pt attach;
|
|
struct vfio_device *device = df->device;
|
|
unsigned long minsz, xend = 0;
|
|
int ret;
|
|
|
|
minsz = offsetofend(struct vfio_device_attach_iommufd_pt, pt_id);
|
|
|
|
if (copy_from_user(&attach, arg, minsz))
|
|
return -EFAULT;
|
|
|
|
if (attach.argsz < minsz)
|
|
return -EINVAL;
|
|
|
|
if (attach.flags & ~VFIO_DEVICE_ATTACH_PASID)
|
|
return -EINVAL;
|
|
|
|
if (attach.flags & VFIO_DEVICE_ATTACH_PASID) {
|
|
if (!device->ops->pasid_attach_ioas)
|
|
return -EOPNOTSUPP;
|
|
xend = offsetofend(struct vfio_device_attach_iommufd_pt, pasid);
|
|
}
|
|
|
|
if (xend) {
|
|
if (attach.argsz < xend)
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user((void *)&attach + minsz,
|
|
(void __user *)arg + minsz, xend - minsz))
|
|
return -EFAULT;
|
|
}
|
|
|
|
mutex_lock(&device->dev_set->lock);
|
|
if (attach.flags & VFIO_DEVICE_ATTACH_PASID)
|
|
ret = device->ops->pasid_attach_ioas(device,
|
|
attach.pasid,
|
|
&attach.pt_id);
|
|
else
|
|
ret = device->ops->attach_ioas(device, &attach.pt_id);
|
|
if (ret)
|
|
goto out_unlock;
|
|
|
|
if (copy_to_user(&arg->pt_id, &attach.pt_id, sizeof(attach.pt_id))) {
|
|
ret = -EFAULT;
|
|
goto out_detach;
|
|
}
|
|
mutex_unlock(&device->dev_set->lock);
|
|
|
|
return 0;
|
|
|
|
out_detach:
|
|
device->ops->detach_ioas(device);
|
|
out_unlock:
|
|
mutex_unlock(&device->dev_set->lock);
|
|
return ret;
|
|
}
|
|
|
|
int vfio_df_ioctl_detach_pt(struct vfio_device_file *df,
|
|
struct vfio_device_detach_iommufd_pt __user *arg)
|
|
{
|
|
struct vfio_device_detach_iommufd_pt detach;
|
|
struct vfio_device *device = df->device;
|
|
unsigned long minsz, xend = 0;
|
|
|
|
minsz = offsetofend(struct vfio_device_detach_iommufd_pt, flags);
|
|
|
|
if (copy_from_user(&detach, arg, minsz))
|
|
return -EFAULT;
|
|
|
|
if (detach.argsz < minsz)
|
|
return -EINVAL;
|
|
|
|
if (detach.flags & ~VFIO_DEVICE_DETACH_PASID)
|
|
return -EINVAL;
|
|
|
|
if (detach.flags & VFIO_DEVICE_DETACH_PASID) {
|
|
if (!device->ops->pasid_detach_ioas)
|
|
return -EOPNOTSUPP;
|
|
xend = offsetofend(struct vfio_device_detach_iommufd_pt, pasid);
|
|
}
|
|
|
|
if (xend) {
|
|
if (detach.argsz < xend)
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user((void *)&detach + minsz,
|
|
(void __user *)arg + minsz, xend - minsz))
|
|
return -EFAULT;
|
|
}
|
|
|
|
mutex_lock(&device->dev_set->lock);
|
|
if (detach.flags & VFIO_DEVICE_DETACH_PASID)
|
|
device->ops->pasid_detach_ioas(device, detach.pasid);
|
|
else
|
|
device->ops->detach_ioas(device);
|
|
mutex_unlock(&device->dev_set->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int vfio_cdev_init(void)
|
|
{
|
|
return alloc_chrdev_region(&device_devt, 0,
|
|
MINORMASK + 1, "vfio-dev");
|
|
}
|
|
|
|
void vfio_cdev_cleanup(void)
|
|
{
|
|
unregister_chrdev_region(device_devt, MINORMASK + 1);
|
|
}
|