]>
Commit | Line | Data |
---|---|---|
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 | 18 | static const char* const duplex_table[_DUP_MAX] = { |
5fde13d7 TG |
19 | [DUP_FULL] = "full", |
20 | [DUP_HALF] = "half" | |
21 | }; | |
22 | ||
23 | DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex); | |
24 | DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting"); | |
25 | ||
2c5859af | 26 | static 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", | |
34 | [WOL_OFF] = "off" | |
5fde13d7 TG |
35 | }; |
36 | ||
37 | DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan); | |
38 | DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting"); | |
a5010333 | 39 | |
593022fa SS |
40 | static const char* const port_table[_NET_DEV_PORT_MAX] = { |
41 | [NET_DEV_PORT_TP] = "tp", | |
42 | [NET_DEV_PORT_AUI] = "aui", | |
43 | [NET_DEV_PORT_MII] = "mii", | |
44 | [NET_DEV_PORT_FIBRE] = "fibre", | |
45 | [NET_DEV_PORT_BNC] = "bnc" | |
46 | }; | |
47 | ||
48 | DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort); | |
49 | DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting"); | |
50 | ||
50725d10 | 51 | static 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 | ||
a5010333 TG |
59 | int ethtool_connect(int *ret) { |
60 | int fd; | |
61 | ||
62 | assert_return(ret, -EINVAL); | |
63 | ||
429b4350 | 64 | fd = socket_ioctl_fd(); |
ece174c5 | 65 | if (fd < 0) |
429b4350 | 66 | return fd; |
2b44daaa | 67 | |
a5010333 TG |
68 | *ret = fd; |
69 | ||
70 | return 0; | |
71 | } | |
72 | ||
aedca892 | 73 | int ethtool_get_driver(int *fd, const char *ifname, char **ret) { |
61f3af4f LP |
74 | struct ethtool_drvinfo ecmd = { |
75 | .cmd = ETHTOOL_GDRVINFO | |
76 | }; | |
77 | struct ifreq ifr = { | |
78 | .ifr_data = (void*) &ecmd | |
79 | }; | |
80 | char *d; | |
847a8a5f TG |
81 | int r; |
82 | ||
aedca892 TG |
83 | if (*fd < 0) { |
84 | r = ethtool_connect(fd); | |
f647962d MS |
85 | if (r < 0) |
86 | return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); | |
aedca892 TG |
87 | } |
88 | ||
847a8a5f | 89 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); |
847a8a5f | 90 | |
aedca892 | 91 | r = ioctl(*fd, SIOCETHTOOL, &ifr); |
847a8a5f TG |
92 | if (r < 0) |
93 | return -errno; | |
94 | ||
61f3af4f LP |
95 | d = strdup(ecmd.driver); |
96 | if (!d) | |
847a8a5f TG |
97 | return -ENOMEM; |
98 | ||
61f3af4f | 99 | *ret = d; |
847a8a5f TG |
100 | return 0; |
101 | } | |
102 | ||
61087906 | 103 | int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex) { |
6c0519c0 TG |
104 | struct ethtool_cmd ecmd = { |
105 | .cmd = ETHTOOL_GSET | |
106 | }; | |
107 | struct ifreq ifr = { | |
108 | .ifr_data = (void*) &ecmd | |
109 | }; | |
0a2c2294 | 110 | bool need_update = false; |
a5010333 TG |
111 | int r; |
112 | ||
5fde13d7 | 113 | if (speed == 0 && duplex == _DUP_INVALID) |
a5010333 TG |
114 | return 0; |
115 | ||
aedca892 TG |
116 | if (*fd < 0) { |
117 | r = ethtool_connect(fd); | |
f647962d MS |
118 | if (r < 0) |
119 | return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); | |
aedca892 TG |
120 | } |
121 | ||
a5010333 | 122 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); |
a5010333 | 123 | |
aedca892 | 124 | r = ioctl(*fd, SIOCETHTOOL, &ifr); |
a5010333 TG |
125 | if (r < 0) |
126 | return -errno; | |
127 | ||
128 | if (ethtool_cmd_speed(&ecmd) != speed) { | |
129 | ethtool_cmd_speed_set(&ecmd, speed); | |
130 | need_update = true; | |
131 | } | |
132 | ||
5fde13d7 TG |
133 | switch (duplex) { |
134 | case DUP_HALF: | |
a5010333 TG |
135 | if (ecmd.duplex != DUPLEX_HALF) { |
136 | ecmd.duplex = DUPLEX_HALF; | |
137 | need_update = true; | |
138 | } | |
5fde13d7 TG |
139 | break; |
140 | case DUP_FULL: | |
a5010333 TG |
141 | if (ecmd.duplex != DUPLEX_FULL) { |
142 | ecmd.duplex = DUPLEX_FULL; | |
143 | need_update = true; | |
144 | } | |
5fde13d7 TG |
145 | break; |
146 | default: | |
147 | break; | |
a5010333 TG |
148 | } |
149 | ||
150 | if (need_update) { | |
151 | ecmd.cmd = ETHTOOL_SSET; | |
152 | ||
aedca892 | 153 | r = ioctl(*fd, SIOCETHTOOL, &ifr); |
a5010333 TG |
154 | if (r < 0) |
155 | return -errno; | |
156 | } | |
157 | ||
158 | return 0; | |
159 | } | |
160 | ||
aedca892 | 161 | int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) { |
6c0519c0 TG |
162 | struct ethtool_wolinfo ecmd = { |
163 | .cmd = ETHTOOL_GWOL | |
164 | }; | |
165 | struct ifreq ifr = { | |
166 | .ifr_data = (void*) &ecmd | |
167 | }; | |
0a2c2294 | 168 | bool need_update = false; |
a5010333 TG |
169 | int r; |
170 | ||
5fde13d7 | 171 | if (wol == _WOL_INVALID) |
a5010333 TG |
172 | return 0; |
173 | ||
aedca892 TG |
174 | if (*fd < 0) { |
175 | r = ethtool_connect(fd); | |
f647962d MS |
176 | if (r < 0) |
177 | return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); | |
aedca892 TG |
178 | } |
179 | ||
a5010333 | 180 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); |
a5010333 | 181 | |
aedca892 | 182 | r = ioctl(*fd, SIOCETHTOOL, &ifr); |
a5010333 TG |
183 | if (r < 0) |
184 | return -errno; | |
185 | ||
5fde13d7 | 186 | switch (wol) { |
617da14c SS |
187 | case WOL_PHY: |
188 | if (ecmd.wolopts != WAKE_PHY) { | |
189 | ecmd.wolopts = WAKE_PHY; | |
190 | need_update = true; | |
191 | } | |
192 | break; | |
193 | case WOL_UCAST: | |
194 | if (ecmd.wolopts != WAKE_UCAST) { | |
195 | ecmd.wolopts = WAKE_UCAST; | |
196 | need_update = true; | |
197 | } | |
198 | break; | |
199 | case WOL_MCAST: | |
200 | if (ecmd.wolopts != WAKE_MCAST) { | |
201 | ecmd.wolopts = WAKE_MCAST; | |
202 | need_update = true; | |
203 | } | |
204 | break; | |
205 | case WOL_BCAST: | |
206 | if (ecmd.wolopts != WAKE_BCAST) { | |
207 | ecmd.wolopts = WAKE_BCAST; | |
208 | need_update = true; | |
209 | } | |
210 | break; | |
211 | case WOL_ARP: | |
212 | if (ecmd.wolopts != WAKE_ARP) { | |
213 | ecmd.wolopts = WAKE_ARP; | |
214 | need_update = true; | |
215 | } | |
216 | break; | |
217 | case WOL_MAGIC: | |
218 | if (ecmd.wolopts != WAKE_MAGIC) { | |
219 | ecmd.wolopts = WAKE_MAGIC; | |
220 | need_update = true; | |
221 | } | |
222 | break; | |
223 | case WOL_MAGICSECURE: | |
224 | if (ecmd.wolopts != WAKE_MAGICSECURE) { | |
225 | ecmd.wolopts = WAKE_MAGICSECURE; | |
226 | need_update = true; | |
227 | } | |
228 | break; | |
229 | case WOL_OFF: | |
230 | if (ecmd.wolopts != 0) { | |
231 | ecmd.wolopts = 0; | |
232 | need_update = true; | |
233 | } | |
234 | break; | |
235 | default: | |
236 | break; | |
5fde13d7 | 237 | } |
a5010333 TG |
238 | |
239 | if (need_update) { | |
240 | ecmd.cmd = ETHTOOL_SWOL; | |
241 | ||
aedca892 | 242 | r = ioctl(*fd, SIOCETHTOOL, &ifr); |
a5010333 TG |
243 | if (r < 0) |
244 | return -errno; | |
245 | } | |
246 | ||
247 | return 0; | |
248 | } | |
50725d10 | 249 | |
2b44daaa | 250 | static int get_stringset(int fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) { |
50725d10 | 251 | _cleanup_free_ struct ethtool_gstrings *strings = NULL; |
a9dee27f SS |
252 | struct { |
253 | struct ethtool_sset_info info; | |
254 | uint32_t space; | |
255 | } buffer = { | |
256 | .info = { | |
257 | .cmd = ETHTOOL_GSSET_INFO, | |
258 | .sset_mask = UINT64_C(1) << stringset_id, | |
259 | }, | |
50725d10 SS |
260 | }; |
261 | unsigned len; | |
262 | int r; | |
263 | ||
a9dee27f | 264 | ifr->ifr_data = (void *) &buffer.info; |
50725d10 | 265 | |
2b44daaa | 266 | r = ioctl(fd, SIOCETHTOOL, ifr); |
50725d10 SS |
267 | if (r < 0) |
268 | return -errno; | |
269 | ||
a9dee27f | 270 | if (!buffer.info.sset_mask) |
50725d10 SS |
271 | return -EINVAL; |
272 | ||
a9dee27f | 273 | len = buffer.info.data[0]; |
50725d10 SS |
274 | |
275 | strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN); | |
276 | if (!strings) | |
277 | return -ENOMEM; | |
278 | ||
279 | strings->cmd = ETHTOOL_GSTRINGS; | |
280 | strings->string_set = stringset_id; | |
281 | strings->len = len; | |
282 | ||
283 | ifr->ifr_data = (void *) strings; | |
284 | ||
2b44daaa | 285 | r = ioctl(fd, SIOCETHTOOL, ifr); |
50725d10 SS |
286 | if (r < 0) |
287 | return -errno; | |
288 | ||
ae2a15bc | 289 | *gstrings = TAKE_PTR(strings); |
50725d10 SS |
290 | |
291 | return 0; | |
292 | } | |
293 | ||
294 | static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) { | |
295 | unsigned i; | |
296 | ||
297 | for (i = 0; i < strings->len; i++) { | |
298 | if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature)) | |
299 | return i; | |
300 | } | |
301 | ||
302 | return -1; | |
303 | } | |
304 | ||
305 | int ethtool_set_features(int *fd, const char *ifname, NetDevFeature *features) { | |
306 | _cleanup_free_ struct ethtool_gstrings *strings = NULL; | |
307 | struct ethtool_sfeatures *sfeatures; | |
308 | int block, bit, i, r; | |
a9dee27f | 309 | struct ifreq ifr = {}; |
50725d10 SS |
310 | |
311 | if (*fd < 0) { | |
312 | r = ethtool_connect(fd); | |
313 | if (r < 0) | |
314 | return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); | |
315 | } | |
316 | ||
317 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); | |
318 | ||
2b44daaa | 319 | r = get_stringset(*fd, &ifr, ETH_SS_FEATURES, &strings); |
50725d10 SS |
320 | if (r < 0) |
321 | return log_warning_errno(r, "link_config: could not get ethtool features for %s", ifname); | |
322 | ||
323 | sfeatures = alloca0(sizeof(struct ethtool_gstrings) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0])); | |
324 | sfeatures->cmd = ETHTOOL_SFEATURES; | |
325 | sfeatures->size = DIV_ROUND_UP(strings->len, 32U); | |
326 | ||
327 | for (i = 0; i < _NET_DEV_FEAT_MAX; i++) { | |
328 | ||
329 | if (features[i] != -1) { | |
330 | ||
331 | r = find_feature_index(strings, netdev_feature_table[i]); | |
332 | if (r < 0) { | |
333 | log_warning_errno(r, "link_config: could not find feature: %s", netdev_feature_table[i]); | |
334 | continue; | |
335 | } | |
336 | ||
337 | block = r / 32; | |
338 | bit = r % 32; | |
339 | ||
340 | sfeatures->features[block].valid |= 1 << bit; | |
341 | ||
342 | if (features[i]) | |
343 | sfeatures->features[block].requested |= 1 << bit; | |
344 | else | |
345 | sfeatures->features[block].requested &= ~(1 << bit); | |
346 | } | |
347 | } | |
348 | ||
349 | ifr.ifr_data = (void *) sfeatures; | |
350 | ||
351 | r = ioctl(*fd, SIOCETHTOOL, &ifr); | |
352 | if (r < 0) | |
353 | return log_warning_errno(r, "link_config: could not set ethtool features for %s", ifname); | |
354 | ||
355 | return 0; | |
356 | } | |
a39f92d3 | 357 | |
2b44daaa | 358 | static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **g) { |
a39f92d3 SS |
359 | struct ecmd { |
360 | struct ethtool_link_settings req; | |
361 | __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]; | |
362 | } ecmd = { | |
363 | .req.cmd = ETHTOOL_GLINKSETTINGS, | |
364 | }; | |
365 | struct ethtool_link_usettings *u; | |
366 | unsigned offset; | |
367 | int r; | |
368 | ||
369 | /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS | |
370 | handshake first to agree on the length of the link mode bitmaps. If kernel doesn't | |
371 | agree with user, it returns the bitmap length it is expecting from user as a negative | |
372 | length (and cmd field is 0). When kernel and user agree, kernel returns valid info in | |
373 | all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on | |
374 | https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf | |
375 | */ | |
376 | ||
377 | ifr->ifr_data = (void *) &ecmd; | |
378 | ||
2b44daaa | 379 | r = ioctl(fd, SIOCETHTOOL, ifr); |
a39f92d3 SS |
380 | if (r < 0) |
381 | return -errno; | |
382 | ||
383 | if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) | |
6b44a121 | 384 | return -EOPNOTSUPP; |
a39f92d3 SS |
385 | |
386 | ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords; | |
387 | ||
388 | ifr->ifr_data = (void *) &ecmd; | |
389 | ||
2b44daaa | 390 | r = ioctl(fd, SIOCETHTOOL, ifr); |
a39f92d3 SS |
391 | if (r < 0) |
392 | return -errno; | |
393 | ||
394 | if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) | |
6b44a121 | 395 | return -EOPNOTSUPP; |
a39f92d3 SS |
396 | |
397 | u = new0(struct ethtool_link_usettings , 1); | |
398 | if (!u) | |
399 | return -ENOMEM; | |
400 | ||
801d2c9f | 401 | u->base = ecmd.req; |
a39f92d3 SS |
402 | |
403 | offset = 0; | |
404 | memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords); | |
405 | ||
406 | offset += ecmd.req.link_mode_masks_nwords; | |
407 | memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords); | |
408 | ||
409 | offset += ecmd.req.link_mode_masks_nwords; | |
410 | memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords); | |
411 | ||
412 | *g = u; | |
413 | ||
414 | return 0; | |
415 | } | |
416 | ||
2b44daaa | 417 | static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) { |
a39f92d3 SS |
418 | struct ethtool_link_usettings *e; |
419 | struct ethtool_cmd ecmd = { | |
420 | .cmd = ETHTOOL_GSET, | |
421 | }; | |
422 | int r; | |
423 | ||
424 | ifr->ifr_data = (void *) &ecmd; | |
425 | ||
2b44daaa | 426 | r = ioctl(fd, SIOCETHTOOL, ifr); |
a39f92d3 SS |
427 | if (r < 0) |
428 | return -errno; | |
429 | ||
430 | e = new0(struct ethtool_link_usettings, 1); | |
431 | if (!e) | |
432 | return -ENOMEM; | |
433 | ||
434 | e->base.cmd = ETHTOOL_GSET; | |
435 | ||
436 | e->base.link_mode_masks_nwords = 1; | |
437 | e->base.speed = ethtool_cmd_speed(&ecmd); | |
438 | e->base.duplex = ecmd.duplex; | |
439 | e->base.port = ecmd.port; | |
440 | e->base.phy_address = ecmd.phy_address; | |
441 | e->base.autoneg = ecmd.autoneg; | |
442 | e->base.mdio_support = ecmd.mdio_support; | |
443 | ||
444 | e->link_modes.supported[0] = ecmd.supported; | |
445 | e->link_modes.advertising[0] = ecmd.advertising; | |
446 | e->link_modes.lp_advertising[0] = ecmd.lp_advertising; | |
447 | ||
448 | *u = e; | |
449 | ||
450 | return 0; | |
451 | } | |
452 | ||
2b44daaa | 453 | static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) { |
a39f92d3 SS |
454 | struct { |
455 | struct ethtool_link_settings req; | |
456 | __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]; | |
94d4acbe | 457 | } ecmd = {}; |
a39f92d3 SS |
458 | unsigned int offset; |
459 | int r; | |
460 | ||
461 | if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0) | |
462 | return -EINVAL; | |
463 | ||
89e1ba0a | 464 | ecmd.req = u->base; |
94d4acbe | 465 | ecmd.req.cmd = ETHTOOL_SLINKSETTINGS; |
a39f92d3 SS |
466 | offset = 0; |
467 | memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords); | |
468 | ||
469 | offset += ecmd.req.link_mode_masks_nwords; | |
470 | memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords); | |
471 | ||
472 | offset += ecmd.req.link_mode_masks_nwords; | |
473 | memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords); | |
474 | ||
475 | ifr->ifr_data = (void *) &ecmd; | |
476 | ||
2b44daaa | 477 | r = ioctl(fd, SIOCETHTOOL, ifr); |
a39f92d3 SS |
478 | if (r < 0) |
479 | return -errno; | |
480 | ||
481 | return 0; | |
482 | } | |
483 | ||
2b44daaa | 484 | static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) { |
a39f92d3 SS |
485 | struct ethtool_cmd ecmd = { |
486 | .cmd = ETHTOOL_SSET, | |
487 | }; | |
488 | int r; | |
489 | ||
490 | if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0) | |
491 | return -EINVAL; | |
492 | ||
493 | ecmd.supported = u->link_modes.supported[0]; | |
494 | ecmd.advertising = u->link_modes.advertising[0]; | |
495 | ecmd.lp_advertising = u->link_modes.lp_advertising[0]; | |
496 | ||
497 | ethtool_cmd_speed_set(&ecmd, u->base.speed); | |
498 | ||
499 | ecmd.duplex = u->base.duplex; | |
500 | ecmd.port = u->base.port; | |
501 | ecmd.phy_address = u->base.phy_address; | |
502 | ecmd.autoneg = u->base.autoneg; | |
503 | ecmd.mdio_support = u->base.mdio_support; | |
504 | ||
505 | ifr->ifr_data = (void *) &ecmd; | |
506 | ||
2b44daaa | 507 | r = ioctl(fd, SIOCETHTOOL, ifr); |
a39f92d3 SS |
508 | if (r < 0) |
509 | return -errno; | |
510 | ||
511 | return 0; | |
512 | } | |
513 | ||
514 | /* If autonegotiation is disabled, the speed and duplex represent the fixed link | |
515 | * mode and are writable if the driver supports multiple link modes. If it is | |
516 | * enabled then they are read-only. If the link is up they represent the negotiated | |
517 | * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest | |
518 | * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode. | |
519 | */ | |
520 | ||
593022fa | 521 | int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *link) { |
a39f92d3 SS |
522 | _cleanup_free_ struct ethtool_link_usettings *u = NULL; |
523 | struct ifreq ifr = {}; | |
524 | int r; | |
525 | ||
593022fa | 526 | if (link->autonegotiation != 0) { |
a39f92d3 SS |
527 | log_info("link_config: autonegotiation is unset or enabled, the speed and duplex are not writable."); |
528 | return 0; | |
529 | } | |
530 | ||
531 | if (*fd < 0) { | |
532 | r = ethtool_connect(fd); | |
533 | if (r < 0) | |
534 | return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); | |
535 | } | |
536 | ||
537 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); | |
538 | ||
2b44daaa | 539 | r = get_glinksettings(*fd, &ifr, &u); |
a39f92d3 | 540 | if (r < 0) { |
2b44daaa | 541 | r = get_gset(*fd, &ifr, &u); |
a39f92d3 SS |
542 | if (r < 0) |
543 | return log_warning_errno(r, "link_config: Cannot get device settings for %s : %m", ifname); | |
544 | } | |
545 | ||
593022fa | 546 | if (link->speed) |
9c5e1172 | 547 | u->base.speed = DIV_ROUND_UP(link->speed, 1000000); |
593022fa SS |
548 | |
549 | if (link->duplex != _DUP_INVALID) | |
550 | u->base.duplex = link->duplex; | |
a39f92d3 | 551 | |
593022fa | 552 | if (link->port != _NET_DEV_PORT_INVALID) |
20d4e995 | 553 | u->base.port = link->port; |
a39f92d3 | 554 | |
593022fa | 555 | u->base.autoneg = link->autonegotiation; |
a39f92d3 SS |
556 | |
557 | if (u->base.cmd == ETHTOOL_GLINKSETTINGS) | |
2b44daaa | 558 | r = set_slinksettings(*fd, &ifr, u); |
a39f92d3 | 559 | else |
2b44daaa | 560 | r = set_sset(*fd, &ifr, u); |
a39f92d3 SS |
561 | if (r < 0) |
562 | return log_warning_errno(r, "link_config: Cannot set device settings for %s : %m", ifname); | |
563 | ||
564 | return r; | |
565 | } | |
5f945202 SS |
566 | |
567 | int config_parse_channel(const char *unit, | |
568 | const char *filename, | |
569 | unsigned line, | |
570 | const char *section, | |
571 | unsigned section_line, | |
572 | const char *lvalue, | |
573 | int ltype, | |
574 | const char *rvalue, | |
575 | void *data, | |
576 | void *userdata) { | |
577 | link_config *config = data; | |
578 | uint32_t k; | |
579 | int r; | |
580 | ||
581 | assert(filename); | |
582 | assert(section); | |
583 | assert(lvalue); | |
584 | assert(rvalue); | |
585 | assert(data); | |
586 | ||
587 | r = safe_atou32(rvalue, &k); | |
588 | if (r < 0) { | |
589 | log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue); | |
590 | return 0; | |
591 | } | |
592 | ||
593 | if (k < 1) { | |
594 | log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue); | |
595 | return 0; | |
596 | } | |
597 | ||
598 | if (streq(lvalue, "RxChannels")) { | |
599 | config->channels.rx_count = k; | |
600 | config->channels.rx_count_set = true; | |
601 | } else if (streq(lvalue, "TxChannels")) { | |
602 | config->channels.tx_count = k; | |
603 | config->channels.tx_count_set = true; | |
604 | } else if (streq(lvalue, "OtherChannels")) { | |
605 | config->channels.other_count = k; | |
606 | config->channels.other_count_set = true; | |
607 | } else if (streq(lvalue, "CombinedChannels")) { | |
608 | config->channels.combined_count = k; | |
609 | config->channels.combined_count_set = true; | |
610 | } | |
611 | ||
612 | return 0; | |
613 | } | |
614 | ||
615 | int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) { | |
616 | struct ethtool_channels ecmd = { | |
617 | .cmd = ETHTOOL_GCHANNELS | |
618 | }; | |
619 | struct ifreq ifr = { | |
620 | .ifr_data = (void*) &ecmd | |
621 | }; | |
622 | ||
623 | bool need_update = false; | |
624 | int r; | |
625 | ||
626 | if (*fd < 0) { | |
627 | r = ethtool_connect(fd); | |
628 | if (r < 0) | |
629 | return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); | |
630 | } | |
631 | ||
632 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); | |
633 | ||
634 | r = ioctl(*fd, SIOCETHTOOL, &ifr); | |
635 | if (r < 0) | |
636 | return -errno; | |
637 | ||
638 | if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) { | |
639 | ecmd.rx_count = channels->rx_count; | |
640 | need_update = true; | |
641 | } | |
642 | ||
643 | if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) { | |
644 | ecmd.tx_count = channels->tx_count; | |
645 | need_update = true; | |
646 | } | |
647 | ||
648 | if (channels->other_count_set && ecmd.other_count != channels->other_count) { | |
649 | ecmd.other_count = channels->other_count; | |
650 | need_update = true; | |
651 | } | |
652 | ||
653 | if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) { | |
654 | ecmd.combined_count = channels->combined_count; | |
655 | need_update = true; | |
656 | } | |
657 | ||
658 | if (need_update) { | |
659 | ecmd.cmd = ETHTOOL_SCHANNELS; | |
660 | ||
661 | r = ioctl(*fd, SIOCETHTOOL, &ifr); | |
662 | if (r < 0) | |
663 | return -errno; | |
664 | } | |
665 | ||
666 | return 0; | |
667 | } |