]>
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" |
5c2316c6 | 10 | #include "extract-word.h" |
ab1263d7 | 11 | #include "log.h" |
0a970718 | 12 | #include "memory-util.h" |
ab1263d7 | 13 | #include "missing.h" |
429b4350 | 14 | #include "socket-util.h" |
8b43440b | 15 | #include "string-table.h" |
a5010333 | 16 | #include "strxcpyx.h" |
5fde13d7 | 17 | |
2c5859af | 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 | 113 | /* Make sure the array is large enough to fit all bits */ |
5c2316c6 | 114 | assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < N_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 | 144 | if (r < 0) |
5c2316c6 | 145 | return log_warning_errno(r, "ethtool: could not connect to ethtool: %m"); |
aedca892 TG |
146 | } |
147 | ||
847a8a5f | 148 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); |
847a8a5f | 149 | |
aedca892 | 150 | r = ioctl(*fd, SIOCETHTOOL, &ifr); |
847a8a5f TG |
151 | if (r < 0) |
152 | return -errno; | |
153 | ||
61f3af4f LP |
154 | d = strdup(ecmd.driver); |
155 | if (!d) | |
847a8a5f TG |
156 | return -ENOMEM; |
157 | ||
61f3af4f | 158 | *ret = d; |
847a8a5f TG |
159 | return 0; |
160 | } | |
161 | ||
14cb109d | 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 | 177 | if (r < 0) |
5c2316c6 | 178 | return log_warning_errno(r, "ethtool: could not connect to ethtool: %m"); |
aedca892 TG |
179 | } |
180 | ||
a5010333 | 181 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); |
a5010333 | 182 | |
aedca892 | 183 | r = ioctl(*fd, SIOCETHTOOL, &ifr); |
a5010333 TG |
184 | if (r < 0) |
185 | return -errno; | |
186 | ||
187 | if (ethtool_cmd_speed(&ecmd) != speed) { | |
188 | ethtool_cmd_speed_set(&ecmd, speed); | |
189 | need_update = true; | |
190 | } | |
191 | ||
5fde13d7 TG |
192 | switch (duplex) { |
193 | case DUP_HALF: | |
a5010333 TG |
194 | if (ecmd.duplex != DUPLEX_HALF) { |
195 | ecmd.duplex = DUPLEX_HALF; | |
196 | need_update = true; | |
197 | } | |
5fde13d7 TG |
198 | break; |
199 | case DUP_FULL: | |
a5010333 TG |
200 | if (ecmd.duplex != DUPLEX_FULL) { |
201 | ecmd.duplex = DUPLEX_FULL; | |
202 | need_update = true; | |
203 | } | |
5fde13d7 TG |
204 | break; |
205 | default: | |
206 | break; | |
a5010333 TG |
207 | } |
208 | ||
209 | if (need_update) { | |
210 | ecmd.cmd = ETHTOOL_SSET; | |
211 | ||
aedca892 | 212 | r = ioctl(*fd, SIOCETHTOOL, &ifr); |
a5010333 TG |
213 | if (r < 0) |
214 | return -errno; | |
215 | } | |
216 | ||
217 | return 0; | |
218 | } | |
219 | ||
aedca892 | 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 | 235 | if (r < 0) |
5c2316c6 | 236 | return log_warning_errno(r, "ethtool: could not connect to ethtool: %m"); |
aedca892 TG |
237 | } |
238 | ||
a5010333 | 239 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); |
a5010333 | 240 | |
aedca892 | 241 | r = ioctl(*fd, SIOCETHTOOL, &ifr); |
a5010333 TG |
242 | if (r < 0) |
243 | return -errno; | |
244 | ||
5fde13d7 | 245 | switch (wol) { |
617da14c SS |
246 | case WOL_PHY: |
247 | if (ecmd.wolopts != WAKE_PHY) { | |
248 | ecmd.wolopts = WAKE_PHY; | |
249 | need_update = true; | |
250 | } | |
251 | break; | |
252 | case WOL_UCAST: | |
253 | if (ecmd.wolopts != WAKE_UCAST) { | |
254 | ecmd.wolopts = WAKE_UCAST; | |
255 | need_update = true; | |
256 | } | |
257 | break; | |
258 | case WOL_MCAST: | |
259 | if (ecmd.wolopts != WAKE_MCAST) { | |
260 | ecmd.wolopts = WAKE_MCAST; | |
261 | need_update = true; | |
262 | } | |
263 | break; | |
264 | case WOL_BCAST: | |
265 | if (ecmd.wolopts != WAKE_BCAST) { | |
266 | ecmd.wolopts = WAKE_BCAST; | |
267 | need_update = true; | |
268 | } | |
269 | break; | |
270 | case WOL_ARP: | |
271 | if (ecmd.wolopts != WAKE_ARP) { | |
272 | ecmd.wolopts = WAKE_ARP; | |
273 | need_update = true; | |
274 | } | |
275 | break; | |
276 | case WOL_MAGIC: | |
277 | if (ecmd.wolopts != WAKE_MAGIC) { | |
278 | ecmd.wolopts = WAKE_MAGIC; | |
279 | need_update = true; | |
280 | } | |
281 | break; | |
282 | case WOL_MAGICSECURE: | |
283 | if (ecmd.wolopts != WAKE_MAGICSECURE) { | |
284 | ecmd.wolopts = WAKE_MAGICSECURE; | |
285 | need_update = true; | |
286 | } | |
287 | break; | |
288 | case WOL_OFF: | |
289 | if (ecmd.wolopts != 0) { | |
290 | ecmd.wolopts = 0; | |
291 | need_update = true; | |
292 | } | |
293 | break; | |
294 | default: | |
295 | break; | |
5fde13d7 | 296 | } |
a5010333 TG |
297 | |
298 | if (need_update) { | |
299 | ecmd.cmd = ETHTOOL_SWOL; | |
300 | ||
aedca892 | 301 | r = ioctl(*fd, SIOCETHTOOL, &ifr); |
a5010333 TG |
302 | if (r < 0) |
303 | return -errno; | |
304 | } | |
305 | ||
306 | return 0; | |
307 | } | |
50725d10 | 308 | |
2b44daaa | 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) | |
5c2316c6 | 373 | return log_warning_errno(r, "ethtool: could not connect to ethtool: %m"); |
50725d10 SS |
374 | } |
375 | ||
376 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); | |
377 | ||
2b44daaa | 378 | r = get_stringset(*fd, &ifr, ETH_SS_FEATURES, &strings); |
50725d10 | 379 | if (r < 0) |
5c2316c6 | 380 | return log_warning_errno(r, "ethtool: could not get ethtool features for %s", ifname); |
50725d10 | 381 | |
3301f9eb | 382 | sfeatures = alloca0(sizeof(struct ethtool_sfeatures) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0])); |
50725d10 SS |
383 | sfeatures->cmd = ETHTOOL_SFEATURES; |
384 | sfeatures->size = DIV_ROUND_UP(strings->len, 32U); | |
385 | ||
386 | for (i = 0; i < _NET_DEV_FEAT_MAX; i++) { | |
387 | ||
388 | if (features[i] != -1) { | |
389 | ||
390 | r = find_feature_index(strings, netdev_feature_table[i]); | |
391 | if (r < 0) { | |
5c2316c6 | 392 | log_warning_errno(r, "ethtool: could not find feature: %s", netdev_feature_table[i]); |
50725d10 SS |
393 | continue; |
394 | } | |
395 | ||
396 | block = r / 32; | |
397 | bit = r % 32; | |
398 | ||
399 | sfeatures->features[block].valid |= 1 << bit; | |
400 | ||
401 | if (features[i]) | |
402 | sfeatures->features[block].requested |= 1 << bit; | |
403 | else | |
404 | sfeatures->features[block].requested &= ~(1 << bit); | |
405 | } | |
406 | } | |
407 | ||
408 | ifr.ifr_data = (void *) sfeatures; | |
409 | ||
410 | r = ioctl(*fd, SIOCETHTOOL, &ifr); | |
411 | if (r < 0) | |
5c2316c6 | 412 | return log_warning_errno(r, "ethtool: could not set ethtool features for %s", ifname); |
50725d10 SS |
413 | |
414 | return 0; | |
415 | } | |
a39f92d3 | 416 | |
2b44daaa | 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 | 455 | |
b9bc7d42 | 456 | u = new(struct ethtool_link_usettings, 1); |
a39f92d3 SS |
457 | if (!u) |
458 | return -ENOMEM; | |
459 | ||
b9bc7d42 YW |
460 | *u = (struct ethtool_link_usettings) { |
461 | .base = ecmd.req, | |
462 | }; | |
a39f92d3 SS |
463 | |
464 | offset = 0; | |
465 | memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords); | |
466 | ||
467 | offset += ecmd.req.link_mode_masks_nwords; | |
468 | memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords); | |
469 | ||
470 | offset += ecmd.req.link_mode_masks_nwords; | |
471 | memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords); | |
472 | ||
473 | *g = u; | |
474 | ||
475 | return 0; | |
476 | } | |
477 | ||
2b44daaa | 478 | static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) { |
a39f92d3 SS |
479 | struct ethtool_link_usettings *e; |
480 | struct ethtool_cmd ecmd = { | |
481 | .cmd = ETHTOOL_GSET, | |
482 | }; | |
483 | int r; | |
484 | ||
485 | ifr->ifr_data = (void *) &ecmd; | |
486 | ||
2b44daaa | 487 | r = ioctl(fd, SIOCETHTOOL, ifr); |
a39f92d3 SS |
488 | if (r < 0) |
489 | return -errno; | |
490 | ||
b9bc7d42 | 491 | e = new(struct ethtool_link_usettings, 1); |
a39f92d3 SS |
492 | if (!e) |
493 | return -ENOMEM; | |
494 | ||
b9bc7d42 YW |
495 | *e = (struct ethtool_link_usettings) { |
496 | .base.cmd = ETHTOOL_GSET, | |
497 | .base.link_mode_masks_nwords = 1, | |
498 | .base.speed = ethtool_cmd_speed(&ecmd), | |
499 | .base.duplex = ecmd.duplex, | |
500 | .base.port = ecmd.port, | |
501 | .base.phy_address = ecmd.phy_address, | |
502 | .base.autoneg = ecmd.autoneg, | |
503 | .base.mdio_support = ecmd.mdio_support, | |
504 | ||
505 | .link_modes.supported[0] = ecmd.supported, | |
506 | .link_modes.advertising[0] = ecmd.advertising, | |
507 | .link_modes.lp_advertising[0] = ecmd.lp_advertising, | |
508 | }; | |
a39f92d3 SS |
509 | |
510 | *u = e; | |
511 | ||
512 | return 0; | |
513 | } | |
514 | ||
2b44daaa | 515 | static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) { |
a39f92d3 SS |
516 | struct { |
517 | struct ethtool_link_settings req; | |
518 | __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]; | |
94d4acbe | 519 | } ecmd = {}; |
14cb109d | 520 | unsigned offset; |
a39f92d3 SS |
521 | int r; |
522 | ||
523 | if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0) | |
524 | return -EINVAL; | |
525 | ||
89e1ba0a | 526 | ecmd.req = u->base; |
94d4acbe | 527 | ecmd.req.cmd = ETHTOOL_SLINKSETTINGS; |
a39f92d3 SS |
528 | offset = 0; |
529 | memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords); | |
530 | ||
531 | offset += ecmd.req.link_mode_masks_nwords; | |
532 | memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords); | |
533 | ||
534 | offset += ecmd.req.link_mode_masks_nwords; | |
535 | memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords); | |
536 | ||
537 | ifr->ifr_data = (void *) &ecmd; | |
538 | ||
2b44daaa | 539 | r = ioctl(fd, SIOCETHTOOL, ifr); |
a39f92d3 SS |
540 | if (r < 0) |
541 | return -errno; | |
542 | ||
543 | return 0; | |
544 | } | |
545 | ||
2b44daaa | 546 | static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) { |
a39f92d3 SS |
547 | struct ethtool_cmd ecmd = { |
548 | .cmd = ETHTOOL_SSET, | |
549 | }; | |
550 | int r; | |
551 | ||
552 | if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0) | |
553 | return -EINVAL; | |
554 | ||
555 | ecmd.supported = u->link_modes.supported[0]; | |
556 | ecmd.advertising = u->link_modes.advertising[0]; | |
557 | ecmd.lp_advertising = u->link_modes.lp_advertising[0]; | |
558 | ||
559 | ethtool_cmd_speed_set(&ecmd, u->base.speed); | |
560 | ||
561 | ecmd.duplex = u->base.duplex; | |
562 | ecmd.port = u->base.port; | |
563 | ecmd.phy_address = u->base.phy_address; | |
564 | ecmd.autoneg = u->base.autoneg; | |
565 | ecmd.mdio_support = u->base.mdio_support; | |
6cf0a204 SS |
566 | ecmd.eth_tp_mdix = u->base.eth_tp_mdix; |
567 | ecmd.eth_tp_mdix_ctrl = u->base.eth_tp_mdix_ctrl; | |
a39f92d3 SS |
568 | |
569 | ifr->ifr_data = (void *) &ecmd; | |
570 | ||
2b44daaa | 571 | r = ioctl(fd, SIOCETHTOOL, ifr); |
a39f92d3 SS |
572 | if (r < 0) |
573 | return -errno; | |
574 | ||
575 | return 0; | |
576 | } | |
577 | ||
578 | /* If autonegotiation is disabled, the speed and duplex represent the fixed link | |
579 | * mode and are writable if the driver supports multiple link modes. If it is | |
49c603bd | 580 | * enabled then they are read-only. If the link is up they represent the negotiated |
a39f92d3 SS |
581 | * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest |
582 | * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode. | |
583 | */ | |
5c2316c6 YW |
584 | int ethtool_set_glinksettings( |
585 | int *fd, | |
586 | const char *ifname, | |
587 | int autonegotiation, | |
588 | uint32_t advertise[static N_ADVERTISE], | |
589 | size_t speed, | |
590 | Duplex duplex, | |
591 | NetDevPort port) { | |
a39f92d3 SS |
592 | _cleanup_free_ struct ethtool_link_usettings *u = NULL; |
593 | struct ifreq ifr = {}; | |
594 | int r; | |
595 | ||
5c2316c6 YW |
596 | if (autonegotiation != AUTONEG_DISABLE && memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) { |
597 | log_info("ethtool: autonegotiation is unset or enabled, the speed and duplex are not writable."); | |
a39f92d3 SS |
598 | return 0; |
599 | } | |
600 | ||
601 | if (*fd < 0) { | |
602 | r = ethtool_connect(fd); | |
603 | if (r < 0) | |
5c2316c6 | 604 | return log_warning_errno(r, "ethtool: could not connect to ethtool: %m"); |
a39f92d3 SS |
605 | } |
606 | ||
607 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); | |
608 | ||
2b44daaa | 609 | r = get_glinksettings(*fd, &ifr, &u); |
a39f92d3 | 610 | if (r < 0) { |
2b44daaa | 611 | r = get_gset(*fd, &ifr, &u); |
a39f92d3 | 612 | if (r < 0) |
5c2316c6 | 613 | return log_warning_errno(r, "ethtool: Cannot get device settings for %s : %m", ifname); |
a39f92d3 SS |
614 | } |
615 | ||
5c2316c6 YW |
616 | if (speed > 0) |
617 | u->base.speed = DIV_ROUND_UP(speed, 1000000); | |
593022fa | 618 | |
5c2316c6 YW |
619 | if (duplex != _DUP_INVALID) |
620 | u->base.duplex = duplex; | |
a39f92d3 | 621 | |
5c2316c6 YW |
622 | if (port != _NET_DEV_PORT_INVALID) |
623 | u->base.port = port; | |
a39f92d3 | 624 | |
5c2316c6 YW |
625 | if (autonegotiation >= 0) |
626 | u->base.autoneg = autonegotiation; | |
a39f92d3 | 627 | |
5c2316c6 | 628 | if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) { |
a0e1ad10 | 629 | u->base.autoneg = AUTONEG_ENABLE; |
5c2316c6 YW |
630 | memcpy(&u->link_modes.advertising, advertise, sizeof(uint32_t) * N_ADVERTISE); |
631 | memzero((uint8_t*) &u->link_modes.advertising + sizeof(uint32_t) * N_ADVERTISE, | |
632 | ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(uint32_t) * N_ADVERTISE); | |
5dd10118 | 633 | } |
6cf0a204 | 634 | |
a39f92d3 | 635 | if (u->base.cmd == ETHTOOL_GLINKSETTINGS) |
2b44daaa | 636 | r = set_slinksettings(*fd, &ifr, u); |
a39f92d3 | 637 | else |
2b44daaa | 638 | r = set_sset(*fd, &ifr, u); |
a39f92d3 | 639 | if (r < 0) |
5c2316c6 | 640 | return log_warning_errno(r, "ethtool: Cannot set device settings for %s : %m", ifname); |
a39f92d3 SS |
641 | |
642 | return r; | |
643 | } | |
5f945202 | 644 | |
5f945202 SS |
645 | int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) { |
646 | struct ethtool_channels ecmd = { | |
647 | .cmd = ETHTOOL_GCHANNELS | |
648 | }; | |
649 | struct ifreq ifr = { | |
650 | .ifr_data = (void*) &ecmd | |
651 | }; | |
652 | ||
653 | bool need_update = false; | |
654 | int r; | |
655 | ||
656 | if (*fd < 0) { | |
657 | r = ethtool_connect(fd); | |
658 | if (r < 0) | |
5c2316c6 | 659 | return log_warning_errno(r, "ethtool: could not connect to ethtool: %m"); |
5f945202 SS |
660 | } |
661 | ||
662 | strscpy(ifr.ifr_name, IFNAMSIZ, ifname); | |
663 | ||
664 | r = ioctl(*fd, SIOCETHTOOL, &ifr); | |
665 | if (r < 0) | |
666 | return -errno; | |
667 | ||
668 | if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) { | |
669 | ecmd.rx_count = channels->rx_count; | |
670 | need_update = true; | |
671 | } | |
672 | ||
673 | if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) { | |
674 | ecmd.tx_count = channels->tx_count; | |
675 | need_update = true; | |
676 | } | |
677 | ||
678 | if (channels->other_count_set && ecmd.other_count != channels->other_count) { | |
679 | ecmd.other_count = channels->other_count; | |
680 | need_update = true; | |
681 | } | |
682 | ||
683 | if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) { | |
684 | ecmd.combined_count = channels->combined_count; | |
685 | need_update = true; | |
686 | } | |
687 | ||
688 | if (need_update) { | |
689 | ecmd.cmd = ETHTOOL_SCHANNELS; | |
690 | ||
691 | r = ioctl(*fd, SIOCETHTOOL, &ifr); | |
692 | if (r < 0) | |
693 | return -errno; | |
694 | } | |
695 | ||
696 | return 0; | |
697 | } | |
6cf0a204 | 698 | |
5c2316c6 YW |
699 | int config_parse_channel(const char *unit, |
700 | const char *filename, | |
701 | unsigned line, | |
702 | const char *section, | |
703 | unsigned section_line, | |
704 | const char *lvalue, | |
705 | int ltype, | |
706 | const char *rvalue, | |
707 | void *data, | |
708 | void *userdata) { | |
709 | netdev_channels *channels = data; | |
710 | uint32_t k; | |
711 | int r; | |
712 | ||
713 | assert(filename); | |
714 | assert(section); | |
715 | assert(lvalue); | |
716 | assert(rvalue); | |
717 | assert(data); | |
718 | ||
719 | r = safe_atou32(rvalue, &k); | |
720 | if (r < 0) { | |
721 | log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue); | |
722 | return 0; | |
723 | } | |
724 | ||
725 | if (k < 1) { | |
726 | log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue); | |
727 | return 0; | |
728 | } | |
729 | ||
730 | if (streq(lvalue, "RxChannels")) { | |
731 | channels->rx_count = k; | |
732 | channels->rx_count_set = true; | |
733 | } else if (streq(lvalue, "TxChannels")) { | |
734 | channels->tx_count = k; | |
735 | channels->tx_count_set = true; | |
736 | } else if (streq(lvalue, "OtherChannels")) { | |
737 | channels->other_count = k; | |
738 | channels->other_count_set = true; | |
739 | } else if (streq(lvalue, "CombinedChannels")) { | |
740 | channels->combined_count = k; | |
741 | channels->combined_count_set = true; | |
742 | } | |
743 | ||
744 | return 0; | |
745 | } | |
746 | ||
6cf0a204 SS |
747 | int config_parse_advertise(const char *unit, |
748 | const char *filename, | |
749 | unsigned line, | |
750 | const char *section, | |
751 | unsigned section_line, | |
752 | const char *lvalue, | |
753 | int ltype, | |
754 | const char *rvalue, | |
755 | void *data, | |
756 | void *userdata) { | |
5c2316c6 | 757 | uint32_t *advertise = data; |
6cf0a204 SS |
758 | const char *p; |
759 | int r; | |
760 | ||
761 | assert(filename); | |
762 | assert(section); | |
763 | assert(lvalue); | |
764 | assert(rvalue); | |
765 | assert(data); | |
766 | ||
767 | if (isempty(rvalue)) { | |
768 | /* Empty string resets the value. */ | |
5c2316c6 | 769 | memzero(advertise, sizeof(uint32_t) * N_ADVERTISE); |
6cf0a204 SS |
770 | return 0; |
771 | } | |
772 | ||
773 | for (p = rvalue;;) { | |
774 | _cleanup_free_ char *w = NULL; | |
5dd10118 | 775 | enum ethtool_link_mode_bit_indices mode; |
6cf0a204 SS |
776 | |
777 | r = extract_first_word(&p, &w, NULL, 0); | |
778 | if (r == -ENOMEM) | |
779 | return log_oom(); | |
780 | if (r < 0) { | |
781 | log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split advertise modes '%s', ignoring: %m", rvalue); | |
782 | break; | |
783 | } | |
784 | if (r == 0) | |
785 | break; | |
786 | ||
2d18ac44 | 787 | mode = ethtool_link_mode_bit_from_string(w); |
84fb56d3 YW |
788 | /* We reuse the kernel provided enum which does not contain negative value. So, the cast |
789 | * below is mandatory. Otherwise, the check below always passes and access an invalid address. */ | |
790 | if ((int) mode < 0) { | |
6cf0a204 SS |
791 | log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w); |
792 | continue; | |
793 | } | |
2d18ac44 | 794 | |
5c2316c6 | 795 | advertise[mode / 32] |= 1UL << (mode % 32); |
2d18ac44 | 796 | } |
6cf0a204 SS |
797 | |
798 | return 0; | |
799 | } |