net: stats: Add infrastructure for collecting ethernet stats

Make sure we are able to collect ethernet statistics and query
it via net management API.

Fixes #6899

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
diff --git a/include/net/ethernet.h b/include/net/ethernet.h
index 2696785..08193af 100644
--- a/include/net/ethernet.h
+++ b/include/net/ethernet.h
@@ -104,6 +104,14 @@
 	 */
 	struct net_if_api iface_api;
 
+#if defined(CONFIG_NET_STATISTICS_ETHERNET)
+	/** Collect optional ethernet specific statistics. This pointer
+	 * should be set by driver if statistics needs to be collected
+	 * for that driver.
+	 */
+	struct net_stats_eth *stats;
+#endif
+
 	/** Get the device capabilities */
 	enum ethernet_hw_caps (*get_capabilities)(struct device *dev);
 
diff --git a/include/net/net_stats.h b/include/net/net_stats.h
index 3538355..d0ac1b5 100644
--- a/include/net/net_stats.h
+++ b/include/net/net_stats.h
@@ -31,8 +31,13 @@
 typedef u32_t net_stats_t;
 
 struct net_stats_bytes {
-	u32_t sent;
-	u32_t received;
+	net_stats_t sent;
+	net_stats_t received;
+};
+
+struct net_stats_pkts {
+	net_stats_t tx;
+	net_stats_t rx;
 };
 
 struct net_stats_ip {
@@ -294,6 +299,65 @@
 #endif
 };
 
+struct net_stats_eth_errors {
+	net_stats_t rx_length_errors;
+	net_stats_t rx_over_errors;
+	net_stats_t rx_crc_errors;
+	net_stats_t rx_frame_errors;
+	net_stats_t rx_no_buffer_count;
+	net_stats_t rx_missed_errors;
+	net_stats_t rx_long_length_errors;
+	net_stats_t rx_short_length_errors;
+	net_stats_t rx_align_errors;
+	net_stats_t rx_dma_failed;
+	net_stats_t rx_buf_alloc_failed;
+
+	net_stats_t tx_aborted_errors;
+	net_stats_t tx_carrier_errors;
+	net_stats_t tx_fifo_errors;
+	net_stats_t tx_heartbeat_errors;
+	net_stats_t tx_window_errors;
+	net_stats_t tx_dma_failed;
+
+	net_stats_t uncorr_ecc_errors;
+	net_stats_t corr_ecc_errors;
+};
+
+struct net_stats_eth_flow {
+	net_stats_t rx_flow_control_xon;
+	net_stats_t rx_flow_control_xoff;
+	net_stats_t tx_flow_control_xon;
+	net_stats_t tx_flow_control_xoff;
+};
+
+struct net_stats_eth_csum {
+	net_stats_t rx_csum_offload_good;
+	net_stats_t rx_csum_offload_errors;
+};
+
+struct net_stats_eth_hw_timestamp {
+	net_stats_t rx_hwtstamp_cleared;
+	net_stats_t tx_hwtstamp_timeouts;
+	net_stats_t tx_hwtstamp_skipped;
+};
+
+/* Ethernet specific statistics */
+struct net_stats_eth {
+	struct net_stats_bytes bytes;
+	struct net_stats_pkts pkts;
+	struct net_stats_pkts broadcast;
+	struct net_stats_pkts multicast;
+	struct net_stats_pkts errors;
+	struct net_stats_eth_errors error_details;
+	struct net_stats_eth_flow flow_control;
+	struct net_stats_eth_csum csum;
+	struct net_stats_eth_hw_timestamp hw_timestamp;
+	net_stats_t collisions;
+	net_stats_t tx_dropped;
+	net_stats_t tx_timeout_count;
+	net_stats_t tx_restart_queue;
+};
+
 #if defined(CONFIG_NET_STATISTICS_USER_API)
 /* Management part definitions */
 
@@ -316,6 +380,7 @@
 	NET_REQUEST_STATS_CMD_GET_UDP,
 	NET_REQUEST_STATS_CMD_GET_TCP,
 	NET_REQUEST_STATS_CMD_GET_RPL,
