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