/* SPDX-License-Identifier: BSD-3-Clause * Copyright (C) 2022 Intel Corporation. * All rights reserved. */ #include "spdk/stdinc.h" #include "spdk/env.h" #include "spdk/thread.h" #include "spdk/log.h" #include "spdk/util.h" #include "spdk/memory.h" #include "spdk/cpuset.h" #include "spdk/likely.h" #include "spdk/vfu_target.h" #include "tgt_internal.h" struct tgt_pci_device_ops { struct spdk_vfu_endpoint_ops ops; TAILQ_ENTRY(tgt_pci_device_ops) link; }; static struct spdk_cpuset g_tgt_core_mask; static pthread_mutex_t g_endpoint_lock = PTHREAD_MUTEX_INITIALIZER; static TAILQ_HEAD(, spdk_vfu_endpoint) g_endpoint = TAILQ_HEAD_INITIALIZER(g_endpoint); static TAILQ_HEAD(, tgt_pci_device_ops) g_pci_device_ops = TAILQ_HEAD_INITIALIZER(g_pci_device_ops); static char g_endpoint_path_dirname[PATH_MAX] = ""; static struct spdk_vfu_endpoint_ops * tgt_get_pci_device_ops(const char *device_type_name) { struct tgt_pci_device_ops *pci_ops, *tmp; bool exist = false; pthread_mutex_lock(&g_endpoint_lock); TAILQ_FOREACH_SAFE(pci_ops, &g_pci_device_ops, link, tmp) { if (!strncmp(device_type_name, pci_ops->ops.name, SPDK_VFU_MAX_NAME_LEN)) { exist = true; break; } } pthread_mutex_unlock(&g_endpoint_lock); if (exist) { return &pci_ops->ops; } return NULL; } int spdk_vfu_register_endpoint_ops(struct spdk_vfu_endpoint_ops *ops) { struct tgt_pci_device_ops *pci_ops; struct spdk_vfu_endpoint_ops *tmp; tmp = tgt_get_pci_device_ops(ops->name); if (tmp) { return -EEXIST; } pci_ops = calloc(1, sizeof(*pci_ops)); if (!pci_ops) { return -ENOMEM; } pci_ops->ops = *ops; pthread_mutex_lock(&g_endpoint_lock); TAILQ_INSERT_TAIL(&g_pci_device_ops, pci_ops, link); pthread_mutex_unlock(&g_endpoint_lock); return 0; } static char * tgt_get_base_path(void) { return g_endpoint_path_dirname; } int spdk_vfu_set_socket_path(const char *basename) { int ret; if (basename && strlen(basename) > 0) { ret = snprintf(g_endpoint_path_dirname, sizeof(g_endpoint_path_dirname) - 2, "%s", basename); if (ret <= 0) { return -EINVAL; } if ((size_t)ret >= sizeof(g_endpoint_path_dirname) - 2) { SPDK_ERRLOG("Char dev dir path length %d is too long\n", ret); return -EINVAL; } if (g_endpoint_path_dirname[ret - 1] != '/') { g_endpoint_path_dirname[ret] = '/'; g_endpoint_path_dirname[ret + 1] = '\0'; } } return 0; } struct spdk_vfu_endpoint * spdk_vfu_get_endpoint_by_name(const char *name) { struct spdk_vfu_endpoint *endpoint, *tmp; bool exist = false; pthread_mutex_lock(&g_endpoint_lock); TAILQ_FOREACH_SAFE(endpoint, &g_endpoint, link, tmp) { if (!strncmp(name, endpoint->name, SPDK_VFU_MAX_NAME_LEN)) { exist = true; break; } } pthread_mutex_unlock(&g_endpoint_lock); if (exist) { return endpoint; } return NULL; } static int tgt_vfu_ctx_poller(void *ctx) { struct spdk_vfu_endpoint *endpoint = ctx; vfu_ctx_t *vfu_ctx = endpoint->vfu_ctx; int ret; ret = vfu_run_ctx(vfu_ctx); if (spdk_unlikely(ret == -1)) { if (errno == EBUSY) { return SPDK_POLLER_IDLE; } if (errno == ENOTCONN) { spdk_poller_unregister(&endpoint->vfu_ctx_poller); if (endpoint->ops.detach_device) { endpoint->ops.detach_device(endpoint); } endpoint->is_attached = false; return SPDK_POLLER_BUSY; } } return ret != 0 ? SPDK_POLLER_BUSY : SPDK_POLLER_IDLE; } static int tgt_accept_poller(void *ctx) { struct spdk_vfu_endpoint *endpoint = ctx; int ret; if (endpoint->is_attached) { return SPDK_POLLER_IDLE; } ret = vfu_attach_ctx(endpoint->vfu_ctx); if (ret == 0) { ret = endpoint->ops.attach_device(endpoint); if (!ret) { SPDK_NOTICELOG("%s: attached successfully\n", spdk_vfu_get_endpoint_id(endpoint)); /* Polling socket too frequently will cause performance issue */ endpoint->vfu_ctx_poller = SPDK_POLLER_REGISTER(tgt_vfu_ctx_poller, endpoint, 1000); endpoint->is_attached = true; } return SPDK_POLLER_BUSY; } if (errno == EAGAIN || errno == EWOULDBLOCK) { return SPDK_POLLER_IDLE; } return SPDK_POLLER_BUSY; } static void tgt_log_cb(vfu_ctx_t *vfu_ctx, int level, char const *msg) { struct spdk_vfu_endpoint *endpoint = vfu_get_private(vfu_ctx); if (level >= LOG_DEBUG) { SPDK_DEBUGLOG(vfu, "%s: %s\n", spdk_vfu_get_endpoint_id(endpoint), msg); } else if (level >= LOG_INFO) { SPDK_INFOLOG(vfu, "%s: %s\n", spdk_vfu_get_endpoint_id(endpoint), msg); } else if (level >= LOG_NOTICE) { SPDK_NOTICELOG("%s: %s\n", spdk_vfu_get_endpoint_id(endpoint), msg); } else if (level >= LOG_WARNING) { SPDK_WARNLOG("%s: %s\n", spdk_vfu_get_endpoint_id(endpoint), msg); } else { SPDK_ERRLOG("%s: %s\n", spdk_vfu_get_endpoint_id(endpoint), msg); } } static int tgt_get_log_level(void) { int level; if (SPDK_DEBUGLOG_FLAG_ENABLED("vfu")) { return LOG_DEBUG; } level = spdk_log_to_syslog_level(spdk_log_get_level()); if (level < 0) { return LOG_ERR; } return level; } static void init_pci_config_space(vfu_pci_config_space_t *p, uint16_t ipin) { /* MLBAR */ p->hdr.bars[0].raw = 0x0; /* MUBAR */ p->hdr.bars[1].raw = 0x0; /* vendor specific, let's set them to zero for now */ p->hdr.bars[3].raw = 0x0; p->hdr.bars[4].raw = 0x0; p->hdr.bars[5].raw = 0x0; /* enable INTx */ p->hdr.intr.ipin = ipin; } static void tgt_memory_region_add_cb(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info) { struct spdk_vfu_endpoint *endpoint = vfu_get_private(vfu_ctx); void *map_start, *map_end; int ret; if (!info->vaddr) { return; } map_start = info->mapping.iov_base; map_end = info->mapping.iov_base + info->mapping.iov_len; if (((uintptr_t)info->mapping.iov_base & MASK_2MB) || (info->mapping.iov_len & MASK_2MB)) { SPDK_DEBUGLOG(vfu, "Invalid memory region vaddr %p, IOVA %p-%p\n", info->vaddr, map_start, map_end); return; } if (info->prot == (PROT_WRITE | PROT_READ)) { ret = spdk_mem_register(info->mapping.iov_base, info->mapping.iov_len); if (ret) { SPDK_ERRLOG("Memory region register %p-%p failed, ret=%d\n", map_start, map_end, ret); } } if (endpoint->ops.post_memory_add) { endpoint->ops.post_memory_add(endpoint, map_start, map_end); } } static void tgt_memory_region_remove_cb(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info) { struct spdk_vfu_endpoint *endpoint = vfu_get_private(vfu_ctx); void *map_start, *map_end; int ret = 0; if (!info->vaddr) { return; } map_start = info->mapping.iov_base; map_end = info->mapping.iov_base + info->mapping.iov_len; if (((uintptr_t)info->mapping.iov_base & MASK_2MB) || (info->mapping.iov_len & MASK_2MB)) { SPDK_DEBUGLOG(vfu, "Invalid memory region vaddr %p, IOVA %p-%p\n", info->vaddr, map_start, map_end); return; } if (endpoint->ops.pre_memory_remove) { endpoint->ops.pre_memory_remove(endpoint, map_start, map_end); } if (info->prot == (PROT_WRITE | PROT_READ)) { ret = spdk_mem_unregister(info->mapping.iov_base, info->mapping.iov_len); if (ret) { SPDK_ERRLOG("Memory region unregister %p-%p failed, ret=%d\n", map_start, map_end, ret); } } } static int tgt_device_quiesce_cb(vfu_ctx_t *vfu_ctx) { struct spdk_vfu_endpoint *endpoint = vfu_get_private(vfu_ctx); int ret; assert(endpoint->ops.quiesce_device); ret = endpoint->ops.quiesce_device(endpoint); if (ret) { errno = EBUSY; ret = -1; } return ret; } static int tgt_device_reset_cb(vfu_ctx_t *vfu_ctx, vfu_reset_type_t type) { struct spdk_vfu_endpoint *endpoint = vfu_get_private(vfu_ctx); SPDK_DEBUGLOG(vfu, "Device reset type %u\n", type); assert(endpoint->ops.reset_device); return endpoint->ops.reset_device(endpoint); } static int tgt_endpoint_realize(struct spdk_vfu_endpoint *endpoint) { int ret; uint8_t buf[512]; struct vsc *vendor_cap; ssize_t cap_offset; uint16_t vendor_cap_idx, cap_size, sparse_mmap_idx; struct spdk_vfu_pci_device pci_dev; uint8_t region_idx; assert(endpoint->ops.get_device_info); ret = endpoint->ops.get_device_info(endpoint, &pci_dev); if (ret) { SPDK_ERRLOG("%s: failed to get pci device info\n", spdk_vfu_get_endpoint_id(endpoint)); return ret; } endpoint->vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, endpoint->uuid, LIBVFIO_USER_FLAG_ATTACH_NB, endpoint, VFU_DEV_TYPE_PCI); if (endpoint->vfu_ctx == NULL) { SPDK_ERRLOG("%s: error creating libvfio-user context\n", spdk_vfu_get_endpoint_id(endpoint)); return -EFAULT; } vfu_setup_log(endpoint->vfu_ctx, tgt_log_cb, tgt_get_log_level()); ret = vfu_pci_init(endpoint->vfu_ctx, VFU_PCI_TYPE_EXPRESS, PCI_HEADER_TYPE_NORMAL, 0); if (ret < 0) { SPDK_ERRLOG("vfu_ctx %p failed to initialize PCI\n", endpoint->vfu_ctx); goto error; } vfu_pci_set_id(endpoint->vfu_ctx, pci_dev.id.vid, pci_dev.id.did, pci_dev.id.ssvid, pci_dev.id.ssid); vfu_pci_set_class(endpoint->vfu_ctx, pci_dev.class.bcc, pci_dev.class.scc, pci_dev.class.pi); /* Add Vendor Capabilities */ for (vendor_cap_idx = 0; vendor_cap_idx < pci_dev.nr_vendor_caps; vendor_cap_idx++) { memset(buf, 0, sizeof(buf)); cap_size = endpoint->ops.get_vendor_capability(endpoint, buf, 256, vendor_cap_idx); if (cap_size) { vendor_cap = (struct vsc *)buf; assert(vendor_cap->hdr.id == PCI_CAP_ID_VNDR); assert(vendor_cap->size == cap_size); cap_offset = vfu_pci_add_capability(endpoint->vfu_ctx, 0, 0, vendor_cap); if (cap_offset < 0) { SPDK_ERRLOG("vfu_ctx %p failed add vendor capability\n", endpoint->vfu_ctx); ret = -EFAULT; goto error; } } } /* Add Standard PCI Capabilities */ cap_offset = vfu_pci_add_capability(endpoint->vfu_ctx, 0, 0, &pci_dev.pmcap); if (cap_offset < 0) { SPDK_ERRLOG("vfu_ctx %p failed add pmcap\n", endpoint->vfu_ctx); ret = -EFAULT; goto error; } SPDK_DEBUGLOG(vfu, "%s PM cap_offset %ld\n", spdk_vfu_get_endpoint_id(endpoint), cap_offset); cap_offset = vfu_pci_add_capability(endpoint->vfu_ctx, 0, 0, &pci_dev.pxcap); if (cap_offset < 0) { SPDK_ERRLOG("vfu_ctx %p failed add pxcap\n", endpoint->vfu_ctx); ret = -EFAULT; goto error; } SPDK_DEBUGLOG(vfu, "%s PX cap_offset %ld\n", spdk_vfu_get_endpoint_id(endpoint), cap_offset); cap_offset = vfu_pci_add_capability(endpoint->vfu_ctx, 0, 0, &pci_dev.msixcap); if (cap_offset < 0) { SPDK_ERRLOG("vfu_ctx %p failed add msixcap\n", endpoint->vfu_ctx); ret = -EFAULT; goto error; } SPDK_DEBUGLOG(vfu, "%s MSIX cap_offset %ld\n", spdk_vfu_get_endpoint_id(endpoint), cap_offset); /* Setup PCI Regions */ for (region_idx = 0; region_idx < VFU_PCI_DEV_NUM_REGIONS; region_idx++) { struct spdk_vfu_pci_region *region = &pci_dev.regions[region_idx]; struct iovec sparse_mmap[SPDK_VFU_MAXIMUM_SPARSE_MMAP_REGIONS]; if (!region->len) { continue; } if (region->nr_sparse_mmaps) { assert(region->nr_sparse_mmaps <= SPDK_VFU_MAXIMUM_SPARSE_MMAP_REGIONS); for (sparse_mmap_idx = 0; sparse_mmap_idx < region->nr_sparse_mmaps; sparse_mmap_idx++) { sparse_mmap[sparse_mmap_idx].iov_base = (void *)region->mmaps[sparse_mmap_idx].offset; sparse_mmap[sparse_mmap_idx].iov_len = region->mmaps[sparse_mmap_idx].len; } } ret = vfu_setup_region(endpoint->vfu_ctx, region_idx, region->len, region->access_cb, region->flags, region->nr_sparse_mmaps ? sparse_mmap : NULL, region->nr_sparse_mmaps, region->fd, region->offset); if (ret) { SPDK_ERRLOG("vfu_ctx %p failed to setup region %u\n", endpoint->vfu_ctx, region_idx); goto error; } SPDK_DEBUGLOG(vfu, "%s: region %u, len 0x%"PRIx64", callback %p, nr sparse mmaps %u, fd %d\n", spdk_vfu_get_endpoint_id(endpoint), region_idx, region->len, region->access_cb, region->nr_sparse_mmaps, region->fd); } ret = vfu_setup_device_dma(endpoint->vfu_ctx, tgt_memory_region_add_cb, tgt_memory_region_remove_cb); if (ret < 0) { SPDK_ERRLOG("vfu_ctx %p failed to setup dma callback\n", endpoint->vfu_ctx); goto error; } if (endpoint->ops.reset_device) { ret = vfu_setup_device_reset_cb(endpoint->vfu_ctx, tgt_device_reset_cb); if (ret < 0) { SPDK_ERRLOG("vfu_ctx %p failed to setup reset callback\n", endpoint->vfu_ctx); goto error; } } if (endpoint->ops.quiesce_device) { vfu_setup_device_quiesce_cb(endpoint->vfu_ctx, tgt_device_quiesce_cb); } ret = vfu_setup_device_nr_irqs(endpoint->vfu_ctx, VFU_DEV_INTX_IRQ, pci_dev.nr_int_irqs); if (ret < 0) { SPDK_ERRLOG("vfu_ctx %p failed to setup INTX\n", endpoint->vfu_ctx); goto error; } ret = vfu_setup_device_nr_irqs(endpoint->vfu_ctx, VFU_DEV_MSIX_IRQ, pci_dev.nr_msix_irqs); if (ret < 0) { SPDK_ERRLOG("vfu_ctx %p failed to setup MSIX\n", endpoint->vfu_ctx); goto error; } ret = vfu_realize_ctx(endpoint->vfu_ctx); if (ret < 0) { SPDK_ERRLOG("vfu_ctx %p failed to realize\n", endpoint->vfu_ctx); goto error; } endpoint->pci_config_space = vfu_pci_get_config_space(endpoint->vfu_ctx); assert(endpoint->pci_config_space != NULL); init_pci_config_space(endpoint->pci_config_space, pci_dev.intr_ipin); assert(cap_offset != 0); endpoint->msix = (struct msixcap *)((uint8_t *)endpoint->pci_config_space + cap_offset); return 0; error: if (endpoint->vfu_ctx) { vfu_destroy_ctx(endpoint->vfu_ctx); } return ret; } static int vfu_parse_core_mask(const char *mask, struct spdk_cpuset *cpumask) { int rc; struct spdk_cpuset negative_vfu_mask; if (cpumask == NULL) { return -1; } if (mask == NULL) { spdk_cpuset_copy(cpumask, &g_tgt_core_mask); return 0; } rc = spdk_cpuset_parse(cpumask, mask); if (rc < 0) { SPDK_ERRLOG("invalid cpumask %s\n", mask); return -1; } spdk_cpuset_copy(&negative_vfu_mask, &g_tgt_core_mask); spdk_cpuset_negate(&negative_vfu_mask); spdk_cpuset_and(&negative_vfu_mask, cpumask); if (spdk_cpuset_count(&negative_vfu_mask) != 0) { SPDK_ERRLOG("one of selected cpu is outside of core mask(=%s)\n", spdk_cpuset_fmt(&g_tgt_core_mask)); return -1; } spdk_cpuset_and(cpumask, &g_tgt_core_mask); if (spdk_cpuset_count(cpumask) == 0) { SPDK_ERRLOG("no cpu is selected among core mask(=%s)\n", spdk_cpuset_fmt(&g_tgt_core_mask)); return -1; } return 0; } static void tgt_endpoint_start_thread(void *arg1) { struct spdk_vfu_endpoint *endpoint = arg1; endpoint->accept_poller = SPDK_POLLER_REGISTER(tgt_accept_poller, endpoint, 1000); assert(endpoint->accept_poller != NULL); } static void tgt_endpoint_thread_exit(void *arg1) { struct spdk_vfu_endpoint *endpoint = arg1; spdk_poller_unregister(&endpoint->accept_poller); spdk_poller_unregister(&endpoint->vfu_ctx_poller); /* Ensure the attached device is stopped before destorying the vfu context */ if (endpoint->ops.detach_device) { endpoint->ops.detach_device(endpoint); } if (endpoint->vfu_ctx) { vfu_destroy_ctx(endpoint->vfu_ctx); } endpoint->ops.destruct(endpoint); free(endpoint); spdk_thread_exit(spdk_get_thread()); } int spdk_vfu_create_endpoint(const char *endpoint_name, const char *cpumask_str, const char *dev_type_name) { char *basename; char uuid[PATH_MAX] = ""; struct spdk_cpuset cpumask = {}; struct spdk_vfu_endpoint *endpoint; struct spdk_vfu_endpoint_ops *ops; int ret = 0; ret = vfu_parse_core_mask(cpumask_str, &cpumask); if (ret) { return ret; } if (strlen(endpoint_name) >= SPDK_VFU_MAX_NAME_LEN - 1) { return -ENAMETOOLONG; } if (spdk_vfu_get_endpoint_by_name(endpoint_name)) { SPDK_ERRLOG("%s already exist\n", endpoint_name); return -EEXIST; } /* Find supported PCI device type */ ops = tgt_get_pci_device_ops(dev_type_name); if (!ops) { SPDK_ERRLOG("Request %s device type isn't registered\n", dev_type_name); return -ENOTSUP; } basename = tgt_get_base_path(); if (snprintf(uuid, sizeof(uuid), "%s%s", basename, endpoint_name) >= (int)sizeof(uuid)) { SPDK_ERRLOG("Resulting socket path for endpoint %s is too long: %s%s\n", endpoint_name, basename, endpoint_name); return -EINVAL; } endpoint = calloc(1, sizeof(*endpoint)); if (!endpoint) { return -ENOMEM; } endpoint->endpoint_ctx = ops->init(endpoint, basename, endpoint_name); if (!endpoint->endpoint_ctx) { free(endpoint); return -EINVAL; } endpoint->ops = *ops; snprintf(endpoint->name, SPDK_VFU_MAX_NAME_LEN, "%s", endpoint_name); snprintf(endpoint->uuid, sizeof(uuid), "%s", uuid); SPDK_DEBUGLOG(vfu, "Construct endpoint %s\n", endpoint_name); /* Endpoint realize */ ret = tgt_endpoint_realize(endpoint); if (ret) { endpoint->ops.destruct(endpoint); free(endpoint); return ret; } endpoint->thread = spdk_thread_create(endpoint_name, &cpumask); if (!endpoint->thread) { endpoint->ops.destruct(endpoint); vfu_destroy_ctx(endpoint->vfu_ctx); free(endpoint); return -EFAULT; } pthread_mutex_lock(&g_endpoint_lock); TAILQ_INSERT_TAIL(&g_endpoint, endpoint, link); pthread_mutex_unlock(&g_endpoint_lock); spdk_thread_send_msg(endpoint->thread, tgt_endpoint_start_thread, endpoint); return 0; } int spdk_vfu_delete_endpoint(const char *endpoint_name) { struct spdk_vfu_endpoint *endpoint; endpoint = spdk_vfu_get_endpoint_by_name(endpoint_name); if (!endpoint) { SPDK_ERRLOG("%s doesn't exist\n", endpoint_name); return -ENOENT; } SPDK_NOTICELOG("Destruct endpoint %s\n", endpoint_name); pthread_mutex_lock(&g_endpoint_lock); TAILQ_REMOVE(&g_endpoint, endpoint, link); pthread_mutex_unlock(&g_endpoint_lock); spdk_thread_send_msg(endpoint->thread, tgt_endpoint_thread_exit, endpoint); return 0; } const char * spdk_vfu_get_endpoint_id(struct spdk_vfu_endpoint *endpoint) { return endpoint->uuid; } const char * spdk_vfu_get_endpoint_name(struct spdk_vfu_endpoint *endpoint) { return endpoint->name; } vfu_ctx_t * spdk_vfu_get_vfu_ctx(struct spdk_vfu_endpoint *endpoint) { return endpoint->vfu_ctx; } void * spdk_vfu_get_endpoint_private(struct spdk_vfu_endpoint *endpoint) { return endpoint->endpoint_ctx; } bool spdk_vfu_endpoint_msix_enabled(struct spdk_vfu_endpoint *endpoint) { return endpoint->msix->mxc.mxe; } bool spdk_vfu_endpoint_intx_enabled(struct spdk_vfu_endpoint *endpoint) { return !endpoint->pci_config_space->hdr.cmd.id; } void * spdk_vfu_endpoint_get_pci_config(struct spdk_vfu_endpoint *endpoint) { return (void *)endpoint->pci_config_space; } void spdk_vfu_init(spdk_vfu_init_cb init_cb) { uint32_t i; size_t len; if (g_endpoint_path_dirname[0] == '\0') { if (getcwd(g_endpoint_path_dirname, sizeof(g_endpoint_path_dirname) - 2) == NULL) { SPDK_ERRLOG("getcwd failed\n"); return; } len = strlen(g_endpoint_path_dirname); if (g_endpoint_path_dirname[len - 1] != '/') { g_endpoint_path_dirname[len] = '/'; g_endpoint_path_dirname[len + 1] = '\0'; } } spdk_cpuset_zero(&g_tgt_core_mask); SPDK_ENV_FOREACH_CORE(i) { spdk_cpuset_set_cpu(&g_tgt_core_mask, i, true); } init_cb(0); } void * spdk_vfu_map_one(struct spdk_vfu_endpoint *endpoint, uint64_t addr, uint64_t len, dma_sg_t *sg, struct iovec *iov, int prot) { int ret; assert(endpoint != NULL); assert(endpoint->vfu_ctx != NULL); assert(sg != NULL); assert(iov != NULL); ret = vfu_addr_to_sgl(endpoint->vfu_ctx, (void *)(uintptr_t)addr, len, sg, 1, prot); if (ret < 0) { return NULL; } ret = vfu_sgl_get(endpoint->vfu_ctx, sg, iov, 1, 0); if (ret != 0) { return NULL; } assert(iov->iov_base != NULL); return iov->iov_base; } void spdk_vfu_unmap_sg(struct spdk_vfu_endpoint *endpoint, dma_sg_t *sg, struct iovec *iov, int iovcnt) { assert(endpoint != NULL); assert(endpoint->vfu_ctx != NULL); assert(sg != NULL); assert(iov != NULL); vfu_sgl_put(endpoint->vfu_ctx, sg, iov, iovcnt); } void spdk_vfu_fini(spdk_vfu_fini_cb fini_cb) { struct spdk_vfu_endpoint *endpoint, *tmp; struct tgt_pci_device_ops *ops, *ops_tmp; pthread_mutex_lock(&g_endpoint_lock); TAILQ_FOREACH_SAFE(ops, &g_pci_device_ops, link, ops_tmp) { TAILQ_REMOVE(&g_pci_device_ops, ops, link); free(ops); } TAILQ_FOREACH_SAFE(endpoint, &g_endpoint, link, tmp) { TAILQ_REMOVE(&g_endpoint, endpoint, link); spdk_thread_send_msg(endpoint->thread, tgt_endpoint_thread_exit, endpoint); } pthread_mutex_unlock(&g_endpoint_lock); fini_cb(); } SPDK_LOG_REGISTER_COMPONENT(vfu)