static int
 rmnet_map_ipv4_dl_csum_trailer(struct sk_buff *skb,
-                              struct rmnet_map_dl_csum_trailer *csum_trailer)
+                              struct rmnet_map_dl_csum_trailer *csum_trailer,
+                              struct rmnet_priv *priv)
 {
        __sum16 *csum_field, csum_temp, pseudo_csum, hdr_csum, ip_payload_csum;
        u16 csum_value, csum_value_final;
 
        ip4h = (struct iphdr *)(skb->data);
        if ((ntohs(ip4h->frag_off) & IP_MF) ||
-           ((ntohs(ip4h->frag_off) & IP_OFFSET) > 0))
+           ((ntohs(ip4h->frag_off) & IP_OFFSET) > 0)) {
+               priv->stats.csum_fragmented_pkt++;
                return -EOPNOTSUPP;
+       }
 
        txporthdr = skb->data + ip4h->ihl * 4;
 
        csum_field = rmnet_map_get_csum_field(ip4h->protocol, txporthdr);
 
-       if (!csum_field)
+       if (!csum_field) {
+               priv->stats.csum_err_invalid_transport++;
                return -EPROTONOSUPPORT;
+       }
 
        /* RFC 768 - Skip IPv4 UDP packets where sender checksum field is 0 */
-       if (*csum_field == 0 && ip4h->protocol == IPPROTO_UDP)
+       if (*csum_field == 0 && ip4h->protocol == IPPROTO_UDP) {
+               priv->stats.csum_skipped++;
                return 0;
+       }
 
        csum_value = ~ntohs(csum_trailer->csum_value);
        hdr_csum = ~ip_fast_csum(ip4h, (int)ip4h->ihl);
                }
        }
 
-       if (csum_value_final == ntohs((__force __be16)*csum_field))
+       if (csum_value_final == ntohs((__force __be16)*csum_field)) {
+               priv->stats.csum_ok++;
                return 0;
-       else
+       } else {
+               priv->stats.csum_validation_failed++;
                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)
+                              struct rmnet_map_dl_csum_trailer *csum_trailer,
+                              struct rmnet_priv *priv)
 {
        __sum16 *csum_field, ip6_payload_csum, pseudo_csum, csum_temp;
        u16 csum_value, csum_value_final;
        txporthdr = skb->data + sizeof(struct ipv6hdr);
        csum_field = rmnet_map_get_csum_field(ip6h->nexthdr, txporthdr);
 
-       if (!csum_field)
+       if (!csum_field) {
+               priv->stats.csum_err_invalid_transport++;
                return -EPROTONOSUPPORT;
+       }
 
        csum_value = ~ntohs(csum_trailer->csum_value);
        ip6_hdr_csum = (__force __be16)
                }
        }
 
-       if (csum_value_final == ntohs((__force __be16)*csum_field))
+       if (csum_value_final == ntohs((__force __be16)*csum_field)) {
+               priv->stats.csum_ok++;
                return 0;
-       else
+       } else {
+               priv->stats.csum_validation_failed++;
                return -EINVAL;
+       }
 }
 #endif
 
  */
 int rmnet_map_checksum_downlink_packet(struct sk_buff *skb, u16 len)
 {
+       struct rmnet_priv *priv = netdev_priv(skb->dev);
        struct rmnet_map_dl_csum_trailer *csum_trailer;
 
-       if (unlikely(!(skb->dev->features & NETIF_F_RXCSUM)))
+       if (unlikely(!(skb->dev->features & NETIF_F_RXCSUM))) {
+               priv->stats.csum_sw++;
                return -EOPNOTSUPP;
+       }
 
        csum_trailer = (struct rmnet_map_dl_csum_trailer *)(skb->data + len);
 
-       if (!csum_trailer->valid)
+       if (!csum_trailer->valid) {
+               priv->stats.csum_valid_unset++;
                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 (skb->protocol == htons(ETH_P_IP)) {
+               return rmnet_map_ipv4_dl_csum_trailer(skb, csum_trailer, priv);
+       } else if (skb->protocol == htons(ETH_P_IPV6)) {
 #if IS_ENABLED(CONFIG_IPV6)
-               return rmnet_map_ipv6_dl_csum_trailer(skb, csum_trailer);
+               return rmnet_map_ipv6_dl_csum_trailer(skb, csum_trailer, priv);
 #else
+               priv->stats.csum_err_invalid_ip_version++;
                return -EPROTONOSUPPORT;
 #endif
+       } else {
+               priv->stats.csum_err_invalid_ip_version++;
+               return -EPROTONOSUPPORT;
+       }
 
        return 0;
 }
 void rmnet_map_checksum_uplink_packet(struct sk_buff *skb,
                                      struct net_device *orig_dev)
 {
+       struct rmnet_priv *priv = netdev_priv(orig_dev);
        struct rmnet_map_ul_csum_header *ul_header;
        void *iphdr;
 
                        rmnet_map_ipv6_ul_csum_header(iphdr, ul_header, skb);
                        return;
 #else
+                       priv->stats.csum_err_invalid_ip_version++;
                        goto sw_csum;
 #endif
+               } else {
+                       priv->stats.csum_err_invalid_ip_version++;
                }
        }
 
        ul_header->csum_insert_offset = 0;
        ul_header->csum_enabled = 0;
        ul_header->udp_ip4_ind = 0;
+
+       priv->stats.csum_sw++;
 }
 
        .ndo_get_stats64 = rmnet_get_stats64,
 };
 
+static const char rmnet_gstrings_stats[][ETH_GSTRING_LEN] = {
+       "Checksum ok",
+       "Checksum valid bit not set",
+       "Checksum validation failed",
+       "Checksum error bad buffer",
+       "Checksum error bad ip version",
+       "Checksum error bad transport",
+       "Checksum skipped on ip fragment",
+       "Checksum skipped",
+       "Checksum computed in software",
+};
+
+static void rmnet_get_strings(struct net_device *dev, u32 stringset, u8 *buf)
+{
+       switch (stringset) {
+       case ETH_SS_STATS:
+               memcpy(buf, &rmnet_gstrings_stats,
+                      sizeof(rmnet_gstrings_stats));
+               break;
+       }
+}
+
+static int rmnet_get_sset_count(struct net_device *dev, int sset)
+{
+       switch (sset) {
+       case ETH_SS_STATS:
+               return ARRAY_SIZE(rmnet_gstrings_stats);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static void rmnet_get_ethtool_stats(struct net_device *dev,
+                                   struct ethtool_stats *stats, u64 *data)
+{
+       struct rmnet_priv *priv = netdev_priv(dev);
+       struct rmnet_priv_stats *st = &priv->stats;
+
+       if (!data)
+               return;
+
+       memcpy(data, st, ARRAY_SIZE(rmnet_gstrings_stats) * sizeof(u64));
+}
+
+static const struct ethtool_ops rmnet_ethtool_ops = {
+       .get_ethtool_stats = rmnet_get_ethtool_stats,
+       .get_strings = rmnet_get_strings,
+       .get_sset_count = rmnet_get_sset_count,
+};
+
 /* Called by kernel whenever a new rmnet<n> device is created. Sets MTU,
  * flags, ARP type, needed headroom, etc...
  */
        rmnet_dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
 
        rmnet_dev->needs_free_netdev = true;
+       rmnet_dev->ethtool_ops = &rmnet_ethtool_ops;
 }
 
 /* Exposed API */