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