]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/udev/net/ethtool-util.c
util: split out some stuff into a new file limits-util.[ch]
[thirdparty/systemd.git] / src / udev / net / 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"
593022fa 10#include "link-config.h"
ab1263d7
LP
11#include "log.h"
12#include "missing.h"
429b4350 13#include "socket-util.h"
8b43440b 14#include "string-table.h"
a5010333
TG
15#include "strxcpyx.h"
16#include "util.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
ZJS
113/* Make sure the array is large enough to fit all bits */
114assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < ELEMENTSOF(((struct link_config){}).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
MS
144 if (r < 0)
145 return log_warning_errno(r, "link_config: 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
MS
177 if (r < 0)
178 return log_warning_errno(r, "link_config: 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
MS
235 if (r < 0)
236 return log_warning_errno(r, "link_config: 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)
373 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
374 }
375
376 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
377
2b44daaa 378 r = get_stringset(*fd, &ifr, ETH_SS_FEATURES, &strings);
50725d10
SS
379 if (r < 0)
380 return log_warning_errno(r, "link_config: could not get ethtool features for %s", ifname);
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) {
392 log_warning_errno(r, "link_config: could not find feature: %s", netdev_feature_table[i]);
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)
412 return log_warning_errno(r, "link_config: could not set ethtool features for %s", ifname);
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
SS
455
456 u = new0(struct ethtool_link_usettings , 1);
457 if (!u)
458 return -ENOMEM;
459
801d2c9f 460 u->base = ecmd.req;
a39f92d3
SS
461
462 offset = 0;
463 memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
464
465 offset += ecmd.req.link_mode_masks_nwords;
466 memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
467
468 offset += ecmd.req.link_mode_masks_nwords;
469 memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
470
471 *g = u;
472
473 return 0;
474}
475
2b44daaa 476static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
a39f92d3
SS
477 struct ethtool_link_usettings *e;
478 struct ethtool_cmd ecmd = {
479 .cmd = ETHTOOL_GSET,
480 };
481 int r;
482
483 ifr->ifr_data = (void *) &ecmd;
484
2b44daaa 485 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
486 if (r < 0)
487 return -errno;
488
489 e = new0(struct ethtool_link_usettings, 1);
490 if (!e)
491 return -ENOMEM;
492
493 e->base.cmd = ETHTOOL_GSET;
494
495 e->base.link_mode_masks_nwords = 1;
496 e->base.speed = ethtool_cmd_speed(&ecmd);
497 e->base.duplex = ecmd.duplex;
498 e->base.port = ecmd.port;
499 e->base.phy_address = ecmd.phy_address;
500 e->base.autoneg = ecmd.autoneg;
501 e->base.mdio_support = ecmd.mdio_support;
502
503 e->link_modes.supported[0] = ecmd.supported;
504 e->link_modes.advertising[0] = ecmd.advertising;
505 e->link_modes.lp_advertising[0] = ecmd.lp_advertising;
506
507 *u = e;
508
509 return 0;
510}
511
2b44daaa 512static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
a39f92d3
SS
513 struct {
514 struct ethtool_link_settings req;
515 __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
94d4acbe 516 } ecmd = {};
14cb109d 517 unsigned offset;
a39f92d3
SS
518 int r;
519
520 if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
521 return -EINVAL;
522
89e1ba0a 523 ecmd.req = u->base;
94d4acbe 524 ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
a39f92d3
SS
525 offset = 0;
526 memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords);
527
528 offset += ecmd.req.link_mode_masks_nwords;
529 memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 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.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords);
533
534 ifr->ifr_data = (void *) &ecmd;
535
2b44daaa 536 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
537 if (r < 0)
538 return -errno;
539
540 return 0;
541}
542
2b44daaa 543static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
a39f92d3
SS
544 struct ethtool_cmd ecmd = {
545 .cmd = ETHTOOL_SSET,
546 };
547 int r;
548
549 if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0)
550 return -EINVAL;
551
552 ecmd.supported = u->link_modes.supported[0];
553 ecmd.advertising = u->link_modes.advertising[0];
554 ecmd.lp_advertising = u->link_modes.lp_advertising[0];
555
556 ethtool_cmd_speed_set(&ecmd, u->base.speed);
557
558 ecmd.duplex = u->base.duplex;
559 ecmd.port = u->base.port;
560 ecmd.phy_address = u->base.phy_address;
561 ecmd.autoneg = u->base.autoneg;
562 ecmd.mdio_support = u->base.mdio_support;
6cf0a204
SS
563 ecmd.eth_tp_mdix = u->base.eth_tp_mdix;
564 ecmd.eth_tp_mdix_ctrl = u->base.eth_tp_mdix_ctrl;
a39f92d3
SS
565
566 ifr->ifr_data = (void *) &ecmd;
567
2b44daaa 568 r = ioctl(fd, SIOCETHTOOL, ifr);
a39f92d3
SS
569 if (r < 0)
570 return -errno;
571
572 return 0;
573}
574
575/* If autonegotiation is disabled, the speed and duplex represent the fixed link
576 * mode and are writable if the driver supports multiple link modes. If it is
49c603bd 577 * enabled then they are read-only. If the link is up they represent the negotiated
a39f92d3
SS
578 * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
579 * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
580 */
593022fa 581int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *link) {
a39f92d3
SS
582 _cleanup_free_ struct ethtool_link_usettings *u = NULL;
583 struct ifreq ifr = {};
584 int r;
585
a0e1ad10 586 if (link->autonegotiation != AUTONEG_DISABLE && eqzero(link->advertise)) {
a39f92d3
SS
587 log_info("link_config: autonegotiation is unset or enabled, the speed and duplex are not writable.");
588 return 0;
589 }
590
591 if (*fd < 0) {
592 r = ethtool_connect(fd);
593 if (r < 0)
594 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
595 }
596
597 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
598
2b44daaa 599 r = get_glinksettings(*fd, &ifr, &u);
a39f92d3 600 if (r < 0) {
2b44daaa 601 r = get_gset(*fd, &ifr, &u);
a39f92d3
SS
602 if (r < 0)
603 return log_warning_errno(r, "link_config: Cannot get device settings for %s : %m", ifname);
604 }
605
593022fa 606 if (link->speed)
9c5e1172 607 u->base.speed = DIV_ROUND_UP(link->speed, 1000000);
593022fa
SS
608
609 if (link->duplex != _DUP_INVALID)
610 u->base.duplex = link->duplex;
a39f92d3 611
593022fa 612 if (link->port != _NET_DEV_PORT_INVALID)
20d4e995 613 u->base.port = link->port;
a39f92d3 614
a0e1ad10
JJ
615 if (link->autonegotiation >= 0)
616 u->base.autoneg = link->autonegotiation;
a39f92d3 617
5dd10118 618 if (!eqzero(link->advertise)) {
a0e1ad10 619 u->base.autoneg = AUTONEG_ENABLE;
5dd10118
ZJS
620 memcpy(&u->link_modes.advertising, link->advertise, sizeof(link->advertise));
621 memzero((uint8_t*) &u->link_modes.advertising + sizeof(link->advertise),
622 ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(link->advertise));
623 }
6cf0a204 624
a39f92d3 625 if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
2b44daaa 626 r = set_slinksettings(*fd, &ifr, u);
a39f92d3 627 else
2b44daaa 628 r = set_sset(*fd, &ifr, u);
a39f92d3
SS
629 if (r < 0)
630 return log_warning_errno(r, "link_config: Cannot set device settings for %s : %m", ifname);
631
632 return r;
633}
5f945202
SS
634
635int config_parse_channel(const char *unit,
636 const char *filename,
637 unsigned line,
638 const char *section,
639 unsigned section_line,
640 const char *lvalue,
641 int ltype,
642 const char *rvalue,
643 void *data,
644 void *userdata) {
645 link_config *config = data;
646 uint32_t k;
647 int r;
648
649 assert(filename);
650 assert(section);
651 assert(lvalue);
652 assert(rvalue);
653 assert(data);
654
655 r = safe_atou32(rvalue, &k);
656 if (r < 0) {
657 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
658 return 0;
659 }
660
661 if (k < 1) {
662 log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
663 return 0;
664 }
665
666 if (streq(lvalue, "RxChannels")) {
667 config->channels.rx_count = k;
668 config->channels.rx_count_set = true;
669 } else if (streq(lvalue, "TxChannels")) {
670 config->channels.tx_count = k;
671 config->channels.tx_count_set = true;
672 } else if (streq(lvalue, "OtherChannels")) {
673 config->channels.other_count = k;
674 config->channels.other_count_set = true;
675 } else if (streq(lvalue, "CombinedChannels")) {
676 config->channels.combined_count = k;
677 config->channels.combined_count_set = true;
678 }
679
680 return 0;
681}
682
683int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) {
684 struct ethtool_channels ecmd = {
685 .cmd = ETHTOOL_GCHANNELS
686 };
687 struct ifreq ifr = {
688 .ifr_data = (void*) &ecmd
689 };
690
691 bool need_update = false;
692 int r;
693
694 if (*fd < 0) {
695 r = ethtool_connect(fd);
696 if (r < 0)
697 return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
698 }
699
700 strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
701
702 r = ioctl(*fd, SIOCETHTOOL, &ifr);
703 if (r < 0)
704 return -errno;
705
706 if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) {
707 ecmd.rx_count = channels->rx_count;
708 need_update = true;
709 }
710
711 if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) {
712 ecmd.tx_count = channels->tx_count;
713 need_update = true;
714 }
715
716 if (channels->other_count_set && ecmd.other_count != channels->other_count) {
717 ecmd.other_count = channels->other_count;
718 need_update = true;
719 }
720
721 if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) {
722 ecmd.combined_count = channels->combined_count;
723 need_update = true;
724 }
725
726 if (need_update) {
727 ecmd.cmd = ETHTOOL_SCHANNELS;
728
729 r = ioctl(*fd, SIOCETHTOOL, &ifr);
730 if (r < 0)
731 return -errno;
732 }
733
734 return 0;
735}
6cf0a204
SS
736
737int config_parse_advertise(const char *unit,
738 const char *filename,
739 unsigned line,
740 const char *section,
741 unsigned section_line,
742 const char *lvalue,
743 int ltype,
744 const char *rvalue,
745 void *data,
746 void *userdata) {
747 link_config *config = data;
6cf0a204
SS
748 const char *p;
749 int r;
750
751 assert(filename);
752 assert(section);
753 assert(lvalue);
754 assert(rvalue);
755 assert(data);
756
757 if (isempty(rvalue)) {
758 /* Empty string resets the value. */
5dd10118 759 zero(config->advertise);
6cf0a204
SS
760 return 0;
761 }
762
763 for (p = rvalue;;) {
764 _cleanup_free_ char *w = NULL;
5dd10118 765 enum ethtool_link_mode_bit_indices mode;
6cf0a204
SS
766
767 r = extract_first_word(&p, &w, NULL, 0);
768 if (r == -ENOMEM)
769 return log_oom();
770 if (r < 0) {
771 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split advertise modes '%s', ignoring: %m", rvalue);
772 break;
773 }
774 if (r == 0)
775 break;
776
2d18ac44 777 mode = ethtool_link_mode_bit_from_string(w);
84fb56d3
YW
778 /* We reuse the kernel provided enum which does not contain negative value. So, the cast
779 * below is mandatory. Otherwise, the check below always passes and access an invalid address. */
780 if ((int) mode < 0) {
6cf0a204
SS
781 log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w);
782 continue;
783 }
2d18ac44 784
5dd10118 785 config->advertise[mode / 32] |= 1UL << (mode % 32);
2d18ac44 786 }
6cf0a204
SS
787
788 return 0;
789}