]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ethtool-util.c
0b0fb47212ff99af553e3dae53fd20577f34335b
[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 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, "ethtool: 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, "ethtool: 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, "ethtool: 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 -ENODATA;
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, "ethtool: 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, "ethtool: 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, "ethtool: 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, "ethtool: 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(
582 int *fd,
583 const char *ifname,
584 int autonegotiation,
585 uint32_t advertise[static N_ADVERTISE],
586 size_t speed,
587 Duplex duplex,
588 NetDevPort port) {
589 _cleanup_free_ struct ethtool_link_usettings *u = NULL;
590 struct ifreq ifr = {};
591 int r;
592
593 if (autonegotiation != AUTONEG_DISABLE && memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
594 log_info("ethtool: autonegotiation is unset or enabled, the speed and duplex are not writable.");
595 return 0;
596 }
597
598 if (*fd < 0) {
599 r = ethtool_connect(fd);
600 if (r < 0)
601 return log_warning_errno(r, "ethtool: could not connect to ethtool: %m");
602 }
603
604 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
605
606 r = get_glinksettings(*fd, &ifr, &u);
607 if (r < 0) {
608 r = get_gset(*fd, &ifr, &u);
609 if (r < 0)
610 return log_warning_errno(r, "ethtool: Cannot get device settings for %s : %m", ifname);
611 }
612
613 if (speed > 0)
614 u->base.speed = DIV_ROUND_UP(speed, 1000000);
615
616 if (duplex != _DUP_INVALID)
617 u->base.duplex = duplex;
618
619 if (port != _NET_DEV_PORT_INVALID)
620 u->base.port = port;
621
622 if (autonegotiation >= 0)
623 u->base.autoneg = autonegotiation;
624
625 if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
626 u->base.autoneg = AUTONEG_ENABLE;
627 memcpy(&u->link_modes.advertising, advertise, sizeof(uint32_t) * N_ADVERTISE);
628 memzero((uint8_t*) &u->link_modes.advertising + sizeof(uint32_t) * N_ADVERTISE,
629 ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(uint32_t) * N_ADVERTISE);
630 }
631
632 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
633 r = set_slinksettings(*fd, &ifr, u);
634 else
635 r = set_sset(*fd, &ifr, u);
636 if (r < 0)
637 return log_warning_errno(r, "ethtool: Cannot set device settings for %s : %m", ifname);
638
639 return r;
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, "ethtool: 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_channel(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 netdev_channels *channels = data;
707 uint32_t k;
708 int r;
709
710 assert(filename);
711 assert(section);
712 assert(lvalue);
713 assert(rvalue);
714 assert(data);
715
716 r = safe_atou32(rvalue, &k);
717 if (r < 0) {
718 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
719 return 0;
720 }
721
722 if (k < 1) {
723 log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
724 return 0;
725 }
726
727 if (streq(lvalue, "RxChannels")) {
728 channels->rx_count = k;
729 channels->rx_count_set = true;
730 } else if (streq(lvalue, "TxChannels")) {
731 channels->tx_count = k;
732 channels->tx_count_set = true;
733 } else if (streq(lvalue, "OtherChannels")) {
734 channels->other_count = k;
735 channels->other_count_set = true;
736 } else if (streq(lvalue, "CombinedChannels")) {
737 channels->combined_count = k;
738 channels->combined_count_set = true;
739 }
740
741 return 0;
742 }
743
744 int config_parse_advertise(const char *unit,
745 const char *filename,
746 unsigned line,
747 const char *section,
748 unsigned section_line,
749 const char *lvalue,
750 int ltype,
751 const char *rvalue,
752 void *data,
753 void *userdata) {
754 uint32_t *advertise = data;
755 const char *p;
756 int r;
757
758 assert(filename);
759 assert(section);
760 assert(lvalue);
761 assert(rvalue);
762 assert(data);
763
764 if (isempty(rvalue)) {
765 /* Empty string resets the value. */
766 memzero(advertise, sizeof(uint32_t) * N_ADVERTISE);
767 return 0;
768 }
769
770 for (p = rvalue;;) {
771 _cleanup_free_ char *w = NULL;
772 enum ethtool_link_mode_bit_indices mode;
773
774 r = extract_first_word(&p, &w, NULL, 0);
775 if (r == -ENOMEM)
776 return log_oom();
777 if (r < 0) {
778 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split advertise modes '%s', ignoring: %m", rvalue);
779 break;
780 }
781 if (r == 0)
782 break;
783
784 mode = ethtool_link_mode_bit_from_string(w);
785 /* We reuse the kernel provided enum which does not contain negative value. So, the cast
786 * below is mandatory. Otherwise, the check below always passes and access an invalid address. */
787 if ((int) mode < 0) {
788 log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w);
789 continue;
790 }
791
792 advertise[mode / 32] |= 1UL << (mode % 32);
793 }
794
795 return 0;
796 }