]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/udev/net/ethtool-util.c
test: cast values in proper type to suppress warnings
[thirdparty/systemd.git] / src / udev / net / ethtool-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
a5010333 2
a5010333 3#include <net/if.h>
8b43440b 4#include <sys/ioctl.h>
a5010333
TG
5#include <linux/ethtool.h>
6#include <linux/sockios.h>
7
8b43440b 8#include "conf-parser.h"
a5010333 9#include "ethtool-util.h"
593022fa 10#include "link-config.h"
ab1263d7
LP
11#include "log.h"
12#include "missing.h"
429b4350 13#include "socket-util.h"
8b43440b 14#include "string-table.h"
a5010333
TG
15#include "strxcpyx.h"
16#include "util.h"
5fde13d7 17
2c5859af 18static const char* const duplex_table[_DUP_MAX] = {
5fde13d7
TG
19 [DUP_FULL] = "full",
20 [DUP_HALF] = "half"
21};
22
23DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
24DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
25
2c5859af 26static const char* const wol_table[_WOL_MAX] = {
617da14c
SS
27 [WOL_PHY] = "phy",
28 [WOL_UCAST] = "unicast",
29 [WOL_MCAST] = "multicast",
30 [WOL_BCAST] = "broadcast",
31 [WOL_ARP] = "arp",
32 [WOL_MAGIC] = "magic",
33 [WOL_MAGICSECURE] = "secureon",
34 [WOL_OFF] = "off"
5fde13d7
TG
35};
36
37DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan);
38DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting");
a5010333 39
593022fa
SS
40static const char* const port_table[_NET_DEV_PORT_MAX] = {
41 [NET_DEV_PORT_TP] = "tp",
42 [NET_DEV_PORT_AUI] = "aui",
43 [NET_DEV_PORT_MII] = "mii",
44 [NET_DEV_PORT_FIBRE] = "fibre",
45 [NET_DEV_PORT_BNC] = "bnc"
46};
47
48DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort);
49DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting");
50
50725d10 51static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = {
ffa69a04
SS
52 [NET_DEV_FEAT_GSO] = "tx-generic-segmentation",
53 [NET_DEV_FEAT_GRO] = "rx-gro",
54 [NET_DEV_FEAT_LRO] = "rx-lro",
55 [NET_DEV_FEAT_TSO] = "tx-tcp-segmentation",
56 [NET_DEV_FEAT_TSO6] = "tx-tcp6-segmentation",
50725d10
SS
57};
58
a5010333
TG
59int ethtool_connect(int *ret) {
60 int fd;
61
62 assert_return(ret, -EINVAL);
63
429b4350 64 fd = socket_ioctl_fd();
ece174c5 65 if (fd < 0)
429b4350 66 return fd;
2b44daaa 67
a5010333
TG
68 *ret = fd;
69
70 return 0;
71}
72
aedca892 73int ethtool_get_driver(int *fd, const char *ifname, char **ret) {
61f3af4f
LP
74 struct ethtool_drvinfo ecmd = {
75 .cmd = ETHTOOL_GDRVINFO
76 };
77 struct ifreq ifr = {
78 .ifr_data = (void*) &ecmd
79 };
80 char *d;
847a8a5f
TG
81 int r;
82
aedca892
TG
83 if (*fd < 0) {
84 r = ethtool_connect(fd);
f647962d
MS
85 if (r < 0)
86 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
aedca892
TG
87 }
88
847a8a5f 89 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
847a8a5f 90
aedca892 91 r = ioctl(*fd, SIOCETHTOOL, &ifr);
847a8a5f
TG
92 if (r < 0)
93 return -errno;
94
61f3af4f
LP
95 d = strdup(ecmd.driver);
96 if (!d)
847a8a5f
TG
97 return -ENOMEM;
98
61f3af4f 99 *ret = d;
847a8a5f
TG
100 return 0;
101}
102
61087906 103int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex) {
6c0519c0
TG
104 struct ethtool_cmd ecmd = {
105 .cmd = ETHTOOL_GSET
106 };
107 struct ifreq ifr = {
108 .ifr_data = (void*) &ecmd
109 };
0a2c2294 110 bool need_update = false;
a5010333
TG
111 int r;
112
5fde13d7 113 if (speed == 0 && duplex == _DUP_INVALID)
a5010333
TG
114 return 0;
115
aedca892
TG
116 if (*fd < 0) {
117 r = ethtool_connect(fd);
f647962d
MS
118 if (r < 0)
119 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
aedca892
TG
120 }
121
a5010333 122 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
a5010333 123
aedca892 124 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
125 if (r < 0)
126 return -errno;
127
128 if (ethtool_cmd_speed(&ecmd) != speed) {
129 ethtool_cmd_speed_set(&ecmd, speed);
130 need_update = true;
131 }
132
5fde13d7
TG
133 switch (duplex) {
134 case DUP_HALF:
a5010333
TG
135 if (ecmd.duplex != DUPLEX_HALF) {
136 ecmd.duplex = DUPLEX_HALF;
137 need_update = true;
138 }
5fde13d7
TG
139 break;
140 case DUP_FULL:
a5010333
TG
141 if (ecmd.duplex != DUPLEX_FULL) {
142 ecmd.duplex = DUPLEX_FULL;
143 need_update = true;
144 }
5fde13d7
TG
145 break;
146 default:
147 break;
a5010333
TG
148 }
149
150 if (need_update) {
151 ecmd.cmd = ETHTOOL_SSET;
152
aedca892 153 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
154 if (r < 0)
155 return -errno;
156 }
157
158 return 0;
159}
160
aedca892 161int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) {
6c0519c0
TG
162 struct ethtool_wolinfo ecmd = {
163 .cmd = ETHTOOL_GWOL
164 };
165 struct ifreq ifr = {
166 .ifr_data = (void*) &ecmd
167 };
0a2c2294 168 bool need_update = false;
a5010333
TG
169 int r;
170
5fde13d7 171 if (wol == _WOL_INVALID)
a5010333
TG
172 return 0;
173
aedca892
TG
174 if (*fd < 0) {
175 r = ethtool_connect(fd);
f647962d
MS
176 if (r < 0)
177 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
aedca892
TG
178 }
179
a5010333 180 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
a5010333 181
aedca892 182 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
183 if (r < 0)
184 return -errno;
185
5fde13d7 186 switch (wol) {
617da14c
SS
187 case WOL_PHY:
188 if (ecmd.wolopts != WAKE_PHY) {
189 ecmd.wolopts = WAKE_PHY;
190 need_update = true;
191 }
192 break;
193 case WOL_UCAST:
194 if (ecmd.wolopts != WAKE_UCAST) {
195 ecmd.wolopts = WAKE_UCAST;
196 need_update = true;
197 }
198 break;
199 case WOL_MCAST:
200 if (ecmd.wolopts != WAKE_MCAST) {
201 ecmd.wolopts = WAKE_MCAST;
202 need_update = true;
203 }
204 break;
205 case WOL_BCAST:
206 if (ecmd.wolopts != WAKE_BCAST) {
207 ecmd.wolopts = WAKE_BCAST;
208 need_update = true;
209 }
210 break;
211 case WOL_ARP:
212 if (ecmd.wolopts != WAKE_ARP) {
213 ecmd.wolopts = WAKE_ARP;
214 need_update = true;
215 }
216 break;
217 case WOL_MAGIC:
218 if (ecmd.wolopts != WAKE_MAGIC) {
219 ecmd.wolopts = WAKE_MAGIC;
220 need_update = true;
221 }
222 break;
223 case WOL_MAGICSECURE:
224 if (ecmd.wolopts != WAKE_MAGICSECURE) {
225 ecmd.wolopts = WAKE_MAGICSECURE;
226 need_update = true;
227 }
228 break;
229 case WOL_OFF:
230 if (ecmd.wolopts != 0) {
231 ecmd.wolopts = 0;
232 need_update = true;
233 }
234 break;
235 default:
236 break;
5fde13d7 237 }
a5010333
TG
238
239 if (need_update) {
240 ecmd.cmd = ETHTOOL_SWOL;
241
aedca892 242 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
243 if (r < 0)
244 return -errno;
245 }
246
247 return 0;
248}
50725d10 249
2b44daaa 250static int get_stringset(int fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) {
50725d10 251 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
a9dee27f
SS
252 struct {
253 struct ethtool_sset_info info;
254 uint32_t space;
255 } buffer = {
256 .info = {
257 .cmd = ETHTOOL_GSSET_INFO,
258 .sset_mask = UINT64_C(1) << stringset_id,
259 },
50725d10
SS
260 };
261 unsigned len;
262 int r;
263
a9dee27f 264 ifr->ifr_data = (void *) &buffer.info;
50725d10 265
2b44daaa 266 r = ioctl(fd, SIOCETHTOOL, ifr);
50725d10
SS
267 if (r < 0)
268 return -errno;
269
a9dee27f 270 if (!buffer.info.sset_mask)
50725d10
SS
271 return -EINVAL;
272
a9dee27f 273 len = buffer.info.data[0];
50725d10
SS
274
275 strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN);
276 if (!strings)
277 return -ENOMEM;
278
279 strings->cmd = ETHTOOL_GSTRINGS;
280 strings->string_set = stringset_id;
281 strings->len = len;
282
283 ifr->ifr_data = (void *) strings;
284
2b44daaa 285 r = ioctl(fd, SIOCETHTOOL, ifr);
50725d10
SS
286 if (r < 0)
287 return -errno;
288
ae2a15bc 289 *gstrings = TAKE_PTR(strings);
50725d10
SS
290
291 return 0;
292}
293
294static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) {
295 unsigned i;
296
297 for (i = 0; i < strings->len; i++) {
298 if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature))
299 return i;
300 }
301
302 return -1;
303}
304
305int ethtool_set_features(int *fd, const char *ifname, NetDevFeature *features) {
306 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
307 struct ethtool_sfeatures *sfeatures;
308 int block, bit, i, r;
a9dee27f 309 struct ifreq ifr = {};
50725d10
SS
310
311 if (*fd < 0) {
312 r = ethtool_connect(fd);
313 if (r < 0)
314 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
315 }
316
317 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
318
2b44daaa 319 r = get_stringset(*fd, &ifr, ETH_SS_FEATURES, &strings);
50725d10
SS
320 if (r < 0)
321 return log_warning_errno(r, "link_config: could not get ethtool features for %s", ifname);
322
323 sfeatures = alloca0(sizeof(struct ethtool_gstrings) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
324 sfeatures->cmd = ETHTOOL_SFEATURES;
325 sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
326
327 for (i = 0; i < _NET_DEV_FEAT_MAX; i++) {
328
329 if (features[i] != -1) {
330
331 r = find_feature_index(strings, netdev_feature_table[i]);
332 if (r < 0) {
333 log_warning_errno(r, "link_config: could not find feature: %s", netdev_feature_table[i]);
334 continue;
335 }
336
337 block = r / 32;
338 bit = r % 32;
339
340 sfeatures->features[block].valid |= 1 << bit;
341
342 if (features[i])
343 sfeatures->features[block].requested |= 1 << bit;
344 else
345 sfeatures->features[block].requested &= ~(1 << bit);
346 }
347 }
348
349 ifr.ifr_data = (void *) sfeatures;
350
351 r = ioctl(*fd, SIOCETHTOOL, &ifr);
352 if (r < 0)
353 return log_warning_errno(r, "link_config: could not set ethtool features for %s", ifname);
354
355 return 0;
356}
a39f92d3 357
2b44daaa 358static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **g) {
a39f92d3
SS
359 struct ecmd {
360 struct ethtool_link_settings req;
361 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
362 } ecmd = {
363 .req.cmd = ETHTOOL_GLINKSETTINGS,
364 };
365 struct ethtool_link_usettings *u;
366 unsigned offset;
367 int r;
368
369 /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS
370 handshake first to agree on the length of the link mode bitmaps. If kernel doesn't
371 agree with user, it returns the bitmap length it is expecting from user as a negative
372 length (and cmd field is 0). When kernel and user agree, kernel returns valid info in
373 all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on
374 https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf
375 */
376
377 ifr->ifr_data = (void *) &ecmd;
378
2b44daaa 379 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
380 if (r < 0)
381 return -errno;
382
383 if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
6b44a121 384 return -EOPNOTSUPP;
a39f92d3
SS
385
386 ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
387
388 ifr->ifr_data = (void *) &ecmd;
389
2b44daaa 390 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
391 if (r < 0)
392 return -errno;
393
394 if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
6b44a121 395 return -EOPNOTSUPP;
a39f92d3
SS
396
397 u = new0(struct ethtool_link_usettings , 1);
398 if (!u)
399 return -ENOMEM;
400
801d2c9f 401 u->base = ecmd.req;
a39f92d3
SS
402
403 offset = 0;
404 memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
405
406 offset += ecmd.req.link_mode_masks_nwords;
407 memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
408
409 offset += ecmd.req.link_mode_masks_nwords;
410 memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
411
412 *g = u;
413
414 return 0;
415}
416
2b44daaa 417static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
a39f92d3
SS
418 struct ethtool_link_usettings *e;
419 struct ethtool_cmd ecmd = {
420 .cmd = ETHTOOL_GSET,
421 };
422 int r;
423
424 ifr->ifr_data = (void *) &ecmd;
425
2b44daaa 426 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
427 if (r < 0)
428 return -errno;
429
430 e = new0(struct ethtool_link_usettings, 1);
431 if (!e)
432 return -ENOMEM;
433
434 e->base.cmd = ETHTOOL_GSET;
435
436 e->base.link_mode_masks_nwords = 1;
437 e->base.speed = ethtool_cmd_speed(&ecmd);
438 e->base.duplex = ecmd.duplex;
439 e->base.port = ecmd.port;
440 e->base.phy_address = ecmd.phy_address;
441 e->base.autoneg = ecmd.autoneg;
442 e->base.mdio_support = ecmd.mdio_support;
443
444 e->link_modes.supported[0] = ecmd.supported;
445 e->link_modes.advertising[0] = ecmd.advertising;
446 e->link_modes.lp_advertising[0] = ecmd.lp_advertising;
447
448 *u = e;
449
450 return 0;
451}
452
2b44daaa 453static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
a39f92d3
SS
454 struct {
455 struct ethtool_link_settings req;
456 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
94d4acbe 457 } ecmd = {};
a39f92d3
SS
458 unsigned int offset;
459 int r;
460
461 if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
462 return -EINVAL;
463
89e1ba0a 464 ecmd.req = u->base;
94d4acbe 465 ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
a39f92d3
SS
466 offset = 0;
467 memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords);
468
469 offset += ecmd.req.link_mode_masks_nwords;
470 memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords);
471
472 offset += ecmd.req.link_mode_masks_nwords;
473 memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords);
474
475 ifr->ifr_data = (void *) &ecmd;
476
2b44daaa 477 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
478 if (r < 0)
479 return -errno;
480
481 return 0;
482}
483
2b44daaa 484static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
a39f92d3
SS
485 struct ethtool_cmd ecmd = {
486 .cmd = ETHTOOL_SSET,
487 };
488 int r;
489
490 if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0)
491 return -EINVAL;
492
493 ecmd.supported = u->link_modes.supported[0];
494 ecmd.advertising = u->link_modes.advertising[0];
495 ecmd.lp_advertising = u->link_modes.lp_advertising[0];
496
497 ethtool_cmd_speed_set(&ecmd, u->base.speed);
498
499 ecmd.duplex = u->base.duplex;
500 ecmd.port = u->base.port;
501 ecmd.phy_address = u->base.phy_address;
502 ecmd.autoneg = u->base.autoneg;
503 ecmd.mdio_support = u->base.mdio_support;
504
505 ifr->ifr_data = (void *) &ecmd;
506
2b44daaa 507 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
508 if (r < 0)
509 return -errno;
510
511 return 0;
512}
513
514/* If autonegotiation is disabled, the speed and duplex represent the fixed link
515 * mode and are writable if the driver supports multiple link modes. If it is
516 * enabled then they are read-only. If the link is up they represent the negotiated
517 * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
518 * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
519 */
520
593022fa 521int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *link) {
a39f92d3
SS
522 _cleanup_free_ struct ethtool_link_usettings *u = NULL;
523 struct ifreq ifr = {};
524 int r;
525
593022fa 526 if (link->autonegotiation != 0) {
a39f92d3
SS
527 log_info("link_config: autonegotiation is unset or enabled, the speed and duplex are not writable.");
528 return 0;
529 }
530
531 if (*fd < 0) {
532 r = ethtool_connect(fd);
533 if (r < 0)
534 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
535 }
536
537 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
538
2b44daaa 539 r = get_glinksettings(*fd, &ifr, &u);
a39f92d3 540 if (r < 0) {
2b44daaa 541 r = get_gset(*fd, &ifr, &u);
a39f92d3
SS
542 if (r < 0)
543 return log_warning_errno(r, "link_config: Cannot get device settings for %s : %m", ifname);
544 }
545
593022fa 546 if (link->speed)
9c5e1172 547 u->base.speed = DIV_ROUND_UP(link->speed, 1000000);
593022fa
SS
548
549 if (link->duplex != _DUP_INVALID)
550 u->base.duplex = link->duplex;
a39f92d3 551
593022fa 552 if (link->port != _NET_DEV_PORT_INVALID)
20d4e995 553 u->base.port = link->port;
a39f92d3 554
593022fa 555 u->base.autoneg = link->autonegotiation;
a39f92d3
SS
556
557 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
2b44daaa 558 r = set_slinksettings(*fd, &ifr, u);
a39f92d3 559 else
2b44daaa 560 r = set_sset(*fd, &ifr, u);
a39f92d3
SS
561 if (r < 0)
562 return log_warning_errno(r, "link_config: Cannot set device settings for %s : %m", ifname);
563
564 return r;
565}
5f945202
SS
566
567int config_parse_channel(const char *unit,
568 const char *filename,
569 unsigned line,
570 const char *section,
571 unsigned section_line,
572 const char *lvalue,
573 int ltype,
574 const char *rvalue,
575 void *data,
576 void *userdata) {
577 link_config *config = data;
578 uint32_t k;
579 int r;
580
581 assert(filename);
582 assert(section);
583 assert(lvalue);
584 assert(rvalue);
585 assert(data);
586
587 r = safe_atou32(rvalue, &k);
588 if (r < 0) {
589 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
590 return 0;
591 }
592
593 if (k < 1) {
594 log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
595 return 0;
596 }
597
598 if (streq(lvalue, "RxChannels")) {
599 config->channels.rx_count = k;
600 config->channels.rx_count_set = true;
601 } else if (streq(lvalue, "TxChannels")) {
602 config->channels.tx_count = k;
603 config->channels.tx_count_set = true;
604 } else if (streq(lvalue, "OtherChannels")) {
605 config->channels.other_count = k;
606 config->channels.other_count_set = true;
607 } else if (streq(lvalue, "CombinedChannels")) {
608 config->channels.combined_count = k;
609 config->channels.combined_count_set = true;
610 }
611
612 return 0;
613}
614
615int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) {
616 struct ethtool_channels ecmd = {
617 .cmd = ETHTOOL_GCHANNELS
618 };
619 struct ifreq ifr = {
620 .ifr_data = (void*) &ecmd
621 };
622
623 bool need_update = false;
624 int r;
625
626 if (*fd < 0) {
627 r = ethtool_connect(fd);
628 if (r < 0)
629 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
630 }
631
632 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
633
634 r = ioctl(*fd, SIOCETHTOOL, &ifr);
635 if (r < 0)
636 return -errno;
637
638 if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) {
639 ecmd.rx_count = channels->rx_count;
640 need_update = true;
641 }
642
643 if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) {
644 ecmd.tx_count = channels->tx_count;
645 need_update = true;
646 }
647
648 if (channels->other_count_set && ecmd.other_count != channels->other_count) {
649 ecmd.other_count = channels->other_count;
650 need_update = true;
651 }
652
653 if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) {
654 ecmd.combined_count = channels->combined_count;
655 need_update = true;
656 }
657
658 if (need_update) {
659 ecmd.cmd = ETHTOOL_SCHANNELS;
660
661 r = ioctl(*fd, SIOCETHTOOL, &ifr);
662 if (r < 0)
663 return -errno;
664 }
665
666 return 0;
667}