enic: add vxlan offload on tx path
authorGovindarajulu Varadarajan <gvaradar@cisco.com>
Thu, 9 Feb 2017 00:43:09 +0000 (16:43 -0800)
committerDavid S. Miller <davem@davemloft.net>
Thu, 9 Feb 2017 22:24:29 +0000 (17:24 -0500)
Define ndo_features_check. Hw supports offload only for ipv4 inner and
ipv4 outer pkt.

Code refactor for setting inner tcp pseudo csum.

Signed-off-by: Govindarajulu Varadarajan <gvaradar@cisco.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/cisco/enic/enic_main.c

index 7e56bf9..4b87bee 100644 (file)
@@ -263,6 +263,48 @@ unlock:
        spin_unlock_bh(&enic->devcmd_lock);
 }
 
+static netdev_features_t enic_features_check(struct sk_buff *skb,
+                                            struct net_device *dev,
+                                            netdev_features_t features)
+{
+       const struct ethhdr *eth = (struct ethhdr *)skb_inner_mac_header(skb);
+       struct enic *enic = netdev_priv(dev);
+       struct udphdr *udph;
+       u16 port = 0;
+       u16 proto;
+
+       if (!skb->encapsulation)
+               return features;
+
+       features = vxlan_features_check(skb, features);
+
+       /* hardware only supports IPv4 vxlan tunnel */
+       if (vlan_get_protocol(skb) != htons(ETH_P_IP))
+               goto out;
+
+       /* hardware does not support offload of ipv6 inner pkt */
+       if (eth->h_proto != ntohs(ETH_P_IP))
+               goto out;
+
+       proto = ip_hdr(skb)->protocol;
+
+       if (proto == IPPROTO_UDP) {
+               udph = udp_hdr(skb);
+               port = be16_to_cpu(udph->dest);
+       }
+
+       /* HW supports offload of only one UDP port. Remove CSUM and GSO MASK
+        * for other UDP port tunnels
+        */
+       if (port  != enic->vxlan.vxlan_udp_port_number)
+               goto out;
+
+       return features;
+
+out:
+       return features & ~(NETIF_F_CSUM_MASK | NETIF_F_GSO_MASK);
+}
+
 int enic_is_dynamic(struct enic *enic)
 {
        return enic->pdev->device == PCI_DEVICE_ID_CISCO_VIC_ENET_DYN;
@@ -591,20 +633,19 @@ static int enic_queue_wq_skb_csum_l4(struct enic *enic, struct vnic_wq *wq,
        return err;
 }
 
-static int enic_queue_wq_skb_tso(struct enic *enic, struct vnic_wq *wq,
-                                struct sk_buff *skb, unsigned int mss,
-                                int vlan_tag_insert, unsigned int vlan_tag,
-                                int loopback)
+static void enic_preload_tcp_csum_encap(struct sk_buff *skb)
 {
-       unsigned int frag_len_left = skb_headlen(skb);
-       unsigned int len_left = skb->len - frag_len_left;
-       unsigned int hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb);
-       int eop = (len_left == 0);
-       unsigned int len;
-       dma_addr_t dma_addr;
-       unsigned int offset = 0;
-       skb_frag_t *frag;
+       if (skb->protocol == cpu_to_be16(ETH_P_IP)) {
+               inner_ip_hdr(skb)->check = 0;
+               inner_tcp_hdr(skb)->check =
+                       ~csum_tcpudp_magic(inner_ip_hdr(skb)->saddr,
+                                          inner_ip_hdr(skb)->daddr, 0,
+                                          IPPROTO_TCP, 0);
+       }
+}
 
