Coverage Report

Created: 2026-01-25 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/shared/ethtool-util.c
Line
Count
Source
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include <linux/ethtool.h>
4
#include <linux/sockios.h>
5
#include <net/if.h>
6
#include <sys/ioctl.h>
7
8
#include "alloc-util.h"
9
#include "conf-parser.h"
10
#include "errno-util.h"
11
#include "ether-addr-util.h"
12
#include "ethtool-util.h"
13
#include "extract-word.h"
14
#include "fd-util.h"
15
#include "log.h"
16
#include "macro-fundamental.h"
17
#include "memory-util.h"
18
#include "parse-util.h"
19
#include "socket-util.h"
20
#include "string-table.h"
21
#include "string-util.h"
22
#include "strv.h"
23
#include "strxcpyx.h"
24
#include "time-util.h"
25
26
static const char* const duplex_table[_DUP_MAX] = {
27
        [DUP_FULL] = "full",
28
        [DUP_HALF] = "half"
29
};
30
31
DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
32
1.60k
DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex);
33
1.60k
34
1.60k
static const struct {
35
1.60k
        uint32_t opt;
36
1.60k
        const char *name;
37
1.60k
} wol_option_map[] = {
38
1.60k
        { WAKE_PHY,         "phy"        },
39
1.60k
        { WAKE_UCAST,       "unicast",   },
40
1.60k
        { WAKE_MCAST,       "multicast", },
41
1.60k
        { WAKE_BCAST,       "broadcast", },
42
1.60k
        { WAKE_ARP,         "arp",       },
43
1.60k
        { WAKE_MAGIC,       "magic",     },
44
1.60k
        { WAKE_MAGICSECURE, "secureon",  },
45
1.60k
};
46
1.60k
47
1.60k
int wol_options_to_string_alloc(uint32_t opts, char **ret) {
48
0
        _cleanup_free_ char *str = NULL;
49
50
0
        assert(ret);
51
52
0
        if (opts == UINT32_MAX) {
53
0
                *ret = NULL;
54
0
                return 0;
55
0
        }
56
57
0
        FOREACH_ELEMENT(option, wol_option_map)
58
0
                if (opts & option->opt &&
59
0
                    !strextend_with_separator(&str, ",", option->name))
60
0
                        return -ENOMEM;
61
62
0
        if (!str) {
63
0
                str = strdup("off");
64
0
                if (!str)
65
0
                        return -ENOMEM;
66
0
        }
67
68
0
        *ret = TAKE_PTR(str);
69
0
        return 1;
70
0
}
71
72
static const char* const port_table[] = {
73
        [NET_DEV_PORT_TP]     = "tp",
74
        [NET_DEV_PORT_AUI]    = "aui",
75
        [NET_DEV_PORT_MII]    = "mii",
76
        [NET_DEV_PORT_FIBRE]  = "fibre",
77
        [NET_DEV_PORT_BNC]    = "bnc",
78
};
79
80
DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort);
81
1.64k
DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort);
82
1.64k
83
1.64k
static const char* const mdi_table[] = {
84
1.64k
        [ETH_TP_MDI_INVALID]  = "unknown",
85
1.64k
        [ETH_TP_MDI]          = "mdi",
86
1.64k
        [ETH_TP_MDI_X]        = "mdi-x",
87
1.64k
        [ETH_TP_MDI_AUTO]     = "auto",
88
1.64k
};
89
1.64k
90
1.64k
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(mdi, int);
91
1.64k
92
1.64k
static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = {
93
1.64k
        [NET_DEV_FEAT_SG]                  = "tx-scatter-gather",
94
1.64k
        [NET_DEV_FEAT_IP_CSUM]             = "tx-checksum-ipv4",
95
1.64k
        [NET_DEV_FEAT_HW_CSUM]             = "tx-checksum-ip-generic",
96
1.64k
        [NET_DEV_FEAT_IPV6_CSUM]           = "tx-checksum-ipv6",
97
1.64k
        [NET_DEV_FEAT_HIGHDMA]             = "highdma",
98
1.64k
        [NET_DEV_FEAT_FRAGLIST]            = "tx-scatter-gather-fraglist",
99
1.64k
        [NET_DEV_FEAT_HW_VLAN_CTAG_TX]     = "tx-vlan-hw-insert",
100
1.64k
        [NET_DEV_FEAT_HW_VLAN_CTAG_RX]     = "rx-vlan-hw-parse",
101
1.64k
        [NET_DEV_FEAT_HW_VLAN_CTAG_FILTER] = "rx-vlan-filter",
102
1.64k
        [NET_DEV_FEAT_HW_VLAN_STAG_TX]     = "tx-vlan-stag-hw-insert",
103
1.64k
        [NET_DEV_FEAT_HW_VLAN_STAG_RX]     = "rx-vlan-stag-hw-parse",
104
1.64k
        [NET_DEV_FEAT_HW_VLAN_STAG_FILTER] = "rx-vlan-stag-filter",
105
1.64k
        [NET_DEV_FEAT_VLAN_CHALLENGED]     = "vlan-challenged",
106
1.64k
        [NET_DEV_FEAT_GSO]                 = "tx-generic-segmentation",
107
1.64k
        [NET_DEV_FEAT_LLTX]                = "tx-lockless",
108
1.64k
        [NET_DEV_FEAT_NETNS_LOCAL]         = "netns-local",
109
1.64k
        [NET_DEV_FEAT_GRO]                 = "rx-gro",
110
1.64k
        [NET_DEV_FEAT_GRO_HW]              = "rx-gro-hw",
111
1.64k
        [NET_DEV_FEAT_LRO]                 = "rx-lro",
112
1.64k
        [NET_DEV_FEAT_TSO]                 = "tx-tcp-segmentation",
113
1.64k
        [NET_DEV_FEAT_GSO_ROBUST]          = "tx-gso-robust",
114
1.64k
        [NET_DEV_FEAT_TSO_ECN]             = "tx-tcp-ecn-segmentation",
115
1.64k
        [NET_DEV_FEAT_TSO_MANGLEID]        = "tx-tcp-mangleid-segmentation",
116
1.64k
        [NET_DEV_FEAT_TSO6]                = "tx-tcp6-segmentation",
117
1.64k
        [NET_DEV_FEAT_FSO]                 = "tx-fcoe-segmentation",
118
1.64k
        [NET_DEV_FEAT_GSO_GRE]             = "tx-gre-segmentation",
119
1.64k
        [NET_DEV_FEAT_GSO_GRE_CSUM]        = "tx-gre-csum-segmentation",
120
1.64k
        [NET_DEV_FEAT_GSO_IPXIP4]          = "tx-ipxip4-segmentation",
121
1.64k
        [NET_DEV_FEAT_GSO_IPXIP6]          = "tx-ipxip6-segmentation",
122
1.64k
        [NET_DEV_FEAT_GSO_UDP_TUNNEL]      = "tx-udp_tnl-segmentation",
123
1.64k
        [NET_DEV_FEAT_GSO_UDP_TUNNEL_CSUM] = "tx-udp_tnl-csum-segmentation",
124
1.64k
        [NET_DEV_FEAT_GSO_PARTIAL]         = "tx-gso-partial",
125
1.64k
        [NET_DEV_FEAT_GSO_TUNNEL_REMCSUM]  = "tx-tunnel-remcsum-segmentation",
126
1.64k
        [NET_DEV_FEAT_GSO_SCTP]            = "tx-sctp-segmentation",
127
1.64k
        [NET_DEV_FEAT_GSO_ESP]             = "tx-esp-segmentation",
128
1.64k
        [NET_DEV_FEAT_GSO_UDP_L4]          = "tx-udp-segmentation",
129
1.64k
        [NET_DEV_FEAT_GSO_FRAGLIST]        = "tx-gso-list",
130
1.64k
        [NET_DEV_FEAT_FCOE_CRC]            = "tx-checksum-fcoe-crc",
131
1.64k
        [NET_DEV_FEAT_SCTP_CRC]            = "tx-checksum-sctp",
132
1.64k
        [NET_DEV_FEAT_FCOE_MTU]            = "fcoe-mtu",
133
1.64k
        [NET_DEV_FEAT_NTUPLE]              = "rx-ntuple-filter",
134
1.64k
        [NET_DEV_FEAT_RXHASH]              = "rx-hashing",
135
1.64k
        [NET_DEV_FEAT_RXCSUM]              = "rx-checksum",
136
1.64k
        [NET_DEV_FEAT_NOCACHE_COPY]        = "tx-nocache-copy",
137
1.64k
        [NET_DEV_FEAT_LOOPBACK]            = "loopback",
138
1.64k
        [NET_DEV_FEAT_RXFCS]               = "rx-fcs",
139
1.64k
        [NET_DEV_FEAT_RXALL]               = "rx-all",
140
1.64k
        [NET_DEV_FEAT_HW_L2FW_DOFFLOAD]    = "l2-fwd-offload",
141
1.64k
        [NET_DEV_FEAT_HW_TC]               = "hw-tc-offload",
142
1.64k
        [NET_DEV_FEAT_HW_ESP]              = "esp-hw-offload",
143
1.64k
        [NET_DEV_FEAT_HW_ESP_TX_CSUM]      = "esp-tx-csum-hw-offload",
144
1.64k
        [NET_DEV_FEAT_RX_UDP_TUNNEL_PORT]  = "rx-udp_tunnel-port-offload",
145
1.64k
        [NET_DEV_FEAT_HW_TLS_RECORD]       = "tls-hw-record",
146
1.64k
        [NET_DEV_FEAT_HW_TLS_TX]           = "tls-hw-tx-offload",
147
1.64k
        [NET_DEV_FEAT_HW_TLS_RX]           = "tls-hw-rx-offload",
148
1.64k
        [NET_DEV_FEAT_GRO_FRAGLIST]        = "rx-gro-list",
149
1.64k
        [NET_DEV_FEAT_HW_MACSEC]           = "macsec-hw-offload",
150
1.64k
        [NET_DEV_FEAT_GRO_UDP_FWD]         = "rx-udp-gro-forwarding",
151
1.64k
        [NET_DEV_FEAT_HW_HSR_TAG_INS]      = "hsr-tag-ins-offload",
152
1.64k
        [NET_DEV_FEAT_HW_HSR_TAG_RM]       = "hsr-tag-rm-offload",
153
1.64k
        [NET_DEV_FEAT_HW_HSR_FWD]          = "hsr-fwd-offload",
154
1.64k
        [NET_DEV_FEAT_HW_HSR_DUP]          = "hsr-dup-offload",
155
1.64k
156
1.64k
        [NET_DEV_FEAT_TXCSUM]              = "tx-checksum-", /* The suffix "-" means any feature beginning with "tx-checksum-" */
157
1.64k
};
158
1.64k
159
1.64k
static const char* const ethtool_link_mode_bit_table[] = {
160
1.64k
#  include "ethtool-link-mode.inc"
161
1.64k
};
162
1.64k
/* Make sure the array is large enough to fit all bits */
163
1.64k
assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < N_ADVERTISE);
164
1.64k
165
1.64k
DEFINE_STRING_TABLE_LOOKUP(ethtool_link_mode_bit, enum ethtool_link_mode_bit_indices);
166
1.64k
167
1.64k
static int ethtool_connect(int *ethtool_fd) {
168
0
        int fd;
169
170
0
        assert(ethtool_fd);
171
172
        /* This does nothing if already connected. */
173
0
        if (*ethtool_fd >= 0)
174
0
                return 0;
175
176
0
        fd = socket_ioctl_fd();
177
0
        if (fd < 0)
178
0
                return log_debug_errno(fd, "ethtool: could not create control socket: %m");
179
180
0
        *ethtool_fd = fd;
181
0
        return 0;
182
0
}
183
184
0
int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) {
185
0
        struct ethtool_drvinfo ecmd = {
186
0
                .cmd = ETHTOOL_GDRVINFO,
187
0
        };
188
0
        struct ifreq ifr = {
189
0
                .ifr_data = (void*) &ecmd,
190
0
        };
191
0
        int r;
192
193
0
        assert(ethtool_fd);
194
0
        assert(ifname);
195
0
        assert(ret);
196
197
0
        r = ethtool_connect(ethtool_fd);
198
0
        if (r < 0)
199
0
                return r;
200
201
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
202
203
0
        if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
204
0
                return -errno;
205
206
0
        if (isempty(ecmd.driver))
207
0
                return -ENODATA;
208
209
0
        return strdup_to(ret, ecmd.driver);
210
0
}
211
212
int ethtool_get_link_info(
213
                int *ethtool_fd,
214
                const char *ifname,
215
                int *ret_autonegotiation,
216
                uint64_t *ret_speed,
217
                Duplex *ret_duplex,
218
0
                NetDevPort *ret_port) {
219
220
0
        struct ethtool_cmd ecmd = {
221
0
                .cmd = ETHTOOL_GSET,
222
0
        };
223
0
        struct ifreq ifr = {
224
0
                .ifr_data = (void*) &ecmd,
225
0
        };
226
0
        int r;
227
228
0
        assert(ethtool_fd);
229
0
        assert(ifname);
230
231
0
        r = ethtool_connect(ethtool_fd);
232
0
        if (r < 0)
233
0
                return r;
234
235
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
236
237
0
        if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
238
0
                return -errno;
239
240
0
        if (ret_autonegotiation)
241
0
                *ret_autonegotiation = ecmd.autoneg;
242
243
0
        if (ret_speed) {
244
0
                uint32_t speed;
245
246
0
                speed = ethtool_cmd_speed(&ecmd);
247
0
                *ret_speed = speed == (uint32_t) SPEED_UNKNOWN ?
248
0
                        UINT64_MAX : (uint64_t) speed * 1000 * 1000;
249
0
        }
250
251
0
        if (ret_duplex)
252
0
                *ret_duplex = ecmd.duplex;
253
254
0
        if (ret_port)
255
0
                *ret_port = ecmd.port;
256
257
0
        return 0;
258
0
}
259
260
0
int ethtool_get_permanent_hw_addr(int *ethtool_fd, const char *ifname, struct hw_addr_data *ret) {
261
0
        _cleanup_close_ int fd = -EBADF;
262
0
        union {
263
0
                struct ethtool_perm_addr addr;
264
0
                uint8_t buf[offsetof(struct ethtool_perm_addr, data) + HW_ADDR_MAX_SIZE];
265
0
        } epaddr = {
266
0
                .addr.cmd = ETHTOOL_GPERMADDR,
267
0
                .addr.size = HW_ADDR_MAX_SIZE,
268
0
        };
269
0
        struct ifreq ifr = {
270
0
                .ifr_data = (caddr_t) &epaddr,
271
0
        };
272
0
        int r;
273
274
0
        assert(ifname);
275
0
        assert(ret);
276
277
0
        if (!ethtool_fd)
278
0
                ethtool_fd = &fd;
279
0
        r = ethtool_connect(ethtool_fd);
280
0
        if (r < 0)
281
0
                return r;
282
283
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
284
285
0
        if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
286
0
                return -errno;
287
288
0
        if (epaddr.addr.size == 0)
289
0
                return -ENODATA;
290
291
0
        if (epaddr.addr.size > HW_ADDR_MAX_SIZE)
292
0
                return -EINVAL;
293
294
0
        ret->length = epaddr.addr.size;
295
0
        memcpy(ret->bytes, epaddr.addr.data, epaddr.addr.size);
296
0
        return 0;
297
0
}
298
299
#define UPDATE(dest, val, updated)                     \
300
0
        do {                                           \
301
0
                typeof(val) _v = (val);                \
302
0
                if (dest != _v)                        \
303
0
                        updated = true;                \
304
0
                dest = _v;                             \
305
0
        } while (false)
