/* * Copyright (c) 2024-2025 Oracle and/or its affiliates. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "qemu/error-report.h" #include "qapi/error.h" #include "hw/vfio/vfio-cpr.h" #include "hw/vfio/vfio-device.h" #include "migration/blocker.h" #include "migration/cpr.h" #include "migration/migration.h" #include "migration/vmstate.h" #include "system/iommufd.h" #include "vfio-iommufd.h" #include "trace.h" typedef struct CprVFIODevice { char *name; unsigned int namelen; uint32_t ioas_id; int devid; uint32_t hwpt_id; QLIST_ENTRY(CprVFIODevice) next; } CprVFIODevice; static const VMStateDescription vmstate_cpr_vfio_device = { .name = "cpr vfio device", .version_id = 1, .minimum_version_id = 1, .fields = (VMStateField[]) { VMSTATE_UINT32(namelen, CprVFIODevice), VMSTATE_VBUFFER_ALLOC_UINT32(name, CprVFIODevice, 0, NULL, namelen), VMSTATE_INT32(devid, CprVFIODevice), VMSTATE_UINT32(ioas_id, CprVFIODevice), VMSTATE_UINT32(hwpt_id, CprVFIODevice), VMSTATE_END_OF_LIST() } }; const VMStateDescription vmstate_cpr_vfio_devices = { .name = CPR_STATE "/vfio devices", .version_id = 1, .minimum_version_id = 1, .fields = (const VMStateField[]){ VMSTATE_QLIST_V(vfio_devices, CprState, 1, vmstate_cpr_vfio_device, CprVFIODevice, next), VMSTATE_END_OF_LIST() } }; static void vfio_cpr_save_device(VFIODevice *vbasedev) { CprVFIODevice *elem = g_new0(CprVFIODevice, 1); elem->name = g_strdup(vbasedev->name); elem->namelen = strlen(vbasedev->name) + 1; elem->ioas_id = vbasedev->cpr.ioas_id; elem->devid = vbasedev->devid; elem->hwpt_id = vbasedev->cpr.hwpt_id; QLIST_INSERT_HEAD(&cpr_state.vfio_devices, elem, next); } static CprVFIODevice *find_device(const char *name) { CprVFIODeviceList *head = &cpr_state.vfio_devices; CprVFIODevice *elem; QLIST_FOREACH(elem, head, next) { if (!strcmp(elem->name, name)) { return elem; } } return NULL; } static void vfio_cpr_delete_device(const char *name) { CprVFIODevice *elem = find_device(name); if (elem) { QLIST_REMOVE(elem, next); g_free(elem->name); g_free(elem); } } static bool vfio_cpr_find_device(VFIODevice *vbasedev) { CprVFIODevice *elem = find_device(vbasedev->name); if (elem) { vbasedev->cpr.ioas_id = elem->ioas_id; vbasedev->devid = elem->devid; vbasedev->cpr.hwpt_id = elem->hwpt_id; trace_vfio_cpr_find_device(elem->ioas_id, elem->devid, elem->hwpt_id); return true; } return false; } static bool vfio_cpr_supported(IOMMUFDBackend *be, Error **errp) { if (!iommufd_change_process_capable(be)) { if (errp) { error_setg(errp, "vfio iommufd backend does not support " "IOMMU_IOAS_CHANGE_PROCESS"); } return false; } return true; } static int iommufd_cpr_pre_save(void *opaque) { IOMMUFDBackend *be = opaque; /* * The process has not changed yet, but proactively try the ioctl, * and it will fail if any DMA mappings are not supported. */ if (!iommufd_change_process_capable(be)) { error_report("some memory regions do not support " "IOMMU_IOAS_CHANGE_PROCESS"); return -1; } return 0; } static int iommufd_cpr_post_load(void *opaque, int version_id) { IOMMUFDBackend *be = opaque; Error *local_err = NULL; if (!iommufd_change_process(be, &local_err)) { error_report_err(local_err); return -1; } return 0; } static const VMStateDescription iommufd_cpr_vmstate = { .name = "iommufd", .version_id = 0, .minimum_version_id = 0, .pre_save = iommufd_cpr_pre_save, .post_load = iommufd_cpr_post_load, .needed = cpr_incoming_needed, .fields = (VMStateField[]) { VMSTATE_END_OF_LIST() } }; bool vfio_iommufd_cpr_register_iommufd(IOMMUFDBackend *be, Error **errp) { Error **cpr_blocker = &be->cpr_blocker; if (!vfio_cpr_supported(be, cpr_blocker)) { return migrate_add_blocker_modes(cpr_blocker, errp, MIG_MODE_CPR_TRANSFER, -1) == 0; } vmstate_register(NULL, -1, &iommufd_cpr_vmstate, be); return true; } void vfio_iommufd_cpr_unregister_iommufd(IOMMUFDBackend *be) { vmstate_unregister(NULL, &iommufd_cpr_vmstate, be); migrate_del_blocker(&be->cpr_blocker); } bool vfio_iommufd_cpr_register_container(VFIOIOMMUFDContainer *container, Error **errp) { VFIOContainerBase *bcontainer = &container->bcontainer; migration_add_notifier_mode(&bcontainer->cpr_reboot_notifier, vfio_cpr_reboot_notifier, MIG_MODE_CPR_REBOOT); vfio_cpr_add_kvm_notifier(); return true; } void vfio_iommufd_cpr_unregister_container(VFIOIOMMUFDContainer *container) { VFIOContainerBase *bcontainer = &container->bcontainer; migration_remove_notifier(&bcontainer->cpr_reboot_notifier); } void vfio_iommufd_cpr_register_device(VFIODevice *vbasedev) { if (!cpr_is_incoming()) { /* * Beware fd may have already been saved by vfio_device_set_fd, * so call resave to avoid a duplicate entry. */ cpr_resave_fd(vbasedev->name, 0, vbasedev->fd); vfio_cpr_save_device(vbasedev); } } void vfio_iommufd_cpr_unregister_device(VFIODevice *vbasedev) { cpr_delete_fd(vbasedev->name, 0); vfio_cpr_delete_device(vbasedev->name); } void vfio_cpr_load_device(VFIODevice *vbasedev) { if (cpr_is_incoming()) { bool ret = vfio_cpr_find_device(vbasedev); g_assert(ret); if (vbasedev->fd < 0) { vbasedev->fd = cpr_find_fd(vbasedev->name, 0); } } }