]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/ethtool-util.c
Merge pull request #14592 from keszybz/simplifications
[thirdparty/systemd.git] / src / shared / ethtool-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
a5010333 2
a5010333 3#include <net/if.h>
8b43440b 4#include <sys/ioctl.h>
a5010333 5#include <linux/ethtool.h>
79b4428a 6#include <linux/netdevice.h>
a5010333
TG
7#include <linux/sockios.h>
8
8b43440b 9#include "conf-parser.h"
a5010333 10#include "ethtool-util.h"
5c2316c6 11#include "extract-word.h"
6666c4fa 12#include "fd-util.h"
ab1263d7 13#include "log.h"
0a970718 14#include "memory-util.h"
429b4350 15#include "socket-util.h"
8b43440b 16#include "string-table.h"
a5010333 17#include "strxcpyx.h"
5fde13d7 18
2c5859af 19static const char* const duplex_table[_DUP_MAX] = {
5fde13d7
TG
20 [DUP_FULL] = "full",
21 [DUP_HALF] = "half"
22};
23
24DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
25DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
26
2c5859af 27static const char* const wol_table[_WOL_MAX] = {
617da14c
SS
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",
44909f1c 35 [WOL_OFF] = "off",
5fde13d7
TG
36};
37
38DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan);
39DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting");
a5010333 40
44909f1c 41static const char* const port_table[] = {
593022fa
SS
42 [NET_DEV_PORT_TP] = "tp",
43 [NET_DEV_PORT_AUI] = "aui",
44 [NET_DEV_PORT_MII] = "mii",
45 [NET_DEV_PORT_FIBRE] = "fibre",
44909f1c 46 [NET_DEV_PORT_BNC] = "bnc",
593022fa
SS
47};
48
49DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort);
50DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting");
51
50725d10 52static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = {
ffa69a04
SS
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",
50725d10
SS
58};
59
64d9f756 60static const char* const ethtool_link_mode_bit_table[] = {
6d028889
YW
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",
72dda93a
YW
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",
6cf0a204 128};
5dd10118 129/* Make sure the array is large enough to fit all bits */
5c2316c6 130assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < N_ADVERTISE);
6cf0a204 131
2d18ac44 132DEFINE_STRING_TABLE_LOOKUP(ethtool_link_mode_bit, enum ethtool_link_mode_bit_indices);
6cf0a204 133
7864b16b 134static int ethtool_connect_or_warn(int *ret, bool warn) {
a5010333
TG
135 int fd;
136
137 assert_return(ret, -EINVAL);
138
429b4350 139 fd = socket_ioctl_fd();
ece174c5 140 if (fd < 0)
7864b16b
YW
141 return log_full_errno(warn ? LOG_WARNING: LOG_DEBUG, fd,
142 "ethtool: could not create control socket: %m");
2b44daaa 143
a5010333
TG
144 *ret = fd;
145
146 return 0;
147}
148
64be35ab 149int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) {
61f3af4f
LP
150 struct ethtool_drvinfo ecmd = {
151 .cmd = ETHTOOL_GDRVINFO
152 };
153 struct ifreq ifr = {
154 .ifr_data = (void*) &ecmd
155 };
156 char *d;
847a8a5f
TG
157 int r;
158
64be35ab
ZJS
159 if (*ethtool_fd < 0) {
160 r = ethtool_connect_or_warn(ethtool_fd, true);
f647962d 161 if (r < 0)
7864b16b 162 return r;
aedca892
TG
163 }
164
847a8a5f 165 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
847a8a5f 166
64be35ab 167 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
847a8a5f
TG
168 if (r < 0)
169 return -errno;
170
61f3af4f
LP
171 d = strdup(ecmd.driver);
172 if (!d)
847a8a5f
TG
173 return -ENOMEM;
174
61f3af4f 175 *ret = d;
847a8a5f
TG
176 return 0;
177}
178
64be35ab 179int ethtool_get_link_info(int *ethtool_fd, const char *ifname,
33a8695f
YW
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
64be35ab
ZJS
190 if (*ethtool_fd < 0) {
191 r = ethtool_connect_or_warn(ethtool_fd, false);
33a8695f
YW
192 if (r < 0)
193 return r;
194 }
195
196 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
197
64be35ab 198 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
33a8695f
YW
199 if (r < 0)
200 return -errno;
201
202 if (ret_autonegotiation)
203 *ret_autonegotiation = ecmd.autoneg;
204
d16c2728
YW
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 }
33a8695f
YW
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
64be35ab 222int ethtool_get_permanent_macaddr(int *ethtool_fd, const char *ifname, struct ether_addr *ret) {
6666c4fa 223 _cleanup_close_ int fd = -1;
0475919b
ZJS
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 };
79b4428a
YW
234 int r;
235
79b4428a
YW
236 assert(ifname);
237 assert(ret);
238
6666c4fa
ZJS
239 if (!ethtool_fd)
240 ethtool_fd = &fd;
241
64be35ab
ZJS
242 if (*ethtool_fd < 0) {
243 r = ethtool_connect_or_warn(ethtool_fd, false);
79b4428a
YW
244 if (r < 0)
245 return r;
246 }
247
79b4428a
YW
248 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
249
64be35ab 250 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
79b4428a
YW
251 if (r < 0)
252 return -errno;
253
0475919b 254 if (epaddr.addr.size != 6)
79b4428a
YW
255 return -EOPNOTSUPP;
256
0475919b
ZJS
257 for (size_t i = 0; i < epaddr.addr.size; i++)
258 ret->ether_addr_octet[i] = epaddr.addr.data[i];
79b4428a
YW
259
260 return 0;
261}
262
64be35ab 263int ethtool_set_speed(int *ethtool_fd, const char *ifname, unsigned speed, Duplex duplex) {
6c0519c0
TG
264 struct ethtool_cmd ecmd = {
265 .cmd = ETHTOOL_GSET
266 };
267 struct ifreq ifr = {
268 .ifr_data = (void*) &ecmd
269 };
0a2c2294 270 bool need_update = false;
a5010333
TG
271 int r;
272
5fde13d7 273 if (speed == 0 && duplex == _DUP_INVALID)
a5010333
TG
274 return 0;
275
64be35ab
ZJS
276 if (*ethtool_fd < 0) {
277 r = ethtool_connect_or_warn(ethtool_fd, true);
f647962d 278 if (r < 0)
7864b16b 279 return r;
aedca892
TG
280 }
281
a5010333 282 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
a5010333 283
64be35ab 284 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
a5010333
TG
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
5fde13d7
TG
293 switch (duplex) {
294 case DUP_HALF:
a5010333
TG
295 if (ecmd.duplex != DUPLEX_HALF) {
296 ecmd.duplex = DUPLEX_HALF;
297 need_update = true;
298 }
5fde13d7
TG
299 break;
300 case DUP_FULL:
a5010333
TG
301 if (ecmd.duplex != DUPLEX_FULL) {
302 ecmd.duplex = DUPLEX_FULL;
303 need_update = true;
304 }
5fde13d7
TG
305 break;
306 default:
307 break;
a5010333
TG
308 }
309
310 if (need_update) {
311 ecmd.cmd = ETHTOOL_SSET;
312
64be35ab 313 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
a5010333
TG
314 if (r < 0)
315 return -errno;
316 }
317
318 return 0;
319}
320
64be35ab 321int ethtool_set_wol(int *ethtool_fd, const char *ifname, WakeOnLan wol) {
6c0519c0
TG
322 struct ethtool_wolinfo ecmd = {
323 .cmd = ETHTOOL_GWOL
324 };
325 struct ifreq ifr = {
326 .ifr_data = (void*) &ecmd
327 };
0a2c2294 328 bool need_update = false;
a5010333
TG
329 int r;
330
5fde13d7 331 if (wol == _WOL_INVALID)
a5010333
TG
332 return 0;
333
64be35ab
ZJS
334 if (*ethtool_fd < 0) {
335 r = ethtool_connect_or_warn(ethtool_fd, true);
f647962d 336 if (r < 0)
7864b16b 337 return r;
aedca892
TG
338 }
339
a5010333 340 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
a5010333 341
64be35ab 342 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
a5010333
TG
343 if (r < 0)
344 return -errno;
345
5fde13d7 346 switch (wol) {
617da14c
SS
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;
5fde13d7 397 }
a5010333
TG
398
399 if (need_update) {
400 ecmd.cmd = ETHTOOL_SWOL;
401
64be35ab 402 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
a5010333
TG
403 if (r < 0)
404 return -errno;
405 }
406
407 return 0;
408}
50725d10 409
64be35ab 410int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, netdev_ring_param *ring) {
224ded67
SS
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
64be35ab
ZJS
420 if (*ethtool_fd < 0) {
421 r = ethtool_connect_or_warn(ethtool_fd, true);
224ded67
SS
422 if (r < 0)
423 return r;
424 }
425
426 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
427
64be35ab 428 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
224ded67
SS
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) {
b80aca0f 440 if (ecmd.tx_pending != ring->tx_pending) {
224ded67
SS
441 ecmd.tx_pending = ring->tx_pending;
442 need_update = true;
443 }
444 }
445
446 if (need_update) {
447 ecmd.cmd = ETHTOOL_SRINGPARAM;
448
64be35ab 449 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
224ded67
SS
450 if (r < 0)
451 return -errno;
452 }
453
454 return 0;
455}
456
64be35ab 457static int get_stringset(int ethtool_fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) {
50725d10 458 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
a9dee27f
SS
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 },
50725d10
SS
467 };
468 unsigned len;
469 int r;
470
a9dee27f 471 ifr->ifr_data = (void *) &buffer.info;
50725d10 472
64be35ab 473 r = ioctl(ethtool_fd, SIOCETHTOOL, ifr);
50725d10
SS
474 if (r < 0)
475 return -errno;
476
a9dee27f 477 if (!buffer.info.sset_mask)
50725d10
SS
478 return -EINVAL;
479
a9dee27f 480 len = buffer.info.data[0];
50725d10
SS
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
64be35ab 492 r = ioctl(ethtool_fd, SIOCETHTOOL, ifr);
50725d10
SS
493 if (r < 0)
494 return -errno;
495
ae2a15bc 496 *gstrings = TAKE_PTR(strings);
50725d10
SS
497
498 return 0;
499}
500
501static 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
ee60be46 509 return -ENODATA;
50725d10
SS
510}
511
64be35ab 512int ethtool_set_features(int *ethtool_fd, const char *ifname, int *features) {
50725d10
SS
513 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
514 struct ethtool_sfeatures *sfeatures;
515 int block, bit, i, r;
a9dee27f 516 struct ifreq ifr = {};
50725d10 517
64be35ab
ZJS
518 if (*ethtool_fd < 0) {
519 r = ethtool_connect_or_warn(ethtool_fd, true);
50725d10 520 if (r < 0)
7864b16b 521 return r;
50725d10
SS
522 }
523
524 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
525
64be35ab 526 r = get_stringset(*ethtool_fd, &ifr, ETH_SS_FEATURES, &strings);
50725d10 527 if (r < 0)
5c2316c6 528 return log_warning_errno(r, "ethtool: could not get ethtool features for %s", ifname);
50725d10 529
3301f9eb 530 sfeatures = alloca0(sizeof(struct ethtool_sfeatures) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
50725d10
SS
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) {
5c2316c6 540 log_warning_errno(r, "ethtool: could not find feature: %s", netdev_feature_table[i]);
50725d10
SS
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
64be35ab 558 r = ioctl(*ethtool_fd, SIOCETHTOOL, &ifr);
50725d10 559 if (r < 0)
5c2316c6 560 return log_warning_errno(r, "ethtool: could not set ethtool features for %s", ifname);
50725d10
SS
561
562 return 0;
563}
a39f92d3 564
2b44daaa 565static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **g) {
a39f92d3
SS
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
2b44daaa 586 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
587 if (r < 0)
588 return -errno;
589
590 if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
6b44a121 591 return -EOPNOTSUPP;
a39f92d3
SS
592
593 ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
594
595 ifr->ifr_data = (void *) &ecmd;
596
2b44daaa 597 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
598 if (r < 0)
599 return -errno;
600
601 if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
6b44a121 602 return -EOPNOTSUPP;
a39f92d3 603
b9bc7d42 604 u = new(struct ethtool_link_usettings, 1);
a39f92d3
SS
605 if (!u)
606 return -ENOMEM;
607
b9bc7d42
YW
608 *u = (struct ethtool_link_usettings) {
609 .base = ecmd.req,
610 };
a39f92d3
SS
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
2b44daaa 626static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
a39f92d3
SS
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
2b44daaa 635 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
636 if (r < 0)
637 return -errno;
638
b9bc7d42 639 e = new(struct ethtool_link_usettings, 1);
a39f92d3
SS
640 if (!e)
641 return -ENOMEM;
642
b9bc7d42
YW
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 };
a39f92d3
SS
657
658 *u = e;
659
660 return 0;
661}
662
2b44daaa 663static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
a39f92d3
SS
664 struct {
665 struct ethtool_link_settings req;
666 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
94d4acbe 667 } ecmd = {};
14cb109d 668 unsigned offset;
a39f92d3
SS
669 int r;
670
671 if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
672 return -EINVAL;
673
89e1ba0a 674 ecmd.req = u->base;
94d4acbe 675 ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
a39f92d3
SS
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
2b44daaa 687 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
688 if (r < 0)
689 return -errno;
690
691 return 0;
692}
693
2b44daaa 694static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
a39f92d3
SS
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;
6cf0a204
SS
714 ecmd.eth_tp_mdix = u->base.eth_tp_mdix;
715 ecmd.eth_tp_mdix_ctrl = u->base.eth_tp_mdix_ctrl;
a39f92d3
SS
716
717 ifr->ifr_data = (void *) &ecmd;
718
2b44daaa 719 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
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
49c603bd 728 * enabled then they are read-only. If the link is up they represent the negotiated
a39f92d3
SS
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 */
5c2316c6
YW
732int 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) {
a39f92d3
SS
740 _cleanup_free_ struct ethtool_link_usettings *u = NULL;
741 struct ifreq ifr = {};
742 int r;
743
2caa38e9
LP
744 assert(advertise);
745
5c2316c6
YW
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.");
a39f92d3
SS
748 return 0;
749 }
750
751 if (*fd < 0) {
7864b16b 752 r = ethtool_connect_or_warn(fd, true);
a39f92d3 753 if (r < 0)
7864b16b 754 return r;
a39f92d3
SS
755 }
756
757 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
758
2b44daaa 759 r = get_glinksettings(*fd, &ifr, &u);
a39f92d3 760 if (r < 0) {
2b44daaa 761 r = get_gset(*fd, &ifr, &u);
a39f92d3 762 if (r < 0)
5c2316c6 763 return log_warning_errno(r, "ethtool: Cannot get device settings for %s : %m", ifname);
a39f92d3
SS
764 }
765
5c2316c6
YW
766 if (speed > 0)
767 u->base.speed = DIV_ROUND_UP(speed, 1000000);
593022fa 768
5c2316c6
YW
769 if (duplex != _DUP_INVALID)
770 u->base.duplex = duplex;
a39f92d3 771
5c2316c6
YW
772 if (port != _NET_DEV_PORT_INVALID)
773 u->base.port = port;
a39f92d3 774
5c2316c6
YW
775 if (autonegotiation >= 0)
776 u->base.autoneg = autonegotiation;
a39f92d3 777
5c2316c6 778 if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
a0e1ad10 779 u->base.autoneg = AUTONEG_ENABLE;
5c2316c6
YW
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);
5dd10118 783 }
6cf0a204 784
a39f92d3 785 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
2b44daaa 786 r = set_slinksettings(*fd, &ifr, u);
a39f92d3 787 else
2b44daaa 788 r = set_sset(*fd, &ifr, u);
a39f92d3 789 if (r < 0)
6eee8857 790 return log_warning_errno(r, "ethtool: Cannot set device settings for %s: %m", ifname);
a39f92d3
SS
791
792 return r;
793}
5f945202 794
5f945202
SS
795int 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) {
7864b16b 807 r = ethtool_connect_or_warn(fd, true);
5f945202 808 if (r < 0)
7864b16b 809 return r;
5f945202
SS
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}
6cf0a204 848
5c2316c6
YW
849int 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
6cf0a204
SS
897int 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) {
5c2316c6 907 uint32_t *advertise = data;
6cf0a204
SS
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. */
5c2316c6 919 memzero(advertise, sizeof(uint32_t) * N_ADVERTISE);
6cf0a204
SS
920 return 0;
921 }
922
923 for (p = rvalue;;) {
924 _cleanup_free_ char *w = NULL;
5dd10118 925 enum ethtool_link_mode_bit_indices mode;
6cf0a204
SS
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
2d18ac44 937 mode = ethtool_link_mode_bit_from_string(w);
84fb56d3
YW
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) {
6cf0a204
SS
941 log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w);
942 continue;
943 }
2d18ac44 944
5c2316c6 945 advertise[mode / 32] |= 1UL << (mode % 32);
2d18ac44 946 }
6cf0a204
SS
947
948 return 0;
949}
224ded67
SS
950
951int 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}