summaryrefslogtreecommitdiffstats
path: root/src/spdk/lib/virtio/virtio.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/spdk/lib/virtio/virtio.c')
-rw-r--r--src/spdk/lib/virtio/virtio.c717
1 files changed, 717 insertions, 0 deletions
diff --git a/src/spdk/lib/virtio/virtio.c b/src/spdk/lib/virtio/virtio.c
new file mode 100644
index 000000000..03866040a
--- /dev/null
+++ b/src/spdk/lib/virtio/virtio.c
@@ -0,0 +1,717 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "spdk/stdinc.h"
+
+#include "spdk/env.h"
+#include "spdk/util.h"
+#include "spdk/barrier.h"
+
+#include "spdk_internal/virtio.h"
+
+/* We use SMP memory barrier variants as all virtio_pci devices
+ * are purely virtual. All MMIO is executed on a CPU core, so
+ * there's no need to do full MMIO synchronization.
+ */
+#define virtio_mb() spdk_smp_mb()
+#define virtio_rmb() spdk_smp_rmb()
+#define virtio_wmb() spdk_smp_wmb()
+
+/* Chain all the descriptors in the ring with an END */
+static inline void
+vring_desc_init(struct vring_desc *dp, uint16_t n)
+{
+ uint16_t i;
+
+ for (i = 0; i < n - 1; i++) {
+ dp[i].next = (uint16_t)(i + 1);
+ }
+ dp[i].next = VQ_RING_DESC_CHAIN_END;
+}
+
+static void
+virtio_init_vring(struct virtqueue *vq)
+{
+ int size = vq->vq_nentries;
+ struct vring *vr = &vq->vq_ring;
+ uint8_t *ring_mem = vq->vq_ring_virt_mem;
+
+ /*
+ * Reinitialise since virtio port might have been stopped and restarted
+ */
+ memset(ring_mem, 0, vq->vq_ring_size);
+ vring_init(vr, size, ring_mem, VIRTIO_PCI_VRING_ALIGN);
+ vq->vq_used_cons_idx = 0;
+ vq->vq_desc_head_idx = 0;
+ vq->vq_avail_idx = 0;
+ vq->vq_desc_tail_idx = (uint16_t)(vq->vq_nentries - 1);
+ vq->vq_free_cnt = vq->vq_nentries;
+ vq->req_start = VQ_RING_DESC_CHAIN_END;
+ vq->req_end = VQ_RING_DESC_CHAIN_END;
+ vq->reqs_finished = 0;
+ memset(vq->vq_descx, 0, sizeof(struct vq_desc_extra) * vq->vq_nentries);
+
+ vring_desc_init(vr->desc, size);
+
+ /* Tell the backend not to interrupt us.
+ * If F_EVENT_IDX is negotiated, we will always set incredibly high
+ * used event idx, so that we will practically never receive an
+ * interrupt. See virtqueue_req_flush()
+ */
+ if (vq->vdev->negotiated_features & (1ULL << VIRTIO_RING_F_EVENT_IDX)) {
+ vring_used_event(&vq->vq_ring) = UINT16_MAX;
+ } else {
+ vq->vq_ring.avail->flags |= VRING_AVAIL_F_NO_INTERRUPT;
+ }
+}
+
+static int
+virtio_init_queue(struct virtio_dev *dev, uint16_t vtpci_queue_idx)
+{
+ unsigned int vq_size, size;
+ struct virtqueue *vq;
+ int rc;
+
+ SPDK_DEBUGLOG(SPDK_LOG_VIRTIO_DEV, "setting up queue: %"PRIu16"\n", vtpci_queue_idx);
+
+ /*
+ * Read the virtqueue size from the Queue Size field
+ * Always power of 2 and if 0 virtqueue does not exist
+ */
+ vq_size = virtio_dev_backend_ops(dev)->get_queue_size(dev, vtpci_queue_idx);
+ SPDK_DEBUGLOG(SPDK_LOG_VIRTIO_DEV, "vq_size: %u\n", vq_size);
+ if (vq_size == 0) {
+ SPDK_ERRLOG("virtqueue %"PRIu16" does not exist\n", vtpci_queue_idx);
+ return -EINVAL;
+ }
+
+ if (!spdk_u32_is_pow2(vq_size)) {
+ SPDK_ERRLOG("virtqueue %"PRIu16" size (%u) is not powerof 2\n",
+ vtpci_queue_idx, vq_size);
+ return -EINVAL;
+ }
+
+ size = sizeof(*vq) + vq_size * sizeof(struct vq_desc_extra);
+
+ if (posix_memalign((void **)&vq, SPDK_CACHE_LINE_SIZE, size)) {
+ SPDK_ERRLOG("can not allocate vq\n");
+ return -ENOMEM;
+ }
+ memset(vq, 0, size);
+ dev->vqs[vtpci_queue_idx] = vq;
+
+ vq->vdev = dev;
+ vq->vq_queue_index = vtpci_queue_idx;
+ vq->vq_nentries = vq_size;
+
+ /*
+ * Reserve a memzone for vring elements
+ */
+ size = vring_size(vq_size, VIRTIO_PCI_VRING_ALIGN);
+ vq->vq_ring_size = SPDK_ALIGN_CEIL(size, VIRTIO_PCI_VRING_ALIGN);
+ SPDK_DEBUGLOG(SPDK_LOG_VIRTIO_DEV, "vring_size: %u, rounded_vring_size: %u\n",
+ size, vq->vq_ring_size);
+
+ vq->owner_thread = NULL;
+
+ rc = virtio_dev_backend_ops(dev)->setup_queue(dev, vq);
+ if (rc < 0) {
+ SPDK_ERRLOG("setup_queue failed\n");
+ free(vq);
+ dev->vqs[vtpci_queue_idx] = NULL;
+ return rc;
+ }
+
+ SPDK_DEBUGLOG(SPDK_LOG_VIRTIO_DEV, "vq->vq_ring_mem: 0x%" PRIx64 "\n",
+ vq->vq_ring_mem);
+ SPDK_DEBUGLOG(SPDK_LOG_VIRTIO_DEV, "vq->vq_ring_virt_mem: 0x%" PRIx64 "\n",
+ (uint64_t)(uintptr_t)vq->vq_ring_virt_mem);
+
+ virtio_init_vring(vq);
+ return 0;
+}
+
+static void
+virtio_free_queues(struct virtio_dev *dev)
+{
+ uint16_t nr_vq = dev->max_queues;
+ struct virtqueue *vq;
+ uint16_t i;
+
+ if (dev->vqs == NULL) {
+ return;
+ }
+
+ for (i = 0; i < nr_vq; i++) {
+ vq = dev->vqs[i];
+ if (!vq) {
+ continue;
+ }
+
+ virtio_dev_backend_ops(dev)->del_queue(dev, vq);
+
+ free(vq);
+ dev->vqs[i] = NULL;
+ }
+
+ free(dev->vqs);
+ dev->vqs = NULL;
+}
+
+static int
+virtio_alloc_queues(struct virtio_dev *dev, uint16_t request_vq_num, uint16_t fixed_vq_num)
+{
+ uint16_t nr_vq;
+ uint16_t i;
+ int ret;
+
+ nr_vq = request_vq_num + fixed_vq_num;
+ if (nr_vq == 0) {
+ /* perfectly fine to have a device with no virtqueues. */
+ return 0;
+ }
+
+ assert(dev->vqs == NULL);
+ dev->vqs = calloc(1, sizeof(struct virtqueue *) * nr_vq);
+ if (!dev->vqs) {
+ SPDK_ERRLOG("failed to allocate %"PRIu16" vqs\n", nr_vq);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < nr_vq; i++) {
+ ret = virtio_init_queue(dev, i);
+ if (ret < 0) {
+ virtio_free_queues(dev);
+ return ret;
+ }
+ }
+
+ dev->max_queues = nr_vq;
+ dev->fixed_queues_num = fixed_vq_num;
+ return 0;
+}
+
+/**
+ * Negotiate virtio features. For virtio_user this will also set
+ * dev->modern flag if VIRTIO_F_VERSION_1 flag is negotiated.
+ */
+static int
+virtio_negotiate_features(struct virtio_dev *dev, uint64_t req_features)
+{
+ uint64_t host_features = virtio_dev_backend_ops(dev)->get_features(dev);
+ int rc;
+
+ SPDK_DEBUGLOG(SPDK_LOG_VIRTIO_DEV, "guest features = %" PRIx64 "\n", req_features);
+ SPDK_DEBUGLOG(SPDK_LOG_VIRTIO_DEV, "device features = %" PRIx64 "\n", host_features);
+
+ rc = virtio_dev_backend_ops(dev)->set_features(dev, req_features & host_features);
+ if (rc != 0) {
+ SPDK_ERRLOG("failed to negotiate device features.\n");
+ return rc;
+ }
+
+ SPDK_DEBUGLOG(SPDK_LOG_VIRTIO_DEV, "negotiated features = %" PRIx64 "\n",
+ dev->negotiated_features);
+
+ virtio_dev_set_status(dev, VIRTIO_CONFIG_S_FEATURES_OK);
+ if (!(virtio_dev_get_status(dev) & VIRTIO_CONFIG_S_FEATURES_OK)) {
+ SPDK_ERRLOG("failed to set FEATURES_OK status!\n");
+ /* either the device failed, or we offered some features that
+ * depend on other, not offered features.
+ */
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int
+virtio_dev_construct(struct virtio_dev *vdev, const char *name,
+ const struct virtio_dev_ops *ops, void *ctx)
+{
+ int rc;
+
+ vdev->name = strdup(name);
+ if (vdev->name == NULL) {
+ return -ENOMEM;
+ }
+
+ rc = pthread_mutex_init(&vdev->mutex, NULL);
+ if (rc != 0) {
+ free(vdev->name);
+ return -rc;
+ }
+
+ vdev->backend_ops = ops;
+ vdev->ctx = ctx;
+
+ return 0;
+}
+
+int
+virtio_dev_reset(struct virtio_dev *dev, uint64_t req_features)
+{
+ req_features |= (1ULL << VIRTIO_F_VERSION_1);
+
+ virtio_dev_stop(dev);
+
+ virtio_dev_set_status(dev, VIRTIO_CONFIG_S_ACKNOWLEDGE);
+ if (!(virtio_dev_get_status(dev) & VIRTIO_CONFIG_S_ACKNOWLEDGE)) {
+ SPDK_ERRLOG("Failed to set VIRTIO_CONFIG_S_ACKNOWLEDGE status.\n");
+ return -EIO;
+ }
+
+ virtio_dev_set_status(dev, VIRTIO_CONFIG_S_DRIVER);
+ if (!(virtio_dev_get_status(dev) & VIRTIO_CONFIG_S_DRIVER)) {
+ SPDK_ERRLOG("Failed to set VIRTIO_CONFIG_S_DRIVER status.\n");
+ return -EIO;
+ }
+
+ return virtio_negotiate_features(dev, req_features);
+}
+
+int
+virtio_dev_start(struct virtio_dev *vdev, uint16_t max_queues, uint16_t fixed_queue_num)
+{
+ int ret;
+
+ ret = virtio_alloc_queues(vdev, max_queues, fixed_queue_num);
+ if (ret < 0) {
+ return ret;
+ }
+
+ virtio_dev_set_status(vdev, VIRTIO_CONFIG_S_DRIVER_OK);
+ if (!(virtio_dev_get_status(vdev) & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ SPDK_ERRLOG("Failed to set VIRTIO_CONFIG_S_DRIVER_OK status.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+virtio_dev_destruct(struct virtio_dev *dev)
+{
+ virtio_dev_backend_ops(dev)->destruct_dev(dev);
+ pthread_mutex_destroy(&dev->mutex);
+ free(dev->name);
+}
+
+static void
+vq_ring_free_chain(struct virtqueue *vq, uint16_t desc_idx)
+{
+ struct vring_desc *dp, *dp_tail;
+ struct vq_desc_extra *dxp;
+ uint16_t desc_idx_last = desc_idx;
+
+ dp = &vq->vq_ring.desc[desc_idx];
+ dxp = &vq->vq_descx[desc_idx];
+ vq->vq_free_cnt = (uint16_t)(vq->vq_free_cnt + dxp->ndescs);
+ if ((dp->flags & VRING_DESC_F_INDIRECT) == 0) {
+ while (dp->flags & VRING_DESC_F_NEXT) {
+ desc_idx_last = dp->next;
+ dp = &vq->vq_ring.desc[dp->next];
+ }
+ }
+ dxp->ndescs = 0;
+
+ /*
+ * We must append the existing free chain, if any, to the end of
+ * newly freed chain. If the virtqueue was completely used, then
+ * head would be VQ_RING_DESC_CHAIN_END (ASSERTed above).
+ */
+ if (vq->vq_desc_tail_idx == VQ_RING_DESC_CHAIN_END) {
+ vq->vq_desc_head_idx = desc_idx;
+ } else {
+ dp_tail = &vq->vq_ring.desc[vq->vq_desc_tail_idx];
+ dp_tail->next = desc_idx;
+ }
+
+ vq->vq_desc_tail_idx = desc_idx_last;
+ dp->next = VQ_RING_DESC_CHAIN_END;
+}
+
+static uint16_t
+virtqueue_dequeue_burst_rx(struct virtqueue *vq, void **rx_pkts,
+ uint32_t *len, uint16_t num)
+{
+ struct vring_used_elem *uep;
+ void *cookie;
+ uint16_t used_idx, desc_idx;
+ uint16_t i;
+
+ /* Caller does the check */
+ for (i = 0; i < num ; i++) {
+ used_idx = (uint16_t)(vq->vq_used_cons_idx & (vq->vq_nentries - 1));
+ uep = &vq->vq_ring.used->ring[used_idx];
+ desc_idx = (uint16_t) uep->id;
+ len[i] = uep->len;
+ cookie = vq->vq_descx[desc_idx].cookie;
+
+ if (spdk_unlikely(cookie == NULL)) {
+ SPDK_WARNLOG("vring descriptor with no mbuf cookie at %"PRIu16"\n",
+ vq->vq_used_cons_idx);
+ break;
+ }
+
+ __builtin_prefetch(cookie);
+
+ rx_pkts[i] = cookie;
+ vq->vq_used_cons_idx++;
+ vq_ring_free_chain(vq, desc_idx);
+ vq->vq_descx[desc_idx].cookie = NULL;
+ }
+
+ return i;
+}
+
+static void
+finish_req(struct virtqueue *vq)
+{
+ struct vring_desc *desc;
+ uint16_t avail_idx;
+
+ desc = &vq->vq_ring.desc[vq->req_end];
+ desc->flags &= ~VRING_DESC_F_NEXT;
+
+ /*
+ * Place the head of the descriptor chain into the next slot and make
+ * it usable to the host. The chain is made available now rather than
+ * deferring to virtqueue_req_flush() in the hopes that if the host is
+ * currently running on another CPU, we can keep it processing the new
+ * descriptor.
+ */
+ avail_idx = (uint16_t)(vq->vq_avail_idx & (vq->vq_nentries - 1));
+ vq->vq_ring.avail->ring[avail_idx] = vq->req_start;
+ vq->vq_avail_idx++;
+ vq->req_end = VQ_RING_DESC_CHAIN_END;
+ virtio_wmb();
+ vq->vq_ring.avail->idx = vq->vq_avail_idx;
+ vq->reqs_finished++;
+}
+
+int
+virtqueue_req_start(struct virtqueue *vq, void *cookie, int iovcnt)
+{
+ struct vq_desc_extra *dxp;
+
+ if (iovcnt > vq->vq_free_cnt) {
+ return iovcnt > vq->vq_nentries ? -EINVAL : -ENOMEM;
+ }
+
+ if (vq->req_end != VQ_RING_DESC_CHAIN_END) {
+ finish_req(vq);
+ }
+
+ vq->req_start = vq->vq_desc_head_idx;
+ dxp = &vq->vq_descx[vq->req_start];
+ dxp->cookie = cookie;
+ dxp->ndescs = 0;
+
+ return 0;
+}
+
+void
+virtqueue_req_flush(struct virtqueue *vq)
+{
+ uint16_t reqs_finished;
+
+ if (vq->req_end == VQ_RING_DESC_CHAIN_END) {
+ /* no non-empty requests have been started */
+ return;
+ }
+
+ finish_req(vq);
+ virtio_mb();
+
+ reqs_finished = vq->reqs_finished;
+ vq->reqs_finished = 0;
+
+ if (vq->vdev->negotiated_features & (1ULL << VIRTIO_RING_F_EVENT_IDX)) {
+ /* Set used event idx to a value the device will never reach.
+ * This effectively disables interrupts.
+ */
+ vring_used_event(&vq->vq_ring) = vq->vq_used_cons_idx - vq->vq_nentries - 1;
+
+ if (!vring_need_event(vring_avail_event(&vq->vq_ring),
+ vq->vq_avail_idx,
+ vq->vq_avail_idx - reqs_finished)) {
+ return;
+ }
+ } else if (vq->vq_ring.used->flags & VRING_USED_F_NO_NOTIFY) {
+ return;
+ }
+
+ virtio_dev_backend_ops(vq->vdev)->notify_queue(vq->vdev, vq);
+ SPDK_DEBUGLOG(SPDK_LOG_VIRTIO_DEV, "Notified backend after xmit\n");
+}
+
+void
+virtqueue_req_abort(struct virtqueue *vq)
+{
+ struct vring_desc *desc;
+
+ if (vq->req_start == VQ_RING_DESC_CHAIN_END) {
+ /* no requests have been started */
+ return;
+ }
+
+ desc = &vq->vq_ring.desc[vq->req_end];
+ desc->flags &= ~VRING_DESC_F_NEXT;
+
+ vq_ring_free_chain(vq, vq->req_start);
+ vq->req_start = VQ_RING_DESC_CHAIN_END;
+}
+
+void
+virtqueue_req_add_iovs(struct virtqueue *vq, struct iovec *iovs, uint16_t iovcnt,
+ enum spdk_virtio_desc_type desc_type)
+{
+ struct vring_desc *desc;
+ struct vq_desc_extra *dxp;
+ uint16_t i, prev_head, new_head;
+
+ assert(vq->req_start != VQ_RING_DESC_CHAIN_END);
+ assert(iovcnt <= vq->vq_free_cnt);
+
+ /* TODO use indirect descriptors if iovcnt is high enough
+ * or the caller specifies SPDK_VIRTIO_DESC_F_INDIRECT
+ */
+
+ prev_head = vq->req_end;
+ new_head = vq->vq_desc_head_idx;
+ for (i = 0; i < iovcnt; ++i) {
+ desc = &vq->vq_ring.desc[new_head];
+
+ if (!vq->vdev->is_hw) {
+ desc->addr = (uintptr_t)iovs[i].iov_base;
+ } else {
+ desc->addr = spdk_vtophys(iovs[i].iov_base, NULL);
+ }
+
+ desc->len = iovs[i].iov_len;
+ /* always set NEXT flag. unset it on the last descriptor
+ * in the request-ending function.
+ */
+ desc->flags = desc_type | VRING_DESC_F_NEXT;
+
+ prev_head = new_head;
+ new_head = desc->next;
+ }
+
+ dxp = &vq->vq_descx[vq->req_start];
+ dxp->ndescs += iovcnt;
+
+ vq->req_end = prev_head;
+ vq->vq_desc_head_idx = new_head;
+ vq->vq_free_cnt = (uint16_t)(vq->vq_free_cnt - iovcnt);
+ if (vq->vq_desc_head_idx == VQ_RING_DESC_CHAIN_END) {
+ assert(vq->vq_free_cnt == 0);
+ vq->vq_desc_tail_idx = VQ_RING_DESC_CHAIN_END;
+ }
+}
+
+#define DESC_PER_CACHELINE (SPDK_CACHE_LINE_SIZE / sizeof(struct vring_desc))
+uint16_t
+virtio_recv_pkts(struct virtqueue *vq, void **io, uint32_t *len, uint16_t nb_pkts)
+{
+ uint16_t nb_used, num;
+
+ nb_used = vq->vq_ring.used->idx - vq->vq_used_cons_idx;
+ virtio_rmb();
+
+ num = (uint16_t)(spdk_likely(nb_used <= nb_pkts) ? nb_used : nb_pkts);
+ if (spdk_likely(num > DESC_PER_CACHELINE)) {
+ num = num - ((vq->vq_used_cons_idx + num) % DESC_PER_CACHELINE);
+ }
+
+ return virtqueue_dequeue_burst_rx(vq, io, len, num);
+}
+
+int
+virtio_dev_acquire_queue(struct virtio_dev *vdev, uint16_t index)
+{
+ struct virtqueue *vq = NULL;
+
+ if (index >= vdev->max_queues) {
+ SPDK_ERRLOG("requested vq index %"PRIu16" exceeds max queue count %"PRIu16".\n",
+ index, vdev->max_queues);
+ return -1;
+ }
+
+ pthread_mutex_lock(&vdev->mutex);
+ vq = vdev->vqs[index];
+ if (vq == NULL || vq->owner_thread != NULL) {
+ pthread_mutex_unlock(&vdev->mutex);
+ return -1;
+ }
+
+ vq->owner_thread = spdk_get_thread();
+ pthread_mutex_unlock(&vdev->mutex);
+ return 0;
+}
+
+int32_t
+virtio_dev_find_and_acquire_queue(struct virtio_dev *vdev, uint16_t start_index)
+{
+ struct virtqueue *vq = NULL;
+ uint16_t i;
+
+ pthread_mutex_lock(&vdev->mutex);
+ for (i = start_index; i < vdev->max_queues; ++i) {
+ vq = vdev->vqs[i];
+ if (vq != NULL && vq->owner_thread == NULL) {
+ break;
+ }
+ }
+
+ if (vq == NULL || i == vdev->max_queues) {
+ SPDK_ERRLOG("no more unused virtio queues with idx >= %"PRIu16".\n", start_index);
+ pthread_mutex_unlock(&vdev->mutex);
+ return -1;
+ }
+
+ vq->owner_thread = spdk_get_thread();
+ pthread_mutex_unlock(&vdev->mutex);
+ return i;
+}
+
+struct spdk_thread *
+virtio_dev_queue_get_thread(struct virtio_dev *vdev, uint16_t index)
+{
+ struct spdk_thread *thread = NULL;
+
+ if (index >= vdev->max_queues) {
+ SPDK_ERRLOG("given vq index %"PRIu16" exceeds max queue count %"PRIu16"\n",
+ index, vdev->max_queues);
+ abort(); /* This is not recoverable */
+ }
+
+ pthread_mutex_lock(&vdev->mutex);
+ thread = vdev->vqs[index]->owner_thread;
+ pthread_mutex_unlock(&vdev->mutex);
+
+ return thread;
+}
+
+bool
+virtio_dev_queue_is_acquired(struct virtio_dev *vdev, uint16_t index)
+{
+ return virtio_dev_queue_get_thread(vdev, index) != NULL;
+}
+
+void
+virtio_dev_release_queue(struct virtio_dev *vdev, uint16_t index)
+{
+ struct virtqueue *vq = NULL;
+
+ if (index >= vdev->max_queues) {
+ SPDK_ERRLOG("given vq index %"PRIu16" exceeds max queue count %"PRIu16".\n",
+ index, vdev->max_queues);
+ return;
+ }
+
+ pthread_mutex_lock(&vdev->mutex);
+ vq = vdev->vqs[index];
+ if (vq == NULL) {
+ SPDK_ERRLOG("virtqueue at index %"PRIu16" is not initialized.\n", index);
+ pthread_mutex_unlock(&vdev->mutex);
+ return;
+ }
+
+ assert(vq->owner_thread == spdk_get_thread());
+ vq->owner_thread = NULL;
+ pthread_mutex_unlock(&vdev->mutex);
+}
+
+int
+virtio_dev_read_dev_config(struct virtio_dev *dev, size_t offset,
+ void *dst, int length)
+{
+ return virtio_dev_backend_ops(dev)->read_dev_cfg(dev, offset, dst, length);
+}
+
+int
+virtio_dev_write_dev_config(struct virtio_dev *dev, size_t offset,
+ const void *src, int length)
+{
+ return virtio_dev_backend_ops(dev)->write_dev_cfg(dev, offset, src, length);
+}
+
+void
+virtio_dev_stop(struct virtio_dev *dev)
+{
+ virtio_dev_backend_ops(dev)->set_status(dev, VIRTIO_CONFIG_S_RESET);
+ /* flush status write */
+ virtio_dev_backend_ops(dev)->get_status(dev);
+ virtio_free_queues(dev);
+}
+
+void
+virtio_dev_set_status(struct virtio_dev *dev, uint8_t status)
+{
+ if (status != VIRTIO_CONFIG_S_RESET) {
+ status |= virtio_dev_backend_ops(dev)->get_status(dev);
+ }
+
+ virtio_dev_backend_ops(dev)->set_status(dev, status);
+}
+
+uint8_t
+virtio_dev_get_status(struct virtio_dev *dev)
+{
+ return virtio_dev_backend_ops(dev)->get_status(dev);
+}
+
+const struct virtio_dev_ops *
+virtio_dev_backend_ops(struct virtio_dev *dev)
+{
+ return dev->backend_ops;
+}
+
+void
+virtio_dev_dump_json_info(struct virtio_dev *hw, struct spdk_json_write_ctx *w)
+{
+ spdk_json_write_named_object_begin(w, "virtio");
+
+ spdk_json_write_named_uint32(w, "vq_count", hw->max_queues);
+
+ spdk_json_write_named_uint32(w, "vq_size",
+ virtio_dev_backend_ops(hw)->get_queue_size(hw, 0));
+
+ virtio_dev_backend_ops(hw)->dump_json_info(hw, w);
+
+ spdk_json_write_object_end(w);
+}
+
+SPDK_LOG_REGISTER_COMPONENT("virtio_dev", SPDK_LOG_VIRTIO_DEV)