diff mbox series

[net-next,07/10] net: qualcomm: rmnet: Add support for RX checksum offload

Message ID 1514341685-11262-8-git-send-email-subashab@codeaurora.org
State Changes Requested, archived
Delegated to: David Miller
Headers show
Series net: qualcomm: rmnet: Enable csum offloads | expand

Commit Message

Subash Abhinov Kasiviswanathan Dec. 27, 2017, 2:28 a.m. UTC
When using the MAPv4 packet format, receive checksum offload can be
enabled in hardware. The checksum computation over pseudo header is
not offloaded but the rest of the checksum computation over
the payload is offloaded. This applies only for TCP / UDP packets
which are not fragmented.

rmnet validates the TCP/UDP checksum for the packet using the checksum
from the checksum trailer added to the packet by hardware. The
validation performed is as following -

1. Perform 1's complement over the checksum value from the trailer
2. Compute 1's complement checksum over IPv4 / IPv6 header and
   subtracts it from the value from step 1
3. Computes 1's complement checksum over IPv4 / IPv6 pseudo header and
   adds it to the value from step 2
4. Subtracts the checksum value from the TCP / UDP header from the
   value from step 3.
5. Compares the value from step 4 to the checksum value from the
   TCP / UDP header.
6. If the comparison in step 5 succeeds, CHECKSUM_UNNECESSARY is set
   and the packet is passed on to network stack. If there is a
   failure, then the packet is passed on as such without modifying
   the ip_summed field.

The checksum field is also checked for UDP checksum 0 as per RFC 768
and for unexpected TCP checksum of 0.

If checksum offload is disabled when using MAPv4 packet format in
receive path, the packet is queued as is to network stack without
the validations above.

Signed-off-by: Subash Abhinov Kasiviswanathan <subashab@codeaurora.org>
---
 .../net/ethernet/qualcomm/rmnet/rmnet_handlers.c   |  15 +-
 drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h    |   4 +-
 .../net/ethernet/qualcomm/rmnet/rmnet_map_data.c   | 177 ++++++++++++++++++++-
 drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c    |   2 +
 4 files changed, 192 insertions(+), 6 deletions(-)

Comments

kernel test robot Dec. 27, 2017, 10:02 p.m. UTC | #1
Hi Subash,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Subash-Abhinov-Kasiviswanathan/net-qualcomm-rmnet-Enable-csum-offloads/20171228-041216
reproduce:
        # apt-get install sparse
        make ARCH=x86_64 allmodconfig
        make C=1 CF=-D__CHECK_ENDIAN__


sparse warnings: (new ones prefixed by >>)


