]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/ethtool-util.c
ethtool-util: use structured initializers
[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
TG
5#include <linux/ethtool.h>
6#include <linux/sockios.h>
7
8b43440b 8#include "conf-parser.h"
a5010333 9#include "ethtool-util.h"
5c2316c6 10#include "extract-word.h"
ab1263d7 11#include "log.h"
0a970718 12#include "memory-util.h"
ab1263d7 13#include "missing.h"
429b4350 14#include "socket-util.h"
8b43440b 15#include "string-table.h"
a5010333 16#include "strxcpyx.h"
5fde13d7 17
2c5859af 18static const char* const duplex_table[_DUP_MAX] = {
5fde13d7
TG
19 [DUP_FULL] = "full",
20 [DUP_HALF] = "half"
21};
22
23DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
24DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
25
2c5859af 26static const char* const wol_table[_WOL_MAX] = {
617da14c
SS
27 [WOL_PHY] = "phy",
28 [WOL_UCAST] = "unicast",
29 [WOL_MCAST] = "multicast",
30 [WOL_BCAST] = "broadcast",
31 [WOL_ARP] = "arp",
32 [WOL_MAGIC] = "magic",
33 [WOL_MAGICSECURE] = "secureon",
44909f1c 34 [WOL_OFF] = "off",
5fde13d7
TG
35};
36
37DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan);
38DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting");
a5010333 39
44909f1c 40static const char* const port_table[] = {
593022fa
SS
41 [NET_DEV_PORT_TP] = "tp",
42 [NET_DEV_PORT_AUI] = "aui",
43 [NET_DEV_PORT_MII] = "mii",
44 [NET_DEV_PORT_FIBRE] = "fibre",
44909f1c 45 [NET_DEV_PORT_BNC] = "bnc",
593022fa
SS
46};
47
48DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort);
49DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting");
50
50725d10 51static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = {
ffa69a04
SS
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",
50725d10
SS
57};
58
64d9f756 59static const char* const ethtool_link_mode_bit_table[] = {
2d18ac44
YW
60 [ETHTOOL_LINK_MODE_10baseT_Half_BIT] = "10baset-half",
61 [ETHTOOL_LINK_MODE_10baseT_Full_BIT] = "10baset-full",
62 [ETHTOOL_LINK_MODE_100baseT_Half_BIT] = "100baset-half",
63 [ETHTOOL_LINK_MODE_100baseT_Full_BIT] = "100baset-full",
64 [ETHTOOL_LINK_MODE_1000baseT_Half_BIT] = "1000baset-half",
65 [ETHTOOL_LINK_MODE_1000baseT_Full_BIT] = "1000baset-full",
66 [ETHTOOL_LINK_MODE_Autoneg_BIT] = "autonegotiation",
67 [ETHTOOL_LINK_MODE_TP_BIT] = "tp",
68 [ETHTOOL_LINK_MODE_AUI_BIT] = "aui",
69 [ETHTOOL_LINK_MODE_MII_BIT] = "mii",
70 [ETHTOOL_LINK_MODE_FIBRE_BIT] = "fibre",
71 [ETHTOOL_LINK_MODE_BNC_BIT] = "bnc",
72 [ETHTOOL_LINK_MODE_10000baseT_Full_BIT] = "10000baset-full",
73 [ETHTOOL_LINK_MODE_Pause_BIT] = "pause",
74 [ETHTOOL_LINK_MODE_Asym_Pause_BIT] = "asym-pause",
75 [ETHTOOL_LINK_MODE_2500baseX_Full_BIT] = "2500basex-full",
76 [ETHTOOL_LINK_MODE_Backplane_BIT] = "backplane",
77 [ETHTOOL_LINK_MODE_1000baseKX_Full_BIT] = "1000basekx-full",
78 [ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT] = "10000basekx4-full",
79 [ETHTOOL_LINK_MODE_10000baseKR_Full_BIT] = "10000basekr-full",
80 [ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = "10000baser-fec",
81 [ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT] = "20000basemld2-full",
82 [ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT] = "20000basekr2-full",
83 [ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT] = "40000basekr4-full",
84 [ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT] = "40000basecr4-full",
85 [ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT] = "40000basesr4-full",
86 [ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT] = "40000baselr4-full",
87 [ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT] = "56000basekr4-full",
88 [ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT] = "56000basecr4-full",
89 [ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT] = "56000basesr4-full",
90 [ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT] = "56000baselr4-full",
91 [ETHTOOL_LINK_MODE_25000baseCR_Full_BIT] = "25000basecr-full",
92 [ETHTOOL_LINK_MODE_25000baseKR_Full_BIT] = "25000basekr-full",
93 [ETHTOOL_LINK_MODE_25000baseSR_Full_BIT] = "25000basesr-full",
94 [ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT] = "50000basecr2-full",
95 [ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT] = "50000basekr2-full",
96 [ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT] = "100000basekr4-full",
97 [ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT] = "100000basesr4-full",
98 [ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT] = "100000basecr4-full",
99 [ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT] = "100000baselr4-er4-full",
100 [ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT] = "50000basesr2-full",
101 [ETHTOOL_LINK_MODE_1000baseX_Full_BIT] = "1000basex-full",
102 [ETHTOOL_LINK_MODE_10000baseCR_Full_BIT] = "10000basecr-full",
103 [ETHTOOL_LINK_MODE_10000baseSR_Full_BIT] = "10000basesr-full",
104 [ETHTOOL_LINK_MODE_10000baseLR_Full_BIT] = "10000baselr-full",
105 [ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT] = "10000baselrm-full",
106 [ETHTOOL_LINK_MODE_10000baseER_Full_BIT] = "10000baseer-full",
107 [ETHTOOL_LINK_MODE_2500baseT_Full_BIT] = "2500baset-full",
108 [ETHTOOL_LINK_MODE_5000baseT_Full_BIT] = "5000baset-full",
109 [ETHTOOL_LINK_MODE_FEC_NONE_BIT] = "fec-none",
110 [ETHTOOL_LINK_MODE_FEC_RS_BIT] = "fec-rs",
111 [ETHTOOL_LINK_MODE_FEC_BASER_BIT] = "fec-baser",
6cf0a204 112};
5dd10118 113/* Make sure the array is large enough to fit all bits */
5c2316c6 114assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < N_ADVERTISE);
6cf0a204 115
2d18ac44 116DEFINE_STRING_TABLE_LOOKUP(ethtool_link_mode_bit, enum ethtool_link_mode_bit_indices);
6cf0a204 117
a5010333
TG
118int ethtool_connect(int *ret) {
119 int fd;
120
121 assert_return(ret, -EINVAL);
122
429b4350 123 fd = socket_ioctl_fd();
ece174c5 124 if (fd < 0)
429b4350 125 return fd;
2b44daaa 126
a5010333
TG
127 *ret = fd;
128
129 return 0;
130}
131
aedca892 132int ethtool_get_driver(int *fd, const char *ifname, char **ret) {
61f3af4f
LP
133 struct ethtool_drvinfo ecmd = {
134 .cmd = ETHTOOL_GDRVINFO
135 };
136 struct ifreq ifr = {
137 .ifr_data = (void*) &ecmd
138 };
139 char *d;
847a8a5f
TG
140 int r;
141
aedca892
TG
142 if (*fd < 0) {
143 r = ethtool_connect(fd);
f647962d 144 if (r < 0)
5c2316c6 145 return log_warning_errno(r, "ethtool: could not connect to ethtool: %m");
aedca892
TG
146 }
147
847a8a5f 148 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
847a8a5f 149
aedca892 150 r = ioctl(*fd, SIOCETHTOOL, &ifr);
847a8a5f
TG
151 if (r < 0)
152 return -errno;
153
61f3af4f
LP
154 d = strdup(ecmd.driver);
155 if (!d)
847a8a5f
TG
156 return -ENOMEM;
157
61f3af4f 158 *ret = d;
847a8a5f
TG
159 return 0;
160}
161
14cb109d 162int ethtool_set_speed(int *fd, const char *ifname, unsigned speed, Duplex duplex) {
6c0519c0
TG
163 struct ethtool_cmd ecmd = {
164 .cmd = ETHTOOL_GSET
165 };
166 struct ifreq ifr = {
167 .ifr_data = (void*) &ecmd
168 };
0a2c2294 169 bool need_update = false;
a5010333
TG
170 int r;
171
5fde13d7 172 if (speed == 0 && duplex == _DUP_INVALID)
a5010333
TG
173 return 0;
174
aedca892
TG
175 if (*fd < 0) {
176 r = ethtool_connect(fd);
f647962d 177 if (r < 0)
5c2316c6 178 return log_warning_errno(r, "ethtool: could not connect to ethtool: %m");
aedca892
TG
179 }
180
a5010333 181 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
a5010333 182
aedca892 183 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
184 if (r < 0)
185 return -errno;
186
187 if (ethtool_cmd_speed(&ecmd) != speed) {
188 ethtool_cmd_speed_set(&ecmd, speed);
189 need_update = true;
190 }
191
5fde13d7
TG
192 switch (duplex) {
193 case DUP_HALF:
a5010333
TG
194 if (ecmd.duplex != DUPLEX_HALF) {
195 ecmd.duplex = DUPLEX_HALF;
196 need_update = true;
197 }
5fde13d7
TG
198 break;
199 case DUP_FULL:
a5010333
TG
200 if (ecmd.duplex != DUPLEX_FULL) {
201 ecmd.duplex = DUPLEX_FULL;
202 need_update = true;
203 }
5fde13d7
TG
204 break;
205 default:
206 break;
a5010333
TG
207 }
208
209 if (need_update) {
210 ecmd.cmd = ETHTOOL_SSET;
211
aedca892 212 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
213 if (r < 0)
214 return -errno;
215 }
216
217 return 0;
218}
219
aedca892 220int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) {
6c0519c0
TG
221 struct ethtool_wolinfo ecmd = {
222 .cmd = ETHTOOL_GWOL
223 };
224 struct ifreq ifr = {
225 .ifr_data = (void*) &ecmd
226 };
0a2c2294 227 bool need_update = false;
a5010333
TG
228 int r;
229
5fde13d7 230 if (wol == _WOL_INVALID)
a5010333
TG
231 return 0;
232
aedca892
TG
233 if (*fd < 0) {
234 r = ethtool_connect(fd);
f647962d 235 if (r < 0)
5c2316c6 236 return log_warning_errno(r, "ethtool: could not connect to ethtool: %m");
aedca892
TG
237 }
238
a5010333 239 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
a5010333 240
aedca892 241 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
242 if (r < 0)
243 return -errno;
244
5fde13d7 245 switch (wol) {
617da14c
SS
246 case WOL_PHY:
247 if (ecmd.wolopts != WAKE_PHY) {
248 ecmd.wolopts = WAKE_PHY;
249 need_update = true;
250 }
251 break;
252 case WOL_UCAST:
253 if (ecmd.wolopts != WAKE_UCAST) {
254 ecmd.wolopts = WAKE_UCAST;
255 need_update = true;
256 }
257 break;
258 case WOL_MCAST:
259 if (ecmd.wolopts != WAKE_MCAST) {
260 ecmd.wolopts = WAKE_MCAST;
261 need_update = true;
262 }
263 break;
264 case WOL_BCAST:
265 if (ecmd.wolopts != WAKE_BCAST) {
266 ecmd.wolopts = WAKE_BCAST;
267 need_update = true;
268 }
269 break;
270 case WOL_ARP:
271 if (ecmd.wolopts != WAKE_ARP) {
272 ecmd.wolopts = WAKE_ARP;
273 need_update = true;
274 }
275 break;
276 case WOL_MAGIC:
277 if (ecmd.wolopts != WAKE_MAGIC) {
278 ecmd.wolopts = WAKE_MAGIC;
279 need_update = true;
280 }
281 break;
282 case WOL_MAGICSECURE:
283 if (ecmd.wolopts != WAKE_MAGICSECURE) {
284 ecmd.wolopts = WAKE_MAGICSECURE;
285 need_update = true;
286 }
287 break;
288 case WOL_OFF:
289 if (ecmd.wolopts != 0) {
290 ecmd.wolopts = 0;
291 need_update = true;
292 }
293 break;
294 default:
295 break;
5fde13d7 296 }
a5010333
TG
297
298 if (need_update) {
299 ecmd.cmd = ETHTOOL_SWOL;
300
aedca892 301 r = ioctl(*fd, SIOCETHTOOL, &ifr);
a5010333
TG
302 if (r < 0)
303 return -errno;
304 }
305
306 return 0;
307}
50725d10 308
2b44daaa 309static int get_stringset(int fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) {
50725d10 310 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
a9dee27f
SS
311 struct {
312 struct ethtool_sset_info info;
313 uint32_t space;
314 } buffer = {
315 .info = {
316 .cmd = ETHTOOL_GSSET_INFO,
317 .sset_mask = UINT64_C(1) << stringset_id,
318 },
50725d10
SS
319 };
320 unsigned len;
321 int r;
322
a9dee27f 323 ifr->ifr_data = (void *) &buffer.info;
50725d10 324
2b44daaa 325 r = ioctl(fd, SIOCETHTOOL, ifr);
50725d10
SS
326 if (r < 0)
327 return -errno;
328
a9dee27f 329 if (!buffer.info.sset_mask)
50725d10
SS
330 return -EINVAL;
331
a9dee27f 332 len = buffer.info.data[0];
50725d10
SS
333
334 strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN);
335 if (!strings)
336 return -ENOMEM;
337
338 strings->cmd = ETHTOOL_GSTRINGS;
339 strings->string_set = stringset_id;
340 strings->len = len;
341
342 ifr->ifr_data = (void *) strings;
343
2b44daaa 344 r = ioctl(fd, SIOCETHTOOL, ifr);
50725d10
SS
345 if (r < 0)
346 return -errno;
347
ae2a15bc 348 *gstrings = TAKE_PTR(strings);
50725d10
SS
349
350 return 0;
351}
352
353static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) {
354 unsigned i;
355
356 for (i = 0; i < strings->len; i++) {
357 if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature))
358 return i;
359 }
360
ee60be46 361 return -ENODATA;
50725d10
SS
362}
363
cc2ff878 364int ethtool_set_features(int *fd, const char *ifname, int *features) {
50725d10
SS
365 _cleanup_free_ struct ethtool_gstrings *strings = NULL;
366 struct ethtool_sfeatures *sfeatures;
367 int block, bit, i, r;
a9dee27f 368 struct ifreq ifr = {};
50725d10
SS
369
370 if (*fd < 0) {
371 r = ethtool_connect(fd);
372 if (r < 0)
5c2316c6 373 return log_warning_errno(r, "ethtool: could not connect to ethtool: %m");
50725d10
SS
374 }
375
376 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
377
2b44daaa 378 r = get_stringset(*fd, &ifr, ETH_SS_FEATURES, &strings);
50725d10 379 if (r < 0)
5c2316c6 380 return log_warning_errno(r, "ethtool: could not get ethtool features for %s", ifname);
50725d10 381
3301f9eb 382 sfeatures = alloca0(sizeof(struct ethtool_sfeatures) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
50725d10
SS
383 sfeatures->cmd = ETHTOOL_SFEATURES;
384 sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
385
386 for (i = 0; i < _NET_DEV_FEAT_MAX; i++) {
387
388 if (features[i] != -1) {
389
390 r = find_feature_index(strings, netdev_feature_table[i]);
391 if (r < 0) {
5c2316c6 392 log_warning_errno(r, "ethtool: could not find feature: %s", netdev_feature_table[i]);
50725d10
SS
393 continue;
394 }
395
396 block = r / 32;
397 bit = r % 32;
398
399 sfeatures->features[block].valid |= 1 << bit;
400
401 if (features[i])
402 sfeatures->features[block].requested |= 1 << bit;
403 else
404 sfeatures->features[block].requested &= ~(1 << bit);
405 }
406 }
407
408 ifr.ifr_data = (void *) sfeatures;
409
410 r = ioctl(*fd, SIOCETHTOOL, &ifr);
411 if (r < 0)
5c2316c6 412 return log_warning_errno(r, "ethtool: could not set ethtool features for %s", ifname);
50725d10
SS
413
414 return 0;
415}
a39f92d3 416
2b44daaa 417static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **g) {
a39f92d3
SS
418 struct ecmd {
419 struct ethtool_link_settings req;
420 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
421 } ecmd = {
422 .req.cmd = ETHTOOL_GLINKSETTINGS,
423 };
424 struct ethtool_link_usettings *u;
425 unsigned offset;
426 int r;
427
428 /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS
429 handshake first to agree on the length of the link mode bitmaps. If kernel doesn't
430 agree with user, it returns the bitmap length it is expecting from user as a negative
431 length (and cmd field is 0). When kernel and user agree, kernel returns valid info in
432 all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on
433 https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf
434 */
435
436 ifr->ifr_data = (void *) &ecmd;
437
2b44daaa 438 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
439 if (r < 0)
440 return -errno;
441
442 if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
6b44a121 443 return -EOPNOTSUPP;
a39f92d3
SS
444
445 ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
446
447 ifr->ifr_data = (void *) &ecmd;
448
2b44daaa 449 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
450 if (r < 0)
451 return -errno;
452
453 if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
6b44a121 454 return -EOPNOTSUPP;
a39f92d3 455
b9bc7d42 456 u = new(struct ethtool_link_usettings, 1);
a39f92d3
SS
457 if (!u)
458 return -ENOMEM;
459
b9bc7d42
YW
460 *u = (struct ethtool_link_usettings) {
461 .base = ecmd.req,
462 };
a39f92d3
SS
463
464 offset = 0;
465 memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
466
467 offset += ecmd.req.link_mode_masks_nwords;
468 memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
469
470 offset += ecmd.req.link_mode_masks_nwords;
471 memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
472
473 *g = u;
474
475 return 0;
476}
477
2b44daaa 478static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
a39f92d3
SS
479 struct ethtool_link_usettings *e;
480 struct ethtool_cmd ecmd = {
481 .cmd = ETHTOOL_GSET,
482 };
483 int r;
484
485 ifr->ifr_data = (void *) &ecmd;
486
2b44daaa 487 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
488 if (r < 0)
489 return -errno;
490
b9bc7d42 491 e = new(struct ethtool_link_usettings, 1);
a39f92d3
SS
492 if (!e)
493 return -ENOMEM;
494
b9bc7d42
YW
495 *e = (struct ethtool_link_usettings) {
496 .base.cmd = ETHTOOL_GSET,
497 .base.link_mode_masks_nwords = 1,
498 .base.speed = ethtool_cmd_speed(&ecmd),
499 .base.duplex = ecmd.duplex,
500 .base.port = ecmd.port,
501 .base.phy_address = ecmd.phy_address,
502 .base.autoneg = ecmd.autoneg,
503 .base.mdio_support = ecmd.mdio_support,
504
505 .link_modes.supported[0] = ecmd.supported,
506 .link_modes.advertising[0] = ecmd.advertising,
507 .link_modes.lp_advertising[0] = ecmd.lp_advertising,
508 };
a39f92d3
SS
509
510 *u = e;
511
512 return 0;
513}
514
2b44daaa 515static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
a39f92d3
SS
516 struct {
517 struct ethtool_link_settings req;
518 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
94d4acbe 519 } ecmd = {};
14cb109d 520 unsigned offset;
a39f92d3
SS
521 int r;
522
523 if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
524 return -EINVAL;
525
89e1ba0a 526 ecmd.req = u->base;
94d4acbe 527 ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
a39f92d3
SS
528 offset = 0;
529 memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords);
530
531 offset += ecmd.req.link_mode_masks_nwords;
532 memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords);
533
534 offset += ecmd.req.link_mode_masks_nwords;
535 memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords);
536
537 ifr->ifr_data = (void *) &ecmd;
538
2b44daaa 539 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
540 if (r < 0)
541 return -errno;
542
543 return 0;
544}
545
2b44daaa 546static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
a39f92d3
SS
547 struct ethtool_cmd ecmd = {
548 .cmd = ETHTOOL_SSET,
549 };
550 int r;
551
552 if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0)
553 return -EINVAL;
554
555 ecmd.supported = u->link_modes.supported[0];
556 ecmd.advertising = u->link_modes.advertising[0];
557 ecmd.lp_advertising = u->link_modes.lp_advertising[0];
558
559 ethtool_cmd_speed_set(&ecmd, u->base.speed);
560
561 ecmd.duplex = u->base.duplex;
562 ecmd.port = u->base.port;
563 ecmd.phy_address = u->base.phy_address;
564 ecmd.autoneg = u->base.autoneg;
565 ecmd.mdio_support = u->base.mdio_support;
6cf0a204
SS
566 ecmd.eth_tp_mdix = u->base.eth_tp_mdix;
567 ecmd.eth_tp_mdix_ctrl = u->base.eth_tp_mdix_ctrl;
a39f92d3
SS
568
569 ifr->ifr_data = (void *) &ecmd;
570
2b44daaa 571 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
572 if (r < 0)
573 return -errno;
574
575 return 0;
576}
577
578/* If autonegotiation is disabled, the speed and duplex represent the fixed link
579 * mode and are writable if the driver supports multiple link modes. If it is
49c603bd 580 * enabled then they are read-only. If the link is up they represent the negotiated
a39f92d3
SS
581 * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
582 * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
583 */
5c2316c6
YW
584int ethtool_set_glinksettings(
585 int *fd,
586 const char *ifname,
587 int autonegotiation,
588 uint32_t advertise[static N_ADVERTISE],
589 size_t speed,
590 Duplex duplex,
591 NetDevPort port) {
a39f92d3
SS
592 _cleanup_free_ struct ethtool_link_usettings *u = NULL;
593 struct ifreq ifr = {};
594 int r;
595
5c2316c6
YW
596 if (autonegotiation != AUTONEG_DISABLE && memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
597 log_info("ethtool: autonegotiation is unset or enabled, the speed and duplex are not writable.");
a39f92d3
SS
598 return 0;
599 }
600
601 if (*fd < 0) {
602 r = ethtool_connect(fd);
603 if (r < 0)
5c2316c6 604 return log_warning_errno(r, "ethtool: could not connect to ethtool: %m");
a39f92d3
SS
605 }
606
607 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
608
2b44daaa 609 r = get_glinksettings(*fd, &ifr, &u);
a39f92d3 610 if (r < 0) {
2b44daaa 611 r = get_gset(*fd, &ifr, &u);
a39f92d3 612 if (r < 0)
5c2316c6 613 return log_warning_errno(r, "ethtool: Cannot get device settings for %s : %m", ifname);
a39f92d3
SS
614 }
615
5c2316c6
YW
616 if (speed > 0)
617 u->base.speed = DIV_ROUND_UP(speed, 1000000);
593022fa 618
5c2316c6
YW
619 if (duplex != _DUP_INVALID)
620 u->base.duplex = duplex;
a39f92d3 621
5c2316c6
YW
622 if (port != _NET_DEV_PORT_INVALID)
623 u->base.port = port;
a39f92d3 624
5c2316c6
YW
625 if (autonegotiation >= 0)
626 u->base.autoneg = autonegotiation;
a39f92d3 627
5c2316c6 628 if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
a0e1ad10 629 u->base.autoneg = AUTONEG_ENABLE;
5c2316c6
YW
630 memcpy(&u->link_modes.advertising, advertise, sizeof(uint32_t) * N_ADVERTISE);
631 memzero((uint8_t*) &u->link_modes.advertising + sizeof(uint32_t) * N_ADVERTISE,
632 ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(uint32_t) * N_ADVERTISE);
5dd10118 633 }
6cf0a204 634
a39f92d3 635 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
2b44daaa 636 r = set_slinksettings(*fd, &ifr, u);
a39f92d3 637 else
2b44daaa 638 r = set_sset(*fd, &ifr, u);
a39f92d3 639 if (r < 0)
5c2316c6 640 return log_warning_errno(r, "ethtool: Cannot set device settings for %s : %m", ifname);
a39f92d3
SS
641
642 return r;
643}
5f945202 644
5f945202
SS
645int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) {
646 struct ethtool_channels ecmd = {
647 .cmd = ETHTOOL_GCHANNELS
648 };
649 struct ifreq ifr = {
650 .ifr_data = (void*) &ecmd
651 };
652
653 bool need_update = false;
654 int r;
655
656 if (*fd < 0) {
657 r = ethtool_connect(fd);
658 if (r < 0)
5c2316c6 659 return log_warning_errno(r, "ethtool: could not connect to ethtool: %m");
5f945202
SS
660 }
661
662 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
663
664 r = ioctl(*fd, SIOCETHTOOL, &ifr);
665 if (r < 0)
666 return -errno;
667
668 if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) {
669 ecmd.rx_count = channels->rx_count;
670 need_update = true;
671 }
672
673 if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) {
674 ecmd.tx_count = channels->tx_count;
675 need_update = true;
676 }
677
678 if (channels->other_count_set && ecmd.other_count != channels->other_count) {
679 ecmd.other_count = channels->other_count;
680 need_update = true;
681 }
682
683 if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) {
684 ecmd.combined_count = channels->combined_count;
685 need_update = true;
686 }
687
688 if (need_update) {
689 ecmd.cmd = ETHTOOL_SCHANNELS;
690
691 r = ioctl(*fd, SIOCETHTOOL, &ifr);
692 if (r < 0)
693 return -errno;
694 }
695
696 return 0;
697}
6cf0a204 698
5c2316c6
YW
699int config_parse_channel(const char *unit,
700 const char *filename,
701 unsigned line,
702 const char *section,
703 unsigned section_line,
704 const char *lvalue,
705 int ltype,
706 const char *rvalue,
707 void *data,
708 void *userdata) {
709 netdev_channels *channels = data;
710 uint32_t k;
711 int r;
712
713 assert(filename);
714 assert(section);
715 assert(lvalue);
716 assert(rvalue);
717 assert(data);
718
719 r = safe_atou32(rvalue, &k);
720 if (r < 0) {
721 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
722 return 0;
723 }
724
725 if (k < 1) {
726 log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
727 return 0;
728 }
729
730 if (streq(lvalue, "RxChannels")) {
731 channels->rx_count = k;
732 channels->rx_count_set = true;
733 } else if (streq(lvalue, "TxChannels")) {
734 channels->tx_count = k;
735 channels->tx_count_set = true;
736 } else if (streq(lvalue, "OtherChannels")) {
737 channels->other_count = k;
738 channels->other_count_set = true;
739 } else if (streq(lvalue, "CombinedChannels")) {
740 channels->combined_count = k;
741 channels->combined_count_set = true;
742 }
743
744 return 0;
745}
746
6cf0a204
SS
747int config_parse_advertise(const char *unit,
748 const char *filename,
749 unsigned line,
750 const char *section,
751 unsigned section_line,
752 const char *lvalue,
753 int ltype,
754 const char *rvalue,
755 void *data,
756 void *userdata) {
5c2316c6 757 uint32_t *advertise = data;
6cf0a204
SS
758 const char *p;
759 int r;
760
761 assert(filename);
762 assert(section);
763 assert(lvalue);
764 assert(rvalue);
765 assert(data);
766
767 if (isempty(rvalue)) {
768 /* Empty string resets the value. */
5c2316c6 769 memzero(advertise, sizeof(uint32_t) * N_ADVERTISE);
6cf0a204
SS
770 return 0;
771 }
772
773 for (p = rvalue;;) {
774 _cleanup_free_ char *w = NULL;
5dd10118 775 enum ethtool_link_mode_bit_indices mode;
6cf0a204
SS
776
777 r = extract_first_word(&p, &w, NULL, 0);
778 if (r == -ENOMEM)
779 return log_oom();
780 if (r < 0) {
781 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split advertise modes '%s', ignoring: %m", rvalue);
782 break;
783 }
784 if (r == 0)
785 break;
786
2d18ac44 787 mode = ethtool_link_mode_bit_from_string(w);
84fb56d3
YW
788 /* We reuse the kernel provided enum which does not contain negative value. So, the cast
789 * below is mandatory. Otherwise, the check below always passes and access an invalid address. */
790 if ((int) mode < 0) {
6cf0a204
SS
791 log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w);
792 continue;
793 }
2d18ac44 794
5c2316c6 795 advertise[mode / 32] |= 1UL << (mode % 32);
2d18ac44 796 }
6cf0a204
SS
797
798 return 0;
799}