Commit 738a8f4b authored by Ben Hutchings's avatar Ben Hutchings Committed by David S. Miller

sfc: Implement TSO for TCP/IPv6

Signed-off-by: default avatarBen Hutchings <bhutchings@solarflare.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 8880f4ec
...@@ -2208,6 +2208,8 @@ static int __devinit efx_pci_probe(struct pci_dev *pci_dev, ...@@ -2208,6 +2208,8 @@ static int __devinit efx_pci_probe(struct pci_dev *pci_dev,
net_dev->features |= (type->offload_features | NETIF_F_SG | net_dev->features |= (type->offload_features | NETIF_F_SG |
NETIF_F_HIGHDMA | NETIF_F_TSO | NETIF_F_HIGHDMA | NETIF_F_TSO |
NETIF_F_GRO); NETIF_F_GRO);
if (type->offload_features & NETIF_F_V6_CSUM)
net_dev->features |= NETIF_F_TSO6;
/* Mask for features that also apply to VLAN devices */ /* Mask for features that also apply to VLAN devices */
net_dev->vlan_features |= (NETIF_F_ALL_CSUM | NETIF_F_SG | net_dev->vlan_features |= (NETIF_F_ALL_CSUM | NETIF_F_SG |
NETIF_F_HIGHDMA | NETIF_F_TSO); NETIF_F_HIGHDMA | NETIF_F_TSO);
......
...@@ -471,6 +471,23 @@ static void efx_ethtool_get_stats(struct net_device *net_dev, ...@@ -471,6 +471,23 @@ static void efx_ethtool_get_stats(struct net_device *net_dev,
} }
} }
static int efx_ethtool_set_tso(struct net_device *net_dev, u32 enable)
{
struct efx_nic *efx __attribute__ ((unused)) = netdev_priv(net_dev);
unsigned long features;
features = NETIF_F_TSO;
if (efx->type->offload_features & NETIF_F_V6_CSUM)
features |= NETIF_F_TSO6;
if (enable)
net_dev->features |= features;
else
net_dev->features &= ~features;
return 0;
}
static int efx_ethtool_set_tx_csum(struct net_device *net_dev, u32 enable) static int efx_ethtool_set_tx_csum(struct net_device *net_dev, u32 enable)
{ {
struct efx_nic *efx = netdev_priv(net_dev); struct efx_nic *efx = netdev_priv(net_dev);
...@@ -834,7 +851,8 @@ const struct ethtool_ops efx_ethtool_ops = { ...@@ -834,7 +851,8 @@ const struct ethtool_ops efx_ethtool_ops = {
.get_sg = ethtool_op_get_sg, .get_sg = ethtool_op_get_sg,
.set_sg = ethtool_op_set_sg, .set_sg = ethtool_op_set_sg,
.get_tso = ethtool_op_get_tso, .get_tso = ethtool_op_get_tso,
.set_tso = ethtool_op_set_tso, /* Need to enable/disable TSO-IPv6 too */
.set_tso = efx_ethtool_set_tso,
.get_flags = ethtool_op_get_flags, .get_flags = ethtool_op_get_flags,
.set_flags = ethtool_op_set_flags, .set_flags = ethtool_op_set_flags,
.get_sset_count = efx_ethtool_get_sset_count, .get_sset_count = efx_ethtool_get_sset_count,
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
#include <linux/tcp.h> #include <linux/tcp.h>
#include <linux/ip.h> #include <linux/ip.h>
#include <linux/in.h> #include <linux/in.h>
#include <linux/ipv6.h>
#include <net/ipv6.h>
#include <linux/if_ether.h> #include <linux/if_ether.h>
#include <linux/highmem.h> #include <linux/highmem.h>
#include "net_driver.h" #include "net_driver.h"
...@@ -531,6 +533,7 @@ void efx_remove_tx_queue(struct efx_tx_queue *tx_queue) ...@@ -531,6 +533,7 @@ void efx_remove_tx_queue(struct efx_tx_queue *tx_queue)
#define ETH_HDR_LEN(skb) (skb_network_header(skb) - (skb)->data) #define ETH_HDR_LEN(skb) (skb_network_header(skb) - (skb)->data)
#define SKB_TCP_OFF(skb) PTR_DIFF(tcp_hdr(skb), (skb)->data) #define SKB_TCP_OFF(skb) PTR_DIFF(tcp_hdr(skb), (skb)->data)
#define SKB_IPV4_OFF(skb) PTR_DIFF(ip_hdr(skb), (skb)->data) #define SKB_IPV4_OFF(skb) PTR_DIFF(ip_hdr(skb), (skb)->data)
#define SKB_IPV6_OFF(skb) PTR_DIFF(ipv6_hdr(skb), (skb)->data)
/** /**
* struct tso_state - TSO state for an SKB * struct tso_state - TSO state for an SKB
...@@ -543,6 +546,7 @@ void efx_remove_tx_queue(struct efx_tx_queue *tx_queue) ...@@ -543,6 +546,7 @@ void efx_remove_tx_queue(struct efx_tx_queue *tx_queue)
* @unmap_len: Length of SKB fragment * @unmap_len: Length of SKB fragment
* @unmap_addr: DMA address of SKB fragment * @unmap_addr: DMA address of SKB fragment
* @unmap_single: DMA single vs page mapping flag * @unmap_single: DMA single vs page mapping flag
* @protocol: Network protocol (after any VLAN header)
* @header_len: Number of bytes of header * @header_len: Number of bytes of header
* @full_packet_size: Number of bytes to put in each outgoing segment * @full_packet_size: Number of bytes to put in each outgoing segment
* *
...@@ -563,6 +567,7 @@ struct tso_state { ...@@ -563,6 +567,7 @@ struct tso_state {
dma_addr_t unmap_addr; dma_addr_t unmap_addr;
bool unmap_single; bool unmap_single;
__be16 protocol;
unsigned header_len; unsigned header_len;
int full_packet_size; int full_packet_size;
}; };
...@@ -570,9 +575,9 @@ struct tso_state { ...@@ -570,9 +575,9 @@ struct tso_state {
/* /*
* Verify that our various assumptions about sk_buffs and the conditions * Verify that our various assumptions about sk_buffs and the conditions
* under which TSO will be attempted hold true. * under which TSO will be attempted hold true. Return the protocol number.
*/ */
static void efx_tso_check_safe(struct sk_buff *skb) static __be16 efx_tso_check_protocol(struct sk_buff *skb)
{ {
__be16 protocol = skb->protocol; __be16 protocol = skb->protocol;
...@@ -587,13 +592,22 @@ static void efx_tso_check_safe(struct sk_buff *skb) ...@@ -587,13 +592,22 @@ static void efx_tso_check_safe(struct sk_buff *skb)
if (protocol == htons(ETH_P_IP)) if (protocol == htons(ETH_P_IP))
skb_set_transport_header(skb, sizeof(*veh) + skb_set_transport_header(skb, sizeof(*veh) +
4 * ip_hdr(skb)->ihl); 4 * ip_hdr(skb)->ihl);
else if (protocol == htons(ETH_P_IPV6))
skb_set_transport_header(skb, sizeof(*veh) +
sizeof(struct ipv6hdr));
} }
EFX_BUG_ON_PARANOID(protocol != htons(ETH_P_IP)); if (protocol == htons(ETH_P_IP)) {
EFX_BUG_ON_PARANOID(ip_hdr(skb)->protocol != IPPROTO_TCP); EFX_BUG_ON_PARANOID(ip_hdr(skb)->protocol != IPPROTO_TCP);
} else {
EFX_BUG_ON_PARANOID(protocol != htons(ETH_P_IPV6));
EFX_BUG_ON_PARANOID(ipv6_hdr(skb)->nexthdr != NEXTHDR_TCP);
}
EFX_BUG_ON_PARANOID((PTR_DIFF(tcp_hdr(skb), skb->data) EFX_BUG_ON_PARANOID((PTR_DIFF(tcp_hdr(skb), skb->data)
+ (tcp_hdr(skb)->doff << 2u)) > + (tcp_hdr(skb)->doff << 2u)) >
skb_headlen(skb)); skb_headlen(skb));
return protocol;
} }
...@@ -836,7 +850,10 @@ static void tso_start(struct tso_state *st, const struct sk_buff *skb) ...@@ -836,7 +850,10 @@ static void tso_start(struct tso_state *st, const struct sk_buff *skb)
+ PTR_DIFF(tcp_hdr(skb), skb->data)); + PTR_DIFF(tcp_hdr(skb), skb->data));
st->full_packet_size = st->header_len + skb_shinfo(skb)->gso_size; st->full_packet_size = st->header_len + skb_shinfo(skb)->gso_size;
st->ipv4_id = ntohs(ip_hdr(skb)->id); if (st->protocol == htons(ETH_P_IP))
st->ipv4_id = ntohs(ip_hdr(skb)->id);
else
st->ipv4_id = 0;
st->seqnum = ntohl(tcp_hdr(skb)->seq); st->seqnum = ntohl(tcp_hdr(skb)->seq);
EFX_BUG_ON_PARANOID(tcp_hdr(skb)->urg); EFX_BUG_ON_PARANOID(tcp_hdr(skb)->urg);
...@@ -951,7 +968,6 @@ static int tso_start_new_packet(struct efx_tx_queue *tx_queue, ...@@ -951,7 +968,6 @@ static int tso_start_new_packet(struct efx_tx_queue *tx_queue,
struct tso_state *st) struct tso_state *st)
{ {
struct efx_tso_header *tsoh; struct efx_tso_header *tsoh;
struct iphdr *tsoh_iph;
struct tcphdr *tsoh_th; struct tcphdr *tsoh_th;
unsigned ip_length; unsigned ip_length;
u8 *header; u8 *header;
...@@ -975,7 +991,6 @@ static int tso_start_new_packet(struct efx_tx_queue *tx_queue, ...@@ -975,7 +991,6 @@ static int tso_start_new_packet(struct efx_tx_queue *tx_queue,
header = TSOH_BUFFER(tsoh); header = TSOH_BUFFER(tsoh);
tsoh_th = (struct tcphdr *)(header + SKB_TCP_OFF(skb)); tsoh_th = (struct tcphdr *)(header + SKB_TCP_OFF(skb));
tsoh_iph = (struct iphdr *)(header + SKB_IPV4_OFF(skb));
/* Copy and update the headers. */ /* Copy and update the headers. */
memcpy(header, skb->data, st->header_len); memcpy(header, skb->data, st->header_len);
...@@ -993,11 +1008,22 @@ static int tso_start_new_packet(struct efx_tx_queue *tx_queue, ...@@ -993,11 +1008,22 @@ static int tso_start_new_packet(struct efx_tx_queue *tx_queue,
tsoh_th->fin = tcp_hdr(skb)->fin; tsoh_th->fin = tcp_hdr(skb)->fin;
tsoh_th->psh = tcp_hdr(skb)->psh; tsoh_th->psh = tcp_hdr(skb)->psh;
} }
tsoh_iph->tot_len = htons(ip_length);
/* Linux leaves suitable gaps in the IP ID space for us to fill. */ if (st->protocol == htons(ETH_P_IP)) {
tsoh_iph->id = htons(st->ipv4_id); struct iphdr *tsoh_iph =
st->ipv4_id++; (struct iphdr *)(header + SKB_IPV4_OFF(skb));
tsoh_iph->tot_len = htons(ip_length);
/* Linux leaves suitable gaps in the IP ID space for us to fill. */
tsoh_iph->id = htons(st->ipv4_id);
st->ipv4_id++;
} else {
struct ipv6hdr *tsoh_iph =
(struct ipv6hdr *)(header + SKB_IPV6_OFF(skb));
tsoh_iph->payload_len = htons(ip_length - sizeof(*tsoh_iph));
}
st->packet_space = skb_shinfo(skb)->gso_size; st->packet_space = skb_shinfo(skb)->gso_size;
++tx_queue->tso_packets; ++tx_queue->tso_packets;
...@@ -1027,8 +1053,8 @@ static int efx_enqueue_skb_tso(struct efx_tx_queue *tx_queue, ...@@ -1027,8 +1053,8 @@ static int efx_enqueue_skb_tso(struct efx_tx_queue *tx_queue,
int frag_i, rc, rc2 = NETDEV_TX_OK; int frag_i, rc, rc2 = NETDEV_TX_OK;
struct tso_state state; struct tso_state state;
/* Verify TSO is safe - these checks should never fail. */ /* Find the packet protocol and sanity-check it */
efx_tso_check_safe(skb); state.protocol = efx_tso_check_protocol(skb);
EFX_BUG_ON_PARANOID(tx_queue->write_count != tx_queue->insert_count); EFX_BUG_ON_PARANOID(tx_queue->write_count != tx_queue->insert_count);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment