Skip to content

Commit

Permalink
qlcnic: Add VXLAN Tx offload support
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Shahed Shaikh authored and David S. Miller committed Mar 24, 2014
1 parent c65d753 commit 381709d
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 29 deletions.
30 changes: 26 additions & 4 deletions drivers/net/ethernet/qlogic/qlcnic/qlcnic.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)));

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions drivers/net/ethernet/qlogic/qlcnic/qlcnic_ethtool.c
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
166 changes: 141 additions & 25 deletions drivers/net/ethernet/qlogic/qlcnic/qlcnic_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand All @@ -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)) {
Expand All @@ -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:
Expand All @@ -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];
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 381709d

Please sign in to comment.