| 1 | /* |
| 2 | * iommufd container backend |
| 3 | * |
| 4 | * Copyright (C) 2023 Intel Corporation. |
| 5 | * Copyright Red Hat, Inc. 2023 |
| 6 | * |
| 7 | * Authors: Yi Liu <yi.l.liu@intel.com> |
| 8 | * Eric Auger <eric.auger@redhat.com> |
| 9 | * |
| 10 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 11 | */ |
| 12 | |
| 13 | #include "qemu/osdep.h" |
| 14 | #include "system/iommufd.h" |
| 15 | #include "qapi/error.h" |
| 16 | #include "qemu/module.h" |
| 17 | #include "qom/object_interfaces.h" |
| 18 | #include "qemu/error-report.h" |
| 19 | #include "migration/cpr.h" |
| 20 | #include "monitor/monitor.h" |
| 21 | #include "trace.h" |
| 22 | #include "hw/vfio/vfio-device.h" |
| 23 | #include <sys/ioctl.h> |
| 24 | #include <linux/iommufd.h> |
| 25 | |
| 26 | static const char *iommufd_fd_name(IOMMUFDBackend *be) |
| 27 | { |
| 28 | return object_get_canonical_path_component(OBJECT(be)); |
| 29 | } |
| 30 | |
| 31 | static void iommufd_backend_init(Object *obj) |
| 32 | { |
| 33 | IOMMUFDBackend *be = IOMMUFD_BACKEND(obj); |
| 34 | |
| 35 | be->fd = -1; |
| 36 | be->users = 0; |
| 37 | be->owned = true; |
| 38 | } |
| 39 | |
| 40 | static void iommufd_backend_finalize(Object *obj) |
| 41 | { |
| 42 | IOMMUFDBackend *be = IOMMUFD_BACKEND(obj); |
| 43 | |
| 44 | if (be->owned) { |
| 45 | close(be->fd); |
| 46 | be->fd = -1; |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | static void iommufd_backend_set_fd(Object *obj, const char *str, Error **errp) |
| 51 | { |
| 52 | ERRP_GUARD(); |
| 53 | IOMMUFDBackend *be = IOMMUFD_BACKEND(obj); |
| 54 | int fd = -1; |
| 55 | |
| 56 | fd = monitor_fd_param(monitor_cur(), str, errp); |
| 57 | if (fd == -1) { |
| 58 | error_prepend(errp, "Could not parse remote object fd %s:", str); |
| 59 | return; |
| 60 | } |
| 61 | be->fd = fd; |
| 62 | be->owned = false; |
| 63 | trace_iommu_backend_set_fd(be->fd); |
| 64 | } |
| 65 | |
| 66 | static bool iommufd_backend_prepare_delete(UserCreatable *uc, Error **errp) |
| 67 | { |
| 68 | IOMMUFDBackend *be = IOMMUFD_BACKEND(uc); |
| 69 | |
| 70 | if (be->users) { |
| 71 | error_setg(errp, "Can not delete IOMMUFD backend '%s' with %d users", |
| 72 | object_get_canonical_path_component(OBJECT(uc)), be->users); |
| 73 | return false; |
| 74 | } |
| 75 | return true; |
| 76 | } |
| 77 | |
| 78 | static void iommufd_backend_complete(UserCreatable *uc, Error **errp) |
| 79 | { |
| 80 | IOMMUFDBackend *be = IOMMUFD_BACKEND(uc); |
| 81 | const char *name = iommufd_fd_name(be); |
| 82 | |
| 83 | if (!be->owned) { |
| 84 | /* fd came from the command line. Fetch updated value from cpr state. */ |
| 85 | if (cpr_is_incoming()) { |
| 86 | be->fd = cpr_find_fd(name, 0); |
| 87 | } else { |
| 88 | cpr_save_fd(name, 0, be->fd); |
| 89 | } |
| 90 | } else if (!g_file_test("/dev/iommu", G_FILE_TEST_EXISTS)) { |
| 91 | error_setg(errp, "/dev/iommu does not exist" |
| 92 | " (is your kernel config missing CONFIG_IOMMUFD?)"); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | static void iommufd_backend_class_init(ObjectClass *oc, const void *data) |
| 97 | { |
| 98 | UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); |
| 99 | |
| 100 | ucc->prepare_delete = iommufd_backend_prepare_delete; |
| 101 | ucc->complete = iommufd_backend_complete; |
| 102 | |
| 103 | object_class_property_add_str(oc, "fd", NULL, iommufd_backend_set_fd); |
| 104 | } |
| 105 | |
| 106 | bool iommufd_change_process_capable(IOMMUFDBackend *be) |
| 107 | { |
| 108 | struct iommu_ioas_change_process args = {.size = sizeof(args)}; |
| 109 | |
| 110 | /* |
| 111 | * Call IOMMU_IOAS_CHANGE_PROCESS to verify it is a recognized ioctl. |
| 112 | * This is a no-op if the process has not changed since DMA was mapped. |
| 113 | */ |
| 114 | return !ioctl(be->fd, IOMMU_IOAS_CHANGE_PROCESS, &args); |
| 115 | } |
| 116 | |
| 117 | bool iommufd_change_process(IOMMUFDBackend *be, Error **errp) |
| 118 | { |
| 119 | struct iommu_ioas_change_process args = {.size = sizeof(args)}; |
| 120 | bool ret = !ioctl(be->fd, IOMMU_IOAS_CHANGE_PROCESS, &args); |
| 121 | |
| 122 | if (!ret) { |
| 123 | error_setg_errno(errp, errno, "IOMMU_IOAS_CHANGE_PROCESS fd %d failed", |
| 124 | be->fd); |
| 125 | } |
| 126 | trace_iommufd_change_process(be->fd, ret); |
| 127 | return ret; |
| 128 | } |
| 129 | |
| 130 | bool iommufd_backend_connect(IOMMUFDBackend *be, Error **errp) |
| 131 | { |
| 132 | int fd; |
| 133 | |
| 134 | if (be->owned && !be->users) { |
| 135 | fd = cpr_open_fd("/dev/iommu", O_RDWR, iommufd_fd_name(be), 0, errp); |
| 136 | if (fd < 0) { |
| 137 | return false; |
| 138 | } |
| 139 | be->fd = fd; |
| 140 | } |
| 141 | if (!be->users && !vfio_iommufd_cpr_register_iommufd(be, errp)) { |
| 142 | if (be->owned) { |
| 143 | close(be->fd); |
| 144 | be->fd = -1; |
| 145 | } |
| 146 | return false; |
| 147 | } |
| 148 | be->users++; |
| 149 | |
| 150 | trace_iommufd_backend_connect(be->fd, be->owned, be->users); |
| 151 | return true; |
| 152 | } |
| 153 | |
| 154 | void iommufd_backend_disconnect(IOMMUFDBackend *be) |
| 155 | { |
| 156 | if (!be->users) { |
| 157 | goto out; |
| 158 | } |
| 159 | be->users--; |
| 160 | if (!be->users) { |
| 161 | vfio_iommufd_cpr_unregister_iommufd(be); |
| 162 | if (be->owned) { |
| 163 | cpr_delete_fd(iommufd_fd_name(be), 0); |
| 164 | close(be->fd); |
| 165 | be->fd = -1; |
| 166 | } |
| 167 | } |
| 168 | out: |
| 169 | trace_iommufd_backend_disconnect(be->fd, be->users); |
| 170 | } |
| 171 | |
| 172 | bool iommufd_backend_alloc_ioas(IOMMUFDBackend *be, uint32_t *ioas_id, |
| 173 | Error **errp) |
| 174 | { |
| 175 | int fd = be->fd; |
| 176 | struct iommu_ioas_alloc alloc_data = { |
| 177 | .size = sizeof(alloc_data), |
| 178 | .flags = 0, |
| 179 | }; |
| 180 | |
| 181 | if (ioctl(fd, IOMMU_IOAS_ALLOC, &alloc_data)) { |
| 182 | error_setg_errno(errp, errno, "Failed to allocate ioas"); |
| 183 | return false; |
| 184 | } |
| 185 | |
| 186 | *ioas_id = alloc_data.out_ioas_id; |
| 187 | trace_iommufd_backend_alloc_ioas(fd, *ioas_id); |
| 188 | |
| 189 | return true; |
| 190 | } |
| 191 | |
| 192 | void iommufd_backend_free_id(IOMMUFDBackend *be, uint32_t id) |
| 193 | { |
| 194 | int ret, fd = be->fd; |
| 195 | struct iommu_destroy des = { |
| 196 | .size = sizeof(des), |
| 197 | .id = id, |
| 198 | }; |
| 199 | |
| 200 | ret = ioctl(fd, IOMMU_DESTROY, &des); |
| 201 | trace_iommufd_backend_free_id(fd, id, ret); |
| 202 | if (ret) { |
| 203 | error_report("Failed to free id: %u %m", id); |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | int iommufd_backend_map_dma(IOMMUFDBackend *be, uint32_t ioas_id, hwaddr iova, |
| 208 | uint64_t size, void *vaddr, bool readonly) |
| 209 | { |
| 210 | int ret, fd = be->fd; |
| 211 | struct iommu_ioas_map map = { |
| 212 | .size = sizeof(map), |
| 213 | .flags = IOMMU_IOAS_MAP_READABLE | |
| 214 | IOMMU_IOAS_MAP_FIXED_IOVA, |
| 215 | .ioas_id = ioas_id, |
| 216 | .__reserved = 0, |
| 217 | .user_va = (uintptr_t)vaddr, |
| 218 | .iova = iova, |
| 219 | .length = size, |
| 220 | }; |
| 221 | |
| 222 | if (!readonly) { |
| 223 | map.flags |= IOMMU_IOAS_MAP_WRITEABLE; |
| 224 | } |
| 225 | |
| 226 | ret = ioctl(fd, IOMMU_IOAS_MAP, &map); |
| 227 | trace_iommufd_backend_map_dma(fd, ioas_id, iova, size, |
| 228 | vaddr, readonly, ret); |
| 229 | if (ret) { |
| 230 | ret = -errno; |
| 231 | |
| 232 | /* TODO: Not support mapping hardware PCI BAR region for now. */ |
| 233 | if (errno == EFAULT) { |
| 234 | warn_report("IOMMU_IOAS_MAP failed: %m, PCI BAR?"); |
| 235 | } |
| 236 | } |
| 237 | return ret; |
| 238 | } |
| 239 | |
| 240 | int iommufd_backend_map_file_dma(IOMMUFDBackend *be, uint32_t ioas_id, |
| 241 | hwaddr iova, uint64_t size, |
| 242 | int mfd, unsigned long start, bool readonly) |
| 243 | { |
| 244 | int ret, fd = be->fd; |
| 245 | struct iommu_ioas_map_file map = { |
| 246 | .size = sizeof(map), |
| 247 | .flags = IOMMU_IOAS_MAP_READABLE | |
| 248 | IOMMU_IOAS_MAP_FIXED_IOVA, |
| 249 | .ioas_id = ioas_id, |
| 250 | .fd = mfd, |
| 251 | .start = start, |
| 252 | .iova = iova, |
| 253 | .length = size, |
| 254 | }; |
| 255 | |
| 256 | if (cpr_is_incoming()) { |
| 257 | return 0; |
| 258 | } |
| 259 | |
| 260 | if (!readonly) { |
| 261 | map.flags |= IOMMU_IOAS_MAP_WRITEABLE; |
| 262 | } |
| 263 | |
| 264 | ret = ioctl(fd, IOMMU_IOAS_MAP_FILE, &map); |
| 265 | trace_iommufd_backend_map_file_dma(fd, ioas_id, iova, size, mfd, start, |
| 266 | readonly, ret); |
| 267 | if (ret) { |
| 268 | ret = -errno; |
| 269 | |
| 270 | /* TODO: Not support mapping hardware PCI BAR region for now. */ |
| 271 | if (errno == EFAULT) { |
| 272 | warn_report("IOMMU_IOAS_MAP_FILE failed: %m, PCI BAR?"); |
| 273 | } |
| 274 | } |
| 275 | return ret; |
| 276 | } |
| 277 | |
| 278 | int iommufd_backend_unmap_dma(IOMMUFDBackend *be, uint32_t ioas_id, |
| 279 | hwaddr iova, uint64_t size) |
| 280 | { |
| 281 | int ret, fd = be->fd; |
| 282 | struct iommu_ioas_unmap unmap = { |
| 283 | .size = sizeof(unmap), |
| 284 | .ioas_id = ioas_id, |
| 285 | .iova = iova, |
| 286 | .length = size, |
| 287 | }; |
| 288 | |
| 289 | if (cpr_is_incoming()) { |
| 290 | return 0; |
| 291 | } |
| 292 | |
| 293 | ret = ioctl(fd, IOMMU_IOAS_UNMAP, &unmap); |
| 294 | /* |
| 295 | * IOMMUFD takes mapping as some kind of object, unmapping |
| 296 | * nonexistent mapping is treated as deleting a nonexistent |
| 297 | * object and return ENOENT. This is different from legacy |
| 298 | * backend which allows it. vIOMMU may trigger a lot of |
| 299 | * redundant unmapping, to avoid flush the log, treat them |
| 300 | * as succeess for IOMMUFD just like legacy backend. |
| 301 | */ |
| 302 | if (ret && errno == ENOENT) { |
| 303 | trace_iommufd_backend_unmap_dma_non_exist(fd, ioas_id, iova, size, ret); |
| 304 | ret = 0; |
| 305 | } else { |
| 306 | trace_iommufd_backend_unmap_dma(fd, ioas_id, iova, size, ret); |
| 307 | } |
| 308 | |
| 309 | if (ret) { |
| 310 | ret = -errno; |
| 311 | } |
| 312 | return ret; |
| 313 | } |
| 314 | |
| 315 | bool iommufd_backend_alloc_hwpt(IOMMUFDBackend *be, uint32_t dev_id, |
| 316 | uint32_t pt_id, uint32_t flags, |
| 317 | uint32_t data_type, uint32_t data_len, |
| 318 | void *data_ptr, uint32_t *out_hwpt, |
| 319 | Error **errp) |
| 320 | { |
| 321 | int ret, fd = be->fd; |
| 322 | struct iommu_hwpt_alloc alloc_hwpt = { |
| 323 | .size = sizeof(struct iommu_hwpt_alloc), |
| 324 | .flags = flags, |
| 325 | .dev_id = dev_id, |
| 326 | .pt_id = pt_id, |
| 327 | .data_type = data_type, |
| 328 | .data_len = data_len, |
| 329 | .data_uptr = (uintptr_t)data_ptr, |
| 330 | }; |
| 331 | |
| 332 | ret = ioctl(fd, IOMMU_HWPT_ALLOC, &alloc_hwpt); |
| 333 | trace_iommufd_backend_alloc_hwpt(fd, dev_id, pt_id, flags, data_type, |
| 334 | data_len, (uintptr_t)data_ptr, |
| 335 | alloc_hwpt.out_hwpt_id, ret); |
| 336 | if (ret) { |
| 337 | error_setg_errno(errp, errno, "Failed to allocate hwpt"); |
| 338 | return false; |
| 339 | } |
| 340 | |
| 341 | *out_hwpt = alloc_hwpt.out_hwpt_id; |
| 342 | return true; |
| 343 | } |
| 344 | |
| 345 | bool iommufd_backend_set_dirty_tracking(IOMMUFDBackend *be, |
| 346 | uint32_t hwpt_id, bool start, |
| 347 | Error **errp) |
| 348 | { |
| 349 | int ret; |
| 350 | struct iommu_hwpt_set_dirty_tracking set_dirty = { |
| 351 | .size = sizeof(set_dirty), |
| 352 | .hwpt_id = hwpt_id, |
| 353 | .flags = start ? IOMMU_HWPT_DIRTY_TRACKING_ENABLE : 0, |
| 354 | }; |
| 355 | |
| 356 | ret = ioctl(be->fd, IOMMU_HWPT_SET_DIRTY_TRACKING, &set_dirty); |
| 357 | trace_iommufd_backend_set_dirty(be->fd, hwpt_id, start, ret ? errno : 0); |
| 358 | if (ret) { |
| 359 | error_setg_errno(errp, errno, |
| 360 | "IOMMU_HWPT_SET_DIRTY_TRACKING(hwpt_id %u) failed", |
| 361 | hwpt_id); |
| 362 | return false; |
| 363 | } |
| 364 | |
| 365 | return true; |
| 366 | } |
| 367 | |
| 368 | bool iommufd_backend_get_dirty_bitmap(IOMMUFDBackend *be, |
| 369 | uint32_t hwpt_id, |
| 370 | uint64_t iova, ram_addr_t size, |
| 371 | uint64_t page_size, uint64_t *data, |
| 372 | uint64_t flags, Error **errp) |
| 373 | { |
| 374 | int ret; |
| 375 | struct iommu_hwpt_get_dirty_bitmap get_dirty_bitmap = { |
| 376 | .size = sizeof(get_dirty_bitmap), |
| 377 | .hwpt_id = hwpt_id, |
| 378 | .iova = iova, |
| 379 | .length = size, |
| 380 | .page_size = page_size, |
| 381 | .data = (uintptr_t)data, |
| 382 | .flags = flags, |
| 383 | }; |
| 384 | |
| 385 | ret = ioctl(be->fd, IOMMU_HWPT_GET_DIRTY_BITMAP, &get_dirty_bitmap); |
| 386 | trace_iommufd_backend_get_dirty_bitmap(be->fd, hwpt_id, iova, size, |
| 387 | flags, page_size, ret ? errno : 0); |
| 388 | if (ret) { |
| 389 | error_setg_errno(errp, errno, |
| 390 | "IOMMU_HWPT_GET_DIRTY_BITMAP (iova: 0x%"HWADDR_PRIx |
| 391 | " size: 0x"RAM_ADDR_FMT") failed", iova, size); |
| 392 | return false; |
| 393 | } |
| 394 | |
| 395 | return true; |
| 396 | } |
| 397 | |
| 398 | /* |
| 399 | * @type can carry a desired HW info type defined in the uapi headers. If caller |
| 400 | * doesn't have one, indicating it wants the default type, then @type should be |
| 401 | * zeroed (i.e. IOMMU_HW_INFO_TYPE_DEFAULT). |
| 402 | */ |
| 403 | bool iommufd_backend_get_device_info(IOMMUFDBackend *be, uint32_t devid, |
| 404 | uint32_t *type, void *data, uint32_t len, |
| 405 | uint64_t *caps, uint8_t *max_pasid_log2, |
| 406 | Error **errp) |
| 407 | { |
| 408 | struct iommu_hw_info info = { |
| 409 | .flags = (*type) ? IOMMU_HW_INFO_FLAG_INPUT_TYPE : 0, |
| 410 | .size = sizeof(info), |
| 411 | .dev_id = devid, |
| 412 | .data_len = len, |
| 413 | .data_uptr = (uintptr_t)data, |
| 414 | .in_data_type = *type, |
| 415 | }; |
| 416 | |
| 417 | if (ioctl(be->fd, IOMMU_GET_HW_INFO, &info)) { |
| 418 | error_setg_errno(errp, errno, "Failed to get hardware info"); |
| 419 | return false; |
| 420 | } |
| 421 | |
| 422 | g_assert(type); |
| 423 | *type = info.out_data_type; |
| 424 | g_assert(caps); |
| 425 | *caps = info.out_capabilities; |
| 426 | |
| 427 | if (max_pasid_log2) { |
| 428 | *max_pasid_log2 = info.out_max_pasid_log2; |
| 429 | } |
| 430 | return true; |
| 431 | } |
| 432 | |
| 433 | bool iommufd_backend_invalidate_cache(IOMMUFDBackend *be, uint32_t id, |
| 434 | uint32_t data_type, uint32_t entry_len, |
| 435 | uint32_t *entry_num, void *data, |
| 436 | Error **errp) |
| 437 | { |
| 438 | int ret, fd = be->fd; |
| 439 | uint32_t total_entries = *entry_num; |
| 440 | struct iommu_hwpt_invalidate cache = { |
| 441 | .size = sizeof(cache), |
| 442 | .hwpt_id = id, |
| 443 | .data_type = data_type, |
| 444 | .entry_len = entry_len, |
| 445 | .entry_num = total_entries, |
| 446 | .data_uptr = (uintptr_t)data, |
| 447 | }; |
| 448 | |
| 449 | ret = ioctl(fd, IOMMU_HWPT_INVALIDATE, &cache); |
| 450 | trace_iommufd_backend_invalidate_cache(fd, id, data_type, entry_len, |
| 451 | total_entries, cache.entry_num, |
| 452 | (uintptr_t)data, ret ? errno : 0); |
| 453 | *entry_num = cache.entry_num; |
| 454 | |
| 455 | if (ret) { |
| 456 | error_setg_errno(errp, errno, "IOMMU_HWPT_INVALIDATE failed:" |
| 457 | " total %d entries, processed %d entries", |
| 458 | total_entries, cache.entry_num); |
| 459 | } else if (total_entries != cache.entry_num) { |
| 460 | error_setg(errp, "IOMMU_HWPT_INVALIDATE succeed but with unprocessed" |
| 461 | " entries: total %d entries, processed %d entries." |
| 462 | " Kernel BUG?!", total_entries, cache.entry_num); |
| 463 | return false; |
| 464 | } |
| 465 | |
| 466 | return !ret; |
| 467 | } |
| 468 | |
| 469 | bool iommufd_backend_alloc_viommu(IOMMUFDBackend *be, uint32_t dev_id, |
| 470 | uint32_t viommu_type, uint32_t hwpt_id, |
| 471 | void *data_ptr, uint32_t data_len, |
| 472 | uint32_t *out_viommu_id, Error **errp) |
| 473 | { |
| 474 | int ret; |
| 475 | struct iommu_viommu_alloc alloc_viommu = { |
| 476 | .size = sizeof(alloc_viommu), |
| 477 | .type = viommu_type, |
| 478 | .dev_id = dev_id, |
| 479 | .hwpt_id = hwpt_id, |
| 480 | .data_len = data_len, |
| 481 | .data_uptr = (uintptr_t)data_ptr, |
| 482 | }; |
| 483 | |
| 484 | ret = ioctl(be->fd, IOMMU_VIOMMU_ALLOC, &alloc_viommu); |
| 485 | |
| 486 | trace_iommufd_backend_alloc_viommu(be->fd, dev_id, viommu_type, hwpt_id, |
| 487 | (uintptr_t)data_ptr, data_len, |
| 488 | alloc_viommu.out_viommu_id, ret); |
| 489 | if (ret) { |
| 490 | error_setg_errno(errp, errno, "IOMMU_VIOMMU_ALLOC failed"); |
| 491 | return false; |
| 492 | } |
| 493 | |
| 494 | g_assert(out_viommu_id); |
| 495 | *out_viommu_id = alloc_viommu.out_viommu_id; |
| 496 | return true; |
| 497 | } |
| 498 | |
| 499 | bool iommufd_backend_alloc_vdev(IOMMUFDBackend *be, uint32_t dev_id, |
| 500 | uint32_t viommu_id, uint64_t virt_id, |
| 501 | uint32_t *out_vdev_id, Error **errp) |
| 502 | { |
| 503 | int ret; |
| 504 | struct iommu_vdevice_alloc alloc_vdev = { |
| 505 | .size = sizeof(alloc_vdev), |
| 506 | .viommu_id = viommu_id, |
| 507 | .dev_id = dev_id, |
| 508 | .virt_id = virt_id, |
| 509 | }; |
| 510 | |
| 511 | ret = ioctl(be->fd, IOMMU_VDEVICE_ALLOC, &alloc_vdev); |
| 512 | |
| 513 | trace_iommufd_backend_alloc_vdev(be->fd, dev_id, viommu_id, virt_id, |
| 514 | alloc_vdev.out_vdevice_id, ret); |
| 515 | |
| 516 | if (ret) { |
| 517 | error_setg_errno(errp, errno, "IOMMU_VDEVICE_ALLOC failed"); |
| 518 | return false; |
| 519 | } |
| 520 | |
| 521 | g_assert(out_vdev_id); |
| 522 | *out_vdev_id = alloc_vdev.out_vdevice_id; |
| 523 | return true; |
| 524 | } |
| 525 | |
| 526 | bool iommufd_backend_alloc_veventq(IOMMUFDBackend *be, uint32_t viommu_id, |
| 527 | uint32_t type, uint32_t depth, |
| 528 | uint32_t *out_veventq_id, |
| 529 | uint32_t *out_veventq_fd, Error **errp) |
| 530 | { |
| 531 | int ret; |
| 532 | struct iommu_veventq_alloc alloc_veventq = { |
| 533 | .size = sizeof(alloc_veventq), |
| 534 | .flags = 0, |
| 535 | .type = type, |
| 536 | .veventq_depth = depth, |
| 537 | .viommu_id = viommu_id, |
| 538 | }; |
| 539 | |
| 540 | ret = ioctl(be->fd, IOMMU_VEVENTQ_ALLOC, &alloc_veventq); |
| 541 | |
| 542 | trace_iommufd_viommu_alloc_eventq(be->fd, viommu_id, type, |
| 543 | alloc_veventq.out_veventq_id, |
| 544 | alloc_veventq.out_veventq_fd, ret); |
| 545 | if (ret) { |
| 546 | error_setg_errno(errp, errno, "IOMMU_VEVENTQ_ALLOC failed"); |
| 547 | return false; |
| 548 | } |
| 549 | |
| 550 | g_assert(out_veventq_id); |
| 551 | g_assert(out_veventq_fd); |
| 552 | *out_veventq_id = alloc_veventq.out_veventq_id; |
| 553 | *out_veventq_fd = alloc_veventq.out_veventq_fd; |
| 554 | return true; |
| 555 | } |
| 556 | |
| 557 | bool iommufd_backend_alloc_hw_queue(IOMMUFDBackend *be, uint32_t viommu_id, |
| 558 | uint32_t queue_type, uint32_t index, |
| 559 | uint64_t addr, uint64_t length, |
| 560 | uint32_t *out_hw_queue_id, Error **errp) |
| 561 | { |
| 562 | int ret; |
| 563 | struct iommu_hw_queue_alloc alloc_hw_queue = { |
| 564 | .size = sizeof(alloc_hw_queue), |
| 565 | .flags = 0, |
| 566 | .viommu_id = viommu_id, |
| 567 | .type = queue_type, |
| 568 | .index = index, |
| 569 | .nesting_parent_iova = addr, |
| 570 | .length = length, |
| 571 | }; |
| 572 | |
| 573 | ret = ioctl(be->fd, IOMMU_HW_QUEUE_ALLOC, &alloc_hw_queue); |
| 574 | |
| 575 | trace_iommufd_backend_alloc_hw_queue(be->fd, viommu_id, queue_type, |
| 576 | index, addr, length, |
| 577 | alloc_hw_queue.out_hw_queue_id, ret); |
| 578 | if (ret) { |
| 579 | error_setg_errno(errp, errno, "IOMMU_HW_QUEUE_ALLOC failed"); |
| 580 | return false; |
| 581 | } |
| 582 | |
| 583 | g_assert(out_hw_queue_id); |
| 584 | *out_hw_queue_id = alloc_hw_queue.out_hw_queue_id; |
| 585 | return true; |
| 586 | } |
| 587 | |
| 588 | /* |
| 589 | * Helper to mmap HW MMIO regions exposed via iommufd for a vIOMMU instance. |
| 590 | * The caller is responsible for unmapping the mapped region. |
| 591 | */ |
| 592 | bool iommufd_backend_viommu_mmap(IOMMUFDBackend *be, uint32_t viommu_id, |
| 593 | uint64_t size, off_t offset, void **out_ptr, |
| 594 | Error **errp) |
| 595 | { |
| 596 | g_assert(viommu_id); |
| 597 | g_assert(out_ptr); |
| 598 | |
| 599 | *out_ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, be->fd, |
| 600 | offset); |
| 601 | trace_iommufd_backend_viommu_mmap(be->fd, viommu_id, size, offset); |
| 602 | if (*out_ptr == MAP_FAILED) { |
| 603 | error_setg_errno(errp, errno, "IOMMUFD vIOMMU mmap failed"); |
| 604 | return false; |
| 605 | } |
| 606 | |
| 607 | return true; |
| 608 | } |
| 609 | |
| 610 | bool host_iommu_device_iommufd_attach_hwpt(HostIOMMUDeviceIOMMUFD *hiodi, |
| 611 | uint32_t pasid, uint32_t hwpt_id, |
| 612 | Error **errp) |
| 613 | { |
| 614 | HostIOMMUDeviceIOMMUFDClass *hiodic = |
| 615 | HOST_IOMMU_DEVICE_IOMMUFD_GET_CLASS(hiodi); |
| 616 | |
| 617 | g_assert(hiodic->attach_hwpt); |
| 618 | return hiodic->attach_hwpt(hiodi, pasid, hwpt_id, errp); |
| 619 | } |
| 620 | |
| 621 | bool host_iommu_device_iommufd_detach_hwpt(HostIOMMUDeviceIOMMUFD *hiodi, |
| 622 | uint32_t pasid, Error **errp) |
| 623 | { |
| 624 | HostIOMMUDeviceIOMMUFDClass *hiodic = |
| 625 | HOST_IOMMU_DEVICE_IOMMUFD_GET_CLASS(hiodi); |
| 626 | |
| 627 | g_assert(hiodic->detach_hwpt); |
| 628 | return hiodic->detach_hwpt(hiodi, pasid, errp); |
| 629 | } |
| 630 | |
| 631 | static int hiod_iommufd_get_cap(HostIOMMUDevice *hiod, int cap, Error **errp) |
| 632 | { |
| 633 | HostIOMMUDeviceCaps *caps = &hiod->caps; |
| 634 | |
| 635 | switch (cap) { |
| 636 | case HOST_IOMMU_DEVICE_CAP_IOMMU_TYPE: |
| 637 | return caps->type; |
| 638 | case HOST_IOMMU_DEVICE_CAP_AW_BITS: |
| 639 | return vfio_device_get_aw_bits(hiod->agent); |
| 640 | default: |
| 641 | error_setg(errp, "%s: unsupported capability %x", hiod->name, cap); |
| 642 | return -EINVAL; |
| 643 | } |
| 644 | } |
| 645 | |
| 646 | static bool hiod_iommufd_support_ats(HostIOMMUDevice *hiod) |
| 647 | { |
| 648 | HostIOMMUDeviceCaps *caps = &hiod->caps; |
| 649 | |
| 650 | return !(caps->hw_caps & IOMMU_HW_CAP_PCI_ATS_NOT_SUPPORTED); |
| 651 | } |
| 652 | |
| 653 | static bool hiod_iommufd_get_pasid_info(HostIOMMUDevice *hiod, |
| 654 | PasidInfo *pasid_info) |
| 655 | { |
| 656 | HostIOMMUDeviceCaps *caps = &hiod->caps; |
| 657 | |
| 658 | if (!caps->max_pasid_log2) { |
| 659 | return false; |
| 660 | } |
| 661 | |
| 662 | g_assert(pasid_info); |
| 663 | pasid_info->exec_perm = (caps->hw_caps & IOMMU_HW_CAP_PCI_PASID_EXEC); |
| 664 | pasid_info->priv_mod = (caps->hw_caps & IOMMU_HW_CAP_PCI_PASID_PRIV); |
| 665 | pasid_info->max_pasid_log2 = caps->max_pasid_log2; |
| 666 | return true; |
| 667 | } |
| 668 | |
| 669 | static void hiod_iommufd_class_init(ObjectClass *oc, const void *data) |
| 670 | { |
| 671 | HostIOMMUDeviceClass *hiodc = HOST_IOMMU_DEVICE_CLASS(oc); |
| 672 | |
| 673 | hiodc->get_cap = hiod_iommufd_get_cap; |
| 674 | hiodc->get_pasid_info = hiod_iommufd_get_pasid_info; |
| 675 | hiodc->support_ats = hiod_iommufd_support_ats; |
| 676 | }; |
| 677 | |
| 678 | static const TypeInfo types[] = { |
| 679 | { |
| 680 | .name = TYPE_IOMMUFD_BACKEND, |
| 681 | .parent = TYPE_OBJECT, |
| 682 | .instance_size = sizeof(IOMMUFDBackend), |
| 683 | .instance_init = iommufd_backend_init, |
| 684 | .instance_finalize = iommufd_backend_finalize, |
| 685 | .class_size = sizeof(IOMMUFDBackendClass), |
| 686 | .class_init = iommufd_backend_class_init, |
| 687 | .interfaces = (const InterfaceInfo[]) { |
| 688 | { TYPE_USER_CREATABLE }, |
| 689 | { } |
| 690 | } |
| 691 | }, { |
| 692 | .name = TYPE_HOST_IOMMU_DEVICE_IOMMUFD, |
| 693 | .parent = TYPE_HOST_IOMMU_DEVICE, |
| 694 | .instance_size = sizeof(HostIOMMUDeviceIOMMUFD), |
| 695 | .class_size = sizeof(HostIOMMUDeviceIOMMUFDClass), |
| 696 | .class_init = hiod_iommufd_class_init, |
| 697 | .abstract = true, |
| 698 | } |
| 699 | }; |
| 700 | |
| 701 | DEFINE_TYPES(types) |