udpv6: Fix HW checksum support for outgoing UFO packets
authorSridhar Samudrala <sri@us.ibm.com>
Thu, 9 Jul 2009 08:09:54 +0000 (08:09 +0000)
committerDavid S. Miller <davem@davemloft.net>
Sun, 12 Jul 2009 21:29:24 +0000 (14:29 -0700)
- add HW checksum support for outgoing large UDP/IPv6 packets destined for
  a UFO enabled device.

Signed-off-by: Sridhar Samudrala <sri@us.ibm.com>
Acked-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/ipv6/udp.c

index 33b59bd..f31b1b9 100644 (file)
@@ -638,6 +638,47 @@ static void udp_v6_flush_pending_frames(struct sock *sk)
        }
 }
 
+/**
+ *     udp6_hwcsum_outgoing  -  handle outgoing HW checksumming
+ *     @sk:    socket we are sending on
+ *     @skb:   sk_buff containing the filled-in UDP header
+ *             (checksum field must be zeroed out)
+ */
+static void udp6_hwcsum_outgoing(struct sock *sk, struct sk_buff *skb,
+                                const struct in6_addr *saddr,
+                                const struct in6_addr *daddr, int len)
+{
+       unsigned int offset;
+       struct udphdr *uh = udp_hdr(skb);
+       __wsum csum = 0;
+
+       if (skb_queue_len(&sk->sk_write_queue) == 1) {
+               /* Only one fragment on the socket.  */
+               skb->csum_start = skb_transport_header(skb) - skb->head;
+               skb->csum_offset = offsetof(struct udphdr, check);
+               uh->check = ~csum_ipv6_magic(saddr, daddr, len, IPPROTO_UDP, 0);
+       } else {
+               /*
+                * HW-checksum won't work as there are two or more
+                * fragments on the socket so that all csums of sk_buffs
+                * should be together
+                */
+               offset = skb_transport_offset(skb);
+               skb->csum = skb_checksum(skb, offset, skb->len - offset, 0);
+
+               skb->ip_summed = CHECKSUM_NONE;
+
+               skb_queue_walk(&sk->sk_write_queue, skb) {
+                       csum = csum_add(csum, skb->csum);
+               }
+
+               uh->check = csum_ipv6_magic(saddr, daddr, len, IPPROTO_UDP,
+                                           csum);
+               if (uh->check == 0)
+                       uh->check = CSUM_MANGLED_0;
+       }
+}
+
 /*
  *     Sending
  */
@@ -668,7 +709,11 @@ static int udp_v6_push_pending_frames(struct sock *sk)
 
        if (is_udplite)
                csum = udplite_csum_outgoing(sk, skb);
-        else
+       else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */
+               udp6_hwcsum_outgoing(sk, skb, &fl->fl6_src, &fl->fl6_dst,
+                                    up->len);
+               goto send;
+       } else
                csum = udp_csum_outgoing(sk, skb);
 
        /* add protocol-dependent pseudo-header */
@@ -677,6 +722,7 @@ static int udp_v6_push_pending_frames(struct sock *sk)
        if (uh->check == 0)
                uh->check = CSUM_MANGLED_0;
 
+send:
        err = ip6_push_pending_frames(sk);
 out:
        up->len = 0;