1c4d83
From 78e57b79c8790448412acca41e5d4495366305a6 Mon Sep 17 00:00:00 2001
1c4d83
From: Yu Watanabe <watanabe.yu+github@gmail.com>
1c4d83
Date: Wed, 18 Aug 2021 16:41:11 +0900
1c4d83
Subject: [PATCH] udev: make RxChannels= or friends also accept "max"
1c4d83
1c4d83
Follow-up for 406041b7de767316674eb6a2f98ad466577ce8a4.
1c4d83
1c4d83
Also, this makes
1c4d83
- the settings accept an empty string,
1c4d83
- if the specified value is too large, also use the advertised maximum
1c4d83
  value.
1c4d83
- mention the range of the value in the man page.
1c4d83
---
1c4d83
 man/systemd.link.xml                 |  49 ++------
1c4d83
 src/shared/ethtool-util.c            | 170 ++++++++++-----------------
1c4d83
 src/shared/ethtool-util.h            |  36 +++---
1c4d83
 src/udev/net/link-config-gperf.gperf |  16 +--
1c4d83
 4 files changed, 90 insertions(+), 181 deletions(-)
1c4d83
1c4d83
diff --git a/man/systemd.link.xml b/man/systemd.link.xml
1c4d83
index fd744ebaed..dfb02073b2 100644
1c4d83
--- a/man/systemd.link.xml
1c4d83
+++ b/man/systemd.link.xml
1c4d83
@@ -710,58 +710,27 @@
1c4d83
       </varlistentry>
1c4d83
       <varlistentry>
1c4d83
         <term><varname>RxChannels=</varname></term>
1c4d83
-        <listitem>
1c4d83
-          <para>Sets the number of receive channels (a number between 1 and 4294967295) .</para>
1c4d83
-        </listitem>
1c4d83
-      </varlistentry>
1c4d83
-      <varlistentry>
1c4d83
         <term><varname>TxChannels=</varname></term>
1c4d83
-        <listitem>
1c4d83
-          <para>Sets the number of transmit channels (a number between 1 and 4294967295).</para>
1c4d83
-        </listitem>
1c4d83
-      </varlistentry>
1c4d83
-      <varlistentry>
1c4d83
         <term><varname>OtherChannels=</varname></term>
1c4d83
-        <listitem>
1c4d83
-          <para>Sets the number of other channels (a number between 1 and 4294967295).</para>
1c4d83
-        </listitem>
1c4d83
-      </varlistentry>
1c4d83
-      <varlistentry>
1c4d83
         <term><varname>CombinedChannels=</varname></term>
1c4d83
         <listitem>
1c4d83
-          <para>Sets the number of combined set channels (a number between 1 and 4294967295).</para>
1c4d83
+          <para>Specifies the number of receive, transmit, other, or combined channels, respectively.
1c4d83
+          Takes an unsigned integer in the range 1…4294967295 or <literal>max</literal>. If set to
1c4d83
+          <literal>max</literal>, the advertised maximum value of the hardware will be used. When
1c4d83
+          unset, the number will not be changed. Defaults to unset.</para>
1c4d83
         </listitem>
1c4d83
       </varlistentry>
1c4d83
       <varlistentry>
1c4d83
         <term><varname>RxBufferSize=</varname></term>
1c4d83
-        <listitem>
1c4d83
-          <para>Takes an integer or <literal>max</literal>. Specifies the maximum number of pending packets
1c4d83
-          in the NIC receive buffer. When unset, the kernel's default will be used. If set to
1c4d83
-          <literal>max</literal>, the hardware's advertised maximum size will be used.</para>
1c4d83
-        </listitem>
1c4d83
-      </varlistentry>
1c4d83
-      <varlistentry>
1c4d83
         <term><varname>RxMiniBufferSize=</varname></term>
1c4d83
-        <listitem>
1c4d83
-          <para>Takes an integer or <literal>max</literal>. Specifies the maximum number of pending packets
1c4d83
-          in the NIC mini receive buffer. When unset, the kernel's default will be used. If set to
1c4d83
-          <literal>max</literal>, the hardware's advertised maximum size will be used.</para>
1c4d83
-        </listitem>
1c4d83
-      </varlistentry>
1c4d83
-      <varlistentry>
1c4d83
         <term><varname>RxJumboBufferSize=</varname></term>
