summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/uapi/linux/rmnet_data.h1
-rw-r--r--net/rmnet_data/rmnet_data_handlers.c21
-rw-r--r--net/rmnet_data/rmnet_data_stats.c17
-rw-r--r--net/rmnet_data/rmnet_data_stats.h1
-rw-r--r--net/rmnet_data/rmnet_data_trace.h20
-rw-r--r--net/rmnet_data/rmnet_data_vnd.c5
-rw-r--r--net/rmnet_data/rmnet_map.h19
-rw-r--r--net/rmnet_data/rmnet_map_data.c97
8 files changed, 177 insertions, 4 deletions
diff --git a/include/uapi/linux/rmnet_data.h b/include/uapi/linux/rmnet_data.h
index 3295a1a5f1fd..aa47160f8fdf 100644
--- a/include/uapi/linux/rmnet_data.h
+++ b/include/uapi/linux/rmnet_data.h
@@ -23,6 +23,7 @@
#define RMNET_EGRESS_FORMAT_MAP (1<<1)
#define RMNET_EGRESS_FORMAT_AGGREGATION (1<<2)
#define RMNET_EGRESS_FORMAT_MUXING (1<<3)
+#define RMNET_EGRESS_FORMAT_MAP_CKSUMV3 (1<<4)
#define RMNET_INGRESS_FIX_ETHERNET (1<<0)
#define RMNET_INGRESS_FORMAT_MAP (1<<1)
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;
+}