306
307
#define UPDATE_WITH_MAX(dest, max, val, updated)       \
308
0
        do {                                           \
309
0
                typeof(dest) _v = (val);               \
310
0
                typeof(dest) _max = (max);             \
311
0
                if (_v == 0 || _v > _max)              \
312
0
                        _v = _max;                     \
313
0
                if (dest != _v)                        \
314
0
                        updated = true;                \
315
0
                dest = _v;                             \
316
0
        } while (false)
317
318
int ethtool_set_wol(
319
                int *ethtool_fd,
320
                const char *ifname,
321
                uint32_t wolopts,
322
0
                const uint8_t password[SOPASS_MAX]) {
323
324
0
        struct ethtool_wolinfo ecmd = {
325
0
                .cmd = ETHTOOL_GWOL,
326
0
        };
327
0
        struct ifreq ifr = {
328
0
                .ifr_data = (void*) &ecmd,
329
0
        };
330
0
        bool need_update = false;
331
0
        int r;
332
333
0
        assert(ethtool_fd);
334
0
        assert(ifname);
335
336
0
        if (wolopts == UINT32_MAX && !password)
337
                /* Nothing requested. Return earlier. */
338
0
                return 0;
339
340
0
        r = ethtool_connect(ethtool_fd);
341
0
        if (r < 0)
342
0
                return r;
343
344
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
345
346
0
        CLEANUP_ERASE(ecmd);
347
348
0
        if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
349
0
                return -errno;
350
351
0
        if (wolopts == UINT32_MAX) {
352
                /* When password is specified without valid WoL options specified, then enable
353
                 * WAKE_MAGICSECURE flag if supported. */
354
0
                wolopts = ecmd.wolopts;
355
0
                if (password && FLAGS_SET(ecmd.supported, WAKE_MAGICSECURE))
356
0
                        wolopts |= WAKE_MAGICSECURE;
357
0
        }
358
359
0
        if ((wolopts & ~ecmd.supported) != 0) {
360
0
                _cleanup_free_ char *str = NULL;
361
362
0
                (void) wol_options_to_string_alloc(wolopts & ~ecmd.supported, &str);
363
0
                log_debug("Network interface %s does not support requested Wake on LAN options \"%s\", ignoring.",
364
0
                          ifname, strna(str));
365
366
0
                wolopts &= ecmd.supported;
367
0
        }
368
369
0
        if (!FLAGS_SET(wolopts, WAKE_MAGICSECURE))
370
                /* When WAKE_MAGICSECURE flag is not set, then ignore password. */
371
0
                password = NULL;
372
373
0
        UPDATE(ecmd.wolopts, wolopts, need_update);
374
0
        if (password &&
375
0
            memcmp(ecmd.sopass, password, sizeof(ecmd.sopass)) != 0) {
376
0
                memcpy(ecmd.sopass, password, sizeof(ecmd.sopass));
377
0
                need_update = true;
378
0
        }
379
380
0
        if (!need_update)
381
0
                return 0;
382
383
0
        ecmd.cmd = ETHTOOL_SWOL;
384
0
        return RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
385
0
}
386
387
0
int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, const netdev_ring_param *ring) {
388
0
        struct ethtool_ringparam ecmd = {
389
0
                .cmd = ETHTOOL_GRINGPARAM,
390
0
        };
391
0
        struct ifreq ifr = {
392
0
                .ifr_data = (void*) &ecmd,
393
0
        };
394
0
        bool need_update = false;
395
0
        int r;
396
397
0
        assert(ethtool_fd);
398
0
        assert(ifname);
399
0
        assert(ring);
400
401
0
        if (!ring->rx.set &&
402
0
            !ring->rx_mini.set &&
403
0
            !ring->rx_jumbo.set &&
404
0
            !ring->tx.set)
405
0
                return 0;
406
407
0
        r = ethtool_connect(ethtool_fd);
408
0
        if (r < 0)
409
0
                return r;
410
411
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
412
413
0
        if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
414
0
                return -errno;
415
416
0
        if (ring->rx.set)
417
0
                UPDATE_WITH_MAX(ecmd.rx_pending, ecmd.rx_max_pending, ring->rx.value, need_update);
418
419
0
        if (ring->rx_mini.set)
420
0
                UPDATE_WITH_MAX(ecmd.rx_mini_pending, ecmd.rx_mini_max_pending, ring->rx_mini.value, need_update);
421
422
0
        if (ring->rx_jumbo.set)
423
0
                UPDATE_WITH_MAX(ecmd.rx_jumbo_pending, ecmd.rx_jumbo_max_pending, ring->rx_jumbo.value, need_update);
424
425
0
        if (ring->tx.set)
426
0
                UPDATE_WITH_MAX(ecmd.tx_pending, ecmd.tx_max_pending, ring->tx.value, need_update);
427
428
0
        if (!need_update)
429
0
                return 0;
430
431
0
        ecmd.cmd = ETHTOOL_SRINGPARAM;
432
0
        return RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
433
0
}
434
435
0
static int get_stringset(int ethtool_fd, const char *ifname, enum ethtool_stringset stringset_id, struct ethtool_gstrings **ret) {
436
0
        _cleanup_free_ struct ethtool_gstrings *strings = NULL;
437
0
        uint8_t buffer[offsetof(struct ethtool_sset_info, data) + sizeof(uint32_t)] = {};
438
0
        struct ifreq ifr = {
439
0
                .ifr_data = (void*) buffer,
440
0
        };
441
0
        uint32_t len;
442
443
0
        assert(ethtool_fd >= 0);
444
0
        assert(ifname);
445
0
        assert(ret);
446
447
0
        struct ethtool_sset_info *info = (struct ethtool_sset_info*) buffer;
448
0
        info->cmd = ETHTOOL_GSSET_INFO;
449
0
        info->sset_mask = UINT64_C(1) << stringset_id;
450
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
451
452
0
        if (ioctl(ethtool_fd, SIOCETHTOOL, &ifr) < 0)
453
0
                return -errno;
454
455
0
        if (info->sset_mask == 0)
456
0
                return -EOPNOTSUPP;
457
458
0
        len = *info->data;
459
0
        if (len == 0)
460
0
                return -EOPNOTSUPP;
461
462
0
        strings = malloc0(offsetof(struct ethtool_gstrings, data) + len * ETH_GSTRING_LEN);
463
0
        if (!strings)
464
0
                return -ENOMEM;
465
466
0
        strings->cmd = ETHTOOL_GSTRINGS;
467
0
        strings->string_set = stringset_id;
468
0
        strings->len = len;
469
470
0
        ifr.ifr_data = (void*) strings;
471
472
0
        if (ioctl(ethtool_fd, SIOCETHTOOL, &ifr) < 0)
473
0
                return -errno;
474
475
0
        *ret = TAKE_PTR(strings);
476
0
        return 0;
477
0
}
478
479
0
static int get_features(int ethtool_fd, const char *ifname, uint32_t n_features, struct ethtool_gfeatures **ret) {
480
0
        _cleanup_free_ struct ethtool_gfeatures *gfeatures = NULL;
481
0
        struct ifreq ifr;
482
483
0
        assert(ethtool_fd >= 0);
484
0
        assert(ifname);
485
0
        assert(ret);
486
0
        assert(n_features > 0);
487
488
0
        gfeatures = malloc0(offsetof(struct ethtool_gfeatures, features) +
489
0
                            DIV_ROUND_UP(n_features, 32U) * sizeof(gfeatures->features[0]));
490
0
        if (!gfeatures)
491
0
                return -ENOMEM;
492
493
0
        gfeatures->cmd = ETHTOOL_GFEATURES;
494
0
        gfeatures->size = DIV_ROUND_UP(n_features, 32U);
495
496
0
        ifr = (struct ifreq) {
497
0
                .ifr_data = (void*) gfeatures,
498
0
        };
499
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
500
501
0
        if (ioctl(ethtool_fd, SIOCETHTOOL, &ifr) < 0)
502
0
                return -errno;
503
504
0
        *ret = TAKE_PTR(gfeatures);
505
0
        return 0;
506
0
}
507
508
static int set_features_bit(
509
                const struct ethtool_gstrings *strings,
510
                const struct ethtool_gfeatures *gfeatures,
511
                struct ethtool_sfeatures *sfeatures,
512
                const char *feature,
513
0
                int flag) {
514
515
0
        assert(strings);
516
0
        assert(gfeatures);
517
0
        assert(sfeatures);
518
0
        assert(feature);
519
520
0
        if (flag < 0)
521
0
                return 0;
522
523
0
        for (uint32_t i = 0; i < strings->len; i++) {
524
0
                uint32_t block, mask;
525
526
0
                if (!strneq((const char*) &strings->data[i * ETH_GSTRING_LEN], feature, ETH_GSTRING_LEN))
527
0
                        continue;
528
529
0
                block = i / 32;
530
0
                mask = UINT32_C(1) << (i % 32);
531
532
0
                if (!FLAGS_SET(gfeatures->features[block].available, mask) ||
533
0
                    FLAGS_SET(gfeatures->features[block].never_changed, mask))
534
0
                        return -EOPNOTSUPP;
535
536
0
                sfeatures->features[block].valid |= mask;
537
0
                SET_FLAG(sfeatures->features[block].requested, mask, flag);
538
539
0
                return 0;
540
0
        }
541
542
0
        return -ENODATA;
543
0
}
544
545
static int set_features_multiple_bit(
546
                const struct ethtool_gstrings *strings,
547
                const struct ethtool_gfeatures *gfeatures,
548
                struct ethtool_sfeatures *sfeatures,
549
                const char *feature,
550
0
                int flag) {
551
552
0
        bool found = false;
553
0
        int r = -ENODATA;
554
555
0
        assert(strings);
556
0
        assert(gfeatures);
557
0
        assert(sfeatures);
558
0
        assert(feature);
559
560
0
        if (flag < 0)
561
0
                return 0;
562
563
0
        for (uint32_t i = 0; i < strings->len; i++) {
564
0
                uint32_t block, mask;
565
566
0
                if (!startswith((const char*) &strings->data[i * ETH_GSTRING_LEN], feature))
567
0
                        continue;
568
569
0
                block = i / 32;
570
0
                mask = UINT32_C(1) << (i % 32);
571
572
0
                if (!FLAGS_SET(gfeatures->features[block].available, mask) ||
573
0
                    FLAGS_SET(gfeatures->features[block].never_changed, mask)) {
574
0
                        r = -EOPNOTSUPP;
575
0
                        continue;
576
0
                }
577
578
                /* The flags is explicitly set by set_features_bit() */
579
0
                if (FLAGS_SET(sfeatures->features[block].valid, mask))
580
0
                        continue;
581
582
0
                sfeatures->features[block].valid |= mask;
583
0
                SET_FLAG(sfeatures->features[block].requested, mask, flag);
584
585
0
                found = true;
586
0
        }
587
588
0
        return found ? 0 : r;
589
0
}
590
591
0
int ethtool_set_features(int *ethtool_fd, const char *ifname, const int features[static _NET_DEV_FEAT_MAX]) {
592
0
        _cleanup_free_ struct ethtool_gstrings *strings = NULL;
593
0
        _cleanup_free_ struct ethtool_gfeatures *gfeatures = NULL;
594
0
        _cleanup_free_ struct ethtool_sfeatures *sfeatures = NULL;
595
0
        struct ifreq ifr;
596
0
        bool have = false;
597
0
        int r;
598
599
0
        assert(ethtool_fd);
600
0
        assert(ifname);
601
0
        assert(features);
602
603
0
        for (size_t i = 0; i < _NET_DEV_FEAT_MAX; i++)
604
0
                if (features[i] >= 0) {
605
0
                        have = true;
606
0
                        break;
607
0
                }
608
609
0
        if (!have)
610
0
                return 0;
611
612
0
        r = ethtool_connect(ethtool_fd);
613
0
        if (r < 0)
614
0
                return r;
615
616
0
        r = get_stringset(*ethtool_fd, ifname, ETH_SS_FEATURES, &strings);
617
0
        if (r < 0)
618
0
                return log_debug_errno(r, "ethtool: could not get ethtool feature strings: %m");
619
620
0
        r = get_features(*ethtool_fd, ifname, strings->len, &gfeatures);
621
0
        if (r < 0)
622
0
                return log_debug_errno(r, "ethtool: could not get ethtool features for %s: %m", ifname);
623
624
0
        sfeatures = malloc0(offsetof(struct ethtool_sfeatures, features) +
625
0
                            DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
626
0
        if (!sfeatures)
627
0
                return log_oom_debug();
628
629
0
        sfeatures->cmd = ETHTOOL_SFEATURES;
630
0
        sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
631
632
0
        for (size_t i = 0; i < _NET_DEV_FEAT_SIMPLE_MAX; i++) {
633
0
                r = set_features_bit(strings, gfeatures, sfeatures, netdev_feature_table[i], features[i]);
634
0
                if (r < 0)
635
0
                        log_debug_errno(r, "ethtool: could not set feature %s for %s, ignoring: %m", netdev_feature_table[i], ifname);
636
0
        }
637
638
0
        for (size_t i = _NET_DEV_FEAT_SIMPLE_MAX; i < _NET_DEV_FEAT_MAX; i++) {
639
0
                r = set_features_multiple_bit(strings, gfeatures, sfeatures, netdev_feature_table[i], features[i]);
640
0
                if (r < 0)
641
0
                        log_debug_errno(r, "ethtool: could not set feature %s for %s, ignoring: %m", netdev_feature_table[i], ifname);
642
0
        }
643
644
0
        ifr = (struct ifreq) {
645
0
                .ifr_data = (void*) sfeatures,
646
0
        };
647
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
648
649
0
        if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
650
0
                return log_debug_errno(errno, "ethtool: could not set ethtool features for %s", ifname);
651
652
0
        return 0;
653
0
}
654
655
0
static int get_link_settings(int fd, struct ifreq *ifr, struct ethtool_link_settings **ret) {
656
0
        struct ethtool_link_settings ecmd = {
657
0
                .cmd = ETHTOOL_GLINKSETTINGS,
658
0
        };
659
660
0
        assert(fd >= 0);
661
0
        assert(ifr);
662
0
        assert(ret);
663
664
        /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS handshake first
665
         * to agree on the length of the link mode bitmaps. If kernel doesn't agree with user, it returns the
666
         * bitmap length it is expecting from user as a negative length. When kernel and user agree, kernel
667
         * returns valid info in all fields (with bitmap length > 0).
668
         * https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf
669
         * https://github.com/torvalds/linux/commit/793cf87de9d1a62dc9079c3ec5fcc01cfc62fafb */
670
671
0
        ifr->ifr_data = &ecmd;
672
673
0
        if (ioctl(fd, SIOCETHTOOL, ifr) < 0)
674
0
                return -errno;
675
676
0
        if (ecmd.link_mode_masks_nwords >= 0 || ecmd.cmd != ETHTOOL_GLINKSETTINGS)
677
0
                return -EOPNOTSUPP;
678
679
0
        int8_t n = -ecmd.link_mode_masks_nwords;
680
0
        size_t sz = offsetof(struct ethtool_link_settings, link_mode_masks) + sizeof(uint32_t) * n * 3;
681
0
        _cleanup_free_ struct ethtool_link_settings *settings = calloc(sz, 1);
682
0
        if (!settings)
683
0
                return -ENOMEM;
684
685
0
        settings->cmd = ETHTOOL_GLINKSETTINGS;
686
0
        settings->link_mode_masks_nwords = n;
687
688
0
        ifr->ifr_data = settings;
689
0
        if (ioctl(fd, SIOCETHTOOL, ifr) < 0)
690
0
                return -errno;
691
692
0
        if (settings->link_mode_masks_nwords != n || settings->cmd != ETHTOOL_GLINKSETTINGS)
693
0
                return -EOPNOTSUPP;
694
695
0
        *ret = TAKE_PTR(settings);
696
0
        return 0;
697
0
}
698
699
int ethtool_set_link_settings(
700
                int *fd,
701
                const char *ifname,
702
                int autonegotiation,
703
                const uint32_t advertise[static N_ADVERTISE],
704
                uint64_t speed,
705
                Duplex duplex,
706
                NetDevPort port,
707
0
                uint8_t mdi) {
708
709
0
        _cleanup_free_ struct ethtool_link_settings *settings = NULL;
710
0
        struct ifreq ifr = {};
711
0
        bool changed = false;
712
0
        int r;
713
714
0
        assert(fd);
715
0
        assert(ifname);
716
0
        assert(advertise);
717
718
0
        if (autonegotiation < 0 && memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE) &&
719
0
            speed == 0 && duplex < 0 && port < 0 && mdi == ETH_TP_MDI_INVALID)
720
0
                return 0;
721
722
        /* If autonegotiation is disabled, the speed and duplex represent the fixed link mode and are
723
         * writable if the driver supports multiple link modes. If it is enabled then they are
724
         * read-only. If the link is up they represent the negotiated link mode; if the link is down,
725
         * the speed is 0, %SPEED_UNKNOWN or the highest enabled speed and @duplex is %DUPLEX_UNKNOWN
726
         * or the best enabled duplex mode. */
727
728
0
        if (speed > 0 || duplex >= 0 || port >= 0) {
729
0
                if (autonegotiation == AUTONEG_ENABLE || !memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
730
0
                        log_debug("ethtool: autonegotiation is enabled, ignoring speed, duplex, or port settings.");
731
0
                        speed = 0;
732
0
                        duplex = _DUP_INVALID;
733
0
                        port = _NET_DEV_PORT_INVALID;
734
0
                } else {
735
0
                        log_debug("ethtool: setting speed, duplex, or port, disabling autonegotiation.");
736
0
                        autonegotiation = AUTONEG_DISABLE;
737
0
                }
738
0
        }
739
740
0
        r = ethtool_connect(fd);
741
0
        if (r < 0)
742
0
                return r;
743
744
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
745
746
0
        r = get_link_settings(*fd, &ifr, &settings);
747
0
        if (r < 0)
748
0
                return log_debug_errno(r, "ethtool: Cannot get link settings for %s: %m", ifname);
749
750
0
        if (speed > 0)
751
0
                UPDATE(settings->speed, DIV_ROUND_UP(speed, 1000000), changed);
752
753
0
        if (duplex >= 0)
754
0
                UPDATE(settings->duplex, duplex, changed);
755
756
0
        if (port >= 0)
757
0
                UPDATE(settings->port, port, changed);
758
759
0
        if (autonegotiation >= 0)
760
0
                UPDATE(settings->autoneg, autonegotiation, changed);
761
762
0
        if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
763
0
                UPDATE(settings->autoneg, AUTONEG_ENABLE, changed);
764
765
0
                uint32_t *a = settings->link_mode_masks + settings->link_mode_masks_nwords;
766
0
                size_t n = MIN(settings->link_mode_masks_nwords, N_ADVERTISE);
767
768
0
                changed = changed ||
769
0
                        memcmp(a, advertise, sizeof(uint32_t) * n) != 0 ||
770
0
                        !memeqzero(a + n, sizeof(uint32_t) * (settings->link_mode_masks_nwords - n));
771
772
0
                memcpy(a, advertise, sizeof(uint32_t) * n);
773
0
                memzero(a + n, sizeof(uint32_t) * (settings->link_mode_masks_nwords - n));
774
0
        }
