]>
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", | |
44909f1c | 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 | |
44909f1c | 40 | static 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 | ||
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 | ||
64d9f756 | 59 | static 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 */ |
114 | assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < ELEMENTSOF(((struct link_config){}).advertise)); | |
6cf0a204 | 115 | |
2d18ac44 | 116 | DEFINE_STRING_TABLE_LOOKUP(ethtool_link_mode_bit, enum ethtool_link_mode_bit_indices); |
6cf0a204 | 117 | |
a5010333 TG |
118 | int 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 | 132 | int 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 | 162 | int 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 | 220 | int 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 | 309 | static 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 | ||
353 | static 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 | 364 | int 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 | 417 | static 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 | 476 | static 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 | 512 | static 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 | 543 | static 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 | 581 | int 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 | |
635 | int 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 | ||
683 | int 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 | |
737 | int 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 | } |