Files
systemd/src/shared/ethtool-util.c
Zbigniew Jędrzejewski-Szmek 6049ddcca2 shared/ethtool-util: autogenerate table of link mode names
We updated this table manually, but the kernel adds new modes periodically
and we forget to do that. So let that happen automatically whenever we import
new kernel headers.

C.f. 72dda93acb.

Effectively, this adds:
        [ETHTOOL_LINK_MODE_10baseT1L_Full_BIT]             = "10baset1l-full",
        [ETHTOOL_LINK_MODE_800000baseCR8_Full_BIT]         = "800000basecr8-full",
        [ETHTOOL_LINK_MODE_800000baseKR8_Full_BIT]         = "800000basekr8-full",
        [ETHTOOL_LINK_MODE_800000baseDR8_Full_BIT]         = "800000basedr8-full",
        [ETHTOOL_LINK_MODE_800000baseDR8_2_Full_BIT]       = "800000basedr8-2-full",
        [ETHTOOL_LINK_MODE_800000baseSR8_Full_BIT]         = "800000basesr8-full",
        [ETHTOOL_LINK_MODE_800000baseVR8_Full_BIT]         = "800000basevr8-full",

N_ADVERTISE needed to be increased because we need more bits.
2023-05-30 13:05:33 +02:00

1424 lines
48 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <net/if.h>
#include <sys/ioctl.h>
#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include <linux/sockios.h>
#include "conf-parser.h"
#include "ethtool-util.h"
#include "extract-word.h"
#include "fd-util.h"
#include "log.h"
#include "memory-util.h"
#include "socket-util.h"
#include "string-table.h"
#include "strv.h"
#include "strxcpyx.h"
static const char* const duplex_table[_DUP_MAX] = {
[DUP_FULL] = "full",
[DUP_HALF] = "half"
};
DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
static const struct {
uint32_t opt;
const char *name;
} wol_option_map[] = {
{ WAKE_PHY, "phy" },
{ WAKE_UCAST, "unicast", },
{ WAKE_MCAST, "multicast", },
{ WAKE_BCAST, "broadcast", },
{ WAKE_ARP, "arp", },
{ WAKE_MAGIC, "magic", },
{ WAKE_MAGICSECURE, "secureon", },
};
int wol_options_to_string_alloc(uint32_t opts, char **ret) {
_cleanup_free_ char *str = NULL;
assert(ret);
if (opts == UINT32_MAX) {
*ret = NULL;
return 0;
}
for (size_t i = 0; i < ELEMENTSOF(wol_option_map); i++)
if (opts & wol_option_map[i].opt &&
!strextend_with_separator(&str, ",", wol_option_map[i].name))
return -ENOMEM;
if (!str) {
str = strdup("off");
if (!str)
return -ENOMEM;
}
*ret = TAKE_PTR(str);
return 1;
}
static const char* const port_table[] = {
[NET_DEV_PORT_TP] = "tp",
[NET_DEV_PORT_AUI] = "aui",
[NET_DEV_PORT_MII] = "mii",
[NET_DEV_PORT_FIBRE] = "fibre",
[NET_DEV_PORT_BNC] = "bnc",
};
DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort);
DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting");
static const char* const mdi_table[] = {
[ETH_TP_MDI_INVALID] = "unknown",
[ETH_TP_MDI] = "mdi",
[ETH_TP_MDI_X] = "mdi-x",
[ETH_TP_MDI_AUTO] = "auto",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(mdi, int);
static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = {
[NET_DEV_FEAT_SG] = "tx-scatter-gather",
[NET_DEV_FEAT_IP_CSUM] = "tx-checksum-ipv4",
[NET_DEV_FEAT_HW_CSUM] = "tx-checksum-ip-generic",
[NET_DEV_FEAT_IPV6_CSUM] = "tx-checksum-ipv6",
[NET_DEV_FEAT_HIGHDMA] = "highdma",
[NET_DEV_FEAT_FRAGLIST] = "tx-scatter-gather-fraglist",
[NET_DEV_FEAT_HW_VLAN_CTAG_TX] = "tx-vlan-hw-insert",
[NET_DEV_FEAT_HW_VLAN_CTAG_RX] = "rx-vlan-hw-parse",
[NET_DEV_FEAT_HW_VLAN_CTAG_FILTER] = "rx-vlan-filter",
[NET_DEV_FEAT_HW_VLAN_STAG_TX] = "tx-vlan-stag-hw-insert",
[NET_DEV_FEAT_HW_VLAN_STAG_RX] = "rx-vlan-stag-hw-parse",
[NET_DEV_FEAT_HW_VLAN_STAG_FILTER] = "rx-vlan-stag-filter",
[NET_DEV_FEAT_VLAN_CHALLENGED] = "vlan-challenged",
[NET_DEV_FEAT_GSO] = "tx-generic-segmentation",
[NET_DEV_FEAT_LLTX] = "tx-lockless",
[NET_DEV_FEAT_NETNS_LOCAL] = "netns-local",
[NET_DEV_FEAT_GRO] = "rx-gro",
[NET_DEV_FEAT_GRO_HW] = "rx-gro-hw",
[NET_DEV_FEAT_LRO] = "rx-lro",
[NET_DEV_FEAT_TSO] = "tx-tcp-segmentation",
[NET_DEV_FEAT_GSO_ROBUST] = "tx-gso-robust",
[NET_DEV_FEAT_TSO_ECN] = "tx-tcp-ecn-segmentation",
[NET_DEV_FEAT_TSO_MANGLEID] = "tx-tcp-mangleid-segmentation",
[NET_DEV_FEAT_TSO6] = "tx-tcp6-segmentation",
[NET_DEV_FEAT_FSO] = "tx-fcoe-segmentation",
[NET_DEV_FEAT_GSO_GRE] = "tx-gre-segmentation",
[NET_DEV_FEAT_GSO_GRE_CSUM] = "tx-gre-csum-segmentation",
[NET_DEV_FEAT_GSO_IPXIP4] = "tx-ipxip4-segmentation",
[NET_DEV_FEAT_GSO_IPXIP6] = "tx-ipxip6-segmentation",
[NET_DEV_FEAT_GSO_UDP_TUNNEL] = "tx-udp_tnl-segmentation",
[NET_DEV_FEAT_GSO_UDP_TUNNEL_CSUM] = "tx-udp_tnl-csum-segmentation",
[NET_DEV_FEAT_GSO_PARTIAL] = "tx-gso-partial",
[NET_DEV_FEAT_GSO_TUNNEL_REMCSUM] = "tx-tunnel-remcsum-segmentation",
[NET_DEV_FEAT_GSO_SCTP] = "tx-sctp-segmentation",
[NET_DEV_FEAT_GSO_ESP] = "tx-esp-segmentation",
[NET_DEV_FEAT_GSO_UDP_L4] = "tx-udp-segmentation",
[NET_DEV_FEAT_GSO_FRAGLIST] = "tx-gso-list",
[NET_DEV_FEAT_FCOE_CRC] = "tx-checksum-fcoe-crc",
[NET_DEV_FEAT_SCTP_CRC] = "tx-checksum-sctp",
[NET_DEV_FEAT_FCOE_MTU] = "fcoe-mtu",
[NET_DEV_FEAT_NTUPLE] = "rx-ntuple-filter",
[NET_DEV_FEAT_RXHASH] = "rx-hashing",
[NET_DEV_FEAT_RXCSUM] = "rx-checksum",
[NET_DEV_FEAT_NOCACHE_COPY] = "tx-nocache-copy",
[NET_DEV_FEAT_LOOPBACK] = "loopback",
[NET_DEV_FEAT_RXFCS] = "rx-fcs",
[NET_DEV_FEAT_RXALL] = "rx-all",
[NET_DEV_FEAT_HW_L2FW_DOFFLOAD] = "l2-fwd-offload",
[NET_DEV_FEAT_HW_TC] = "hw-tc-offload",
[NET_DEV_FEAT_HW_ESP] = "esp-hw-offload",
[NET_DEV_FEAT_HW_ESP_TX_CSUM] = "esp-tx-csum-hw-offload",
[NET_DEV_FEAT_RX_UDP_TUNNEL_PORT] = "rx-udp_tunnel-port-offload",
[NET_DEV_FEAT_HW_TLS_RECORD] = "tls-hw-record",
[NET_DEV_FEAT_HW_TLS_TX] = "tls-hw-tx-offload",
[NET_DEV_FEAT_HW_TLS_RX] = "tls-hw-rx-offload",
[NET_DEV_FEAT_GRO_FRAGLIST] = "rx-gro-list",
[NET_DEV_FEAT_HW_MACSEC] = "macsec-hw-offload",
[NET_DEV_FEAT_GRO_UDP_FWD] = "rx-udp-gro-forwarding",
[NET_DEV_FEAT_HW_HSR_TAG_INS] = "hsr-tag-ins-offload",
[NET_DEV_FEAT_HW_HSR_TAG_RM] = "hsr-tag-rm-offload",
[NET_DEV_FEAT_HW_HSR_FWD] = "hsr-fwd-offload",
[NET_DEV_FEAT_HW_HSR_DUP] = "hsr-dup-offload",
[NET_DEV_FEAT_TXCSUM] = "tx-checksum-", /* The suffix "-" means any feature beginning with "tx-checksum-" */
};
static const char* const ethtool_link_mode_bit_table[] = {
# include "ethtool-link-mode.h"
};
/* Make sure the array is large enough to fit all bits */
assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < N_ADVERTISE);
DEFINE_STRING_TABLE_LOOKUP(ethtool_link_mode_bit, enum ethtool_link_mode_bit_indices);
static int ethtool_connect(int *ethtool_fd) {
int fd;
assert(ethtool_fd);
/* This does nothing if already connected. */
if (*ethtool_fd >= 0)
return 0;
fd = socket_ioctl_fd();
if (fd < 0)
return log_debug_errno(fd, "ethtool: could not create control socket: %m");
*ethtool_fd = fd;
return 0;
}
int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) {
struct ethtool_drvinfo ecmd = {
.cmd = ETHTOOL_GDRVINFO,
};
struct ifreq ifr = {
.ifr_data = (void*) &ecmd,
};
char *d;
int r;
assert(ethtool_fd);
assert(ifname);
assert(ret);
r = ethtool_connect(ethtool_fd);
if (r < 0)
return r;
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
if (isempty(ecmd.driver))
return -ENODATA;
d = strdup(ecmd.driver);
if (!d)
return -ENOMEM;
*ret = d;
return 0;
}
int ethtool_get_link_info(
int *ethtool_fd,
const char *ifname,
int *ret_autonegotiation,
uint64_t *ret_speed,
Duplex *ret_duplex,
NetDevPort *ret_port) {
struct ethtool_cmd ecmd = {
.cmd = ETHTOOL_GSET,
};
struct ifreq ifr = {
.ifr_data = (void*) &ecmd,
};
int r;
assert(ethtool_fd);
assert(ifname);
r = ethtool_connect(ethtool_fd);
if (r < 0)
return r;
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
if (ret_autonegotiation)
*ret_autonegotiation = ecmd.autoneg;
if (ret_speed) {
uint32_t speed;
speed = ethtool_cmd_speed(&ecmd);
*ret_speed = speed == (uint32_t) SPEED_UNKNOWN ?
UINT64_MAX : (uint64_t) speed * 1000 * 1000;
}
if (ret_duplex)
*ret_duplex = ecmd.duplex;
if (ret_port)
*ret_port = ecmd.port;
return 0;
}
int ethtool_get_permanent_hw_addr(int *ethtool_fd, const char *ifname, struct hw_addr_data *ret) {
_cleanup_close_ int fd = -EBADF;
struct {
struct ethtool_perm_addr addr;
uint8_t space[HW_ADDR_MAX_SIZE];
} epaddr = {
.addr.cmd = ETHTOOL_GPERMADDR,
.addr.size = HW_ADDR_MAX_SIZE,
};
struct ifreq ifr = {
.ifr_data = (caddr_t) &epaddr,
};
int r;
assert(ifname);
assert(ret);
if (!ethtool_fd)
ethtool_fd = &fd;
r = ethtool_connect(ethtool_fd);
if (r < 0)
return r;
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
if (epaddr.addr.size == 0)
return -ENODATA;
if (epaddr.addr.size > HW_ADDR_MAX_SIZE)
return -EINVAL;
ret->length = epaddr.addr.size;
memcpy(ret->bytes, epaddr.addr.data, epaddr.addr.size);
return 0;
}
#define UPDATE(dest, val, updated) \
do { \
typeof(val) _v = (val); \
if (dest != _v) \
updated = true; \
dest = _v; \
} while (false)
#define UPDATE_WITH_MAX(dest, max, val, updated) \
do { \
typeof(dest) _v = (val); \
typeof(dest) _max = (max); \
if (_v == 0 || _v > _max) \
_v = _max; \
if (dest != _v) \
updated = true; \
dest = _v; \
} while (false)
int ethtool_set_wol(
int *ethtool_fd,
const char *ifname,
uint32_t wolopts,
const uint8_t password[SOPASS_MAX]) {
struct ethtool_wolinfo ecmd = {
.cmd = ETHTOOL_GWOL,
};
struct ifreq ifr = {
.ifr_data = (void*) &ecmd,
};
bool need_update = false;
int r;
assert(ethtool_fd);
assert(ifname);
if (wolopts == UINT32_MAX && !password)
/* Nothing requested. Return earlier. */
return 0;
r = ethtool_connect(ethtool_fd);
if (r < 0)
return r;
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
CLEANUP_ERASE(ecmd);
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
if (wolopts == UINT32_MAX) {
/* When password is specified without valid WoL options specified, then enable
* WAKE_MAGICSECURE flag if supported. */
wolopts = ecmd.wolopts;
if (password && FLAGS_SET(ecmd.supported, WAKE_MAGICSECURE))
wolopts |= WAKE_MAGICSECURE;
}
if ((wolopts & ~ecmd.supported) != 0) {
_cleanup_free_ char *str = NULL;
(void) wol_options_to_string_alloc(wolopts & ~ecmd.supported, &str);
log_debug("Network interface %s does not support requested Wake on LAN options \"%s\", ignoring.",
ifname, strna(str));
wolopts &= ecmd.supported;
}
if (!FLAGS_SET(wolopts, WAKE_MAGICSECURE))
/* When WAKE_MAGICSECURE flag is not set, then ignore password. */
password = NULL;
UPDATE(ecmd.wolopts, wolopts, need_update);
if (password &&
memcmp(ecmd.sopass, password, sizeof(ecmd.sopass)) != 0) {
memcpy(ecmd.sopass, password, sizeof(ecmd.sopass));
need_update = true;
}
if (!need_update)
return 0;
ecmd.cmd = ETHTOOL_SWOL;
return RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
}
int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, const netdev_ring_param *ring) {
struct ethtool_ringparam ecmd = {
.cmd = ETHTOOL_GRINGPARAM,
};
struct ifreq ifr = {
.ifr_data = (void*) &ecmd,
};
bool need_update = false;
int r;
assert(ethtool_fd);
assert(ifname);
assert(ring);
if (!ring->rx.set &&
!ring->rx_mini.set &&
!ring->rx_jumbo.set &&
!ring->tx.set)
return 0;
r = ethtool_connect(ethtool_fd);
if (r < 0)
return r;
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
if (ring->rx.set)
UPDATE_WITH_MAX(ecmd.rx_pending, ecmd.rx_max_pending, ring->rx.value, need_update);
if (ring->rx_mini.set)
UPDATE_WITH_MAX(ecmd.rx_mini_pending, ecmd.rx_mini_max_pending, ring->rx_mini.value, need_update);
if (ring->rx_jumbo.set)
UPDATE_WITH_MAX(ecmd.rx_jumbo_pending, ecmd.rx_jumbo_max_pending, ring->rx_jumbo.value, need_update);
if (ring->tx.set)
UPDATE_WITH_MAX(ecmd.tx_pending, ecmd.tx_max_pending, ring->tx.value, need_update);
if (!need_update)
return 0;
ecmd.cmd = ETHTOOL_SRINGPARAM;
return RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
}
static int get_stringset(int ethtool_fd, const char *ifname, enum ethtool_stringset stringset_id, struct ethtool_gstrings **ret) {
_cleanup_free_ struct ethtool_gstrings *strings = NULL;
struct {
struct ethtool_sset_info info;
uint32_t space;
} buffer = {
.info.cmd = ETHTOOL_GSSET_INFO,
.info.sset_mask = UINT64_C(1) << stringset_id,
};
struct ifreq ifr = {
.ifr_data = (void*) &buffer,
};
uint32_t len;
assert(ethtool_fd >= 0);
assert(ifname);
assert(ret);
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
if (ioctl(ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
if (buffer.info.sset_mask == 0)
return -EOPNOTSUPP;
#pragma GCC diagnostic push
#if HAVE_ZERO_LENGTH_BOUNDS
# pragma GCC diagnostic ignored "-Wzero-length-bounds"
#endif
len = buffer.info.data[0];
#pragma GCC diagnostic pop
if (len == 0)
return -EOPNOTSUPP;
strings = malloc0(offsetof(struct ethtool_gstrings, data) + len * ETH_GSTRING_LEN);
if (!strings)
return -ENOMEM;
strings->cmd = ETHTOOL_GSTRINGS;
strings->string_set = stringset_id;
strings->len = len;
ifr.ifr_data = (void*) strings;
if (ioctl(ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
*ret = TAKE_PTR(strings);
return 0;
}
static int get_features(int ethtool_fd, const char *ifname, uint32_t n_features, struct ethtool_gfeatures **ret) {
_cleanup_free_ struct ethtool_gfeatures *gfeatures = NULL;
struct ifreq ifr;
assert(ethtool_fd >= 0);
assert(ifname);
assert(ret);
assert(n_features > 0);
gfeatures = malloc0(offsetof(struct ethtool_gfeatures, features) +
DIV_ROUND_UP(n_features, 32U) * sizeof(gfeatures->features[0]));
if (!gfeatures)
return -ENOMEM;
gfeatures->cmd = ETHTOOL_GFEATURES;
gfeatures->size = DIV_ROUND_UP(n_features, 32U);
ifr = (struct ifreq) {
.ifr_data = (void*) gfeatures,
};
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
if (ioctl(ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
*ret = TAKE_PTR(gfeatures);
return 0;
}
static int set_features_bit(
const struct ethtool_gstrings *strings,
const struct ethtool_gfeatures *gfeatures,
struct ethtool_sfeatures *sfeatures,
const char *feature,
int flag) {
assert(strings);
assert(gfeatures);
assert(sfeatures);
assert(feature);
if (flag < 0)
return 0;
for (uint32_t i = 0; i < strings->len; i++) {
uint32_t block, mask;
if (!strneq((const char*) &strings->data[i * ETH_GSTRING_LEN], feature, ETH_GSTRING_LEN))
continue;
block = i / 32;
mask = UINT32_C(1) << (i % 32);
if (!FLAGS_SET(gfeatures->features[block].available, mask) ||
FLAGS_SET(gfeatures->features[block].never_changed, mask))
return -EOPNOTSUPP;
sfeatures->features[block].valid |= mask;
SET_FLAG(sfeatures->features[block].requested, mask, flag);
return 0;
}
return -ENODATA;
}
static int set_features_multiple_bit(
const struct ethtool_gstrings *strings,
const struct ethtool_gfeatures *gfeatures,
struct ethtool_sfeatures *sfeatures,
const char *feature,
int flag) {
bool found = false;
int r = -ENODATA;
assert(strings);
assert(gfeatures);
assert(sfeatures);
assert(feature);
if (flag < 0)
return 0;
for (uint32_t i = 0; i < strings->len; i++) {
uint32_t block, mask;
if (!startswith((const char*) &strings->data[i * ETH_GSTRING_LEN], feature))
continue;
block = i / 32;
mask = UINT32_C(1) << (i % 32);
if (!FLAGS_SET(gfeatures->features[block].available, mask) ||
FLAGS_SET(gfeatures->features[block].never_changed, mask)) {
r = -EOPNOTSUPP;
continue;
}
/* The flags is explicitly set by set_features_bit() */
if (FLAGS_SET(sfeatures->features[block].valid, mask))
continue;
sfeatures->features[block].valid |= mask;
SET_FLAG(sfeatures->features[block].requested, mask, flag);
found = true;
}
return found ? 0 : r;
}
int ethtool_set_features(int *ethtool_fd, const char *ifname, const int features[static _NET_DEV_FEAT_MAX]) {
_cleanup_free_ struct ethtool_gstrings *strings = NULL;
_cleanup_free_ struct ethtool_gfeatures *gfeatures = NULL;
_cleanup_free_ struct ethtool_sfeatures *sfeatures = NULL;
struct ifreq ifr;
bool have = false;
int r;
assert(ethtool_fd);
assert(ifname);
assert(features);
for (size_t i = 0; i < _NET_DEV_FEAT_MAX; i++)
if (features[i] >= 0) {
have = true;
break;
}
if (!have)
return 0;
r = ethtool_connect(ethtool_fd);
if (r < 0)
return r;
r = get_stringset(*ethtool_fd, ifname, ETH_SS_FEATURES, &strings);
if (r < 0)
return log_debug_errno(r, "ethtool: could not get ethtool feature strings: %m");
r = get_features(*ethtool_fd, ifname, strings->len, &gfeatures);
if (r < 0)
return log_debug_errno(r, "ethtool: could not get ethtool features for %s: %m", ifname);
sfeatures = malloc0(offsetof(struct ethtool_sfeatures, features) +
DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
if (!sfeatures)
return log_oom_debug();
sfeatures->cmd = ETHTOOL_SFEATURES;
sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
for (size_t i = 0; i < _NET_DEV_FEAT_SIMPLE_MAX; i++) {
r = set_features_bit(strings, gfeatures, sfeatures, netdev_feature_table[i], features[i]);
if (r < 0)
log_debug_errno(r, "ethtool: could not set feature %s for %s, ignoring: %m", netdev_feature_table[i], ifname);
}
for (size_t i = _NET_DEV_FEAT_SIMPLE_MAX; i < _NET_DEV_FEAT_MAX; i++) {
r = set_features_multiple_bit(strings, gfeatures, sfeatures, netdev_feature_table[i], features[i]);
if (r < 0)
log_debug_errno(r, "ethtool: could not set feature %s for %s, ignoring: %m", netdev_feature_table[i], ifname);
}
ifr = (struct ifreq) {
.ifr_data = (void*) sfeatures,
};
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return log_debug_errno(errno, "ethtool: could not set ethtool features for %s", ifname);
return 0;
}
static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **ret) {
struct ecmd {
struct ethtool_link_settings req;
uint32_t link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
} ecmd = {
.req.cmd = ETHTOOL_GLINKSETTINGS,
};
struct ethtool_link_usettings *u;
unsigned offset;
assert(fd >= 0);
assert(ifr);
assert(ret);
/* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS
handshake first to agree on the length of the link mode bitmaps. If kernel doesn't
agree with user, it returns the bitmap length it is expecting from user as a negative
length (and cmd field is 0). When kernel and user agree, kernel returns valid info in
all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on
https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf
*/
ifr->ifr_data = (void *) &ecmd;
if (ioctl(fd, SIOCETHTOOL, ifr) < 0)
return -errno;
if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
return -EOPNOTSUPP;
ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
ifr->ifr_data = (void *) &ecmd;
if (ioctl(fd, SIOCETHTOOL, ifr) < 0)
return -errno;
if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
return -EOPNOTSUPP;
u = new(struct ethtool_link_usettings, 1);
if (!u)
return -ENOMEM;
*u = (struct ethtool_link_usettings) {
.base = ecmd.req,
};
offset = 0;
memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
offset += ecmd.req.link_mode_masks_nwords;
memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
offset += ecmd.req.link_mode_masks_nwords;
memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
*ret = u;
return 0;
}
static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **ret) {
struct ethtool_link_usettings *e;
struct ethtool_cmd ecmd = {
.cmd = ETHTOOL_GSET,
};
assert(fd >= 0);
assert(ifr);
assert(ret);
ifr->ifr_data = (void *) &ecmd;
if (ioctl(fd, SIOCETHTOOL, ifr) < 0)
return -errno;
e = new(struct ethtool_link_usettings, 1);
if (!e)
return -ENOMEM;
*e = (struct ethtool_link_usettings) {
.base.cmd = ETHTOOL_GSET,
.base.link_mode_masks_nwords = 1,
.base.speed = ethtool_cmd_speed(&ecmd),
.base.duplex = ecmd.duplex,
.base.port = ecmd.port,
.base.phy_address = ecmd.phy_address,
.base.autoneg = ecmd.autoneg,
.base.mdio_support = ecmd.mdio_support,
.base.eth_tp_mdix = ecmd.eth_tp_mdix,
.base.eth_tp_mdix_ctrl = ecmd.eth_tp_mdix_ctrl,
.link_modes.supported[0] = ecmd.supported,
.link_modes.advertising[0] = ecmd.advertising,
.link_modes.lp_advertising[0] = ecmd.lp_advertising,
};
*ret = e;
return 0;
}
static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
struct {
struct ethtool_link_settings req;
uint32_t link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
} ecmd = {};
unsigned offset;
assert(fd >= 0);
assert(ifr);
assert(u);
if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
return -EINVAL;
ecmd.req = u->base;
ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;
offset = 0;
memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords);
offset += ecmd.req.link_mode_masks_nwords;
memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords);
offset += ecmd.req.link_mode_masks_nwords;
memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords);
ifr->ifr_data = (void *) &ecmd;
return RET_NERRNO(ioctl(fd, SIOCETHTOOL, ifr));
}
static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
struct ethtool_cmd ecmd = {
.cmd = ETHTOOL_SSET,
};
assert(fd >= 0);
assert(ifr);
assert(u);
if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0)
return -EINVAL;
ecmd.supported = u->link_modes.supported[0];
ecmd.advertising = u->link_modes.advertising[0];
ecmd.lp_advertising = u->link_modes.lp_advertising[0];
ethtool_cmd_speed_set(&ecmd, u->base.speed);
ecmd.duplex = u->base.duplex;
ecmd.port = u->base.port;
ecmd.phy_address = u->base.phy_address;
ecmd.autoneg = u->base.autoneg;
ecmd.mdio_support = u->base.mdio_support;
ecmd.eth_tp_mdix = u->base.eth_tp_mdix;
ecmd.eth_tp_mdix_ctrl = u->base.eth_tp_mdix_ctrl;
ifr->ifr_data = (void *) &ecmd;
return RET_NERRNO(ioctl(fd, SIOCETHTOOL, ifr));
}
int ethtool_set_glinksettings(
int *fd,
const char *ifname,
int autonegotiation,
const uint32_t advertise[static N_ADVERTISE],
uint64_t speed,
Duplex duplex,
NetDevPort port,
uint8_t mdi) {
_cleanup_free_ struct ethtool_link_usettings *u = NULL;
struct ifreq ifr = {};
bool changed = false;
int r;
assert(fd);
assert(ifname);
assert(advertise);
if (autonegotiation < 0 && memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE) &&
speed == 0 && duplex < 0 && port < 0 && mdi == ETH_TP_MDI_INVALID)
return 0;
/* If autonegotiation is disabled, the speed and duplex represent the fixed link mode and are
* writable if the driver supports multiple link modes. If it is enabled then they are
* read-only. If the link is up they represent the negotiated link mode; if the link is down,
* the speed is 0, %SPEED_UNKNOWN or the highest enabled speed and @duplex is %DUPLEX_UNKNOWN
* or the best enabled duplex mode. */
if (speed > 0 || duplex >= 0 || port >= 0) {
if (autonegotiation == AUTONEG_ENABLE || !memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
log_debug("ethtool: autonegotiation is enabled, ignoring speed, duplex, or port settings.");
speed = 0;
duplex = _DUP_INVALID;
port = _NET_DEV_PORT_INVALID;
} else {
log_debug("ethtool: setting speed, duplex, or port, disabling autonegotiation.");
autonegotiation = AUTONEG_DISABLE;
}
}
r = ethtool_connect(fd);
if (r < 0)
return r;
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
r = get_glinksettings(*fd, &ifr, &u);
if (r < 0) {
r = get_gset(*fd, &ifr, &u);
if (r < 0)
return log_debug_errno(r, "ethtool: Cannot get device settings for %s: %m", ifname);
}
if (speed > 0)
UPDATE(u->base.speed, DIV_ROUND_UP(speed, 1000000), changed);
if (duplex >= 0)
UPDATE(u->base.duplex, duplex, changed);
if (port >= 0)
UPDATE(u->base.port, port, changed);
if (autonegotiation >= 0)
UPDATE(u->base.autoneg, autonegotiation, changed);
if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
UPDATE(u->base.autoneg, AUTONEG_ENABLE, changed);
changed = changed ||
memcmp(&u->link_modes.advertising, advertise, sizeof(uint32_t) * N_ADVERTISE) != 0 ||
!memeqzero((uint8_t*) &u->link_modes.advertising + sizeof(uint32_t) * N_ADVERTISE,
ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(uint32_t) * N_ADVERTISE);
memcpy(&u->link_modes.advertising, advertise, sizeof(uint32_t) * N_ADVERTISE);
memzero((uint8_t*) &u->link_modes.advertising + sizeof(uint32_t) * N_ADVERTISE,
ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(uint32_t) * N_ADVERTISE);
}
if (mdi != ETH_TP_MDI_INVALID) {
if (u->base.eth_tp_mdix_ctrl == ETH_TP_MDI_INVALID)
log_debug("ethtool: setting MDI not supported for %s, ignoring.", ifname);
else
UPDATE(u->base.eth_tp_mdix_ctrl, mdi, changed);
}
if (!changed)
return 0;
if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
r = set_slinksettings(*fd, &ifr, u);
else
r = set_sset(*fd, &ifr, u);
if (r < 0)
return log_debug_errno(r, "ethtool: Cannot set device settings for %s: %m", ifname);
return r;
}
int ethtool_set_channels(int *fd, const char *ifname, const netdev_channels *channels) {
struct ethtool_channels ecmd = {
.cmd = ETHTOOL_GCHANNELS,
};
struct ifreq ifr = {
.ifr_data = (void*) &ecmd,
};
bool need_update = false;
int r;
assert(fd);
assert(ifname);
assert(channels);
if (!channels->rx.set &&
!channels->tx.set &&
!channels->other.set &&
!channels->combined.set)
return 0;
r = ethtool_connect(fd);
if (r < 0)
return r;
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
if (ioctl(*fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
if (channels->rx.set)
UPDATE_WITH_MAX(ecmd.rx_count, ecmd.max_rx, channels->rx.value, need_update);
if (channels->tx.set)
UPDATE_WITH_MAX(ecmd.tx_count, ecmd.max_tx, channels->tx.value, need_update);
if (channels->other.set)
UPDATE_WITH_MAX(ecmd.other_count, ecmd.max_other, channels->other.value, need_update);
if (channels->combined.set)
UPDATE_WITH_MAX(ecmd.combined_count, ecmd.max_combined, channels->combined.value, need_update);
if (!need_update)
return 0;
ecmd.cmd = ETHTOOL_SCHANNELS;
return RET_NERRNO(ioctl(*fd, SIOCETHTOOL, &ifr));
}
int ethtool_set_flow_control(int *fd, const char *ifname, int rx, int tx, int autoneg) {
struct ethtool_pauseparam ecmd = {
.cmd = ETHTOOL_GPAUSEPARAM,
};
struct ifreq ifr = {
.ifr_data = (void*) &ecmd,
};
bool need_update = false;
int r;
assert(fd);
assert(ifname);
if (rx < 0 && tx < 0 && autoneg < 0)
return 0;
r = ethtool_connect(fd);
if (r < 0)
return r;
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
if (ioctl(*fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
if (rx >= 0)
UPDATE(ecmd.rx_pause, (uint32_t) rx, need_update);
if (tx >= 0)
UPDATE(ecmd.tx_pause, (uint32_t) tx, need_update);
if (autoneg >= 0)
UPDATE(ecmd.autoneg, (uint32_t) autoneg, need_update);
if (!need_update)
return 0;
ecmd.cmd = ETHTOOL_SPAUSEPARAM;
return RET_NERRNO(ioctl(*fd, SIOCETHTOOL, &ifr));
}
int ethtool_set_nic_coalesce_settings(int *ethtool_fd, const char *ifname, const netdev_coalesce_param *coalesce) {
struct ethtool_coalesce ecmd = {
.cmd = ETHTOOL_GCOALESCE,
};
struct ifreq ifr = {
.ifr_data = (void*) &ecmd,
};
bool need_update = false;
int r;
assert(ethtool_fd);
assert(ifname);
assert(coalesce);
if (coalesce->use_adaptive_rx_coalesce < 0 &&
coalesce->use_adaptive_tx_coalesce < 0 &&
!coalesce->rx_coalesce_usecs.set &&
!coalesce->rx_max_coalesced_frames.set &&
!coalesce->rx_coalesce_usecs_irq.set &&
!coalesce->rx_max_coalesced_frames_irq.set &&
!coalesce->tx_coalesce_usecs.set &&
!coalesce->tx_max_coalesced_frames.set &&
!coalesce->tx_coalesce_usecs_irq.set &&
!coalesce->tx_max_coalesced_frames_irq.set &&
!coalesce->stats_block_coalesce_usecs.set &&
!coalesce->pkt_rate_low.set &&
!coalesce->rx_coalesce_usecs_low.set &&
!coalesce->rx_max_coalesced_frames_low.set &&
!coalesce->tx_coalesce_usecs_low.set &&
!coalesce->tx_max_coalesced_frames_low.set &&
!coalesce->pkt_rate_high.set &&
!coalesce->rx_coalesce_usecs_high.set &&
!coalesce->rx_max_coalesced_frames_high.set &&
!coalesce->tx_coalesce_usecs_high.set &&
!coalesce->tx_max_coalesced_frames_high.set &&
!coalesce->rate_sample_interval.set)
return 0;
r = ethtool_connect(ethtool_fd);
if (r < 0)
return r;
strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname);
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
if (coalesce->use_adaptive_rx_coalesce >= 0)
UPDATE(ecmd.use_adaptive_rx_coalesce, (uint32_t) coalesce->use_adaptive_rx_coalesce, need_update);
if (coalesce->use_adaptive_tx_coalesce >= 0)
UPDATE(ecmd.use_adaptive_tx_coalesce, (uint32_t) coalesce->use_adaptive_tx_coalesce, need_update);
if (coalesce->rx_coalesce_usecs.set)
UPDATE(ecmd.rx_coalesce_usecs, coalesce->rx_coalesce_usecs.value, need_update);
if (coalesce->rx_max_coalesced_frames.set)
UPDATE(ecmd.rx_max_coalesced_frames, coalesce->rx_max_coalesced_frames.value, need_update);
if (coalesce->rx_coalesce_usecs_irq.set)
UPDATE(ecmd.rx_coalesce_usecs_irq, coalesce->rx_coalesce_usecs_irq.value, need_update);
if (coalesce->rx_max_coalesced_frames_irq.set)
UPDATE(ecmd.rx_max_coalesced_frames_irq, coalesce->rx_max_coalesced_frames_irq.value, need_update);
if (coalesce->tx_coalesce_usecs.set)
UPDATE(ecmd.tx_coalesce_usecs, coalesce->tx_coalesce_usecs.value, need_update);
if (coalesce->tx_max_coalesced_frames.set)
UPDATE(ecmd.tx_max_coalesced_frames, coalesce->tx_max_coalesced_frames.value, need_update);
if (coalesce->tx_coalesce_usecs_irq.set)
UPDATE(ecmd.tx_coalesce_usecs_irq, coalesce->tx_coalesce_usecs_irq.value, need_update);
if (coalesce->tx_max_coalesced_frames_irq.set)
UPDATE(ecmd.tx_max_coalesced_frames_irq, coalesce->tx_max_coalesced_frames_irq.value, need_update);
if (coalesce->stats_block_coalesce_usecs.set)
UPDATE(ecmd.stats_block_coalesce_usecs, coalesce->stats_block_coalesce_usecs.value, need_update);
if (coalesce->pkt_rate_low.set)
UPDATE(ecmd.pkt_rate_low, coalesce->pkt_rate_low.value, need_update);
if (coalesce->rx_coalesce_usecs_low.set)
UPDATE(ecmd.rx_coalesce_usecs_low, coalesce->rx_coalesce_usecs_low.value, need_update);
if (coalesce->rx_max_coalesced_frames_low.set)
UPDATE(ecmd.rx_max_coalesced_frames_low, coalesce->rx_max_coalesced_frames_low.value, need_update);
if (coalesce->tx_coalesce_usecs_low.set)
UPDATE(ecmd.tx_coalesce_usecs_low, coalesce->tx_coalesce_usecs_low.value, need_update);
if (coalesce->tx_max_coalesced_frames_low.set)
UPDATE(ecmd.tx_max_coalesced_frames_low, coalesce->tx_max_coalesced_frames_low.value, need_update);
if (coalesce->pkt_rate_high.set)
UPDATE(ecmd.pkt_rate_high, coalesce->pkt_rate_high.value, need_update);
if (coalesce->rx_coalesce_usecs_high.set)
UPDATE(ecmd.rx_coalesce_usecs_high, coalesce->rx_coalesce_usecs_high.value, need_update);
if (coalesce->rx_max_coalesced_frames_high.set)
UPDATE(ecmd.rx_max_coalesced_frames_high, coalesce->rx_max_coalesced_frames_high.value, need_update);
if (coalesce->tx_coalesce_usecs_high.set)
UPDATE(ecmd.tx_coalesce_usecs_high, coalesce->tx_coalesce_usecs_high.value, need_update);
if (coalesce->tx_max_coalesced_frames_high.set)
UPDATE(ecmd.tx_max_coalesced_frames_high, coalesce->tx_max_coalesced_frames_high.value, need_update);
if (coalesce->rate_sample_interval.set)
UPDATE(ecmd.rate_sample_interval, DIV_ROUND_UP(coalesce->rate_sample_interval.value, USEC_PER_SEC), need_update);
if (!need_update)
return 0;
ecmd.cmd = ETHTOOL_SCOALESCE;
return RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr));
}
int config_parse_advertise(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
uint32_t *advertise = ASSERT_PTR(data);
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
if (isempty(rvalue)) {
/* Empty string resets the value. */
memzero(advertise, sizeof(uint32_t) * N_ADVERTISE);
return 0;
}
for (const char *p = rvalue;;) {
_cleanup_free_ char *w = NULL;
enum ethtool_link_mode_bit_indices mode;
r = extract_first_word(&p, &w, NULL, 0);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to split advertise modes '%s', ignoring assignment: %m", rvalue);
return 0;
}
if (r == 0)
return 0;
mode = ethtool_link_mode_bit_from_string(w);
/* We reuse the kernel provided enum which does not contain negative value. So, the cast
* below is mandatory. Otherwise, the check below always passes and access an invalid address. */
if ((int) mode < 0) {
log_syntax(unit, LOG_WARNING, filename, line, mode,
"Failed to parse advertise mode, ignoring: %s", w);
continue;
}
advertise[mode / 32] |= 1UL << (mode % 32);
}
}
int config_parse_mdi(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
uint8_t *mdi = ASSERT_PTR(data);
assert(filename);
assert(rvalue);
if (isempty(rvalue)) {
*mdi = ETH_TP_MDI_INVALID;
return 0;
}
if (STR_IN_SET(rvalue, "mdi", "straight")) {
*mdi = ETH_TP_MDI;
return 0;
}
if (STR_IN_SET(rvalue, "mdi-x", "mdix", "crossover")) {
*mdi = ETH_TP_MDI_X;
return 0;
}
if (streq(rvalue, "auto")) {
*mdi = ETH_TP_MDI_AUTO;
return 0;
}
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Failed to parse %s= setting, ignoring assignment: %s", lvalue, rvalue);
return 0;
}
int config_parse_ring_buffer_or_channel(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
u32_opt *dst = ASSERT_PTR(data);
uint32_t k;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
if (isempty(rvalue)) {
dst->value = 0;
dst->set = false;
return 0;
}
if (streq(rvalue, "max")) {
dst->value = 0;
dst->set = true;
return 0;
}
r = safe_atou32(rvalue, &k);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse %s=, ignoring: %s", lvalue, rvalue);
return 0;
}
if (k < 1) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Invalid %s= value, ignoring: %s", lvalue, rvalue);
return 0;
}
dst->value = k;
dst->set = true;
return 0;
}
int config_parse_wol(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
uint32_t new_opts = 0, *opts = data;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
if (isempty(rvalue)) {
*opts = UINT32_MAX; /* Do not update WOL option. */
return 0;
}
if (streq(rvalue, "off")) {
*opts = 0; /* Disable WOL. */
return 0;
}
for (const char *p = rvalue;;) {
_cleanup_free_ char *w = NULL;
bool found = false;
r = extract_first_word(&p, &w, NULL, 0);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to split wake-on-lan modes '%s', ignoring assignment: %m", rvalue);
return 0;
}
if (r == 0)
break;
for (size_t i = 0; i < ELEMENTSOF(wol_option_map); i++)
if (streq(w, wol_option_map[i].name)) {
new_opts |= wol_option_map[i].opt;
found = true;
break;
}
if (!found)
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Unknown wake-on-lan mode '%s', ignoring.", w);
}
if (*opts == UINT32_MAX)
*opts = new_opts;
else
*opts |= new_opts;
return 0;
}
int config_parse_coalesce_u32(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
u32_opt *dst = data;
uint32_t k;
int r;
if (isempty(rvalue)) {
dst->value = 0;
dst->set = false;
return 0;
}
r = safe_atou32(rvalue, &k);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse %s=, ignoring: %s", lvalue, rvalue);
return 0;
}
dst->value = k;
dst->set = true;
return 0;
}
int config_parse_coalesce_sec(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
u32_opt *dst = data;
usec_t usec;
int r;
if (isempty(rvalue)) {
dst->value = 0;
dst->set = false;
return 0;
}
r = parse_sec(rvalue, &usec);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse coalesce setting value, ignoring: %s", rvalue);
return 0;
}
if (usec > UINT32_MAX) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Too large %s= value, ignoring: %s", lvalue, rvalue);
return 0;
}
if (STR_IN_SET(lvalue, "StatisticsBlockCoalesceSec", "CoalescePacketRateSampleIntervalSec") && usec < 1) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Invalid %s= value, ignoring: %s", lvalue, rvalue);
return 0;
}
dst->value = (uint32_t) usec;
dst->set = true;
return 0;
}