1c4d83
-        <listitem>
1c4d83
-          <para>Takes an integer or <literal>max</literal>. Specifies the maximum number of pending packets
1c4d83
-          in the NIC jumbo receive buffer. When unset, the kernel's default will be used. If set to
1c4d83
-          <literal>max</literal>, the hardware's advertised maximum size will be used.</para>
1c4d83
-        </listitem>
1c4d83
-      </varlistentry>
1c4d83
-      <varlistentry>
1c4d83
         <term><varname>TxBufferSize=</varname></term>
1c4d83
         <listitem>
1c4d83
-          <para>Takes an integer or <literal>max</literal>. Specifies the maximum number of pending packets
1c4d83
-          in the NIC transmit buffer. When unset, the kernel's default will be used. If set to
1c4d83
-          <literal>max</literal>, the hardware's advertised maximum size will be used.</para>
1c4d83
+          <para>Specifies the maximum number of pending packets in the NIC receive buffer, mini receive
1c4d83
+          buffer, jumbo receive buffer, or transmit buffer, respectively. Takes an unsigned integer in
1c4d83
+          the range 1…4294967295 or <literal>max</literal>. If set to <literal>max</literal>, the
1c4d83
+          advertised maximum value of the hardware will be used. When unset, the number will not be
1c4d83
+          changed. Defaults to unset.</para>
1c4d83
         </listitem>
1c4d83
       </varlistentry>
1c4d83
       <varlistentry>
1c4d83
diff --git a/src/shared/ethtool-util.c b/src/shared/ethtool-util.c
1c4d83
index ed251ec8dd..2d41d861ba 100644
1c4d83
--- a/src/shared/ethtool-util.c
1c4d83
+++ b/src/shared/ethtool-util.c
1c4d83
@@ -329,6 +329,17 @@ int ethtool_get_permanent_macaddr(int *ethtool_fd, const char *ifname, struct et
1c4d83
                 dest = _v;                             \
1c4d83
         } while(false)
1c4d83
 
