1 /*
2 * virmdev.c: helper APIs for managing host mediated devices
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see
16 * <http://www.gnu.org/licenses/>.
17 */
18
19 #include <config.h>
20
21 #include "virmdev.h"
22 #include "virlog.h"
23 #include "virerror.h"
24 #include "virfile.h"
25 #include "virstring.h"
26 #include "viralloc.h"
27
28 #define VIR_FROM_THIS VIR_FROM_NONE
29
30 #define MDEV_SYSFS_DEVICES "/sys/bus/mdev/devices/"
31
32 VIR_LOG_INIT("util.mdev");
33
34 struct _virMediatedDevice {
35 char *path; /* sysfs path */
36 virMediatedDeviceModelType model;
37
38 char *used_by_drvname;
39 char *used_by_domname;
40 };
41
42 struct _virMediatedDeviceList {
43 virObjectLockable parent;
44
45 size_t count;
46 virMediatedDevice **devs;
47 };
48
49 VIR_ENUM_IMPL(virMediatedDeviceModel,
50 VIR_MDEV_MODEL_TYPE_LAST,
51 "vfio-pci",
52 "vfio-ccw",
53 "vfio-ap",
54 );
55
56 static virClass *virMediatedDeviceListClass;
57
58 static void
59 virMediatedDeviceListDispose(void *obj);
60
61 static int
virMediatedOnceInit(void)62 virMediatedOnceInit(void)
63 {
64 if (!VIR_CLASS_NEW(virMediatedDeviceList, virClassForObjectLockable()))
65 return -1;
66
67 return 0;
68 }
69
70 VIR_ONCE_GLOBAL_INIT(virMediated);
71
72 #ifdef __linux__
73
74 static int
virMediatedDeviceGetSysfsDeviceAPI(virMediatedDevice * dev,char ** device_api)75 virMediatedDeviceGetSysfsDeviceAPI(virMediatedDevice *dev,
76 char **device_api)
77 {
78 g_autofree char *buf = NULL;
79 g_autofree char *file = NULL;
80 char *tmp = NULL;
81
82 file = g_strdup_printf("%s/mdev_type/device_api", dev->path);
83
84 /* TODO - make this a generic method to access sysfs files for various
85 * kinds of devices
86 */
87 if (!virFileExists(file)) {
88 virReportSystemError(errno, _("failed to read '%s'"), file);
89 return -1;
90 }
91
92 if (virFileReadAll(file, 1024, &buf) < 0)
93 return -1;
94
95 if ((tmp = strchr(buf, '\n')))
96 *tmp = '\0';
97
98 *device_api = g_steal_pointer(&buf);
99
100 return 0;
101 }
102
103
104 static int
virMediatedDeviceCheckModel(virMediatedDevice * dev,virMediatedDeviceModelType model)105 virMediatedDeviceCheckModel(virMediatedDevice *dev,
106 virMediatedDeviceModelType model)
107 {
108 g_autofree char *dev_api = NULL;
109 int actual_model;
110
111 if (virMediatedDeviceGetSysfsDeviceAPI(dev, &dev_api) < 0)
112 return -1;
113
114 /* safeguard in case we've got an older libvirt which doesn't know newer
115 * device_api models yet
116 */
117 if ((actual_model = virMediatedDeviceModelTypeFromString(dev_api)) < 0) {
118 virReportError(VIR_ERR_INTERNAL_ERROR,
119 _("device API '%s' not supported yet"),
120 dev_api);
121 return -1;
122 }
123
124 if (actual_model != model) {
125 virReportError(VIR_ERR_INTERNAL_ERROR,
126 _("invalid device API '%s' for device %s: "
127 "device only supports '%s'"),
128 virMediatedDeviceModelTypeToString(model),
129 dev->path, dev_api);
130 return -1;
131 }
132
133 return 0;
134 }
135
136
137 virMediatedDevice *
virMediatedDeviceNew(const char * uuidstr,virMediatedDeviceModelType model)138 virMediatedDeviceNew(const char *uuidstr, virMediatedDeviceModelType model)
139 {
140 g_autoptr(virMediatedDevice) dev = NULL;
141 g_autofree char *sysfspath = NULL;
142
143 sysfspath = virMediatedDeviceGetSysfsPath(uuidstr);
144 if (!virFileExists(sysfspath)) {
145 virReportError(VIR_ERR_DEVICE_MISSING,
146 _("mediated device '%s' not found"), uuidstr);
147 return NULL;
148 }
149
150 dev = g_new0(virMediatedDevice, 1);
151
152 dev->path = g_steal_pointer(&sysfspath);
153
154 /* Check whether the user-provided model corresponds with the actually
155 * supported mediated device's API.
156 */
157 if (virMediatedDeviceCheckModel(dev, model))
158 return NULL;
159
160 dev->model = model;
161 return g_steal_pointer(&dev);
162 }
163
164 #else
165
166 virMediatedDevice *
virMediatedDeviceNew(const char * uuidstr G_GNUC_UNUSED,virMediatedDeviceModelType model G_GNUC_UNUSED)167 virMediatedDeviceNew(const char *uuidstr G_GNUC_UNUSED,
168 virMediatedDeviceModelType model G_GNUC_UNUSED)
169 {
170 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
171 _("mediated devices are not supported on non-linux "
172 "platforms"));
173 return NULL;
174 }
175
176 #endif /* __linux__ */
177
178 void
virMediatedDeviceFree(virMediatedDevice * dev)179 virMediatedDeviceFree(virMediatedDevice *dev)
180 {
181 if (!dev)
182 return;
183 g_free(dev->path);
184 g_free(dev->used_by_drvname);
185 g_free(dev->used_by_domname);
186 g_free(dev);
187 }
188
189
190 const char *
virMediatedDeviceGetPath(virMediatedDevice * dev)191 virMediatedDeviceGetPath(virMediatedDevice *dev)
192 {
193 return dev->path;
194 }
195
196
197 /* Returns an absolute canonicalized path to the device used to control the
198 * mediated device's IOMMU group (e.g. "/dev/vfio/15"). Caller is responsible
199 * for freeing the result.
200 */
201 char *
virMediatedDeviceGetIOMMUGroupDev(const char * uuidstr)202 virMediatedDeviceGetIOMMUGroupDev(const char *uuidstr)
203 {
204 int group_num = virMediatedDeviceGetIOMMUGroupNum(uuidstr);
205
206 if (group_num < 0)
207 return NULL;
208
209 return g_strdup_printf("/dev/vfio/%i", group_num);
210 }
211
212
213 int
virMediatedDeviceGetIOMMUGroupNum(const char * uuidstr)214 virMediatedDeviceGetIOMMUGroupNum(const char *uuidstr)
215 {
216 g_autofree char *result_path = NULL;
217 g_autofree char *group_num_str = NULL;
218 g_autofree char *iommu_path = NULL;
219 g_autofree char *dev_path = virMediatedDeviceGetSysfsPath(uuidstr);
220 unsigned int group_num = -1;
221
222 iommu_path = g_strdup_printf("%s/iommu_group", dev_path);
223
224 if (!virFileExists(iommu_path)) {
225 virReportSystemError(errno, _("failed to access '%s'"), iommu_path);
226 return -1;
227 }
228
229 if (virFileResolveLink(iommu_path, &result_path) < 0) {
230 virReportSystemError(errno, _("failed to resolve '%s'"), iommu_path);
231 return -1;
232 }
233
234 group_num_str = g_path_get_basename(result_path);
235 ignore_value(virStrToLong_ui(group_num_str, NULL, 10, &group_num));
236 return group_num;
237 }
238
239
240 void
virMediatedDeviceGetUsedBy(virMediatedDevice * dev,const char ** drvname,const char ** domname)241 virMediatedDeviceGetUsedBy(virMediatedDevice *dev,
242 const char **drvname, const char **domname)
243 {
244 *drvname = dev->used_by_drvname;
245 *domname = dev->used_by_domname;
246 }
247
248
249 int
virMediatedDeviceSetUsedBy(virMediatedDevice * dev,const char * drvname,const char * domname)250 virMediatedDeviceSetUsedBy(virMediatedDevice *dev,
251 const char *drvname,
252 const char *domname)
253 {
254 VIR_FREE(dev->used_by_drvname);
255 VIR_FREE(dev->used_by_domname);
256 dev->used_by_drvname = g_strdup(drvname);
257 dev->used_by_domname = g_strdup(domname);
258
259 return 0;
260 }
261
262
263 virMediatedDeviceList *
virMediatedDeviceListNew(void)264 virMediatedDeviceListNew(void)
265 {
266 virMediatedDeviceList *list;
267
268 if (virMediatedInitialize() < 0)
269 return NULL;
270
271 if (!(list = virObjectLockableNew(virMediatedDeviceListClass)))
272 return NULL;
273
274 return list;
275 }
276
277
278 static void
virMediatedDeviceListDispose(void * obj)279 virMediatedDeviceListDispose(void *obj)
280 {
281 virMediatedDeviceList *list = obj;
282 size_t i;
283
284 for (i = 0; i < list->count; i++) {
285 virMediatedDeviceFree(list->devs[i]);
286 list->devs[i] = NULL;
287 }
288
289 list->count = 0;
290 g_free(list->devs);
291 }
292
293
294 /* The reason for @dev to be double pointer is that VIR_APPEND_ELEMENT clears
295 * the pointer and we need to clear the original not a copy on the stack
296 */
297 int
virMediatedDeviceListAdd(virMediatedDeviceList * list,virMediatedDevice ** dev)298 virMediatedDeviceListAdd(virMediatedDeviceList *list,
299 virMediatedDevice **dev)
300 {
301 if (virMediatedDeviceListFind(list, (*dev)->path)) {
302 virReportError(VIR_ERR_INTERNAL_ERROR,
303 _("device %s is already in use"), (*dev)->path);
304 return -1;
305 }
306 VIR_APPEND_ELEMENT(list->devs, list->count, *dev);
307
308 return 0;
309 }
310
311
312 virMediatedDevice *
virMediatedDeviceListGet(virMediatedDeviceList * list,ssize_t idx)313 virMediatedDeviceListGet(virMediatedDeviceList *list,
314 ssize_t idx)
315 {
316 if (idx < 0 || idx >= list->count)
317 return NULL;
318
319 return list->devs[idx];
320 }
321
322
323 size_t
virMediatedDeviceListCount(virMediatedDeviceList * list)324 virMediatedDeviceListCount(virMediatedDeviceList *list)
325 {
326 return list->count;
327 }
328
329
330 virMediatedDevice *
virMediatedDeviceListStealIndex(virMediatedDeviceList * list,ssize_t idx)331 virMediatedDeviceListStealIndex(virMediatedDeviceList *list,
332 ssize_t idx)
333 {
334 virMediatedDevice *ret;
335
336 if (idx < 0 || idx >= list->count)
337 return NULL;
338
339 ret = list->devs[idx];
340 VIR_DELETE_ELEMENT(list->devs, idx, list->count);
341 return ret;
342 }
343
344
345 virMediatedDevice *
virMediatedDeviceListSteal(virMediatedDeviceList * list,virMediatedDevice * dev)346 virMediatedDeviceListSteal(virMediatedDeviceList *list,
347 virMediatedDevice *dev)
348 {
349 int idx = -1;
350
351 if (!dev)
352 return NULL;
353
354 idx = virMediatedDeviceListFindIndex(list, dev->path);
355
356 return virMediatedDeviceListStealIndex(list, idx);
357 }
358
359
360 void
virMediatedDeviceListDel(virMediatedDeviceList * list,virMediatedDevice * dev)361 virMediatedDeviceListDel(virMediatedDeviceList *list,
362 virMediatedDevice *dev)
363 {
364 virMediatedDeviceFree(virMediatedDeviceListSteal(list, dev));
365 }
366
367
368 int
virMediatedDeviceListFindIndex(virMediatedDeviceList * list,const char * sysfspath)369 virMediatedDeviceListFindIndex(virMediatedDeviceList *list,
370 const char *sysfspath)
371 {
372 size_t i;
373
374 for (i = 0; i < list->count; i++) {
375 virMediatedDevice *dev = list->devs[i];
376 if (STREQ(sysfspath, dev->path))
377 return i;
378 }
379 return -1;
380 }
381
382
383 virMediatedDevice *
virMediatedDeviceListFind(virMediatedDeviceList * list,const char * sysfspath)384 virMediatedDeviceListFind(virMediatedDeviceList *list,
385 const char *sysfspath)
386 {
387 int idx;
388
389 if ((idx = virMediatedDeviceListFindIndex(list, sysfspath)) >= 0)
390 return list->devs[idx];
391 else
392 return NULL;
393 }
394
395
396 bool
virMediatedDeviceIsUsed(virMediatedDevice * dev,virMediatedDeviceList * list)397 virMediatedDeviceIsUsed(virMediatedDevice *dev,
398 virMediatedDeviceList *list)
399 {
400 const char *drvname, *domname;
401 virMediatedDevice *tmp = NULL;
402
403 if ((tmp = virMediatedDeviceListFind(list, dev->path))) {
404 virMediatedDeviceGetUsedBy(tmp, &drvname, &domname);
405 virReportError(VIR_ERR_OPERATION_INVALID,
406 _("mediated device %s is in use by "
407 "driver %s, domain %s"),
408 tmp->path, drvname, domname);
409 }
410
411 return !!tmp;
412 }
413
414
415 char *
virMediatedDeviceGetSysfsPath(const char * uuidstr)416 virMediatedDeviceGetSysfsPath(const char *uuidstr)
417 {
418 return g_strdup_printf(MDEV_SYSFS_DEVICES "%s", uuidstr);
419 }
420
421
422 int
virMediatedDeviceListMarkDevices(virMediatedDeviceList * dst,virMediatedDeviceList * src,const char * drvname,const char * domname)423 virMediatedDeviceListMarkDevices(virMediatedDeviceList *dst,
424 virMediatedDeviceList *src,
425 const char *drvname,
426 const char *domname)
427 {
428 int ret = -1;
429 size_t count = virMediatedDeviceListCount(src);
430 size_t i, j;
431
432 virObjectLock(dst);
433 for (i = 0; i < count; i++) {
434 virMediatedDevice *mdev = virMediatedDeviceListGet(src, i);
435
436 if (virMediatedDeviceIsUsed(mdev, dst) ||
437 virMediatedDeviceSetUsedBy(mdev, drvname, domname) < 0)
438 goto rollback;
439
440 /* Copy mdev references to the driver list:
441 * - caller is responsible for NOT freeing devices in @src on success
442 * - we're responsible for performing a rollback on failure
443 */
444 VIR_DEBUG("Add '%s' to list of active mediated devices used by '%s'",
445 mdev->path, domname);
446 if (virMediatedDeviceListAdd(dst, &mdev) < 0)
447 goto rollback;
448
449 }
450
451 ret = 0;
452 cleanup:
453 virObjectUnlock(dst);
454 return ret;
455
456 rollback:
457 for (j = 0; j < i; j++) {
458 virMediatedDevice *tmp = virMediatedDeviceListGet(src, j);
459 virMediatedDeviceListSteal(dst, tmp);
460 }
461 goto cleanup;
462 }
463
464
465 void
virMediatedDeviceTypeFree(virMediatedDeviceType * type)466 virMediatedDeviceTypeFree(virMediatedDeviceType *type)
467 {
468 if (!type)
469 return;
470
471 g_free(type->id);
472 g_free(type->name);
473 g_free(type->device_api);
474 g_free(type);
475 }
476
477
478 int
virMediatedDeviceTypeReadAttrs(const char * sysfspath,virMediatedDeviceType ** type)479 virMediatedDeviceTypeReadAttrs(const char *sysfspath,
480 virMediatedDeviceType **type)
481 {
482 g_autoptr(virMediatedDeviceType) tmp = NULL;
483
484 #define MDEV_GET_SYSFS_ATTR(attr, dst, cb, optional) \
485 do { \
486 int rc; \
487 if ((rc = cb(dst, "%s/%s", sysfspath, attr)) < 0) { \
488 if (rc != -2 || !optional) \
489 return -1; \
490 } \
491 } while (0)
492
493 tmp = g_new0(virMediatedDeviceType, 1);
494
495 tmp->id = g_path_get_basename(sysfspath);
496
497 /* @name sysfs attribute is optional, so getting ENOENT is fine */
498 MDEV_GET_SYSFS_ATTR("name", &tmp->name, virFileReadValueString, true);
499 MDEV_GET_SYSFS_ATTR("device_api", &tmp->device_api,
500 virFileReadValueString, false);
501 MDEV_GET_SYSFS_ATTR("available_instances", &tmp->available_instances,
502 virFileReadValueUint, false);
503
504 #undef MDEV_GET_SYSFS_ATTR
505
506 *type = g_steal_pointer(&tmp);
507
508 return 0;
509 }
510
virMediatedDeviceAttrNew(void)511 virMediatedDeviceAttr *virMediatedDeviceAttrNew(void)
512 {
513 return g_new0(virMediatedDeviceAttr, 1);
514 }
515
virMediatedDeviceAttrFree(virMediatedDeviceAttr * attr)516 void virMediatedDeviceAttrFree(virMediatedDeviceAttr *attr)
517 {
518 g_free(attr->name);
519 g_free(attr->value);
520 g_free(attr);
521 }
522
523
524 #ifdef __linux__
525
526 ssize_t
virMediatedDeviceGetMdevTypes(const char * sysfspath,virMediatedDeviceType *** types,size_t * ntypes)527 virMediatedDeviceGetMdevTypes(const char *sysfspath,
528 virMediatedDeviceType ***types,
529 size_t *ntypes)
530 {
531 ssize_t ret = -1;
532 int dirret = -1;
533 g_autoptr(DIR) dir = NULL;
534 struct dirent *entry;
535 g_autofree char *types_path = NULL;
536 g_autoptr(virMediatedDeviceType) mdev_type = NULL;
537 virMediatedDeviceType **mdev_types = NULL;
538 size_t nmdev_types = 0;
539 size_t i;
540
541 types_path = g_strdup_printf("%s/mdev_supported_types", sysfspath);
542
543 if ((dirret = virDirOpenIfExists(&dir, types_path)) < 0)
544 goto cleanup;
545
546 if (dirret == 0) {
547 ret = 0;
548 goto cleanup;
549 }
550
551 while ((dirret = virDirRead(dir, &entry, types_path)) > 0) {
552 g_autofree char *tmppath = NULL;
553 /* append the type id to the path and read the attributes from there */
554 tmppath = g_strdup_printf("%s/%s", types_path, entry->d_name);
555
556 if (virMediatedDeviceTypeReadAttrs(tmppath, &mdev_type) < 0)
557 goto cleanup;
558
559 VIR_APPEND_ELEMENT(mdev_types, nmdev_types, mdev_type);
560 }
561
562 if (dirret < 0)
563 goto cleanup;
564
565 *types = g_steal_pointer(&mdev_types);
566 *ntypes = nmdev_types;
567 nmdev_types = 0;
568 ret = 0;
569 cleanup:
570 for (i = 0; i < nmdev_types; i++)
571 virMediatedDeviceTypeFree(mdev_types[i]);
572 VIR_FREE(mdev_types);
573 return ret;
574 }
575
576 #else
577 static const char *unsupported = N_("not supported on non-linux platforms");
578
579 ssize_t
virMediatedDeviceGetMdevTypes(const char * sysfspath G_GNUC_UNUSED,virMediatedDeviceType *** types G_GNUC_UNUSED,size_t * ntypes G_GNUC_UNUSED)580 virMediatedDeviceGetMdevTypes(const char *sysfspath G_GNUC_UNUSED,
581 virMediatedDeviceType ***types G_GNUC_UNUSED,
582 size_t *ntypes G_GNUC_UNUSED)
583 {
584 virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported));
585 return -1;
586 }
587
588 #endif /* __linux__ */
589