1 /* 2 * Copyright (c) 2024-2025 Oracle and/or its affiliates. 3 * 4 * SPDX-License-Identifier: GPL-2.0-or-later 5 */ 6 7 #include "qemu/osdep.h" 8 #include "qemu/error-report.h" 9 #include "qapi/error.h" 10 #include "hw/vfio/vfio-cpr.h" 11 #include "hw/vfio/vfio-device.h" 12 #include "migration/blocker.h" 13 #include "migration/cpr.h" 14 #include "migration/migration.h" 15 #include "migration/vmstate.h" 16 #include "system/iommufd.h" 17 #include "vfio-iommufd.h" 18 #include "trace.h" 19 20 typedef struct CprVFIODevice { 21 char *name; 22 unsigned int namelen; 23 uint32_t ioas_id; 24 int devid; 25 uint32_t hwpt_id; 26 QLIST_ENTRY(CprVFIODevice) next; 27 } CprVFIODevice; 28 29 static const VMStateDescription vmstate_cpr_vfio_device = { 30 .name = "cpr vfio device", 31 .version_id = 1, 32 .minimum_version_id = 1, 33 .fields = (VMStateField[]) { 34 VMSTATE_UINT32(namelen, CprVFIODevice), 35 VMSTATE_VBUFFER_ALLOC_UINT32(name, CprVFIODevice, 0, NULL, namelen), 36 VMSTATE_INT32(devid, CprVFIODevice), 37 VMSTATE_UINT32(ioas_id, CprVFIODevice), 38 VMSTATE_UINT32(hwpt_id, CprVFIODevice), 39 VMSTATE_END_OF_LIST() 40 } 41 }; 42 43 const VMStateDescription vmstate_cpr_vfio_devices = { 44 .name = CPR_STATE "/vfio devices", 45 .version_id = 1, 46 .minimum_version_id = 1, 47 .fields = (const VMStateField[]){ 48 VMSTATE_QLIST_V(vfio_devices, CprState, 1, vmstate_cpr_vfio_device, 49 CprVFIODevice, next), 50 VMSTATE_END_OF_LIST() 51 } 52 }; 53 54 static void vfio_cpr_save_device(VFIODevice *vbasedev) 55 { 56 CprVFIODevice *elem = g_new0(CprVFIODevice, 1); 57 58 elem->name = g_strdup(vbasedev->name); 59 elem->namelen = strlen(vbasedev->name) + 1; 60 elem->ioas_id = vbasedev->cpr.ioas_id; 61 elem->devid = vbasedev->devid; 62 elem->hwpt_id = vbasedev->cpr.hwpt_id; 63 QLIST_INSERT_HEAD(&cpr_state.vfio_devices, elem, next); 64 } 65 66 static CprVFIODevice *find_device(const char *name) 67 { 68 CprVFIODeviceList *head = &cpr_state.vfio_devices; 69 CprVFIODevice *elem; 70 71 QLIST_FOREACH(elem, head, next) { 72 if (!strcmp(elem->name, name)) { 73 return elem; 74 } 75 } 76 return NULL; 77 } 78 79 static void vfio_cpr_delete_device(const char *name) 80 { 81 CprVFIODevice *elem = find_device(name); 82 83 if (elem) { 84 QLIST_REMOVE(elem, next); 85 g_free(elem->name); 86 g_free(elem); 87 } 88 } 89 90 static bool vfio_cpr_find_device(VFIODevice *vbasedev) 91 { 92 CprVFIODevice *elem = find_device(vbasedev->name); 93 94 if (elem) { 95 vbasedev->cpr.ioas_id = elem->ioas_id; 96 vbasedev->devid = elem->devid; 97 vbasedev->cpr.hwpt_id = elem->hwpt_id; 98 trace_vfio_cpr_find_device(elem->ioas_id, elem->devid, elem->hwpt_id); 99 return true; 100 } 101 return false; 102 } 103 104 static bool vfio_cpr_supported(IOMMUFDBackend *be, Error **errp) 105 { 106 if (!iommufd_change_process_capable(be)) { 107 if (errp) { 108 error_setg(errp, "vfio iommufd backend does not support " 109 "IOMMU_IOAS_CHANGE_PROCESS"); 110 } 111 return false; 112 } 113 return true; 114 } 115 116 static int iommufd_cpr_pre_save(void *opaque) 117 { 118 IOMMUFDBackend *be = opaque; 119 120 /* 121 * The process has not changed yet, but proactively try the ioctl, 122 * and it will fail if any DMA mappings are not supported. 123 */ 124 if (!iommufd_change_process_capable(be)) { 125 error_report("some memory regions do not support " 126 "IOMMU_IOAS_CHANGE_PROCESS"); 127 return -1; 128 } 129 return 0; 130 } 131 132 static int iommufd_cpr_post_load(void *opaque, int version_id) 133 { 134 IOMMUFDBackend *be = opaque; 135 Error *local_err = NULL; 136 137 if (!iommufd_change_process(be, &local_err)) { 138 error_report_err(local_err); 139 return -1; 140 } 141 return 0; 142 } 143 144 static const VMStateDescription iommufd_cpr_vmstate = { 145 .name = "iommufd", 146 .version_id = 0, 147 .minimum_version_id = 0, 148 .pre_save = iommufd_cpr_pre_save, 149 .post_load = iommufd_cpr_post_load, 150 .needed = cpr_incoming_needed, 151 .fields = (VMStateField[]) { 152 VMSTATE_END_OF_LIST() 153 } 154 }; 155 156 bool vfio_iommufd_cpr_register_iommufd(IOMMUFDBackend *be, Error **errp) 157 { 158 Error **cpr_blocker = &be->cpr_blocker; 159 160 if (!vfio_cpr_supported(be, cpr_blocker)) { 161 return migrate_add_blocker_modes(cpr_blocker, errp, 162 MIG_MODE_CPR_TRANSFER, -1) == 0; 163 } 164 165 vmstate_register(NULL, -1, &iommufd_cpr_vmstate, be); 166 167 return true; 168 } 169 170 void vfio_iommufd_cpr_unregister_iommufd(IOMMUFDBackend *be) 171 { 172 vmstate_unregister(NULL, &iommufd_cpr_vmstate, be); 173 migrate_del_blocker(&be->cpr_blocker); 174 } 175 176 bool vfio_iommufd_cpr_register_container(VFIOIOMMUFDContainer *container, 177 Error **errp) 178 { 179 VFIOContainerBase *bcontainer = &container->bcontainer; 180 181 migration_add_notifier_mode(&bcontainer->cpr_reboot_notifier, 182 vfio_cpr_reboot_notifier, 183 MIG_MODE_CPR_REBOOT); 184 185 vfio_cpr_add_kvm_notifier(); 186 187 return true; 188 } 189 190 void vfio_iommufd_cpr_unregister_container(VFIOIOMMUFDContainer *container) 191 { 192 VFIOContainerBase *bcontainer = &container->bcontainer; 193 194 migration_remove_notifier(&bcontainer->cpr_reboot_notifier); 195 } 196 197 void vfio_iommufd_cpr_register_device(VFIODevice *vbasedev) 198 { 199 if (!cpr_is_incoming()) { 200 /* 201 * Beware fd may have already been saved by vfio_device_set_fd, 202 * so call resave to avoid a duplicate entry. 203 */ 204 cpr_resave_fd(vbasedev->name, 0, vbasedev->fd); 205 vfio_cpr_save_device(vbasedev); 206 } 207 } 208 209 void vfio_iommufd_cpr_unregister_device(VFIODevice *vbasedev) 210 { 211 cpr_delete_fd(vbasedev->name, 0); 212 vfio_cpr_delete_device(vbasedev->name); 213 } 214 215 void vfio_cpr_load_device(VFIODevice *vbasedev) 216 { 217 if (cpr_is_incoming()) { 218 bool ret = vfio_cpr_find_device(vbasedev); 219 g_assert(ret); 220 221 if (vbasedev->fd < 0) { 222 vbasedev->fd = cpr_find_fd(vbasedev->name, 0); 223 } 224 } 225 } 226