diff --git a/core/rmnet_config.h b/core/rmnet_config.h index 4bb68eec33..d70d57d074 100755 --- a/core/rmnet_config.h +++ b/core/rmnet_config.h @@ -175,6 +175,8 @@ struct rmnet_priv_stats { u64 csum_hw; struct rmnet_coal_stats coal; u64 ul_prio; + u64 tso_pkts; + u64 tso_arriv_errs; }; struct rmnet_priv { diff --git a/core/rmnet_handlers.c b/core/rmnet_handlers.c index 0399116017..21b82ed636 100755 --- a/core/rmnet_handlers.c +++ b/core/rmnet_handlers.c @@ -325,7 +325,7 @@ static int rmnet_map_egress_handler(struct sk_buff *skb, struct rmnet_port *port, u8 mux_id, struct net_device *orig_dev) { - int required_headroom, additional_header_len, csum_type; + int required_headroom, additional_header_len, csum_type, tso = 0; struct rmnet_map_header *map_header; additional_header_len = 0; @@ -351,6 +351,20 @@ static int rmnet_map_egress_handler(struct sk_buff *skb, if (port->data_format & RMNET_INGRESS_FORMAT_PS) qmi_rmnet_work_maybe_restart(port); + if (csum_type && + (skb_shinfo(skb)->gso_type & (SKB_GSO_UDP_L4 | SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) && + skb_shinfo(skb)->gso_size) { + unsigned long flags; + + spin_lock_irqsave(&port->agg_lock, flags); + rmnet_map_send_agg_skb(port, flags); + + if (rmnet_map_add_tso_header(skb, port, orig_dev)) + return -EINVAL; + csum_type = 0; + tso = 1; + } + if (csum_type) rmnet_map_checksum_uplink_packet(skb, port, orig_dev, csum_type); @@ -363,7 +377,7 @@ static int rmnet_map_egress_handler(struct sk_buff *skb, map_header->mux_id = mux_id; if (port->data_format & RMNET_EGRESS_FORMAT_AGGREGATION) { - if (rmnet_map_tx_agg_skip(skb, required_headroom)) + if (rmnet_map_tx_agg_skip(skb, required_headroom) || tso) goto done; rmnet_map_tx_aggregate(skb, port); @@ -467,7 +481,7 @@ void rmnet_egress_handler(struct sk_buff *skb) skb_len = skb->len; err = rmnet_map_egress_handler(skb, port, mux_id, orig_dev); - if (err == -ENOMEM) { + if (err == -ENOMEM || err == -EINVAL) { goto drop; } else if (err == -EINPROGRESS) { rmnet_vnd_tx_fixup(orig_dev, skb_len); diff --git a/core/rmnet_map.h b/core/rmnet_map.h index 853318da8f..ec6bf47409 100644 --- a/core/rmnet_map.h +++ b/core/rmnet_map.h @@ -48,6 +48,7 @@ enum rmnet_map_v5_header_type { RMNET_MAP_HEADER_TYPE_UNKNOWN, RMNET_MAP_HEADER_TYPE_COALESCING = 0x1, RMNET_MAP_HEADER_TYPE_CSUM_OFFLOAD = 0x2, + RMNET_MAP_HEADER_TYPE_TSO = 0x3, RMNET_MAP_HEADER_TYPE_ENUM_LENGTH }; @@ -111,6 +112,16 @@ struct rmnet_map_v5_coal_header { struct rmnet_map_v5_nl_pair nl_pairs[RMNET_MAP_V5_MAX_NLOS]; } __aligned(1); +struct rmnet_map_v5_tso_header { + u8 next_hdr:1; + u8 header_type:7; + u8 hw_reserved:5; + u8 priority:1; + u8 zero_csum:1; + u8 ip_id_cfg:1; + __be16 segment_size; +} __aligned(1); + /* QMAP v4 headers */ struct rmnet_map_dl_csum_trailer { u8 reserved1; @@ -280,4 +291,7 @@ int rmnet_map_dl_ind_register(struct rmnet_port *port, int rmnet_map_dl_ind_deregister(struct rmnet_port *port, struct rmnet_map_dl_ind *dl_ind); void rmnet_map_cmd_exit(struct rmnet_port *port); +void rmnet_map_send_agg_skb(struct rmnet_port *port, unsigned long flags); +int rmnet_map_add_tso_header(struct sk_buff *skb, struct rmnet_port *port, + struct net_device *orig_dev); #endif /* _RMNET_MAP_H_ */ diff --git a/core/rmnet_map_data.c b/core/rmnet_map_data.c index 9516c8ae91..301b41a432 100644 --- a/core/rmnet_map_data.c +++ b/core/rmnet_map_data.c @@ -1423,7 +1423,7 @@ static struct sk_buff *rmnet_map_build_skb(struct rmnet_port *port) return skb; } -static void rmnet_map_send_agg_skb(struct rmnet_port *port, unsigned long flags) +void rmnet_map_send_agg_skb(struct rmnet_port *port, unsigned long flags) { struct sk_buff *agg_skb; @@ -1621,3 +1621,38 @@ void rmnet_map_tx_qmap_cmd(struct sk_buff *qmap_skb) dev_queue_xmit(qmap_skb); } EXPORT_SYMBOL(rmnet_map_tx_qmap_cmd); + +int rmnet_map_add_tso_header(struct sk_buff *skb, struct rmnet_port *port, + struct net_device *orig_dev) +{ + struct rmnet_priv *priv = netdev_priv(orig_dev); + struct rmnet_map_v5_tso_header *ul_header; + + if (!(orig_dev->features & (NETIF_F_ALL_TSO | NETIF_F_GSO_UDP_L4))) { + priv->stats.tso_arriv_errs++; + return -EINVAL; + } + + ul_header = (struct rmnet_map_v5_tso_header *) + skb_push(skb, sizeof(*ul_header)); + memset(ul_header, 0, sizeof(*ul_header)); + ul_header->header_type = RMNET_MAP_HEADER_TYPE_TSO; + + if (port->data_format & RMNET_EGRESS_FORMAT_PRIORITY) + rmnet_map_v5_check_priority(skb, orig_dev, + (struct rmnet_map_v5_csum_header *)ul_header); + + ul_header->segment_size = htons(skb_shinfo(skb)->gso_size); + + if (skb_shinfo(skb)->gso_type & SKB_GSO_TCP_FIXEDID) + ul_header->ip_id_cfg = 1; + + skb->ip_summed = CHECKSUM_NONE; + skb_shinfo(skb)->gso_size = 0; + skb_shinfo(skb)->gso_segs = 0; + skb_shinfo(skb)->gso_type = 0; + + priv->stats.tso_pkts++; + + return 0; +} diff --git a/core/rmnet_vnd.c b/core/rmnet_vnd.c index 85617d02b0..403999cf0e 100644 --- a/core/rmnet_vnd.c +++ b/core/rmnet_vnd.c @@ -257,6 +257,8 @@ static const char rmnet_gstrings_stats[][ETH_GSTRING_LEN] = { "Coalescing UDP frames", "Coalescing UDP bytes", "Uplink priority packets", + "TSO packets", + "TSO packets arriving incorrectly", }; static const char rmnet_port_gstrings_stats[][ETH_GSTRING_LEN] = { @@ -390,9 +392,13 @@ int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev, rmnet_dev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; rmnet_dev->hw_features |= NETIF_F_SG; rmnet_dev->hw_features |= NETIF_F_GRO_HW; + rmnet_dev->hw_features |= NETIF_F_GSO_UDP_L4; + rmnet_dev->hw_features |= NETIF_F_ALL_TSO; priv->real_dev = real_dev; + rmnet_dev->gso_max_size = 64000; + rc = register_netdevice(rmnet_dev); if (!rc) { ep->egress_dev = rmnet_dev;