1c4d83
+#define UPDATE_WITH_MAX(dest, max, val, updated)       \
1c4d83
+        do {                                           \
1c4d83
+                typeof(dest) _v = (val);               \
1c4d83
+                typeof(dest) _max = (max);             \
1c4d83
+                if (_v == 0 || _v > _max)              \
1c4d83
+                        _v = _max;                     \
1c4d83
+                if (dest != _v)                        \
1c4d83
+                        updated = true;                \
1c4d83
+                dest = _v;                             \
1c4d83
+        } while(false)
1c4d83
+
1c4d83
 int ethtool_set_wol(int *ethtool_fd, const char *ifname, uint32_t wolopts) {
1c4d83
         struct ethtool_wolinfo ecmd = {
1c4d83
                 .cmd = ETHTOOL_GWOL,
1c4d83
@@ -382,10 +393,10 @@ int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, const netde
1c4d83
         assert(ifname);
1c4d83
         assert(ring);
1c4d83
 
1c4d83
-        if (!ring->rx_pending_set &&
1c4d83
-            !ring->rx_mini_pending_set &&
1c4d83
-            !ring->rx_jumbo_pending_set &&
1c4d83
-            !ring->tx_pending_set)
1c4d83
+        if (!ring->rx.set &&
1c4d83
+            !ring->rx_mini.set &&
1c4d83
+            !ring->rx_jumbo.set &&
1c4d83
+            !ring->tx.set)
1c4d83
                 return 0;
1c4d83
 
1c4d83
         r = ethtool_connect(ethtool_fd);
1c4d83
@@ -398,25 +409,17 @@ int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, const netde
1c4d83
         if (r < 0)
1c4d83
                 return -errno;
1c4d83
 
1c4d83
-        if (ring->rx_pending_set)
1c4d83
-                UPDATE(ecmd.rx_pending,
1c4d83
-                       ring->rx_pending == 0 ? ecmd.rx_max_pending : ring->rx_pending,
1c4d83
-                       need_update);
1c4d83
+        if (ring->rx.set)
1c4d83
+                UPDATE_WITH_MAX(ecmd.rx_pending, ecmd.rx_max_pending, ring->rx.value, need_update);
1c4d83
 
1c4d83
-        if (ring->rx_mini_pending_set)
1c4d83
-                UPDATE(ecmd.rx_mini_pending,
1c4d83
-                       ring->rx_mini_pending == 0 ? ecmd.rx_mini_max_pending : ring->rx_mini_pending,
1c4d83
-                       need_update);
1c4d83
+        if (ring->rx_mini.set)
1c4d83
+                UPDATE_WITH_MAX(ecmd.rx_mini_pending, ecmd.rx_mini_max_pending, ring->rx_mini.value, need_update);
1c4d83
 
1c4d83
-        if (ring->rx_jumbo_pending_set)
1c4d83
-                UPDATE(ecmd.rx_jumbo_pending,
1c4d83
-                       ring->rx_jumbo_pending == 0 ? ecmd.rx_jumbo_max_pending : ring->rx_jumbo_pending,
1c4d83
-                       need_update);
1c4d83
+        if (ring->rx_jumbo.set)
1c4d83
+                UPDATE_WITH_MAX(ecmd.rx_jumbo_pending, ecmd.rx_jumbo_max_pending, ring->rx_jumbo.value, need_update);
1c4d83
 
1c4d83
-        if (ring->tx_pending_set)
1c4d83
-                UPDATE(ecmd.tx_pending,
1c4d83
-                       ring->tx_pending == 0 ? ecmd.tx_max_pending : ring->tx_pending,
1c4d83
-                       need_update);
1c4d83
+        if (ring->tx.set)
1c4d83
+                UPDATE_WITH_MAX(ecmd.tx_pending, ecmd.tx_max_pending, ring->tx.value, need_update);
1c4d83
 
1c4d83
         if (!need_update)
1c4d83
                 return 0;
1c4d83
@@ -832,10 +835,10 @@ int ethtool_set_channels(int *fd, const char *ifname, const netdev_channels *cha
1c4d83
         assert(ifname);
1c4d83
         assert(channels);
1c4d83
 
1c4d83
-        if (!channels->rx_count_set &&
1c4d83
-            !channels->tx_count_set &&
1c4d83
-            !channels->other_count_set &&
1c4d83
-            !channels->combined_count_set)
1c4d83
+        if (!channels->rx.set &&
1c4d83
+            !channels->tx.set &&
1c4d83
+            !channels->other.set &&
1c4d83
+            !channels->combined.set)
1c4d83
                 return 0;
1c4d83
 
1c4d83
         r = ethtool_connect(fd);
1c4d83
@@ -848,17 +851,17 @@ int ethtool_set_channels(int *fd, const char *ifname, const netdev_channels *cha
1c4d83
         if (r < 0)
1c4d83
                 return -errno;
1c4d83
 
1c4d83
-        if (channels->rx_count_set)
1c4d83
-                UPDATE(ecmd.rx_count, channels->rx_count, need_update);
1c4d83
+        if (channels->rx.set)
1c4d83
+                UPDATE_WITH_MAX(ecmd.rx_count, ecmd.max_rx, channels->rx.value, need_update);
1c4d83
 
1c4d83
-        if (channels->tx_count_set)
1c4d83
-                UPDATE(ecmd.tx_count, channels->tx_count, need_update);
1c4d83
+        if (channels->tx.set)
1c4d83
+                UPDATE_WITH_MAX(ecmd.tx_count, ecmd.max_tx, channels->tx.value, need_update);
1c4d83
 
1c4d83
-        if (channels->other_count_set)
1c4d83
-                UPDATE(ecmd.other_count, channels->other_count, need_update);
1c4d83
+        if (channels->other.set)
1c4d83
+                UPDATE_WITH_MAX(ecmd.other_count, ecmd.max_other, channels->other.value, need_update);
1c4d83
 
1c4d83
-        if (channels->combined_count_set)
1c4d83
-                UPDATE(ecmd.combined_count, channels->combined_count, need_update);
1c4d83
+        if (channels->combined.set)
1c4d83
+                UPDATE_WITH_MAX(ecmd.combined_count, ecmd.max_combined, channels->combined.value, need_update);
1c4d83
 
1c4d83
         if (!need_update)
1c4d83
                 return 0;
1c4d83
@@ -917,57 +920,6 @@ int ethtool_set_flow_control(int *fd, const char *ifname, int rx, int tx, int au
1c4d83
         return 0;
1c4d83
 }
1c4d83
 
1c4d83
-int config_parse_channel(
1c4d83
-                const char *unit,
1c4d83
-                const char *filename,
1c4d83
-                unsigned line,
1c4d83
-                const char *section,
1c4d83
-                unsigned section_line,
1c4d83
-                const char *lvalue,
1c4d83
-                int ltype,
1c4d83
-                const char *rvalue,
1c4d83
-                void *data,
1c4d83
-                void *userdata) {
1c4d83
-
1c4d83
-        netdev_channels *channels = data;
1c4d83
-        uint32_t k;
1c4d83
-        int r;
1c4d83
-
1c4d83
-        assert(filename);
1c4d83
-        assert(section);
1c4d83
-        assert(lvalue);
1c4d83
-        assert(rvalue);
1c4d83
-        assert(data);
1c4d83
-
1c4d83
-        r = safe_atou32(rvalue, &k);
1c4d83
-        if (r < 0) {
1c4d83
-                log_syntax(unit, LOG_WARNING, filename, line, r,
1c4d83
-                           "Failed to parse channel value for %s=, ignoring: %s", lvalue, rvalue);
1c4d83
-                return 0;
1c4d83
-        }
1c4d83
-        if (k < 1) {
1c4d83
-                log_syntax(unit, LOG_WARNING, filename, line, 0,
1c4d83
-                           "Invalid %s= value, ignoring: %s", lvalue, rvalue);
1c4d83
-                return 0;
1c4d83
-        }
1c4d83
-
1c4d83
-        if (streq(lvalue, "RxChannels")) {
1c4d83
-                channels->rx_count = k;
1c4d83
-                channels->rx_count_set = true;
1c4d83
-        } else if (streq(lvalue, "TxChannels")) {
1c4d83
-                channels->tx_count = k;
1c4d83
-                channels->tx_count_set = true;
1c4d83
-        } else if (streq(lvalue, "OtherChannels")) {
1c4d83
-                channels->other_count = k;
1c4d83
-                channels->other_count_set = true;
1c4d83
-        } else if (streq(lvalue, "CombinedChannels")) {
1c4d83
-                channels->combined_count = k;
1c4d83
-                channels->combined_count_set = true;
1c4d83
-        }
1c4d83
-
1c4d83
-        return 0;
1c4d83
-}
1c4d83
-
1c4d83
 int config_parse_advertise(
1c4d83
                 const char *unit,
1c4d83
                 const char *filename,
1c4d83
@@ -1023,7 +975,7 @@ int config_parse_advertise(
1c4d83
         }
1c4d83
 }
1c4d83
 
1c4d83
-int config_parse_nic_buffer_size(
1c4d83
+int config_parse_ring_buffer_or_channel(
1c4d83
                 const char *unit,
1c4d83
                 const char *filename,
1c4d83
                 unsigned line,
1c4d83
@@ -1035,7 +987,7 @@ int config_parse_nic_buffer_size(
1c4d83
                 void *data,
1c4d83
                 void *userdata) {
1c4d83
 
1c4d83
-        netdev_ring_param *ring = data;
1c4d83
+        u32_opt *dst = data;
1c4d83
         uint32_t k;
1c4d83
         int r;
1c4d83
 
1c4d83
@@ -1045,36 +997,32 @@ int config_parse_nic_buffer_size(
1c4d83
         assert(rvalue);
1c4d83
         assert(data);
1c4d83
 
1c4d83
-        if (streq(rvalue, "max"))
1c4d83
-                k = 0;
1c4d83
-        else {
1c4d83
-                r = safe_atou32(rvalue, &k);
1c4d83
-                if (r < 0) {
1c4d83
-                        log_syntax(unit, LOG_WARNING, filename, line, r,
1c4d83
-                                "Failed to parse interface buffer value, ignoring: %s", rvalue);
1c4d83
-                        return 0;
1c4d83
-                }
1c4d83
-                if (k < 1) {
1c4d83
-                        log_syntax(unit, LOG_WARNING, filename, line, 0,
1c4d83
-                                "Invalid %s= value, ignoring: %s", lvalue, rvalue);
1c4d83
-                        return 0;
1c4d83
-                }
1c4d83
+        if (isempty(rvalue)) {
1c4d83
+                dst->value = 0;
1c4d83
+                dst->set = false;
1c4d83
+                return 0;
1c4d83
+        }
1c4d83
+
1c4d83
+        if (streq(rvalue, "max")) {
1c4d83
+                dst->value = 0;
1c4d83
+                dst->set = true;
1c4d83
+                return 0;
1c4d83
         }
1c4d83
 
1c4d83
-        if (streq(lvalue, "RxBufferSize")) {
1c4d83
-                ring->rx_pending = k;
1c4d83
-                ring->rx_pending_set = true;
1c4d83
-        } else if (streq(lvalue, "RxMiniBufferSize")) {
1c4d83
-                ring->rx_mini_pending = k;
1c4d83
-                ring->rx_mini_pending_set = true;
1c4d83
-        } else if (streq(lvalue, "RxJumboBufferSize")) {
1c4d83
-                ring->rx_jumbo_pending = k;
1c4d83
-                ring->rx_jumbo_pending_set = true;
1c4d83
-        } else if (streq(lvalue, "TxBufferSize")) {
1c4d83
-                ring->tx_pending = k;
1c4d83
-                ring->tx_pending_set = true;
1c4d83
+        r = safe_atou32(rvalue, &k);
1c4d83
+        if (r < 0) {
1c4d83
+                log_syntax(unit, LOG_WARNING, filename, line, r,
1c4d83
+                           "Failed to parse %s=, ignoring: %s", lvalue, rvalue);
1c4d83
+                return 0;
1c4d83
+        }
1c4d83
+        if (k < 1) {
1c4d83
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
1c4d83
+                           "Invalid %s= value, ignoring: %s", lvalue, rvalue);
1c4d83
+                return 0;
1c4d83
         }
1c4d83
 
1c4d83
+        dst->value = k;
1c4d83
+        dst->set = true;
1c4d83
         return 0;
1c4d83
 }
1c4d83
 
1c4d83
diff --git a/src/shared/ethtool-util.h b/src/shared/ethtool-util.h
1c4d83
index aea131914e..8fdbdec39a 100644
1c4d83
--- a/src/shared/ethtool-util.h
1c4d83
+++ b/src/shared/ethtool-util.h
1c4d83
@@ -57,30 +57,23 @@ struct ethtool_link_usettings {
1c4d83
         } link_modes;
1c4d83
 };
1c4d83
 
1c4d83
+typedef struct u32_opt {
1c4d83
+        uint32_t value; /* a value of 0 indicates the hardware advertised maximum should be used.*/
1c4d83
+        bool set;
1c4d83
+} u32_opt;
1c4d83
+
1c4d83
 typedef struct netdev_channels {
1c4d83
-        uint32_t rx_count;
1c4d83
-        uint32_t tx_count;
1c4d83
-        uint32_t other_count;
1c4d83
-        uint32_t combined_count;
1c4d83
-
1c4d83
-        bool rx_count_set;
1c4d83
-        bool tx_count_set;
1c4d83
-        bool other_count_set;
1c4d83
-        bool combined_count_set;
1c4d83
+        u32_opt rx;
1c4d83
+        u32_opt tx;
1c4d83
+        u32_opt other;
1c4d83
+        u32_opt combined;
1c4d83
 } netdev_channels;
1c4d83
 
1c4d83
 typedef struct netdev_ring_param {
1c4d83
-        /* For any of the 4 following settings, a value of 0 indicates the hardware advertised maximum should
1c4d83
-         * be used. */
1c4d83
-        uint32_t rx_pending;
1c4d83
-        uint32_t rx_mini_pending;
1c4d83
-        uint32_t rx_jumbo_pending;
1c4d83
-        uint32_t tx_pending;
1c4d83
-
1c4d83
-        bool rx_pending_set;
1c4d83
-        bool rx_mini_pending_set;
1c4d83
-        bool rx_jumbo_pending_set;
1c4d83
-        bool tx_pending_set;
1c4d83
+        u32_opt rx;
1c4d83
+        u32_opt rx_mini;
1c4d83
+        u32_opt rx_jumbo;
1c4d83
+        u32_opt tx;
1c4d83
 } netdev_ring_param;
1c4d83
 
1c4d83
 int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret);
1c4d83
@@ -111,6 +104,5 @@ enum ethtool_link_mode_bit_indices ethtool_link_mode_bit_from_string(const char
1c4d83
 CONFIG_PARSER_PROTOTYPE(config_parse_duplex);
1c4d83
 CONFIG_PARSER_PROTOTYPE(config_parse_wol);
1c4d83
 CONFIG_PARSER_PROTOTYPE(config_parse_port);
1c4d83
-CONFIG_PARSER_PROTOTYPE(config_parse_channel);
1c4d83
 CONFIG_PARSER_PROTOTYPE(config_parse_advertise);
1c4d83
-CONFIG_PARSER_PROTOTYPE(config_parse_nic_buffer_size);
1c4d83
+CONFIG_PARSER_PROTOTYPE(config_parse_ring_buffer_or_channel);
1c4d83
diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf
1c4d83
index e2f07d758b..d0190da5cb 100644
1c4d83
--- a/src/udev/net/link-config-gperf.gperf
1c4d83
+++ b/src/udev/net/link-config-gperf.gperf
1c4d83
@@ -58,15 +58,15 @@ Link.TCP6SegmentationOffload,          config_parse_tristate,                 0,
1c4d83
 Link.UDPSegmentationOffload,           config_parse_warn_compat,              DISABLED_LEGACY,               0
1c4d83
 Link.GenericReceiveOffload,            config_parse_tristate,                 0,                             offsetof(LinkConfig, features[NET_DEV_FEAT_GRO])
1c4d83
 Link.LargeReceiveOffload,              config_parse_tristate,                 0,                             offsetof(LinkConfig, features[NET_DEV_FEAT_LRO])
1c4d83
-Link.RxChannels,                       config_parse_channel,                  0,                             offsetof(LinkConfig, channels)
1c4d83
-Link.TxChannels,                       config_parse_channel,                  0,                             offsetof(LinkConfig, channels)
1c4d83
-Link.OtherChannels,                    config_parse_channel,                  0,                             offsetof(LinkConfig, channels)
1c4d83
-Link.CombinedChannels,                 config_parse_channel,                  0,                             offsetof(LinkConfig, channels)
1c4d83
+Link.RxChannels,                       config_parse_ring_buffer_or_channel,   0,                             offsetof(LinkConfig, channels.rx)
1c4d83
+Link.TxChannels,                       config_parse_ring_buffer_or_channel,   0,                             offsetof(LinkConfig, channels.tx)
1c4d83
+Link.OtherChannels,                    config_parse_ring_buffer_or_channel,   0,                             offsetof(LinkConfig, channels.other)
1c4d83
+Link.CombinedChannels,                 config_parse_ring_buffer_or_channel,   0,                             offsetof(LinkConfig, channels.combined)
1c4d83
 Link.Advertise,                        config_parse_advertise,                0,                             offsetof(LinkConfig, advertise)
1c4d83
-Link.RxBufferSize,                     config_parse_nic_buffer_size,          0,                             offsetof(LinkConfig, ring)
1c4d83
-Link.RxMiniBufferSize,                 config_parse_nic_buffer_size,          0,                             offsetof(LinkConfig, ring)
1c4d83
-Link.RxJumboBufferSize,                config_parse_nic_buffer_size,          0,                             offsetof(LinkConfig, ring)
1c4d83
-Link.TxBufferSize,                     config_parse_nic_buffer_size,          0,                             offsetof(LinkConfig, ring)
1c4d83
+Link.RxBufferSize,                     config_parse_ring_buffer_or_channel,   0,                             offsetof(LinkConfig, ring.rx)
1c4d83
+Link.RxMiniBufferSize,                 config_parse_ring_buffer_or_channel,   0,                             offsetof(LinkConfig, ring.rx_mini)
1c4d83
+Link.RxJumboBufferSize,                config_parse_ring_buffer_or_channel,   0,                             offsetof(LinkConfig, ring.rx_jumbo)
1c4d83
+Link.TxBufferSize,                     config_parse_ring_buffer_or_channel,   0,                             offsetof(LinkConfig, ring.tx)
1c4d83
 Link.RxFlowControl,                    config_parse_tristate,                 0,                             offsetof(LinkConfig, rx_flow_control)
1c4d83
 Link.TxFlowControl,                    config_parse_tristate,                 0,                             offsetof(LinkConfig, tx_flow_control)
1c4d83
 Link.AutoNegotiationFlowControl,       config_parse_tristate,                 0,                             offsetof(LinkConfig, autoneg_flow_control)
1c4d83
-- 
1c4d83
2.31.1
1c4d83