+static void enic_preload_tcp_csum(struct sk_buff *skb)
+{
        /* Preload TCP csum field with IP pseudo hdr calculated
         * with IP length set to zero.  HW will later add in length
         * to each TCP segment resulting from the TSO.
@@ -618,6 +659,30 @@ static int enic_queue_wq_skb_tso(struct enic *enic, struct vnic_wq *wq,
                tcp_hdr(skb)->check = ~csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
                        &ipv6_hdr(skb)->daddr, 0, IPPROTO_TCP, 0);
        }
+}
+
+static int enic_queue_wq_skb_tso(struct enic *enic, struct vnic_wq *wq,
+                                struct sk_buff *skb, unsigned int mss,
+                                int vlan_tag_insert, unsigned int vlan_tag,
+                                int loopback)
+{
+       unsigned int frag_len_left = skb_headlen(skb);
+       unsigned int len_left = skb->len - frag_len_left;
+       int eop = (len_left == 0);
+       unsigned int offset = 0;
+       unsigned int hdr_len;
+       dma_addr_t dma_addr;
+       unsigned int len;
+       skb_frag_t *frag;
+
+       if (skb->encapsulation) {
+               hdr_len = skb_inner_transport_header(skb) - skb->data;
+               hdr_len += inner_tcp_hdrlen(skb);
+               enic_preload_tcp_csum_encap(skb);
+       } else {
+               hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb);
+               enic_preload_tcp_csum(skb);
+       }
 
        /* Queue WQ_ENET_MAX_DESC_LEN length descriptors
         * for the main skb fragment
@@ -666,6 +731,38 @@ static int enic_queue_wq_skb_tso(struct enic *enic, struct vnic_wq *wq,
        return 0;
 }
 
+static inline int enic_queue_wq_skb_encap(struct enic *enic, struct vnic_wq *wq,
+                                         struct sk_buff *skb,
+                                         int vlan_tag_insert,
+                                         unsigned int vlan_tag, int loopback)
+{
+       unsigned int head_len = skb_headlen(skb);
+       unsigned int len_left = skb->len - head_len;
+       /* Hardware will overwrite the checksum fields, calculating from
+        * scratch and ignoring the value placed by software.
+        * Offload mode = 00
+        * mss[2], mss[1], mss[0] bits are set
+        */
+       unsigned int mss_or_csum = 7;
+       int eop = (len_left == 0);
+       dma_addr_t dma_addr;
+       int err = 0;
+
+       dma_addr = pci_map_single(enic->pdev, skb->data, head_len,
+                                 PCI_DMA_TODEVICE);
+       if (unlikely(enic_dma_map_check(enic, dma_addr)))
+               return -ENOMEM;
+
+       enic_queue_wq_desc_ex(wq, skb, dma_addr, head_len, mss_or_csum, 0,
+                             vlan_tag_insert, vlan_tag,
+                             WQ_ENET_OFFLOAD_MODE_CSUM, eop, 1 /* SOP */, eop,
+                             loopback);
+       if (!eop)
+               err = enic_queue_wq_skb_cont(enic, wq, skb, len_left, loopback);
+
+       return err;
+}
+
 static inline void enic_queue_wq_skb(struct enic *enic,
        struct vnic_wq *wq, struct sk_buff *skb)
 {
@@ -688,6 +785,9 @@ static inline void enic_queue_wq_skb(struct enic *enic,
                err = enic_queue_wq_skb_tso(enic, wq, skb, mss,
                                            vlan_tag_insert, vlan_tag,
                                            loopback);
+       else if (skb->encapsulation)
+               err = enic_queue_wq_skb_encap(enic, wq, skb, vlan_tag_insert,
+                                             vlan_tag, loopback);
        else if (skb->ip_summed == CHECKSUM_PARTIAL)
                err = enic_queue_wq_skb_csum_l4(enic, wq, skb, vlan_tag_insert,
                                                vlan_tag, loopback);
@@ -2400,6 +2500,7 @@ static const struct net_device_ops enic_netdev_dynamic_ops = {
 #endif
        .ndo_udp_tunnel_add     = enic_udp_tunnel_add,
        .ndo_udp_tunnel_del     = enic_udp_tunnel_del,
+       .ndo_features_check     = enic_features_check,
 };
 
 static const struct net_device_ops enic_netdev_ops = {
@@ -2425,6 +2526,7 @@ static const struct net_device_ops enic_netdev_ops = {
 #endif
        .ndo_udp_tunnel_add     = enic_udp_tunnel_add,
        .ndo_udp_tunnel_del     = enic_udp_tunnel_del,
+       .ndo_features_check     = enic_features_check,
 };
 
 static void enic_dev_deinit(struct enic *enic)