qlcnic: Add VXLAN Tx offload support
authorShahed Shaikh <shahed.shaikh@qlogic.com>
Fri, 21 Mar 2014 08:41:16 +0000 (04:41 -0400)
committerDavid S. Miller <davem@davemloft.net>
Mon, 24 Mar 2014 04:44:23 +0000 (00:44 -0400)
This patch adds LSO, LSO6 and Tx checksum offload support for VXLAN
encapsulated packets on 83xx/84xx series adapters.

Signed-off-by: Shahed Shaikh <shahed.shaikh@qlogic.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/qlogic/qlcnic/qlcnic.h
drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c
drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c
drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c

index df9daa33529286d673e6434075a116328dd27006..5c48263207dd8196c6052dd026af576db521f6f2 100644 (file)
@@ -169,11 +169,20 @@ struct cmd_desc_type0 {
 
        __le64 addr_buffer2;
 
-       __le16 reference_handle;
+       __le16 encap_descr;     /* 15:10 offset of outer L3 header,
+                                * 9:6 number of 32bit words in outer L3 header,
+                                * 5 offload outer L4 checksum,
+                                * 4 offload outer L3 checksum,
+                                * 3 Inner L4 type, TCP=0, UDP=1,
+                                * 2 Inner L3 type, IPv4=0, IPv6=1,
+                                * 1 Outer L3 type,IPv4=0, IPv6=1,
+                                * 0 type of encapsulation, GRE=0, VXLAN=1
+                                */
        __le16 mss;
        u8 port_ctxid;          /* 7:4 ctxid 3:0 port */
-       u8 total_hdr_length;    /* LSO only : MAC+IP+TCP Hdr size */
-       __le16 conn_id;         /* IPSec offoad only */
+       u8 hdr_length;          /* LSO only : MAC+IP+TCP Hdr size */
+       u8 outer_hdr_length;    /* Encapsulation only */
+       u8 rsvd1;
 
        __le64 addr_buffer3;
        __le64 addr_buffer1;
@@ -183,7 +192,9 @@ struct cmd_desc_type0 {
        __le64 addr_buffer4;
 
        u8 eth_addr[ETH_ALEN];
-       __le16 vlan_TCI;
+       __le16 vlan_TCI;        /* In case of  encapsulation,
+                                * this is for outer VLAN
+                                */
 
 } __attribute__ ((aligned(64)));
 
@@ -538,6 +549,8 @@ struct qlcnic_adapter_stats {
        u64  txbytes;
        u64  lrobytes;
        u64  lso_frames;
+       u64  encap_lso_frames;
+       u64  encap_tx_csummed;
        u64  xmit_on;
        u64  xmit_off;
        u64  skb_alloc_failure;
@@ -899,6 +912,9 @@ struct qlcnic_mac_vlan_list {
 #define QLCNIC_FW_CAPABILITY_2_BEACON          BIT_7
 #define QLCNIC_FW_CAPABILITY_2_PER_PORT_ESWITCH_CFG    BIT_9
 
+#define QLCNIC_83XX_FW_CAPAB_ENCAP_TX_OFFLOAD  BIT_1
+#define QLCNIC_83XX_FW_CAPAB_ENCAP_CKO_OFFLOAD BIT_4
+
 /* module types */
 #define LINKEVENT_MODULE_NOT_PRESENT                   1
 #define LINKEVENT_MODULE_OPTICAL_UNKNOWN               2
@@ -1806,6 +1822,12 @@ struct qlcnic_hardware_ops {
 
 extern struct qlcnic_nic_template qlcnic_vf_ops;
 
+static inline bool qlcnic_encap_tx_offload(struct qlcnic_adapter *adapter)
+{
+       return adapter->ahw->extra_capability[0] &
+              QLCNIC_83XX_FW_CAPAB_ENCAP_TX_OFFLOAD;
+}
+
 static inline int qlcnic_start_firmware(struct qlcnic_adapter *adapter)
 {
        return adapter->nic_ops->start_firmware(adapter);
index 1960609481cec5d470c91e2cb728e1aaf643d315..dfc25f7c2cf223254e12a5adf06aa161960f016e 100644 (file)
@@ -47,6 +47,10 @@ static const struct qlcnic_stats qlcnic_gstrings_stats[] = {
        {"lro_pkts", QLC_SIZEOF(stats.lro_pkts), QLC_OFF(stats.lro_pkts)},
        {"lrobytes", QLC_SIZEOF(stats.lrobytes), QLC_OFF(stats.lrobytes)},
        {"lso_frames", QLC_SIZEOF(stats.lso_frames), QLC_OFF(stats.lso_frames)},
+       {"encap_lso_frames", QLC_SIZEOF(stats.encap_lso_frames),
+        QLC_OFF(stats.encap_lso_frames)},
+       {"encap_tx_csummed", QLC_SIZEOF(stats.encap_tx_csummed),
+        QLC_OFF(stats.encap_tx_csummed)},
        {"skb_alloc_failure", QLC_SIZEOF(stats.skb_alloc_failure),
         QLC_OFF(stats.skb_alloc_failure)},
        {"mac_filter_limit_overrun", QLC_SIZEOF(stats.mac_filter_limit_overrun),
index 54ebf300332a353246066e5cf62f1d097355e1e5..7252af99ad017fef27d31182aa50dc28c2d1f081 100644 (file)
 
 #include "qlcnic.h"
 
-#define TX_ETHER_PKT   0x01
-#define TX_TCP_PKT     0x02
-#define TX_UDP_PKT     0x03
-#define TX_IP_PKT      0x04
-#define TX_TCP_LSO     0x05
-#define TX_TCP_LSO6    0x06
-#define TX_TCPV6_PKT   0x0b
-#define TX_UDPV6_PKT   0x0c
-#define FLAGS_VLAN_TAGGED      0x10
-#define FLAGS_VLAN_OOB         0x40
+#define QLCNIC_TX_ETHER_PKT            0x01
+#define QLCNIC_TX_TCP_PKT              0x02
+#define QLCNIC_TX_UDP_PKT              0x03
+#define QLCNIC_TX_IP_PKT               0x04
+#define QLCNIC_TX_TCP_LSO              0x05
+#define QLCNIC_TX_TCP_LSO6             0x06
+#define QLCNIC_TX_ENCAP_PKT            0x07
+#define QLCNIC_TX_ENCAP_LSO            0x08
+#define QLCNIC_TX_TCPV6_PKT            0x0b
+#define QLCNIC_TX_UDPV6_PKT            0x0c
+
+#define QLCNIC_FLAGS_VLAN_TAGGED       0x10
+#define QLCNIC_FLAGS_VLAN_OOB          0x40
 
 #define qlcnic_set_tx_vlan_tci(cmd_desc, v)    \
        (cmd_desc)->vlan_TCI = cpu_to_le16(v);
@@ -364,6 +367,101 @@ static void qlcnic_send_filter(struct qlcnic_adapter *adapter,
        spin_unlock(&adapter->mac_learn_lock);
 }
 
+#define QLCNIC_ENCAP_VXLAN_PKT         BIT_0
+#define QLCNIC_ENCAP_OUTER_L3_IP6      BIT_1
+#define QLCNIC_ENCAP_INNER_L3_IP6      BIT_2
+#define QLCNIC_ENCAP_INNER_L4_UDP      BIT_3
+#define QLCNIC_ENCAP_DO_L3_CSUM                BIT_4
+#define QLCNIC_ENCAP_DO_L4_CSUM                BIT_5
+
+static int qlcnic_tx_encap_pkt(struct qlcnic_adapter *adapter,
+                              struct cmd_desc_type0 *first_desc,
+                              struct sk_buff *skb,
+                              struct qlcnic_host_tx_ring *tx_ring)
+{
+       u8 opcode = 0, inner_hdr_len = 0, outer_hdr_len = 0, total_hdr_len = 0;
+       int copied, copy_len, descr_size;
+       u32 producer = tx_ring->producer;
+       struct cmd_desc_type0 *hwdesc;
+       u16 flags = 0, encap_descr = 0;
+
+       opcode = QLCNIC_TX_ETHER_PKT;
+       encap_descr = QLCNIC_ENCAP_VXLAN_PKT;
+
+       if (skb_is_gso(skb)) {
+               inner_hdr_len = skb_inner_transport_header(skb) +
+                               inner_tcp_hdrlen(skb) -
+                               skb_inner_mac_header(skb);
+
+               /* VXLAN header size = 8 */
+               outer_hdr_len = skb_transport_offset(skb) + 8 +
+                               sizeof(struct udphdr);
+               first_desc->outer_hdr_length = outer_hdr_len;
+               total_hdr_len = inner_hdr_len + outer_hdr_len;
+               encap_descr |= QLCNIC_ENCAP_DO_L3_CSUM |
+                              QLCNIC_ENCAP_DO_L4_CSUM;
+               first_desc->mss = cpu_to_le16(skb_shinfo(skb)->gso_size);
+               first_desc->hdr_length = inner_hdr_len;
+
+               /* Copy inner and outer headers in Tx descriptor(s)
+                * If total_hdr_len > cmd_desc_type0, use multiple
+                * descriptors
+                */
+               copied = 0;
+               descr_size = (int)sizeof(struct cmd_desc_type0);
+               while (copied < total_hdr_len) {
+                       copy_len = min(descr_size, (total_hdr_len - copied));
+                       hwdesc = &tx_ring->desc_head[producer];
+                       tx_ring->cmd_buf_arr[producer].skb = NULL;
+                       skb_copy_from_linear_data_offset(skb, copied,
+                                                        (char *)hwdesc,
+                                                        copy_len);
+                       copied += copy_len;
+                       producer = get_next_index(producer, tx_ring->num_desc);
+               }
+
+               tx_ring->producer = producer;
+
+               /* Make sure updated tx_ring->producer is visible
+                * for qlcnic_tx_avail()
+                */
+               smp_mb();
+               adapter->stats.encap_lso_frames++;
+
+               opcode = QLCNIC_TX_ENCAP_LSO;
+       } else if (skb->ip_summed == CHECKSUM_PARTIAL) {
+               if (inner_ip_hdr(skb)->version == 6) {
+                       if (inner_ipv6_hdr(skb)->nexthdr == IPPROTO_UDP)
+                               encap_descr |= QLCNIC_ENCAP_INNER_L4_UDP;
+               } else {
+                       if (inner_ip_hdr(skb)->protocol == IPPROTO_UDP)
+                               encap_descr |= QLCNIC_ENCAP_INNER_L4_UDP;
+               }
+
+               adapter->stats.encap_tx_csummed++;
+               opcode = QLCNIC_TX_ENCAP_PKT;
+       }
+
+       /* Prepare first 16 bits of byte offset 16 of Tx descriptor */
+       if (ip_hdr(skb)->version == 6)
+               encap_descr |= QLCNIC_ENCAP_OUTER_L3_IP6;
+
+       /* outer IP header's size in 32bit words size*/
+       encap_descr |= (skb_network_header_len(skb) >> 2) << 6;
+
+       /* outer IP header offset */
+       encap_descr |= skb_network_offset(skb) << 10;
+       first_desc->encap_descr = cpu_to_le16(encap_descr);
+
+       first_desc->tcp_hdr_offset = skb_inner_transport_header(skb) -
+                                    skb->data;
+       first_desc->ip_hdr_offset = skb_inner_network_offset(skb);
+
+       qlcnic_set_tx_flags_opcode(first_desc, flags, opcode);
+
+       return 0;
+}
+
 static int qlcnic_tx_pkt(struct qlcnic_adapter *adapter,
                         struct cmd_desc_type0 *first_desc, struct sk_buff *skb,
                         struct qlcnic_host_tx_ring *tx_ring)
@@ -378,11 +476,11 @@ static int qlcnic_tx_pkt(struct qlcnic_adapter *adapter,
 
        if (protocol == ETH_P_8021Q) {
                vh = (struct vlan_ethhdr *)skb->data;
-               flags = FLAGS_VLAN_TAGGED;
+               flags = QLCNIC_FLAGS_VLAN_TAGGED;
                vlan_tci = ntohs(vh->h_vlan_TCI);
                protocol = ntohs(vh->h_vlan_encapsulated_proto);
        } else if (vlan_tx_tag_present(skb)) {
-               flags = FLAGS_VLAN_OOB;
+               flags = QLCNIC_FLAGS_VLAN_OOB;
                vlan_tci = vlan_tx_tag_get(skb);
        }
        if (unlikely(adapter->tx_pvid)) {
@@ -391,7 +489,7 @@ static int qlcnic_tx_pkt(struct qlcnic_adapter *adapter,
                if (vlan_tci && (adapter->flags & QLCNIC_TAGGING_ENABLED))
                        goto set_flags;
 
-               flags = FLAGS_VLAN_OOB;
+               flags = QLCNIC_FLAGS_VLAN_OOB;
                vlan_tci = adapter->tx_pvid;
        }
 set_flags:
@@ -402,25 +500,26 @@ set_flags:
                flags |= BIT_0;
                memcpy(&first_desc->eth_addr, skb->data, ETH_ALEN);
        }
-       opcode = TX_ETHER_PKT;
+       opcode = QLCNIC_TX_ETHER_PKT;
        if (skb_is_gso(skb)) {
                hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb);
                first_desc->mss = cpu_to_le16(skb_shinfo(skb)->gso_size);
-               first_desc->total_hdr_length = hdr_len;
-               opcode = (protocol == ETH_P_IPV6) ? TX_TCP_LSO6 : TX_TCP_LSO;
+               first_desc->hdr_length = hdr_len;
+               opcode = (protocol == ETH_P_IPV6) ? QLCNIC_TX_TCP_LSO6 :
+                                                   QLCNIC_TX_TCP_LSO;
 
                /* For LSO, we need to copy the MAC/IP/TCP headers into
                * the descriptor ring */
                copied = 0;
                offset = 2;
 
-               if (flags & FLAGS_VLAN_OOB) {
-                       first_desc->total_hdr_length += VLAN_HLEN;
+               if (flags & QLCNIC_FLAGS_VLAN_OOB) {
+                       first_desc->hdr_length += VLAN_HLEN;
                        first_desc->tcp_hdr_offset = VLAN_HLEN;
                        first_desc->ip_hdr_offset = VLAN_HLEN;
 
                        /* Only in case of TSO on vlan device */
-                       flags |= FLAGS_VLAN_TAGGED;
+                       flags |= QLCNIC_FLAGS_VLAN_TAGGED;
 
                        /* Create a TSO vlan header template for firmware */
                        hwdesc = &tx_ring->desc_head[producer];
@@ -464,16 +563,16 @@ set_flags:
                        l4proto = ip_hdr(skb)->protocol;
 
                        if (l4proto == IPPROTO_TCP)
-                               opcode = TX_TCP_PKT;
+                               opcode = QLCNIC_TX_TCP_PKT;
                        else if (l4proto == IPPROTO_UDP)
-                               opcode = TX_UDP_PKT;
+                               opcode = QLCNIC_TX_UDP_PKT;
                } else if (protocol == ETH_P_IPV6) {
                        l4proto = ipv6_hdr(skb)->nexthdr;
 
                        if (l4proto == IPPROTO_TCP)
-                               opcode = TX_TCPV6_PKT;
+                               opcode = QLCNIC_TX_TCPV6_PKT;
                        else if (l4proto == IPPROTO_UDP)
-                               opcode = TX_UDPV6_PKT;
+                               opcode = QLCNIC_TX_UDPV6_PKT;
                }
        }
        first_desc->tcp_hdr_offset += skb_transport_offset(skb);
@@ -563,6 +662,8 @@ netdev_tx_t qlcnic_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
        struct ethhdr *phdr;
        int i, k, frag_count, delta = 0;
        u32 producer, num_txd;
+       u16 protocol;
+       bool l4_is_udp = false;
 
        if (!test_bit(__QLCNIC_DEV_UP, &adapter->state)) {
                netif_tx_stop_all_queues(netdev);
@@ -653,8 +754,23 @@ netdev_tx_t qlcnic_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
        tx_ring->producer = get_next_index(producer, num_txd);
        smp_mb();
 
-       if (unlikely(qlcnic_tx_pkt(adapter, first_desc, skb, tx_ring)))
-               goto unwind_buff;
+       protocol = ntohs(skb->protocol);
+       if (protocol == ETH_P_IP)
+               l4_is_udp = ip_hdr(skb)->protocol == IPPROTO_UDP;
+       else if (protocol == ETH_P_IPV6)
+               l4_is_udp = ipv6_hdr(skb)->nexthdr == IPPROTO_UDP;
+
+       /* Check if it is a VXLAN packet */
+       if (!skb->encapsulation || !l4_is_udp ||
+           !qlcnic_encap_tx_offload(adapter)) {
+               if (unlikely(qlcnic_tx_pkt(adapter, first_desc, skb,
+                                          tx_ring)))
+                       goto unwind_buff;
+       } else {
+               if (unlikely(qlcnic_tx_encap_pkt(adapter, first_desc,
+                                                skb, tx_ring)))
+                       goto unwind_buff;
+       }
 
        if (adapter->drv_mac_learn)
                qlcnic_send_filter(adapter, first_desc, skb);
index 4b92d9d90267ea4cac2f3c9eefa60316ad81f082..6655bf49c79b729a824294766c5f1dc9208e4c00 100644 (file)
@@ -2205,6 +2205,16 @@ qlcnic_setup_netdev(struct qlcnic_adapter *adapter, struct net_device *netdev,
        if (adapter->ahw->capabilities & QLCNIC_FW_CAPABILITY_HW_LRO)
                netdev->features |= NETIF_F_LRO;
 
+       if (qlcnic_encap_tx_offload(adapter)) {
+               netdev->features |= NETIF_F_GSO_UDP_TUNNEL;
+
+               /* encapsulation Tx offload supported by Adapter */
+               netdev->hw_enc_features = NETIF_F_IP_CSUM        |
+                                         NETIF_F_GSO_UDP_TUNNEL |
+                                         NETIF_F_TSO            |
+                                         NETIF_F_TSO6;
+       }
+
        netdev->hw_features = netdev->features;
        netdev->priv_flags |= IFF_UNICAST_FLT;
        netdev->irq = adapter->msix_entries[0].vector;