xref: /openbmc/linux/net/ethtool/stats.c (revision f09ea6fb)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #include "netlink.h"
4 #include "common.h"
5 #include "bitset.h"
6 
7 struct stats_req_info {
8 	struct ethnl_req_info		base;
9 	DECLARE_BITMAP(stat_mask, __ETHTOOL_STATS_CNT);
10 };
11 
12 #define STATS_REQINFO(__req_base) \
13 	container_of(__req_base, struct stats_req_info, base)
14 
15 struct stats_reply_data {
16 	struct ethnl_reply_data		base;
17 	struct ethtool_eth_phy_stats	phy_stats;
18 };
19 
20 #define STATS_REPDATA(__reply_base) \
21 	container_of(__reply_base, struct stats_reply_data, base)
22 
23 const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN] = {
24 	[ETHTOOL_STATS_ETH_PHY]			= "eth-phy",
25 };
26 
27 const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN] = {
28 	[ETHTOOL_A_STATS_ETH_PHY_5_SYM_ERR]	= "SymbolErrorDuringCarrier",
29 };
30 
31 const struct nla_policy ethnl_stats_get_policy[ETHTOOL_A_STATS_GROUPS + 1] = {
32 	[ETHTOOL_A_STATS_HEADER]	=
33 		NLA_POLICY_NESTED(ethnl_header_policy),
34 	[ETHTOOL_A_STATS_GROUPS]	= { .type = NLA_NESTED },
35 };
36 
37 static int stats_parse_request(struct ethnl_req_info *req_base,
38 			       struct nlattr **tb,
39 			       struct netlink_ext_ack *extack)
40 {
41 	struct stats_req_info *req_info = STATS_REQINFO(req_base);
42 	bool mod = false;
43 	int err;
44 
45 	err = ethnl_update_bitset(req_info->stat_mask, __ETHTOOL_STATS_CNT,
46 				  tb[ETHTOOL_A_STATS_GROUPS], stats_std_names,
47 				  extack, &mod);
48 	if (err)
49 		return err;
50 
51 	if (!mod) {
52 		NL_SET_ERR_MSG(extack, "no stats requested");
53 		return -EINVAL;
54 	}
55 
56 	return 0;
57 }
58 
59 static int stats_prepare_data(const struct ethnl_req_info *req_base,
60 			      struct ethnl_reply_data *reply_base,
61 			      struct genl_info *info)
62 {
63 	const struct stats_req_info *req_info = STATS_REQINFO(req_base);
64 	struct stats_reply_data *data = STATS_REPDATA(reply_base);
65 	struct net_device *dev = reply_base->dev;
66 	int ret;
67 
68 	ret = ethnl_ops_begin(dev);
69 	if (ret < 0)
70 		return ret;
71 
72 	memset(&data->phy_stats, 0xff, sizeof(data->phy_stats));
73 
74 	if (test_bit(ETHTOOL_STATS_ETH_PHY, req_info->stat_mask) &&
75 	    dev->ethtool_ops->get_eth_phy_stats)
76 		dev->ethtool_ops->get_eth_phy_stats(dev, &data->phy_stats);
77 
78 	ethnl_ops_complete(dev);
79 	return 0;
80 }
81 
82 static int stats_reply_size(const struct ethnl_req_info *req_base,
83 			    const struct ethnl_reply_data *reply_base)
84 {
85 	const struct stats_req_info *req_info = STATS_REQINFO(req_base);
86 	unsigned int n_grps = 0, n_stats = 0;
87 	int len = 0;
88 
89 	if (test_bit(ETHTOOL_STATS_ETH_PHY, req_info->stat_mask)) {
90 		n_stats += sizeof(struct ethtool_eth_phy_stats) / sizeof(u64);
91 		n_grps++;
92 	}
93 
94 	len += n_grps * (nla_total_size(0) + /* _A_STATS_GRP */
95 			 nla_total_size(4) + /* _A_STATS_GRP_ID */
96 			 nla_total_size(4)); /* _A_STATS_GRP_SS_ID */
97 	len += n_stats * (nla_total_size(0) + /* _A_STATS_GRP_STAT */
98 			  nla_total_size_64bit(sizeof(u64)));
99 
100 	return len;
101 }
102 
103 static int stat_put(struct sk_buff *skb, u16 attrtype, u64 val)
104 {
105 	struct nlattr *nest;
106 	int ret;
107 
108 	if (val == ETHTOOL_STAT_NOT_SET)
109 		return 0;
110 
111 	/* We want to start stats attr types from 0, so we don't have a type
112 	 * for pad inside ETHTOOL_A_STATS_GRP_STAT. Pad things on the outside
113 	 * of ETHTOOL_A_STATS_GRP_STAT. Since we're one nest away from the
114 	 * actual attr we're 4B off - nla_need_padding_for_64bit() & co.
115 	 * can't be used.
116 	 */
117 #ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
118 	if (!IS_ALIGNED((unsigned long)skb_tail_pointer(skb), 8))
119 		if (!nla_reserve(skb, ETHTOOL_A_STATS_GRP_PAD, 0))
120 			return -EMSGSIZE;
121 #endif
122 
123 	nest = nla_nest_start(skb, ETHTOOL_A_STATS_GRP_STAT);
124 	if (!nest)
125 		return -EMSGSIZE;
126 
127 	ret = nla_put_u64_64bit(skb, attrtype, val, -1 /* not used */);
128 	if (ret) {
129 		nla_nest_cancel(skb, nest);
130 		return ret;
131 	}
132 
133 	nla_nest_end(skb, nest);
134 	return 0;
135 }
136 
137 static int stats_put_phy_stats(struct sk_buff *skb,
138 			       const struct stats_reply_data *data)
139 {
140 	if (stat_put(skb, ETHTOOL_A_STATS_ETH_PHY_5_SYM_ERR,
141 		     data->phy_stats.SymbolErrorDuringCarrier))
142 		return -EMSGSIZE;
143 	return 0;
144 }
145 
146 static int stats_put_stats(struct sk_buff *skb,
147 			   const struct stats_reply_data *data,
148 			   u32 id, u32 ss_id,
149 			   int (*cb)(struct sk_buff *skb,
150 				     const struct stats_reply_data *data))
151 {
152 	struct nlattr *nest;
153 
154 	nest = nla_nest_start(skb, ETHTOOL_A_STATS_GRP);
155 	if (!nest)
156 		return -EMSGSIZE;
157 
158 	if (nla_put_u32(skb, ETHTOOL_A_STATS_GRP_ID, id) ||
159 	    nla_put_u32(skb, ETHTOOL_A_STATS_GRP_SS_ID, ss_id))
160 		goto err_cancel;
161 
162 	if (cb(skb, data))
163 		goto err_cancel;
164 
165 	nla_nest_end(skb, nest);
166 	return 0;
167 
168 err_cancel:
169 	nla_nest_cancel(skb, nest);
170 	return -EMSGSIZE;
171 }
172 
173 static int stats_fill_reply(struct sk_buff *skb,
174 			    const struct ethnl_req_info *req_base,
175 			    const struct ethnl_reply_data *reply_base)
176 {
177 	const struct stats_req_info *req_info = STATS_REQINFO(req_base);
178 	const struct stats_reply_data *data = STATS_REPDATA(reply_base);
179 	int ret = 0;
180 
181 	if (!ret && test_bit(ETHTOOL_STATS_ETH_PHY, req_info->stat_mask))
182 		ret = stats_put_stats(skb, data, ETHTOOL_STATS_ETH_PHY,
183 				      ETH_SS_STATS_ETH_PHY,
184 				      stats_put_phy_stats);
185 
186 	return ret;
187 }
188 
189 const struct ethnl_request_ops ethnl_stats_request_ops = {
190 	.request_cmd		= ETHTOOL_MSG_STATS_GET,
191 	.reply_cmd		= ETHTOOL_MSG_STATS_GET_REPLY,
192 	.hdr_attr		= ETHTOOL_A_STATS_HEADER,
193 	.req_info_size		= sizeof(struct stats_req_info),
194 	.reply_data_size	= sizeof(struct stats_reply_data),
195 
196 	.parse_request		= stats_parse_request,
197 	.prepare_data		= stats_prepare_data,
198 	.reply_size		= stats_reply_size,
199 	.fill_reply		= stats_fill_reply,
200 };
201