xref: /qemu/hw/vfio/cpr-iommufd.c (revision 563ac3d18129a2770a285cc16c20ad50c8adc7c0)
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 
vfio_cpr_save_device(VFIODevice * vbasedev)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 
find_device(const char * name)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 
vfio_cpr_delete_device(const char * name)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 
vfio_cpr_find_device(VFIODevice * vbasedev)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 
vfio_cpr_supported(IOMMUFDBackend * be,Error ** errp)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 
iommufd_cpr_pre_save(void * opaque)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 
iommufd_cpr_post_load(void * opaque,int version_id)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 
vfio_iommufd_cpr_register_iommufd(IOMMUFDBackend * be,Error ** errp)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 
vfio_iommufd_cpr_unregister_iommufd(IOMMUFDBackend * be)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 
vfio_iommufd_cpr_register_container(VFIOIOMMUFDContainer * container,Error ** errp)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 
vfio_iommufd_cpr_unregister_container(VFIOIOMMUFDContainer * container)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 
vfio_iommufd_cpr_register_device(VFIODevice * vbasedev)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 
vfio_iommufd_cpr_unregister_device(VFIODevice * vbasedev)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 
vfio_cpr_load_device(VFIODevice * vbasedev)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