vim +30 drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c

    26	
    27	static u16 *rmnet_map_get_csum_field(unsigned char protocol,
    28					     const void *txporthdr)
    29	{
  > 30		u16 *check = 0;
    31	
    32		switch (protocol) {
    33		case IPPROTO_TCP:
  > 34			check = &(((struct tcphdr *)txporthdr)->check);
    35			break;
    36	
    37		case IPPROTO_UDP:
  > 38			check = &(((struct udphdr *)txporthdr)->check);
    39			break;
    40	
    41		default:
    42			check = 0;
    43			break;
    44		}
    45	
    46		return check;
    47	}
    48	
    49	static int
    50	rmnet_map_ipv4_dl_csum_trailer(struct sk_buff *skb,
    51				       struct rmnet_map_dl_csum_trailer *csum_trailer)
    52	{
    53		u16 ip_pseudo_payload_csum, pseudo_csum, ip_hdr_csum, *csum_field;
    54		u16 csum_value, ip_payload_csum, csum_value_final;
    55		struct iphdr *ip4h;
    56		void *txporthdr;
    57	
    58		ip4h = (struct iphdr *)(skb->data);
    59		if ((ntohs(ip4h->frag_off) & IP_MF) ||
    60		    ((ntohs(ip4h->frag_off) & IP_OFFSET) > 0))
    61			return -EOPNOTSUPP;
    62	
    63		txporthdr = skb->data + ip4h->ihl * 4;
    64	
    65		csum_field = rmnet_map_get_csum_field(ip4h->protocol, txporthdr);
    66	
    67		if (!csum_field)
    68			return -EPROTONOSUPPORT;
    69	
    70		/* RFC 768 - Skip IPv4 UDP packets where sender checksum field is 0 */
    71		if (*csum_field == 0 && ip4h->protocol == IPPROTO_UDP)
    72			return 0;
    73	
  > 74		csum_value = ~ntohs(csum_trailer->csum_value);
  > 75		ip_hdr_csum = ~ip_fast_csum(ip4h, (int)ip4h->ihl);
  > 76		ip_payload_csum = csum16_sub(csum_value, ip_hdr_csum);
    77	
  > 78		pseudo_csum = ~ntohs(csum_tcpudp_magic(ip4h->saddr, ip4h->daddr,
    79				     (u16)(ntohs(ip4h->tot_len) - ip4h->ihl * 4),
    80				     (u16)ip4h->protocol, 0));
  > 81		ip_pseudo_payload_csum = csum16_add(ip_payload_csum, pseudo_csum);
    82	
  > 83		csum_value_final = ~csum16_sub(ip_pseudo_payload_csum,
  > 84					       ntohs(*csum_field));
    85	
    86		if (unlikely(csum_value_final == 0)) {
    87			switch (ip4h->protocol) {
    88			case IPPROTO_UDP:
    89				/* RFC 768 - DL4 1's complement rule for UDP csum 0 */
    90				csum_value_final = ~csum_value_final;
    91				break;
    92	
    93			case IPPROTO_TCP:
    94				/* DL4 Non-RFC compliant TCP checksum found */
    95				if (*csum_field == 0xFFFF)
    96					csum_value_final = ~csum_value_final;
    97				break;
    98			}
    99		}
   100	
   101		if (csum_value_final == ntohs(*csum_field))
   102			return 0;
   103		else
   104			return -EINVAL;
   105	}
   106	
   107	#if IS_ENABLED(CONFIG_IPV6)
   108	static int
   109	rmnet_map_ipv6_dl_csum_trailer(struct sk_buff *skb,
   110				       struct rmnet_map_dl_csum_trailer *csum_trailer)
   111	{
   112		u16 ip_pseudo_payload_csum, pseudo_csum, ip6_hdr_csum, *csum_field;
   113		u16 csum_value, ip6_payload_csum, csum_value_final;
   114		struct ipv6hdr *ip6h;
   115		void *txporthdr;
   116		u32 length;
   117	
   118		ip6h = (struct ipv6hdr *)(skb->data);
   119	
   120		txporthdr = skb->data + sizeof(struct ipv6hdr);
   121		csum_field = rmnet_map_get_csum_field(ip6h->nexthdr, txporthdr);
   122	
   123		if (!csum_field)
   124			return -EPROTONOSUPPORT;
   125	
   126		csum_value = ~ntohs(csum_trailer->csum_value);
 > 127		ip6_hdr_csum = ~ntohs(ip_compute_csum(ip6h,
   128				      (int)(txporthdr - (void *)(skb->data))));
 > 129		ip6_payload_csum = csum16_sub(csum_value, ip6_hdr_csum);
   130	
   131		length = (ip6h->nexthdr == IPPROTO_UDP) ?
   132			 ntohs(((struct udphdr *)txporthdr)->len) :
   133			 ntohs(ip6h->payload_len);
   134		pseudo_csum = ~ntohs(csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
   135				     length, ip6h->nexthdr, 0));
   136		ip_pseudo_payload_csum = csum16_add(ip6_payload_csum, pseudo_csum);
   137	
   138		csum_value_final = ~csum16_sub(ip_pseudo_payload_csum,
   139					       ntohs(*csum_field));
   140	
   141		if (unlikely(csum_value_final == 0)) {
   142			switch (ip6h->nexthdr) {
   143			case IPPROTO_UDP:
   144				/* RFC 2460 section 8.1
   145				 * DL6 One's complement rule for UDP checksum 0
   146				 */
   147				csum_value_final = ~csum_value_final;
   148				break;
   149	
   150			case IPPROTO_TCP:
   151				/* DL6 Non-RFC compliant TCP checksum found */
   152				if (*csum_field == 0xFFFF)
   153					csum_value_final = ~csum_value_final;
   154				break;
   155			}
   156		}
   157	
   158		if (csum_value_final == ntohs(*csum_field))
   159			return 0;
   160		else
   161			return -EINVAL;
   162	}
   163	#endif
   164	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
