diff options
author | Sivan Reinstein <sivanr@codeaurora.org> | 2014-09-03 15:40:27 +0300 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-22 11:05:23 -0700 |
commit | 84914d32cb367eca2b2ecdb8e162ce6589a81899 (patch) | |
tree | c3b53a56ba96cb748724eb87cd56549b79f8ef6e /net/rmnet_data | |
parent | 104da2c2579b47270c15acc606ed13b8a563292e (diff) |
net: rmnet_data: add support for UL MAP based checksum offload
Add UL checksum offload routines for MAPv3. Can bypass checksum software
for IPv4/IPv6 TCP/UDP protocols.
Set rmnet_data VNDs hw_flags to NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM to
define the checksum offload abilities.
Add UL checksum meta-info header for IPv4/IPv6 TCP/UDP packets for which
UL checksum is being offloaded.
CRs-fixed: 731693
Change-Id: Ief139d357b528aead66acfe39a5227328b8fbf93
Signed-off-by: Sivan Reinstein <sivanr@codeaurora.org>
Diffstat (limited to 'net/rmnet_data')
-rw-r--r-- | net/rmnet_data/rmnet_data_handlers.c | 21 | ||||
-rw-r--r-- | net/rmnet_data/rmnet_data_stats.c | 17 | ||||
-rw-r--r-- | net/rmnet_data/rmnet_data_stats.h | 1 | ||||
-rw-r--r-- | net/rmnet_data/rmnet_data_trace.h | 20 | ||||
-rw-r--r-- | net/rmnet_data/rmnet_data_vnd.c | 5 | ||||
-rw-r--r-- | net/rmnet_data/rmnet_map.h | 19 | ||||
-rw-r--r-- | net/rmnet_data/rmnet_map_data.c | 97 |
7 files changed, 176 insertions, 4 deletions
diff --git a/net/rmnet_data/rmnet_data_handlers.c b/net/rmnet_data/rmnet_data_handlers.c index b131cb1134f2..d801aee0f915 100644 --- a/net/rmnet_data/rmnet_data_handlers.c +++ b/net/rmnet_data/rmnet_data_handlers.c @@ -19,6 +19,7 @@ #include <linux/module.h> #include <linux/rmnet_data.h> #include <linux/net_map.h> +#include <linux/netdev_features.h> #include "rmnet_data_private.h" #include "rmnet_data_config.h" #include "rmnet_data_vnd.h" @@ -351,6 +352,7 @@ static rx_handler_result_t rmnet_map_ingress_handler(struct sk_buff *skb, * @config: Physical endpoint configuration for the egress device * @ep: logical endpoint configuration of the packet originator * (e.g.. RmNet virtual network device) + * @orig_dev: The originator vnd device * * Called if and only if MAP is configured in the egress device's egress data * format. Will expand skb if there is insufficient headroom for MAP protocol. @@ -362,14 +364,21 @@ static rx_handler_result_t rmnet_map_ingress_handler(struct sk_buff *skb, */ static int rmnet_map_egress_handler(struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config, - struct rmnet_logical_ep_conf_s *ep) + struct rmnet_logical_ep_conf_s *ep, + struct net_device *orig_dev) { - int required_headroom, additional_header_length; + int required_headroom, additional_header_length, ckresult; struct rmnet_map_header_s *map_header; additional_header_length = 0; required_headroom = sizeof(struct rmnet_map_header_s); + if (config->egress_data_format & RMNET_EGRESS_FORMAT_MAP_CKSUMV3) { + required_headroom += + sizeof(struct rmnet_map_ul_checksum_header_s); + additional_header_length += + sizeof(struct rmnet_map_ul_checksum_header_s); + } LOGD("headroom of %d bytes", required_headroom); @@ -381,6 +390,12 @@ static int rmnet_map_egress_handler(struct sk_buff *skb, } } + if (config->egress_data_format & RMNET_EGRESS_FORMAT_MAP_CKSUMV3) { + ckresult = rmnet_map_checksum_uplink_packet(skb, orig_dev); + trace_rmnet_map_checksum_uplink_packet(orig_dev, ckresult); + rmnet_stats_ul_checksum(ckresult); + } + map_header = rmnet_map_add_map_header(skb, additional_header_length); if (!map_header) { @@ -544,7 +559,7 @@ void rmnet_egress_handler(struct sk_buff *skb, skb->dev->name, config->egress_data_format); if (config->egress_data_format & RMNET_EGRESS_FORMAT_MAP) { - switch (rmnet_map_egress_handler(skb, config, ep)) { + switch (rmnet_map_egress_handler(skb, config, ep, orig_dev)) { case RMNET_MAP_CONSUMED: LOGD("%s", "MAP process consumed packet"); return; diff --git a/net/rmnet_data/rmnet_data_stats.c b/net/rmnet_data/rmnet_data_stats.c index caf936f3ebfa..5fefda55c72c 100644 --- a/net/rmnet_data/rmnet_data_stats.c +++ b/net/rmnet_data/rmnet_data_stats.c @@ -57,6 +57,11 @@ unsigned long int checksum_dl_stats[RMNET_MAP_CHECKSUM_ENUM_LENGTH]; module_param_array(checksum_dl_stats, ulong, 0, S_IRUGO); MODULE_PARM_DESC(checksum_dl_stats, "Downlink Checksum Statistics"); +static DEFINE_SPINLOCK(rmnet_checksum_ul_stats); +unsigned long int checksum_ul_stats[RMNET_MAP_CHECKSUM_ENUM_LENGTH]; +module_param_array(checksum_ul_stats, ulong, 0, S_IRUGO); +MODULE_PARM_DESC(checksum_ul_stats, "Uplink Checksum Statistics"); + void rmnet_kfree_skb(struct sk_buff *skb, unsigned int reason) { unsigned long flags; @@ -117,3 +122,15 @@ void rmnet_stats_dl_checksum(unsigned int rc) checksum_dl_stats[rc]++; spin_unlock_irqrestore(&rmnet_checksum_dl_stats, flags); } + +void rmnet_stats_ul_checksum(unsigned int rc) +{ + unsigned long flags; + + if (rc >= RMNET_MAP_CHECKSUM_ENUM_LENGTH) + rc = RMNET_MAP_CHECKSUM_ERR_UNKOWN; + + spin_lock_irqsave(&rmnet_checksum_ul_stats, flags); + checksum_ul_stats[rc]++; + spin_unlock_irqrestore(&rmnet_checksum_ul_stats, flags); +} diff --git a/net/rmnet_data/rmnet_data_stats.h b/net/rmnet_data/rmnet_data_stats.h index b25c45edcdca..e9b2f6c050e9 100644 --- a/net/rmnet_data/rmnet_data_stats.h +++ b/net/rmnet_data/rmnet_data_stats.h @@ -56,4 +56,5 @@ void rmnet_stats_queue_xmit(int rc, unsigned int reason); void rmnet_stats_deagg_pkts(int aggcount); void rmnet_stats_agg_pkts(int aggcount); void rmnet_stats_dl_checksum(unsigned int rc); +void rmnet_stats_ul_checksum(unsigned int rc); #endif /* _RMNET_DATA_STATS_H_ */ diff --git a/net/rmnet_data/rmnet_data_trace.h b/net/rmnet_data/rmnet_data_trace.h index 98a6ed5213a9..ae5180e3733a 100644 --- a/net/rmnet_data/rmnet_data_trace.h +++ b/net/rmnet_data/rmnet_data_trace.h @@ -220,6 +220,26 @@ TRACE_EVENT(rmnet_map_checksum_downlink_packet, __get_str(name), __entry->res) ) +TRACE_EVENT(rmnet_map_checksum_uplink_packet, + + TP_PROTO(struct net_device *dev, int ckresult), + + TP_ARGS(dev, ckresult), + + TP_STRUCT__entry( + __string(name, dev->name) + __field(int, res) + ), + + TP_fast_assign( + __assign_str(name, dev->name); + __entry->res = ckresult; + ), + + TP_printk("UL checksum on dev=%s, res: %d", + __get_str(name), __entry->res) +) + #endif /* _RMNET_DATA_TRACE_H_ */ /* This part must be outside protection */ diff --git a/net/rmnet_data/rmnet_data_vnd.c b/net/rmnet_data/rmnet_data_vnd.c index e9843c5a4386..c2afc39f67a1 100644 --- a/net/rmnet_data/rmnet_data_vnd.c +++ b/net/rmnet_data/rmnet_data_vnd.c @@ -591,6 +591,11 @@ int rmnet_vnd_create_dev(int id, struct net_device **new_device, return RMNET_CONFIG_NOMEM; } + if (!prefix) { + /* Configuring UL checksum offload on rmnet_data interfaces */ + dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; + } + rc = register_netdevice(dev); if (rc != 0) { LOGE("Failed to to register netdev [%s]", dev->name); diff --git a/net/rmnet_data/rmnet_map.h b/net/rmnet_data/rmnet_map.h index f6d253609695..e4bc3aaf5c6a 100644 --- a/net/rmnet_data/rmnet_map.h +++ b/net/rmnet_data/rmnet_map.h @@ -60,6 +60,21 @@ struct rmnet_map_dl_checksum_trailer_s { unsigned short checksum_value; } __aligned(1); +struct rmnet_map_ul_checksum_header_s { + unsigned short checksum_start_offset; +#if defined(__LITTLE_ENDIAN_BITFIELD) + unsigned short checksum_insert_offset:14; + unsigned short udp_ip4_ind:1; + unsigned short cks_en:1; +#elif defined(__BIG_ENDIAN_BITFIELD) + unsigned short cks_en:1; + unsigned short udp_ip4_ind:1; + unsigned short checksum_insert_offset:14; +#else +#error "Please fix <asm/byteorder.h>" +#endif +} __aligned(1); + enum rmnet_map_results_e { RMNET_MAP_SUCCESS, RMNET_MAP_CONSUMED, @@ -89,6 +104,7 @@ enum rmnet_map_checksum_errors_e { RMNET_MAP_CHECKSUM_ERR_UNKNOWN_TRANSPORT, RMNET_MAP_CHECKSUM_FRAGMENTED_PACKET, RMNET_MAP_CHECKSUM_SKIPPED, + RMNET_MAP_CHECKSUM_SW, /* This should always be the last element */ RMNET_MAP_CHECKSUM_ENUM_LENGTH }; @@ -124,6 +140,7 @@ void rmnet_map_aggregate(struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config); int rmnet_map_checksum_downlink_packet(struct sk_buff *skb); - +int rmnet_map_checksum_uplink_packet(struct sk_buff *skb, + struct net_device *orig_dev); #endif /* _RMNET_MAP_H_ */ diff --git a/net/rmnet_data/rmnet_map_data.c b/net/rmnet_data/rmnet_map_data.c index 96ce624e5df0..ad843ce03061 100644 --- a/net/rmnet_data/rmnet_map_data.c +++ b/net/rmnet_data/rmnet_map_data.c @@ -571,3 +571,100 @@ int rmnet_map_checksum_downlink_packet(struct sk_buff *skb) return RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION; } + +static void rmnet_map_fill_ipv4_packet_ul_checksum_header(void *iphdr, + struct rmnet_map_ul_checksum_header_s *ul_header, struct sk_buff *skb) +{ + struct iphdr *ip4h = (struct iphdr *)iphdr; + unsigned short *hdr = (unsigned short *)ul_header; + + ul_header->checksum_start_offset = htons((unsigned short) + (skb_transport_header(skb) - (unsigned char *)iphdr)); + ul_header->checksum_insert_offset = skb->csum_offset + (unsigned short) + (skb_transport_header(skb) - (unsigned char *)iphdr); + ul_header->cks_en = 1; + if (ip4h->protocol == IPPROTO_UDP) + ul_header->udp_ip4_ind = 1; + else + ul_header->udp_ip4_ind = 0; + /* Changing checksum_insert_offset to network order */ + hdr++; + *hdr = htons(*hdr); + skb->ip_summed = CHECKSUM_NONE; +} + +static void rmnet_map_fill_ipv6_packet_ul_checksum_header(void *iphdr, + struct rmnet_map_ul_checksum_header_s *ul_header, struct sk_buff *skb) +{ + unsigned short *hdr = (unsigned short *)ul_header; + + ul_header->checksum_start_offset = htons((unsigned short) + (skb_transport_header(skb) - (unsigned char *)iphdr)); + ul_header->checksum_insert_offset = skb->csum_offset + (unsigned short) + (skb_transport_header(skb) - (unsigned char *)iphdr); + ul_header->cks_en = 1; + ul_header->udp_ip4_ind = 0; + /* Changing checksum_insert_offset to network order */ + hdr++; + *hdr = htons(*hdr); + skb->ip_summed = CHECKSUM_NONE; +} + +/** + * rmnet_map_checksum_uplink_packet() - Generates UL checksum + * meta info header + * @skb: Pointer to the packet's skb. + * + * Generates UL checksum meta info header for IPv4 and IPv6 over TCP and UDP + * packets that are supported for UL checksum offload. + * + * Return: + * - RMNET_MAP_CHECKSUM_OK: Validation of checksum succeeded. + * - RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION: Unrecognized IP header. + * - RMNET_MAP_CHECKSUM_SW: Unsupported packet for UL checksum offload. + */ +int rmnet_map_checksum_uplink_packet(struct sk_buff *skb, + struct net_device *orig_dev) +{ + unsigned char ip_version; + struct rmnet_map_ul_checksum_header_s *ul_header; + void *iphdr; + int ret; + + ul_header = (struct rmnet_map_ul_checksum_header_s *) + skb_push(skb, sizeof(struct rmnet_map_ul_checksum_header_s)); + + if (unlikely(!(orig_dev->features & + (NETIF_F_V4_CSUM | NETIF_F_V6_CSUM)))) { + ret = RMNET_MAP_CHECKSUM_SW; + goto sw_checksum; + } + + if (skb->ip_summed == CHECKSUM_PARTIAL) { + iphdr = (char *)ul_header + + sizeof(struct rmnet_map_ul_checksum_header_s); + ip_version = (*(char *)iphdr & 0xF0) >> 4; + if (ip_version == 0x04) { + rmnet_map_fill_ipv4_packet_ul_checksum_header(iphdr, + ul_header, skb); + return RMNET_MAP_CHECKSUM_OK; + } else if (ip_version == 0x06) { + rmnet_map_fill_ipv6_packet_ul_checksum_header(iphdr, + ul_header, skb); + return RMNET_MAP_CHECKSUM_OK; + } else { + ret = RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION; + goto sw_checksum; + } + } else { + ret = RMNET_MAP_CHECKSUM_SW; + goto sw_checksum; + } + +sw_checksum: + ul_header->checksum_start_offset = 0; + ul_header->checksum_insert_offset = 0; + ul_header->cks_en = 0; + ul_header->udp_ip4_ind = 0; + return ret; +} |