]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #4104 from ssahani/ethtool_xlink_settings
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 10 Nov 2016 22:09:44 +0000 (17:09 -0500)
committerGitHub <noreply@github.com>
Thu, 10 Nov 2016 22:09:44 +0000 (17:09 -0500)
Link: port to new ethtool ETHTOOL_xLINKSETTINGS

configure.ac
man/systemd.link.xml
src/basic/missing.h
src/udev/net/ethtool-util.c
src/udev/net/ethtool-util.h
src/udev/net/link-config-gperf.gperf
src/udev/net/link-config.c
src/udev/net/link-config.h

index 7f6b3b937c92cc4cdc777fd22ed30cc7a9ecce2e..6423204178ab7f11638bdafe0b2e267ecb65c772 100644 (file)
@@ -319,9 +319,10 @@ AC_CHECK_DECLS([
 #include <linux/random.h>
 ]])
 
-AC_CHECK_TYPES([char16_t, char32_t, key_serial_t],
+AC_CHECK_TYPES([char16_t, char32_t, key_serial_t, struct ethtool_link_settings],
                [], [], [[
 #include <uchar.h>
+#include <linux/ethtool.h>
 ]])
 
 AC_CHECK_DECLS([IFLA_INET6_ADDR_GEN_MODE,
index 8edbe758d94735b8f3d5f1d3e6ad8c8ad2794508..023e24eeb39469772f9f38529b3cf2c46dde31a0 100644 (file)
           <literal>full</literal>.</para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term><varname>AutoNegotiation=</varname></term>
+        <listitem>
+          <para>Enables or disables automatic negotiation of transmission parameters.
+          Autonegotiation is a procedure by which two connected ethernet devices choose
+          common transmission parameters, such as speed, duplex mode, and flow control.
+          Takes a boolean value. Unset by default, which means that the kernel default
+          will be used.</para>
+
+          <para>Note that if autonegotiation is enabled, speed and duplex settings are
+          read-only. If autonegotation is disabled, speed and duplex settings are writable
+          if the driver supports multiple link modes.</para>
+        </listitem>
+      </varlistentry>
       <varlistentry>
         <term><varname>WakeOnLan=</varname></term>
         <listitem>
index 4c013be608dfaea98a8543f490ee8812fd5ba3c2..a5ae5d9e79d078d2bee14dda9263d910cf6e8527 100644 (file)
@@ -1076,6 +1076,33 @@ typedef int32_t key_serial_t;
 #define IFA_F_MCAUTOJOIN 0x400
 #endif
 
+#ifndef HAVE_STRUCT_ETHTOOL_LINK_SETTINGS
+
+#define ETHTOOL_GLINKSETTINGS   0x0000004c /* Get ethtool_link_settings */
+#define ETHTOOL_SLINKSETTINGS   0x0000004d /* Set ethtool_link_settings */
+
+struct ethtool_link_settings {
+        __u32   cmd;
+        __u32   speed;
+        __u8    duplex;
+        __u8    port;
+        __u8    phy_address;
+        __u8    autoneg;
+        __u8    mdio_support;
+        __u8    eth_tp_mdix;
+        __u8    eth_tp_mdix_ctrl;
+        __s8    link_mode_masks_nwords;
+        __u32   reserved[8];
+        __u32   link_mode_masks[0];
+        /* layout of link_mode_masks fields:
+         * __u32 map_supported[link_mode_masks_nwords];
+         * __u32 map_advertising[link_mode_masks_nwords];
+         * __u32 map_lp_advertising[link_mode_masks_nwords];
+         */
+};
+
+#endif
+
 #endif
 
 #include "missing_syscall.h"
index 708a66557605dc7fb6ef2c994b3fa7f9874b9598..d7edbb396bc6ce7a0f23dd9a4e5058a8c397e1e7 100644 (file)
@@ -29,6 +29,7 @@
 #include "string-table.h"
 #include "strxcpyx.h"
 #include "util.h"
+#include "missing.h"
 
 static const char* const duplex_table[_DUP_MAX] = {
         [DUP_FULL] = "full",
@@ -323,3 +324,211 @@ int ethtool_set_features(int *fd, const char *ifname, NetDevFeature *features) {
 
         return 0;
 }
+
+static int get_glinksettings(int *fd, struct ifreq *ifr, struct ethtool_link_usettings **g) {
+        struct ecmd {
+                struct ethtool_link_settings req;
+                __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+        } ecmd = {
+                .req.cmd = ETHTOOL_GLINKSETTINGS,
+        };
+        struct ethtool_link_usettings *u;
+        unsigned offset;
+        int r;
+
+        /* 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;
+
+        r = ioctl(*fd, SIOCETHTOOL, ifr);
+        if (r < 0)
+                return -errno;
+
+        if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
+                return -ENOTSUP;
+
+        ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
+
+        ifr->ifr_data = (void *) &ecmd;
+
+        r = ioctl(*fd, SIOCETHTOOL, ifr);
+        if (r < 0)
+                return -errno;
+
+        if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
+                return -ENOTSUP;
+
+        u = new0(struct ethtool_link_usettings , 1);
+        if (!u)
+                return -ENOMEM;
+
+        memcpy(&u->base, &ecmd.req, sizeof(struct ethtool_link_settings));
+
+        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);
+
+        *g = u;
+
+        return 0;
+}
+
+static int get_gset(int *fd, struct ifreq *ifr, struct ethtool_link_usettings **u) {
+        struct ethtool_link_usettings *e;
+        struct ethtool_cmd ecmd = {
+                .cmd = ETHTOOL_GSET,
+        };
+        int r;
+
+        ifr->ifr_data = (void *) &ecmd;
+
+        r = ioctl(*fd, SIOCETHTOOL, ifr);
+        if (r < 0)
+                return -errno;
+
+        e = new0(struct ethtool_link_usettings, 1);
+        if (!e)
+                return -ENOMEM;
+
+        e->base.cmd = ETHTOOL_GSET;
+
+        e->base.link_mode_masks_nwords = 1;
+        e->base.speed = ethtool_cmd_speed(&ecmd);
+        e->base.duplex = ecmd.duplex;
+        e->base.port = ecmd.port;
+        e->base.phy_address = ecmd.phy_address;
+        e->base.autoneg = ecmd.autoneg;
+        e->base.mdio_support = ecmd.mdio_support;
+
+        e->link_modes.supported[0] = ecmd.supported;
+        e->link_modes.advertising[0] = ecmd.advertising;
+        e->link_modes.lp_advertising[0] = ecmd.lp_advertising;
+
+        *u = e;
+
+        return 0;
+}
+
+static int set_slinksettings(int *fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
+        struct {
+                struct ethtool_link_settings req;
+                __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+        } ecmd = {
+                .req.cmd = ETHTOOL_SLINKSETTINGS,
+        };
+        unsigned int offset;
+        int r;
+
+        if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0)
+                return -EINVAL;
+
+        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;
+
+        r = ioctl(*fd, SIOCETHTOOL, ifr);
+        if (r < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int set_sset(int *fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) {
+        struct ethtool_cmd ecmd = {
+                .cmd = ETHTOOL_SSET,
+        };
+        int r;
+
+        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;
+
+        ifr->ifr_data = (void *) &ecmd;
+
+        r = ioctl(*fd, SIOCETHTOOL, ifr);
+        if (r < 0)
+                return -errno;
+
+        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.
+ */
+
+int ethtool_set_glinksettings(int *fd, const char *ifname, unsigned int speed, Duplex duplex, int autonegotiation) {
+        _cleanup_free_ struct ethtool_link_usettings *u = NULL;
+        struct ifreq ifr = {};
+        int r;
+
+        if (autonegotiation != 0) {
+                log_info("link_config: autonegotiation is unset or enabled, the speed and duplex are not writable.");
+                return 0;
+        }
+
+        if (*fd < 0) {
+                r = ethtool_connect(fd);
+                if (r < 0)
+                        return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+        }
+
+        strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
+
+        r = get_glinksettings(fd, &ifr, &u);
+        if (r < 0) {
+
+                r = get_gset(fd, &ifr, &u);
+                if (r < 0)
+                        return log_warning_errno(r, "link_config: Cannot get device settings for %s : %m", ifname);
+        }
+
+        if (speed)
+                u->base.speed = speed;
+
+        if (duplex != _DUP_INVALID)
+                u->base.duplex = duplex;
+
+        u->base.autoneg = autonegotiation;
+
+        if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
+                r = set_slinksettings(fd, &ifr, u);
+        else
+                r = set_sset(fd, &ifr, u);
+
+        if (r < 0)
+                return log_warning_errno(r, "link_config: Cannot set device settings for %s : %m", ifname);
+
+        return r;
+}
index 074416465388eab7bae3910a54a8e4676bcb2ec7..75d6af396b0ab543c7ca0a29b929047b165977cb 100644 (file)
@@ -20,6 +20,9 @@
 ***/
 
 #include <macro.h>
+#include <linux/ethtool.h>
+
+#include "missing.h"
 
 /* we can't use DUPLEX_ prefix, as it
  * clashes with <linux/ethtool.h> */
@@ -48,12 +51,27 @@ typedef enum NetDevFeature {
         _NET_DEV_FEAT_INVALID = -1
 } NetDevFeature;
 
+
+#define ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32    (SCHAR_MAX)
+
+/* layout of the struct passed from/to userland */
+struct ethtool_link_usettings {
+        struct ethtool_link_settings base;
+
+        struct {
+                uint32_t supported[ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+                uint32_t advertising[ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+                uint32_t lp_advertising[ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+        } link_modes;
+};
+
 int ethtool_connect(int *ret);
 
 int ethtool_get_driver(int *fd, const char *ifname, char **ret);
 int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex);
 int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol);
 int ethtool_set_features(int *fd, const char *ifname, NetDevFeature *features);
+int ethtool_set_glinksettings(int *fd, const char *ifname, unsigned int speed, Duplex duplex, int autoneg);
 
 const char *duplex_to_string(Duplex d) _const_;
 Duplex duplex_from_string(const char *d) _pure_;
index f8b85cbd13ab5b767f6e98d3737a9fd52ea1b817..78e551df22c4851c10b94f9feafaa46dc63a9420 100644 (file)
@@ -34,6 +34,7 @@ Link.Alias,                      config_parse_ifalias,       0,
 Link.MTUBytes,                   config_parse_iec_size,      0,                             offsetof(link_config, mtu)
 Link.BitsPerSecond,              config_parse_si_size,       0,                             offsetof(link_config, speed)
 Link.Duplex,                     config_parse_duplex,        0,                             offsetof(link_config, duplex)
+Link.AutoNegotiation,            config_parse_tristate,      0,                             offsetof(link_config, autonegotiation)
 Link.WakeOnLan,                  config_parse_wol,           0,                             offsetof(link_config, wol)
 Link.GenericSegmentationOffload, config_parse_tristate,      0,                             offsetof(link_config, features[NET_DEV_FEAT_GSO])
 Link.TCPSegmentationOffload,     config_parse_tristate,      0,                             offsetof(link_config, features[NET_DEV_FEAT_TSO])
index ece9248c2ab4073c4722a58628571df88453209e..4578d0d9b2d9b2171e2ea74e4c9f8aa07b542e22 100644 (file)
@@ -167,6 +167,7 @@ static int load_link(link_config_ctx *ctx, const char *filename) {
         link->mac_policy = _MACPOLICY_INVALID;
         link->wol = _WOL_INVALID;
         link->duplex = _DUP_INVALID;
+        link->autonegotiation = -1;
 
         memset(&link->features, -1, _NET_DEV_FEAT_MAX);
 
@@ -202,9 +203,9 @@ static bool enable_name_policy(void) {
 }
 
 int link_config_load(link_config_ctx *ctx) {
-        int r;
         _cleanup_strv_free_ char **files;
         char **f;
+        int r;
 
         link_configs_free(ctx);
 
@@ -364,11 +365,12 @@ static int get_mac(struct udev_device *device, bool want_random,
 
 int link_config_apply(link_config_ctx *ctx, link_config *config,
                       struct udev_device *device, const char **name) {
-        const char *old_name;
-        const char *new_name = NULL;
+        bool respect_predictable = false;
         struct ether_addr generated_mac;
         struct ether_addr *mac = NULL;
-        bool respect_predictable = false;
+        const char *new_name = NULL;
+        const char *old_name;
+        unsigned speed;
         int r, ifindex;
 
         assert(ctx);
@@ -380,11 +382,19 @@ int link_config_apply(link_config_ctx *ctx, link_config *config,
         if (!old_name)
                 return -EINVAL;
 
-        r = ethtool_set_speed(&ctx->ethtool_fd, old_name, config->speed / 1024, config->duplex);
-        if (r < 0)
-                log_warning_errno(r, "Could not set speed or duplex of %s to %zu Mbps (%s): %m",
-                                  old_name, config->speed / 1024,
-                                  duplex_to_string(config->duplex));
+
+        speed = DIV_ROUND_UP(config->speed, 1000000);
+
+        r = ethtool_set_glinksettings(&ctx->ethtool_fd, old_name, speed, config->duplex, config->autonegotiation);
+        if (r < 0) {
+
+                if (r == -EOPNOTSUPP)
+                        r = ethtool_set_speed(&ctx->ethtool_fd, old_name, speed, config->duplex);
+
+                if (r < 0)
+                        log_warning_errno(r, "Could not set speed or duplex of %s to %u Mbps (%s): %m",
+                                          old_name, speed, duplex_to_string(config->duplex));
+        }
 
         r = ethtool_set_wol(&ctx->ethtool_fd, old_name, config->wol);
         if (r < 0)
index 91cc0357c417260b8a5b6091d4ae5527f51c6cc8..a99060d943f80c6eb2839a506661c8579dab3882 100644 (file)
@@ -69,6 +69,7 @@ struct link_config {
         size_t mtu;
         size_t speed;
         Duplex duplex;
+        int autonegotiation;
         WakeOnLan wol;
         NetDevFeature features[_NET_DEV_FEAT_MAX];