Branch data 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 [ + + + + ]: 32 : 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 [ + + + + ]: 80 : 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 : : }
|