Commit a8b06e9d authored by Jakub Kicinski's avatar Jakub Kicinski Committed by David S. Miller
Browse files

ethtool: add interface to read RMON stats



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: default avatarJakub Kicinski <kuba@kernel.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent bfad2b97
Loading
Loading
Loading
Loading
+43 −0
Original line number Diff line number Diff line
@@ -346,6 +346,44 @@ struct ethtool_fec_stats {
	} corrected_blocks, uncorrectable_blocks, corrected_bits;
};

/**
 * 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];
};

#define ETH_MODULE_EEPROM_PAGE_LEN	128
#define ETH_MODULE_MAX_I2C_ADDRESS	0x7f

@@ -534,6 +572,8 @@ struct ethtool_module_eeprom {
 * @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_eth_ctrl_stats: Query some of the IEEE 802.3 MAC Ctrl 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
@@ -650,6 +690,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);
+2 −0
Original line number Diff line number Diff line
@@ -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
+23 −0
Original line number Diff line number Diff line
@@ -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
@@ -714,6 +715,13 @@ enum {

	ETHTOOL_A_STATS_GRP_STAT,		/* nest */

	ETHTOOL_A_STATS_GRP_HIST_RX,		/* nest */
	ETHTOOL_A_STATS_GRP_HIST_TX,		/* nest */

	ETHTOOL_A_STATS_GRP_HIST_BKT_LOW,	/* u32 */
	ETHTOOL_A_STATS_GRP_HIST_BKT_HI,	/* u32 */
	ETHTOOL_A_STATS_GRP_HIST_VAL,		/* u64 */

	/* add new constants above here */
	__ETHTOOL_A_STATS_GRP_CNT,
	ETHTOOL_A_STATS_GRP_MAX = (__ETHTOOL_A_STATS_CNT - 1)
@@ -793,6 +801,21 @@ enum {
	ETHTOOL_A_STATS_ETH_CTRL_MAX = (__ETHTOOL_A_STATS_ETH_CTRL_CNT - 1)
};

enum {
	/* 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
+1 −0
Original line number Diff line number Diff line
@@ -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 */
+87 −0
Original line number Diff line number Diff line
@@ -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;
@@ -141,6 +156,16 @@ static int stats_reply_size(const struct ethnl_req_info *req_base,
		n_stats += sizeof(struct ethtool_eth_ctrl_stats) / sizeof(u64);
		n_grps++;
	}
	if (test_bit(ETHTOOL_STATS_RMON, req_info->stat_mask)) {
		n_stats += sizeof(struct ethtool_rmon_stats) / sizeof(u64);
		n_grps++;
		/* Above includes the space for _A_STATS_GRP_HIST_VALs */

		len += (nla_total_size(0) +	/* _A_STATS_GRP_HIST */
			nla_total_size(4) +	/* _A_STATS_GRP_HIST_BKT_LOW */
			nla_total_size(4)) *	/* _A_STATS_GRP_HIST_BKT_HI */
			ETHTOOL_RMON_HIST_MAX * 2;
	}

	len += n_grps * (nla_total_size(0) + /* _A_STATS_GRP */
			 nla_total_size(4) + /* _A_STATS_GRP_ID */
@@ -258,6 +283,65 @@ 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;

	if (!ranges)
		return 0;

	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 (!nest)
			return -EMSGSIZE;

		if (nla_put_u32(skb, ETHTOOL_A_STATS_GRP_HIST_BKT_LOW,
				ranges[i].low) ||
		    nla_put_u32(skb, ETHTOOL_A_STATS_GRP_HIST_BKT_HI,
				ranges[i].high) ||
		    nla_put_u64_64bit(skb, ETHTOOL_A_STATS_GRP_HIST_VAL,
				      hist[i], ETHTOOL_A_STATS_GRP_PAD))
			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_GRP_HIST_RX,
				data->rmon_stats.hist, data->rmon_ranges) ||
	    stats_put_rmon_hist(skb, ETHTOOL_A_STATS_GRP_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,
@@ -305,6 +389,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;
}
Loading