]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/udev/net/ethtool-util.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / udev / net / ethtool-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
a5010333
TG
2/***
3 This file is part of systemd.
4
5 Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
a5010333 21#include <net/if.h>
8b43440b 22#include <sys/ioctl.h>
a5010333
TG
23#include <linux/ethtool.h>
24#include <linux/sockios.h>
25
8b43440b 26#include "conf-parser.h"
a5010333 27#include "ethtool-util.h"
8b43440b 28#include "log.h"
593022fa 29#include "link-config.h"
429b4350 30#include "socket-util.h"
8b43440b 31#include "string-table.h"
a5010333
TG
32#include "strxcpyx.h"
33#include "util.h"
a39f92d3 34#include "missing.h"
5fde13d7 35
2c5859af 36static const char* const duplex_table[_DUP_MAX] = {
5fde13d7
TG
37 [DUP_FULL] = "full",
38 [DUP_HALF] = "half"
39};
40
41DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
42DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
43
2c5859af 44static const char* const wol_table[_WOL_MAX] = {
617da14c
SS
45 [WOL_PHY] = "phy",
46 [WOL_UCAST] = "unicast",
47 [WOL_MCAST] = "multicast",
48 [WOL_BCAST] = "broadcast",
49 [WOL_ARP] = "arp",
50 [WOL_MAGIC] = "magic",
51 [WOL_MAGICSECURE] = "secureon",
52 [WOL_OFF] = "off"
5fde13d7
TG
53};
54
55DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan);
56DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting");
a5010333 57
593022fa
SS
58static const char* const port_table[_NET_DEV_PORT_MAX] = {
59 [NET_DEV_PORT_TP] = "tp",
60 [NET_DEV_PORT_AUI] = "aui",
61 [NET_DEV_PORT_MII] = "mii",
62 [NET_DEV_PORT_FIBRE] = "fibre",
63 [NET_DEV_PORT_BNC] = "bnc"
64};
65
66DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort);
67DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting");
68
50725d10 69static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = {
ffa69a04
SS
70 [NET_DEV_FEAT_GSO] = "tx-generic-segmentation",
71 [NET_DEV_FEAT_GRO] = "rx-gro",
72 [NET_DEV_FEAT_LRO] = "rx-lro",
73 [NET_DEV_FEAT_TSO] = "tx-tcp-segmentation",
74 [NET_DEV_FEAT_TSO6] = "tx-tcp6-segmentation",
75 [NET_DEV_FEAT_UFO] = "tx-udp-fragmentation",
50725d10
SS
76};
77
a5010333
TG
78int ethtool_connect(int *ret) {
79 int fd;
80
81 assert_return(ret, -EINVAL);
82
429b4350 83 fd = socket_ioctl_fd();
ece174c5 84 if (fd < 0)
429b4350 85 return fd;
a5010333
TG
86 *ret = fd;
87
88 return 0;
89}
90
aedca892 91int ethtool_get_driver(int *fd, const char *ifname, char **ret) {
61f3af4f
LP
92 struct ethtool_drvinfo ecmd = {
93 .cmd = ETHTOOL_GDRVINFO
94 };
95 struct ifreq ifr = {
96 .ifr_data = (void*) &ecmd
97 };
98 char *d;
847a8a5f
TG
99 int r;
100
aedca892
TG
101 if (*fd < 0) {
102 r = ethtool_connect(fd);
f647962d
MS
103 if (r < 0)
104 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
aedca892
TG
105 }
106
847a8a5f 107 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
847a8a5f 108
aedca892 109 r = ioctl(*fd, SIOCETHTOOL, &ifr);
847a8a5f
TG
110 if (r < 0)
111 return -errno;
112
61f3af4f
LP
113 d = strdup(ecmd.driver);
114 if (!d)
847a8a5f
TG
115 return -ENOMEM;
116
61f3af4f 117 *ret = d;
847a8a5f
TG
118 return 0;
119}
120
61087906 121int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex) {
6c0519c0
TG
122 struct ethtool_cmd ecmd = {
123 .cmd = ETHTOOL_GSET
124 };
125 struct ifreq ifr = {
126 .ifr_data = (void*) &ecmd
127 };
0a2c2294 128 bool need_update = false;
a5010333
TG
129 int r;
130
5fde13d7 131 if (speed == 0 && duplex == _DUP_INVALID)
a5010333
TG
132 return 0;
133
aedca892
TG
134 if (*fd < 0) {
135 r = ethtool_connect(fd);
f647962d
MS
136 if (r < 0)
137 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
aedca892
TG
138 }
139
a5010333 140 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
a5010333 141
aedca892 142 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
143 if (r < 0)
144 return -errno;
145
146 if (ethtool_cmd_speed(&ecmd) != speed) {
147 ethtool_cmd_speed_set(&ecmd, speed);
148 need_update = true;
149 }
150
5fde13d7
TG
151 switch (duplex) {
152 case DUP_HALF:
a5010333
TG
153 if (ecmd.duplex != DUPLEX_HALF) {
154 ecmd.duplex = DUPLEX_HALF;
155 need_update = true;
156 }
5fde13d7
TG
157 break;
158 case DUP_FULL:
a5010333
TG
159 if (ecmd.duplex != DUPLEX_FULL) {
160 ecmd.duplex = DUPLEX_FULL;
161 need_update = true;
162 }
5fde13d7
TG
163 break;
164 default:
165 break;
a5010333
TG
166 }
167
168 if (need_update) {
169 ecmd.cmd = ETHTOOL_SSET;
170
aedca892 171 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
172 if (r < 0)
173 return -errno;
174 }
175
176 return 0;
177}
178
aedca892 179int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) {
6c0519c0
TG
180 struct ethtool_wolinfo ecmd = {
181 .cmd = ETHTOOL_GWOL
182 };
183 struct ifreq ifr = {
184 .ifr_data = (void*) &ecmd
185 };
0a2c2294 186 bool need_update = false;
a5010333
TG
187 int r;
188
5fde13d7 189 if (wol == _WOL_INVALID)
a5010333
TG
190 return 0;
191
aedca892
TG
192 if (*fd < 0) {
193 r = ethtool_connect(fd);
f647962d
MS
194 if (r < 0)
195 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
aedca892
TG
196 }
197
a5010333 198 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
a5010333 199
aedca892 200 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
201 if (r < 0)
202 return -errno;
203
5fde13d7 204 switch (wol) {
617da14c
SS
205 case WOL_PHY:
206 if (ecmd.wolopts != WAKE_PHY) {
207 ecmd.wolopts = WAKE_PHY;
208 need_update = true;
209 }
210 break;
211 case WOL_UCAST:
212 if (ecmd.wolopts != WAKE_UCAST) {
213 ecmd.wolopts = WAKE_UCAST;
214 need_update = true;
215 }
216 break;
217 case WOL_MCAST:
218 if (ecmd.wolopts != WAKE_MCAST) {
219 ecmd.wolopts = WAKE_MCAST;
220 need_update = true;
221 }
222 break;
223 case WOL_BCAST:
224 if (ecmd.wolopts != WAKE_BCAST) {
225 ecmd.wolopts = WAKE_BCAST;
226 need_update = true;
227 }
228 break;
229 case WOL_ARP:
230 if (ecmd.wolopts != WAKE_ARP) {
231 ecmd.wolopts = WAKE_ARP;
232 need_update = true;
233 }
234 break;
235 case WOL_MAGIC:
236 if (ecmd.wolopts != WAKE_MAGIC) {
237 ecmd.wolopts = WAKE_MAGIC;
238 need_update = true;
239 }
240 break;
241 case WOL_MAGICSECURE:
242 if (ecmd.wolopts != WAKE_MAGICSECURE) {
243 ecmd.wolopts = WAKE_MAGICSECURE;
244 need_update = true;
245 }
246 break;
247 case WOL_OFF:
248 if (ecmd.wolopts != 0) {
249 ecmd.wolopts = 0;
250 need_update = true;
251 }
252 break;
253 default:
254 break;
5fde13d7 255 }
a5010333
TG
256
257 if (need_update) {
258 ecmd.cmd = ETHTOOL_SWOL;
259
aedca892 260 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
261 if (r < 0)
262 return -errno;
263 }
264
265 return 0;
266}
50725d10
SS
267
268static int ethtool_get_stringset(int *fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) {
269 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
a9dee27f
SS
270 struct {
271 struct ethtool_sset_info info;
272 uint32_t space;
273 } buffer = {
274 .info = {
275 .cmd = ETHTOOL_GSSET_INFO,
276 .sset_mask = UINT64_C(1) << stringset_id,
277 },
50725d10
SS
278 };
279 unsigned len;
280 int r;
281
a9dee27f 282 ifr->ifr_data = (void *) &buffer.info;
50725d10
SS
283
284 r = ioctl(*fd, SIOCETHTOOL, ifr);
285 if (r < 0)
286 return -errno;
287
a9dee27f 288 if (!buffer.info.sset_mask)
50725d10
SS
289 return -EINVAL;
290
a9dee27f 291 len = buffer.info.data[0];
50725d10
SS
292
293 strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN);
294 if (!strings)
295 return -ENOMEM;
296
297 strings->cmd = ETHTOOL_GSTRINGS;
298 strings->string_set = stringset_id;
299 strings->len = len;
300
301 ifr->ifr_data = (void *) strings;
302
303 r = ioctl(*fd, SIOCETHTOOL, ifr);
304 if (r < 0)
305 return -errno;
306
307 *gstrings = strings;
308 strings = NULL;
309
310 return 0;
311}
312
313static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) {
314 unsigned i;
315
316 for (i = 0; i < strings->len; i++) {
317 if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature))
318 return i;
319 }
320
321 return -1;
322}
323
324int ethtool_set_features(int *fd, const char *ifname, NetDevFeature *features) {
325 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
326 struct ethtool_sfeatures *sfeatures;
327 int block, bit, i, r;
a9dee27f 328 struct ifreq ifr = {};
50725d10
SS
329
330 if (*fd < 0) {
331 r = ethtool_connect(fd);
332 if (r < 0)
333 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
334 }
335
336 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
337
338 r = ethtool_get_stringset(fd, &ifr, ETH_SS_FEATURES, &strings);
339 if (r < 0)
340 return log_warning_errno(r, "link_config: could not get ethtool features for %s", ifname);
341
342 sfeatures = alloca0(sizeof(struct ethtool_gstrings) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
343 sfeatures->cmd = ETHTOOL_SFEATURES;
344 sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
345
346 for (i = 0; i < _NET_DEV_FEAT_MAX; i++) {
347
348 if (features[i] != -1) {
349
350 r = find_feature_index(strings, netdev_feature_table[i]);
351 if (r < 0) {
352 log_warning_errno(r, "link_config: could not find feature: %s", netdev_feature_table[i]);
353 continue;
354 }
355
356 block = r / 32;
357 bit = r % 32;
358
359 sfeatures->features[block].valid |= 1 << bit;
360
361 if (features[i])
362 sfeatures->features[block].requested |= 1 << bit;
363 else
364 sfeatures->features[block].requested &= ~(1 << bit);
365 }
366 }
367
368 ifr.ifr_data = (void *) sfeatures;
369
370 r = ioctl(*fd, SIOCETHTOOL, &ifr);
371 if (r < 0)
372 return log_warning_errno(r, "link_config: could not set ethtool features for %s", ifname);
373
374 return 0;
375}
a39f92d3
SS
376
377static int get_glinksettings(int *fd, struct ifreq *ifr, struct ethtool_link_usettings **g) {
378 struct ecmd {
379 struct ethtool_link_settings req;
380 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
381 } ecmd = {
382 .req.cmd = ETHTOOL_GLINKSETTINGS,
383 };
384 struct ethtool_link_usettings *u;
385 unsigned offset;
386 int r;
387
388 /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS
389 handshake first to agree on the length of the link mode bitmaps. If kernel doesn't
390 agree with user, it returns the bitmap length it is expecting from user as a negative
391 length (and cmd field is 0). When kernel and user agree, kernel returns valid info in
392 all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on
393 https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf
394 */
395
396 ifr->ifr_data = (void *) &ecmd;
397
398 r = ioctl(*fd, SIOCETHTOOL, ifr);
399 if (r < 0)
400 return -errno;
401
402 if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
403 return -ENOTSUP;
404
405 ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
406
407 ifr->ifr_data = (void *) &ecmd;
408
409 r = ioctl(*fd, SIOCETHTOOL, ifr);
410 if (r < 0)
411 return -errno;
412
413 if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
414 return -ENOTSUP;
415
416 u = new0(struct ethtool_link_usettings , 1);
417 if (!u)
418 return -ENOMEM;
419
420 memcpy(&u->base, &ecmd.req, sizeof(struct ethtool_link_settings));
421
422 offset = 0;
423 memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
424
425 offset += ecmd.req.link_mode_masks_nwords;
426 memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
427
428 offset += ecmd.req.link_mode_masks_nwords;
429 memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
430
431 *g = u;
432
433 return 0;
434}
435
436static int get_gset(int *fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
437 struct ethtool_link_usettings *e;
438 struct ethtool_cmd ecmd = {
439 .cmd = ETHTOOL_GSET,
440 };
441 int r;
442
443 ifr->ifr_data = (void *) &ecmd;
444
445 r = ioctl(*fd, SIOCETHTOOL, ifr);
446 if (r < 0)
447 return -errno;
448
449 e = new0(struct ethtool_link_usettings, 1);
450 if (!e)
451 return -ENOMEM;
452
453 e->base.cmd = ETHTOOL_GSET;
454
455 e->base.link_mode_masks_nwords = 1;
456 e->base.speed = ethtool_cmd_speed(&ecmd);
457 e->base.duplex = ecmd.duplex;
458 e->base.port = ecmd.port;
459 e->base.phy_address = ecmd.phy_address;
460 e->base.autoneg = ecmd.autoneg;
461 e->base.mdio_support = ecmd.mdio_support;
462
463 e->link_modes.supported[0] = ecmd.supported;
464 e->link_modes.advertising[0] = ecmd.advertising;
465 e->link_modes.lp_advertising[0] = ecmd.lp_advertising;
466
467 *u = e;
468
469 return 0;
470}
471
472static int set_slinksettings(int *fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
473 struct {
474 struct ethtool_link_settings req;
475 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
476 } ecmd = {
477 .req.cmd = ETHTOOL_SLINKSETTINGS,
478 };
479 unsigned int offset;
480 int r;
481
482 if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
483 return -EINVAL;
484
485 offset = 0;
486 memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords);
487
488 offset += ecmd.req.link_mode_masks_nwords;
489 memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords);
490
491 offset += ecmd.req.link_mode_masks_nwords;
492 memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords);
493
494 ifr->ifr_data = (void *) &ecmd;
495
496 r = ioctl(*fd, SIOCETHTOOL, ifr);
497 if (r < 0)
498 return -errno;
499
500 return 0;
501}
502
503static int set_sset(int *fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
504 struct ethtool_cmd ecmd = {
505 .cmd = ETHTOOL_SSET,
506 };
507 int r;
508
509 if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0)
510 return -EINVAL;
511
512 ecmd.supported = u->link_modes.supported[0];
513 ecmd.advertising = u->link_modes.advertising[0];
514 ecmd.lp_advertising = u->link_modes.lp_advertising[0];
515
516 ethtool_cmd_speed_set(&ecmd, u->base.speed);
517
518 ecmd.duplex = u->base.duplex;
519 ecmd.port = u->base.port;
520 ecmd.phy_address = u->base.phy_address;
521 ecmd.autoneg = u->base.autoneg;
522 ecmd.mdio_support = u->base.mdio_support;
523
524 ifr->ifr_data = (void *) &ecmd;
525
526 r = ioctl(*fd, SIOCETHTOOL, ifr);
527 if (r < 0)
528 return -errno;
529
530 return 0;
531}
532
533/* If autonegotiation is disabled, the speed and duplex represent the fixed link
534 * mode and are writable if the driver supports multiple link modes. If it is
535 * enabled then they are read-only. If the link is up they represent the negotiated
536 * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
537 * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
538 */
539
593022fa 540int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *link) {
a39f92d3
SS
541 _cleanup_free_ struct ethtool_link_usettings *u = NULL;
542 struct ifreq ifr = {};
543 int r;
544
593022fa 545 if (link->autonegotiation != 0) {
a39f92d3
SS
546 log_info("link_config: autonegotiation is unset or enabled, the speed and duplex are not writable.");
547 return 0;
548 }
549
550 if (*fd < 0) {
551 r = ethtool_connect(fd);
552 if (r < 0)
553 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
554 }
555
556 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
557
558 r = get_glinksettings(fd, &ifr, &u);
559 if (r < 0) {
560
561 r = get_gset(fd, &ifr, &u);
562 if (r < 0)
563 return log_warning_errno(r, "link_config: Cannot get device settings for %s : %m", ifname);
564 }
565
593022fa 566 if (link->speed)
9c5e1172 567 u->base.speed = DIV_ROUND_UP(link->speed, 1000000);
593022fa
SS
568
569 if (link->duplex != _DUP_INVALID)
570 u->base.duplex = link->duplex;
a39f92d3 571
593022fa
SS
572 if (link->port != _NET_DEV_PORT_INVALID)
573 u->base.port = link->port;
a39f92d3 574
593022fa 575 u->base.autoneg = link->autonegotiation;
a39f92d3
SS
576
577 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
578 r = set_slinksettings(fd, &ifr, u);
579 else
580 r = set_sset(fd, &ifr, u);
581
582 if (r < 0)
583 return log_warning_errno(r, "link_config: Cannot set device settings for %s : %m", ifname);
584
585 return r;
586}