|
@@ -25,6 +25,7 @@
|
|
|
#include "qemu/cutils.h"
|
|
|
#include "qemu/chardev_open.h"
|
|
|
#include "pci.h"
|
|
|
+#include "exec/ram_addr.h"
|
|
|
|
|
|
static int iommufd_cdev_map(const VFIOContainerBase *bcontainer, hwaddr iova,
|
|
|
ram_addr_t size, void *vaddr, bool readonly)
|
|
@@ -110,6 +111,68 @@ static void iommufd_cdev_unbind_and_disconnect(VFIODevice *vbasedev)
|
|
|
iommufd_backend_disconnect(vbasedev->iommufd);
|
|
|
}
|
|
|
|
|
|
+static bool iommufd_hwpt_dirty_tracking(VFIOIOASHwpt *hwpt)
|
|
|
+{
|
|
|
+ return hwpt && hwpt->hwpt_flags & IOMMU_HWPT_ALLOC_DIRTY_TRACKING;
|
|
|
+}
|
|
|
+
|
|
|
+static int iommufd_set_dirty_page_tracking(const VFIOContainerBase *bcontainer,
|
|
|
+ bool start, Error **errp)
|
|
|
+{
|
|
|
+ const VFIOIOMMUFDContainer *container =
|
|
|
+ container_of(bcontainer, VFIOIOMMUFDContainer, bcontainer);
|
|
|
+ VFIOIOASHwpt *hwpt;
|
|
|
+
|
|
|
+ QLIST_FOREACH(hwpt, &container->hwpt_list, next) {
|
|
|
+ if (!iommufd_hwpt_dirty_tracking(hwpt)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!iommufd_backend_set_dirty_tracking(container->be,
|
|
|
+ hwpt->hwpt_id, start, errp)) {
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+err:
|
|
|
+ QLIST_FOREACH(hwpt, &container->hwpt_list, next) {
|
|
|
+ if (!iommufd_hwpt_dirty_tracking(hwpt)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ iommufd_backend_set_dirty_tracking(container->be,
|
|
|
+ hwpt->hwpt_id, !start, NULL);
|
|
|
+ }
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
+
|
|
|
+static int iommufd_query_dirty_bitmap(const VFIOContainerBase *bcontainer,
|
|
|
+ VFIOBitmap *vbmap, hwaddr iova,
|
|
|
+ hwaddr size, Error **errp)
|
|
|
+{
|
|
|
+ VFIOIOMMUFDContainer *container = container_of(bcontainer,
|
|
|
+ VFIOIOMMUFDContainer,
|
|
|
+ bcontainer);
|
|
|
+ unsigned long page_size = qemu_real_host_page_size();
|
|
|
+ VFIOIOASHwpt *hwpt;
|
|
|
+
|
|
|
+ QLIST_FOREACH(hwpt, &container->hwpt_list, next) {
|
|
|
+ if (!iommufd_hwpt_dirty_tracking(hwpt)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!iommufd_backend_get_dirty_bitmap(container->be, hwpt->hwpt_id,
|
|
|
+ iova, size, page_size,
|
|
|
+ (uint64_t *)vbmap->bitmap,
|
|
|
+ errp)) {
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int iommufd_cdev_getfd(const char *sysfs_path, Error **errp)
|
|
|
{
|
|
|
ERRP_GUARD();
|
|
@@ -172,7 +235,7 @@ out:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
-static bool iommufd_cdev_attach_ioas_hwpt(VFIODevice *vbasedev, uint32_t id,
|
|
|
+static int iommufd_cdev_attach_ioas_hwpt(VFIODevice *vbasedev, uint32_t id,
|
|
|
Error **errp)
|
|
|
{
|
|
|
int iommufd = vbasedev->iommufd->fd;
|
|
@@ -187,12 +250,12 @@ static bool iommufd_cdev_attach_ioas_hwpt(VFIODevice *vbasedev, uint32_t id,
|
|
|
error_setg_errno(errp, errno,
|
|
|
"[iommufd=%d] error attach %s (%d) to id=%d",
|
|
|
iommufd, vbasedev->name, vbasedev->fd, id);
|
|
|
- return false;
|
|
|
+ return -errno;
|
|
|
}
|
|
|
|
|
|
trace_iommufd_cdev_attach_ioas_hwpt(iommufd, vbasedev->name,
|
|
|
vbasedev->fd, id);
|
|
|
- return true;
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
static bool iommufd_cdev_detach_ioas_hwpt(VFIODevice *vbasedev, Error **errp)
|
|
@@ -212,11 +275,111 @@ static bool iommufd_cdev_detach_ioas_hwpt(VFIODevice *vbasedev, Error **errp)
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+static bool iommufd_cdev_autodomains_get(VFIODevice *vbasedev,
|
|
|
+ VFIOIOMMUFDContainer *container,
|
|
|
+ Error **errp)
|
|
|
+{
|
|
|
+ ERRP_GUARD();
|
|
|
+ IOMMUFDBackend *iommufd = vbasedev->iommufd;
|
|
|
+ uint32_t flags = 0;
|
|
|
+ VFIOIOASHwpt *hwpt;
|
|
|
+ uint32_t hwpt_id;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Try to find a domain */
|
|
|
+ QLIST_FOREACH(hwpt, &container->hwpt_list, next) {
|
|
|
+ ret = iommufd_cdev_attach_ioas_hwpt(vbasedev, hwpt->hwpt_id, errp);
|
|
|
+ if (ret) {
|
|
|
+ /* -EINVAL means the domain is incompatible with the device. */
|
|
|
+ if (ret == -EINVAL) {
|
|
|
+ /*
|
|
|
+ * It is an expected failure and it just means we will try
|
|
|
+ * another domain, or create one if no existing compatible
|
|
|
+ * domain is found. Hence why the error is discarded below.
|
|
|
+ */
|
|
|
+ error_free(*errp);
|
|
|
+ *errp = NULL;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ } else {
|
|
|
+ vbasedev->hwpt = hwpt;
|
|
|
+ QLIST_INSERT_HEAD(&hwpt->device_list, vbasedev, hwpt_next);
|
|
|
+ vbasedev->iommu_dirty_tracking = iommufd_hwpt_dirty_tracking(hwpt);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * This is quite early and VFIO Migration state isn't yet fully
|
|
|
+ * initialized, thus rely only on IOMMU hardware capabilities as to
|
|
|
+ * whether IOMMU dirty tracking is going to be requested. Later
|
|
|
+ * vfio_migration_realize() may decide to use VF dirty tracking
|
|
|
+ * instead.
|
|
|
+ */
|
|
|
+ if (vbasedev->hiod->caps.hw_caps & IOMMU_HW_CAP_DIRTY_TRACKING) {
|
|
|
+ flags = IOMMU_HWPT_ALLOC_DIRTY_TRACKING;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!iommufd_backend_alloc_hwpt(iommufd, vbasedev->devid,
|
|
|
+ container->ioas_id, flags,
|
|
|
+ IOMMU_HWPT_DATA_NONE, 0, NULL,
|
|
|
+ &hwpt_id, errp)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ hwpt = g_malloc0(sizeof(*hwpt));
|
|
|
+ hwpt->hwpt_id = hwpt_id;
|
|
|
+ hwpt->hwpt_flags = flags;
|
|
|
+ QLIST_INIT(&hwpt->device_list);
|
|
|
+
|
|
|
+ ret = iommufd_cdev_attach_ioas_hwpt(vbasedev, hwpt->hwpt_id, errp);
|
|
|
+ if (ret) {
|
|
|
+ iommufd_backend_free_id(container->be, hwpt->hwpt_id);
|
|
|
+ g_free(hwpt);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ vbasedev->hwpt = hwpt;
|
|
|
+ vbasedev->iommu_dirty_tracking = iommufd_hwpt_dirty_tracking(hwpt);
|
|
|
+ QLIST_INSERT_HEAD(&hwpt->device_list, vbasedev, hwpt_next);
|
|
|
+ QLIST_INSERT_HEAD(&container->hwpt_list, hwpt, next);
|
|
|
+ container->bcontainer.dirty_pages_supported |=
|
|
|
+ vbasedev->iommu_dirty_tracking;
|
|
|
+ if (container->bcontainer.dirty_pages_supported &&
|
|
|
+ !vbasedev->iommu_dirty_tracking) {
|
|
|
+ warn_report("IOMMU instance for device %s doesn't support dirty tracking",
|
|
|
+ vbasedev->name);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static void iommufd_cdev_autodomains_put(VFIODevice *vbasedev,
|
|
|
+ VFIOIOMMUFDContainer *container)
|
|
|
+{
|
|
|
+ VFIOIOASHwpt *hwpt = vbasedev->hwpt;
|
|
|
+
|
|
|
+ QLIST_REMOVE(vbasedev, hwpt_next);
|
|
|
+ vbasedev->hwpt = NULL;
|
|
|
+
|
|
|
+ if (QLIST_EMPTY(&hwpt->device_list)) {
|
|
|
+ QLIST_REMOVE(hwpt, next);
|
|
|
+ iommufd_backend_free_id(container->be, hwpt->hwpt_id);
|
|
|
+ g_free(hwpt);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static bool iommufd_cdev_attach_container(VFIODevice *vbasedev,
|
|
|
VFIOIOMMUFDContainer *container,
|
|
|
Error **errp)
|
|
|
{
|
|
|
- return iommufd_cdev_attach_ioas_hwpt(vbasedev, container->ioas_id, errp);
|
|
|
+ /* mdevs aren't physical devices and will fail with auto domains */
|
|
|
+ if (!vbasedev->mdev) {
|
|
|
+ return iommufd_cdev_autodomains_get(vbasedev, container, errp);
|
|
|
+ }
|
|
|
+
|
|
|
+ return !iommufd_cdev_attach_ioas_hwpt(vbasedev, container->ioas_id, errp);
|
|
|
}
|
|
|
|
|
|
static void iommufd_cdev_detach_container(VFIODevice *vbasedev,
|
|
@@ -227,6 +390,11 @@ static void iommufd_cdev_detach_container(VFIODevice *vbasedev,
|
|
|
if (!iommufd_cdev_detach_ioas_hwpt(vbasedev, &err)) {
|
|
|
error_report_err(err);
|
|
|
}
|
|
|
+
|
|
|
+ if (vbasedev->hwpt) {
|
|
|
+ iommufd_cdev_autodomains_put(vbasedev, container);
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
static void iommufd_cdev_container_destroy(VFIOIOMMUFDContainer *container)
|
|
@@ -320,6 +488,17 @@ static bool iommufd_cdev_attach(const char *name, VFIODevice *vbasedev,
|
|
|
|
|
|
space = vfio_get_address_space(as);
|
|
|
|
|
|
+ /*
|
|
|
+ * The HostIOMMUDevice data from legacy backend is static and doesn't need
|
|
|
+ * any information from the (type1-iommu) backend to be initialized. In
|
|
|
+ * contrast however, the IOMMUFD HostIOMMUDevice data requires the iommufd
|
|
|
+ * FD to be connected and having a devid to be able to successfully call
|
|
|
+ * iommufd_backend_get_device_info().
|
|
|
+ */
|
|
|
+ if (!vfio_device_hiod_realize(vbasedev, errp)) {
|
|
|
+ goto err_alloc_ioas;
|
|
|
+ }
|
|
|
+
|
|
|
/* try to attach to an existing container in this space */
|
|
|
QLIST_FOREACH(bcontainer, &space->containers, next) {
|
|
|
container = container_of(bcontainer, VFIOIOMMUFDContainer, bcontainer);
|
|
@@ -354,6 +533,7 @@ static bool iommufd_cdev_attach(const char *name, VFIODevice *vbasedev,
|
|
|
container = VFIO_IOMMU_IOMMUFD(object_new(TYPE_VFIO_IOMMU_IOMMUFD));
|
|
|
container->be = vbasedev->iommufd;
|
|
|
container->ioas_id = ioas_id;
|
|
|
+ QLIST_INIT(&container->hwpt_list);
|
|
|
|
|
|
bcontainer = &container->bcontainer;
|
|
|
vfio_address_space_insert(space, bcontainer);
|
|
@@ -617,6 +797,8 @@ static void vfio_iommu_iommufd_class_init(ObjectClass *klass, void *data)
|
|
|
vioc->attach_device = iommufd_cdev_attach;
|
|
|
vioc->detach_device = iommufd_cdev_detach;
|
|
|
vioc->pci_hot_reset = iommufd_cdev_pci_hot_reset;
|
|
|
+ vioc->set_dirty_page_tracking = iommufd_set_dirty_page_tracking;
|
|
|
+ vioc->query_dirty_bitmap = iommufd_query_dirty_bitmap;
|
|
|
};
|
|
|
|
|
|
static bool hiod_iommufd_vfio_realize(HostIOMMUDevice *hiod, void *opaque,
|
|
@@ -628,17 +810,19 @@ static bool hiod_iommufd_vfio_realize(HostIOMMUDevice *hiod, void *opaque,
|
|
|
union {
|
|
|
struct iommu_hw_info_vtd vtd;
|
|
|
} data;
|
|
|
+ uint64_t hw_caps;
|
|
|
|
|
|
hiod->agent = opaque;
|
|
|
|
|
|
if (!iommufd_backend_get_device_info(vdev->iommufd, vdev->devid,
|
|
|
- &type, &data, sizeof(data), errp)) {
|
|
|
+ &type, &data, sizeof(data),
|
|
|
+ &hw_caps, errp)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
hiod->name = g_strdup(vdev->name);
|
|
|
caps->type = type;
|
|
|
- caps->aw_bits = vfio_device_get_aw_bits(vdev);
|
|
|
+ caps->hw_caps = hw_caps;
|
|
|
|
|
|
return true;
|
|
|
}
|