blob: 8ba3f99022f8f7497e236bf37ff3ec958aaefb64 [file] [log] [blame]
/*
*------------------------------------------------------------------
* Copyright (c) 2018 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*------------------------------------------------------------------
*/
#include <vlib/vlib.h>
#include <vlib/unix/unix.h>
#include <vlib/pci/pci.h>
#include <vnet/ethernet/ethernet.h>
#include <vnet/devices/devices.h>
#include <vnet/ip/ip6_packet.h>
#include <vnet/ip/ip4_packet.h>
#include <vmxnet3/vmxnet3.h>
static_always_inline void
vmxnet3_tx_comp_ring_advance_next (vmxnet3_txq_t * txq)
{
vmxnet3_tx_comp_ring *comp_ring = &txq->tx_comp_ring;
comp_ring->next++;
if (PREDICT_FALSE (comp_ring->next == txq->size))
{
comp_ring->next = 0;
comp_ring->gen ^= VMXNET3_TXCF_GEN;
}
}
static_always_inline void
vmxnet3_tx_ring_advance_produce (vmxnet3_txq_t * txq)
{
txq->tx_ring.produce++;
if (PREDICT_FALSE (txq->tx_ring.produce == txq->size))
{
txq->tx_ring.produce = 0;
txq->tx_ring.gen ^= VMXNET3_TXF_GEN;
}
}
static_always_inline void
vmxnet3_tx_ring_advance_consume (vmxnet3_txq_t * txq)
{
txq->tx_ring.consume++;
txq->tx_ring.consume &= txq->size - 1;
}
static_always_inline void
vmxnet3_txq_release (vlib_main_t * vm, vmxnet3_device_t * vd,
vmxnet3_txq_t * txq)
{
vmxnet3_tx_comp *tx_comp;
vmxnet3_tx_comp_ring *comp_ring;
comp_ring = &txq->tx_comp_ring;
tx_comp = &txq->tx_comp[comp_ring->next];
while ((tx_comp->flags & VMXNET3_TXCF_GEN) == comp_ring->gen)
{
u16 eop_idx = tx_comp->index & VMXNET3_TXC_INDEX;
u32 bi0 = txq->tx_ring.bufs[txq->tx_ring.consume];
vlib_buffer_free_one (vm, bi0);
while (txq->tx_ring.consume != eop_idx)
{
vmxnet3_tx_ring_advance_consume (txq);
}
vmxnet3_tx_ring_advance_consume (txq);
vmxnet3_tx_comp_ring_advance_next (txq);
tx_comp = &txq->tx_comp[comp_ring->next];
}
}
static_always_inline u16
vmxnet3_tx_ring_space_left (vmxnet3_txq_t * txq)
{
u16 count;
count = (txq->tx_ring.consume - txq->tx_ring.produce - 1);
/* Wrapped? */
if (txq->tx_ring.produce >= txq->tx_ring.consume)
count += txq->size;
return count;
}
VNET_DEVICE_CLASS_TX_FN (vmxnet3_device_class) (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
vmxnet3_main_t *vmxm = &vmxnet3_main;
vnet_interface_output_runtime_t *rd = (void *) node->runtime_data;
vmxnet3_device_t *vd = pool_elt_at_index (vmxm->devices, rd->dev_instance);
u32 *buffers = vlib_frame_vector_args (frame);
u32 bi0;
vlib_buffer_t *b0;
vmxnet3_tx_desc *txd = 0;
u32 desc_idx, generation, first_idx;
u16 space_left;
u16 n_left = frame->n_vectors;
vmxnet3_txq_t *txq;
vnet_hw_if_tx_frame_t *tf = vlib_frame_scalar_args (frame);
u16 qid = tf->queue_id, produce;
if (PREDICT_FALSE (!(vd->flags & VMXNET3_DEVICE_F_LINK_UP)))
{
vlib_buffer_free (vm, buffers, n_left);
vlib_error_count (vm, node->node_index, VMXNET3_TX_ERROR_LINK_DOWN,
n_left);
return (0);
}
txq = vec_elt_at_index (vd->txqs, qid);
if (tf->shared_queue)
clib_spinlock_lock (&txq->lock);
vmxnet3_txq_release (vm, vd, txq);
produce = txq->tx_ring.produce;
while (PREDICT_TRUE (n_left))
{
u16 space_needed = 1, i;
u32 gso_size = 0;
u32 l4_hdr_sz;
vlib_buffer_t *b;
u32 hdr_len = 0;
bi0 = buffers[0];
b0 = vlib_get_buffer (vm, bi0);
b = b0;
space_left = vmxnet3_tx_ring_space_left (txq);
while (b->flags & VLIB_BUFFER_NEXT_PRESENT)
{
u32 next_buffer = b->next_buffer;
b = vlib_get_buffer (vm, next_buffer);
space_needed++;
}
if (PREDICT_FALSE (space_left < space_needed))
{
vmxnet3_txq_release (vm, vd, txq);
space_left = vmxnet3_tx_ring_space_left (txq);
if (PREDICT_FALSE (space_left < space_needed))
{
vlib_buffer_free_one (vm, bi0);
vlib_error_count (vm, node->node_index,
VMXNET3_TX_ERROR_NO_FREE_SLOTS, 1);
buffers++;
n_left--;
/*
* Drop this packet. But we may have enough room for the next
* packet
*/
continue;
}
}
/*
* Toggle the generation bit for SOP fragment to avoid device starts
* reading incomplete packet
*/
generation = txq->tx_ring.gen ^ VMXNET3_TXF_GEN;
first_idx = txq->tx_ring.produce;
for (i = 0; i < space_needed; i++)
{
b0 = vlib_get_buffer (vm, bi0);
desc_idx = txq->tx_ring.produce;
vmxnet3_tx_ring_advance_produce (txq);
txq->tx_ring.bufs[desc_idx] = bi0;
txd = &txq->tx_desc[desc_idx];
txd->address = vlib_buffer_get_current_pa (vm, b0);
txd->flags[0] = generation | b0->current_length;
txd->flags[1] = 0;
if (PREDICT_FALSE (b0->flags & VNET_BUFFER_F_GSO))
{
/*
* We should not be getting GSO outbound traffic unless it is
* lro is enable
*/
ASSERT (vd->gso_enable == 1);
gso_size = vnet_buffer2 (b0)->gso_size;
l4_hdr_sz = vnet_buffer2 (b0)->gso_l4_hdr_sz;
if (b0->flags & VNET_BUFFER_F_IS_IP6)
hdr_len = sizeof (ethernet_header_t) + sizeof (ip6_header_t) +
l4_hdr_sz;
else
hdr_len = sizeof (ethernet_header_t) + sizeof (ip4_header_t) +
l4_hdr_sz;
}
generation = txq->tx_ring.gen;
bi0 = b0->next_buffer;
}
if (PREDICT_FALSE (gso_size != 0))
{
txq->tx_desc[first_idx].flags[1] = hdr_len;
txq->tx_desc[first_idx].flags[1] |= VMXNET3_TXF_OM (VMXNET3_OM_TSO);
txq->tx_desc[first_idx].flags[0] |= VMXNET3_TXF_MSSCOF (gso_size);
}
txd->flags[1] |= VMXNET3_TXF_CQ | VMXNET3_TXF_EOP;
asm volatile ("":::"memory");
/*
* Now toggle back the generation bit for the first segment.
* Device can start reading the packet
*/
txq->tx_desc[first_idx].flags[0] ^= VMXNET3_TXF_GEN;
buffers++;
n_left--;
}
if (PREDICT_TRUE (produce != txq->tx_ring.produce))
vmxnet3_reg_write_inline (vd, 0, txq->reg_txprod, txq->tx_ring.produce);
if (tf->shared_queue)
clib_spinlock_unlock (&txq->lock);
return (frame->n_vectors - n_left);
}
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "gnu")
* End:
*/