Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <net/if.h>
4 : #include <sys/ioctl.h>
5 : #include <linux/ethtool.h>
6 : #include <linux/sockios.h>
7 :
8 : #include "conf-parser.h"
9 : #include "ethtool-util.h"
10 : #include "extract-word.h"
11 : #include "log.h"
12 : #include "memory-util.h"
13 : #include "missing.h"
14 : #include "socket-util.h"
15 : #include "string-table.h"
16 : #include "strxcpyx.h"
17 :
18 : static const char* const duplex_table[_DUP_MAX] = {
19 : [DUP_FULL] = "full",
20 : [DUP_HALF] = "half"
21 : };
22 :
23 8 : DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
24 0 : DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
25 :
26 : static const char* const wol_table[_WOL_MAX] = {
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",
34 : [WOL_OFF] = "off",
35 : };
36 :
37 20 : DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan);
38 0 : DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting");
39 :
40 : static const char* const port_table[] = {
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",
46 : };
47 :
48 0 : DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort);
49 0 : DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting");
50 :
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",
57 : };
58 :
59 : static const char* const ethtool_link_mode_bit_table[] = {
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",
112 : [ETHTOOL_LINK_MODE_50000baseKR_Full_BIT] = "50000basekr_full",
113 : [ETHTOOL_LINK_MODE_50000baseSR_Full_BIT] = "50000basesr_full",
114 : [ETHTOOL_LINK_MODE_50000baseCR_Full_BIT] = "50000basecr_full",
115 : [ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT] = "50000baselr_er_fr_full",
116 : [ETHTOOL_LINK_MODE_50000baseDR_Full_BIT] = "50000basedr_full",
117 : [ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT] = "100000basekr2_full",
118 : [ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT] = "100000basesr2_full",
119 : [ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT] = "100000basecr2_full",
120 : [ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT] = "100000baselr2_er2_fr2_full",
121 : [ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT] = "100000basedr2_full",
122 : [ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT] = "200000basekr4_full",
123 : [ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT] = "200000basesr4_full",
124 : [ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT] = "200000basecr4_full",
125 : [ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT] = "200000baselr4_er4_fr4_full",
126 : [ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT] = "200000basedr4_full",
127 : };
128 : /* Make sure the array is large enough to fit all bits */
129 : assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < N_ADVERTISE);
130 :
131 0 : DEFINE_STRING_TABLE_LOOKUP(ethtool_link_mode_bit, enum ethtool_link_mode_bit_indices);
132 :
133 0 : static int ethtool_connect_or_warn(int *ret, bool warn) {
134 : int fd;
135 :
136 0 : assert_return(ret, -EINVAL);
137 :
138 0 : fd = socket_ioctl_fd();
139 0 : if (fd < 0)
140 0 : return log_full_errno(warn ? LOG_WARNING: LOG_DEBUG, fd,
141 : "ethtool: could not create control socket: %m");
142 :
143 0 : *ret = fd;
144 :
145 0 : return 0;
146 : }
147 :
148 0 : int ethtool_get_driver(int *fd, const char *ifname, char **ret) {
149 0 : struct ethtool_drvinfo ecmd = {
150 : .cmd = ETHTOOL_GDRVINFO
151 : };
152 0 : struct ifreq ifr = {
153 : .ifr_data = (void*) &ecmd
154 : };
155 : char *d;
156 : int r;
157 :
158 0 : if (*fd < 0) {
159 0 : r = ethtool_connect_or_warn(fd, true);
160 0 : if (r < 0)
161 0 : return r;
162 : }
163 :
164 0 : strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
165 :
166 0 : r = ioctl(*fd, SIOCETHTOOL, &ifr);
167 0 : if (r < 0)
168 0 : return -errno;
169 :
170 0 : d = strdup(ecmd.driver);
171 0 : if (!d)
172 0 : return -ENOMEM;
173 :
174 0 : *ret = d;
175 0 : return 0;
176 : }
177 :
178 0 : int ethtool_get_link_info(int *fd, const char *ifname,
179 : int *ret_autonegotiation, size_t *ret_speed,
180 : Duplex *ret_duplex, NetDevPort *ret_port) {
181 0 : struct ethtool_cmd ecmd = {
182 : .cmd = ETHTOOL_GSET,
183 : };
184 0 : struct ifreq ifr = {
185 : .ifr_data = (void*) &ecmd,
186 : };
187 : int r;
188 :
189 0 : if (*fd < 0) {
190 0 : r = ethtool_connect_or_warn(fd, false);
191 0 : if (r < 0)
192 0 : return r;
193 : }
194 :
195 0 : strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
196 :
197 0 : r = ioctl(*fd, SIOCETHTOOL, &ifr);
198 0 : if (r < 0)
199 0 : return -errno;
200 :
201 0 : if (ret_autonegotiation)
202 0 : *ret_autonegotiation = ecmd.autoneg;
203 :
204 0 : if (ret_speed) {
205 : uint32_t speed;
206 :
207 0 : speed = ethtool_cmd_speed(&ecmd);
208 0 : *ret_speed = speed == (uint32_t) SPEED_UNKNOWN ?
209 0 : SIZE_MAX : (size_t) speed * 1000 * 1000;
210 : }
211 :
212 0 : if (ret_duplex)
213 0 : *ret_duplex = ecmd.duplex;
214 :
215 0 : if (ret_port)
216 0 : *ret_port = ecmd.port;
217 :
218 0 : return 0;
219 : }
220 :
221 0 : int ethtool_set_speed(int *fd, const char *ifname, unsigned speed, Duplex duplex) {
222 0 : struct ethtool_cmd ecmd = {
223 : .cmd = ETHTOOL_GSET
224 : };
225 0 : struct ifreq ifr = {
226 : .ifr_data = (void*) &ecmd
227 : };
228 0 : bool need_update = false;
229 : int r;
230 :
231 0 : if (speed == 0 && duplex == _DUP_INVALID)
232 0 : return 0;
233 :
234 0 : if (*fd < 0) {
235 0 : r = ethtool_connect_or_warn(fd, true);
236 0 : if (r < 0)
237 0 : return r;
238 : }
239 :
240 0 : strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
241 :
242 0 : r = ioctl(*fd, SIOCETHTOOL, &ifr);
243 0 : if (r < 0)
244 0 : return -errno;
245 :
246 0 : if (ethtool_cmd_speed(&ecmd) != speed) {
247 0 : ethtool_cmd_speed_set(&ecmd, speed);
248 0 : need_update = true;
249 : }
250 :
251 0 : switch (duplex) {
252 0 : case DUP_HALF:
253 0 : if (ecmd.duplex != DUPLEX_HALF) {
254 0 : ecmd.duplex = DUPLEX_HALF;
255 0 : need_update = true;
256 : }
257 0 : break;
258 0 : case DUP_FULL:
259 0 : if (ecmd.duplex != DUPLEX_FULL) {
260 0 : ecmd.duplex = DUPLEX_FULL;
261 0 : need_update = true;
262 : }
263 0 : break;
264 0 : default:
265 0 : break;
266 : }
267 :
268 0 : if (need_update) {
269 0 : ecmd.cmd = ETHTOOL_SSET;
270 :
271 0 : r = ioctl(*fd, SIOCETHTOOL, &ifr);
272 0 : if (r < 0)
273 0 : return -errno;
274 : }
275 :
276 0 : return 0;
277 : }
278 :
279 0 : int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) {
280 0 : struct ethtool_wolinfo ecmd = {
281 : .cmd = ETHTOOL_GWOL
282 : };
283 0 : struct ifreq ifr = {
284 : .ifr_data = (void*) &ecmd
285 : };
286 0 : bool need_update = false;
287 : int r;
288 :
289 0 : if (wol == _WOL_INVALID)
290 0 : return 0;
291 :
292 0 : if (*fd < 0) {
293 0 : r = ethtool_connect_or_warn(fd, true);
294 0 : if (r < 0)
295 0 : return r;
296 : }
297 :
298 0 : strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
299 :
300 0 : r = ioctl(*fd, SIOCETHTOOL, &ifr);
301 0 : if (r < 0)
302 0 : return -errno;
303 :
304 0 : switch (wol) {
305 0 : case WOL_PHY:
306 0 : if (ecmd.wolopts != WAKE_PHY) {
307 0 : ecmd.wolopts = WAKE_PHY;
308 0 : need_update = true;
309 : }
310 0 : break;
311 0 : case WOL_UCAST:
312 0 : if (ecmd.wolopts != WAKE_UCAST) {
313 0 : ecmd.wolopts = WAKE_UCAST;
314 0 : need_update = true;
315 : }
316 0 : break;
317 0 : case WOL_MCAST:
318 0 : if (ecmd.wolopts != WAKE_MCAST) {
319 0 : ecmd.wolopts = WAKE_MCAST;
320 0 : need_update = true;
321 : }
322 0 : break;
323 0 : case WOL_BCAST:
324 0 : if (ecmd.wolopts != WAKE_BCAST) {
325 0 : ecmd.wolopts = WAKE_BCAST;
326 0 : need_update = true;
327 : }
328 0 : break;
329 0 : case WOL_ARP:
330 0 : if (ecmd.wolopts != WAKE_ARP) {
331 0 : ecmd.wolopts = WAKE_ARP;
332 0 : need_update = true;
333 : }
334 0 : break;
335 0 : case WOL_MAGIC:
336 0 : if (ecmd.wolopts != WAKE_MAGIC) {
337 0 : ecmd.wolopts = WAKE_MAGIC;
338 0 : need_update = true;
339 : }
340 0 : break;
341 0 : case WOL_MAGICSECURE:
342 0 : if (ecmd.wolopts != WAKE_MAGICSECURE) {
343 0 : ecmd.wolopts = WAKE_MAGICSECURE;
344 0 : need_update = true;
345 : }
346 0 : break;
347 0 : case WOL_OFF:
348 0 : if (ecmd.wolopts != 0) {
349 0 : ecmd.wolopts = 0;
350 0 : need_update = true;
351 : }
352 0 : break;
353 0 : default:
354 0 : break;
355 : }
356 :
357 0 : if (need_update) {
358 0 : ecmd.cmd = ETHTOOL_SWOL;
359 :
360 0 : r = ioctl(*fd, SIOCETHTOOL, &ifr);
361 0 : if (r < 0)
362 0 : return -errno;
363 : }
364 :
365 0 : return 0;
366 : }
367 :
368 0 : static int get_stringset(int fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) {
369 0 : _cleanup_free_ struct ethtool_gstrings *strings = NULL;
370 : struct {
371 : struct ethtool_sset_info info;
372 : uint32_t space;
373 0 : } buffer = {
374 : .info = {
375 : .cmd = ETHTOOL_GSSET_INFO,
376 0 : .sset_mask = UINT64_C(1) << stringset_id,
377 : },
378 : };
379 : unsigned len;
380 : int r;
381 :
382 0 : ifr->ifr_data = (void *) &buffer.info;
383 :
384 0 : r = ioctl(fd, SIOCETHTOOL, ifr);
385 0 : if (r < 0)
386 0 : return -errno;
387 :
388 0 : if (!buffer.info.sset_mask)
389 0 : return -EINVAL;
390 :
391 0 : len = buffer.info.data[0];
392 :
393 0 : strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN);
394 0 : if (!strings)
395 0 : return -ENOMEM;
396 :
397 0 : strings->cmd = ETHTOOL_GSTRINGS;
398 0 : strings->string_set = stringset_id;
399 0 : strings->len = len;
400 :
401 0 : ifr->ifr_data = (void *) strings;
402 :
403 0 : r = ioctl(fd, SIOCETHTOOL, ifr);
404 0 : if (r < 0)
405 0 : return -errno;
406 :
407 0 : *gstrings = TAKE_PTR(strings);
408 :
409 0 : return 0;
410 : }
411 :
412 0 : static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) {
413 : unsigned i;
414 :
415 0 : for (i = 0; i < strings->len; i++) {
416 0 : if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature))
417 0 : return i;
418 : }
419 :
420 0 : return -ENODATA;
421 : }
422 :
423 0 : int ethtool_set_features(int *fd, const char *ifname, int *features) {
424 0 : _cleanup_free_ struct ethtool_gstrings *strings = NULL;
425 : struct ethtool_sfeatures *sfeatures;
426 : int block, bit, i, r;
427 0 : struct ifreq ifr = {};
428 :
429 0 : if (*fd < 0) {
430 0 : r = ethtool_connect_or_warn(fd, true);
431 0 : if (r < 0)
432 0 : return r;
433 : }
434 :
435 0 : strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
436 :
437 0 : r = get_stringset(*fd, &ifr, ETH_SS_FEATURES, &strings);
438 0 : if (r < 0)
439 0 : return log_warning_errno(r, "ethtool: could not get ethtool features for %s", ifname);
440 :
441 0 : sfeatures = alloca0(sizeof(struct ethtool_sfeatures) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
442 0 : sfeatures->cmd = ETHTOOL_SFEATURES;
443 0 : sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
444 :
445 0 : for (i = 0; i < _NET_DEV_FEAT_MAX; i++) {
446 :
447 0 : if (features[i] != -1) {
448 :
449 0 : r = find_feature_index(strings, netdev_feature_table[i]);
450 0 : if (r < 0) {
451 0 : log_warning_errno(r, "ethtool: could not find feature: %s", netdev_feature_table[i]);
452 0 : continue;
453 : }
454 :
455 0 : block = r / 32;
456 0 : bit = r % 32;
457 :
458 0 : sfeatures->features[block].valid |= 1 << bit;
459 :
460 0 : if (features[i])
461 0 : sfeatures->features[block].requested |= 1 << bit;
462 : else
463 0 : sfeatures->features[block].requested &= ~(1 << bit);
464 : }
465 : }
466 :
467 0 : ifr.ifr_data = (void *) sfeatures;
468 :
469 0 : r = ioctl(*fd, SIOCETHTOOL, &ifr);
470 0 : if (r < 0)
471 0 : return log_warning_errno(r, "ethtool: could not set ethtool features for %s", ifname);
472 :
473 0 : return 0;
474 : }
475 :
476 0 : static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **g) {
477 : struct ecmd {
478 : struct ethtool_link_settings req;
479 : __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
480 0 : } ecmd = {
481 : .req.cmd = ETHTOOL_GLINKSETTINGS,
482 : };
483 : struct ethtool_link_usettings *u;
484 : unsigned offset;
485 : int r;
486 :
487 : /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS
488 : handshake first to agree on the length of the link mode bitmaps. If kernel doesn't
489 : agree with user, it returns the bitmap length it is expecting from user as a negative
490 : length (and cmd field is 0). When kernel and user agree, kernel returns valid info in
491 : all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on
492 : https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf
493 : */
494 :
495 0 : ifr->ifr_data = (void *) &ecmd;
496 :
497 0 : r = ioctl(fd, SIOCETHTOOL, ifr);
498 0 : if (r < 0)
499 0 : return -errno;
500 :
501 0 : if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
502 0 : return -EOPNOTSUPP;
503 :
504 0 : ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
505 :
506 0 : ifr->ifr_data = (void *) &ecmd;
507 :
508 0 : r = ioctl(fd, SIOCETHTOOL, ifr);
509 0 : if (r < 0)
510 0 : return -errno;
511 :
512 0 : if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
513 0 : return -EOPNOTSUPP;
514 :
515 0 : u = new(struct ethtool_link_usettings, 1);
516 0 : if (!u)
517 0 : return -ENOMEM;
518 :
519 0 : *u = (struct ethtool_link_usettings) {
520 : .base = ecmd.req,
521 : };
522 :
523 0 : offset = 0;
524 0 : memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
525 :
526 0 : offset += ecmd.req.link_mode_masks_nwords;
527 0 : memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
528 :
529 0 : offset += ecmd.req.link_mode_masks_nwords;
530 0 : memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
531 :
532 0 : *g = u;
533 :
534 0 : return 0;
535 : }
536 :
537 0 : static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
538 : struct ethtool_link_usettings *e;
539 0 : struct ethtool_cmd ecmd = {
540 : .cmd = ETHTOOL_GSET,
541 : };
542 : int r;
543 :
544 0 : ifr->ifr_data = (void *) &ecmd;
545 :
546 0 : r = ioctl(fd, SIOCETHTOOL, ifr);
547 0 : if (r < 0)
548 0 : return -errno;
549 :
550 0 : e = new(struct ethtool_link_usettings, 1);
551 0 : if (!e)
552 0 : return -ENOMEM;
553 :
554 0 : *e = (struct ethtool_link_usettings) {
555 : .base.cmd = ETHTOOL_GSET,
556 : .base.link_mode_masks_nwords = 1,
557 0 : .base.speed = ethtool_cmd_speed(&ecmd),
558 0 : .base.duplex = ecmd.duplex,
559 0 : .base.port = ecmd.port,
560 0 : .base.phy_address = ecmd.phy_address,
561 0 : .base.autoneg = ecmd.autoneg,
562 0 : .base.mdio_support = ecmd.mdio_support,
563 :
564 0 : .link_modes.supported[0] = ecmd.supported,
565 0 : .link_modes.advertising[0] = ecmd.advertising,
566 0 : .link_modes.lp_advertising[0] = ecmd.lp_advertising,
567 : };
568 :
569 0 : *u = e;
570 :
571 0 : return 0;
572 : }
573 :
574 0 : static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
575 : struct {
576 : struct ethtool_link_settings req;
577 : __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
578 0 : } ecmd = {};
579 : unsigned offset;
580 : int r;
581 :
582 0 : if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
583 0 : return -EINVAL;
584 :
585 0 : ecmd.req = u->base;
586 0 : ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
587 0 : offset = 0;
588 0 : memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords);
589 :
590 0 : offset += ecmd.req.link_mode_masks_nwords;
591 0 : memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords);
592 :
593 0 : offset += ecmd.req.link_mode_masks_nwords;
594 0 : memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords);
595 :
596 0 : ifr->ifr_data = (void *) &ecmd;
597 :
598 0 : r = ioctl(fd, SIOCETHTOOL, ifr);
599 0 : if (r < 0)
600 0 : return -errno;
601 :
602 0 : return 0;
603 : }
604 :
605 0 : static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
606 0 : struct ethtool_cmd ecmd = {
607 : .cmd = ETHTOOL_SSET,
608 : };
609 : int r;
610 :
611 0 : if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0)
612 0 : return -EINVAL;
613 :
614 0 : ecmd.supported = u->link_modes.supported[0];
615 0 : ecmd.advertising = u->link_modes.advertising[0];
616 0 : ecmd.lp_advertising = u->link_modes.lp_advertising[0];
617 :
618 0 : ethtool_cmd_speed_set(&ecmd, u->base.speed);
619 :
620 0 : ecmd.duplex = u->base.duplex;
621 0 : ecmd.port = u->base.port;
622 0 : ecmd.phy_address = u->base.phy_address;
623 0 : ecmd.autoneg = u->base.autoneg;
624 0 : ecmd.mdio_support = u->base.mdio_support;
625 0 : ecmd.eth_tp_mdix = u->base.eth_tp_mdix;
626 0 : ecmd.eth_tp_mdix_ctrl = u->base.eth_tp_mdix_ctrl;
627 :
628 0 : ifr->ifr_data = (void *) &ecmd;
629 :
630 0 : r = ioctl(fd, SIOCETHTOOL, ifr);
631 0 : if (r < 0)
632 0 : return -errno;
633 :
634 0 : return 0;
635 : }
636 :
637 : /* If autonegotiation is disabled, the speed and duplex represent the fixed link
638 : * mode and are writable if the driver supports multiple link modes. If it is
639 : * enabled then they are read-only. If the link is up they represent the negotiated
640 : * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
641 : * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
642 : */
643 0 : int ethtool_set_glinksettings(
644 : int *fd,
645 : const char *ifname,
646 : int autonegotiation,
647 : uint32_t advertise[static N_ADVERTISE],
648 : size_t speed,
649 : Duplex duplex,
650 : NetDevPort port) {
651 0 : _cleanup_free_ struct ethtool_link_usettings *u = NULL;
652 0 : struct ifreq ifr = {};
653 : int r;
654 :
655 0 : assert(advertise);
656 :
657 0 : if (autonegotiation != AUTONEG_DISABLE && memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
658 0 : log_info("ethtool: autonegotiation is unset or enabled, the speed and duplex are not writable.");
659 0 : return 0;
660 : }
661 :
662 0 : if (*fd < 0) {
663 0 : r = ethtool_connect_or_warn(fd, true);
664 0 : if (r < 0)
665 0 : return r;
666 : }
667 :
668 0 : strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
669 :
670 0 : r = get_glinksettings(*fd, &ifr, &u);
671 0 : if (r < 0) {
672 0 : r = get_gset(*fd, &ifr, &u);
673 0 : if (r < 0)
674 0 : return log_warning_errno(r, "ethtool: Cannot get device settings for %s : %m", ifname);
675 : }
676 :
677 0 : if (speed > 0)
678 0 : u->base.speed = DIV_ROUND_UP(speed, 1000000);
679 :
680 0 : if (duplex != _DUP_INVALID)
681 0 : u->base.duplex = duplex;
682 :
683 0 : if (port != _NET_DEV_PORT_INVALID)
684 0 : u->base.port = port;
685 :
686 0 : if (autonegotiation >= 0)
687 0 : u->base.autoneg = autonegotiation;
688 :
689 0 : if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
690 0 : u->base.autoneg = AUTONEG_ENABLE;
691 0 : memcpy(&u->link_modes.advertising, advertise, sizeof(uint32_t) * N_ADVERTISE);
692 0 : memzero((uint8_t*) &u->link_modes.advertising + sizeof(uint32_t) * N_ADVERTISE,
693 : ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(uint32_t) * N_ADVERTISE);
694 : }
695 :
696 0 : if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
697 0 : r = set_slinksettings(*fd, &ifr, u);
698 : else
699 0 : r = set_sset(*fd, &ifr, u);
700 0 : if (r < 0)
701 0 : return log_warning_errno(r, "ethtool: Cannot set device settings for %s : %m", ifname);
702 :
703 0 : return r;
704 : }
705 :
706 0 : int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) {
707 0 : struct ethtool_channels ecmd = {
708 : .cmd = ETHTOOL_GCHANNELS
709 : };
710 0 : struct ifreq ifr = {
711 : .ifr_data = (void*) &ecmd
712 : };
713 :
714 0 : bool need_update = false;
715 : int r;
716 :
717 0 : if (*fd < 0) {
718 0 : r = ethtool_connect_or_warn(fd, true);
719 0 : if (r < 0)
720 0 : return r;
721 : }
722 :
723 0 : strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
724 :
725 0 : r = ioctl(*fd, SIOCETHTOOL, &ifr);
726 0 : if (r < 0)
727 0 : return -errno;
728 :
729 0 : if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) {
730 0 : ecmd.rx_count = channels->rx_count;
731 0 : need_update = true;
732 : }
733 :
734 0 : if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) {
735 0 : ecmd.tx_count = channels->tx_count;
736 0 : need_update = true;
737 : }
738 :
739 0 : if (channels->other_count_set && ecmd.other_count != channels->other_count) {
740 0 : ecmd.other_count = channels->other_count;
741 0 : need_update = true;
742 : }
743 :
744 0 : if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) {
745 0 : ecmd.combined_count = channels->combined_count;
746 0 : need_update = true;
747 : }
748 :
749 0 : if (need_update) {
750 0 : ecmd.cmd = ETHTOOL_SCHANNELS;
751 :
752 0 : r = ioctl(*fd, SIOCETHTOOL, &ifr);
753 0 : if (r < 0)
754 0 : return -errno;
755 : }
756 :
757 0 : return 0;
758 : }
759 :
760 0 : int config_parse_channel(const char *unit,
761 : const char *filename,
762 : unsigned line,
763 : const char *section,
764 : unsigned section_line,
765 : const char *lvalue,
766 : int ltype,
767 : const char *rvalue,
768 : void *data,
769 : void *userdata) {
770 0 : netdev_channels *channels = data;
771 : uint32_t k;
772 : int r;
773 :
774 0 : assert(filename);
775 0 : assert(section);
776 0 : assert(lvalue);
777 0 : assert(rvalue);
778 0 : assert(data);
779 :
780 0 : r = safe_atou32(rvalue, &k);
781 0 : if (r < 0) {
782 0 : log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
783 0 : return 0;
784 : }
785 :
786 0 : if (k < 1) {
787 0 : log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
788 0 : return 0;
789 : }
790 :
791 0 : if (streq(lvalue, "RxChannels")) {
792 0 : channels->rx_count = k;
793 0 : channels->rx_count_set = true;
794 0 : } else if (streq(lvalue, "TxChannels")) {
795 0 : channels->tx_count = k;
796 0 : channels->tx_count_set = true;
797 0 : } else if (streq(lvalue, "OtherChannels")) {
798 0 : channels->other_count = k;
799 0 : channels->other_count_set = true;
800 0 : } else if (streq(lvalue, "CombinedChannels")) {
801 0 : channels->combined_count = k;
802 0 : channels->combined_count_set = true;
803 : }
804 :
805 0 : return 0;
806 : }
807 :
808 0 : int config_parse_advertise(const char *unit,
809 : const char *filename,
810 : unsigned line,
811 : const char *section,
812 : unsigned section_line,
813 : const char *lvalue,
814 : int ltype,
815 : const char *rvalue,
816 : void *data,
817 : void *userdata) {
818 0 : uint32_t *advertise = data;
819 : const char *p;
820 : int r;
821 :
822 0 : assert(filename);
823 0 : assert(section);
824 0 : assert(lvalue);
825 0 : assert(rvalue);
826 0 : assert(data);
827 :
828 0 : if (isempty(rvalue)) {
829 : /* Empty string resets the value. */
830 0 : memzero(advertise, sizeof(uint32_t) * N_ADVERTISE);
831 0 : return 0;
832 : }
833 :
834 0 : for (p = rvalue;;) {
835 0 : _cleanup_free_ char *w = NULL;
836 : enum ethtool_link_mode_bit_indices mode;
837 :
838 0 : r = extract_first_word(&p, &w, NULL, 0);
839 0 : if (r == -ENOMEM)
840 0 : return log_oom();
841 0 : if (r < 0) {
842 0 : log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split advertise modes '%s', ignoring: %m", rvalue);
843 0 : break;
844 : }
845 0 : if (r == 0)
846 0 : break;
847 :
848 0 : mode = ethtool_link_mode_bit_from_string(w);
849 : /* We reuse the kernel provided enum which does not contain negative value. So, the cast
850 : * below is mandatory. Otherwise, the check below always passes and access an invalid address. */
851 0 : if ((int) mode < 0) {
852 0 : log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w);
853 0 : continue;
854 : }
855 :
856 0 : advertise[mode / 32] |= 1UL << (mode % 32);
857 : }
858 :
859 0 : return 0;
860 : }
|