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