Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306// 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){ 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;
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_safe(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; }
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);}