vfio: Create pvIOMM for guest devices

Use new UAPI for KVM-VFIO device to create a pvIOMMU and attached
to guest VMs for VFIO assigned devices.

Signed-off-by: Mostafa Saleh <smostafa@google.com>
diff --git a/vfio/core.c b/vfio/core.c
index c30044c..ee4e248 100644
--- a/vfio/core.c
+++ b/vfio/core.c
@@ -11,6 +11,8 @@
 static int vfio_container;
 static LIST_HEAD(vfio_groups);
 static struct vfio_device *vfio_devices;
+/* Just a random start of SID for pvIOMMU, nothing special. */
+static int sid_offset = 0x55;
 
 static int vfio_device_pci_parser(const struct option *opt, char *arg,
 				  struct vfio_device_params *dev)
@@ -679,8 +681,9 @@
 	struct kvm_create_device cd = {
 		.type = KVM_DEV_TYPE_VFIO,
 	};
+	int ret, i, pviommufd;
+	u32 j;
 
-	int ret;
 	ret = ioctl(kvm->vm_fd, KVM_CREATE_DEVICE, &cd);
 	if (ret) {
 		pr_err("Failed to create kvm-vfio bridge %d\n", ret);
@@ -700,6 +703,53 @@
 		}
 	}
 
+	/* Create pvIOMMU for guest. */
+	struct kvm_device_attr attr = {
+		.group = KVM_DEV_VFIO_PVIOMMU,
+		.attr = KVM_DEV_VFIO_PVIOMMU_ATTACH,
+	};
+
+	pviommufd = ioctl(cd.fd, KVM_SET_DEVICE_ATTR, &attr);
+	if (pviommufd < 0) {
+		pr_err("Failed to attach pviommu to kvm-vfio device %d\n", ret);
+		return pviommufd;
+	}
+
+	/* For VFIO devices, add to the pvIOMMU. */
+	for (i = 0 ; i < kvm->cfg.num_vfio_devices; ++i) {
+		struct kvm_vfio_iommu_info info = {
+			.device_fd = vfio_devices[i].fd,
+		};
+		/* Probe number of SIDs for this device. */
+		struct kvm_device_attr attr = {
+		.group = KVM_DEV_VFIO_PVIOMMU,
+		.attr = KVM_DEV_VFIO_PVIOMMU_GET_INFO,
+		.addr = (uint64_t)&info,
+		};
+		ret = ioctl(cd.fd, KVM_SET_DEVICE_ATTR, &attr);
+		/* Set vSID => SID translation. */
+		for (j = 0; j < info.nr_sids; ++j) {
+			struct kvm_vfio_iommu_config config = {
+				.pviommu_fd = pviommufd,
+				.vfio_dev_fd = vfio_devices[i].fd,
+				.sid_idx = j,
+				.vsid = sid_offset++,
+			};
+
+			struct kvm_device_attr attr = {
+			.group = KVM_DEV_VFIO_PVIOMMU,
+			.attr = KVM_DEV_VFIO_PVIOMMU_SET_CONFIG,
+			.addr = (uint64_t)&config,
+			};
+
+			ret = ioctl(cd.fd, KVM_SET_DEVICE_ATTR, &attr);
+			if (ret) {
+				pr_err("Failed to set vsid(%d) => sid(%d) translation, err %d\n", config.vsid, j, ret);
+				return ret;
+			}
+		}
+	}
+
 	return 0;
 }