| /* |
| *------------------------------------------------------------------ |
| * 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: |
| */ |