Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 381709de authored by Shahed Shaikh's avatar Shahed Shaikh Committed by David S. Miller
Browse files

qlcnic: Add VXLAN Tx offload support



This patch adds LSO, LSO6 and Tx checksum offload support for VXLAN
encapsulated packets on 83xx/84xx series adapters.

Signed-off-by: default avatarShahed Shaikh <shahed.shaikh@qlogic.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent c65d7533
Loading
Loading
Loading
Loading
+26 −4
Original line number Diff line number Diff line
@@ -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);
+4 −0
Original line number Diff line number Diff line
@@ -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),
+141 −25
Original line number Diff line number Diff line
@@ -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 @@ static int qlcnic_tx_pkt(struct qlcnic_adapter *adapter,
		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 @@ static int qlcnic_tx_pkt(struct qlcnic_adapter *adapter,
			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)))
	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);
+10 −0
Original line number Diff line number Diff line
@@ -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;