775
776
0
        if (mdi != ETH_TP_MDI_INVALID) {
777
0
                if (settings->eth_tp_mdix_ctrl == ETH_TP_MDI_INVALID)
778
0
                        log_debug("ethtool: setting MDI not supported for %s, ignoring.", ifname);
779
0
                else
780
0
                        UPDATE(settings->eth_tp_mdix_ctrl, mdi, changed);
781
0
        }
782
783
0
        if (!changed)
784
0
                return 0;
785
786
0
        settings->cmd = ETHTOOL_SLINKSETTINGS;
787
0
        ifr.ifr_data = settings;
788
0
        if (ioctl(*fd, SIOCETHTOOL, &ifr) < 0)
789
0
                return log_debug_errno(errno, "ethtool: Cannot set link settings for %s: %m", ifname);
790
791
0
        return r;
792
0
}
793
794
0
int ethtool_set_channels(int *fd, const char *ifname, const netdev_channels *channels) {
795
0
        struct ethtool_channels ecmd = {
796
0
                .cmd = ETHTOOL_GCHANNELS,
797
0
        };
798
0
        struct ifreq ifr = {
799
0
                .ifr_data = (void*) &ecmd,
800
0
        };
801
0
        bool need_update = false;
802
0
        int r;
803
804
0
        assert(fd);
805
0
        assert(ifname);
806
0
        assert(channels);
807
808
0
        if (!channels->rx.set &&
809
0
            !channels->tx.set &&
810
0
            !channels->other.set &&
811
0
            !channels->combined.set)
812
0
                return 0;
813
814
0
        r = ethtool_connect(fd);
815
0
        if (r < 0)
816
0
                return r;
817
818
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
819
820
0
        if (ioctl(*fd, SIOCETHTOOL, &ifr) < 0)
821
0
                return -errno;
822
823
0
        if (channels->rx.set)
824
0
                UPDATE_WITH_MAX(ecmd.rx_count, ecmd.max_rx, channels->rx.value, need_update);
825
826
0
        if (channels->tx.set)
827
0
                UPDATE_WITH_MAX(ecmd.tx_count, ecmd.max_tx, channels->tx.value, need_update);
828
829
0
        if (channels->other.set)
830
0
                UPDATE_WITH_MAX(ecmd.other_count, ecmd.max_other, channels->other.value, need_update);
831
832
0
        if (channels->combined.set)
833
0
                UPDATE_WITH_MAX(ecmd.combined_count, ecmd.max_combined, channels->combined.value, need_update);
834
835
0
        if (!need_update)
836
0
                return 0;
837
838
0
        ecmd.cmd = ETHTOOL_SCHANNELS;
839
0
        return RET_NERRNO(ioctl(*fd, SIOCETHTOOL, &ifr));
840
0
}
841
842
0
int ethtool_set_flow_control(int *fd, const char *ifname, int rx, int tx, int autoneg) {
843
0
        struct ethtool_pauseparam ecmd = {
844
0
                .cmd = ETHTOOL_GPAUSEPARAM,
845
0
        };
846
0
        struct ifreq ifr = {
847
0
                .ifr_data = (void*) &ecmd,
848
0
        };
849
0
        bool need_update = false;
850
0
        int r;
851
852
0
        assert(fd);
853
0
        assert(ifname);
854
855
0
        if (rx < 0 && tx < 0 && autoneg < 0)
856
0
                return 0;
857
858
0
        r = ethtool_connect(fd);
859
0
        if (r < 0)
860
0
                return r;
861
862
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
863
864
0
        if (ioctl(*fd, SIOCETHTOOL, &ifr) < 0)
865
0
                return -errno;
866
867
0
        if (rx >= 0)
868
0
                UPDATE(ecmd.rx_pause, (uint32_t) rx, need_update);
869
870
0
        if (tx >= 0)
871
0
                UPDATE(ecmd.tx_pause, (uint32_t) tx, need_update);
872
873
0
        if (autoneg >= 0)
874
0
                UPDATE(ecmd.autoneg, (uint32_t) autoneg, need_update);
875
876
0
        if (!need_update)
877
0
                return 0;
878
879
0
        ecmd.cmd = ETHTOOL_SPAUSEPARAM;
880
0
        return RET_NERRNO(ioctl(*fd, SIOCETHTOOL, &ifr));
881
0
}
882
883
0
int ethtool_set_nic_coalesce_settings(int *ethtool_fd, const char *ifname, const netdev_coalesce_param *coalesce) {
884
0
        struct ethtool_coalesce ecmd = {
885
0
                .cmd = ETHTOOL_GCOALESCE,
886
0
        };
887
0
        struct ifreq ifr = {
888
0
                .ifr_data = (void*) &ecmd,
889
0
        };
890
0
        bool need_update = false;
891
0
        int r;
892
893
0
        assert(ethtool_fd);
894
0
        assert(ifname);
895
0
        assert(coalesce);
896
897
0
        if (coalesce->use_adaptive_rx_coalesce < 0 &&
898
0
            coalesce->use_adaptive_tx_coalesce < 0 &&
899
0
            !coalesce->rx_coalesce_usecs.set &&
900
0
            !coalesce->rx_max_coalesced_frames.set &&
901
0
            !coalesce->rx_coalesce_usecs_irq.set &&
902
0
            !coalesce->rx_max_coalesced_frames_irq.set &&
903
0
            !coalesce->tx_coalesce_usecs.set &&
904
0
            !coalesce->tx_max_coalesced_frames.set &&
905
0
            !coalesce->tx_coalesce_usecs_irq.set &&
906
0
            !coalesce->tx_max_coalesced_frames_irq.set &&
907
0
            !coalesce->stats_block_coalesce_usecs.set &&
908
0
            !coalesce->pkt_rate_low.set &&
909
0
            !coalesce->rx_coalesce_usecs_low.set &&
910
0
            !coalesce->rx_max_coalesced_frames_low.set &&
911
0
            !coalesce->tx_coalesce_usecs_low.set &&
912
0
            !coalesce->tx_max_coalesced_frames_low.set &&
913
0
            !coalesce->pkt_rate_high.set &&
914
0
            !coalesce->rx_coalesce_usecs_high.set &&
915
0
            !coalesce->rx_max_coalesced_frames_high.set &&
916
0
            !coalesce->tx_coalesce_usecs_high.set &&
917
0
            !coalesce->tx_max_coalesced_frames_high.set &&
918
0
            !coalesce->rate_sample_interval.set)
919
0
                return 0;
920
921
0
        r = ethtool_connect(ethtool_fd);
922
0
        if (r < 0)
923
0
                return r;
924
925
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
926
927
0
        if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
928
0
                return -errno;
929
930
0
        if (coalesce->use_adaptive_rx_coalesce >= 0)
931
0
                UPDATE(ecmd.use_adaptive_rx_coalesce, (uint32_t) coalesce->use_adaptive_rx_coalesce, need_update);
932
933
0
        if (coalesce->use_adaptive_tx_coalesce >= 0)
934
0
                UPDATE(ecmd.use_adaptive_tx_coalesce, (uint32_t) coalesce->use_adaptive_tx_coalesce, need_update);
935
936
0
        if (coalesce->rx_coalesce_usecs.set)
937
0
                UPDATE(ecmd.rx_coalesce_usecs, coalesce->rx_coalesce_usecs.value, need_update);
938
939
0
        if (coalesce->rx_max_coalesced_frames.set)
940
0
                UPDATE(ecmd.rx_max_coalesced_frames, coalesce->rx_max_coalesced_frames.value, need_update);
941
942
0
        if (coalesce->rx_coalesce_usecs_irq.set)
943
0
                UPDATE(ecmd.rx_coalesce_usecs_irq, coalesce->rx_coalesce_usecs_irq.value, need_update);
944
945
0
        if (coalesce->rx_max_coalesced_frames_irq.set)
946
0
                UPDATE(ecmd.rx_max_coalesced_frames_irq, coalesce->rx_max_coalesced_frames_irq.value, need_update);
947
948
0
        if (coalesce->tx_coalesce_usecs.set)
949
0
                UPDATE(ecmd.tx_coalesce_usecs, coalesce->tx_coalesce_usecs.value, need_update);
950
951
0
        if (coalesce->tx_max_coalesced_frames.set)
952
0
                UPDATE(ecmd.tx_max_coalesced_frames, coalesce->tx_max_coalesced_frames.value, need_update);
953
954
0
        if (coalesce->tx_coalesce_usecs_irq.set)
955
0
                UPDATE(ecmd.tx_coalesce_usecs_irq, coalesce->tx_coalesce_usecs_irq.value, need_update);
956
957
0
        if (coalesce->tx_max_coalesced_frames_irq.set)
958
0
                UPDATE(ecmd.tx_max_coalesced_frames_irq, coalesce->tx_max_coalesced_frames_irq.value, need_update);
959
960
0
        if (coalesce->stats_block_coalesce_usecs.set)
961
0
                UPDATE(ecmd.stats_block_coalesce_usecs, coalesce->stats_block_coalesce_usecs.value, need_update);
962
963
0
        if (coalesce->pkt_rate_low.set)
964
0
                UPDATE(ecmd.pkt_rate_low, coalesce->pkt_rate_low.value, need_update);
965
966
0
        if (coalesce->rx_coalesce_usecs_low.set)
967
0
                UPDATE(ecmd.rx_coalesce_usecs_low, coalesce->rx_coalesce_usecs_low.value, need_update);
968
969
0
        if (coalesce->rx_max_coalesced_frames_low.set)
970
0
                UPDATE(ecmd.rx_max_coalesced_frames_low, coalesce->rx_max_coalesced_frames_low.value, need_update);
971
972
0
        if (coalesce->tx_coalesce_usecs_low.set)
973
0
                UPDATE(ecmd.tx_coalesce_usecs_low, coalesce->tx_coalesce_usecs_low.value, need_update);
974
975
0
        if (coalesce->tx_max_coalesced_frames_low.set)
976
0
                UPDATE(ecmd.tx_max_coalesced_frames_low, coalesce->tx_max_coalesced_frames_low.value, need_update);
977
978
0
        if (coalesce->pkt_rate_high.set)
979
0
                UPDATE(ecmd.pkt_rate_high, coalesce->pkt_rate_high.value, need_update);
980
981
0
        if (coalesce->rx_coalesce_usecs_high.set)
982
0
                UPDATE(ecmd.rx_coalesce_usecs_high, coalesce->rx_coalesce_usecs_high.value, need_update);
983
984
0
        if (coalesce->rx_max_coalesced_frames_high.set)
985
0
                UPDATE(ecmd.rx_max_coalesced_frames_high, coalesce->rx_max_coalesced_frames_high.value, need_update);
986
987
0
        if (coalesce->tx_coalesce_usecs_high.set)
988
0
                UPDATE(ecmd.tx_coalesce_usecs_high, coalesce->tx_coalesce_usecs_high.value, need_update);
989
990
0
        if (coalesce->tx_max_coalesced_frames_high.set)
991
0
                UPDATE(ecmd.tx_max_coalesced_frames_high, coalesce->tx_max_coalesced_frames_high.value, need_update);
992
993
0
        if (coalesce->rate_sample_interval.set)
994
0
                UPDATE(ecmd.rate_sample_interval, DIV_ROUND_UP(coalesce->rate_sample_interval.value, USEC_PER_SEC), need_update);
995
996
0
        if (!need_update)
997
0
                return 0;
998
999
0
        ecmd.cmd = ETHTOOL_SCOALESCE;
1000
0
        return RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
1001
0
}
1002
1003
int ethtool_set_eee_settings(
1004
                int *ethtool_fd,
1005
                const char *ifname,
1006
                int enabled,
1007
                int tx_lpi_enabled,
1008
                usec_t tx_lpi_timer_usec,
1009
0
                uint32_t advertise) {
1010
1011
0
        int r;
1012
1013
0
        assert(ethtool_fd);
1014
0
        assert(ifname);
1015
1016
0
        if (enabled < 0 &&
1017
0
            tx_lpi_enabled < 0 &&
1018
0
            tx_lpi_timer_usec == USEC_INFINITY &&
1019
0
            advertise == 0)
1020
0
                return 0; /* Nothing requested. */
1021
1022
0
        r = ethtool_connect(ethtool_fd);
1023
0
        if (r < 0)
1024
0
                return r;
1025
1026
0
        struct ethtool_eee ecmd = {
1027
0
                .cmd = ETHTOOL_GEEE,
1028
0
        };
1029
0
        struct ifreq ifr = {
1030
0
                .ifr_data = (void*) &ecmd,
1031
0
        };
1032
1033
0
        strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
1034
1035
0
        if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
1036
0
                return -errno;
1037
1038
0
        if (ecmd.supported == 0)
1039
0
                return 0; /* Unsupported. */
1040
1041
0
        bool need_update = false;
1042
1043
0
        if (enabled >= 0)
1044
0
                UPDATE(ecmd.eee_enabled, (uint32_t) enabled, need_update);
1045
0
        if (tx_lpi_enabled >= 0)
1046
0
                UPDATE(ecmd.tx_lpi_enabled, (uint32_t) tx_lpi_enabled, need_update);
1047
0
        if (tx_lpi_timer_usec != USEC_INFINITY)
1048
0
                UPDATE(ecmd.tx_lpi_timer, (uint32_t) MIN(DIV_ROUND_UP(tx_lpi_timer_usec, USEC_PER_MSEC), (usec_t) UINT32_MAX), need_update);
1049
0
        if (advertise != 0)
1050
0
                UPDATE(ecmd.advertised, advertise & ecmd.supported, need_update);
1051
1052
0
        if (!need_update)
1053
0
                return 0; /* Nothing changed. */
1054
1055
0
        ecmd.cmd = ETHTOOL_SEEE;
1056
0
        return RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
1057
0
}
1058
1059
int config_parse_advertise(
1060
                const char *unit,
1061
                const char *filename,
1062
                unsigned line,
1063
                const char *section,
1064
                unsigned section_line,
1065
                const char *lvalue,
1066
                int ltype,
1067
                const char *rvalue,
1068
                void *data,
1069
675
                void *userdata) {
1070
1071
675
        uint32_t *advertise = ASSERT_PTR(data);
1072
675
        int r;
1073
1074
675
        assert(filename);
1075
675
        assert(section);
1076
675
        assert(lvalue);
1077
675
        assert(rvalue);
1078
1079
675
        if (isempty(rvalue)) {
1080
                /* Empty string resets the value. */
1081
194
                memzero(advertise, sizeof(uint32_t) * N_ADVERTISE);
1082
194
                return 0;
1083
194
        }
1084
1085
1.14k
        for (const char *p = rvalue;;) {
1086
1.14k
                _cleanup_free_ char *w = NULL;
1087
1.14k
                enum ethtool_link_mode_bit_indices mode;
1088
1089
1.14k
                r = extract_first_word(&p, &w, NULL, 0);
1090
1.14k
                if (r == -ENOMEM)
1091
0
                        return log_oom();
1092
1.14k
                if (r < 0) {
1093
201
                        log_syntax(unit, LOG_WARNING, filename, line, r,
1094
201
                                   "Failed to split advertise modes '%s', ignoring assignment: %m", rvalue);
1095
201
                        return 0;
1096
201
                }
1097
948
                if (r == 0)
1098
280
                        return 0;
1099
1100
668
                mode = ethtool_link_mode_bit_from_string(w);
1101
                /* We reuse the kernel provided enum which does not contain negative value. So, the cast
1102
                 * below is mandatory. Otherwise, the check below always passes and access an invalid address. */
1103
668
                if ((int) mode < 0) {
1104
472
                        log_syntax(unit, LOG_WARNING, filename, line, mode,
1105
472
                                   "Failed to parse advertise mode, ignoring: %s", w);
1106
472
                        continue;
1107
472
                }
1108
1109
196
                advertise[mode / 32] |= 1UL << (mode % 32);
1110
196
        }
1111
481
}
1112
1113
int config_parse_mdi(
1114
                const char *unit,
1115
                const char *filename,
1116
                unsigned line,
1117
                const char *section,
1118
                unsigned section_line,
1119
                const char *lvalue,
1120
                int ltype,
1121
                const char *rvalue,
1122
                void *data,
1123
1.05k
                void *userdata) {
1124
1125
1.05k
        uint8_t *mdi = ASSERT_PTR(data);
1126
1127
1.05k
        assert(filename);
1128
1.05k
        assert(rvalue);
1129
1130
1.05k
        if (isempty(rvalue)) {
1131
194
                *mdi = ETH_TP_MDI_INVALID;
1132
194
                return 0;
1133
194
        }
1134
1135
861
        if (STR_IN_SET(rvalue, "mdi", "straight")) {
1136
195
                *mdi = ETH_TP_MDI;
1137
195
                return 0;
1138
195
        }
1139
1140
666
        if (STR_IN_SET(rvalue, "mdi-x", "mdix", "crossover")) {
1141
194
                *mdi = ETH_TP_MDI_X;
1142
194
                return 0;
1143
194
        }
1144
1145
472
        if (streq(rvalue, "auto")) {
1146
194
                *mdi = ETH_TP_MDI_AUTO;
1147
194
                return 0;
1148
194
        }
1149
1150
472
        log_syntax(unit, LOG_WARNING, filename, line, 0,
1151
278
                   "Failed to parse %s= setting, ignoring assignment: %s", lvalue, rvalue);
1152
278
        return 0;
1153
472
}
1154
1155
int config_parse_ring_buffer_or_channel(
1156
                const char *unit,
1157
                const char *filename,
1158
                unsigned line,
1159
                const char *section,
1160
                unsigned section_line,
1161
                const char *lvalue,
1162
                int ltype,
1163
                const char *rvalue,
1164
                void *data,
1165
1.10k
                void *userdata) {
1166
1167
1.10k
        u32_opt *dst = ASSERT_PTR(data);
1168
1.10k
        uint32_t k;
1169
1.10k
        int r;
1170
1171
1.10k
        assert(filename);
1172
1.10k
        assert(section);
1173
1.10k
        assert(lvalue);
1174
1.10k
        assert(rvalue);
1175
1176
1.10k
        if (isempty(rvalue)) {
1177
194
                dst->value = 0;
1178
194
                dst->set = false;
1179
194
                return 0;
1180
194
        }
1181
1182
907
        if (streq(rvalue, "max")) {
1183
194
                dst->value = 0;
1184
194
                dst->set = true;
1185
194
                return 0;
1186
194
        }
1187
1188
713
        r = safe_atou32(rvalue, &k);
1189
713
        if (r < 0) {
1190
217
                log_syntax(unit, LOG_WARNING, filename, line, r,
1191
217
                           "Failed to parse %s=, ignoring: %s", lvalue, rvalue);
1192
217
                return 0;
1193
217
        }
1194
496
        if (k < 1) {
1195
236
                log_syntax(unit, LOG_WARNING, filename, line, 0,
1196
236
                           "Invalid %s= value, ignoring: %s", lvalue, rvalue);
1197
236
                return 0;
1198
236
        }
1199
1200
260
        dst->value = k;
1201
260
        dst->set = true;
1202
260
        return 0;
1203
496
}
1204
1205
int config_parse_wol(
1206
                const char *unit,
1207
                const char *filename,
1208
                unsigned line,
1209
                const char *section,
1210
                unsigned section_line,
1211
                const char *lvalue,
1212
                int ltype,
1213
                const char *rvalue,
1214
                void *data,
1215
1.48k
                void *userdata) {
1216
1217
1.48k
        uint32_t new_opts = 0, *opts = data;
1218
1.48k
        int r;
1219
1220
1.48k
        assert(filename);
1221
1.48k
        assert(section);
1222
1.48k
        assert(lvalue);
1223
1.48k
        assert(rvalue);
1224
1.48k
        assert(data);
1225
1226
1.48k
        if (isempty(rvalue)) {
1227
382
                *opts = UINT32_MAX; /* Do not update WOL option. */
1228
382
                return 0;
1229
382
        }
1230
1231
1.10k
        if (streq(rvalue, "off")) {
1232
194
                *opts = 0; /* Disable WOL. */
1233
194
                return 0;
1234
194
        }
1235
1236
2.06k
        for (const char *p = rvalue;;) {
1237
2.06k
                _cleanup_free_ char *w = NULL;
1238
2.06k
                bool found = false;
1239
1240
2.06k
                r = extract_first_word(&p, &w, NULL, 0);
1241
2.06k
                if (r == -ENOMEM)
1242
0
                        return log_oom();
1243
2.06k
                if (r < 0) {
1244
194
                        log_syntax(unit, LOG_WARNING, filename, line, r,
1245
194
                                   "Failed to split wake-on-lan modes '%s', ignoring assignment: %m", rvalue);
1246
194
                        return 0;
1247
194
                }
1248
1.87k
                if (r == 0)
1249
719
                        break;
1250
1251
1.15k
                FOREACH_ELEMENT(option, wol_option_map)
1252
7.43k
                        if (streq(w, option->name)) {
1253
254
                                new_opts |= option->opt;
1254
254
                                found = true;
1255
254
                                break;
1256
254
                        }
1257
1258
1.15k
                if (!found)
1259
897
                        log_syntax(unit, LOG_WARNING, filename, line, 0,
1260
1.15k
                                   "Unknown wake-on-lan mode '%s', ignoring.", w);
1261
1.15k
        }
1262
1263
719
        if (*opts == UINT32_MAX)
1264
317
                *opts = new_opts;
1265
402
        else
1266
402
                *opts |= new_opts;
1267
1268
719
        return 0;
1269
913
}
1270
1271
int config_parse_coalesce_u32(
1272
                const char *unit,
1273
                const char *filename,
1274
                unsigned line,
1275
                const char *section,
1276
                unsigned section_line,
1277
                const char *lvalue,
1278
                int ltype,
1279
                const char *rvalue,
1280
                void *data,
1281
744
                void *userdata) {
1282
744
        u32_opt *dst = data;
1283
744
        uint32_t k;
1284
744
        int r;
1285
1286
744
        if (isempty(rvalue)) {
1287
195
                dst->value = 0;
1288
195
                dst->set = false;
1289
195
                return 0;
1290
195
        }
1291
1292
549
        r = safe_atou32(rvalue, &k);
1293
549
        if (r < 0) {
1294
206
                log_syntax(unit, LOG_WARNING, filename, line, r,
1295
206
                           "Failed to parse %s=, ignoring: %s", lvalue, rvalue);
1296
206
                return 0;
1297
206
        }
1298
1299
343
        dst->value = k;
1300
343
        dst->set = true;
1301
343
        return 0;
1302
549
}
1303
1304
int config_parse_coalesce_sec(
1305
                const char *unit,
1306
                const char *filename,
1307
                unsigned line,
1308
                const char *section,
1309
                unsigned section_line,
1310
                const char *lvalue,
1311
                int ltype,
1312
                const char *rvalue,
1313
                void *data,
1314
3.57k
                void *userdata) {
1315
3.57k
        u32_opt *dst = data;
1316
3.57k
        usec_t usec;
1317
3.57k
        int r;
1318
1319
3.57k
        if (isempty(rvalue)) {
1320
195
                dst->value = 0;
1321
195
                dst->set = false;
1322
195
                return 0;
1323
195
        }
1324
1325
3.37k
        r = parse_sec(rvalue, &usec);
1326
3.37k
        if (r < 0) {
1327
2.11k
                log_syntax(unit, LOG_WARNING, filename, line, r,
1328
2.11k
                           "Failed to parse coalesce setting value, ignoring: %s", rvalue);
1329
2.11k
                return 0;
1330
2.11k
        }
1331
1332
1.26k
        if (usec > UINT32_MAX) {
1333
536
                log_syntax(unit, LOG_WARNING, filename, line, 0,
1334
536
                           "Too large %s= value, ignoring: %s", lvalue, rvalue);
1335
536
                return 0;
1336
536
        }
1337
1338
729
        if (STR_IN_SET(lvalue, "StatisticsBlockCoalesceSec", "CoalescePacketRateSampleIntervalSec") && usec < 1) {
1339
194
                log_syntax(unit, LOG_WARNING, filename, line, 0,
1340
194
                           "Invalid %s= value, ignoring: %s", lvalue, rvalue);
1341
194
                return 0;
1342
194
        }
1343
1344
535
        dst->value = (uint32_t) usec;
1345
535
        dst->set = true;
1346
1347
535
        return 0;
1348
729
}