xref: /linux/net/ethtool/pause.c (revision 8a5f956a9fb7d74fff681145082acfad5afa6bb8)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 /* See Documentation/networking/flow_control.rst for a high level description of
4  * the userspace interface.
5  */
6 
7 #include "netlink.h"
8 #include "common.h"
9 
10 struct pause_req_info {
11 	struct ethnl_req_info		base;
12 	enum ethtool_mac_stats_src	src;
13 };
14 
15 #define PAUSE_REQINFO(__req_base) \
16 	container_of(__req_base, struct pause_req_info, base)
17 
18 struct pause_reply_data {
19 	struct ethnl_reply_data		base;
20 	struct ethtool_pauseparam	pauseparam;
21 	struct ethtool_pause_stats	pausestat;
22 };
23 
24 #define PAUSE_REPDATA(__reply_base) \
25 	container_of(__reply_base, struct pause_reply_data, base)
26 
27 const struct nla_policy ethnl_pause_get_policy[] = {
28 	[ETHTOOL_A_PAUSE_HEADER]		=
29 		NLA_POLICY_NESTED(ethnl_header_policy_stats),
30 	[ETHTOOL_A_PAUSE_STATS_SRC]		=
31 		NLA_POLICY_MAX(NLA_U32, ETHTOOL_MAC_STATS_SRC_PMAC),
32 };
33 
34 static int pause_parse_request(struct ethnl_req_info *req_base,
35 			       struct nlattr **tb,
36 			       struct netlink_ext_ack *extack)
37 {
38 	enum ethtool_mac_stats_src src = ETHTOOL_MAC_STATS_SRC_AGGREGATE;
39 	struct pause_req_info *req_info = PAUSE_REQINFO(req_base);
40 
41 	if (tb[ETHTOOL_A_PAUSE_STATS_SRC]) {
42 		if (!(req_base->flags & ETHTOOL_FLAG_STATS)) {
43 			NL_SET_ERR_MSG_MOD(extack,
44 					   "ETHTOOL_FLAG_STATS must be set when requesting a source of stats");
45 			return -EINVAL;
46 		}
47 
48 		src = nla_get_u32(tb[ETHTOOL_A_PAUSE_STATS_SRC]);
49 	}
50 
51 	req_info->src = src;
52 
53 	return 0;
54 }
55 
56 static int pause_prepare_data(const struct ethnl_req_info *req_base,
57 			      struct ethnl_reply_data *reply_base,
58 			      const struct genl_info *info)
59 {
60 	const struct pause_req_info *req_info = PAUSE_REQINFO(req_base);
61 	struct pause_reply_data *data = PAUSE_REPDATA(reply_base);
62 	enum ethtool_mac_stats_src src = req_info->src;
63 	struct net_device *dev = reply_base->dev;
64 	int ret;
65 
66 	if (!dev->ethtool_ops->get_pauseparam)
67 		return -EOPNOTSUPP;
68 
69 	ethtool_stats_init((u64 *)&data->pausestat,
70 			   sizeof(data->pausestat) / 8);
71 	data->pausestat.src = src;
72 
73 	ret = ethnl_ops_begin(dev);
74 	if (ret < 0)
75 		return ret;
76 
77 	if ((src == ETHTOOL_MAC_STATS_SRC_EMAC ||
78 	     src == ETHTOOL_MAC_STATS_SRC_PMAC) &&
79 	    !__ethtool_dev_mm_supported(dev)) {
80 		NL_SET_ERR_MSG_MOD(info->extack,
81 				   "Device does not support MAC merge layer");
82 		ethnl_ops_complete(dev);
83 		return -EOPNOTSUPP;
84 	}
85 
86 	dev->ethtool_ops->get_pauseparam(dev, &data->pauseparam);
87 	if (req_base->flags & ETHTOOL_FLAG_STATS &&
88 	    dev->ethtool_ops->get_pause_stats)
89 		dev->ethtool_ops->get_pause_stats(dev, &data->pausestat);
90 
91 	ethnl_ops_complete(dev);
92 
93 	return 0;
94 }
95 
96 static int pause_reply_size(const struct ethnl_req_info *req_base,
97 			    const struct ethnl_reply_data *reply_base)
98 {
99 	int n = nla_total_size(sizeof(u8)) +	/* _PAUSE_AUTONEG */
100 		nla_total_size(sizeof(u8)) +	/* _PAUSE_RX */
101 		nla_total_size(sizeof(u8));	/* _PAUSE_TX */
102 
103 	if (req_base->flags & ETHTOOL_FLAG_STATS)
104 		n += nla_total_size(0) +	/* _PAUSE_STATS */
105 		     nla_total_size(sizeof(u32)) + /* _PAUSE_STATS_SRC */
106 		     nla_total_size_64bit(sizeof(u64)) * ETHTOOL_PAUSE_STAT_CNT;
107 	return n;
108 }
109 
110 static int ethtool_put_stat(struct sk_buff *skb, u64 val, u16 attrtype,
111 			    u16 padtype)
112 {
113 	if (val == ETHTOOL_STAT_NOT_SET)
114 		return 0;
115 	if (nla_put_u64_64bit(skb, attrtype, val, padtype))
116 		return -EMSGSIZE;
117 
118 	return 0;
119 }
120 
121 static int pause_put_stats(struct sk_buff *skb,
122 			   const struct ethtool_pause_stats *pause_stats)
123 {
124 	const u16 pad = ETHTOOL_A_PAUSE_STAT_PAD;
125 	struct nlattr *nest;
126 
127 	if (nla_put_u32(skb, ETHTOOL_A_PAUSE_STATS_SRC, pause_stats->src))
128 		return -EMSGSIZE;
129 
130 	nest = nla_nest_start(skb, ETHTOOL_A_PAUSE_STATS);
131 	if (!nest)
132 		return -EMSGSIZE;
133 
134 	if (ethtool_put_stat(skb, pause_stats->tx_pause_frames,
135 			     ETHTOOL_A_PAUSE_STAT_TX_FRAMES, pad) ||
136 	    ethtool_put_stat(skb, pause_stats->rx_pause_frames,
137 			     ETHTOOL_A_PAUSE_STAT_RX_FRAMES, pad))
138 		goto err_cancel;
139 
140 	nla_nest_end(skb, nest);
141 	return 0;
142 
143 err_cancel:
144 	nla_nest_cancel(skb, nest);
145 	return -EMSGSIZE;
146 }
147 
148 static int pause_fill_reply(struct sk_buff *skb,
149 			    const struct ethnl_req_info *req_base,
150 			    const struct ethnl_reply_data *reply_base)
151 {
152 	const struct pause_reply_data *data = PAUSE_REPDATA(reply_base);
153 	const struct ethtool_pauseparam *pauseparam = &data->pauseparam;
154 
155 	if (nla_put_u8(skb, ETHTOOL_A_PAUSE_AUTONEG, !!pauseparam->autoneg) ||
156 	    nla_put_u8(skb, ETHTOOL_A_PAUSE_RX, !!pauseparam->rx_pause) ||
157 	    nla_put_u8(skb, ETHTOOL_A_PAUSE_TX, !!pauseparam->tx_pause))
158 		return -EMSGSIZE;
159 
160 	if (req_base->flags & ETHTOOL_FLAG_STATS &&
161 	    pause_put_stats(skb, &data->pausestat))
162 		return -EMSGSIZE;
163 
164 	return 0;
165 }
166 
167 /* PAUSE_SET */
168 
169 const struct nla_policy ethnl_pause_set_policy[] = {
170 	[ETHTOOL_A_PAUSE_HEADER]		=
171 		NLA_POLICY_NESTED(ethnl_header_policy),
172 	[ETHTOOL_A_PAUSE_AUTONEG]		= { .type = NLA_U8 },
173 	[ETHTOOL_A_PAUSE_RX]			= { .type = NLA_U8 },
174 	[ETHTOOL_A_PAUSE_TX]			= { .type = NLA_U8 },
175 	[ETHTOOL_A_PAUSE_STATS_SRC]		= { .type = NLA_REJECT },
176 };
177 
178 static int
179 ethnl_set_pause_validate(struct ethnl_req_info *req_info,
180 			 struct genl_info *info)
181 {
182 	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
183 
184 	return ops->get_pauseparam && ops->set_pauseparam ? 1 : -EOPNOTSUPP;
185 }
186 
187 static int
188 ethnl_set_pause(struct ethnl_req_info *req_info, struct genl_info *info)
189 {
190 	struct net_device *dev = req_info->dev;
191 	struct ethtool_pauseparam params = {};
192 	struct nlattr **tb = info->attrs;
193 	bool mod = false;
194 	int ret;
195 
196 	dev->ethtool_ops->get_pauseparam(dev, &params);
197 
198 	ethnl_update_bool32(&params.autoneg, tb[ETHTOOL_A_PAUSE_AUTONEG], &mod);
199 	ethnl_update_bool32(&params.rx_pause, tb[ETHTOOL_A_PAUSE_RX], &mod);
200 	ethnl_update_bool32(&params.tx_pause, tb[ETHTOOL_A_PAUSE_TX], &mod);
201 	if (!mod)
202 		return 0;
203 
204 	ret = dev->ethtool_ops->set_pauseparam(dev, &params);
205 	return ret < 0 ? ret : 1;
206 }
207 
208 const struct ethnl_request_ops ethnl_pause_request_ops = {
209 	.request_cmd		= ETHTOOL_MSG_PAUSE_GET,
210 	.reply_cmd		= ETHTOOL_MSG_PAUSE_GET_REPLY,
211 	.hdr_attr		= ETHTOOL_A_PAUSE_HEADER,
212 	.req_info_size		= sizeof(struct pause_req_info),
213 	.reply_data_size	= sizeof(struct pause_reply_data),
214 
215 	.parse_request		= pause_parse_request,
216 	.prepare_data		= pause_prepare_data,
217 	.reply_size		= pause_reply_size,
218 	.fill_reply		= pause_fill_reply,
219 
220 	.set_validate		= ethnl_set_pause_validate,
221 	.set			= ethnl_set_pause,
222 	.set_ntf_cmd		= ETHTOOL_MSG_PAUSE_NTF,
223 };
224