diff mbox series

Patch

diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
index 8f8c4f2..3409458 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c
@@ -66,8 +66,8 @@  static void rmnet_set_skb_proto(struct sk_buff *skb)
 			    struct rmnet_port *port)
 {
 	struct rmnet_endpoint *ep;
+	u16 len, pad;
 	u8 mux_id;
-	u16 len;
 
 	if (RMNET_MAP_GET_CD_BIT(skb)) {
 		if (port->data_format & RMNET_INGRESS_FORMAT_MAP_COMMANDS)
@@ -77,7 +77,8 @@  static void rmnet_set_skb_proto(struct sk_buff *skb)
 	}
 
 	mux_id = RMNET_MAP_GET_MUX_ID(skb);
-	len = RMNET_MAP_GET_LENGTH(skb) - RMNET_MAP_GET_PAD(skb);
+	pad = RMNET_MAP_GET_PAD(skb);
+	len = RMNET_MAP_GET_LENGTH(skb) - pad;
 
 	if (mux_id >= RMNET_MAX_LOGICAL_EP)
 		goto free_skb;
@@ -90,8 +91,14 @@  static void rmnet_set_skb_proto(struct sk_buff *skb)
 
 	/* Subtract MAP header */
 	skb_pull(skb, sizeof(struct rmnet_map_header));
-	skb_trim(skb, len);
 	rmnet_set_skb_proto(skb);
+
+	if (port->data_format & RMNET_INGRESS_FORMAT_MAP_CKSUMV4) {
+		if (!rmnet_map_checksum_downlink_packet(skb, len + pad))
+			skb->ip_summed = CHECKSUM_UNNECESSARY;
+	}
+
+	skb_trim(skb, len);
 	rmnet_deliver_skb(skb);
 	return;
 
@@ -115,7 +122,7 @@  static void rmnet_set_skb_proto(struct sk_buff *skb)
 	}
 
 	if (port->data_format & RMNET_INGRESS_FORMAT_DEAGGREGATION) {
-		while ((skbn = rmnet_map_deaggregate(skb)) != NULL)
+		while ((skbn = rmnet_map_deaggregate(skb, port)) != NULL)
 			__rmnet_map_ingress_handler(skbn, port);
 
 		consume_skb(skb);
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
index 01d876c..0539d99 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h
@@ -83,9 +83,11 @@  struct rmnet_map_ul_csum_header {
 #define RMNET_MAP_NO_PAD_BYTES        0
 #define RMNET_MAP_ADD_PAD_BYTES       1
 
-struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb);
+struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
+				      struct rmnet_port *port);
 struct rmnet_map_header *rmnet_map_add_map_header(struct sk_buff *skb,
 						  int hdrlen, int pad);
 void rmnet_map_command(struct sk_buff *skb, struct rmnet_port *port);
+int rmnet_map_checksum_downlink_packet(struct sk_buff *skb, u16 len);
 
 #endif /* _RMNET_MAP_H_ */
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
index 978ce26..543e423 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
@@ -14,6 +14,9 @@ 
  */
 
 #include <linux/netdevice.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <net/ip6_checksum.h>
 #include "rmnet_config.h"
 #include "rmnet_map.h"
 #include "rmnet_private.h"
@@ -21,6 +24,144 @@ 
 #define RMNET_MAP_DEAGGR_SPACING  64
 #define RMNET_MAP_DEAGGR_HEADROOM (RMNET_MAP_DEAGGR_SPACING / 2)
 
