]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/net/ethtool-util.c
Merge pull request #11827 from keszybz/pkgconfig-variables
[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[] = {
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 -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, "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 != AUTONEG_DISABLE && eqzero(link->advertise)) {
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 if (link->autonegotiation >= 0)
616 u->base.autoneg = link->autonegotiation;
617
618 if (!eqzero(link->advertise)) {
619 u->base.autoneg = AUTONEG_ENABLE;
620 memcpy(&u->link_modes.advertising, link->advertise, sizeof(link->advertise));
621 memzero((uint8_t*) &u->link_modes.advertising + sizeof(link->advertise),
622 ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(link->advertise));
623 }
624
625 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
626 r = set_slinksettings(*fd, &ifr, u);
627 else
628 r = set_sset(*fd, &ifr, u);
629 if (r < 0)
630 return log_warning_errno(r, "link_config: Cannot set device settings for %s : %m", ifname);
631
632 return r;
633 }
634
635 int config_parse_channel(const char *unit,
636 const char *filename,
637 unsigned line,
638 const char *section,
639 unsigned section_line,
640 const char *lvalue,
641 int ltype,
642 const char *rvalue,
643 void *data,
644 void *userdata) {
645 link_config *config = data;
646 uint32_t k;
647 int r;
648
649 assert(filename);
650 assert(section);
651 assert(lvalue);
652 assert(rvalue);
653 assert(data);
654
655 r = safe_atou32(rvalue, &k);
656 if (r < 0) {
657 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
658 return 0;
659 }
660
661 if (k < 1) {
662 log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
663 return 0;
664 }
665
666 if (streq(lvalue, "RxChannels")) {
667 config->channels.rx_count = k;
668 config->channels.rx_count_set = true;
669 } else if (streq(lvalue, "TxChannels")) {
670 config->channels.tx_count = k;
671 config->channels.tx_count_set = true;
672 } else if (streq(lvalue, "OtherChannels")) {
673 config->channels.other_count = k;
674 config->channels.other_count_set = true;
675 } else if (streq(lvalue, "CombinedChannels")) {
676 config->channels.combined_count = k;
677 config->channels.combined_count_set = true;
678 }
679
680 return 0;
681 }
682
683 int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) {
684 struct ethtool_channels ecmd = {
685 .cmd = ETHTOOL_GCHANNELS
686 };
687 struct ifreq ifr = {
688 .ifr_data = (void*) &ecmd
689 };
690
691 bool need_update = false;
692 int r;
693
694 if (*fd < 0) {
695 r = ethtool_connect(fd);
696 if (r < 0)
697 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
698 }
699
700 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
701
702 r = ioctl(*fd, SIOCETHTOOL, &ifr);
703 if (r < 0)
704 return -errno;
705
706 if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) {
707 ecmd.rx_count = channels->rx_count;
708 need_update = true;
709 }
710
711 if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) {
712 ecmd.tx_count = channels->tx_count;
713 need_update = true;
714 }
715
716 if (channels->other_count_set && ecmd.other_count != channels->other_count) {
717 ecmd.other_count = channels->other_count;
718 need_update = true;
719 }
720
721 if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) {
722 ecmd.combined_count = channels->combined_count;
723 need_update = true;
724 }
725
726 if (need_update) {
727 ecmd.cmd = ETHTOOL_SCHANNELS;
728
729 r = ioctl(*fd, SIOCETHTOOL, &ifr);
730 if (r < 0)
731 return -errno;
732 }
733
734 return 0;
735 }
736
737 int config_parse_advertise(const char *unit,
738 const char *filename,
739 unsigned line,
740 const char *section,
741 unsigned section_line,
742 const char *lvalue,
743 int ltype,
744 const char *rvalue,
745 void *data,
746 void *userdata) {
747 link_config *config = data;
748 const char *p;
749 int r;
750
751 assert(filename);
752 assert(section);
753 assert(lvalue);
754 assert(rvalue);
755 assert(data);
756
757 if (isempty(rvalue)) {
758 /* Empty string resets the value. */
759 zero(config->advertise);
760 return 0;
761 }
762
763 for (p = rvalue;;) {
764 _cleanup_free_ char *w = NULL;
765 enum ethtool_link_mode_bit_indices mode;
766
767 r = extract_first_word(&p, &w, NULL, 0);
768 if (r == -ENOMEM)
769 return log_oom();
770 if (r < 0) {
771 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split advertise modes '%s', ignoring: %m", rvalue);
772 break;
773 }
774 if (r == 0)
775 break;
776
777 mode = ethtool_link_mode_bit_from_string(w);
778 /* We reuse the kernel provided enum which does not contain negative value. So, the cast
779 * below is mandatory. Otherwise, the check below always passes and access an invalid address. */
780 if ((int) mode < 0) {
781 log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w);
782 continue;
783 }
784
785 config->advertise[mode / 32] |= 1UL << (mode % 32);
786 }
787
788 return 0;
789 }