1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #include <linux/dim.h>
4 #include "netlink.h"
5 #include "common.h"
6 
7 struct coalesce_req_info {
8 	struct ethnl_req_info		base;
9 };
10 
11 struct coalesce_reply_data {
12 	struct ethnl_reply_data		base;
13 	struct ethtool_coalesce		coalesce;
14 	struct kernel_ethtool_coalesce	kernel_coalesce;
15 	u32				supported_params;
16 };
17 
18 #define COALESCE_REPDATA(__reply_base) \
19 	container_of(__reply_base, struct coalesce_reply_data, base)
20 
21 #define __SUPPORTED_OFFSET ETHTOOL_A_COALESCE_RX_USECS
attr_to_mask(unsigned int attr_type)22 static u32 attr_to_mask(unsigned int attr_type)
23 {
24 	return BIT(attr_type - __SUPPORTED_OFFSET);
25 }
26 
27 /* build time check that indices in ethtool_ops::supported_coalesce_params
28  * match corresponding attribute types with an offset
29  */
30 #define __CHECK_SUPPORTED_OFFSET(x) \
31 	static_assert((ETHTOOL_ ## x) == \
32 		      BIT((ETHTOOL_A_ ## x) - __SUPPORTED_OFFSET))
33 __CHECK_SUPPORTED_OFFSET(COALESCE_RX_USECS);
34 __CHECK_SUPPORTED_OFFSET(COALESCE_RX_MAX_FRAMES);
35 __CHECK_SUPPORTED_OFFSET(COALESCE_RX_USECS_IRQ);
36 __CHECK_SUPPORTED_OFFSET(COALESCE_RX_MAX_FRAMES_IRQ);
37 __CHECK_SUPPORTED_OFFSET(COALESCE_TX_USECS);
38 __CHECK_SUPPORTED_OFFSET(COALESCE_TX_MAX_FRAMES);
39 __CHECK_SUPPORTED_OFFSET(COALESCE_TX_USECS_IRQ);
40 __CHECK_SUPPORTED_OFFSET(COALESCE_TX_MAX_FRAMES_IRQ);
41 __CHECK_SUPPORTED_OFFSET(COALESCE_STATS_BLOCK_USECS);
42 __CHECK_SUPPORTED_OFFSET(COALESCE_USE_ADAPTIVE_RX);
43 __CHECK_SUPPORTED_OFFSET(COALESCE_USE_ADAPTIVE_TX);
44 __CHECK_SUPPORTED_OFFSET(COALESCE_PKT_RATE_LOW);
45 __CHECK_SUPPORTED_OFFSET(COALESCE_RX_USECS_LOW);
46 __CHECK_SUPPORTED_OFFSET(COALESCE_RX_MAX_FRAMES_LOW);
47 __CHECK_SUPPORTED_OFFSET(COALESCE_TX_USECS_LOW);
48 __CHECK_SUPPORTED_OFFSET(COALESCE_TX_MAX_FRAMES_LOW);
49 __CHECK_SUPPORTED_OFFSET(COALESCE_PKT_RATE_HIGH);
50 __CHECK_SUPPORTED_OFFSET(COALESCE_RX_USECS_HIGH);
51 __CHECK_SUPPORTED_OFFSET(COALESCE_RX_MAX_FRAMES_HIGH);
52 __CHECK_SUPPORTED_OFFSET(COALESCE_TX_USECS_HIGH);
53 __CHECK_SUPPORTED_OFFSET(COALESCE_TX_MAX_FRAMES_HIGH);
54 __CHECK_SUPPORTED_OFFSET(COALESCE_RATE_SAMPLE_INTERVAL);
55 
56 const struct nla_policy ethnl_coalesce_get_policy[] = {
57 	[ETHTOOL_A_COALESCE_HEADER]		=
58 		NLA_POLICY_NESTED(ethnl_header_policy),
59 };
60 
coalesce_prepare_data(const struct ethnl_req_info * req_base,struct ethnl_reply_data * reply_base,const struct genl_info * info)61 static int coalesce_prepare_data(const struct ethnl_req_info *req_base,
62 				 struct ethnl_reply_data *reply_base,
63 				 const struct genl_info *info)
64 {
65 	struct coalesce_reply_data *data = COALESCE_REPDATA(reply_base);
66 	struct net_device *dev = reply_base->dev;
67 	int ret;
68 
69 	if (!dev->ethtool_ops->get_coalesce)
70 		return -EOPNOTSUPP;
71 	data->supported_params = dev->ethtool_ops->supported_coalesce_params;
72 	ret = ethnl_ops_begin(dev);
73 	if (ret < 0)
74 		return ret;
75 	ret = dev->ethtool_ops->get_coalesce(dev, &data->coalesce,
76 					     &data->kernel_coalesce,
77 					     info->extack);
78 	ethnl_ops_complete(dev);
79 
80 	return ret;
81 }
82 
coalesce_reply_size(const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)83 static int coalesce_reply_size(const struct ethnl_req_info *req_base,
84 			       const struct ethnl_reply_data *reply_base)
85 {
86 	int modersz = nla_total_size(0) + /* _PROFILE_IRQ_MODERATION, nest */
87 		      nla_total_size(sizeof(u32)) + /* _IRQ_MODERATION_USEC */
88 		      nla_total_size(sizeof(u32)) + /* _IRQ_MODERATION_PKTS */
89 		      nla_total_size(sizeof(u32));  /* _IRQ_MODERATION_COMPS */
90 
91 	int total_modersz = nla_total_size(0) +  /* _{R,T}X_PROFILE, nest */
92 			modersz * NET_DIM_PARAMS_NUM_PROFILES;
93 
94 	return nla_total_size(sizeof(u32)) +	/* _RX_USECS */
95 	       nla_total_size(sizeof(u32)) +	/* _RX_MAX_FRAMES */
96 	       nla_total_size(sizeof(u32)) +	/* _RX_USECS_IRQ */
97 	       nla_total_size(sizeof(u32)) +	/* _RX_MAX_FRAMES_IRQ */
98 	       nla_total_size(sizeof(u32)) +	/* _TX_USECS */
99 	       nla_total_size(sizeof(u32)) +	/* _TX_MAX_FRAMES */
100 	       nla_total_size(sizeof(u32)) +	/* _TX_USECS_IRQ */
101 	       nla_total_size(sizeof(u32)) +	/* _TX_MAX_FRAMES_IRQ */
102 	       nla_total_size(sizeof(u32)) +	/* _STATS_BLOCK_USECS */
103 	       nla_total_size(sizeof(u8)) +	/* _USE_ADAPTIVE_RX */
104 	       nla_total_size(sizeof(u8)) +	/* _USE_ADAPTIVE_TX */
105 	       nla_total_size(sizeof(u32)) +	/* _PKT_RATE_LOW */
106 	       nla_total_size(sizeof(u32)) +	/* _RX_USECS_LOW */
107 	       nla_total_size(sizeof(u32)) +	/* _RX_MAX_FRAMES_LOW */
108 	       nla_total_size(sizeof(u32)) +	/* _TX_USECS_LOW */
109 	       nla_total_size(sizeof(u32)) +	/* _TX_MAX_FRAMES_LOW */
110 	       nla_total_size(sizeof(u32)) +	/* _PKT_RATE_HIGH */
111 	       nla_total_size(sizeof(u32)) +	/* _RX_USECS_HIGH */
112 	       nla_total_size(sizeof(u32)) +	/* _RX_MAX_FRAMES_HIGH */
113 	       nla_total_size(sizeof(u32)) +	/* _TX_USECS_HIGH */
114 	       nla_total_size(sizeof(u32)) +	/* _TX_MAX_FRAMES_HIGH */
115 	       nla_total_size(sizeof(u32)) +	/* _RATE_SAMPLE_INTERVAL */
116 	       nla_total_size(sizeof(u8)) +	/* _USE_CQE_MODE_TX */
117 	       nla_total_size(sizeof(u8)) +	/* _USE_CQE_MODE_RX */
118 	       nla_total_size(sizeof(u32)) +	/* _TX_AGGR_MAX_BYTES */
119 	       nla_total_size(sizeof(u32)) +	/* _TX_AGGR_MAX_FRAMES */
120 	       nla_total_size(sizeof(u32)) +	/* _TX_AGGR_TIME_USECS */
121 	       total_modersz * 2;		/* _{R,T}X_PROFILE */
122 }
123 
coalesce_put_u32(struct sk_buff * skb,u16 attr_type,u32 val,u32 supported_params)124 static bool coalesce_put_u32(struct sk_buff *skb, u16 attr_type, u32 val,
125 			     u32 supported_params)
126 {
127 	if (!val && !(supported_params & attr_to_mask(attr_type)))
128 		return false;
129 	return nla_put_u32(skb, attr_type, val);
130 }
131 
coalesce_put_bool(struct sk_buff * skb,u16 attr_type,u32 val,u32 supported_params)132 static bool coalesce_put_bool(struct sk_buff *skb, u16 attr_type, u32 val,
133 			      u32 supported_params)
134 {
135 	if (!val && !(supported_params & attr_to_mask(attr_type)))
136 		return false;
137 	return nla_put_u8(skb, attr_type, !!val);
138 }
139 
140 /**
141  * coalesce_put_profile - fill reply with a nla nest with four child nla nests.
142  * @skb: socket buffer the message is stored in
143  * @attr_type: nest attr type ETHTOOL_A_COALESCE_*X_PROFILE
144  * @profile: data passed to userspace
145  * @coal_flags: modifiable parameters supported by the driver
146  *
147  * Put a dim profile nest attribute. Refer to ETHTOOL_A_PROFILE_IRQ_MODERATION.
148  *
149  * Return: 0 on success or a negative error code.
150  */
coalesce_put_profile(struct sk_buff * skb,u16 attr_type,const struct dim_cq_moder * profile,u8 coal_flags)151 static int coalesce_put_profile(struct sk_buff *skb, u16 attr_type,
152 				const struct dim_cq_moder *profile,
153 				u8 coal_flags)
154 {
155 	struct nlattr *profile_attr, *moder_attr;
156 	int i, ret;
157 
158 	if (!profile || !coal_flags)
159 		return 0;
160 
161 	profile_attr = nla_nest_start(skb, attr_type);
162 	if (!profile_attr)
163 		return -EMSGSIZE;
164 
165 	for (i = 0; i < NET_DIM_PARAMS_NUM_PROFILES; i++) {
166 		moder_attr = nla_nest_start(skb,
167 					    ETHTOOL_A_PROFILE_IRQ_MODERATION);
168 		if (!moder_attr) {
169 			ret = -EMSGSIZE;
170 			goto cancel_profile;
171 		}
172 
173 		if (coal_flags & DIM_COALESCE_USEC) {
174 			ret = nla_put_u32(skb, ETHTOOL_A_IRQ_MODERATION_USEC,
175 					  profile[i].usec);
176 			if (ret)
177 				goto cancel_moder;
178 		}
179 
180 		if (coal_flags & DIM_COALESCE_PKTS) {
181 			ret = nla_put_u32(skb, ETHTOOL_A_IRQ_MODERATION_PKTS,
182 					  profile[i].pkts);
183 			if (ret)
184 				goto cancel_moder;
185 		}
186 
187 		if (coal_flags & DIM_COALESCE_COMPS) {
188 			ret = nla_put_u32(skb, ETHTOOL_A_IRQ_MODERATION_COMPS,
189 					  profile[i].comps);
190 			if (ret)
191 				goto cancel_moder;
192 		}
193 
194 		nla_nest_end(skb, moder_attr);
195 	}
196 
197 	nla_nest_end(skb, profile_attr);
198 
199 	return 0;
200 
201 cancel_moder:
202 	nla_nest_cancel(skb, moder_attr);
203 cancel_profile:
204 	nla_nest_cancel(skb, profile_attr);
205 	return ret;
206 }
207 
coalesce_fill_reply(struct sk_buff * skb,const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)208 static int coalesce_fill_reply(struct sk_buff *skb,
209 			       const struct ethnl_req_info *req_base,
210 			       const struct ethnl_reply_data *reply_base)
211 {
212 	const struct coalesce_reply_data *data = COALESCE_REPDATA(reply_base);
213 	const struct kernel_ethtool_coalesce *kcoal = &data->kernel_coalesce;
214 	const struct ethtool_coalesce *coal = &data->coalesce;
215 	u32 supported = data->supported_params;
216 	struct dim_irq_moder *moder;
217 	int ret = 0;
218 
219 	if (coalesce_put_u32(skb, ETHTOOL_A_COALESCE_RX_USECS,
220 			     coal->rx_coalesce_usecs, supported) ||
221 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_RX_MAX_FRAMES,
222 			     coal->rx_max_coalesced_frames, supported) ||
223 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_RX_USECS_IRQ,
224 			     coal->rx_coalesce_usecs_irq, supported) ||
225 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ,
226 			     coal->rx_max_coalesced_frames_irq, supported) ||
227 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_USECS,
228 			     coal->tx_coalesce_usecs, supported) ||
229 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_MAX_FRAMES,
230 			     coal->tx_max_coalesced_frames, supported) ||
231 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_USECS_IRQ,
232 			     coal->tx_coalesce_usecs_irq, supported) ||
233 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ,
234 			     coal->tx_max_coalesced_frames_irq, supported) ||
235 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_STATS_BLOCK_USECS,
236 			     coal->stats_block_coalesce_usecs, supported) ||
237 	    coalesce_put_bool(skb, ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX,
238 			      coal->use_adaptive_rx_coalesce, supported) ||
239 	    coalesce_put_bool(skb, ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX,
240 			      coal->use_adaptive_tx_coalesce, supported) ||
241 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_PKT_RATE_LOW,
242 			     coal->pkt_rate_low, supported) ||
243 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_RX_USECS_LOW,
244 			     coal->rx_coalesce_usecs_low, supported) ||
245 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW,
246 			     coal->rx_max_coalesced_frames_low, supported) ||
247 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_USECS_LOW,
248 			     coal->tx_coalesce_usecs_low, supported) ||
249 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW,
250 			     coal->tx_max_coalesced_frames_low, supported) ||
251 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_PKT_RATE_HIGH,
252 			     coal->pkt_rate_high, supported) ||
253 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_RX_USECS_HIGH,
254 			     coal->rx_coalesce_usecs_high, supported) ||
255 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH,
256 			     coal->rx_max_coalesced_frames_high, supported) ||
257 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_USECS_HIGH,
258 			     coal->tx_coalesce_usecs_high, supported) ||
259 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH,
260 			     coal->tx_max_coalesced_frames_high, supported) ||
261 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL,
262 			     coal->rate_sample_interval, supported) ||
263 	    coalesce_put_bool(skb, ETHTOOL_A_COALESCE_USE_CQE_MODE_TX,
264 			      kcoal->use_cqe_mode_tx, supported) ||
265 	    coalesce_put_bool(skb, ETHTOOL_A_COALESCE_USE_CQE_MODE_RX,
266 			      kcoal->use_cqe_mode_rx, supported) ||
267 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES,
268 			     kcoal->tx_aggr_max_bytes, supported) ||
269 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES,
270 			     kcoal->tx_aggr_max_frames, supported) ||
271 	    coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS,
272 			     kcoal->tx_aggr_time_usecs, supported))
273 		return -EMSGSIZE;
274 
275 	if (!req_base->dev || !req_base->dev->irq_moder)
276 		return 0;
277 
278 	moder = req_base->dev->irq_moder;
279 	rcu_read_lock();
280 	if (moder->profile_flags & DIM_PROFILE_RX) {
281 		ret = coalesce_put_profile(skb, ETHTOOL_A_COALESCE_RX_PROFILE,
282 					   rcu_dereference(moder->rx_profile),
283 					   moder->coal_flags);
284 		if (ret)
285 			goto out;
286 	}
287 
288 	if (moder->profile_flags & DIM_PROFILE_TX)
289 		ret = coalesce_put_profile(skb, ETHTOOL_A_COALESCE_TX_PROFILE,
290 					   rcu_dereference(moder->tx_profile),
291 					   moder->coal_flags);
292 
293 out:
294 	rcu_read_unlock();
295 	return ret;
296 }
297 
298 /* COALESCE_SET */
299 
300 static const struct nla_policy coalesce_irq_moderation_policy[] = {
301 	[ETHTOOL_A_IRQ_MODERATION_USEC]	= { .type = NLA_U32 },
302 	[ETHTOOL_A_IRQ_MODERATION_PKTS]	= { .type = NLA_U32 },
303 	[ETHTOOL_A_IRQ_MODERATION_COMPS] = { .type = NLA_U32 },
304 };
305 
306 static const struct nla_policy coalesce_profile_policy[] = {
307 	[ETHTOOL_A_PROFILE_IRQ_MODERATION] =
308 		NLA_POLICY_NESTED(coalesce_irq_moderation_policy),
309 };
310 
311 const struct nla_policy ethnl_coalesce_set_policy[] = {
312 	[ETHTOOL_A_COALESCE_HEADER]		=
313 		NLA_POLICY_NESTED(ethnl_header_policy),
314 	[ETHTOOL_A_COALESCE_RX_USECS]		= { .type = NLA_U32 },
315 	[ETHTOOL_A_COALESCE_RX_MAX_FRAMES]	= { .type = NLA_U32 },
316 	[ETHTOOL_A_COALESCE_RX_USECS_IRQ]	= { .type = NLA_U32 },
317 	[ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ]	= { .type = NLA_U32 },
318 	[ETHTOOL_A_COALESCE_TX_USECS]		= { .type = NLA_U32 },
319 	[ETHTOOL_A_COALESCE_TX_MAX_FRAMES]	= { .type = NLA_U32 },
320 	[ETHTOOL_A_COALESCE_TX_USECS_IRQ]	= { .type = NLA_U32 },
321 	[ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ]	= { .type = NLA_U32 },
322 	[ETHTOOL_A_COALESCE_STATS_BLOCK_USECS]	= { .type = NLA_U32 },
323 	[ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX]	= { .type = NLA_U8 },
324 	[ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX]	= { .type = NLA_U8 },
325 	[ETHTOOL_A_COALESCE_PKT_RATE_LOW]	= { .type = NLA_U32 },
326 	[ETHTOOL_A_COALESCE_RX_USECS_LOW]	= { .type = NLA_U32 },
327 	[ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW]	= { .type = NLA_U32 },
328 	[ETHTOOL_A_COALESCE_TX_USECS_LOW]	= { .type = NLA_U32 },
329 	[ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW]	= { .type = NLA_U32 },
330 	[ETHTOOL_A_COALESCE_PKT_RATE_HIGH]	= { .type = NLA_U32 },
331 	[ETHTOOL_A_COALESCE_RX_USECS_HIGH]	= { .type = NLA_U32 },
332 	[ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH]	= { .type = NLA_U32 },
333 	[ETHTOOL_A_COALESCE_TX_USECS_HIGH]	= { .type = NLA_U32 },
334 	[ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH]	= { .type = NLA_U32 },
335 	[ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL] = { .type = NLA_U32 },
336 	[ETHTOOL_A_COALESCE_USE_CQE_MODE_TX]	= NLA_POLICY_MAX(NLA_U8, 1),
337 	[ETHTOOL_A_COALESCE_USE_CQE_MODE_RX]	= NLA_POLICY_MAX(NLA_U8, 1),
338 	[ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES] = { .type = NLA_U32 },
339 	[ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES] = { .type = NLA_U32 },
340 	[ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS] = { .type = NLA_U32 },
341 	[ETHTOOL_A_COALESCE_RX_PROFILE] =
342 		NLA_POLICY_NESTED(coalesce_profile_policy),
343 	[ETHTOOL_A_COALESCE_TX_PROFILE] =
344 		NLA_POLICY_NESTED(coalesce_profile_policy),
345 };
346 
347 static int
ethnl_set_coalesce_validate(struct ethnl_req_info * req_info,struct genl_info * info)348 ethnl_set_coalesce_validate(struct ethnl_req_info *req_info,
349 			    struct genl_info *info)
350 {
351 	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
352 	struct dim_irq_moder *irq_moder = req_info->dev->irq_moder;
353 	struct nlattr **tb = info->attrs;
354 	u32 supported_params;
355 	u16 a;
356 
357 	if (!ops->get_coalesce || !ops->set_coalesce)
358 		return -EOPNOTSUPP;
359 
360 	/* make sure that only supported parameters are present */
361 	supported_params = ops->supported_coalesce_params;
362 	if (irq_moder && irq_moder->profile_flags & DIM_PROFILE_RX)
363 		supported_params |= ETHTOOL_COALESCE_RX_PROFILE;
364 
365 	if (irq_moder && irq_moder->profile_flags & DIM_PROFILE_TX)
366 		supported_params |= ETHTOOL_COALESCE_TX_PROFILE;
367 
368 	for (a = ETHTOOL_A_COALESCE_RX_USECS; a < __ETHTOOL_A_COALESCE_CNT; a++)
369 		if (tb[a] && !(supported_params & attr_to_mask(a))) {
370 			NL_SET_ERR_MSG_ATTR(info->extack, tb[a],
371 					    "cannot modify an unsupported parameter");
372 			return -EINVAL;
373 		}
374 
375 	return 1;
376 }
377 
378 /**
379  * ethnl_update_irq_moder - update a specific field in the given profile
380  * @irq_moder: place that collects dim related information
381  * @irq_field: field in profile to modify
382  * @attr_type: attr type ETHTOOL_A_IRQ_MODERATION_*
383  * @tb: netlink attribute with new values or null
384  * @coal_bit: DIM_COALESCE_* bit from coal_flags
385  * @mod: pointer to bool for modification tracking
386  * @extack: netlink extended ack
387  *
388  * Return: 0 on success or a negative error code.
389  */
ethnl_update_irq_moder(struct dim_irq_moder * irq_moder,u16 * irq_field,u16 attr_type,struct nlattr ** tb,u8 coal_bit,bool * mod,struct netlink_ext_ack * extack)390 static int ethnl_update_irq_moder(struct dim_irq_moder *irq_moder,
391 				  u16 *irq_field, u16 attr_type,
392 				  struct nlattr **tb,
393 				  u8 coal_bit, bool *mod,
394 				  struct netlink_ext_ack *extack)
395 {
396 	int ret = 0;
397 	u32 val;
398 
399 	if (!tb[attr_type])
400 		return 0;
401 
402 	if (irq_moder->coal_flags & coal_bit) {
403 		val = nla_get_u32(tb[attr_type]);
404 		if (*irq_field == val)
405 			return 0;
406 
407 		*irq_field = val;
408 		*mod = true;
409 	} else {
410 		NL_SET_BAD_ATTR(extack, tb[attr_type]);
411 		ret = -EOPNOTSUPP;
412 	}
413 
414 	return ret;
415 }
416 
417 /**
418  * ethnl_update_profile - get a profile nest with child nests from userspace.
419  * @dev: netdevice to update the profile
420  * @dst: profile get from the driver and modified by ethnl_update_profile.
421  * @nests: nest attr ETHTOOL_A_COALESCE_*X_PROFILE to set profile.
422  * @mod: pointer to bool for modification tracking
423  * @extack: Netlink extended ack
424  *
425  * Layout of nests:
426  *   Nested ETHTOOL_A_COALESCE_*X_PROFILE attr
427  *     Nested ETHTOOL_A_PROFILE_IRQ_MODERATION attr
428  *       ETHTOOL_A_IRQ_MODERATION_USEC attr
429  *       ETHTOOL_A_IRQ_MODERATION_PKTS attr
430  *       ETHTOOL_A_IRQ_MODERATION_COMPS attr
431  *     ...
432  *     Nested ETHTOOL_A_PROFILE_IRQ_MODERATION attr
433  *       ETHTOOL_A_IRQ_MODERATION_USEC attr
434  *       ETHTOOL_A_IRQ_MODERATION_PKTS attr
435  *       ETHTOOL_A_IRQ_MODERATION_COMPS attr
436  *
437  * Return: 0 on success or a negative error code.
438  */
ethnl_update_profile(struct net_device * dev,struct dim_cq_moder __rcu ** dst,const struct nlattr * nests,bool * mod,struct netlink_ext_ack * extack)439 static int ethnl_update_profile(struct net_device *dev,
440 				struct dim_cq_moder __rcu **dst,
441 				const struct nlattr *nests,
442 				bool *mod,
443 				struct netlink_ext_ack *extack)
444 {
445 	int len_irq_moder = ARRAY_SIZE(coalesce_irq_moderation_policy);
446 	struct nlattr *tb[ARRAY_SIZE(coalesce_irq_moderation_policy)];
447 	struct dim_irq_moder *irq_moder = dev->irq_moder;
448 	struct dim_cq_moder *new_profile, *old_profile;
449 	int ret, rem, i = 0, len;
450 	struct nlattr *nest;
451 
452 	if (!nests)
453 		return 0;
454 
455 	if (!*dst)
456 		return -EOPNOTSUPP;
457 
458 	old_profile = rtnl_dereference(*dst);
459 	len = NET_DIM_PARAMS_NUM_PROFILES * sizeof(*old_profile);
460 	new_profile = kmemdup(old_profile, len, GFP_KERNEL);
461 	if (!new_profile)
462 		return -ENOMEM;
463 
464 	nla_for_each_nested_type(nest, ETHTOOL_A_PROFILE_IRQ_MODERATION,
465 				 nests, rem) {
466 		ret = nla_parse_nested(tb, len_irq_moder - 1, nest,
467 				       coalesce_irq_moderation_policy,
468 				       extack);
469 		if (ret)
470 			goto err_out;
471 
472 		ret = ethnl_update_irq_moder(irq_moder, &new_profile[i].usec,
473 					     ETHTOOL_A_IRQ_MODERATION_USEC,
474 					     tb, DIM_COALESCE_USEC,
475 					     mod, extack);
476 		if (ret)
477 			goto err_out;
478 
479 		ret = ethnl_update_irq_moder(irq_moder, &new_profile[i].pkts,
480 					     ETHTOOL_A_IRQ_MODERATION_PKTS,
481 					     tb, DIM_COALESCE_PKTS,
482 					     mod, extack);
483 		if (ret)
484 			goto err_out;
485 
486 		ret = ethnl_update_irq_moder(irq_moder, &new_profile[i].comps,
487 					     ETHTOOL_A_IRQ_MODERATION_COMPS,
488 					     tb, DIM_COALESCE_COMPS,
489 					     mod, extack);
490 		if (ret)
491 			goto err_out;
492 
493 		i++;
494 	}
495 
496 	/* After the profile is modified, dim itself is a dynamic
497 	 * mechanism and will quickly fit to the appropriate
498 	 * coalescing parameters according to the new profile.
499 	 */
500 	rcu_assign_pointer(*dst, new_profile);
501 	kfree_rcu(old_profile, rcu);
502 
503 	return 0;
504 
505 err_out:
506 	kfree(new_profile);
507 	return ret;
508 }
509 
510 static int
__ethnl_set_coalesce(struct ethnl_req_info * req_info,struct genl_info * info,bool * dual_change)511 __ethnl_set_coalesce(struct ethnl_req_info *req_info, struct genl_info *info,
512 		     bool *dual_change)
513 {
514 	struct kernel_ethtool_coalesce kernel_coalesce = {};
515 	struct net_device *dev = req_info->dev;
516 	struct ethtool_coalesce coalesce = {};
517 	bool mod_mode = false, mod = false;
518 	struct nlattr **tb = info->attrs;
519 	int ret;
520 
521 	ret = dev->ethtool_ops->get_coalesce(dev, &coalesce, &kernel_coalesce,
522 					     info->extack);
523 	if (ret < 0)
524 		return ret;
525 
526 	/* Update values */
527 	ethnl_update_u32(&coalesce.rx_coalesce_usecs,
528 			 tb[ETHTOOL_A_COALESCE_RX_USECS], &mod);
529 	ethnl_update_u32(&coalesce.rx_max_coalesced_frames,
530 			 tb[ETHTOOL_A_COALESCE_RX_MAX_FRAMES], &mod);
531 	ethnl_update_u32(&coalesce.rx_coalesce_usecs_irq,
532 			 tb[ETHTOOL_A_COALESCE_RX_USECS_IRQ], &mod);
533 	ethnl_update_u32(&coalesce.rx_max_coalesced_frames_irq,
534 			 tb[ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ], &mod);
535 	ethnl_update_u32(&coalesce.tx_coalesce_usecs,
536 			 tb[ETHTOOL_A_COALESCE_TX_USECS], &mod);
537 	ethnl_update_u32(&coalesce.tx_max_coalesced_frames,
538 			 tb[ETHTOOL_A_COALESCE_TX_MAX_FRAMES], &mod);
539 	ethnl_update_u32(&coalesce.tx_coalesce_usecs_irq,
540 			 tb[ETHTOOL_A_COALESCE_TX_USECS_IRQ], &mod);
541 	ethnl_update_u32(&coalesce.tx_max_coalesced_frames_irq,
542 			 tb[ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ], &mod);
543 	ethnl_update_u32(&coalesce.stats_block_coalesce_usecs,
544 			 tb[ETHTOOL_A_COALESCE_STATS_BLOCK_USECS], &mod);
545 	ethnl_update_u32(&coalesce.pkt_rate_low,
546 			 tb[ETHTOOL_A_COALESCE_PKT_RATE_LOW], &mod);
547 	ethnl_update_u32(&coalesce.rx_coalesce_usecs_low,
548 			 tb[ETHTOOL_A_COALESCE_RX_USECS_LOW], &mod);
549 	ethnl_update_u32(&coalesce.rx_max_coalesced_frames_low,
550 			 tb[ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW], &mod);
551 	ethnl_update_u32(&coalesce.tx_coalesce_usecs_low,
552 			 tb[ETHTOOL_A_COALESCE_TX_USECS_LOW], &mod);
553 	ethnl_update_u32(&coalesce.tx_max_coalesced_frames_low,
554 			 tb[ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW], &mod);
555 	ethnl_update_u32(&coalesce.pkt_rate_high,
556 			 tb[ETHTOOL_A_COALESCE_PKT_RATE_HIGH], &mod);
557 	ethnl_update_u32(&coalesce.rx_coalesce_usecs_high,
558 			 tb[ETHTOOL_A_COALESCE_RX_USECS_HIGH], &mod);
559 	ethnl_update_u32(&coalesce.rx_max_coalesced_frames_high,
560 			 tb[ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH], &mod);
561 	ethnl_update_u32(&coalesce.tx_coalesce_usecs_high,
562 			 tb[ETHTOOL_A_COALESCE_TX_USECS_HIGH], &mod);
563 	ethnl_update_u32(&coalesce.tx_max_coalesced_frames_high,
564 			 tb[ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH], &mod);
565 	ethnl_update_u32(&coalesce.rate_sample_interval,
566 			 tb[ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL], &mod);
567 	ethnl_update_u32(&kernel_coalesce.tx_aggr_max_bytes,
568 			 tb[ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES], &mod);
569 	ethnl_update_u32(&kernel_coalesce.tx_aggr_max_frames,
570 			 tb[ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES], &mod);
571 	ethnl_update_u32(&kernel_coalesce.tx_aggr_time_usecs,
572 			 tb[ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS], &mod);
573 
574 	if (dev->irq_moder && dev->irq_moder->profile_flags & DIM_PROFILE_RX) {
575 		ret = ethnl_update_profile(dev, &dev->irq_moder->rx_profile,
576 					   tb[ETHTOOL_A_COALESCE_RX_PROFILE],
577 					   &mod, info->extack);
578 		if (ret < 0)
579 			return ret;
580 	}
581 
582 	if (dev->irq_moder && dev->irq_moder->profile_flags & DIM_PROFILE_TX) {
583 		ret = ethnl_update_profile(dev, &dev->irq_moder->tx_profile,
584 					   tb[ETHTOOL_A_COALESCE_TX_PROFILE],
585 					   &mod, info->extack);
586 		if (ret < 0)
587 			return ret;
588 	}
589 
590 	/* Update operation modes */
591 	ethnl_update_bool32(&coalesce.use_adaptive_rx_coalesce,
592 			    tb[ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX], &mod_mode);
593 	ethnl_update_bool32(&coalesce.use_adaptive_tx_coalesce,
594 			    tb[ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX], &mod_mode);
595 	ethnl_update_u8(&kernel_coalesce.use_cqe_mode_tx,
596 			tb[ETHTOOL_A_COALESCE_USE_CQE_MODE_TX], &mod_mode);
597 	ethnl_update_u8(&kernel_coalesce.use_cqe_mode_rx,
598 			tb[ETHTOOL_A_COALESCE_USE_CQE_MODE_RX], &mod_mode);
599 
600 	*dual_change = mod && mod_mode;
601 	if (!mod && !mod_mode)
602 		return 0;
603 
604 	ret = dev->ethtool_ops->set_coalesce(dev, &coalesce, &kernel_coalesce,
605 					     info->extack);
606 	return ret < 0 ? ret : 1;
607 }
608 
609 static int
ethnl_set_coalesce(struct ethnl_req_info * req_info,struct genl_info * info)610 ethnl_set_coalesce(struct ethnl_req_info *req_info, struct genl_info *info)
611 {
612 	bool dual_change;
613 	int err, ret;
614 
615 	/* SET_COALESCE may change operation mode and parameters in one call.
616 	 * Changing operation mode may cause the driver to reset the parameter
617 	 * values, and therefore ignore user input (driver does not know which
618 	 * parameters come from user and which are echoed back from ->get).
619 	 * To not complicate the drivers if user tries to change both the mode
620 	 * and parameters at once - call the driver twice.
621 	 */
622 	err = __ethnl_set_coalesce(req_info, info, &dual_change);
623 	if (err < 0)
624 		return err;
625 	ret = err;
626 
627 	if (ret && dual_change) {
628 		err = __ethnl_set_coalesce(req_info, info, &dual_change);
629 		if (err < 0)
630 			return err;
631 	}
632 	return ret;
633 }
634 
635 const struct ethnl_request_ops ethnl_coalesce_request_ops = {
636 	.request_cmd		= ETHTOOL_MSG_COALESCE_GET,
637 	.reply_cmd		= ETHTOOL_MSG_COALESCE_GET_REPLY,
638 	.hdr_attr		= ETHTOOL_A_COALESCE_HEADER,
639 	.req_info_size		= sizeof(struct coalesce_req_info),
640 	.reply_data_size	= sizeof(struct coalesce_reply_data),
641 
642 	.prepare_data		= coalesce_prepare_data,
643 	.reply_size		= coalesce_reply_size,
644 	.fill_reply		= coalesce_fill_reply,
645 
646 	.set_validate		= ethnl_set_coalesce_validate,
647 	.set			= ethnl_set_coalesce,
648 	.set_ntf_cmd		= ETHTOOL_MSG_COALESCE_NTF,
649 };
650