diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic.h b/drivers/net/ethernet/qlogic/qlcnic/qlcnic.h index df9daa335292..5c48263207dd 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic.h +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic.h @@ -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); diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c index 1960609481ce..dfc25f7c2cf2 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c @@ -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), diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c index 54ebf300332a..7252af99ad01 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c @@ -13,16 +13,19 @@ #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); diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c index 4b92d9d90267..6655bf49c79b 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c @@ -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;