xref: /linux-6.15/drivers/vfio/device_cdev.c (revision ad744ed5)
18b6f173aSYi Liu // SPDX-License-Identifier: GPL-2.0-only
28b6f173aSYi Liu /*
38b6f173aSYi Liu  * Copyright (c) 2023 Intel Corporation.
48b6f173aSYi Liu  */
58b6f173aSYi Liu #include <linux/vfio.h>
65fcc2696SYi Liu #include <linux/iommufd.h>
78b6f173aSYi Liu 
88b6f173aSYi Liu #include "vfio.h"
98b6f173aSYi Liu 
108b6f173aSYi Liu static dev_t device_devt;
118b6f173aSYi Liu 
vfio_init_device_cdev(struct vfio_device * device)128b6f173aSYi Liu void vfio_init_device_cdev(struct vfio_device *device)
138b6f173aSYi Liu {
148b6f173aSYi Liu 	device->device.devt = MKDEV(MAJOR(device_devt), device->index);
158b6f173aSYi Liu 	cdev_init(&device->cdev, &vfio_device_fops);
168b6f173aSYi Liu 	device->cdev.owner = THIS_MODULE;
178b6f173aSYi Liu }
188b6f173aSYi Liu 
198b6f173aSYi Liu /*
208b6f173aSYi Liu  * device access via the fd opened by this function is blocked until
218b6f173aSYi Liu  * .open_device() is called successfully during BIND_IOMMUFD.
228b6f173aSYi Liu  */
vfio_device_fops_cdev_open(struct inode * inode,struct file * filep)238b6f173aSYi Liu int vfio_device_fops_cdev_open(struct inode *inode, struct file *filep)
248b6f173aSYi Liu {
258b6f173aSYi Liu 	struct vfio_device *device = container_of(inode->i_cdev,
268b6f173aSYi Liu 						  struct vfio_device, cdev);
278b6f173aSYi Liu 	struct vfio_device_file *df;
288b6f173aSYi Liu 	int ret;
298b6f173aSYi Liu 
308b6f173aSYi Liu 	/* Paired with the put in vfio_device_fops_release() */
318b6f173aSYi Liu 	if (!vfio_device_try_get_registration(device))
328b6f173aSYi Liu 		return -ENODEV;
338b6f173aSYi Liu 
348b6f173aSYi Liu 	df = vfio_allocate_device_file(device);
358b6f173aSYi Liu 	if (IS_ERR(df)) {
368b6f173aSYi Liu 		ret = PTR_ERR(df);
378b6f173aSYi Liu 		goto err_put_registration;
388b6f173aSYi Liu 	}
398b6f173aSYi Liu 
408b6f173aSYi Liu 	filep->private_data = df;
418b6f173aSYi Liu 
42b7c5e64fSAlex Williamson 	/*
43b7c5e64fSAlex Williamson 	 * Use the pseudo fs inode on the device to link all mmaps
44b7c5e64fSAlex Williamson 	 * to the same address space, allowing us to unmap all vmas
45b7c5e64fSAlex Williamson 	 * associated to this device using unmap_mapping_range().
46b7c5e64fSAlex Williamson 	 */
47b7c5e64fSAlex Williamson 	filep->f_mapping = device->inode->i_mapping;
48b7c5e64fSAlex Williamson 
498b6f173aSYi Liu 	return 0;
508b6f173aSYi Liu 
518b6f173aSYi Liu err_put_registration:
528b6f173aSYi Liu 	vfio_device_put_registration(device);
538b6f173aSYi Liu 	return ret;
548b6f173aSYi Liu }
558b6f173aSYi Liu 
vfio_df_get_kvm_safe(struct vfio_device_file * df)565fcc2696SYi Liu static void vfio_df_get_kvm_safe(struct vfio_device_file *df)
575fcc2696SYi Liu {
585fcc2696SYi Liu 	spin_lock(&df->kvm_ref_lock);
595fcc2696SYi Liu 	vfio_device_get_kvm_safe(df->device, df->kvm);
605fcc2696SYi Liu 	spin_unlock(&df->kvm_ref_lock);
615fcc2696SYi Liu }
625fcc2696SYi Liu 
vfio_df_ioctl_bind_iommufd(struct vfio_device_file * df,struct vfio_device_bind_iommufd __user * arg)635fcc2696SYi Liu long vfio_df_ioctl_bind_iommufd(struct vfio_device_file *df,
645fcc2696SYi Liu 				struct vfio_device_bind_iommufd __user *arg)
655fcc2696SYi Liu {
665fcc2696SYi Liu 	struct vfio_device *device = df->device;
675fcc2696SYi Liu 	struct vfio_device_bind_iommufd bind;
685fcc2696SYi Liu 	unsigned long minsz;
695fcc2696SYi Liu 	int ret;
705fcc2696SYi Liu 
715fcc2696SYi Liu 	static_assert(__same_type(arg->out_devid, df->devid));
725fcc2696SYi Liu 
735fcc2696SYi Liu 	minsz = offsetofend(struct vfio_device_bind_iommufd, out_devid);
745fcc2696SYi Liu 
755fcc2696SYi Liu 	if (copy_from_user(&bind, arg, minsz))
765fcc2696SYi Liu 		return -EFAULT;
775fcc2696SYi Liu 
785fcc2696SYi Liu 	if (bind.argsz < minsz || bind.flags || bind.iommufd < 0)
795fcc2696SYi Liu 		return -EINVAL;
805fcc2696SYi Liu 
815fcc2696SYi Liu 	/* BIND_IOMMUFD only allowed for cdev fds */
825fcc2696SYi Liu 	if (df->group)
835fcc2696SYi Liu 		return -EINVAL;
845fcc2696SYi Liu 
855fcc2696SYi Liu 	ret = vfio_device_block_group(device);
865fcc2696SYi Liu 	if (ret)
875fcc2696SYi Liu 		return ret;
885fcc2696SYi Liu 
895fcc2696SYi Liu 	mutex_lock(&device->dev_set->lock);
905fcc2696SYi Liu 	/* one device cannot be bound twice */
915fcc2696SYi Liu 	if (df->access_granted) {
925fcc2696SYi Liu 		ret = -EINVAL;
935fcc2696SYi Liu 		goto out_unlock;
945fcc2696SYi Liu 	}
955fcc2696SYi Liu 
965fcc2696SYi Liu 	df->iommufd = iommufd_ctx_from_fd(bind.iommufd);
975fcc2696SYi Liu 	if (IS_ERR(df->iommufd)) {
985fcc2696SYi Liu 		ret = PTR_ERR(df->iommufd);
995fcc2696SYi Liu 		df->iommufd = NULL;
1005fcc2696SYi Liu 		goto out_unlock;
1015fcc2696SYi Liu 	}
1025fcc2696SYi Liu 
1035fcc2696SYi Liu 	/*
1045fcc2696SYi Liu 	 * Before the device open, get the KVM pointer currently
1055fcc2696SYi Liu 	 * associated with the device file (if there is) and obtain
1065fcc2696SYi Liu 	 * a reference.  This reference is held until device closed.
1075fcc2696SYi Liu 	 * Save the pointer in the device for use by drivers.
1085fcc2696SYi Liu 	 */
1095fcc2696SYi Liu 	vfio_df_get_kvm_safe(df);
1105fcc2696SYi Liu 
1115fcc2696SYi Liu 	ret = vfio_df_open(df);
1125fcc2696SYi Liu 	if (ret)
1135fcc2696SYi Liu 		goto out_put_kvm;
1145fcc2696SYi Liu 
1155fcc2696SYi Liu 	ret = copy_to_user(&arg->out_devid, &df->devid,
1165fcc2696SYi Liu 			   sizeof(df->devid)) ? -EFAULT : 0;
1175fcc2696SYi Liu 	if (ret)
1185fcc2696SYi Liu 		goto out_close_device;
1195fcc2696SYi Liu 
1205fcc2696SYi Liu 	device->cdev_opened = true;
1215fcc2696SYi Liu 	/*
1225fcc2696SYi Liu 	 * Paired with smp_load_acquire() in vfio_device_fops::ioctl/
1235fcc2696SYi Liu 	 * read/write/mmap
1245fcc2696SYi Liu 	 */
1255fcc2696SYi Liu 	smp_store_release(&df->access_granted, true);
1265fcc2696SYi Liu 	mutex_unlock(&device->dev_set->lock);
1275fcc2696SYi Liu 	return 0;
1285fcc2696SYi Liu 
1295fcc2696SYi Liu out_close_device:
1305fcc2696SYi Liu 	vfio_df_close(df);
1315fcc2696SYi Liu out_put_kvm:
1325fcc2696SYi Liu 	vfio_device_put_kvm(device);
1335fcc2696SYi Liu 	iommufd_ctx_put(df->iommufd);
1345fcc2696SYi Liu 	df->iommufd = NULL;
1355fcc2696SYi Liu out_unlock:
1365fcc2696SYi Liu 	mutex_unlock(&device->dev_set->lock);
1375fcc2696SYi Liu 	vfio_device_unblock_group(device);
1385fcc2696SYi Liu 	return ret;
1395fcc2696SYi Liu }
1405fcc2696SYi Liu 
vfio_df_unbind_iommufd(struct vfio_device_file * df)1415fcc2696SYi Liu void vfio_df_unbind_iommufd(struct vfio_device_file *df)
1425fcc2696SYi Liu {
1435fcc2696SYi Liu 	struct vfio_device *device = df->device;
1445fcc2696SYi Liu 
1455fcc2696SYi Liu 	/*
1465fcc2696SYi Liu 	 * In the time of close, there is no contention with another one
1475fcc2696SYi Liu 	 * changing this flag.  So read df->access_granted without lock
1485fcc2696SYi Liu 	 * and no smp_load_acquire() is ok.
1495fcc2696SYi Liu 	 */
1505fcc2696SYi Liu 	if (!df->access_granted)
1515fcc2696SYi Liu 		return;
1525fcc2696SYi Liu 
1535fcc2696SYi Liu 	mutex_lock(&device->dev_set->lock);
1545fcc2696SYi Liu 	vfio_df_close(df);
1555fcc2696SYi Liu 	vfio_device_put_kvm(device);
1565fcc2696SYi Liu 	iommufd_ctx_put(df->iommufd);
1575fcc2696SYi Liu 	device->cdev_opened = false;
1585fcc2696SYi Liu 	mutex_unlock(&device->dev_set->lock);
1595fcc2696SYi Liu 	vfio_device_unblock_group(device);
1605fcc2696SYi Liu }
1615fcc2696SYi Liu 
vfio_df_ioctl_attach_pt(struct vfio_device_file * df,struct vfio_device_attach_iommufd_pt __user * arg)162b290a05fSYi Liu int vfio_df_ioctl_attach_pt(struct vfio_device_file *df,
163b290a05fSYi Liu 			    struct vfio_device_attach_iommufd_pt __user *arg)
164b290a05fSYi Liu {
165b290a05fSYi Liu 	struct vfio_device_attach_iommufd_pt attach;
166*ad744ed5SYi Liu 	struct vfio_device *device = df->device;
167*ad744ed5SYi Liu 	unsigned long minsz, xend = 0;
168b290a05fSYi Liu 	int ret;
169b290a05fSYi Liu 
170b290a05fSYi Liu 	minsz = offsetofend(struct vfio_device_attach_iommufd_pt, pt_id);
171b290a05fSYi Liu 
172b290a05fSYi Liu 	if (copy_from_user(&attach, arg, minsz))
173b290a05fSYi Liu 		return -EFAULT;
174b290a05fSYi Liu 
175*ad744ed5SYi Liu 	if (attach.argsz < minsz)
176b290a05fSYi Liu 		return -EINVAL;
177b290a05fSYi Liu 
178*ad744ed5SYi Liu 	if (attach.flags & ~VFIO_DEVICE_ATTACH_PASID)
179*ad744ed5SYi Liu 		return -EINVAL;
180*ad744ed5SYi Liu 
181*ad744ed5SYi Liu 	if (attach.flags & VFIO_DEVICE_ATTACH_PASID) {
182*ad744ed5SYi Liu 		if (!device->ops->pasid_attach_ioas)
183*ad744ed5SYi Liu 			return -EOPNOTSUPP;
184*ad744ed5SYi Liu 		xend = offsetofend(struct vfio_device_attach_iommufd_pt, pasid);
185*ad744ed5SYi Liu 	}
186*ad744ed5SYi Liu 
187*ad744ed5SYi Liu 	if (xend) {
188*ad744ed5SYi Liu 		if (attach.argsz < xend)
189*ad744ed5SYi Liu 			return -EINVAL;
190*ad744ed5SYi Liu 
191*ad744ed5SYi Liu 		if (copy_from_user((void *)&attach + minsz,
192*ad744ed5SYi Liu 				   (void __user *)arg + minsz, xend - minsz))
193*ad744ed5SYi Liu 			return -EFAULT;
194*ad744ed5SYi Liu 	}
195*ad744ed5SYi Liu 
196b290a05fSYi Liu 	mutex_lock(&device->dev_set->lock);
197*ad744ed5SYi Liu 	if (attach.flags & VFIO_DEVICE_ATTACH_PASID)
198*ad744ed5SYi Liu 		ret = device->ops->pasid_attach_ioas(device,
199*ad744ed5SYi Liu 						     attach.pasid,
200*ad744ed5SYi Liu 						     &attach.pt_id);
201*ad744ed5SYi Liu 	else
202b290a05fSYi Liu 		ret = device->ops->attach_ioas(device, &attach.pt_id);
203b290a05fSYi Liu 	if (ret)
204b290a05fSYi Liu 		goto out_unlock;
205b290a05fSYi Liu 
206b290a05fSYi Liu 	if (copy_to_user(&arg->pt_id, &attach.pt_id, sizeof(attach.pt_id))) {
207b290a05fSYi Liu 		ret = -EFAULT;
208b290a05fSYi Liu 		goto out_detach;
209b290a05fSYi Liu 	}
210b290a05fSYi Liu 	mutex_unlock(&device->dev_set->lock);
211b290a05fSYi Liu 
212b290a05fSYi Liu 	return 0;
213b290a05fSYi Liu 
214b290a05fSYi Liu out_detach:
215b290a05fSYi Liu 	device->ops->detach_ioas(device);
216b290a05fSYi Liu out_unlock:
217b290a05fSYi Liu 	mutex_unlock(&device->dev_set->lock);
218b290a05fSYi Liu 	return ret;
219b290a05fSYi Liu }
220b290a05fSYi Liu 
vfio_df_ioctl_detach_pt(struct vfio_device_file * df,struct vfio_device_detach_iommufd_pt __user * arg)221b290a05fSYi Liu int vfio_df_ioctl_detach_pt(struct vfio_device_file *df,
222b290a05fSYi Liu 			    struct vfio_device_detach_iommufd_pt __user *arg)
223b290a05fSYi Liu {
224b290a05fSYi Liu 	struct vfio_device_detach_iommufd_pt detach;
225*ad744ed5SYi Liu 	struct vfio_device *device = df->device;
226*ad744ed5SYi Liu 	unsigned long minsz, xend = 0;
227b290a05fSYi Liu 
228b290a05fSYi Liu 	minsz = offsetofend(struct vfio_device_detach_iommufd_pt, flags);
229b290a05fSYi Liu 
230b290a05fSYi Liu 	if (copy_from_user(&detach, arg, minsz))
231b290a05fSYi Liu 		return -EFAULT;
232b290a05fSYi Liu 
233*ad744ed5SYi Liu 	if (detach.argsz < minsz)
234b290a05fSYi Liu 		return -EINVAL;
235b290a05fSYi Liu 
236*ad744ed5SYi Liu 	if (detach.flags & ~VFIO_DEVICE_DETACH_PASID)
237*ad744ed5SYi Liu 		return -EINVAL;
238*ad744ed5SYi Liu 
239*ad744ed5SYi Liu 	if (detach.flags & VFIO_DEVICE_DETACH_PASID) {
240*ad744ed5SYi Liu 		if (!device->ops->pasid_detach_ioas)
241*ad744ed5SYi Liu 			return -EOPNOTSUPP;
242*ad744ed5SYi Liu 		xend = offsetofend(struct vfio_device_detach_iommufd_pt, pasid);
243*ad744ed5SYi Liu 	}
244*ad744ed5SYi Liu 
245*ad744ed5SYi Liu 	if (xend) {
246*ad744ed5SYi Liu 		if (detach.argsz < xend)
247*ad744ed5SYi Liu 			return -EINVAL;
248*ad744ed5SYi Liu 
249*ad744ed5SYi Liu 		if (copy_from_user((void *)&detach + minsz,
250*ad744ed5SYi Liu 				   (void __user *)arg + minsz, xend - minsz))
251*ad744ed5SYi Liu 			return -EFAULT;
252*ad744ed5SYi Liu 	}
253*ad744ed5SYi Liu 
254b290a05fSYi Liu 	mutex_lock(&device->dev_set->lock);
255*ad744ed5SYi Liu 	if (detach.flags & VFIO_DEVICE_DETACH_PASID)
256*ad744ed5SYi Liu 		device->ops->pasid_detach_ioas(device, detach.pasid);
257*ad744ed5SYi Liu 	else
258b290a05fSYi Liu 		device->ops->detach_ioas(device);
259b290a05fSYi Liu 	mutex_unlock(&device->dev_set->lock);
260b290a05fSYi Liu 
261b290a05fSYi Liu 	return 0;
262b290a05fSYi Liu }
263b290a05fSYi Liu 
vfio_device_devnode(const struct device * dev,umode_t * mode)2648b6f173aSYi Liu static char *vfio_device_devnode(const struct device *dev, umode_t *mode)
2658b6f173aSYi Liu {
2668b6f173aSYi Liu 	return kasprintf(GFP_KERNEL, "vfio/devices/%s", dev_name(dev));
2678b6f173aSYi Liu }
2688b6f173aSYi Liu 
vfio_cdev_init(struct class * device_class)2698b6f173aSYi Liu int vfio_cdev_init(struct class *device_class)
2708b6f173aSYi Liu {
2718b6f173aSYi Liu 	device_class->devnode = vfio_device_devnode;
2728b6f173aSYi Liu 	return alloc_chrdev_region(&device_devt, 0,
2738b6f173aSYi Liu 				   MINORMASK + 1, "vfio-dev");
2748b6f173aSYi Liu }
2758b6f173aSYi Liu 
vfio_cdev_cleanup(void)2768b6f173aSYi Liu void vfio_cdev_cleanup(void)
2778b6f173aSYi Liu {
2788b6f173aSYi Liu 	unregister_chrdev_region(device_devt, MINORMASK + 1);
2798b6f173aSYi Liu }
280