+static u16 *rmnet_map_get_csum_field(unsigned char protocol,
+				     const void *txporthdr)
+{
+	u16 *check = 0;
+
+	switch (protocol) {
+	case IPPROTO_TCP:
+		check = &(((struct tcphdr *)txporthdr)->check);
+		break;
+
+	case IPPROTO_UDP:
+		check = &(((struct udphdr *)txporthdr)->check);
+		break;
+
+	default:
+		check = 0;
+		break;
+	}
+
+	return check;
+}
+
+static int
+rmnet_map_ipv4_dl_csum_trailer(struct sk_buff *skb,
+			       struct rmnet_map_dl_csum_trailer *csum_trailer)
+{
+	u16 ip_pseudo_payload_csum, pseudo_csum, ip_hdr_csum, *csum_field;
+	u16 csum_value, ip_payload_csum, csum_value_final;
+	struct iphdr *ip4h;
+	void *txporthdr;
+
+	ip4h = (struct iphdr *)(skb->data);
+	if ((ntohs(ip4h->frag_off) & IP_MF) ||
+	    ((ntohs(ip4h->frag_off) & IP_OFFSET) > 0))
+		return -EOPNOTSUPP;
+
+	txporthdr = skb->data + ip4h->ihl * 4;
+
+	csum_field = rmnet_map_get_csum_field(ip4h->protocol, txporthdr);
+
+	if (!csum_field)
+		return -EPROTONOSUPPORT;
+
+	/* RFC 768 - Skip IPv4 UDP packets where sender checksum field is 0 */
+	if (*csum_field == 0 && ip4h->protocol == IPPROTO_UDP)
+		return 0;
+
+	csum_value = ~ntohs(csum_trailer->csum_value);
+	ip_hdr_csum = ~ip_fast_csum(ip4h, (int)ip4h->ihl);
+	ip_payload_csum = csum16_sub(csum_value, ip_hdr_csum);
+
+	pseudo_csum = ~ntohs(csum_tcpudp_magic(ip4h->saddr, ip4h->daddr,
+			     (u16)(ntohs(ip4h->tot_len) - ip4h->ihl * 4),
+			     (u16)ip4h->protocol, 0));
+	ip_pseudo_payload_csum = csum16_add(ip_payload_csum, pseudo_csum);
+
+	csum_value_final = ~csum16_sub(ip_pseudo_payload_csum,
+				       ntohs(*csum_field));
+
+	if (unlikely(csum_value_final == 0)) {
+		switch (ip4h->protocol) {
+		case IPPROTO_UDP:
+			/* RFC 768 - DL4 1's complement rule for UDP csum 0 */
+			csum_value_final = ~csum_value_final;
+			break;
+
+		case IPPROTO_TCP:
+			/* DL4 Non-RFC compliant TCP checksum found */
+			if (*csum_field == 0xFFFF)
+				csum_value_final = ~csum_value_final;
+			break;
+		}
+	}
+
+	if (csum_value_final == ntohs(*csum_field))
+		return 0;
+	else
+		return -EINVAL;
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static int
+rmnet_map_ipv6_dl_csum_trailer(struct sk_buff *skb,
+			       struct rmnet_map_dl_csum_trailer *csum_trailer)
+{
+	u16 ip_pseudo_payload_csum, pseudo_csum, ip6_hdr_csum, *csum_field;
+	u16 csum_value, ip6_payload_csum, csum_value_final;
+	struct ipv6hdr *ip6h;
+	void *txporthdr;
+	u32 length;
+
+	ip6h = (struct ipv6hdr *)(skb->data);
+
+	txporthdr = skb->data + sizeof(struct ipv6hdr);
+	csum_field = rmnet_map_get_csum_field(ip6h->nexthdr, txporthdr);
+
+	if (!csum_field)
+		return -EPROTONOSUPPORT;
+
+	csum_value = ~ntohs(csum_trailer->csum_value);
+	ip6_hdr_csum = ~ntohs(ip_compute_csum(ip6h,
+			      (int)(txporthdr - (void *)(skb->data))));
+	ip6_payload_csum = csum16_sub(csum_value, ip6_hdr_csum);
+
+	length = (ip6h->nexthdr == IPPROTO_UDP) ?
+		 ntohs(((struct udphdr *)txporthdr)->len) :
+		 ntohs(ip6h->payload_len);
+	pseudo_csum = ~ntohs(csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
+			     length, ip6h->nexthdr, 0));
+	ip_pseudo_payload_csum = csum16_add(ip6_payload_csum, pseudo_csum);
+
+	csum_value_final = ~csum16_sub(ip_pseudo_payload_csum,
+				       ntohs(*csum_field));
+
+	if (unlikely(csum_value_final == 0)) {
+		switch (ip6h->nexthdr) {
+		case IPPROTO_UDP:
+			/* RFC 2460 section 8.1
+			 * DL6 One's complement rule for UDP checksum 0
+			 */
+			csum_value_final = ~csum_value_final;
+			break;
+
+		case IPPROTO_TCP:
+			/* DL6 Non-RFC compliant TCP checksum found */
+			if (*csum_field == 0xFFFF)
+				csum_value_final = ~csum_value_final;
+			break;
+		}
+	}
+
+	if (csum_value_final == ntohs(*csum_field))
+		return 0;
+	else
+		return -EINVAL;
+}
+#endif
+
 /* Adds MAP header to front of skb->data
  * Padding is calculated and set appropriately in MAP header. Mux ID is
  * initialized to 0.
@@ -66,7 +207,8 @@  struct rmnet_map_header *rmnet_map_add_map_header(struct sk_buff *skb,
  * returned, indicating that there are no more packets to deaggregate. Caller
  * is responsible for freeing the original skb.
  */
-struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb)
+struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
+				      struct rmnet_port *port)
 {
 	struct rmnet_map_header *maph;
 	struct sk_buff *skbn;
@@ -78,6 +220,9 @@  struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb)
 	maph = (struct rmnet_map_header *)skb->data;
 	packet_len = ntohs(maph->pkt_len) + sizeof(struct rmnet_map_header);
 
+	if (port->data_format & RMNET_INGRESS_FORMAT_MAP_CKSUMV4)
+		packet_len += sizeof(struct rmnet_map_dl_csum_trailer);
+
 	if (((int)skb->len - (int)packet_len) < 0)
 		return NULL;
 
@@ -97,3 +242,33 @@  struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb)
 
 	return skbn;
 }
+
+/* Validates packet checksums. Function takes a pointer to
+ * the beginning of a buffer which contains the IP payload +
+ * padding + checksum trailer.
+ * Only IPv4 and IPv6 are supported along with TCP & UDP.
+ * Fragmented or tunneled packets are not supported.
+ */
+int rmnet_map_checksum_downlink_packet(struct sk_buff *skb, u16 len)
+{
+	struct rmnet_map_dl_csum_trailer *csum_trailer;
+
+	if (unlikely(!(skb->dev->features & NETIF_F_RXCSUM)))
+		return -EOPNOTSUPP;
+
+	csum_trailer = (struct rmnet_map_dl_csum_trailer *)(skb->data + len);
+
+	if (!ntohs(csum_trailer->valid))
+		return -EINVAL;
+
+	if (skb->protocol == htons(ETH_P_IP))
+		return rmnet_map_ipv4_dl_csum_trailer(skb, csum_trailer);
+	else if (skb->protocol == htons(ETH_P_IPV6))
+#if IS_ENABLED(CONFIG_IPV6)
+		return rmnet_map_ipv6_dl_csum_trailer(skb, csum_trailer);
+#else
+		return -EPROTONOSUPPORT;
+#endif
+
+	return 0;
+}
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
index 5bb29f4..879a2e0 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
@@ -188,6 +188,8 @@  int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev,
 	if (rmnet_get_endpoint(port, id))
 		return -EBUSY;
 
+	rmnet_dev->hw_features = NETIF_F_RXCSUM;
+
 	rc = register_netdevice(rmnet_dev);
 	if (!rc) {
 		ep->egress_dev = rmnet_dev;