Closes #14770.
<para>Takes a integer. Specifies the NIC transmit ring buffer size. When unset, the kernel's default will be used.</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>RxFlowControl=</varname></term>
+ <listitem>
+ <para>Takes a boolean. When set, enables the receive flow control, also known as the ethernet
+ receive PAUSE message (generate and send ethernet PAUSE frames). When unset, the kernel's
+ default will be used.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>TxFlowControl=</varname></term>
+ <listitem>
+ <para>Takes a boolean. When set, enables the transmit flow control, also known as the ethernet
+ transmit PAUSE message (respond to received ethernet PAUSE frames). When unset, the kernel's
+ default will be used.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>AutoNegotiationFlowControl=</varname></term>
+ <listitem>
+ <para>Takes a boolean. When set, the auto negotiation enables the interface to exchange state
+ advertisements with the connected peer so that the two devices can agree on the ethernet
+ PAUSE configuration. When unset, the kernel's default will be used.</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
return 0;
}
+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;
+
+ if (*fd < 0) {
+ r = ethtool_connect_or_warn(fd, true);
+ if (r < 0)
+ return r;
+ }
+
+ strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
+
+ r = ioctl(*fd, SIOCETHTOOL, &ifr);
+ if (r < 0)
+ return -errno;
+
+ if (rx >= 0 && ecmd.rx_pause != (uint32_t) rx) {
+ ecmd.rx_pause = rx;
+ need_update = true;
+ }
+
+ if (tx >= 0 && ecmd.tx_pause != (uint32_t) tx) {
+ ecmd.tx_pause = tx;
+ need_update = true;
+ }
+
+ if (autoneg >= 0 && ecmd.autoneg != (uint32_t) autoneg) {
+ ecmd.autoneg = autoneg;
+ need_update = true;
+ }
+
+ if (need_update) {
+ ecmd.cmd = ETHTOOL_SPAUSEPARAM;
+
+ r = ioctl(*fd, SIOCETHTOOL, &ifr);
+ if (r < 0)
+ return -errno;
+ }
+
+ return 0;
+}
+
int config_parse_channel(const char *unit,
const char *filename,
unsigned line,
int autonegotiation, uint32_t advertise[static N_ADVERTISE],
uint64_t speed, Duplex duplex, NetDevPort port);
int ethtool_set_channels(int *ethtool_fd, const char *ifname, netdev_channels *channels);
+int ethtool_set_flow_control(int *fd, const char *ifname, int rx, int tx, int autoneg);
const char *duplex_to_string(Duplex d) _const_;
Duplex duplex_from_string(const char *d) _pure_;
Link.Advertise, config_parse_advertise, 0, offsetof(link_config, advertise)
Link.RxBufferSize, config_parse_nic_buffer_size, 0, offsetof(link_config, ring)
Link.TxBufferSize, config_parse_nic_buffer_size, 0, offsetof(link_config, ring)
+Link.RxFlowControl, config_parse_tristate, 0, offsetof(link_config, rx_flow_control)
+Link.TxFlowControl, config_parse_tristate, 0, offsetof(link_config, tx_flow_control)
+Link.AutoNegotiationFlowControl, config_parse_tristate, 0, offsetof(link_config, autoneg_flow_control)
.duplex = _DUP_INVALID,
.port = _NET_DEV_PORT_INVALID,
.autonegotiation = -1,
+ .rx_flow_control = -1,
+ .tx_flow_control = -1,
+ .autoneg_flow_control = -1,
};
for (i = 0; i < ELEMENTSOF(link->features); i++)
log_warning_errno(r, "Could not set ring buffer of %s: %m", old_name);
}
+ r = ethtool_set_flow_control(&ctx->ethtool_fd, old_name, config->rx_flow_control, config->tx_flow_control, config->autoneg_flow_control);
+ if (r < 0)
+ log_warning_errno(r, "Could not set flow control of %s: %m", old_name);
+
r = sd_device_get_ifindex(device, &ifindex);
if (r < 0)
return log_device_warning_errno(device, r, "Could not find ifindex: %m");
int features[_NET_DEV_FEAT_MAX];
netdev_channels channels;
netdev_ring_param ring;
+ int rx_flow_control;
+ int tx_flow_control;
+ int autoneg_flow_control;
LIST_FIELDS(link_config, links);
};
Advertise=
RxBufferSize=
TxBufferSize=
+RxFlowControl=
+TxFlowControl=
+AutoNegotiationFlowControl=