1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
10 #include <linux/ethtool.h>
11 #include <linux/sockios.h>
13 #include "conf-parser.h"
14 #include "ethtool-util.h"
15 #include "link-config.h"
18 #include "socket-util.h"
19 #include "string-table.h"
23 static const char* const duplex_table
[_DUP_MAX
] = {
28 DEFINE_STRING_TABLE_LOOKUP(duplex
, Duplex
);
29 DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex
, duplex
, Duplex
, "Failed to parse duplex setting");
31 static const char* const wol_table
[_WOL_MAX
] = {
33 [WOL_UCAST
] = "unicast",
34 [WOL_MCAST
] = "multicast",
35 [WOL_BCAST
] = "broadcast",
37 [WOL_MAGIC
] = "magic",
38 [WOL_MAGICSECURE
] = "secureon",
42 DEFINE_STRING_TABLE_LOOKUP(wol
, WakeOnLan
);
43 DEFINE_CONFIG_PARSE_ENUM(config_parse_wol
, wol
, WakeOnLan
, "Failed to parse WakeOnLan setting");
45 static const char* const port_table
[_NET_DEV_PORT_MAX
] = {
46 [NET_DEV_PORT_TP
] = "tp",
47 [NET_DEV_PORT_AUI
] = "aui",
48 [NET_DEV_PORT_MII
] = "mii",
49 [NET_DEV_PORT_FIBRE
] = "fibre",
50 [NET_DEV_PORT_BNC
] = "bnc"
53 DEFINE_STRING_TABLE_LOOKUP(port
, NetDevPort
);
54 DEFINE_CONFIG_PARSE_ENUM(config_parse_port
, port
, NetDevPort
, "Failed to parse Port setting");
56 static const char* const netdev_feature_table
[_NET_DEV_FEAT_MAX
] = {
57 [NET_DEV_FEAT_GSO
] = "tx-generic-segmentation",
58 [NET_DEV_FEAT_GRO
] = "rx-gro",
59 [NET_DEV_FEAT_LRO
] = "rx-lro",
60 [NET_DEV_FEAT_TSO
] = "tx-tcp-segmentation",
61 [NET_DEV_FEAT_TSO6
] = "tx-tcp6-segmentation",
64 int ethtool_connect(int *ret
) {
67 assert_return(ret
, -EINVAL
);
69 fd
= socket_ioctl_fd();
78 int ethtool_get_driver(int *fd
, const char *ifname
, char **ret
) {
79 struct ethtool_drvinfo ecmd
= {
80 .cmd
= ETHTOOL_GDRVINFO
83 .ifr_data
= (void*) &ecmd
89 r
= ethtool_connect(fd
);
91 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
94 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
96 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
100 d
= strdup(ecmd
.driver
);
108 int ethtool_set_speed(int *fd
, const char *ifname
, unsigned int speed
, Duplex duplex
) {
109 struct ethtool_cmd ecmd
= {
113 .ifr_data
= (void*) &ecmd
115 bool need_update
= false;
118 if (speed
== 0 && duplex
== _DUP_INVALID
)
122 r
= ethtool_connect(fd
);
124 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
127 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
129 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
133 if (ethtool_cmd_speed(&ecmd
) != speed
) {
134 ethtool_cmd_speed_set(&ecmd
, speed
);
140 if (ecmd
.duplex
!= DUPLEX_HALF
) {
141 ecmd
.duplex
= DUPLEX_HALF
;
146 if (ecmd
.duplex
!= DUPLEX_FULL
) {
147 ecmd
.duplex
= DUPLEX_FULL
;
156 ecmd
.cmd
= ETHTOOL_SSET
;
158 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
166 int ethtool_set_wol(int *fd
, const char *ifname
, WakeOnLan wol
) {
167 struct ethtool_wolinfo ecmd
= {
171 .ifr_data
= (void*) &ecmd
173 bool need_update
= false;
176 if (wol
== _WOL_INVALID
)
180 r
= ethtool_connect(fd
);
182 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
185 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
187 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
193 if (ecmd
.wolopts
!= WAKE_PHY
) {
194 ecmd
.wolopts
= WAKE_PHY
;
199 if (ecmd
.wolopts
!= WAKE_UCAST
) {
200 ecmd
.wolopts
= WAKE_UCAST
;
205 if (ecmd
.wolopts
!= WAKE_MCAST
) {
206 ecmd
.wolopts
= WAKE_MCAST
;
211 if (ecmd
.wolopts
!= WAKE_BCAST
) {
212 ecmd
.wolopts
= WAKE_BCAST
;
217 if (ecmd
.wolopts
!= WAKE_ARP
) {
218 ecmd
.wolopts
= WAKE_ARP
;
223 if (ecmd
.wolopts
!= WAKE_MAGIC
) {
224 ecmd
.wolopts
= WAKE_MAGIC
;
228 case WOL_MAGICSECURE
:
229 if (ecmd
.wolopts
!= WAKE_MAGICSECURE
) {
230 ecmd
.wolopts
= WAKE_MAGICSECURE
;
235 if (ecmd
.wolopts
!= 0) {
245 ecmd
.cmd
= ETHTOOL_SWOL
;
247 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
255 static int get_stringset(int fd
, struct ifreq
*ifr
, int stringset_id
, struct ethtool_gstrings
**gstrings
) {
256 _cleanup_free_
struct ethtool_gstrings
*strings
= NULL
;
258 struct ethtool_sset_info info
;
262 .cmd
= ETHTOOL_GSSET_INFO
,
263 .sset_mask
= UINT64_C(1) << stringset_id
,
269 ifr
->ifr_data
= (void *) &buffer
.info
;
271 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
275 if (!buffer
.info
.sset_mask
)
278 len
= buffer
.info
.data
[0];
280 strings
= malloc0(sizeof(struct ethtool_gstrings
) + len
* ETH_GSTRING_LEN
);
284 strings
->cmd
= ETHTOOL_GSTRINGS
;
285 strings
->string_set
= stringset_id
;
288 ifr
->ifr_data
= (void *) strings
;
290 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
294 *gstrings
= TAKE_PTR(strings
);
299 static int find_feature_index(struct ethtool_gstrings
*strings
, const char *feature
) {
302 for (i
= 0; i
< strings
->len
; i
++) {
303 if (streq((char *) &strings
->data
[i
* ETH_GSTRING_LEN
], feature
))
310 int ethtool_set_features(int *fd
, const char *ifname
, NetDevFeature
*features
) {
311 _cleanup_free_
struct ethtool_gstrings
*strings
= NULL
;
312 struct ethtool_sfeatures
*sfeatures
;
313 int block
, bit
, i
, r
;
314 struct ifreq ifr
= {};
317 r
= ethtool_connect(fd
);
319 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
322 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
324 r
= get_stringset(*fd
, &ifr
, ETH_SS_FEATURES
, &strings
);
326 return log_warning_errno(r
, "link_config: could not get ethtool features for %s", ifname
);
328 sfeatures
= alloca0(sizeof(struct ethtool_gstrings
) + DIV_ROUND_UP(strings
->len
, 32U) * sizeof(sfeatures
->features
[0]));
329 sfeatures
->cmd
= ETHTOOL_SFEATURES
;
330 sfeatures
->size
= DIV_ROUND_UP(strings
->len
, 32U);
332 for (i
= 0; i
< _NET_DEV_FEAT_MAX
; i
++) {
334 if (features
[i
] != -1) {
336 r
= find_feature_index(strings
, netdev_feature_table
[i
]);
338 log_warning_errno(r
, "link_config: could not find feature: %s", netdev_feature_table
[i
]);
345 sfeatures
->features
[block
].valid
|= 1 << bit
;
348 sfeatures
->features
[block
].requested
|= 1 << bit
;
350 sfeatures
->features
[block
].requested
&= ~(1 << bit
);
354 ifr
.ifr_data
= (void *) sfeatures
;
356 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
358 return log_warning_errno(r
, "link_config: could not set ethtool features for %s", ifname
);
363 static int get_glinksettings(int fd
, struct ifreq
*ifr
, struct ethtool_link_usettings
**g
) {
365 struct ethtool_link_settings req
;
366 __u32 link_mode_data
[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32
];
368 .req
.cmd
= ETHTOOL_GLINKSETTINGS
,
370 struct ethtool_link_usettings
*u
;
374 /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS
375 handshake first to agree on the length of the link mode bitmaps. If kernel doesn't
376 agree with user, it returns the bitmap length it is expecting from user as a negative
377 length (and cmd field is 0). When kernel and user agree, kernel returns valid info in
378 all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on
379 https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf
382 ifr
->ifr_data
= (void *) &ecmd
;
384 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
388 if (ecmd
.req
.link_mode_masks_nwords
>= 0 || ecmd
.req
.cmd
!= ETHTOOL_GLINKSETTINGS
)
391 ecmd
.req
.link_mode_masks_nwords
= -ecmd
.req
.link_mode_masks_nwords
;
393 ifr
->ifr_data
= (void *) &ecmd
;
395 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
399 if (ecmd
.req
.link_mode_masks_nwords
<= 0 || ecmd
.req
.cmd
!= ETHTOOL_GLINKSETTINGS
)
402 u
= new0(struct ethtool_link_usettings
, 1);
409 memcpy(u
->link_modes
.supported
, &ecmd
.link_mode_data
[offset
], 4 * ecmd
.req
.link_mode_masks_nwords
);
411 offset
+= ecmd
.req
.link_mode_masks_nwords
;
412 memcpy(u
->link_modes
.advertising
, &ecmd
.link_mode_data
[offset
], 4 * ecmd
.req
.link_mode_masks_nwords
);
414 offset
+= ecmd
.req
.link_mode_masks_nwords
;
415 memcpy(u
->link_modes
.lp_advertising
, &ecmd
.link_mode_data
[offset
], 4 * ecmd
.req
.link_mode_masks_nwords
);
422 static int get_gset(int fd
, struct ifreq
*ifr
, struct ethtool_link_usettings
**u
) {
423 struct ethtool_link_usettings
*e
;
424 struct ethtool_cmd ecmd
= {
429 ifr
->ifr_data
= (void *) &ecmd
;
431 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
435 e
= new0(struct ethtool_link_usettings
, 1);
439 e
->base
.cmd
= ETHTOOL_GSET
;
441 e
->base
.link_mode_masks_nwords
= 1;
442 e
->base
.speed
= ethtool_cmd_speed(&ecmd
);
443 e
->base
.duplex
= ecmd
.duplex
;
444 e
->base
.port
= ecmd
.port
;
445 e
->base
.phy_address
= ecmd
.phy_address
;
446 e
->base
.autoneg
= ecmd
.autoneg
;
447 e
->base
.mdio_support
= ecmd
.mdio_support
;
449 e
->link_modes
.supported
[0] = ecmd
.supported
;
450 e
->link_modes
.advertising
[0] = ecmd
.advertising
;
451 e
->link_modes
.lp_advertising
[0] = ecmd
.lp_advertising
;
458 static int set_slinksettings(int fd
, struct ifreq
*ifr
, const struct ethtool_link_usettings
*u
) {
460 struct ethtool_link_settings req
;
461 __u32 link_mode_data
[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32
];
466 if (u
->base
.cmd
!= ETHTOOL_GLINKSETTINGS
|| u
->base
.link_mode_masks_nwords
<= 0)
470 ecmd
.req
.cmd
= ETHTOOL_SLINKSETTINGS
;
472 memcpy(&ecmd
.link_mode_data
[offset
], u
->link_modes
.supported
, 4 * ecmd
.req
.link_mode_masks_nwords
);
474 offset
+= ecmd
.req
.link_mode_masks_nwords
;
475 memcpy(&ecmd
.link_mode_data
[offset
], u
->link_modes
.advertising
, 4 * ecmd
.req
.link_mode_masks_nwords
);
477 offset
+= ecmd
.req
.link_mode_masks_nwords
;
478 memcpy(&ecmd
.link_mode_data
[offset
], u
->link_modes
.lp_advertising
, 4 * ecmd
.req
.link_mode_masks_nwords
);
480 ifr
->ifr_data
= (void *) &ecmd
;
482 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
489 static int set_sset(int fd
, struct ifreq
*ifr
, const struct ethtool_link_usettings
*u
) {
490 struct ethtool_cmd ecmd
= {
495 if (u
->base
.cmd
!= ETHTOOL_GSET
|| u
->base
.link_mode_masks_nwords
<= 0)
498 ecmd
.supported
= u
->link_modes
.supported
[0];
499 ecmd
.advertising
= u
->link_modes
.advertising
[0];
500 ecmd
.lp_advertising
= u
->link_modes
.lp_advertising
[0];
502 ethtool_cmd_speed_set(&ecmd
, u
->base
.speed
);
504 ecmd
.duplex
= u
->base
.duplex
;
505 ecmd
.port
= u
->base
.port
;
506 ecmd
.phy_address
= u
->base
.phy_address
;
507 ecmd
.autoneg
= u
->base
.autoneg
;
508 ecmd
.mdio_support
= u
->base
.mdio_support
;
510 ifr
->ifr_data
= (void *) &ecmd
;
512 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
519 /* If autonegotiation is disabled, the speed and duplex represent the fixed link
520 * mode and are writable if the driver supports multiple link modes. If it is
521 * enabled then they are read-only. If the link is up they represent the negotiated
522 * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
523 * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
526 int ethtool_set_glinksettings(int *fd
, const char *ifname
, struct link_config
*link
) {
527 _cleanup_free_
struct ethtool_link_usettings
*u
= NULL
;
528 struct ifreq ifr
= {};
531 if (link
->autonegotiation
!= 0) {
532 log_info("link_config: autonegotiation is unset or enabled, the speed and duplex are not writable.");
537 r
= ethtool_connect(fd
);
539 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
542 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
544 r
= get_glinksettings(*fd
, &ifr
, &u
);
546 r
= get_gset(*fd
, &ifr
, &u
);
548 return log_warning_errno(r
, "link_config: Cannot get device settings for %s : %m", ifname
);
552 u
->base
.speed
= DIV_ROUND_UP(link
->speed
, 1000000);
554 if (link
->duplex
!= _DUP_INVALID
)
555 u
->base
.duplex
= link
->duplex
;
557 if (link
->port
!= _NET_DEV_PORT_INVALID
)
558 u
->base
.port
= link
->port
;
560 u
->base
.autoneg
= link
->autonegotiation
;
562 if (u
->base
.cmd
== ETHTOOL_GLINKSETTINGS
)
563 r
= set_slinksettings(*fd
, &ifr
, u
);
565 r
= set_sset(*fd
, &ifr
, u
);
567 return log_warning_errno(r
, "link_config: Cannot set device settings for %s : %m", ifname
);