| /* |
| *------------------------------------------------------------------ |
| * 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 <vnet/udp/udp_packet.h> |
| |
| #include <vmxnet3/vmxnet3.h> |
| |
| #define foreach_vmxnet3_input_error \ |
| _(BUFFER_ALLOC, "buffer alloc error") \ |
| _(RX_PACKET_NO_SOP, "Rx packet error - no SOP") \ |
| _(RX_PACKET, "Rx packet error") \ |
| _(RX_PACKET_EOP, "Rx packet error found on EOP") \ |
| _(NO_BUFFER, "Rx no buffer error") |
| |
| typedef enum |
| { |
| #define _(f,s) VMXNET3_INPUT_ERROR_##f, |
| foreach_vmxnet3_input_error |
| #undef _ |
| VMXNET3_INPUT_N_ERROR, |
| } vmxnet3_input_error_t; |
| |
| static __clib_unused char *vmxnet3_input_error_strings[] = { |
| #define _(n,s) s, |
| foreach_vmxnet3_input_error |
| #undef _ |
| }; |
| |
| static_always_inline u16 |
| vmxnet3_find_rid (vmxnet3_device_t * vd, vmxnet3_rx_comp * rx_comp) |
| { |
| u32 rid; |
| |
| // rid is bits 16-25 (10 bits number) |
| rid = rx_comp->index & (0xffffffff >> 6); |
| rid >>= 16; |
| if ((rid >= vd->num_rx_queues) && (rid < (vd->num_rx_queues << 1))) |
| return 1; |
| else |
| return 0; |
| } |
| |
| static_always_inline void |
| vmxnet3_rx_comp_ring_advance_next (vmxnet3_rxq_t * rxq) |
| { |
| vmxnet3_rx_comp_ring *comp_ring = &rxq->rx_comp_ring; |
| |
| comp_ring->next++; |
| if (PREDICT_FALSE (comp_ring->next == rxq->size)) |
| { |
| comp_ring->next = 0; |
| comp_ring->gen ^= VMXNET3_RXCF_GEN; |
| } |
| } |
| |
| static_always_inline void |
| vmxnet3_handle_offload (vmxnet3_rx_comp * rx_comp, vlib_buffer_t * hb, |
| u16 gso_size) |
| { |
| u8 l4_hdr_sz = 0; |
| |
| if (rx_comp->flags & VMXNET3_RXCF_IP4) |
| { |
| ip4_header_t *ip4 = (ip4_header_t *) (hb->data + |
| sizeof (ethernet_header_t)); |
| |
| vnet_buffer (hb)->l2_hdr_offset = 0; |
| vnet_buffer (hb)->l3_hdr_offset = sizeof (ethernet_header_t); |
| vnet_buffer (hb)->l4_hdr_offset = sizeof (ethernet_header_t) + |
| ip4_header_bytes (ip4); |
| hb->flags |= VNET_BUFFER_F_L2_HDR_OFFSET_VALID | |
| VNET_BUFFER_F_L3_HDR_OFFSET_VALID | |
| VNET_BUFFER_F_L4_HDR_OFFSET_VALID | VNET_BUFFER_F_IS_IP4; |
| |
| /* checksum offload */ |
| if (!(rx_comp->index & VMXNET3_RXCI_CNC)) |
| { |
| if (!(rx_comp->flags & VMXNET3_RXCF_IPC)) |
| { |
| hb->flags |= VNET_BUFFER_F_OFFLOAD_IP_CKSUM; |
| ip4->checksum = 0; |
| } |
| if (!(rx_comp->flags & VMXNET3_RXCF_TUC)) |
| { |
| if (rx_comp->flags & VMXNET3_RXCF_TCP) |
| { |
| tcp_header_t *tcp = |
| (tcp_header_t *) (hb->data + |
| vnet_buffer (hb)->l4_hdr_offset); |
| hb->flags |= VNET_BUFFER_F_OFFLOAD_TCP_CKSUM; |
| tcp->checksum = 0; |
| } |
| else if (rx_comp->flags & VMXNET3_RXCF_UDP) |
| { |
| udp_header_t *udp = |
| (udp_header_t *) (hb->data + |
| vnet_buffer (hb)->l4_hdr_offset); |
| hb->flags |= VNET_BUFFER_F_OFFLOAD_UDP_CKSUM; |
| udp->checksum = 0; |
| } |
| } |
| } |
| |
| if (gso_size) |
| { |
| if (rx_comp->flags & VMXNET3_RXCF_TCP) |
| { |
| tcp_header_t *tcp = |
| (tcp_header_t *) (hb->data + vnet_buffer (hb)->l4_hdr_offset); |
| l4_hdr_sz = tcp_header_bytes (tcp); |
| } |
| else if (rx_comp->flags & VMXNET3_RXCF_UDP) |
| { |
| udp_header_t *udp = |
| (udp_header_t *) (hb->data + vnet_buffer (hb)->l4_hdr_offset); |
| l4_hdr_sz = sizeof (*udp); |
| } |
| vnet_buffer2 (hb)->gso_size = gso_size; |
| vnet_buffer2 (hb)->gso_l4_hdr_sz = l4_hdr_sz; |
| hb->flags |= VNET_BUFFER_F_GSO; |
| } |
| } |
| else if (rx_comp->flags & VMXNET3_RXCF_IP6) |
| { |
| vnet_buffer (hb)->l2_hdr_offset = 0; |
| vnet_buffer (hb)->l3_hdr_offset = sizeof (ethernet_header_t); |
| vnet_buffer (hb)->l4_hdr_offset = sizeof (ethernet_header_t) + |
| sizeof (ip6_header_t); |
| hb->flags |= VNET_BUFFER_F_L2_HDR_OFFSET_VALID | |
| VNET_BUFFER_F_L3_HDR_OFFSET_VALID | |
| VNET_BUFFER_F_L4_HDR_OFFSET_VALID | VNET_BUFFER_F_IS_IP6; |
| |
| /* checksum offload */ |
| if (!(rx_comp->index & VMXNET3_RXCI_CNC)) |
| { |
| if (!(rx_comp->flags & VMXNET3_RXCF_TUC)) |
| { |
| if (rx_comp->flags & VMXNET3_RXCF_TCP) |
| { |
| tcp_header_t *tcp = |
| (tcp_header_t *) (hb->data + |
| vnet_buffer (hb)->l4_hdr_offset); |
| hb->flags |= VNET_BUFFER_F_OFFLOAD_TCP_CKSUM; |
| tcp->checksum = 0; |
| } |
| else if (rx_comp->flags & VMXNET3_RXCF_UDP) |
| { |
| udp_header_t *udp = |
| (udp_header_t *) (hb->data + |
| vnet_buffer (hb)->l4_hdr_offset); |
| hb->flags |= VNET_BUFFER_F_OFFLOAD_UDP_CKSUM; |
| udp->checksum = 0; |
| } |
| } |
| } |
| |
| if (gso_size) |
| { |
| if (rx_comp->flags & VMXNET3_RXCF_TCP) |
| { |
| tcp_header_t *tcp = |
| (tcp_header_t *) (hb->data + vnet_buffer (hb)->l4_hdr_offset); |
| l4_hdr_sz = tcp_header_bytes (tcp); |
| } |
| else if (rx_comp->flags & VMXNET3_RXCF_UDP) |
| { |
| udp_header_t *udp = |
| (udp_header_t *) (hb->data + vnet_buffer (hb)->l4_hdr_offset); |
| l4_hdr_sz = sizeof (*udp); |
| } |
| vnet_buffer2 (hb)->gso_size = gso_size; |
| vnet_buffer2 (hb)->gso_l4_hdr_sz = l4_hdr_sz; |
| hb->flags |= VNET_BUFFER_F_GSO; |
| } |
| } |
| } |
| |
| static_always_inline uword |
| vmxnet3_device_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, |
| vlib_frame_t * frame, vmxnet3_device_t * vd, |
| u16 qid) |
| { |
| vnet_main_t *vnm = vnet_get_main (); |
| uword n_trace = vlib_get_trace_count (vm, node); |
| u32 n_rx_packets = 0, n_rx_bytes = 0; |
| vmxnet3_rx_comp *rx_comp; |
| u32 desc_idx; |
| vmxnet3_rxq_t *rxq; |
| u32 thread_index = vm->thread_index; |
| u32 buffer_indices[VLIB_FRAME_SIZE], *bi; |
| u16 nexts[VLIB_FRAME_SIZE], *next; |
| vmxnet3_rx_ring *ring; |
| vmxnet3_rx_comp_ring *comp_ring; |
| u16 rid; |
| vlib_buffer_t *prev_b0 = 0, *hb = 0; |
| u32 next_index = VNET_DEVICE_INPUT_NEXT_ETHERNET_INPUT; |
| u8 known_next = 0, got_packet = 0; |
| vmxnet3_rx_desc *rxd; |
| clib_error_t *error; |
| u16 gso_size = 0; |
| |
| rxq = vec_elt_at_index (vd->rxqs, qid); |
| comp_ring = &rxq->rx_comp_ring; |
| bi = buffer_indices; |
| next = nexts; |
| rx_comp = &rxq->rx_comp[comp_ring->next]; |
| |
| while (PREDICT_TRUE ((n_rx_packets < VLIB_FRAME_SIZE) && |
| (comp_ring->gen == |
| (rx_comp->flags & VMXNET3_RXCF_GEN)))) |
| { |
| vlib_buffer_t *b0; |
| u32 bi0; |
| |
| rid = vmxnet3_find_rid (vd, rx_comp); |
| ring = &rxq->rx_ring[rid]; |
| |
| if (PREDICT_TRUE (ring->fill >= 1)) |
| ring->fill--; |
| else |
| { |
| vlib_error_count (vm, node->node_index, |
| VMXNET3_INPUT_ERROR_NO_BUFFER, 1); |
| if (hb) |
| { |
| vlib_buffer_free_one (vm, vlib_get_buffer_index (vm, hb)); |
| hb = 0; |
| } |
| prev_b0 = 0; |
| break; |
| } |
| |
| desc_idx = rx_comp->index & VMXNET3_RXC_INDEX; |
| ring->consume = desc_idx; |
| rxd = &rxq->rx_desc[rid][desc_idx]; |
| |
| bi0 = ring->bufs[desc_idx]; |
| ring->bufs[desc_idx] = ~0; |
| |
| b0 = vlib_get_buffer (vm, bi0); |
| vnet_buffer (b0)->sw_if_index[VLIB_RX] = vd->sw_if_index; |
| vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; |
| vnet_buffer (b0)->feature_arc_index = 0; |
| b0->current_length = rx_comp->len & VMXNET3_RXCL_LEN_MASK; |
| b0->current_data = 0; |
| b0->total_length_not_including_first_buffer = 0; |
| b0->next_buffer = 0; |
| b0->flags = 0; |
| b0->error = 0; |
| b0->current_config_index = 0; |
| |
| if (PREDICT_FALSE ((rx_comp->index & VMXNET3_RXCI_EOP) && |
| (rx_comp->len & VMXNET3_RXCL_ERROR))) |
| { |
| vlib_buffer_free_one (vm, bi0); |
| vlib_error_count (vm, node->node_index, |
| VMXNET3_INPUT_ERROR_RX_PACKET_EOP, 1); |
| if (hb && vlib_get_buffer_index (vm, hb) != bi0) |
| { |
| vlib_buffer_free_one (vm, vlib_get_buffer_index (vm, hb)); |
| hb = 0; |
| } |
| prev_b0 = 0; |
| goto next; |
| } |
| |
| if (rx_comp->index & VMXNET3_RXCI_SOP) |
| { |
| ASSERT (!(rxd->flags & VMXNET3_RXF_BTYPE)); |
| /* start segment */ |
| if (vd->gso_enable && |
| (rx_comp->flags & VMXNET3_RXCF_CT) == VMXNET3_RXCOMP_TYPE_LRO) |
| { |
| vmxnet3_rx_comp_ext *lro = (vmxnet3_rx_comp_ext *) rx_comp; |
| |
| gso_size = lro->flags & VMXNET3_RXECF_MSS_MASK; |
| } |
| |
| hb = b0; |
| bi[0] = bi0; |
| if (!(rx_comp->index & VMXNET3_RXCI_EOP)) |
| { |
| hb->flags = VLIB_BUFFER_TOTAL_LENGTH_VALID; |
| prev_b0 = b0; |
| } |
| else |
| { |
| /* |
| * Both start and end of packet is set. It is a complete packet |
| */ |
| prev_b0 = 0; |
| got_packet = 1; |
| } |
| } |
| else if (rx_comp->index & VMXNET3_RXCI_EOP) |
| { |
| /* end of segment */ |
| if (PREDICT_TRUE (prev_b0 != 0)) |
| { |
| if (PREDICT_TRUE (b0->current_length != 0)) |
| { |
| prev_b0->flags |= VLIB_BUFFER_NEXT_PRESENT; |
| prev_b0->next_buffer = bi0; |
| hb->total_length_not_including_first_buffer += |
| b0->current_length; |
| } |
| else |
| { |
| vlib_buffer_free_one (vm, bi0); |
| } |
| prev_b0 = 0; |
| got_packet = 1; |
| } |
| else |
| { |
| /* EOP without SOP, error */ |
| vlib_error_count (vm, node->node_index, |
| VMXNET3_INPUT_ERROR_RX_PACKET_NO_SOP, 1); |
| vlib_buffer_free_one (vm, bi0); |
| if (hb && vlib_get_buffer_index (vm, hb) != bi0) |
| { |
| vlib_buffer_free_one (vm, vlib_get_buffer_index (vm, hb)); |
| hb = 0; |
| } |
| goto next; |
| } |
| } |
| else if (prev_b0) // !sop && !eop |
| { |
| /* mid chain */ |
| ASSERT (rxd->flags & VMXNET3_RXF_BTYPE); |
| prev_b0->flags |= VLIB_BUFFER_NEXT_PRESENT; |
| prev_b0->next_buffer = bi0; |
| prev_b0 = b0; |
| hb->total_length_not_including_first_buffer += b0->current_length; |
| } |
| else |
| { |
| vlib_error_count (vm, node->node_index, |
| VMXNET3_INPUT_ERROR_RX_PACKET, 1); |
| vlib_buffer_free_one (vm, bi0); |
| if (hb && vlib_get_buffer_index (vm, hb) != bi0) |
| { |
| vlib_buffer_free_one (vm, vlib_get_buffer_index (vm, hb)); |
| hb = 0; |
| } |
| goto next; |
| } |
| |
| n_rx_bytes += b0->current_length; |
| |
| if (got_packet) |
| { |
| if (PREDICT_FALSE (vd->per_interface_next_index != ~0)) |
| { |
| next_index = vd->per_interface_next_index; |
| known_next = 1; |
| } |
| |
| if (PREDICT_FALSE |
| (vnet_device_input_have_features (vd->sw_if_index))) |
| { |
| vnet_feature_start_device_input_x1 (vd->sw_if_index, |
| &next_index, hb); |
| known_next = 1; |
| } |
| |
| if (PREDICT_FALSE (known_next)) |
| next[0] = next_index; |
| else |
| { |
| ethernet_header_t *e = (ethernet_header_t *) hb->data; |
| |
| next[0] = VNET_DEVICE_INPUT_NEXT_ETHERNET_INPUT; |
| if (!ethernet_frame_is_tagged (ntohs (e->type))) |
| vmxnet3_handle_offload (rx_comp, hb, gso_size); |
| } |
| |
| n_rx_packets++; |
| next++; |
| bi++; |
| hb = 0; |
| got_packet = 0; |
| gso_size = 0; |
| } |
| |
| next: |
| vmxnet3_rx_comp_ring_advance_next (rxq); |
| rx_comp = &rxq->rx_comp[comp_ring->next]; |
| } |
| |
| if (PREDICT_FALSE ((n_trace = vlib_get_trace_count (vm, node)))) |
| { |
| u32 n_left = n_rx_packets; |
| |
| bi = buffer_indices; |
| next = nexts; |
| while (n_trace && n_left) |
| { |
| vlib_buffer_t *b; |
| vmxnet3_input_trace_t *tr; |
| |
| b = vlib_get_buffer (vm, bi[0]); |
| vlib_trace_buffer (vm, node, next[0], b, /* follow_chain */ 0); |
| tr = vlib_add_trace (vm, node, b, sizeof (*tr)); |
| tr->next_index = next[0]; |
| tr->hw_if_index = vd->hw_if_index; |
| tr->buffer = *b; |
| |
| n_trace--; |
| n_left--; |
| bi++; |
| next++; |
| } |
| vlib_set_trace_count (vm, node, n_trace); |
| } |
| |
| if (PREDICT_TRUE (n_rx_packets)) |
| { |
| vlib_buffer_enqueue_to_next (vm, node, buffer_indices, nexts, |
| n_rx_packets); |
| vlib_increment_combined_counter |
| (vnm->interface_main.combined_sw_if_counters + |
| VNET_INTERFACE_COUNTER_RX, thread_index, |
| vd->sw_if_index, n_rx_packets, n_rx_bytes); |
| } |
| |
| error = vmxnet3_rxq_refill_ring0 (vm, vd, rxq); |
| if (PREDICT_FALSE (error != 0)) |
| { |
| vlib_error_count (vm, node->node_index, |
| VMXNET3_INPUT_ERROR_BUFFER_ALLOC, 1); |
| } |
| error = vmxnet3_rxq_refill_ring1 (vm, vd, rxq); |
| if (PREDICT_FALSE (error != 0)) |
| { |
| vlib_error_count (vm, node->node_index, |
| VMXNET3_INPUT_ERROR_BUFFER_ALLOC, 1); |
| } |
| |
| return n_rx_packets; |
| } |
| |
| VLIB_NODE_FN (vmxnet3_input_node) (vlib_main_t * vm, |
| vlib_node_runtime_t * node, |
| vlib_frame_t * frame) |
| { |
| u32 n_rx = 0; |
| vmxnet3_main_t *vmxm = &vmxnet3_main; |
| vnet_device_input_runtime_t *rt = (void *) node->runtime_data; |
| vnet_device_and_queue_t *dq; |
| |
| foreach_device_and_queue (dq, rt->devices_and_queues) |
| { |
| vmxnet3_device_t *vd; |
| vd = vec_elt_at_index (vmxm->devices, dq->dev_instance); |
| if ((vd->flags & VMXNET3_DEVICE_F_ADMIN_UP) == 0) |
| continue; |
| n_rx += vmxnet3_device_input_inline (vm, node, frame, vd, dq->queue_id); |
| } |
| return n_rx; |
| } |
| |
| #ifndef CLIB_MARCH_VARIANT |
| /* *INDENT-OFF* */ |
| VLIB_REGISTER_NODE (vmxnet3_input_node) = { |
| .name = "vmxnet3-input", |
| .sibling_of = "device-input", |
| .flags = VLIB_NODE_FLAG_TRACE_SUPPORTED, |
| .format_trace = format_vmxnet3_input_trace, |
| .type = VLIB_NODE_TYPE_INPUT, |
| .state = VLIB_NODE_STATE_DISABLED, |
| .n_errors = VMXNET3_INPUT_N_ERROR, |
| .error_strings = vmxnet3_input_error_strings, |
| }; |
| #endif |
| |
| /* *INDENT-ON* */ |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |