]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ethtool-util.c
ethtool-util: use structured initializers
[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 = new(struct ethtool_link_usettings, 1);
457 if (!u)
458 return -ENOMEM;
459
460 *u = (struct ethtool_link_usettings) {
461 .base = ecmd.req,
462 };
463
464 offset = 0;
465 memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
466
467 offset += ecmd.req.link_mode_masks_nwords;
468 memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
469
470 offset += ecmd.req.link_mode_masks_nwords;
471 memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
472
473 *g = u;
474
475 return 0;
476 }
477
478 static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
479 struct ethtool_link_usettings *e;
480 struct ethtool_cmd ecmd = {
481 .cmd = ETHTOOL_GSET,
482 };
483 int r;
484
485 ifr->ifr_data = (void *) &ecmd;
486
487 r = ioctl(fd, SIOCETHTOOL, ifr);
488 if (r < 0)
489 return -errno;
490
491 e = new(struct ethtool_link_usettings, 1);
492 if (!e)
493 return -ENOMEM;
494
495 *e = (struct ethtool_link_usettings) {
496 .base.cmd = ETHTOOL_GSET,
497 .base.link_mode_masks_nwords = 1,
498 .base.speed = ethtool_cmd_speed(&ecmd),
499 .base.duplex = ecmd.duplex,
500 .base.port = ecmd.port,
501 .base.phy_address = ecmd.phy_address,
502 .base.autoneg = ecmd.autoneg,
503 .base.mdio_support = ecmd.mdio_support,
504
505 .link_modes.supported[0] = ecmd.supported,
506 .link_modes.advertising[0] = ecmd.advertising,
507 .link_modes.lp_advertising[0] = ecmd.lp_advertising,
508 };
509
510 *u = e;
511
512 return 0;
513 }
514
515 static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
516 struct {
517 struct ethtool_link_settings req;
518 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
519 } ecmd = {};
520 unsigned offset;
521 int r;
522
523 if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
524 return -EINVAL;
525
526 ecmd.req = u->base;
527 ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
528 offset = 0;
529 memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 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.advertising, 4 * ecmd.req.link_mode_masks_nwords);
533
534 offset += ecmd.req.link_mode_masks_nwords;
535 memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords);
536
537 ifr->ifr_data = (void *) &ecmd;
538
539 r = ioctl(fd, SIOCETHTOOL, ifr);
540 if (r < 0)
541 return -errno;
542
543 return 0;
544 }
545
546 static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
547 struct ethtool_cmd ecmd = {
548 .cmd = ETHTOOL_SSET,
549 };
550 int r;
551
552 if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0)
553 return -EINVAL;
554
555 ecmd.supported = u->link_modes.supported[0];
556 ecmd.advertising = u->link_modes.advertising[0];
557 ecmd.lp_advertising = u->link_modes.lp_advertising[0];
558
559 ethtool_cmd_speed_set(&ecmd, u->base.speed);
560
561 ecmd.duplex = u->base.duplex;
562 ecmd.port = u->base.port;
563 ecmd.phy_address = u->base.phy_address;
564 ecmd.autoneg = u->base.autoneg;
565 ecmd.mdio_support = u->base.mdio_support;
566 ecmd.eth_tp_mdix = u->base.eth_tp_mdix;
567 ecmd.eth_tp_mdix_ctrl = u->base.eth_tp_mdix_ctrl;
568
569 ifr->ifr_data = (void *) &ecmd;
570
571 r = ioctl(fd, SIOCETHTOOL, ifr);
572 if (r < 0)
573 return -errno;
574
575 return 0;
576 }
577
578 /* If autonegotiation is disabled, the speed and duplex represent the fixed link
579 * mode and are writable if the driver supports multiple link modes. If it is
580 * enabled then they are read-only. If the link is up they represent the negotiated
581 * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
582 * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
583 */
584 int ethtool_set_glinksettings(
585 int *fd,
586 const char *ifname,
587 int autonegotiation,
588 uint32_t advertise[static N_ADVERTISE],
589 size_t speed,
590 Duplex duplex,
591 NetDevPort port) {
592 _cleanup_free_ struct ethtool_link_usettings *u = NULL;
593 struct ifreq ifr = {};
594 int r;
595
596 if (autonegotiation != AUTONEG_DISABLE && memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
597 log_info("ethtool: autonegotiation is unset or enabled, the speed and duplex are not writable.");
598 return 0;
599 }
600
601 if (*fd < 0) {
602 r = ethtool_connect(fd);
603 if (r < 0)
604 return log_warning_errno(r, "ethtool: could not connect to ethtool: %m");
605 }
606
607 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
608
609 r = get_glinksettings(*fd, &ifr, &u);
610 if (r < 0) {
611 r = get_gset(*fd, &ifr, &u);
612 if (r < 0)
613 return log_warning_errno(r, "ethtool: Cannot get device settings for %s : %m", ifname);
614 }
615
616 if (speed > 0)
617 u->base.speed = DIV_ROUND_UP(speed, 1000000);
618
619 if (duplex != _DUP_INVALID)
620 u->base.duplex = duplex;
621
622 if (port != _NET_DEV_PORT_INVALID)
623 u->base.port = port;
624
625 if (autonegotiation >= 0)
626 u->base.autoneg = autonegotiation;
627
628 if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
629 u->base.autoneg = AUTONEG_ENABLE;
630 memcpy(&u->link_modes.advertising, advertise, sizeof(uint32_t) * N_ADVERTISE);
631 memzero((uint8_t*) &u->link_modes.advertising + sizeof(uint32_t) * N_ADVERTISE,
632 ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(uint32_t) * N_ADVERTISE);
633 }
634
635 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
636 r = set_slinksettings(*fd, &ifr, u);
637 else
638 r = set_sset(*fd, &ifr, u);
639 if (r < 0)
640 return log_warning_errno(r, "ethtool: Cannot set device settings for %s : %m", ifname);
641
642 return r;
643 }
644
645 int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) {
646 struct ethtool_channels ecmd = {
647 .cmd = ETHTOOL_GCHANNELS
648 };
649 struct ifreq ifr = {
650 .ifr_data = (void*) &ecmd
651 };
652
653 bool need_update = false;
654 int r;
655
656 if (*fd < 0) {
657 r = ethtool_connect(fd);
658 if (r < 0)
659 return log_warning_errno(r, "ethtool: could not connect to ethtool: %m");
660 }
661
662 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
663
664 r = ioctl(*fd, SIOCETHTOOL, &ifr);
665 if (r < 0)
666 return -errno;
667
668 if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) {
669 ecmd.rx_count = channels->rx_count;
670 need_update = true;
671 }
672
673 if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) {
674 ecmd.tx_count = channels->tx_count;
675 need_update = true;
676 }
677
678 if (channels->other_count_set && ecmd.other_count != channels->other_count) {
679 ecmd.other_count = channels->other_count;
680 need_update = true;
681 }
682
683 if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) {
684 ecmd.combined_count = channels->combined_count;
685 need_update = true;
686 }
687
688 if (need_update) {
689 ecmd.cmd = ETHTOOL_SCHANNELS;
690
691 r = ioctl(*fd, SIOCETHTOOL, &ifr);
692 if (r < 0)
693 return -errno;
694 }
695
696 return 0;
697 }
698
699 int config_parse_channel(const char *unit,
700 const char *filename,
701 unsigned line,
702 const char *section,
703 unsigned section_line,
704 const char *lvalue,
705 int ltype,
706 const char *rvalue,
707 void *data,
708 void *userdata) {
709 netdev_channels *channels = data;
710 uint32_t k;
711 int r;
712
713 assert(filename);
714 assert(section);
715 assert(lvalue);
716 assert(rvalue);
717 assert(data);
718
719 r = safe_atou32(rvalue, &k);
720 if (r < 0) {
721 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
722 return 0;
723 }
724
725 if (k < 1) {
726 log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
727 return 0;
728 }
729
730 if (streq(lvalue, "RxChannels")) {
731 channels->rx_count = k;
732 channels->rx_count_set = true;
733 } else if (streq(lvalue, "TxChannels")) {
734 channels->tx_count = k;
735 channels->tx_count_set = true;
736 } else if (streq(lvalue, "OtherChannels")) {
737 channels->other_count = k;
738 channels->other_count_set = true;
739 } else if (streq(lvalue, "CombinedChannels")) {
740 channels->combined_count = k;
741 channels->combined_count_set = true;
742 }
743
744 return 0;
745 }
746
747 int config_parse_advertise(const char *unit,
748 const char *filename,
749 unsigned line,
750 const char *section,
751 unsigned section_line,
752 const char *lvalue,
753 int ltype,
754 const char *rvalue,
755 void *data,
756 void *userdata) {
757 uint32_t *advertise = data;
758 const char *p;
759 int r;
760
761 assert(filename);
762 assert(section);
763 assert(lvalue);
764 assert(rvalue);
765 assert(data);
766
767 if (isempty(rvalue)) {
768 /* Empty string resets the value. */
769 memzero(advertise, sizeof(uint32_t) * N_ADVERTISE);
770 return 0;
771 }
772
773 for (p = rvalue;;) {
774 _cleanup_free_ char *w = NULL;
775 enum ethtool_link_mode_bit_indices mode;
776
777 r = extract_first_word(&p, &w, NULL, 0);
778 if (r == -ENOMEM)
779 return log_oom();
780 if (r < 0) {
781 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split advertise modes '%s', ignoring: %m", rvalue);
782 break;
783 }
784 if (r == 0)
785 break;
786
787 mode = ethtool_link_mode_bit_from_string(w);
788 /* We reuse the kernel provided enum which does not contain negative value. So, the cast
789 * below is mandatory. Otherwise, the check below always passes and access an invalid address. */
790 if ((int) mode < 0) {
791 log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w);
792 continue;
793 }
794
795 advertise[mode / 32] |= 1UL << (mode % 32);
796 }
797
798 return 0;
799 }