]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ethtool-util.c
ethtool-util: introduce ethtool_get_link_info()
[thirdparty/systemd.git] / src / shared / 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 "extract-word.h"
11 #include "log.h"
12 #include "memory-util.h"
13 #include "missing.h"
14 #include "socket-util.h"
15 #include "string-table.h"
16 #include "strxcpyx.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[] = {
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 < N_ADVERTISE);
115
116 DEFINE_STRING_TABLE_LOOKUP(ethtool_link_mode_bit, enum ethtool_link_mode_bit_indices);
117
118 static int ethtool_connect_or_warn(int *ret, bool warn) {
119 int fd;
120
121 assert_return(ret, -EINVAL);
122
123 fd = socket_ioctl_fd();
124 if (fd < 0)
125 return log_full_errno(warn ? LOG_WARNING: LOG_DEBUG, fd,
126 "ethtool: could not create control socket: %m");
127
128 *ret = fd;
129
130 return 0;
131 }
132
133 int ethtool_get_driver(int *fd, const char *ifname, char **ret) {
134 struct ethtool_drvinfo ecmd = {
135 .cmd = ETHTOOL_GDRVINFO
136 };
137 struct ifreq ifr = {
138 .ifr_data = (void*) &ecmd
139 };
140 char *d;
141 int r;
142
143 if (*fd < 0) {
144 r = ethtool_connect_or_warn(fd, true);
145 if (r < 0)
146 return r;
147 }
148
149 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
150
151 r = ioctl(*fd, SIOCETHTOOL, &ifr);
152 if (r < 0)
153 return -errno;
154
155 d = strdup(ecmd.driver);
156 if (!d)
157 return -ENOMEM;
158
159 *ret = d;
160 return 0;
161 }
162
163 int ethtool_get_link_info(int *fd, const char *ifname,
164 int *ret_autonegotiation, size_t *ret_speed,
165 Duplex *ret_duplex, NetDevPort *ret_port) {
166 struct ethtool_cmd ecmd = {
167 .cmd = ETHTOOL_GSET,
168 };
169 struct ifreq ifr = {
170 .ifr_data = (void*) &ecmd,
171 };
172 int r;
173
174 if (*fd < 0) {
175 r = ethtool_connect_or_warn(fd, false);
176 if (r < 0)
177 return r;
178 }
179
180 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
181
182 r = ioctl(*fd, SIOCETHTOOL, &ifr);
183 if (r < 0)
184 return -errno;
185
186 if (ret_autonegotiation)
187 *ret_autonegotiation = ecmd.autoneg;
188
189 if (ret_speed)
190 *ret_speed = ethtool_cmd_speed(&ecmd) * 1000 * 1000;
191
192 if (ret_duplex)
193 *ret_duplex = ecmd.duplex;
194
195 if (ret_port)
196 *ret_port = ecmd.port;
197
198 return 0;
199 }
200
201 int ethtool_set_speed(int *fd, const char *ifname, unsigned speed, Duplex duplex) {
202 struct ethtool_cmd ecmd = {
203 .cmd = ETHTOOL_GSET
204 };
205 struct ifreq ifr = {
206 .ifr_data = (void*) &ecmd
207 };
208 bool need_update = false;
209 int r;
210
211 if (speed == 0 && duplex == _DUP_INVALID)
212 return 0;
213
214 if (*fd < 0) {
215 r = ethtool_connect_or_warn(fd, true);
216 if (r < 0)
217 return r;
218 }
219
220 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
221
222 r = ioctl(*fd, SIOCETHTOOL, &ifr);
223 if (r < 0)
224 return -errno;
225
226 if (ethtool_cmd_speed(&ecmd) != speed) {
227 ethtool_cmd_speed_set(&ecmd, speed);
228 need_update = true;
229 }
230
231 switch (duplex) {
232 case DUP_HALF:
233 if (ecmd.duplex != DUPLEX_HALF) {
234 ecmd.duplex = DUPLEX_HALF;
235 need_update = true;
236 }
237 break;
238 case DUP_FULL:
239 if (ecmd.duplex != DUPLEX_FULL) {
240 ecmd.duplex = DUPLEX_FULL;
241 need_update = true;
242 }
243 break;
244 default:
245 break;
246 }
247
248 if (need_update) {
249 ecmd.cmd = ETHTOOL_SSET;
250
251 r = ioctl(*fd, SIOCETHTOOL, &ifr);
252 if (r < 0)
253 return -errno;
254 }
255
256 return 0;
257 }
258
259 int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) {
260 struct ethtool_wolinfo ecmd = {
261 .cmd = ETHTOOL_GWOL
262 };
263 struct ifreq ifr = {
264 .ifr_data = (void*) &ecmd
265 };
266 bool need_update = false;
267 int r;
268
269 if (wol == _WOL_INVALID)
270 return 0;
271
272 if (*fd < 0) {
273 r = ethtool_connect_or_warn(fd, true);
274 if (r < 0)
275 return r;
276 }
277
278 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
279
280 r = ioctl(*fd, SIOCETHTOOL, &ifr);
281 if (r < 0)
282 return -errno;
283
284 switch (wol) {
285 case WOL_PHY:
286 if (ecmd.wolopts != WAKE_PHY) {
287 ecmd.wolopts = WAKE_PHY;
288 need_update = true;
289 }
290 break;
291 case WOL_UCAST:
292 if (ecmd.wolopts != WAKE_UCAST) {
293 ecmd.wolopts = WAKE_UCAST;
294 need_update = true;
295 }
296 break;
297 case WOL_MCAST:
298 if (ecmd.wolopts != WAKE_MCAST) {
299 ecmd.wolopts = WAKE_MCAST;
300 need_update = true;
301 }
302 break;
303 case WOL_BCAST:
304 if (ecmd.wolopts != WAKE_BCAST) {
305 ecmd.wolopts = WAKE_BCAST;
306 need_update = true;
307 }
308 break;
309 case WOL_ARP:
310 if (ecmd.wolopts != WAKE_ARP) {
311 ecmd.wolopts = WAKE_ARP;
312 need_update = true;
313 }
314 break;
315 case WOL_MAGIC:
316 if (ecmd.wolopts != WAKE_MAGIC) {
317 ecmd.wolopts = WAKE_MAGIC;
318 need_update = true;
319 }
320 break;
321 case WOL_MAGICSECURE:
322 if (ecmd.wolopts != WAKE_MAGICSECURE) {
323 ecmd.wolopts = WAKE_MAGICSECURE;
324 need_update = true;
325 }
326 break;
327 case WOL_OFF:
328 if (ecmd.wolopts != 0) {
329 ecmd.wolopts = 0;
330 need_update = true;
331 }
332 break;
333 default:
334 break;
335 }
336
337 if (need_update) {
338 ecmd.cmd = ETHTOOL_SWOL;
339
340 r = ioctl(*fd, SIOCETHTOOL, &ifr);
341 if (r < 0)
342 return -errno;
343 }
344
345 return 0;
346 }
347
348 static int get_stringset(int fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) {
349 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
350 struct {
351 struct ethtool_sset_info info;
352 uint32_t space;
353 } buffer = {
354 .info = {
355 .cmd = ETHTOOL_GSSET_INFO,
356 .sset_mask = UINT64_C(1) << stringset_id,
357 },
358 };
359 unsigned len;
360 int r;
361
362 ifr->ifr_data = (void *) &buffer.info;
363
364 r = ioctl(fd, SIOCETHTOOL, ifr);
365 if (r < 0)
366 return -errno;
367
368 if (!buffer.info.sset_mask)
369 return -EINVAL;
370
371 len = buffer.info.data[0];
372
373 strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN);
374 if (!strings)
375 return -ENOMEM;
376
377 strings->cmd = ETHTOOL_GSTRINGS;
378 strings->string_set = stringset_id;
379 strings->len = len;
380
381 ifr->ifr_data = (void *) strings;
382
383 r = ioctl(fd, SIOCETHTOOL, ifr);
384 if (r < 0)
385 return -errno;
386
387 *gstrings = TAKE_PTR(strings);
388
389 return 0;
390 }
391
392 static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) {
393 unsigned i;
394
395 for (i = 0; i < strings->len; i++) {
396 if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature))
397 return i;
398 }
399
400 return -ENODATA;
401 }
402
403 int ethtool_set_features(int *fd, const char *ifname, int *features) {
404 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
405 struct ethtool_sfeatures *sfeatures;
406 int block, bit, i, r;
407 struct ifreq ifr = {};
408
409 if (*fd < 0) {
410 r = ethtool_connect_or_warn(fd, true);
411 if (r < 0)
412 return r;
413 }
414
415 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
416
417 r = get_stringset(*fd, &ifr, ETH_SS_FEATURES, &strings);
418 if (r < 0)
419 return log_warning_errno(r, "ethtool: could not get ethtool features for %s", ifname);
420
421 sfeatures = alloca0(sizeof(struct ethtool_sfeatures) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
422 sfeatures->cmd = ETHTOOL_SFEATURES;
423 sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
424
425 for (i = 0; i < _NET_DEV_FEAT_MAX; i++) {
426
427 if (features[i] != -1) {
428
429 r = find_feature_index(strings, netdev_feature_table[i]);
430 if (r < 0) {
431 log_warning_errno(r, "ethtool: could not find feature: %s", netdev_feature_table[i]);
432 continue;
433 }
434
435 block = r / 32;
436 bit = r % 32;
437
438 sfeatures->features[block].valid |= 1 << bit;
439
440 if (features[i])
441 sfeatures->features[block].requested |= 1 << bit;
442 else
443 sfeatures->features[block].requested &= ~(1 << bit);
444 }
445 }
446
447 ifr.ifr_data = (void *) sfeatures;
448
449 r = ioctl(*fd, SIOCETHTOOL, &ifr);
450 if (r < 0)
451 return log_warning_errno(r, "ethtool: could not set ethtool features for %s", ifname);
452
453 return 0;
454 }
455
456 static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **g) {
457 struct ecmd {
458 struct ethtool_link_settings req;
459 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
460 } ecmd = {
461 .req.cmd = ETHTOOL_GLINKSETTINGS,
462 };
463 struct ethtool_link_usettings *u;
464 unsigned offset;
465 int r;
466
467 /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS
468 handshake first to agree on the length of the link mode bitmaps. If kernel doesn't
469 agree with user, it returns the bitmap length it is expecting from user as a negative
470 length (and cmd field is 0). When kernel and user agree, kernel returns valid info in
471 all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on
472 https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf
473 */
474
475 ifr->ifr_data = (void *) &ecmd;
476
477 r = ioctl(fd, SIOCETHTOOL, ifr);
478 if (r < 0)
479 return -errno;
480
481 if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
482 return -EOPNOTSUPP;
483
484 ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
485
486 ifr->ifr_data = (void *) &ecmd;
487
488 r = ioctl(fd, SIOCETHTOOL, ifr);
489 if (r < 0)
490 return -errno;
491
492 if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
493 return -EOPNOTSUPP;
494
495 u = new(struct ethtool_link_usettings, 1);
496 if (!u)
497 return -ENOMEM;
498
499 *u = (struct ethtool_link_usettings) {
500 .base = ecmd.req,
501 };
502
503 offset = 0;
504 memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
505
506 offset += ecmd.req.link_mode_masks_nwords;
507 memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
508
509 offset += ecmd.req.link_mode_masks_nwords;
510 memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
511
512 *g = u;
513
514 return 0;
515 }
516
517 static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
518 struct ethtool_link_usettings *e;
519 struct ethtool_cmd ecmd = {
520 .cmd = ETHTOOL_GSET,
521 };
522 int r;
523
524 ifr->ifr_data = (void *) &ecmd;
525
526 r = ioctl(fd, SIOCETHTOOL, ifr);
527 if (r < 0)
528 return -errno;
529
530 e = new(struct ethtool_link_usettings, 1);
531 if (!e)
532 return -ENOMEM;
533
534 *e = (struct ethtool_link_usettings) {
535 .base.cmd = ETHTOOL_GSET,
536 .base.link_mode_masks_nwords = 1,
537 .base.speed = ethtool_cmd_speed(&ecmd),
538 .base.duplex = ecmd.duplex,
539 .base.port = ecmd.port,
540 .base.phy_address = ecmd.phy_address,
541 .base.autoneg = ecmd.autoneg,
542 .base.mdio_support = ecmd.mdio_support,
543
544 .link_modes.supported[0] = ecmd.supported,
545 .link_modes.advertising[0] = ecmd.advertising,
546 .link_modes.lp_advertising[0] = ecmd.lp_advertising,
547 };
548
549 *u = e;
550
551 return 0;
552 }
553
554 static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
555 struct {
556 struct ethtool_link_settings req;
557 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
558 } ecmd = {};
559 unsigned offset;
560 int r;
561
562 if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
563 return -EINVAL;
564
565 ecmd.req = u->base;
566 ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
567 offset = 0;
568 memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords);
569
570 offset += ecmd.req.link_mode_masks_nwords;
571 memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords);
572
573 offset += ecmd.req.link_mode_masks_nwords;
574 memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords);
575
576 ifr->ifr_data = (void *) &ecmd;
577
578 r = ioctl(fd, SIOCETHTOOL, ifr);
579 if (r < 0)
580 return -errno;
581
582 return 0;
583 }
584
585 static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
586 struct ethtool_cmd ecmd = {
587 .cmd = ETHTOOL_SSET,
588 };
589 int r;
590
591 if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0)
592 return -EINVAL;
593
594 ecmd.supported = u->link_modes.supported[0];
595 ecmd.advertising = u->link_modes.advertising[0];
596 ecmd.lp_advertising = u->link_modes.lp_advertising[0];
597
598 ethtool_cmd_speed_set(&ecmd, u->base.speed);
599
600 ecmd.duplex = u->base.duplex;
601 ecmd.port = u->base.port;
602 ecmd.phy_address = u->base.phy_address;
603 ecmd.autoneg = u->base.autoneg;
604 ecmd.mdio_support = u->base.mdio_support;
605 ecmd.eth_tp_mdix = u->base.eth_tp_mdix;
606 ecmd.eth_tp_mdix_ctrl = u->base.eth_tp_mdix_ctrl;
607
608 ifr->ifr_data = (void *) &ecmd;
609
610 r = ioctl(fd, SIOCETHTOOL, ifr);
611 if (r < 0)
612 return -errno;
613
614 return 0;
615 }
616
617 /* If autonegotiation is disabled, the speed and duplex represent the fixed link
618 * mode and are writable if the driver supports multiple link modes. If it is
619 * enabled then they are read-only. If the link is up they represent the negotiated
620 * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
621 * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
622 */
623 int ethtool_set_glinksettings(
624 int *fd,
625 const char *ifname,
626 int autonegotiation,
627 uint32_t advertise[static N_ADVERTISE],
628 size_t speed,
629 Duplex duplex,
630 NetDevPort port) {
631 _cleanup_free_ struct ethtool_link_usettings *u = NULL;
632 struct ifreq ifr = {};
633 int r;
634
635 if (autonegotiation != AUTONEG_DISABLE && memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
636 log_info("ethtool: autonegotiation is unset or enabled, the speed and duplex are not writable.");
637 return 0;
638 }
639
640 if (*fd < 0) {
641 r = ethtool_connect_or_warn(fd, true);
642 if (r < 0)
643 return r;
644 }
645
646 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
647
648 r = get_glinksettings(*fd, &ifr, &u);
649 if (r < 0) {
650 r = get_gset(*fd, &ifr, &u);
651 if (r < 0)
652 return log_warning_errno(r, "ethtool: Cannot get device settings for %s : %m", ifname);
653 }
654
655 if (speed > 0)
656 u->base.speed = DIV_ROUND_UP(speed, 1000000);
657
658 if (duplex != _DUP_INVALID)
659 u->base.duplex = duplex;
660
661 if (port != _NET_DEV_PORT_INVALID)
662 u->base.port = port;
663
664 if (autonegotiation >= 0)
665 u->base.autoneg = autonegotiation;
666
667 if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
668 u->base.autoneg = AUTONEG_ENABLE;
669 memcpy(&u->link_modes.advertising, advertise, sizeof(uint32_t) * N_ADVERTISE);
670 memzero((uint8_t*) &u->link_modes.advertising + sizeof(uint32_t) * N_ADVERTISE,
671 ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(uint32_t) * N_ADVERTISE);
672 }
673
674 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
675 r = set_slinksettings(*fd, &ifr, u);
676 else
677 r = set_sset(*fd, &ifr, u);
678 if (r < 0)
679 return log_warning_errno(r, "ethtool: Cannot set device settings for %s : %m", ifname);
680
681 return r;
682 }
683
684 int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) {
685 struct ethtool_channels ecmd = {
686 .cmd = ETHTOOL_GCHANNELS
687 };
688 struct ifreq ifr = {
689 .ifr_data = (void*) &ecmd
690 };
691
692 bool need_update = false;
693 int r;
694
695 if (*fd < 0) {
696 r = ethtool_connect_or_warn(fd, true);
697 if (r < 0)
698 return r;
699 }
700
701 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
702
703 r = ioctl(*fd, SIOCETHTOOL, &ifr);
704 if (r < 0)
705 return -errno;
706
707 if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) {
708 ecmd.rx_count = channels->rx_count;
709 need_update = true;
710 }
711
712 if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) {
713 ecmd.tx_count = channels->tx_count;
714 need_update = true;
715 }
716
717 if (channels->other_count_set && ecmd.other_count != channels->other_count) {
718 ecmd.other_count = channels->other_count;
719 need_update = true;
720 }
721
722 if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) {
723 ecmd.combined_count = channels->combined_count;
724 need_update = true;
725 }
726
727 if (need_update) {
728 ecmd.cmd = ETHTOOL_SCHANNELS;
729
730 r = ioctl(*fd, SIOCETHTOOL, &ifr);
731 if (r < 0)
732 return -errno;
733 }
734
735 return 0;
736 }
737
738 int config_parse_channel(const char *unit,
739 const char *filename,
740 unsigned line,
741 const char *section,
742 unsigned section_line,
743 const char *lvalue,
744 int ltype,
745 const char *rvalue,
746 void *data,
747 void *userdata) {
748 netdev_channels *channels = data;
749 uint32_t k;
750 int r;
751
752 assert(filename);
753 assert(section);
754 assert(lvalue);
755 assert(rvalue);
756 assert(data);
757
758 r = safe_atou32(rvalue, &k);
759 if (r < 0) {
760 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
761 return 0;
762 }
763
764 if (k < 1) {
765 log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
766 return 0;
767 }
768
769 if (streq(lvalue, "RxChannels")) {
770 channels->rx_count = k;
771 channels->rx_count_set = true;
772 } else if (streq(lvalue, "TxChannels")) {
773 channels->tx_count = k;
774 channels->tx_count_set = true;
775 } else if (streq(lvalue, "OtherChannels")) {
776 channels->other_count = k;
777 channels->other_count_set = true;
778 } else if (streq(lvalue, "CombinedChannels")) {
779 channels->combined_count = k;
780 channels->combined_count_set = true;
781 }
782
783 return 0;
784 }
785
786 int config_parse_advertise(const char *unit,
787 const char *filename,
788 unsigned line,
789 const char *section,
790 unsigned section_line,
791 const char *lvalue,
792 int ltype,
793 const char *rvalue,
794 void *data,
795 void *userdata) {
796 uint32_t *advertise = data;
797 const char *p;
798 int r;
799
800 assert(filename);
801 assert(section);
802 assert(lvalue);
803 assert(rvalue);
804 assert(data);
805
806 if (isempty(rvalue)) {
807 /* Empty string resets the value. */
808 memzero(advertise, sizeof(uint32_t) * N_ADVERTISE);
809 return 0;
810 }
811
812 for (p = rvalue;;) {
813 _cleanup_free_ char *w = NULL;
814 enum ethtool_link_mode_bit_indices mode;
815
816 r = extract_first_word(&p, &w, NULL, 0);
817 if (r == -ENOMEM)
818 return log_oom();
819 if (r < 0) {
820 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split advertise modes '%s', ignoring: %m", rvalue);
821 break;
822 }
823 if (r == 0)
824 break;
825
826 mode = ethtool_link_mode_bit_from_string(w);
827 /* We reuse the kernel provided enum which does not contain negative value. So, the cast
828 * below is mandatory. Otherwise, the check below always passes and access an invalid address. */
829 if ((int) mode < 0) {
830 log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w);
831 continue;
832 }
833
834 advertise[mode / 32] |= 1UL << (mode % 32);
835 }
836
837 return 0;
838 }