]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ethtool-util.c
Merge pull request #14592 from keszybz/simplifications
[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/netdevice.h>
7 #include <linux/sockios.h>
8
9 #include "conf-parser.h"
10 #include "ethtool-util.h"
11 #include "extract-word.h"
12 #include "fd-util.h"
13 #include "log.h"
14 #include "memory-util.h"
15 #include "socket-util.h"
16 #include "string-table.h"
17 #include "strxcpyx.h"
18
19 static const char* const duplex_table[_DUP_MAX] = {
20 [DUP_FULL] = "full",
21 [DUP_HALF] = "half"
22 };
23
24 DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
25 DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
26
27 static const char* const wol_table[_WOL_MAX] = {
28 [WOL_PHY] = "phy",
29 [WOL_UCAST] = "unicast",
30 [WOL_MCAST] = "multicast",
31 [WOL_BCAST] = "broadcast",
32 [WOL_ARP] = "arp",
33 [WOL_MAGIC] = "magic",
34 [WOL_MAGICSECURE] = "secureon",
35 [WOL_OFF] = "off",
36 };
37
38 DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan);
39 DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting");
40
41 static const char* const port_table[] = {
42 [NET_DEV_PORT_TP] = "tp",
43 [NET_DEV_PORT_AUI] = "aui",
44 [NET_DEV_PORT_MII] = "mii",
45 [NET_DEV_PORT_FIBRE] = "fibre",
46 [NET_DEV_PORT_BNC] = "bnc",
47 };
48
49 DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort);
50 DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting");
51
52 static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = {
53 [NET_DEV_FEAT_GSO] = "tx-generic-segmentation",
54 [NET_DEV_FEAT_GRO] = "rx-gro",
55 [NET_DEV_FEAT_LRO] = "rx-lro",
56 [NET_DEV_FEAT_TSO] = "tx-tcp-segmentation",
57 [NET_DEV_FEAT_TSO6] = "tx-tcp6-segmentation",
58 };
59
60 static const char* const ethtool_link_mode_bit_table[] = {
61 [ETHTOOL_LINK_MODE_10baseT_Half_BIT] = "10baset-half",
62 [ETHTOOL_LINK_MODE_10baseT_Full_BIT] = "10baset-full",
63 [ETHTOOL_LINK_MODE_100baseT_Half_BIT] = "100baset-half",
64 [ETHTOOL_LINK_MODE_100baseT_Full_BIT] = "100baset-full",
65 [ETHTOOL_LINK_MODE_1000baseT_Half_BIT] = "1000baset-half",
66 [ETHTOOL_LINK_MODE_1000baseT_Full_BIT] = "1000baset-full",
67 [ETHTOOL_LINK_MODE_Autoneg_BIT] = "autonegotiation",
68 [ETHTOOL_LINK_MODE_TP_BIT] = "tp",
69 [ETHTOOL_LINK_MODE_AUI_BIT] = "aui",
70 [ETHTOOL_LINK_MODE_MII_BIT] = "mii",
71 [ETHTOOL_LINK_MODE_FIBRE_BIT] = "fibre",
72 [ETHTOOL_LINK_MODE_BNC_BIT] = "bnc",
73 [ETHTOOL_LINK_MODE_10000baseT_Full_BIT] = "10000baset-full",
74 [ETHTOOL_LINK_MODE_Pause_BIT] = "pause",
75 [ETHTOOL_LINK_MODE_Asym_Pause_BIT] = "asym-pause",
76 [ETHTOOL_LINK_MODE_2500baseX_Full_BIT] = "2500basex-full",
77 [ETHTOOL_LINK_MODE_Backplane_BIT] = "backplane",
78 [ETHTOOL_LINK_MODE_1000baseKX_Full_BIT] = "1000basekx-full",
79 [ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT] = "10000basekx4-full",
80 [ETHTOOL_LINK_MODE_10000baseKR_Full_BIT] = "10000basekr-full",
81 [ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = "10000baser-fec",
82 [ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT] = "20000basemld2-full",
83 [ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT] = "20000basekr2-full",
84 [ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT] = "40000basekr4-full",
85 [ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT] = "40000basecr4-full",
86 [ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT] = "40000basesr4-full",
87 [ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT] = "40000baselr4-full",
88 [ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT] = "56000basekr4-full",
89 [ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT] = "56000basecr4-full",
90 [ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT] = "56000basesr4-full",
91 [ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT] = "56000baselr4-full",
92 [ETHTOOL_LINK_MODE_25000baseCR_Full_BIT] = "25000basecr-full",
93 [ETHTOOL_LINK_MODE_25000baseKR_Full_BIT] = "25000basekr-full",
94 [ETHTOOL_LINK_MODE_25000baseSR_Full_BIT] = "25000basesr-full",
95 [ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT] = "50000basecr2-full",
96 [ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT] = "50000basekr2-full",
97 [ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT] = "100000basekr4-full",
98 [ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT] = "100000basesr4-full",
99 [ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT] = "100000basecr4-full",
100 [ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT] = "100000baselr4-er4-full",
101 [ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT] = "50000basesr2-full",
102 [ETHTOOL_LINK_MODE_1000baseX_Full_BIT] = "1000basex-full",
103 [ETHTOOL_LINK_MODE_10000baseCR_Full_BIT] = "10000basecr-full",
104 [ETHTOOL_LINK_MODE_10000baseSR_Full_BIT] = "10000basesr-full",
105 [ETHTOOL_LINK_MODE_10000baseLR_Full_BIT] = "10000baselr-full",
106 [ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT] = "10000baselrm-full",
107 [ETHTOOL_LINK_MODE_10000baseER_Full_BIT] = "10000baseer-full",
108 [ETHTOOL_LINK_MODE_2500baseT_Full_BIT] = "2500baset-full",
109 [ETHTOOL_LINK_MODE_5000baseT_Full_BIT] = "5000baset-full",
110 [ETHTOOL_LINK_MODE_FEC_NONE_BIT] = "fec-none",
111 [ETHTOOL_LINK_MODE_FEC_RS_BIT] = "fec-rs",
112 [ETHTOOL_LINK_MODE_FEC_BASER_BIT] = "fec-baser",
113 [ETHTOOL_LINK_MODE_50000baseKR_Full_BIT] = "50000basekr_full",
114 [ETHTOOL_LINK_MODE_50000baseSR_Full_BIT] = "50000basesr_full",
115 [ETHTOOL_LINK_MODE_50000baseCR_Full_BIT] = "50000basecr_full",
116 [ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT] = "50000baselr_er_fr_full",
117 [ETHTOOL_LINK_MODE_50000baseDR_Full_BIT] = "50000basedr_full",
118 [ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT] = "100000basekr2_full",
119 [ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT] = "100000basesr2_full",
120 [ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT] = "100000basecr2_full",
121 [ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT] = "100000baselr2_er2_fr2_full",
122 [ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT] = "100000basedr2_full",
123 [ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT] = "200000basekr4_full",
124 [ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT] = "200000basesr4_full",
125 [ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT] = "200000basecr4_full",
126 [ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT] = "200000baselr4_er4_fr4_full",
127 [ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT] = "200000basedr4_full",
128 };
129 /* Make sure the array is large enough to fit all bits */
130 assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < N_ADVERTISE);
131
132 DEFINE_STRING_TABLE_LOOKUP(ethtool_link_mode_bit, enum ethtool_link_mode_bit_indices);
133
134 static int ethtool_connect_or_warn(int *ret, bool warn) {
135 int fd;
136
137 assert_return(ret, -EINVAL);
138
139 fd = socket_ioctl_fd();
140 if (fd < 0)
141 return log_full_errno(warn ? LOG_WARNING: LOG_DEBUG, fd,
142 "ethtool: could not create control socket: %m");
143
144 *ret = fd;
145
146 return 0;
147 }
148
149 int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) {
150 struct ethtool_drvinfo ecmd = {
151 .cmd = ETHTOOL_GDRVINFO
152 };
153 struct ifreq ifr = {
154 .ifr_data = (void*) &ecmd
155 };
156 char *d;
157 int r;
158
159 if (*ethtool_fd < 0) {
160 r = ethtool_connect_or_warn(ethtool_fd, true);
161 if (r < 0)
162 return r;
163 }
164
165 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
166
167 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
168 if (r < 0)
169 return -errno;
170
171 d = strdup(ecmd.driver);
172 if (!d)
173 return -ENOMEM;
174
175 *ret = d;
176 return 0;
177 }
178
179 int ethtool_get_link_info(int *ethtool_fd, const char *ifname,
180 int *ret_autonegotiation, size_t *ret_speed,
181 Duplex *ret_duplex, NetDevPort *ret_port) {
182 struct ethtool_cmd ecmd = {
183 .cmd = ETHTOOL_GSET,
184 };
185 struct ifreq ifr = {
186 .ifr_data = (void*) &ecmd,
187 };
188 int r;
189
190 if (*ethtool_fd < 0) {
191 r = ethtool_connect_or_warn(ethtool_fd, false);
192 if (r < 0)
193 return r;
194 }
195
196 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
197
198 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
199 if (r < 0)
200 return -errno;
201
202 if (ret_autonegotiation)
203 *ret_autonegotiation = ecmd.autoneg;
204
205 if (ret_speed) {
206 uint32_t speed;
207
208 speed = ethtool_cmd_speed(&ecmd);
209 *ret_speed = speed == (uint32_t) SPEED_UNKNOWN ?
210 SIZE_MAX : (size_t) speed * 1000 * 1000;
211 }
212
213 if (ret_duplex)
214 *ret_duplex = ecmd.duplex;
215
216 if (ret_port)
217 *ret_port = ecmd.port;
218
219 return 0;
220 }
221
222 int ethtool_get_permanent_macaddr(int *ethtool_fd, const char *ifname, struct ether_addr *ret) {
223 _cleanup_close_ int fd = -1;
224 struct {
225 struct ethtool_perm_addr addr;
226 uint8_t space[MAX_ADDR_LEN];
227 } epaddr = {
228 .addr.cmd = ETHTOOL_GPERMADDR,
229 .addr.size = MAX_ADDR_LEN,
230 };
231 struct ifreq ifr = {
232 .ifr_data = (caddr_t) &epaddr,
233 };
234 int r;
235
236 assert(ifname);
237 assert(ret);
238
239 if (!ethtool_fd)
240 ethtool_fd = &fd;
241
242 if (*ethtool_fd < 0) {
243 r = ethtool_connect_or_warn(ethtool_fd, false);
244 if (r < 0)
245 return r;
246 }
247
248 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
249
250 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
251 if (r < 0)
252 return -errno;
253
254 if (epaddr.addr.size != 6)
255 return -EOPNOTSUPP;
256
257 for (size_t i = 0; i < epaddr.addr.size; i++)
258 ret->ether_addr_octet[i] = epaddr.addr.data[i];
259
260 return 0;
261 }
262
263 int ethtool_set_speed(int *ethtool_fd, const char *ifname, unsigned speed, Duplex duplex) {
264 struct ethtool_cmd ecmd = {
265 .cmd = ETHTOOL_GSET
266 };
267 struct ifreq ifr = {
268 .ifr_data = (void*) &ecmd
269 };
270 bool need_update = false;
271 int r;
272
273 if (speed == 0 && duplex == _DUP_INVALID)
274 return 0;
275
276 if (*ethtool_fd < 0) {
277 r = ethtool_connect_or_warn(ethtool_fd, true);
278 if (r < 0)
279 return r;
280 }
281
282 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
283
284 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
285 if (r < 0)
286 return -errno;
287
288 if (ethtool_cmd_speed(&ecmd) != speed) {
289 ethtool_cmd_speed_set(&ecmd, speed);
290 need_update = true;
291 }
292
293 switch (duplex) {
294 case DUP_HALF:
295 if (ecmd.duplex != DUPLEX_HALF) {
296 ecmd.duplex = DUPLEX_HALF;
297 need_update = true;
298 }
299 break;
300 case DUP_FULL:
301 if (ecmd.duplex != DUPLEX_FULL) {
302 ecmd.duplex = DUPLEX_FULL;
303 need_update = true;
304 }
305 break;
306 default:
307 break;
308 }
309
310 if (need_update) {
311 ecmd.cmd = ETHTOOL_SSET;
312
313 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
314 if (r < 0)
315 return -errno;
316 }
317
318 return 0;
319 }
320
321 int ethtool_set_wol(int *ethtool_fd, const char *ifname, WakeOnLan wol) {
322 struct ethtool_wolinfo ecmd = {
323 .cmd = ETHTOOL_GWOL
324 };
325 struct ifreq ifr = {
326 .ifr_data = (void*) &ecmd
327 };
328 bool need_update = false;
329 int r;
330
331 if (wol == _WOL_INVALID)
332 return 0;
333
334 if (*ethtool_fd < 0) {
335 r = ethtool_connect_or_warn(ethtool_fd, true);
336 if (r < 0)
337 return r;
338 }
339
340 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
341
342 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
343 if (r < 0)
344 return -errno;
345
346 switch (wol) {
347 case WOL_PHY:
348 if (ecmd.wolopts != WAKE_PHY) {
349 ecmd.wolopts = WAKE_PHY;
350 need_update = true;
351 }
352 break;
353 case WOL_UCAST:
354 if (ecmd.wolopts != WAKE_UCAST) {
355 ecmd.wolopts = WAKE_UCAST;
356 need_update = true;
357 }
358 break;
359 case WOL_MCAST:
360 if (ecmd.wolopts != WAKE_MCAST) {
361 ecmd.wolopts = WAKE_MCAST;
362 need_update = true;
363 }
364 break;
365 case WOL_BCAST:
366 if (ecmd.wolopts != WAKE_BCAST) {
367 ecmd.wolopts = WAKE_BCAST;
368 need_update = true;
369 }
370 break;
371 case WOL_ARP:
372 if (ecmd.wolopts != WAKE_ARP) {
373 ecmd.wolopts = WAKE_ARP;
374 need_update = true;
375 }
376 break;
377 case WOL_MAGIC:
378 if (ecmd.wolopts != WAKE_MAGIC) {
379 ecmd.wolopts = WAKE_MAGIC;
380 need_update = true;
381 }
382 break;
383 case WOL_MAGICSECURE:
384 if (ecmd.wolopts != WAKE_MAGICSECURE) {
385 ecmd.wolopts = WAKE_MAGICSECURE;
386 need_update = true;
387 }
388 break;
389 case WOL_OFF:
390 if (ecmd.wolopts != 0) {
391 ecmd.wolopts = 0;
392 need_update = true;
393 }
394 break;
395 default:
396 break;
397 }
398
399 if (need_update) {
400 ecmd.cmd = ETHTOOL_SWOL;
401
402 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
403 if (r < 0)
404 return -errno;
405 }
406
407 return 0;
408 }
409
410 int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, netdev_ring_param *ring) {
411 struct ethtool_ringparam ecmd = {
412 .cmd = ETHTOOL_GRINGPARAM
413 };
414 struct ifreq ifr = {
415 .ifr_data = (void*) &ecmd
416 };
417 bool need_update = false;
418 int r;
419
420 if (*ethtool_fd < 0) {
421 r = ethtool_connect_or_warn(ethtool_fd, true);
422 if (r < 0)
423 return r;
424 }
425
426 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
427
428 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
429 if (r < 0)
430 return -errno;
431
432 if (ring->rx_pending_set) {
433 if (ecmd.rx_pending != ring->rx_pending) {
434 ecmd.rx_pending = ring->rx_pending;
435 need_update = true;
436 }
437 }
438
439 if (ring->tx_pending_set) {
440 if (ecmd.tx_pending != ring->tx_pending) {
441 ecmd.tx_pending = ring->tx_pending;
442 need_update = true;
443 }
444 }
445
446 if (need_update) {
447 ecmd.cmd = ETHTOOL_SRINGPARAM;
448
449 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
450 if (r < 0)
451 return -errno;
452 }
453
454 return 0;
455 }
456
457 static int get_stringset(int ethtool_fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) {
458 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
459 struct {
460 struct ethtool_sset_info info;
461 uint32_t space;
462 } buffer = {
463 .info = {
464 .cmd = ETHTOOL_GSSET_INFO,
465 .sset_mask = UINT64_C(1) << stringset_id,
466 },
467 };
468 unsigned len;
469 int r;
470
471 ifr->ifr_data = (void *) &buffer.info;
472
473 r = ioctl(ethtool_fd, SIOCETHTOOL, ifr);
474 if (r < 0)
475 return -errno;
476
477 if (!buffer.info.sset_mask)
478 return -EINVAL;
479
480 len = buffer.info.data[0];
481
482 strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN);
483 if (!strings)
484 return -ENOMEM;
485
486 strings->cmd = ETHTOOL_GSTRINGS;
487 strings->string_set = stringset_id;
488 strings->len = len;
489
490 ifr->ifr_data = (void *) strings;
491
492 r = ioctl(ethtool_fd, SIOCETHTOOL, ifr);
493 if (r < 0)
494 return -errno;
495
496 *gstrings = TAKE_PTR(strings);
497
498 return 0;
499 }
500
501 static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) {
502 unsigned i;
503
504 for (i = 0; i < strings->len; i++) {
505 if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature))
506 return i;
507 }
508
509 return -ENODATA;
510 }
511
512 int ethtool_set_features(int *ethtool_fd, const char *ifname, int *features) {
513 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
514 struct ethtool_sfeatures *sfeatures;
515 int block, bit, i, r;
516 struct ifreq ifr = {};
517
518 if (*ethtool_fd < 0) {
519 r = ethtool_connect_or_warn(ethtool_fd, true);
520 if (r < 0)
521 return r;
522 }
523
524 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
525
526 r = get_stringset(*ethtool_fd, &ifr, ETH_SS_FEATURES, &strings);
527 if (r < 0)
528 return log_warning_errno(r, "ethtool: could not get ethtool features for %s", ifname);
529
530 sfeatures = alloca0(sizeof(struct ethtool_sfeatures) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
531 sfeatures->cmd = ETHTOOL_SFEATURES;
532 sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
533
534 for (i = 0; i < _NET_DEV_FEAT_MAX; i++) {
535
536 if (features[i] != -1) {
537
538 r = find_feature_index(strings, netdev_feature_table[i]);
539 if (r < 0) {
540 log_warning_errno(r, "ethtool: could not find feature: %s", netdev_feature_table[i]);
541 continue;
542 }
543
544 block = r / 32;
545 bit = r % 32;
546
547 sfeatures->features[block].valid |= 1 << bit;
548
549 if (features[i])
550 sfeatures->features[block].requested |= 1 << bit;
551 else
552 sfeatures->features[block].requested &= ~(1 << bit);
553 }
554 }
555
556 ifr.ifr_data = (void *) sfeatures;
557
558 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
559 if (r < 0)
560 return log_warning_errno(r, "ethtool: could not set ethtool features for %s", ifname);
561
562 return 0;
563 }
564
565 static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **g) {
566 struct ecmd {
567 struct ethtool_link_settings req;
568 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
569 } ecmd = {
570 .req.cmd = ETHTOOL_GLINKSETTINGS,
571 };
572 struct ethtool_link_usettings *u;
573 unsigned offset;
574 int r;
575
576 /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS
577 handshake first to agree on the length of the link mode bitmaps. If kernel doesn't
578 agree with user, it returns the bitmap length it is expecting from user as a negative
579 length (and cmd field is 0). When kernel and user agree, kernel returns valid info in
580 all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on
581 https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf
582 */
583
584 ifr->ifr_data = (void *) &ecmd;
585
586 r = ioctl(fd, SIOCETHTOOL, ifr);
587 if (r < 0)
588 return -errno;
589
590 if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
591 return -EOPNOTSUPP;
592
593 ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
594
595 ifr->ifr_data = (void *) &ecmd;
596
597 r = ioctl(fd, SIOCETHTOOL, ifr);
598 if (r < 0)
599 return -errno;
600
601 if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
602 return -EOPNOTSUPP;
603
604 u = new(struct ethtool_link_usettings, 1);
605 if (!u)
606 return -ENOMEM;
607
608 *u = (struct ethtool_link_usettings) {
609 .base = ecmd.req,
610 };
611
612 offset = 0;
613 memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
614
615 offset += ecmd.req.link_mode_masks_nwords;
616 memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
617
618 offset += ecmd.req.link_mode_masks_nwords;
619 memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
620
621 *g = u;
622
623 return 0;
624 }
625
626 static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
627 struct ethtool_link_usettings *e;
628 struct ethtool_cmd ecmd = {
629 .cmd = ETHTOOL_GSET,
630 };
631 int r;
632
633 ifr->ifr_data = (void *) &ecmd;
634
635 r = ioctl(fd, SIOCETHTOOL, ifr);
636 if (r < 0)
637 return -errno;
638
639 e = new(struct ethtool_link_usettings, 1);
640 if (!e)
641 return -ENOMEM;
642
643 *e = (struct ethtool_link_usettings) {
644 .base.cmd = ETHTOOL_GSET,
645 .base.link_mode_masks_nwords = 1,
646 .base.speed = ethtool_cmd_speed(&ecmd),
647 .base.duplex = ecmd.duplex,
648 .base.port = ecmd.port,
649 .base.phy_address = ecmd.phy_address,
650 .base.autoneg = ecmd.autoneg,
651 .base.mdio_support = ecmd.mdio_support,
652
653 .link_modes.supported[0] = ecmd.supported,
654 .link_modes.advertising[0] = ecmd.advertising,
655 .link_modes.lp_advertising[0] = ecmd.lp_advertising,
656 };
657
658 *u = e;
659
660 return 0;
661 }
662
663 static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
664 struct {
665 struct ethtool_link_settings req;
666 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
667 } ecmd = {};
668 unsigned offset;
669 int r;
670
671 if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
672 return -EINVAL;
673
674 ecmd.req = u->base;
675 ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
676 offset = 0;
677 memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords);
678
679 offset += ecmd.req.link_mode_masks_nwords;
680 memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords);
681
682 offset += ecmd.req.link_mode_masks_nwords;
683 memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords);
684
685 ifr->ifr_data = (void *) &ecmd;
686
687 r = ioctl(fd, SIOCETHTOOL, ifr);
688 if (r < 0)
689 return -errno;
690
691 return 0;
692 }
693
694 static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
695 struct ethtool_cmd ecmd = {
696 .cmd = ETHTOOL_SSET,
697 };
698 int r;
699
700 if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0)
701 return -EINVAL;
702
703 ecmd.supported = u->link_modes.supported[0];
704 ecmd.advertising = u->link_modes.advertising[0];
705 ecmd.lp_advertising = u->link_modes.lp_advertising[0];
706
707 ethtool_cmd_speed_set(&ecmd, u->base.speed);
708
709 ecmd.duplex = u->base.duplex;
710 ecmd.port = u->base.port;
711 ecmd.phy_address = u->base.phy_address;
712 ecmd.autoneg = u->base.autoneg;
713 ecmd.mdio_support = u->base.mdio_support;
714 ecmd.eth_tp_mdix = u->base.eth_tp_mdix;
715 ecmd.eth_tp_mdix_ctrl = u->base.eth_tp_mdix_ctrl;
716
717 ifr->ifr_data = (void *) &ecmd;
718
719 r = ioctl(fd, SIOCETHTOOL, ifr);
720 if (r < 0)
721 return -errno;
722
723 return 0;
724 }
725
726 /* If autonegotiation is disabled, the speed and duplex represent the fixed link
727 * mode and are writable if the driver supports multiple link modes. If it is
728 * enabled then they are read-only. If the link is up they represent the negotiated
729 * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
730 * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
731 */
732 int ethtool_set_glinksettings(
733 int *fd,
734 const char *ifname,
735 int autonegotiation,
736 uint32_t advertise[static N_ADVERTISE],
737 size_t speed,
738 Duplex duplex,
739 NetDevPort port) {
740 _cleanup_free_ struct ethtool_link_usettings *u = NULL;
741 struct ifreq ifr = {};
742 int r;
743
744 assert(advertise);
745
746 if (autonegotiation != AUTONEG_DISABLE && memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
747 log_info("ethtool: autonegotiation is unset or enabled, the speed and duplex are not writable.");
748 return 0;
749 }
750
751 if (*fd < 0) {
752 r = ethtool_connect_or_warn(fd, true);
753 if (r < 0)
754 return r;
755 }
756
757 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
758
759 r = get_glinksettings(*fd, &ifr, &u);
760 if (r < 0) {
761 r = get_gset(*fd, &ifr, &u);
762 if (r < 0)
763 return log_warning_errno(r, "ethtool: Cannot get device settings for %s : %m", ifname);
764 }
765
766 if (speed > 0)
767 u->base.speed = DIV_ROUND_UP(speed, 1000000);
768
769 if (duplex != _DUP_INVALID)
770 u->base.duplex = duplex;
771
772 if (port != _NET_DEV_PORT_INVALID)
773 u->base.port = port;
774
775 if (autonegotiation >= 0)
776 u->base.autoneg = autonegotiation;
777
778 if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
779 u->base.autoneg = AUTONEG_ENABLE;
780 memcpy(&u->link_modes.advertising, advertise, sizeof(uint32_t) * N_ADVERTISE);
781 memzero((uint8_t*) &u->link_modes.advertising + sizeof(uint32_t) * N_ADVERTISE,
782 ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(uint32_t) * N_ADVERTISE);
783 }
784
785 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
786 r = set_slinksettings(*fd, &ifr, u);
787 else
788 r = set_sset(*fd, &ifr, u);
789 if (r < 0)
790 return log_warning_errno(r, "ethtool: Cannot set device settings for %s: %m", ifname);
791
792 return r;
793 }
794
795 int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) {
796 struct ethtool_channels ecmd = {
797 .cmd = ETHTOOL_GCHANNELS
798 };
799 struct ifreq ifr = {
800 .ifr_data = (void*) &ecmd
801 };
802
803 bool need_update = false;
804 int r;
805
806 if (*fd < 0) {
807 r = ethtool_connect_or_warn(fd, true);
808 if (r < 0)
809 return r;
810 }
811
812 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
813
814 r = ioctl(*fd, SIOCETHTOOL, &ifr);
815 if (r < 0)
816 return -errno;
817
818 if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) {
819 ecmd.rx_count = channels->rx_count;
820 need_update = true;
821 }
822
823 if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) {
824 ecmd.tx_count = channels->tx_count;
825 need_update = true;
826 }
827
828 if (channels->other_count_set && ecmd.other_count != channels->other_count) {
829 ecmd.other_count = channels->other_count;
830 need_update = true;
831 }
832
833 if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) {
834 ecmd.combined_count = channels->combined_count;
835 need_update = true;
836 }
837
838 if (need_update) {
839 ecmd.cmd = ETHTOOL_SCHANNELS;
840
841 r = ioctl(*fd, SIOCETHTOOL, &ifr);
842 if (r < 0)
843 return -errno;
844 }
845
846 return 0;
847 }
848
849 int config_parse_channel(const char *unit,
850 const char *filename,
851 unsigned line,
852 const char *section,
853 unsigned section_line,
854 const char *lvalue,
855 int ltype,
856 const char *rvalue,
857 void *data,
858 void *userdata) {
859 netdev_channels *channels = data;
860 uint32_t k;
861 int r;
862
863 assert(filename);
864 assert(section);
865 assert(lvalue);
866 assert(rvalue);
867 assert(data);
868
869 r = safe_atou32(rvalue, &k);
870 if (r < 0) {
871 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
872 return 0;
873 }
874
875 if (k < 1) {
876 log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
877 return 0;
878 }
879
880 if (streq(lvalue, "RxChannels")) {
881 channels->rx_count = k;
882 channels->rx_count_set = true;
883 } else if (streq(lvalue, "TxChannels")) {
884 channels->tx_count = k;
885 channels->tx_count_set = true;
886 } else if (streq(lvalue, "OtherChannels")) {
887 channels->other_count = k;
888 channels->other_count_set = true;
889 } else if (streq(lvalue, "CombinedChannels")) {
890 channels->combined_count = k;
891 channels->combined_count_set = true;
892 }
893
894 return 0;
895 }
896
897 int config_parse_advertise(const char *unit,
898 const char *filename,
899 unsigned line,
900 const char *section,
901 unsigned section_line,
902 const char *lvalue,
903 int ltype,
904 const char *rvalue,
905 void *data,
906 void *userdata) {
907 uint32_t *advertise = data;
908 const char *p;
909 int r;
910
911 assert(filename);
912 assert(section);
913 assert(lvalue);
914 assert(rvalue);
915 assert(data);
916
917 if (isempty(rvalue)) {
918 /* Empty string resets the value. */
919 memzero(advertise, sizeof(uint32_t) * N_ADVERTISE);
920 return 0;
921 }
922
923 for (p = rvalue;;) {
924 _cleanup_free_ char *w = NULL;
925 enum ethtool_link_mode_bit_indices mode;
926
927 r = extract_first_word(&p, &w, NULL, 0);
928 if (r == -ENOMEM)
929 return log_oom();
930 if (r < 0) {
931 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split advertise modes '%s', ignoring: %m", rvalue);
932 break;
933 }
934 if (r == 0)
935 break;
936
937 mode = ethtool_link_mode_bit_from_string(w);
938 /* We reuse the kernel provided enum which does not contain negative value. So, the cast
939 * below is mandatory. Otherwise, the check below always passes and access an invalid address. */
940 if ((int) mode < 0) {
941 log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w);
942 continue;
943 }
944
945 advertise[mode / 32] |= 1UL << (mode % 32);
946 }
947
948 return 0;
949 }
950
951 int config_parse_nic_buffer_size(const char *unit,
952 const char *filename,
953 unsigned line,
954 const char *section,
955 unsigned section_line,
956 const char *lvalue,
957 int ltype,
958 const char *rvalue,
959 void *data,
960 void *userdata) {
961 netdev_ring_param *ring = data;
962 uint32_t k;
963 int r;
964
965 assert(filename);
966 assert(section);
967 assert(lvalue);
968 assert(rvalue);
969 assert(data);
970
971 r = safe_atou32(rvalue, &k);
972 if (r < 0) {
973 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse interface buffer value, ignoring: %s", rvalue);
974 return 0;
975 }
976
977 if (k < 1) {
978 log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid %s value, ignoring: %s", lvalue, rvalue);
979 return 0;
980 }
981
982 if (streq(lvalue, "RxBufferSize")) {
983 ring->rx_pending = k;
984 ring->rx_pending_set = true;
985 } else if (streq(lvalue, "TxBufferSize")) {
986 ring->tx_pending = k;
987 ring->tx_pending_set = true;
988 }
989
990 return 0;
991 }