+	NET_REQUEST_STATS_CMD_GET_ETHERNET,
 };
 
 #define NET_REQUEST_STATS_GET_ALL				\
@@ -387,6 +452,13 @@
 NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_STATS_GET_RPL);
 #endif /* CONFIG_NET_STATISTICS_RPL */
 
+#if defined(CONFIG_NET_STATISTICS_ETHERNET)
+#define NET_REQUEST_STATS_GET_ETHERNET				\
+	(_NET_STATS_BASE | NET_REQUEST_STATS_CMD_GET_ETHERNET)
+
+NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_STATS_GET_ETHERNET);
+#endif /* CONFIG_NET_STATISTICS_ETHERNET */
+
 #endif /* CONFIG_NET_STATISTICS_USER_API */
 
 /**
diff --git a/subsys/net/ip/Kconfig.stats b/subsys/net/ip/Kconfig.stats
index 7c9c13b..4035107 100644
--- a/subsys/net/ip/Kconfig.stats
+++ b/subsys/net/ip/Kconfig.stats
@@ -93,4 +93,13 @@
 	help
 	  Keep track of MLD related statistics
 
+config NET_STATISTICS_ETHERNET
+	bool "Ethernet statistics"
+	depends on NET_L2_ETHERNET
+	default y
+	help
+	  Keep track of Ethernet related statistics. Note that this
+	  requires support from the ethernet driver. The driver needs
+	  to collect the statistics.
+
 endif # NET_STATISTICS
diff --git a/subsys/net/ip/l2/ethernet/CMakeLists.txt b/subsys/net/ip/l2/ethernet/CMakeLists.txt
index d37d4ac..54257d7 100644
--- a/subsys/net/ip/l2/ethernet/CMakeLists.txt
+++ b/subsys/net/ip/l2/ethernet/CMakeLists.txt
@@ -7,3 +7,4 @@
 zephyr_library_sources_ifdef(CONFIG_NET_ARP              arp.c)
 zephyr_library_sources_ifdef(CONFIG_NET_L2_ETHERNET      ethernet.c)
 zephyr_library_sources_ifdef(CONFIG_NET_L2_ETHERNET_MGMT ethernet_mgmt.c)
+zephyr_library_sources_ifdef(CONFIG_NET_STATISTICS_ETHERNET ethernet_stats.c)
diff --git a/subsys/net/ip/l2/ethernet/eth_stats.h b/subsys/net/ip/l2/ethernet/eth_stats.h
new file mode 100644
index 0000000..e511a3a
--- /dev/null
+++ b/subsys/net/ip/l2/ethernet/eth_stats.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef __ETH_STATS_H__
+#define __ETH_STATS_H__
+
+#if defined(CONFIG_NET_STATISTICS_ETHERNET)
+
+#include <net/net_ip.h>
+#include <net/net_stats.h>
+#include <net/net_if.h>
+
+static inline void eth_stats_update_bytes_rx(struct net_if *iface,
+					     u32_t bytes)
+{
+	struct net_stats_eth *stats;
+
+	stats = ((const struct ethernet_api *)
+		 net_if_get_device(iface)->driver_api)->stats;
+	if (!stats) {
+		return;
+	}
+
+	stats->bytes.received += bytes;
+}
+
+static inline void eth_stats_update_bytes_tx(struct net_if *iface,
+					     u32_t bytes)
+{
+	struct net_stats_eth *stats;
+
+	stats = ((const struct ethernet_api *)
+		 net_if_get_device(iface)->driver_api)->stats;
+	if (!stats) {
+		return;
+	}
+
+	stats->bytes.sent += bytes;
+}
+
+static inline void eth_stats_update_pkts_rx(struct net_if *iface)
+{
+	struct net_stats_eth *stats;
+
+	stats = ((const struct ethernet_api *)
+		 net_if_get_device(iface)->driver_api)->stats;
+	if (!stats) {
+		return;
+	}
+
+	stats->pkts.rx++;
+}
+
+static inline void eth_stats_update_pkts_tx(struct net_if *iface)
+{
+	struct net_stats_eth *stats;
+
+	stats = ((const struct ethernet_api *)
+		 net_if_get_device(iface)->driver_api)->stats;
+	if (!stats) {
+		return;
+	}
+
+	stats->pkts.tx++;
+}
+
+static inline void eth_stats_update_broadcast_rx(struct net_if *iface)
+{
+	struct net_stats_eth *stats;
+
+	stats = ((const struct ethernet_api *)
+		 net_if_get_device(iface)->driver_api)->stats;
+	if (!stats) {
+		return;
+	}
+
+	stats->broadcast.rx++;
+}
+
+static inline void eth_stats_update_broadcast_tx(struct net_if *iface)
+{
+	struct net_stats_eth *stats;
+
+	stats = ((const struct ethernet_api *)
+		 net_if_get_device(iface)->driver_api)->stats;
+	if (!stats) {
+		return;
+	}
+
+	stats->broadcast.tx++;
+}
+
+static inline void eth_stats_update_multicast_rx(struct net_if *iface)
+{
+	struct net_stats_eth *stats;
+
+	stats = ((const struct ethernet_api *)
+		 net_if_get_device(iface)->driver_api)->stats;
+	if (!stats) {
+		return;
+	}
+
+	stats->multicast.rx++;
+}
+
+static inline void eth_stats_update_multicast_tx(struct net_if *iface)
+{
+	struct net_stats_eth *stats;
+
+	stats = ((const struct ethernet_api *)
+		 net_if_get_device(iface)->driver_api)->stats;
+	if (!stats) {
+		return;
+	}
+
+	stats->multicast.tx++;
+}
+
+#else /* CONFIG_NET_STATISTICS_ETHERNET */
+
+#define eth_stats_update_bytes_rx(iface, bytes)
+#define eth_stats_update_bytes_tx(iface, bytes)
+#define eth_stats_update_pkts_rx(iface)
+#define eth_stats_update_pkts_tx(iface)
+#define eth_stats_update_broadcast_rx(iface)
+#define eth_stats_update_broadcast_tx(iface)
+#define eth_stats_update_multicast_rx(iface)
+#define eth_stats_update_multicast_tx(iface)
+
+#endif /* CONFIG_NET_STATISTICS_ETHERNET */
+
+#endif /* __ETH_STATS_H__ */
diff --git a/subsys/net/ip/l2/ethernet/ethernet_stats.c b/subsys/net/ip/l2/ethernet/ethernet_stats.c
new file mode 100644
index 0000000..de475d5
--- /dev/null
+++ b/subsys/net/ip/l2/ethernet/ethernet_stats.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <kernel.h>
+#include <string.h>
+#include <errno.h>
+#include <net/net_core.h>
+#include <net/ethernet.h>
+
+#include "net_stats.h"
+
+#if defined(CONFIG_NET_STATISTICS_USER_API)
+
+static int eth_stats_get(u32_t mgmt_request, struct net_if *iface,
+			 void *data, size_t len)
+{
+	const struct ethernet_api *eth;
+	size_t len_chk = 0;
+	void *src = NULL;
+
+	switch (NET_MGMT_GET_COMMAND(mgmt_request)) {
+	case NET_REQUEST_STATS_CMD_GET_ETHERNET:
+		if (net_if_l2(iface) != &NET_L2_GET_NAME(ETHERNET)) {
+			return -ENOENT;
+		}
+
+		eth = net_if_get_device(iface)->driver_api;
+		len_chk = sizeof(struct net_stats_eth);
+		src = eth->stats;
+		break;
+	}
+
+	if (len != len_chk || !src) {
+		return -EINVAL;
+	}
+
+	memcpy(data, src, len);
+
+	return 0;
+}
+
+NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_STATS_GET_ETHERNET,
+				  eth_stats_get);
+
+#endif /* CONFIG_NET_STATISTICS_USER_API */