@@ -371,6 +371,44 @@ struct ethtool_module_eeprom {
__u8 *data;
};
+/**
+ * struct ethtool_rmon_hist_range - byte range for histogram statistics
+ * @low: low bound of the bucket (inclusive)
+ * @high: high bound of the bucket (inclusive)
+ */
+struct ethtool_rmon_hist_range {
+ u16 low;
+ u16 high;
+};
+
+#define ETHTOOL_RMON_HIST_MAX 10
+
+/**
+ * struct ethtool_rmon_stats - selected RMON (RFC 2819) statistics
+ * @undersize_pkts: Equivalent to `etherStatsUndersizePkts` from the RFC.
+ * @oversize_pkts: Equivalent to `etherStatsOversizePkts` from the RFC.
+ * @fragments: Equivalent to `etherStatsFragments` from the RFC.
+ * @jabbers: Equivalent to `etherStatsJabbers` from the RFC.
+ * @hist: Packet counter for packet length buckets (e.g.
+ * `etherStatsPkts128to255Octets` from the RFC).
+ * @hist_tx: Tx counters in similar form to @hist, not defined in the RFC.
+ *
+ * Selection of RMON (RFC 2819) statistics which are not exposed via different
+ * APIs, primarily the packet-length-based counters.
+ * Unfortunately different designs choose different buckets beyond
+ * the 1024B mark (jumbo frame teritory), so the definition of the bucket
+ * ranges is left to the driver.
+ */
+struct ethtool_rmon_stats {
+ u64 undersize_pkts;
+ u64 oversize_pkts;
+ u64 fragments;
+ u64 jabbers;
+
+ u64 hist[ETHTOOL_RMON_HIST_MAX];
+ u64 hist_tx[ETHTOOL_RMON_HIST_MAX];
+};
+
/**
* struct ethtool_ops - optional netdev operations
* @cap_link_lanes_supported: indicates if the driver supports lanes
@@ -533,6 +571,8 @@ struct ethtool_module_eeprom {
* read.
* @get_eth_phy_stats: Query some of the IEEE 802.3 PHY statistics.
* @get_eth_mac_stats: Query some of the IEEE 802.3 MAC statistics.
+ * @get_rmon_stats: Query some of the RMON (RFC 2819) statistics.
+ * Set %ranges to a pointer to zero-terminated array of byte ranges.
*
* All operations are optional (i.e. the function pointer may be set
* to %NULL) and callers must take this into account. Callers must
@@ -649,6 +689,9 @@ struct ethtool_ops {
struct ethtool_eth_mac_stats *mac_stats);
void (*get_eth_ctrl_stats)(struct net_device *dev,
struct ethtool_eth_ctrl_stats *ctrl_stats);
+ void (*get_rmon_stats)(struct net_device *dev,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges);
};
int ethtool_check_ops(const struct ethtool_ops *ops);
@@ -673,6 +673,7 @@ enum ethtool_link_ext_substate_cable_issue {
* @ETH_SS_STATS_ETH_PHY: names of IEEE 802.3 PHY statistics
* @ETH_SS_STATS_ETH_MAC: names of IEEE 802.3 MAC statistics
* @ETH_SS_STATS_ETH_CTRL: names of IEEE 802.3 MAC Control statistics
+ * @ETH_SS_STATS_RMON: names of RMON statistics
*
* @ETH_SS_COUNT: number of defined string sets
*/
@@ -697,6 +698,7 @@ enum ethtool_stringset {
ETH_SS_STATS_ETH_PHY,
ETH_SS_STATS_ETH_MAC,
ETH_SS_STATS_ETH_CTRL,
+ ETH_SS_STATS_RMON,
/* add new constants above here */
ETH_SS_COUNT
@@ -700,6 +700,7 @@ enum {
ETHTOOL_STATS_ETH_PHY,
ETHTOOL_STATS_ETH_MAC,
ETHTOOL_STATS_ETH_CTRL,
+ ETHTOOL_STATS_RMON,
/* add new constants above here */
__ETHTOOL_STATS_CNT
@@ -790,6 +791,28 @@ enum {
ETHTOOL_A_STATS_ETH_CTRL_MAX = (__ETHTOOL_A_STATS_ETH_CTRL_CNT - 1)
};
+enum {
+ ETHTOOL_A_STATS_RMON_HIST = ETHTOOL_A_STATS_GRP_FIRST_ATTR,
+ ETHTOOL_A_STATS_RMON_HIST_BKT_LOW, /* u32 */
+ ETHTOOL_A_STATS_RMON_HIST_BKT_HI, /* u32 */
+ ETHTOOL_A_STATS_RMON_HIST_VAL,
+
+ ETHTOOL_A_STATS_RMON_HIST_TX,
+
+ /* etherStatsUndersizePkts */
+ ETHTOOL_A_STATS_RMON_UNDERSIZE,
+ /* etherStatsOversizePkts */
+ ETHTOOL_A_STATS_RMON_OVERSIZE,
+ /* etherStatsFragments */
+ ETHTOOL_A_STATS_RMON_FRAG,
+ /* etherStatsJabbers */
+ ETHTOOL_A_STATS_RMON_JABBER,
+
+ /* add new constants above here */
+ __ETHTOOL_A_STATS_RMON_CNT,
+ ETHTOOL_A_STATS_RMON_MAX = (__ETHTOOL_A_STATS_RMON_CNT - 1)
+};
+
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1
@@ -405,5 +405,6 @@ extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];
extern const char stats_eth_mac_names[__ETHTOOL_A_STATS_ETH_MAC_CNT][ETH_GSTRING_LEN];
extern const char stats_eth_ctrl_names[__ETHTOOL_A_STATS_ETH_CTRL_CNT][ETH_GSTRING_LEN];
+extern const char stats_rmon_names[__ETHTOOL_A_STATS_RMON_CNT][ETH_GSTRING_LEN];
#endif /* _NET_ETHTOOL_NETLINK_H */
@@ -17,6 +17,8 @@ struct stats_reply_data {
struct ethtool_eth_phy_stats phy_stats;
struct ethtool_eth_mac_stats mac_stats;
struct ethtool_eth_ctrl_stats ctrl_stats;
+ struct ethtool_rmon_stats rmon_stats;
+ const struct ethtool_rmon_hist_range *rmon_ranges;
};
#define STATS_REPDATA(__reply_base) \
@@ -26,6 +28,7 @@ const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN] = {
[ETHTOOL_STATS_ETH_PHY] = "eth-phy",
[ETHTOOL_STATS_ETH_MAC] = "eth-mac",
[ETHTOOL_STATS_ETH_CTRL] = "eth-ctrl",
+ [ETHTOOL_STATS_RMON] = "rmon",
};
const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN] = {
@@ -63,6 +66,13 @@ const char stats_eth_ctrl_names[__ETHTOOL_A_STATS_ETH_CTRL_CNT][ETH_GSTRING_LEN]
[ETHTOOL_A_STATS_ETH_CTRL_5_RX_UNSUP] = "UnsupportedOpcodesReceived",
};
+const char stats_rmon_names[__ETHTOOL_A_STATS_RMON_CNT][ETH_GSTRING_LEN] = {
+ [ETHTOOL_A_STATS_RMON_UNDERSIZE] = "etherStatsUndersizePkts",
+ [ETHTOOL_A_STATS_RMON_OVERSIZE] = "etherStatsOversizePkts",
+ [ETHTOOL_A_STATS_RMON_FRAG] = "etherStatsFragments",
+ [ETHTOOL_A_STATS_RMON_JABBER] = "etherStatsJabbers",
+};
+
const struct nla_policy ethnl_stats_get_policy[ETHTOOL_A_STATS_GROUPS + 1] = {
[ETHTOOL_A_STATS_HEADER] =
NLA_POLICY_NESTED(ethnl_header_policy),
@@ -107,6 +117,7 @@ static int stats_prepare_data(const struct ethnl_req_info *req_base,
memset(&data->phy_stats, 0xff, sizeof(data->phy_stats));
memset(&data->mac_stats, 0xff, sizeof(data->mac_stats));
memset(&data->ctrl_stats, 0xff, sizeof(data->mac_stats));
+ memset(&data->rmon_stats, 0xff, sizeof(data->rmon_stats));
if (test_bit(ETHTOOL_STATS_ETH_PHY, req_info->stat_mask) &&
dev->ethtool_ops->get_eth_phy_stats)
@@ -117,6 +128,10 @@ static int stats_prepare_data(const struct ethnl_req_info *req_base,
if (test_bit(ETHTOOL_STATS_ETH_CTRL, req_info->stat_mask) &&
dev->ethtool_ops->get_eth_ctrl_stats)
dev->ethtool_ops->get_eth_ctrl_stats(dev, &data->ctrl_stats);
+ if (test_bit(ETHTOOL_STATS_RMON, req_info->stat_mask) &&
+ dev->ethtool_ops->get_rmon_stats)
+ dev->ethtool_ops->get_rmon_stats(dev, &data->rmon_stats,
+ &data->rmon_ranges);
ethnl_ops_complete(dev);
return 0;
@@ -140,6 +155,17 @@ static int stats_reply_size(const struct ethnl_req_info *req_base,
len += nla_total_size(0) +
sizeof(struct ethtool_eth_ctrl_stats) / sizeof(u64) *
nla_total_size_64bit(sizeof(u64));
+ if (test_bit(ETHTOOL_STATS_RMON, req_info->stat_mask)) {
+ len += nla_total_size(0) +
+ sizeof(struct ethtool_rmon_stats) / sizeof(u64) *
+ nla_total_size_64bit(sizeof(u64));
+ /* Above includes the space for _A_STATS_RMON_HIST_VALs */
+
+ len += (nla_total_size(0) + /* _A_STATS_RMON_HIST */
+ nla_total_size(4) + /* _A_STATS_RMON_HIST_BKT_LOW */
+ nla_total_size(4)) * /* _A_STATS_RMON_HIST_BKT_HI */
+ ETHTOOL_RMON_HIST_MAX * 2;
+ }
return len;
}
@@ -224,6 +250,59 @@ static int stats_put_ctrl_stats(struct sk_buff *skb,
return 0;
}
+static int stats_put_rmon_hist(struct sk_buff *skb, u32 attr, const u64 *hist,
+ const struct ethtool_rmon_hist_range *ranges)
+{
+ struct nlattr *nest;
+ int i;
+
+ for (i = 0; i < ETHTOOL_RMON_HIST_MAX; i++) {
+ if (!ranges[i].low && !ranges[i].high)
+ break;
+ if (hist[i] == ETHTOOL_STAT_NOT_SET)
+ continue;
+
+ nest = nla_nest_start(skb, attr);
+
+ if (nla_put_u32(skb, ETHTOOL_A_STATS_RMON_HIST_BKT_LOW,
+ ranges[i].low) ||
+ nla_put_u32(skb, ETHTOOL_A_STATS_RMON_HIST_BKT_HI,
+ ranges[i].high) ||
+ stat_put(skb, ETHTOOL_A_STATS_RMON_HIST_VAL, hist[i]))
+ goto err_cancel_hist;
+
+ nla_nest_end(skb, nest);
+ }
+
+ return 0;
+
+err_cancel_hist:
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+}
+
+static int stats_put_rmon_stats(struct sk_buff *skb,
+ const struct stats_reply_data *data)
+{
+ if (stats_put_rmon_hist(skb, ETHTOOL_A_STATS_RMON_HIST,
+ data->rmon_stats.hist, data->rmon_ranges) ||
+ stats_put_rmon_hist(skb, ETHTOOL_A_STATS_RMON_HIST_TX,
+ data->rmon_stats.hist_tx, data->rmon_ranges))
+ return -EMSGSIZE;
+
+ if (stat_put(skb, ETHTOOL_A_STATS_RMON_UNDERSIZE,
+ data->rmon_stats.undersize_pkts) ||
+ stat_put(skb, ETHTOOL_A_STATS_RMON_OVERSIZE,
+ data->rmon_stats.oversize_pkts) ||
+ stat_put(skb, ETHTOOL_A_STATS_RMON_FRAG,
+ data->rmon_stats.fragments) ||
+ stat_put(skb, ETHTOOL_A_STATS_RMON_JABBER,
+ data->rmon_stats.jabbers))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
static int stats_put_stats(struct sk_buff *skb,
const struct stats_reply_data *data,
u32 id, u32 ss_id,
@@ -271,6 +350,9 @@ static int stats_fill_reply(struct sk_buff *skb,
ret = stats_put_stats(skb, data, ETHTOOL_STATS_ETH_CTRL,
ETH_SS_STATS_ETH_CTRL,
stats_put_ctrl_stats);
+ if (!ret && test_bit(ETHTOOL_STATS_RMON, req_info->stat_mask))
+ ret = stats_put_stats(skb, data, ETHTOOL_STATS_RMON,
+ ETH_SS_STATS_RMON, stats_put_rmon_stats);
return ret;
}
@@ -100,6 +100,11 @@ static const struct strset_info info_template[] = {
.count = __ETHTOOL_A_STATS_ETH_CTRL_CNT,
.strings = stats_eth_ctrl_names,
},
+ [ETH_SS_STATS_RMON] = {
+ .per_dev = false,
+ .count = __ETHTOOL_A_STATS_RMON_CNT,
+ .strings = stats_rmon_names,
+ },
};
struct strset_req_info {
Most devices maintain RMON (RFC 2819) stats - particularly the "histogram" of packets received by size. Unlike other RFCs which duplicate IEEE stats, the short/oversized frame counters in RMON don't seem to match IEEE stats 1-to-1 either, so expose those, too. Do not expose basic packet, CRC errors etc - those are already otherwise covered. Because standard defines packet ranges only up to 1518, and everything above that should theoretically be "oversized" - devices often create their own ranges. Going beyond what the RFC defines - expose the "histogram" in the Tx direction (assume for now that the ranges will be the same). Signed-off-by: Jakub Kicinski <kuba@kernel.org> --- include/linux/ethtool.h | 43 +++++++++++++++ include/uapi/linux/ethtool.h | 2 + include/uapi/linux/ethtool_netlink.h | 23 ++++++++ net/ethtool/netlink.h | 1 + net/ethtool/stats.c | 82 ++++++++++++++++++++++++++++ net/ethtool/strset.c | 5 ++ 6 files changed, 156 insertions(+)