mirror of
https://github.com/systemd/systemd.git
synced 2025-03-25 18:50:18 +03:00
Link: port to new ethtool ETHTOOL_xLINKSETTINGS
Link: port to new ethtool ETHTOOL_xLINKSETTINGS
This patch defines a new ETHTOOL_GLINKSETTINGS/SLINKSETTINGS API,
handled by the new get_link_ksettings/set_link_ksettings .
This is a WIP version based on this [kernel
patch](https://patchwork.kernel.org/patch/8411401/).
commit 0527f1c
3f1ac7a700
ommit
35afb33
This commit is contained in:
parent
843d5baf6a
commit
a39f92d391
@ -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,
|
||||
|
@ -358,6 +358,20 @@
|
||||
<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>
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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])
|
||||
|
@ -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)
|
||||
|
@ -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];
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user