diff options
Diffstat (limited to 'drivers/net/usb/r8152.c')
-rw-r--r-- | drivers/net/usb/r8152.c | 106 |
1 files changed, 103 insertions, 3 deletions
diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c index b23b2ae0bddc..c7ef30dee1b9 100644 --- a/drivers/net/usb/r8152.c +++ b/drivers/net/usb/r8152.c @@ -21,6 +21,7 @@ #include <linux/list.h> #include <linux/ip.h> #include <linux/ipv6.h> +#include <net/ip6_checksum.h> /* Version Information */ #define DRIVER_VERSION "v1.06.0 (2014/03/03)" @@ -471,6 +472,7 @@ struct rx_desc { __le32 opts2; #define RD_UDP_CS (1 << 23) #define RD_TCP_CS (1 << 22) +#define RD_IPV6_CS (1 << 20) #define RD_IPV4_CS (1 << 19) __le32 opts3; @@ -488,7 +490,9 @@ struct tx_desc { #define TX_FS (1 << 31) /* First segment of a packet */ #define TX_LS (1 << 30) /* Final segment of a packet */ #define GTSENDV4 (1 << 28) +#define GTSENDV6 (1 << 27) #define GTTCPHO_SHIFT 18 +#define GTTCPHO_MAX 0x7fU #define TX_LEN_MAX 0x3ffffU __le32 opts2; @@ -499,6 +503,7 @@ struct tx_desc { #define MSS_SHIFT 17 #define MSS_MAX 0x7ffU #define TCPHO_SHIFT 17 +#define TCPHO_MAX 0x7ffU }; struct r8152; @@ -1318,6 +1323,69 @@ static inline __be16 get_protocol(struct sk_buff *skb) return protocol; } +/* + * r8152_csum_workaround() + * The hw limites the value the transport offset. When the offset is out of the + * range, calculate the checksum by sw. + */ +static void r8152_csum_workaround(struct r8152 *tp, struct sk_buff *skb, + struct sk_buff_head *list) +{ + if (skb_shinfo(skb)->gso_size) { + netdev_features_t features = tp->netdev->features; + struct sk_buff_head seg_list; + struct sk_buff *segs, *nskb; + + features &= ~(NETIF_F_IP_CSUM | NETIF_F_SG | NETIF_F_TSO); + segs = skb_gso_segment(skb, features); + if (IS_ERR(segs) || !segs) + goto drop; + + __skb_queue_head_init(&seg_list); + + do { + nskb = segs; + segs = segs->next; + nskb->next = NULL; + __skb_queue_tail(&seg_list, nskb); + } while (segs); + + skb_queue_splice(&seg_list, list); + dev_kfree_skb(skb); + } else if (skb->ip_summed == CHECKSUM_PARTIAL) { + if (skb_checksum_help(skb) < 0) + goto drop; + + __skb_queue_head(list, skb); + } else { + struct net_device_stats *stats; + +drop: + stats = &tp->netdev->stats; + stats->tx_dropped++; + dev_kfree_skb(skb); + } +} + +/* + * msdn_giant_send_check() + * According to the document of microsoft, the TCP Pseudo Header excludes the + * packet length for IPv6 TCP large packets. + */ +static int msdn_giant_send_check(struct sk_buff *skb) +{ + const struct ipv6hdr *ipv6h; + struct tcphdr *th; + + ipv6h = ipv6_hdr(skb); + th = tcp_hdr(skb); + + th->check = 0; + th->check = ~tcp_v6_check(0, &ipv6h->saddr, &ipv6h->daddr, 0); + + return 0; +} + static int r8152_tx_csum(struct r8152 *tp, struct tx_desc *desc, struct sk_buff *skb, u32 len, u32 transport_offset) { @@ -1330,11 +1398,24 @@ static int r8152_tx_csum(struct r8152 *tp, struct tx_desc *desc, opts1 = len | TX_FS | TX_LS; if (mss) { + if (transport_offset > GTTCPHO_MAX) { + netif_warn(tp, tx_err, tp->netdev, + "Invalid transport offset 0x%x for TSO\n", + transport_offset); + ret = TX_CSUM_TSO; + goto unavailable; + } + switch (get_protocol(skb)) { case htons(ETH_P_IP): opts1 |= GTSENDV4; break; + case htons(ETH_P_IPV6): + opts1 |= GTSENDV6; + msdn_giant_send_check(skb); + break; + default: WARN_ON_ONCE(1); break; @@ -1345,6 +1426,14 @@ static int r8152_tx_csum(struct r8152 *tp, struct tx_desc *desc, } else if (skb->ip_summed == CHECKSUM_PARTIAL) { u8 ip_protocol; + if (transport_offset > TCPHO_MAX) { + netif_warn(tp, tx_err, tp->netdev, + "Invalid transport offset 0x%x\n", + transport_offset); + ret = TX_CSUM_NONE; + goto unavailable; + } + switch (get_protocol(skb)) { case htons(ETH_P_IP): opts2 |= IPV4_CS; @@ -1374,6 +1463,7 @@ static int r8152_tx_csum(struct r8152 *tp, struct tx_desc *desc, desc->opts2 = cpu_to_le32(opts2); desc->opts1 = cpu_to_le32(opts1); +unavailable: return ret; } @@ -1414,7 +1504,10 @@ static int r8152_tx_agg_fill(struct r8152 *tp, struct tx_agg *agg) offset = (u32)skb_transport_offset(skb); - r8152_tx_csum(tp, tx_desc, skb, skb->len, offset); + if (r8152_tx_csum(tp, tx_desc, skb, skb->len, offset)) { + r8152_csum_workaround(tp, skb, &skb_head); + continue; + } tx_data += sizeof(*tx_desc); @@ -1487,6 +1580,11 @@ static u8 r8152_rx_csum(struct r8152 *tp, struct rx_desc *rx_desc) checksum = CHECKSUM_NONE; else checksum = CHECKSUM_UNNECESSARY; + } else if (RD_IPV6_CS) { + if ((opts2 & RD_UDP_CS) && !(opts3 & UDPF)) + checksum = CHECKSUM_UNNECESSARY; + else if ((opts2 & RD_TCP_CS) && !(opts3 & TCPF)) + checksum = CHECKSUM_UNNECESSARY; } return_result: @@ -3195,9 +3293,11 @@ static int rtl8152_probe(struct usb_interface *intf, netdev->watchdog_timeo = RTL8152_TX_TIMEOUT; netdev->features |= NETIF_F_RXCSUM | NETIF_F_IP_CSUM | NETIF_F_SG | - NETIF_F_TSO | NETIF_F_FRAGLIST; + NETIF_F_TSO | NETIF_F_FRAGLIST | NETIF_F_IPV6_CSUM | + NETIF_F_TSO6; netdev->hw_features = NETIF_F_RXCSUM | NETIF_F_IP_CSUM | NETIF_F_SG | - NETIF_F_TSO | NETIF_F_FRAGLIST; + NETIF_F_TSO | NETIF_F_FRAGLIST | + NETIF_F_IPV6_CSUM | NETIF_F_TSO6; SET_ETHTOOL_OPS(netdev, &ops); netif_set_gso_max_size(netdev, RTL_LIMITED_TSO_SIZE); |