2017-05-30 14:13:50 -07:00
|
|
|
/*-
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2017-10-18 10:14:30 -07:00
|
|
|
#include "spdk/stdinc.h"
|
2017-05-30 14:13:50 -07:00
|
|
|
|
|
|
|
#include <linux/virtio_scsi.h>
|
2017-11-02 15:20:25 +01:00
|
|
|
#include <linux/virtio_pci.h>
|
|
|
|
#include <linux/virtio_config.h>
|
2017-05-30 14:13:50 -07:00
|
|
|
|
2017-11-15 14:52:32 +01:00
|
|
|
#include <rte_config.h>
|
2017-05-30 14:13:50 -07:00
|
|
|
#include <rte_memcpy.h>
|
|
|
|
#include <rte_string_fns.h>
|
|
|
|
#include <rte_memzone.h>
|
|
|
|
#include <rte_malloc.h>
|
|
|
|
#include <rte_atomic.h>
|
|
|
|
#include <rte_branch_prediction.h>
|
|
|
|
#include <rte_pci.h>
|
|
|
|
#include <rte_common.h>
|
|
|
|
#include <rte_errno.h>
|
|
|
|
|
|
|
|
#include <rte_eal.h>
|
|
|
|
#include <rte_dev.h>
|
2017-10-14 11:57:03 +02:00
|
|
|
#include <rte_prefetch.h>
|
2017-05-30 14:13:50 -07:00
|
|
|
|
2017-11-02 15:20:25 +01:00
|
|
|
#include "spdk/env.h"
|
2017-11-03 16:55:38 +01:00
|
|
|
#include "spdk/barrier.h"
|
|
|
|
|
2017-11-23 14:40:39 +01:00
|
|
|
/* 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.
|
2017-11-03 16:55:38 +01:00
|
|
|
*/
|
2017-11-23 14:40:39 +01:00
|
|
|
#define virtio_mb() spdk_smp_mb()
|
|
|
|
#define virtio_rmb() spdk_smp_rmb()
|
|
|
|
#define virtio_wmb() spdk_smp_wmb()
|
2017-11-02 15:20:25 +01:00
|
|
|
|
2017-11-03 14:53:10 +01:00
|
|
|
#include "virtio.h"
|
2017-11-02 15:20:25 +01:00
|
|
|
|
|
|
|
struct virtio_driver g_virtio_driver = {
|
|
|
|
.init_ctrlrs = TAILQ_HEAD_INITIALIZER(g_virtio_driver.init_ctrlrs),
|
|
|
|
.attached_ctrlrs = TAILQ_HEAD_INITIALIZER(g_virtio_driver.attached_ctrlrs),
|
|
|
|
};
|
2017-05-30 14:13:50 -07:00
|
|
|
|
2017-11-03 16:55:38 +01:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tell the backend not to interrupt us.
|
|
|
|
*/
|
|
|
|
static inline void
|
|
|
|
virtqueue_disable_intr(struct virtqueue *vq)
|
|
|
|
{
|
|
|
|
vq->vq_ring.avail->flags |= VRING_AVAIL_F_NO_INTERRUPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define VIRTQUEUE_NUSED(vq) ((uint16_t)((vq)->vq_ring.used->idx - (vq)->vq_used_cons_idx))
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
vq_update_avail_idx(struct virtqueue *vq)
|
|
|
|
{
|
|
|
|
virtio_wmb();
|
|
|
|
vq->vq_ring.avail->idx = vq->vq_avail_idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
vq_update_avail_ring(struct virtqueue *vq, uint16_t desc_idx)
|
|
|
|
{
|
|
|
|
uint16_t avail_idx;
|
|
|
|
/*
|
|
|
|
* 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_notify() 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));
|
|
|
|
if (spdk_unlikely(vq->vq_ring.avail->ring[avail_idx] != desc_idx))
|
|
|
|
vq->vq_ring.avail->ring[avail_idx] = desc_idx;
|
|
|
|
vq->vq_avail_idx++;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
virtqueue_kick_prepare(struct virtqueue *vq)
|
|
|
|
{
|
|
|
|
return !(vq->vq_ring.used->flags & VRING_USED_F_NO_NOTIFY);
|
|
|
|
}
|
|
|
|
|
2017-05-30 14:13:50 -07:00
|
|
|
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;
|
|
|
|
memset(vq->vq_descx, 0, sizeof(struct vq_desc_extra) * vq->vq_nentries);
|
|
|
|
|
|
|
|
vring_desc_init(vr->desc, size);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Disable device(host) interrupting guest
|
|
|
|
*/
|
|
|
|
virtqueue_disable_intr(vq);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2017-09-01 19:33:53 +02:00
|
|
|
virtio_init_queue(struct virtio_dev *dev, uint16_t vtpci_queue_idx)
|
2017-05-30 14:13:50 -07:00
|
|
|
{
|
2017-11-03 17:37:51 +01:00
|
|
|
void *queue_mem;
|
2017-05-30 14:13:50 -07:00
|
|
|
unsigned int vq_size, size;
|
2017-11-03 17:37:51 +01:00
|
|
|
uint64_t queue_mem_phys_addr;
|
2017-05-30 14:13:50 -07:00
|
|
|
struct virtqueue *vq;
|
|
|
|
int ret;
|
|
|
|
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "setting up queue: %"PRIu16"\n", vtpci_queue_idx);
|
2017-05-30 14:13:50 -07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the virtqueue size from the Queue Size field
|
|
|
|
* Always power of 2 and if 0 virtqueue does not exist
|
|
|
|
*/
|
2017-11-02 17:10:56 +01:00
|
|
|
vq_size = virtio_dev_backend_ops(dev)->get_queue_num(dev, vtpci_queue_idx);
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "vq_size: %u\n", vq_size);
|
2017-05-30 14:13:50 -07:00
|
|
|
if (vq_size == 0) {
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_ERRLOG("virtqueue %"PRIu16" does not exist\n", vtpci_queue_idx);
|
2017-05-30 14:13:50 -07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!rte_is_power_of_2(vq_size)) {
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_ERRLOG("virtqueue %"PRIu16" size (%u) is not powerof 2\n",
|
|
|
|
vtpci_queue_idx, vq_size);
|
2017-05-30 14:13:50 -07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
size = RTE_ALIGN_CEIL(sizeof(*vq) +
|
2017-10-18 10:06:51 -07:00
|
|
|
vq_size * sizeof(struct vq_desc_extra),
|
|
|
|
RTE_CACHE_LINE_SIZE);
|
2017-05-30 14:13:50 -07:00
|
|
|
|
2017-11-20 23:05:22 +01:00
|
|
|
vq = spdk_dma_zmalloc(size, RTE_CACHE_LINE_SIZE, NULL);
|
2017-05-30 14:13:50 -07:00
|
|
|
if (vq == NULL) {
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_ERRLOG("can not allocate vq\n");
|
2017-05-30 14:13:50 -07:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2017-09-01 19:33:53 +02:00
|
|
|
dev->vqs[vtpci_queue_idx] = vq;
|
2017-05-30 14:13:50 -07:00
|
|
|
|
2017-09-01 19:33:53 +02:00
|
|
|
vq->vdev = dev;
|
2017-05-30 14:13:50 -07:00
|
|
|
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 = RTE_ALIGN_CEIL(size, VIRTIO_PCI_VRING_ALIGN);
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "vring_size: %u, rounded_vring_size: %u\n",
|
2017-10-18 10:06:51 -07:00
|
|
|
size, vq->vq_ring_size);
|
2017-05-30 14:13:50 -07:00
|
|
|
|
2017-11-03 17:37:51 +01:00
|
|
|
queue_mem = spdk_dma_zmalloc(vq->vq_ring_size, VIRTIO_PCI_VRING_ALIGN, &queue_mem_phys_addr);
|
|
|
|
if (queue_mem == NULL) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto fail_q_alloc;
|
2017-05-30 14:13:50 -07:00
|
|
|
}
|
|
|
|
|
2017-11-03 17:37:51 +01:00
|
|
|
vq->vq_ring_mem = queue_mem_phys_addr;
|
|
|
|
vq->vq_ring_virt_mem = queue_mem;
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "vq->vq_ring_mem: 0x%" PRIx64 "\n",
|
2017-11-03 17:37:51 +01:00
|
|
|
vq->vq_ring_mem);
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "vq->vq_ring_virt_mem: 0x%" PRIx64 "\n",
|
2017-11-03 17:37:51 +01:00
|
|
|
(uint64_t)(uintptr_t)vq->vq_ring_virt_mem);
|
2017-05-30 14:13:50 -07:00
|
|
|
|
|
|
|
virtio_init_vring(vq);
|
|
|
|
|
2017-11-23 20:15:23 +01:00
|
|
|
vq->owner_thread = NULL;
|
2017-10-10 20:44:35 +02:00
|
|
|
vq->poller = NULL;
|
|
|
|
|
2017-11-02 17:10:56 +01:00
|
|
|
if (virtio_dev_backend_ops(dev)->setup_queue(dev, vq) < 0) {
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_ERRLOG("setup_queue failed\n");
|
2017-05-30 14:13:50 -07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
fail_q_alloc:
|
|
|
|
rte_free(vq);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2017-09-01 19:33:53 +02:00
|
|
|
virtio_free_queues(struct virtio_dev *dev)
|
2017-05-30 14:13:50 -07:00
|
|
|
{
|
2017-10-10 17:50:20 +02:00
|
|
|
uint16_t nr_vq = dev->max_queues;
|
2017-05-30 14:13:50 -07:00
|
|
|
struct virtqueue *vq;
|
|
|
|
uint16_t i;
|
|
|
|
|
2017-09-01 19:33:53 +02:00
|
|
|
if (dev->vqs == NULL)
|
2017-05-30 14:13:50 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = 0; i < nr_vq; i++) {
|
2017-09-01 19:33:53 +02:00
|
|
|
vq = dev->vqs[i];
|
2017-05-30 14:13:50 -07:00
|
|
|
if (!vq)
|
|
|
|
continue;
|
|
|
|
|
2017-11-03 17:37:51 +01:00
|
|
|
spdk_dma_free(vq->vq_ring_virt_mem);
|
2017-05-30 14:13:50 -07:00
|
|
|
|
|
|
|
rte_free(vq);
|
2017-09-01 19:33:53 +02:00
|
|
|
dev->vqs[i] = NULL;
|
2017-05-30 14:13:50 -07:00
|
|
|
}
|
|
|
|
|
2017-09-01 19:33:53 +02:00
|
|
|
rte_free(dev->vqs);
|
|
|
|
dev->vqs = NULL;
|
2017-05-30 14:13:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2017-09-01 19:33:53 +02:00
|
|
|
virtio_alloc_queues(struct virtio_dev *dev)
|
2017-05-30 14:13:50 -07:00
|
|
|
{
|
2017-10-10 17:50:20 +02:00
|
|
|
uint16_t nr_vq = dev->max_queues;
|
2017-05-30 14:13:50 -07:00
|
|
|
uint16_t i;
|
|
|
|
int ret;
|
|
|
|
|
2017-11-20 18:22:49 +01:00
|
|
|
if (dev->vqs != NULL) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-09-01 19:33:53 +02:00
|
|
|
dev->vqs = rte_zmalloc(NULL, sizeof(struct virtqueue *) * nr_vq, 0);
|
|
|
|
if (!dev->vqs) {
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_ERRLOG("failed to allocate %"PRIu16" vqs\n", nr_vq);
|
2017-05-30 14:13:50 -07:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < nr_vq; i++) {
|
2017-09-01 19:33:53 +02:00
|
|
|
ret = virtio_init_queue(dev, i);
|
2017-05-30 14:13:50 -07:00
|
|
|
if (ret < 0) {
|
2017-09-01 19:33:53 +02:00
|
|
|
virtio_free_queues(dev);
|
2017-05-30 14:13:50 -07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-10-10 17:50:20 +02:00
|
|
|
/**
|
|
|
|
* Negotiate virtio features. For virtio_user this will also set
|
|
|
|
* dev->modern flag if VIRTIO_F_VERSION_1 flag is negotiated.
|
2017-09-04 15:54:42 +02:00
|
|
|
*/
|
2017-05-30 14:13:50 -07:00
|
|
|
static int
|
2017-09-01 19:33:53 +02:00
|
|
|
virtio_negotiate_features(struct virtio_dev *dev, uint64_t req_features)
|
2017-05-30 14:13:50 -07:00
|
|
|
{
|
2017-11-02 17:10:56 +01:00
|
|
|
uint64_t host_features = virtio_dev_backend_ops(dev)->get_features(dev);
|
2017-10-05 14:46:24 +02:00
|
|
|
int rc;
|
2017-05-30 14:13:50 -07:00
|
|
|
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "guest features = %" PRIx64 "\n", req_features);
|
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "device features = %" PRIx64 "\n", host_features);
|
2017-05-30 14:13:50 -07:00
|
|
|
|
2017-11-02 17:10:56 +01:00
|
|
|
rc = virtio_dev_backend_ops(dev)->set_features(dev, req_features & host_features);
|
2017-10-05 14:46:24 +02:00
|
|
|
if (rc != 0) {
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_ERRLOG("failed to negotiate device features.\n");
|
2017-10-05 14:46:24 +02:00
|
|
|
return -1;
|
2017-09-04 15:54:42 +02:00
|
|
|
}
|
|
|
|
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "negotiated features = %" PRIx64 "\n",
|
|
|
|
dev->negotiated_features);
|
2017-10-05 14:46:24 +02:00
|
|
|
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_set_status(dev, VIRTIO_CONFIG_S_FEATURES_OK);
|
|
|
|
if (!(virtio_dev_get_status(dev) & VIRTIO_CONFIG_S_FEATURES_OK)) {
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_ERRLOG("failed to set FEATURES_OK status!\n");
|
2017-09-04 15:54:42 +02:00
|
|
|
return -1;
|
2017-05-30 14:13:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-11-02 15:20:25 +01:00
|
|
|
struct virtio_dev *
|
2017-11-03 15:41:31 +01:00
|
|
|
virtio_dev_construct(const struct virtio_dev_ops *ops, void *ctx)
|
2017-11-02 15:20:25 +01:00
|
|
|
{
|
|
|
|
struct virtio_dev *vdev;
|
|
|
|
|
|
|
|
vdev = calloc(1, sizeof(*vdev));
|
|
|
|
if (vdev == NULL) {
|
|
|
|
SPDK_ERRLOG("virtio device calloc failed\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pthread_mutex_init(&vdev->mutex, NULL);
|
|
|
|
vdev->backend_ops = ops;
|
|
|
|
vdev->ctx = ctx;
|
|
|
|
|
|
|
|
return vdev;
|
|
|
|
}
|
|
|
|
|
2017-08-31 17:04:08 +02:00
|
|
|
int
|
2017-09-29 13:03:45 +02:00
|
|
|
virtio_dev_init(struct virtio_dev *dev, uint64_t req_features)
|
2017-05-30 14:13:50 -07:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* Reset the device although not necessary at startup */
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_reset(dev);
|
2017-05-30 14:13:50 -07:00
|
|
|
|
|
|
|
/* Tell the host we've noticed this device. */
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_set_status(dev, VIRTIO_CONFIG_S_ACKNOWLEDGE);
|
2017-05-30 14:13:50 -07:00
|
|
|
|
|
|
|
/* Tell the host we've known how to drive the device. */
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_set_status(dev, VIRTIO_CONFIG_S_DRIVER);
|
2017-09-01 19:33:53 +02:00
|
|
|
if (virtio_negotiate_features(dev, req_features) < 0)
|
2017-05-30 14:13:50 -07:00
|
|
|
return -1;
|
|
|
|
|
2017-09-01 19:33:53 +02:00
|
|
|
ret = virtio_alloc_queues(dev);
|
2017-05-30 14:13:50 -07:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_set_status(dev, VIRTIO_CONFIG_S_DRIVER_OK);
|
2017-05-30 14:13:50 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-09-29 13:03:45 +02:00
|
|
|
void
|
|
|
|
virtio_dev_free(struct virtio_dev *dev)
|
|
|
|
{
|
|
|
|
virtio_free_queues(dev);
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_backend_ops(dev)->free_vdev(dev);
|
2017-11-15 13:25:38 +01:00
|
|
|
pthread_mutex_destroy(&dev->mutex);
|
|
|
|
free(dev);
|
2017-09-29 13:03:45 +02:00
|
|
|
}
|
|
|
|
|
2017-10-14 11:57:03 +02:00
|
|
|
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, struct virtio_req **rx_pkts,
|
|
|
|
uint32_t *len, uint16_t num)
|
|
|
|
{
|
|
|
|
struct vring_used_elem *uep;
|
|
|
|
struct virtio_req *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 = (struct virtio_req *)vq->vq_descx[desc_idx].cookie;
|
|
|
|
|
|
|
|
if (spdk_unlikely(cookie == NULL)) {
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_WARNLOG("vring descriptor with no mbuf cookie at %"PRIu16"\n",
|
|
|
|
vq->vq_used_cons_idx);
|
2017-10-14 11:57:03 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
rte_prefetch0(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 inline void
|
|
|
|
virtqueue_iov_to_desc(struct virtqueue *vq, uint16_t desc_idx, struct iovec *iov)
|
|
|
|
{
|
|
|
|
if (!vq->vdev->is_hw) {
|
|
|
|
vq->vq_ring.desc[desc_idx].addr = (uintptr_t)iov->iov_base;
|
|
|
|
} else {
|
|
|
|
vq->vq_ring.desc[desc_idx].addr = spdk_vtophys(iov->iov_base);
|
|
|
|
}
|
|
|
|
|
|
|
|
vq->vq_ring.desc[desc_idx].len = iov->iov_len;
|
|
|
|
}
|
|
|
|
|
2017-10-17 14:25:17 +02:00
|
|
|
static int
|
2017-10-14 11:57:03 +02:00
|
|
|
virtqueue_enqueue_xmit(struct virtqueue *vq, struct virtio_req *req)
|
|
|
|
{
|
|
|
|
struct vq_desc_extra *dxp;
|
|
|
|
struct vring_desc *descs;
|
|
|
|
uint32_t i;
|
|
|
|
uint16_t head_idx, idx;
|
|
|
|
uint32_t total_iovs = req->iovcnt + 2;
|
|
|
|
struct iovec *iov = req->iov;
|
|
|
|
|
|
|
|
if (total_iovs > vq->vq_free_cnt) {
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV,
|
|
|
|
"not enough free descriptors. requested %"PRIu32", got %"PRIu16"\n",
|
|
|
|
total_iovs, vq->vq_free_cnt);
|
2017-10-17 14:25:17 +02:00
|
|
|
return -ENOMEM;
|
2017-10-14 11:57:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
head_idx = vq->vq_desc_head_idx;
|
|
|
|
idx = head_idx;
|
|
|
|
dxp = &vq->vq_descx[idx];
|
|
|
|
dxp->cookie = (void *)req;
|
|
|
|
dxp->ndescs = total_iovs;
|
|
|
|
|
|
|
|
descs = vq->vq_ring.desc;
|
|
|
|
|
|
|
|
virtqueue_iov_to_desc(vq, idx, &req->iov_req);
|
|
|
|
descs[idx].flags = VRING_DESC_F_NEXT;
|
|
|
|
idx = descs[idx].next;
|
|
|
|
|
2017-10-18 19:55:27 +02:00
|
|
|
if (req->is_write || req->iovcnt == 0) {
|
2017-10-14 11:57:03 +02:00
|
|
|
for (i = 0; i < req->iovcnt; i++) {
|
|
|
|
virtqueue_iov_to_desc(vq, idx, &iov[i]);
|
|
|
|
descs[idx].flags = VRING_DESC_F_NEXT;
|
|
|
|
idx = descs[idx].next;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtqueue_iov_to_desc(vq, idx, &req->iov_resp);
|
|
|
|
descs[idx].flags = VRING_DESC_F_WRITE;
|
|
|
|
idx = descs[idx].next;
|
|
|
|
} else {
|
|
|
|
virtqueue_iov_to_desc(vq, idx, &req->iov_resp);
|
|
|
|
descs[idx].flags = VRING_DESC_F_WRITE | VRING_DESC_F_NEXT;
|
|
|
|
idx = descs[idx].next;
|
|
|
|
|
|
|
|
for (i = 0; i < req->iovcnt; i++) {
|
|
|
|
virtqueue_iov_to_desc(vq, idx, &iov[i]);
|
|
|
|
descs[idx].flags = VRING_DESC_F_WRITE;
|
|
|
|
descs[idx].flags |= (i + 1) != req->iovcnt ? VRING_DESC_F_NEXT : 0;
|
|
|
|
idx = descs[idx].next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vq->vq_desc_head_idx = idx;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
vq->vq_free_cnt = (uint16_t)(vq->vq_free_cnt - total_iovs);
|
|
|
|
vq_update_avail_ring(vq, head_idx);
|
2017-10-17 14:25:17 +02:00
|
|
|
return 0;
|
2017-10-14 11:57:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#define VIRTIO_MBUF_BURST_SZ 64
|
|
|
|
#define DESC_PER_CACHELINE (RTE_CACHE_LINE_SIZE / sizeof(struct vring_desc))
|
|
|
|
uint16_t
|
|
|
|
virtio_recv_pkts(struct virtqueue *vq, struct virtio_req **reqs, uint16_t nb_pkts)
|
|
|
|
{
|
|
|
|
struct virtio_req *rxm;
|
|
|
|
uint16_t nb_used, num, nb_rx;
|
|
|
|
uint32_t len[VIRTIO_MBUF_BURST_SZ];
|
|
|
|
struct virtio_req *rcv_pkts[VIRTIO_MBUF_BURST_SZ];
|
|
|
|
uint32_t i;
|
|
|
|
|
2017-11-20 17:53:23 +01:00
|
|
|
assert(virtio_dev_get_status(vq->vdev) & VIRTIO_CONFIG_S_DRIVER_OK);
|
2017-10-14 11:57:03 +02:00
|
|
|
|
2017-11-20 17:53:23 +01:00
|
|
|
nb_rx = 0;
|
2017-10-14 11:57:03 +02:00
|
|
|
nb_used = VIRTQUEUE_NUSED(vq);
|
|
|
|
|
|
|
|
virtio_rmb();
|
|
|
|
|
|
|
|
num = (uint16_t)(spdk_likely(nb_used <= nb_pkts) ? nb_used : nb_pkts);
|
|
|
|
num = (uint16_t)(spdk_likely(num <= VIRTIO_MBUF_BURST_SZ) ? num : VIRTIO_MBUF_BURST_SZ);
|
|
|
|
if (spdk_likely(num > DESC_PER_CACHELINE))
|
|
|
|
num = num - ((vq->vq_used_cons_idx + num) % DESC_PER_CACHELINE);
|
|
|
|
|
|
|
|
num = virtqueue_dequeue_burst_rx(vq, rcv_pkts, len, num);
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "used:%"PRIu16" dequeue:%"PRIu16"\n", nb_used, num);
|
2017-10-14 11:57:03 +02:00
|
|
|
|
|
|
|
for (i = 0; i < num ; i++) {
|
|
|
|
rxm = rcv_pkts[i];
|
|
|
|
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "packet len:%"PRIu32"\n", len[i]);
|
2017-10-14 11:57:03 +02:00
|
|
|
|
|
|
|
rxm->data_transferred = (uint16_t)(len[i]);
|
|
|
|
|
|
|
|
reqs[nb_rx++] = rxm;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nb_rx;
|
|
|
|
}
|
|
|
|
|
2017-10-17 14:25:17 +02:00
|
|
|
int
|
|
|
|
virtio_xmit_pkt(struct virtqueue *vq, struct virtio_req *req)
|
2017-10-14 11:57:03 +02:00
|
|
|
{
|
|
|
|
struct virtio_dev *vdev = vq->vdev;
|
2017-10-17 14:25:17 +02:00
|
|
|
int rc;
|
2017-10-14 11:57:03 +02:00
|
|
|
|
2017-11-20 17:53:23 +01:00
|
|
|
assert(virtio_dev_get_status(vdev) & VIRTIO_CONFIG_S_DRIVER_OK);
|
2017-10-14 11:57:03 +02:00
|
|
|
virtio_rmb();
|
|
|
|
|
2017-10-17 14:25:17 +02:00
|
|
|
rc = virtqueue_enqueue_xmit(vq, req);
|
|
|
|
if (spdk_unlikely(rc != 0)) {
|
|
|
|
return rc;
|
|
|
|
}
|
2017-10-14 11:57:03 +02:00
|
|
|
|
|
|
|
vq_update_avail_idx(vq);
|
|
|
|
|
|
|
|
if (spdk_unlikely(virtqueue_kick_prepare(vq))) {
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_backend_ops(vdev)->notify_queue(vdev, vq);
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_DEBUGLOG(SPDK_TRACE_VIRTIO_DEV, "Notified backend after xmit\n");
|
2017-10-14 11:57:03 +02:00
|
|
|
}
|
|
|
|
|
2017-10-17 14:25:17 +02:00
|
|
|
return 0;
|
2017-10-14 11:57:03 +02:00
|
|
|
}
|
2017-10-14 12:43:58 +02:00
|
|
|
|
2017-10-10 20:44:35 +02:00
|
|
|
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];
|
2017-11-23 20:15:23 +01:00
|
|
|
if (vq == NULL || vq->owner_thread != NULL) {
|
2017-10-10 20:44:35 +02:00
|
|
|
pthread_mutex_unlock(&vdev->mutex);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(vq->poller == NULL);
|
2017-11-23 20:15:23 +01:00
|
|
|
vq->owner_thread = spdk_get_thread();
|
2017-10-10 20:44:35 +02:00
|
|
|
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];
|
2017-11-23 20:15:23 +01:00
|
|
|
if (vq != NULL && vq->owner_thread == NULL) {
|
2017-10-10 20:44:35 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(vq->poller == NULL);
|
2017-11-23 20:15:23 +01:00
|
|
|
vq->owner_thread = spdk_get_thread();
|
2017-10-10 20:44:35 +02:00
|
|
|
pthread_mutex_unlock(&vdev->mutex);
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2017-11-23 20:34:44 +01:00
|
|
|
struct spdk_thread *
|
|
|
|
virtio_dev_queue_get_thread(struct virtio_dev *vdev, uint16_t index)
|
2017-10-11 13:45:53 +02:00
|
|
|
{
|
2017-11-23 20:34:44 +01:00
|
|
|
struct virtqueue *vq;
|
|
|
|
struct spdk_thread *thread = NULL;
|
2017-10-11 13:45:53 +02:00
|
|
|
|
|
|
|
if (index >= vdev->max_queues) {
|
|
|
|
SPDK_ERRLOG("given vq index %"PRIu16" exceeds max queue count %"PRIu16"\n",
|
|
|
|
index, vdev->max_queues);
|
2017-11-23 20:34:44 +01:00
|
|
|
return NULL;
|
2017-10-11 13:45:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pthread_mutex_lock(&vdev->mutex);
|
|
|
|
vq = vdev->vqs[index];
|
2017-11-23 20:34:44 +01:00
|
|
|
if (vq != NULL) {
|
|
|
|
thread = vq->owner_thread;
|
2017-10-11 13:45:53 +02:00
|
|
|
}
|
|
|
|
pthread_mutex_unlock(&vdev->mutex);
|
|
|
|
|
2017-11-23 20:34:44 +01:00
|
|
|
return thread;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
virtio_dev_queue_is_acquired(struct virtio_dev *vdev, uint16_t index)
|
|
|
|
{
|
|
|
|
return virtio_dev_queue_get_thread(vdev, index) != NULL;
|
2017-10-11 13:45:53 +02:00
|
|
|
}
|
|
|
|
|
2017-10-10 20:44:35 +02:00
|
|
|
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);
|
2017-10-16 13:44:51 -07:00
|
|
|
pthread_mutex_unlock(&vdev->mutex);
|
2017-10-10 20:44:35 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(vq->poller == NULL);
|
2017-11-23 20:15:23 +01:00
|
|
|
assert(vq->owner_thread == spdk_get_thread());
|
|
|
|
vq->owner_thread = NULL;
|
2017-10-10 20:44:35 +02:00
|
|
|
pthread_mutex_unlock(&vdev->mutex);
|
|
|
|
}
|
|
|
|
|
2017-11-02 15:20:25 +01:00
|
|
|
void
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_read_dev_config(struct virtio_dev *dev, size_t offset,
|
|
|
|
void *dst, int length)
|
2017-11-02 15:20:25 +01:00
|
|
|
{
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_backend_ops(dev)->read_dev_cfg(dev, offset, dst, length);
|
2017-11-02 15:20:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_write_dev_config(struct virtio_dev *dev, size_t offset,
|
|
|
|
const void *src, int length)
|
2017-11-02 15:20:25 +01:00
|
|
|
{
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_backend_ops(dev)->write_dev_cfg(dev, offset, src, length);
|
2017-11-02 15:20:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_reset(struct virtio_dev *dev)
|
2017-11-02 15:20:25 +01:00
|
|
|
{
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_backend_ops(dev)->set_status(dev, VIRTIO_CONFIG_S_RESET);
|
2017-11-02 15:20:25 +01:00
|
|
|
/* flush status write */
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_backend_ops(dev)->get_status(dev);
|
2017-11-02 15:20:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_set_status(struct virtio_dev *dev, uint8_t status)
|
2017-11-02 15:20:25 +01:00
|
|
|
{
|
|
|
|
if (status != VIRTIO_CONFIG_S_RESET)
|
2017-11-02 17:10:56 +01:00
|
|
|
status |= virtio_dev_backend_ops(dev)->get_status(dev);
|
2017-11-02 15:20:25 +01:00
|
|
|
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_backend_ops(dev)->set_status(dev, status);
|
2017-11-02 15:20:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_get_status(struct virtio_dev *dev)
|
2017-11-02 15:20:25 +01:00
|
|
|
{
|
2017-11-02 17:10:56 +01:00
|
|
|
return virtio_dev_backend_ops(dev)->get_status(dev);
|
2017-11-02 15:20:25 +01:00
|
|
|
}
|
|
|
|
|
2017-11-03 15:41:31 +01:00
|
|
|
const struct virtio_dev_ops *
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_backend_ops(struct virtio_dev *dev)
|
2017-11-02 15:20:25 +01:00
|
|
|
{
|
|
|
|
return dev->backend_ops;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_dump_json_config(struct virtio_dev *hw, struct spdk_json_write_ctx *w)
|
2017-11-02 15:20:25 +01:00
|
|
|
{
|
|
|
|
spdk_json_write_name(w, "virtio");
|
|
|
|
spdk_json_write_object_begin(w);
|
|
|
|
|
|
|
|
spdk_json_write_name(w, "vq_count");
|
|
|
|
spdk_json_write_uint32(w, hw->max_queues);
|
|
|
|
|
|
|
|
spdk_json_write_name(w, "vq_size");
|
2017-11-02 17:10:56 +01:00
|
|
|
spdk_json_write_uint32(w, virtio_dev_backend_ops(hw)->get_queue_num(hw, 0));
|
2017-11-02 15:20:25 +01:00
|
|
|
|
2017-11-02 17:10:56 +01:00
|
|
|
virtio_dev_backend_ops(hw)->dump_json_config(hw, w);
|
2017-11-02 15:20:25 +01:00
|
|
|
|
|
|
|
spdk_json_write_object_end(w);
|
|
|
|
}
|
|
|
|
|
2017-10-14 12:43:58 +02:00
|
|
|
SPDK_LOG_REGISTER_TRACE_FLAG("virtio_dev", SPDK_TRACE_VIRTIO_DEV)
|