1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include <linux/ethtool.h>
6 #include <linux/sockios.h>
8 #include "conf-parser.h"
9 #include "ethtool-util.h"
10 #include "link-config.h"
13 #include "socket-util.h"
14 #include "string-table.h"
18 static const char* const duplex_table
[_DUP_MAX
] = {
23 DEFINE_STRING_TABLE_LOOKUP(duplex
, Duplex
);
24 DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex
, duplex
, Duplex
, "Failed to parse duplex setting");
26 static const char* const wol_table
[_WOL_MAX
] = {
28 [WOL_UCAST
] = "unicast",
29 [WOL_MCAST
] = "multicast",
30 [WOL_BCAST
] = "broadcast",
32 [WOL_MAGIC
] = "magic",
33 [WOL_MAGICSECURE
] = "secureon",
37 DEFINE_STRING_TABLE_LOOKUP(wol
, WakeOnLan
);
38 DEFINE_CONFIG_PARSE_ENUM(config_parse_wol
, wol
, WakeOnLan
, "Failed to parse WakeOnLan setting");
40 static const char* const port_table
[_NET_DEV_PORT_MAX
] = {
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"
48 DEFINE_STRING_TABLE_LOOKUP(port
, NetDevPort
);
49 DEFINE_CONFIG_PARSE_ENUM(config_parse_port
, port
, NetDevPort
, "Failed to parse Port setting");
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",
59 static const char* const advertise_table
[_NET_DEV_ADVERTISE_MAX
] = {
60 [NET_DEV_ADVERTISE_10BASET_HALF
] = "10baset-half",
61 [NET_DEV_ADVERTISE_10BASET_FULL
] = "10baset-full",
62 [NET_DEV_ADVERTISE_100BASET_HALF
] = "100baset-half",
63 [NET_DEV_ADVERTISE_100BASET_FULL
] = "100baset-full",
64 [NET_DEV_ADVERTISE_1000BASET_HALF
] = "1000baset-half",
65 [NET_DEV_ADVERTISE_1000BASET_FULL
] = "1000baset-full",
66 [NET_DEV_ADVERTISE_10000BASET_FULL
] = "10000baset-full",
67 [NET_DEV_ADVERTISE_2500BASEX_FULL
] = "2500basex-full",
68 [NET_DEV_ADVERTISE_1000BASEKX_FULL
] = "1000basekx-full",
69 [NET_DEV_ADVERTISE_10000BASEKX4_FULL
] = "10000basekx4-full",
70 [NET_DEV_ADVERTISE_10000BASEKR_FULL
] = "10000basekr-full",
71 [NET_DEV_ADVERTISE_10000BASER_FEC
] = "10000baser-fec",
72 [NET_DEV_ADVERTISE_20000BASEMLD2_Full
] = "20000basemld2-full",
73 [NET_DEV_ADVERTISE_20000BASEKR2_Full
] = "20000basekr2-full",
76 DEFINE_STRING_TABLE_LOOKUP(advertise
, NetDevAdvertise
);
78 int ethtool_connect(int *ret
) {
81 assert_return(ret
, -EINVAL
);
83 fd
= socket_ioctl_fd();
92 int ethtool_get_driver(int *fd
, const char *ifname
, char **ret
) {
93 struct ethtool_drvinfo ecmd
= {
94 .cmd
= ETHTOOL_GDRVINFO
97 .ifr_data
= (void*) &ecmd
103 r
= ethtool_connect(fd
);
105 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
108 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
110 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
114 d
= strdup(ecmd
.driver
);
122 int ethtool_set_speed(int *fd
, const char *ifname
, unsigned speed
, Duplex duplex
) {
123 struct ethtool_cmd ecmd
= {
127 .ifr_data
= (void*) &ecmd
129 bool need_update
= false;
132 if (speed
== 0 && duplex
== _DUP_INVALID
)
136 r
= ethtool_connect(fd
);
138 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
141 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
143 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
147 if (ethtool_cmd_speed(&ecmd
) != speed
) {
148 ethtool_cmd_speed_set(&ecmd
, speed
);
154 if (ecmd
.duplex
!= DUPLEX_HALF
) {
155 ecmd
.duplex
= DUPLEX_HALF
;
160 if (ecmd
.duplex
!= DUPLEX_FULL
) {
161 ecmd
.duplex
= DUPLEX_FULL
;
170 ecmd
.cmd
= ETHTOOL_SSET
;
172 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
180 int ethtool_set_wol(int *fd
, const char *ifname
, WakeOnLan wol
) {
181 struct ethtool_wolinfo ecmd
= {
185 .ifr_data
= (void*) &ecmd
187 bool need_update
= false;
190 if (wol
== _WOL_INVALID
)
194 r
= ethtool_connect(fd
);
196 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
199 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
201 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
207 if (ecmd
.wolopts
!= WAKE_PHY
) {
208 ecmd
.wolopts
= WAKE_PHY
;
213 if (ecmd
.wolopts
!= WAKE_UCAST
) {
214 ecmd
.wolopts
= WAKE_UCAST
;
219 if (ecmd
.wolopts
!= WAKE_MCAST
) {
220 ecmd
.wolopts
= WAKE_MCAST
;
225 if (ecmd
.wolopts
!= WAKE_BCAST
) {
226 ecmd
.wolopts
= WAKE_BCAST
;
231 if (ecmd
.wolopts
!= WAKE_ARP
) {
232 ecmd
.wolopts
= WAKE_ARP
;
237 if (ecmd
.wolopts
!= WAKE_MAGIC
) {
238 ecmd
.wolopts
= WAKE_MAGIC
;
242 case WOL_MAGICSECURE
:
243 if (ecmd
.wolopts
!= WAKE_MAGICSECURE
) {
244 ecmd
.wolopts
= WAKE_MAGICSECURE
;
249 if (ecmd
.wolopts
!= 0) {
259 ecmd
.cmd
= ETHTOOL_SWOL
;
261 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
269 static int get_stringset(int fd
, struct ifreq
*ifr
, int stringset_id
, struct ethtool_gstrings
**gstrings
) {
270 _cleanup_free_
struct ethtool_gstrings
*strings
= NULL
;
272 struct ethtool_sset_info info
;
276 .cmd
= ETHTOOL_GSSET_INFO
,
277 .sset_mask
= UINT64_C(1) << stringset_id
,
283 ifr
->ifr_data
= (void *) &buffer
.info
;
285 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
289 if (!buffer
.info
.sset_mask
)
292 len
= buffer
.info
.data
[0];
294 strings
= malloc0(sizeof(struct ethtool_gstrings
) + len
* ETH_GSTRING_LEN
);
298 strings
->cmd
= ETHTOOL_GSTRINGS
;
299 strings
->string_set
= stringset_id
;
302 ifr
->ifr_data
= (void *) strings
;
304 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
308 *gstrings
= TAKE_PTR(strings
);
313 static int find_feature_index(struct ethtool_gstrings
*strings
, const char *feature
) {
316 for (i
= 0; i
< strings
->len
; i
++) {
317 if (streq((char *) &strings
->data
[i
* ETH_GSTRING_LEN
], feature
))
324 int ethtool_set_features(int *fd
, const char *ifname
, int *features
) {
325 _cleanup_free_
struct ethtool_gstrings
*strings
= NULL
;
326 struct ethtool_sfeatures
*sfeatures
;
327 int block
, bit
, i
, r
;
328 struct ifreq ifr
= {};
331 r
= ethtool_connect(fd
);
333 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
336 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
338 r
= get_stringset(*fd
, &ifr
, ETH_SS_FEATURES
, &strings
);
340 return log_warning_errno(r
, "link_config: could not get ethtool features for %s", ifname
);
342 sfeatures
= alloca0(sizeof(struct ethtool_sfeatures
) + DIV_ROUND_UP(strings
->len
, 32U) * sizeof(sfeatures
->features
[0]));
343 sfeatures
->cmd
= ETHTOOL_SFEATURES
;
344 sfeatures
->size
= DIV_ROUND_UP(strings
->len
, 32U);
346 for (i
= 0; i
< _NET_DEV_FEAT_MAX
; i
++) {
348 if (features
[i
] != -1) {
350 r
= find_feature_index(strings
, netdev_feature_table
[i
]);
352 log_warning_errno(r
, "link_config: could not find feature: %s", netdev_feature_table
[i
]);
359 sfeatures
->features
[block
].valid
|= 1 << bit
;
362 sfeatures
->features
[block
].requested
|= 1 << bit
;
364 sfeatures
->features
[block
].requested
&= ~(1 << bit
);
368 ifr
.ifr_data
= (void *) sfeatures
;
370 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
372 return log_warning_errno(r
, "link_config: could not set ethtool features for %s", ifname
);
377 static int get_glinksettings(int fd
, struct ifreq
*ifr
, struct ethtool_link_usettings
**g
) {
379 struct ethtool_link_settings req
;
380 __u32 link_mode_data
[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32
];
382 .req
.cmd
= ETHTOOL_GLINKSETTINGS
,
384 struct ethtool_link_usettings
*u
;
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
396 ifr
->ifr_data
= (void *) &ecmd
;
398 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
402 if (ecmd
.req
.link_mode_masks_nwords
>= 0 || ecmd
.req
.cmd
!= ETHTOOL_GLINKSETTINGS
)
405 ecmd
.req
.link_mode_masks_nwords
= -ecmd
.req
.link_mode_masks_nwords
;
407 ifr
->ifr_data
= (void *) &ecmd
;
409 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
413 if (ecmd
.req
.link_mode_masks_nwords
<= 0 || ecmd
.req
.cmd
!= ETHTOOL_GLINKSETTINGS
)
416 u
= new0(struct ethtool_link_usettings
, 1);
423 memcpy(u
->link_modes
.supported
, &ecmd
.link_mode_data
[offset
], 4 * ecmd
.req
.link_mode_masks_nwords
);
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
);
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
);
436 static int get_gset(int fd
, struct ifreq
*ifr
, struct ethtool_link_usettings
**u
) {
437 struct ethtool_link_usettings
*e
;
438 struct ethtool_cmd ecmd
= {
443 ifr
->ifr_data
= (void *) &ecmd
;
445 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
449 e
= new0(struct ethtool_link_usettings
, 1);
453 e
->base
.cmd
= ETHTOOL_GSET
;
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
;
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
;
472 static int set_slinksettings(int fd
, struct ifreq
*ifr
, const struct ethtool_link_usettings
*u
) {
474 struct ethtool_link_settings req
;
475 __u32 link_mode_data
[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32
];
480 if (u
->base
.cmd
!= ETHTOOL_GLINKSETTINGS
|| u
->base
.link_mode_masks_nwords
<= 0)
484 ecmd
.req
.cmd
= ETHTOOL_SLINKSETTINGS
;
486 memcpy(&ecmd
.link_mode_data
[offset
], u
->link_modes
.supported
, 4 * ecmd
.req
.link_mode_masks_nwords
);
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
);
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
);
494 ifr
->ifr_data
= (void *) &ecmd
;
496 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
503 static int set_sset(int fd
, struct ifreq
*ifr
, const struct ethtool_link_usettings
*u
) {
504 struct ethtool_cmd ecmd
= {
509 if (u
->base
.cmd
!= ETHTOOL_GSET
|| u
->base
.link_mode_masks_nwords
<= 0)
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];
516 ethtool_cmd_speed_set(&ecmd
, u
->base
.speed
);
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 ecmd
.eth_tp_mdix
= u
->base
.eth_tp_mdix
;
524 ecmd
.eth_tp_mdix_ctrl
= u
->base
.eth_tp_mdix_ctrl
;
526 ifr
->ifr_data
= (void *) &ecmd
;
528 r
= ioctl(fd
, SIOCETHTOOL
, ifr
);
535 /* If autonegotiation is disabled, the speed and duplex represent the fixed link
536 * mode and are writable if the driver supports multiple link modes. If it is
537 * enabled then they are read-only. If the link is up they represent the negotiated
538 * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
539 * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
541 int ethtool_set_glinksettings(int *fd
, const char *ifname
, struct link_config
*link
) {
542 _cleanup_free_
struct ethtool_link_usettings
*u
= NULL
;
543 struct ifreq ifr
= {};
546 if (link
->autonegotiation
!= 0) {
547 log_info("link_config: autonegotiation is unset or enabled, the speed and duplex are not writable.");
552 r
= ethtool_connect(fd
);
554 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
557 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
559 r
= get_glinksettings(*fd
, &ifr
, &u
);
561 r
= get_gset(*fd
, &ifr
, &u
);
563 return log_warning_errno(r
, "link_config: Cannot get device settings for %s : %m", ifname
);
567 u
->base
.speed
= DIV_ROUND_UP(link
->speed
, 1000000);
569 if (link
->duplex
!= _DUP_INVALID
)
570 u
->base
.duplex
= link
->duplex
;
572 if (link
->port
!= _NET_DEV_PORT_INVALID
)
573 u
->base
.port
= link
->port
;
575 u
->base
.autoneg
= link
->autonegotiation
;
577 if (link
->advertise
) {
578 uint32_t advertise
[ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32
] = {};
580 advertise
[0] = link
->advertise
;
581 memcpy(&u
->link_modes
.advertising
, advertise
, ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES
);
584 if (u
->base
.cmd
== ETHTOOL_GLINKSETTINGS
)
585 r
= set_slinksettings(*fd
, &ifr
, u
);
587 r
= set_sset(*fd
, &ifr
, u
);
589 return log_warning_errno(r
, "link_config: Cannot set device settings for %s : %m", ifname
);
594 int config_parse_channel(const char *unit
,
595 const char *filename
,
598 unsigned section_line
,
604 link_config
*config
= data
;
614 r
= safe_atou32(rvalue
, &k
);
616 log_syntax(unit
, LOG_ERR
, filename
, line
, r
, "Failed to parse channel value, ignoring: %s", rvalue
);
621 log_syntax(unit
, LOG_ERR
, filename
, line
, -EINVAL
, "Invalid %s value, ignoring: %s", lvalue
, rvalue
);
625 if (streq(lvalue
, "RxChannels")) {
626 config
->channels
.rx_count
= k
;
627 config
->channels
.rx_count_set
= true;
628 } else if (streq(lvalue
, "TxChannels")) {
629 config
->channels
.tx_count
= k
;
630 config
->channels
.tx_count_set
= true;
631 } else if (streq(lvalue
, "OtherChannels")) {
632 config
->channels
.other_count
= k
;
633 config
->channels
.other_count_set
= true;
634 } else if (streq(lvalue
, "CombinedChannels")) {
635 config
->channels
.combined_count
= k
;
636 config
->channels
.combined_count_set
= true;
642 int ethtool_set_channels(int *fd
, const char *ifname
, netdev_channels
*channels
) {
643 struct ethtool_channels ecmd
= {
644 .cmd
= ETHTOOL_GCHANNELS
647 .ifr_data
= (void*) &ecmd
650 bool need_update
= false;
654 r
= ethtool_connect(fd
);
656 return log_warning_errno(r
, "link_config: could not connect to ethtool: %m");
659 strscpy(ifr
.ifr_name
, IFNAMSIZ
, ifname
);
661 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
665 if (channels
->rx_count_set
&& ecmd
.rx_count
!= channels
->rx_count
) {
666 ecmd
.rx_count
= channels
->rx_count
;
670 if (channels
->tx_count_set
&& ecmd
.tx_count
!= channels
->tx_count
) {
671 ecmd
.tx_count
= channels
->tx_count
;
675 if (channels
->other_count_set
&& ecmd
.other_count
!= channels
->other_count
) {
676 ecmd
.other_count
= channels
->other_count
;
680 if (channels
->combined_count_set
&& ecmd
.combined_count
!= channels
->combined_count
) {
681 ecmd
.combined_count
= channels
->combined_count
;
686 ecmd
.cmd
= ETHTOOL_SCHANNELS
;
688 r
= ioctl(*fd
, SIOCETHTOOL
, &ifr
);
696 int config_parse_advertise(const char *unit
,
697 const char *filename
,
700 unsigned section_line
,
706 link_config
*config
= data
;
707 NetDevAdvertise mode
, a
= 0;
717 if (isempty(rvalue
)) {
718 /* Empty string resets the value. */
719 config
->advertise
= 0;
724 _cleanup_free_
char *w
= NULL
;
726 r
= extract_first_word(&p
, &w
, NULL
, 0);
730 log_syntax(unit
, LOG_ERR
, filename
, line
, r
, "Failed to split advertise modes '%s', ignoring: %m", rvalue
);
736 mode
= advertise_from_string(w
);
737 if (mode
== _NET_DEV_ADVERTISE_INVALID
) {
738 log_syntax(unit
, LOG_ERR
, filename
, line
, 0, "Failed to parse advertise mode, ignoring: %s", w
);
744 config
->advertise
|= a
;