提交 1b3b289f 编写于 作者: D David S. Miller

Merge branch 'ethtool-netlink-part-one'

Michal Kubecek says:

====================
ethtool netlink interface, part 1

This is first part of netlink based alternative userspace interface for
ethtool. It aims to address some long known issues with the ioctl
interface, mainly lack of extensibility, raciness, limited error reporting
and absence of notifications. The goal is to allow userspace ethtool
utility to provide all features it currently does but without using the
ioctl interface. However, some features provided by ethtool ioctl API will
be available through other netlink interfaces (rtnetlink, devlink) if it's
more appropriate.

The interface uses generic netlink family "ethtool" and provides multicast
group "monitor" which is used for notifications. Documentation for the
interface is in Documentation/networking/ethtool-netlink.rst file. The
netlink interface is optional, it is built when CONFIG_ETHTOOL_NETLINK
(bool) option is enabled.

There are three types of request messages distinguished by suffix "_GET"
(query for information), "_SET" (modify parameters) and "_ACT" (perform an
action). Kernel reply messages have name with additional suffix "_REPLY"
(e.g. ETHTOOL_MSG_SETTINGS_GET_REPLY). Most "_SET" and "_ACT" message types
do not have matching reply type as only some of them need additional reply
data beyond numeric error code and extack. Kernel also broadcasts
notification messages ("_NTF" suffix) on changes.

Basic concepts:

- make extensions easier not only by allowing new attributes but also by
  imposing as few artificial limits as possible, e.g. by using arbitrary
  size bit sets for most bitmap attributes or by not using fixed size
  strings
- use extack for error reporting and warnings
- send netlink notifications on changes (even if they were done using the
  ioctl interface) and actions
- avoid the racy read/modify/write cycle between kernel and userspace by
  sending only attributes which userspace wants to change; there is still
  a read/modify/write cycle between generic kernel code and ethtool_ops
  handler in NIC driver but it is only in kernel and under RTNL lock
- reduce the number of name lists that need to be kept in sync between
  kernel and userspace (e.g. recognized link modes)
- where feasible, allow dump requests to query specific information for all
  network devices
- as parsing and generating netlink messages is more complicated than
  simply copying data structures between userspace API and ethtool_ops
  handlers (which most ioctl commands do), split the code into multiple
  files in net/ethtool directory; move net/core/ethtool.c also to this
  directory and rename it to ioctl.c

Changes between v8 and v9:

- fix ethnl_update_u8()
- fix description of ETHTOOL_A_LINKSTATE_LINK in rst file
- add explanation of verbose vs. compact bitset usage to documentation
- link ethtool-netlink.rst into toctree

Main changes between v7 and v8:

- preliminary patches sent as a separate series (already in net-next)
- split notification related changes out of _SET patches
- drop request specific flags from common header
- use FLAG/flag rather than GFLAG/gflag for global flags (as there are
  only global flags now)
- allow device names up to ALTIFNAMSIZ characters
- rename ETHTOOL_A_BITSET_LIST to ETHTOOL_A_BITSET_NOMASK
- rename ETHTOOL_A_BIT{,S}_* to ETHTOOL_A_BITSET_BIT{,S}_*
- use standard bitset helpers for link modes (rather than in-place
  conversion)
- use "default" rather than "standard" for unified _GET handlers
- fixed 64-bit big endian bitset code

Main changes between v6 and v7:

- split complex messages into small single purpose ones (drop info and
  request masks and one level of nesting)
- separate request information and reply data into two structures
- refactor bitset handling (no simultaneous u32/ulong handling but avoid
  kmalloc() except for long bitmaps on 64-bit big endian architectures)
- use only fixed size strings internally (will be replaced by char *
  eventually but that will require rewriting also existing ioctl code)
- rework ethnl_update_* helpers to return error code
- rename request flag constants (to ETHTOOL_[GR]FLAG_ prefix)
- convert documentation to rst

Main changes between v5 and v6:

- use ETHTOOL_MSG_ prefix for message types
- replace ETHA_ prefix for netlink attributes by ETHTOOL_A_
- replace ETH_x_IM_y for infomask bits by ETHTOOL_IM_x_y
- split GET reply types from SET requests and notifications
- split kernel and userspace message types into different enums
- remove INFO_GET requests from submitted part
- drop EVENT notifications (use rtnetlink and on-demand string set load)
- reorganize patches to reduce the number of intermitent warnings
- unify request/reply header and its processing
- another nest around strings in a string set for consistency
- more consistent identifier naming
- coding style cleanup
- get rid of some of the helpers
- set bad attribute in extack where applicable
- various bug fixes
- improve documentation and code comments, more kerneldoc comments
- more verbose commit messages

Changes between v4 and v5:

- do not panic on failed initialization, only WARN()

Main changes between RFC v3 and v4:

- use more kerneldoc style comments
- strict attribute policy checking
- use macros for tables of link mode names and parameters
- provide permanent hardware address in rtnetlink
- coding style cleanup
- split too long patches, reorder
- wrap more ETHA_SETTINGS_* attributes in nests
- add also some SET_* implementation into submitted part

Main changes between RFC v2 and RFC v3:

- do not allow building as a module (no netdev notifiers needed)
- drop some obsolete fields
- add permanent hw address, timestamping and private flags support
- rework bitset handling to get rid of variable length arrays
- notify monitor on device renames
- restructure GET_SETTINGS/SET_SETTINGS messages
- split too long patches and submit only first part of the series

Main changes between RFC v1 and RFC v2:

- support dumps for all "get" requests
- provide notifications for changes related to supported request types
- support getting string sets (both global and per device)
- support getting/setting device features
- get rid of family specific header, everything passed as attributes
- split netlink code into multiple files in net/ethtool/ directory
====================
Signed-off-by: NDavid S. Miller <davem@davemloft.net>
此差异已折叠。
......@@ -16,6 +16,7 @@ Contents:
devlink-info-versions
devlink-trap
devlink-trap-netdevsim
ethtool-netlink
ieee802154
j1939
kapi
......
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _LINUX_ETHTOOL_NETLINK_H_
#define _LINUX_ETHTOOL_NETLINK_H_
#include <uapi/linux/ethtool_netlink.h>
#include <linux/ethtool.h>
#include <linux/netdevice.h>
#define __ETHTOOL_LINK_MODE_MASK_NWORDS \
DIV_ROUND_UP(__ETHTOOL_LINK_MODE_MASK_NBITS, 32)
enum ethtool_multicast_groups {
ETHNL_MCGRP_MONITOR,
};
#endif /* _LINUX_ETHTOOL_NETLINK_H_ */
......@@ -4393,6 +4393,15 @@ struct netdev_notifier_bonding_info {
void netdev_bonding_info_change(struct net_device *dev,
struct netdev_bonding_info *bonding_info);
#if IS_ENABLED(CONFIG_ETHTOOL_NETLINK)
void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data);
#else
static inline void ethtool_notify(struct net_device *dev, unsigned int cmd,
const void *data)
{
}
#endif
static inline
struct sk_buff *skb_gso_segment(struct sk_buff *skb, netdev_features_t features)
{
......
......@@ -606,6 +606,9 @@ enum ethtool_stringset {
ETH_SS_PHY_STATS,
ETH_SS_PHY_TUNABLES,
ETH_SS_LINK_MODES,
/* add new constants above here */
ETH_SS_COUNT
};
/**
......
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
/*
* include/uapi/linux/ethtool_netlink.h - netlink interface for ethtool
*
* See Documentation/networking/ethtool-netlink.txt in kernel source tree for
* doucumentation of the interface.
*/
#ifndef _UAPI_LINUX_ETHTOOL_NETLINK_H_
#define _UAPI_LINUX_ETHTOOL_NETLINK_H_
#include <linux/ethtool.h>
/* message types - userspace to kernel */
enum {
ETHTOOL_MSG_USER_NONE,
ETHTOOL_MSG_STRSET_GET,
ETHTOOL_MSG_LINKINFO_GET,
ETHTOOL_MSG_LINKINFO_SET,
ETHTOOL_MSG_LINKMODES_GET,
ETHTOOL_MSG_LINKMODES_SET,
ETHTOOL_MSG_LINKSTATE_GET,
/* add new constants above here */
__ETHTOOL_MSG_USER_CNT,
ETHTOOL_MSG_USER_MAX = __ETHTOOL_MSG_USER_CNT - 1
};
/* message types - kernel to userspace */
enum {
ETHTOOL_MSG_KERNEL_NONE,
ETHTOOL_MSG_STRSET_GET_REPLY,
ETHTOOL_MSG_LINKINFO_GET_REPLY,
ETHTOOL_MSG_LINKINFO_NTF,
ETHTOOL_MSG_LINKMODES_GET_REPLY,
ETHTOOL_MSG_LINKMODES_NTF,
ETHTOOL_MSG_LINKSTATE_GET_REPLY,
/* add new constants above here */
__ETHTOOL_MSG_KERNEL_CNT,
ETHTOOL_MSG_KERNEL_MAX = __ETHTOOL_MSG_KERNEL_CNT - 1
};
/* request header */
/* use compact bitsets in reply */
#define ETHTOOL_FLAG_COMPACT_BITSETS (1 << 0)
/* provide optional reply for SET or ACT requests */
#define ETHTOOL_FLAG_OMIT_REPLY (1 << 1)
#define ETHTOOL_FLAG_ALL (ETHTOOL_FLAG_COMPACT_BITSETS | \
ETHTOOL_FLAG_OMIT_REPLY)
enum {
ETHTOOL_A_HEADER_UNSPEC,
ETHTOOL_A_HEADER_DEV_INDEX, /* u32 */
ETHTOOL_A_HEADER_DEV_NAME, /* string */
ETHTOOL_A_HEADER_FLAGS, /* u32 - ETHTOOL_FLAG_* */
/* add new constants above here */
__ETHTOOL_A_HEADER_CNT,
ETHTOOL_A_HEADER_MAX = __ETHTOOL_A_HEADER_CNT - 1
};
/* bit sets */
enum {
ETHTOOL_A_BITSET_BIT_UNSPEC,
ETHTOOL_A_BITSET_BIT_INDEX, /* u32 */
ETHTOOL_A_BITSET_BIT_NAME, /* string */
ETHTOOL_A_BITSET_BIT_VALUE, /* flag */
/* add new constants above here */
__ETHTOOL_A_BITSET_BIT_CNT,
ETHTOOL_A_BITSET_BIT_MAX = __ETHTOOL_A_BITSET_BIT_CNT - 1
};
enum {
ETHTOOL_A_BITSET_BITS_UNSPEC,
ETHTOOL_A_BITSET_BITS_BIT, /* nest - _A_BITSET_BIT_* */
/* add new constants above here */
__ETHTOOL_A_BITSET_BITS_CNT,
ETHTOOL_A_BITSET_BITS_MAX = __ETHTOOL_A_BITSET_BITS_CNT - 1
};
enum {
ETHTOOL_A_BITSET_UNSPEC,
ETHTOOL_A_BITSET_NOMASK, /* flag */
ETHTOOL_A_BITSET_SIZE, /* u32 */
ETHTOOL_A_BITSET_BITS, /* nest - _A_BITSET_BITS_* */
ETHTOOL_A_BITSET_VALUE, /* binary */
ETHTOOL_A_BITSET_MASK, /* binary */
/* add new constants above here */
__ETHTOOL_A_BITSET_CNT,
ETHTOOL_A_BITSET_MAX = __ETHTOOL_A_BITSET_CNT - 1
};
/* string sets */
enum {
ETHTOOL_A_STRING_UNSPEC,
ETHTOOL_A_STRING_INDEX, /* u32 */
ETHTOOL_A_STRING_VALUE, /* string */
/* add new constants above here */
__ETHTOOL_A_STRING_CNT,
ETHTOOL_A_STRING_MAX = __ETHTOOL_A_STRING_CNT - 1
};
enum {
ETHTOOL_A_STRINGS_UNSPEC,
ETHTOOL_A_STRINGS_STRING, /* nest - _A_STRINGS_* */
/* add new constants above here */
__ETHTOOL_A_STRINGS_CNT,
ETHTOOL_A_STRINGS_MAX = __ETHTOOL_A_STRINGS_CNT - 1
};
enum {
ETHTOOL_A_STRINGSET_UNSPEC,
ETHTOOL_A_STRINGSET_ID, /* u32 */
ETHTOOL_A_STRINGSET_COUNT, /* u32 */
ETHTOOL_A_STRINGSET_STRINGS, /* nest - _A_STRINGS_* */
/* add new constants above here */
__ETHTOOL_A_STRINGSET_CNT,
ETHTOOL_A_STRINGSET_MAX = __ETHTOOL_A_STRINGSET_CNT - 1
};
enum {
ETHTOOL_A_STRINGSETS_UNSPEC,
ETHTOOL_A_STRINGSETS_STRINGSET, /* nest - _A_STRINGSET_* */
/* add new constants above here */
__ETHTOOL_A_STRINGSETS_CNT,
ETHTOOL_A_STRINGSETS_MAX = __ETHTOOL_A_STRINGSETS_CNT - 1
};
/* STRSET */
enum {
ETHTOOL_A_STRSET_UNSPEC,
ETHTOOL_A_STRSET_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_STRSET_STRINGSETS, /* nest - _A_STRINGSETS_* */
ETHTOOL_A_STRSET_COUNTS_ONLY, /* flag */
/* add new constants above here */
__ETHTOOL_A_STRSET_CNT,
ETHTOOL_A_STRSET_MAX = __ETHTOOL_A_STRSET_CNT - 1
};
/* LINKINFO */
enum {
ETHTOOL_A_LINKINFO_UNSPEC,
ETHTOOL_A_LINKINFO_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_LINKINFO_PORT, /* u8 */
ETHTOOL_A_LINKINFO_PHYADDR, /* u8 */
ETHTOOL_A_LINKINFO_TP_MDIX, /* u8 */
ETHTOOL_A_LINKINFO_TP_MDIX_CTRL, /* u8 */
ETHTOOL_A_LINKINFO_TRANSCEIVER, /* u8 */
/* add new constants above here */
__ETHTOOL_A_LINKINFO_CNT,
ETHTOOL_A_LINKINFO_MAX = __ETHTOOL_A_LINKINFO_CNT - 1
};
/* LINKMODES */
enum {
ETHTOOL_A_LINKMODES_UNSPEC,
ETHTOOL_A_LINKMODES_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_LINKMODES_AUTONEG, /* u8 */
ETHTOOL_A_LINKMODES_OURS, /* bitset */
ETHTOOL_A_LINKMODES_PEER, /* bitset */
ETHTOOL_A_LINKMODES_SPEED, /* u32 */
ETHTOOL_A_LINKMODES_DUPLEX, /* u8 */
/* add new constants above here */
__ETHTOOL_A_LINKMODES_CNT,
ETHTOOL_A_LINKMODES_MAX = __ETHTOOL_A_LINKMODES_CNT - 1
};
/* LINKSTATE */
enum {
ETHTOOL_A_LINKSTATE_UNSPEC,
ETHTOOL_A_LINKSTATE_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_LINKSTATE_LINK, /* u8 */
/* add new constants above here */
__ETHTOOL_A_LINKSTATE_CNT,
ETHTOOL_A_LINKSTATE_MAX = __ETHTOOL_A_LINKSTATE_CNT - 1
};
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1
#define ETHTOOL_MCGRP_MONITOR_NAME "monitor"
#endif /* _UAPI_LINUX_ETHTOOL_NETLINK_H_ */
......@@ -449,6 +449,14 @@ config FAILOVER
migration of VMs with direct attached VFs by failing over to the
paravirtual datapath when the VF is unplugged.
config ETHTOOL_NETLINK
bool "Netlink interface for ethtool"
default y
help
An alternative userspace interface for ethtool based on generic
netlink. It provides better extensibility and some new features,
e.g. notification messages.
endif # if NET
# Used by archs to tell that they support BPF JIT compiler plus which flavour.
......
# SPDX-License-Identifier: GPL-2.0-only
obj-y += ioctl.o common.o
obj-y += ioctl.o common.o
obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o
ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o \
linkstate.o
此差异已折叠。
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _NET_ETHTOOL_BITSET_H
#define _NET_ETHTOOL_BITSET_H
typedef const char (*const ethnl_string_array_t)[ETH_GSTRING_LEN];
int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact);
int ethnl_bitset_size(const unsigned long *val, const unsigned long *mask,
unsigned int nbits, ethnl_string_array_t names,
bool compact);
int ethnl_bitset32_size(const u32 *val, const u32 *mask, unsigned int nbits,
ethnl_string_array_t names, bool compact);
int ethnl_put_bitset(struct sk_buff *skb, int attrtype,
const unsigned long *val, const unsigned long *mask,
unsigned int nbits, ethnl_string_array_t names,
bool compact);
int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, const u32 *val,
const u32 *mask, unsigned int nbits,
ethnl_string_array_t names, bool compact);
int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
const struct nlattr *attr, ethnl_string_array_t names,
struct netlink_ext_ack *extack, bool *mod);
int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits,
const struct nlattr *attr, ethnl_string_array_t names,
struct netlink_ext_ack *extack, bool *mod);
#endif /* _NET_ETHTOOL_BITSET_H */
......@@ -169,3 +169,59 @@ const char link_mode_names[][ETH_GSTRING_LEN] = {
__DEFINE_LINK_MODE_NAME(400000, CR8, Full),
};
static_assert(ARRAY_SIZE(link_mode_names) == __ETHTOOL_LINK_MODE_MASK_NBITS);
/* return false if legacy contained non-0 deprecated fields
* maxtxpkt/maxrxpkt. rest of ksettings always updated
*/
bool
convert_legacy_settings_to_link_ksettings(
struct ethtool_link_ksettings *link_ksettings,
const struct ethtool_cmd *legacy_settings)
{
bool retval = true;
memset(link_ksettings, 0, sizeof(*link_ksettings));
/* This is used to tell users that driver is still using these
* deprecated legacy fields, and they should not use
* %ETHTOOL_GLINKSETTINGS/%ETHTOOL_SLINKSETTINGS
*/
if (legacy_settings->maxtxpkt ||
legacy_settings->maxrxpkt)
retval = false;
ethtool_convert_legacy_u32_to_link_mode(
link_ksettings->link_modes.supported,
legacy_settings->supported);
ethtool_convert_legacy_u32_to_link_mode(
link_ksettings->link_modes.advertising,
legacy_settings->advertising);
ethtool_convert_legacy_u32_to_link_mode(
link_ksettings->link_modes.lp_advertising,
legacy_settings->lp_advertising);
link_ksettings->base.speed
= ethtool_cmd_speed(legacy_settings);
link_ksettings->base.duplex
= legacy_settings->duplex;
link_ksettings->base.port
= legacy_settings->port;
link_ksettings->base.phy_address
= legacy_settings->phy_address;
link_ksettings->base.autoneg
= legacy_settings->autoneg;
link_ksettings->base.mdio_support
= legacy_settings->mdio_support;
link_ksettings->base.eth_tp_mdix
= legacy_settings->eth_tp_mdix;
link_ksettings->base.eth_tp_mdix_ctrl
= legacy_settings->eth_tp_mdix_ctrl;
return retval;
}
int __ethtool_get_link(struct net_device *dev)
{
if (!dev->ethtool_ops->get_link)
return -EOPNOTSUPP;
return netif_running(dev) && dev->ethtool_ops->get_link(dev);
}
......@@ -3,6 +3,7 @@
#ifndef _ETHTOOL_COMMON_H
#define _ETHTOOL_COMMON_H
#include <linux/netdevice.h>
#include <linux/ethtool.h>
/* compose link mode index from speed, type and duplex */
......@@ -19,4 +20,10 @@ extern const char
phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
extern const char link_mode_names[][ETH_GSTRING_LEN];
int __ethtool_get_link(struct net_device *dev);
bool convert_legacy_settings_to_link_ksettings(
struct ethtool_link_ksettings *link_ksettings,
const struct ethtool_cmd *legacy_settings);
#endif /* _ETHTOOL_COMMON_H */
......@@ -26,6 +26,7 @@
#include <net/devlink.h>
#include <net/xdp_sock.h>
#include <net/flow_offload.h>
#include <linux/ethtool_netlink.h>
#include "common.h"
......@@ -358,54 +359,6 @@ bool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32,
}
EXPORT_SYMBOL(ethtool_convert_link_mode_to_legacy_u32);
/* return false if legacy contained non-0 deprecated fields
* maxtxpkt/maxrxpkt. rest of ksettings always updated
*/
static bool
convert_legacy_settings_to_link_ksettings(
struct ethtool_link_ksettings *link_ksettings,
const struct ethtool_cmd *legacy_settings)
{
bool retval = true;
memset(link_ksettings, 0, sizeof(*link_ksettings));
/* This is used to tell users that driver is still using these
* deprecated legacy fields, and they should not use
* %ETHTOOL_GLINKSETTINGS/%ETHTOOL_SLINKSETTINGS
*/
if (legacy_settings->maxtxpkt ||
legacy_settings->maxrxpkt)
retval = false;
ethtool_convert_legacy_u32_to_link_mode(
link_ksettings->link_modes.supported,
legacy_settings->supported);
ethtool_convert_legacy_u32_to_link_mode(
link_ksettings->link_modes.advertising,
legacy_settings->advertising);
ethtool_convert_legacy_u32_to_link_mode(
link_ksettings->link_modes.lp_advertising,
legacy_settings->lp_advertising);
link_ksettings->base.speed
= ethtool_cmd_speed(legacy_settings);
link_ksettings->base.duplex
= legacy_settings->duplex;
link_ksettings->base.port
= legacy_settings->port;
link_ksettings->base.phy_address
= legacy_settings->phy_address;
link_ksettings->base.autoneg
= legacy_settings->autoneg;
link_ksettings->base.mdio_support
= legacy_settings->mdio_support;
link_ksettings->base.eth_tp_mdix
= legacy_settings->eth_tp_mdix;
link_ksettings->base.eth_tp_mdix_ctrl
= legacy_settings->eth_tp_mdix_ctrl;
return retval;
}
/* return false if ksettings link modes had higher bits
* set. legacy_settings always updated (best effort)
*/
......@@ -619,7 +572,12 @@ static int ethtool_set_link_ksettings(struct net_device *dev,
!= link_ksettings.base.link_mode_masks_nwords)
return -EINVAL;
return dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
err = dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
if (err >= 0) {
ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF, NULL);
}
return err;
}
/* Query device for its ethtool_cmd settings.
......@@ -668,6 +626,7 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
{
struct ethtool_link_ksettings link_ksettings;
struct ethtool_cmd cmd;
int ret;
ASSERT_RTNL();
......@@ -680,7 +639,12 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
return -EINVAL;
link_ksettings.base.link_mode_masks_nwords =
__ETHTOOL_LINK_MODE_MASK_NU32;
return dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
ret = dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
if (ret >= 0) {
ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF, NULL);
}
return ret;
}
static noinline_for_stack int ethtool_get_drvinfo(struct net_device *dev,
......@@ -1401,12 +1365,12 @@ static int ethtool_nway_reset(struct net_device *dev)
static int ethtool_get_link(struct net_device *dev, char __user *useraddr)
{
struct ethtool_value edata = { .cmd = ETHTOOL_GLINK };
int link = __ethtool_get_link(dev);
if (!dev->ethtool_ops->get_link)
return -EOPNOTSUPP;
edata.data = netif_running(dev) && dev->ethtool_ops->get_link(dev);
if (link < 0)
return link;
edata.data = link;
if (copy_to_user(useraddr, &edata, sizeof(edata)))
return -EFAULT;
return 0;
......
// SPDX-License-Identifier: GPL-2.0-only
#include "netlink.h"
#include "common.h"
struct linkinfo_req_info {
struct ethnl_req_info base;
};
struct linkinfo_reply_data {
struct ethnl_reply_data base;
struct ethtool_link_ksettings ksettings;
struct ethtool_link_settings *lsettings;
};
#define LINKINFO_REPDATA(__reply_base) \
container_of(__reply_base, struct linkinfo_reply_data, base)
static const struct nla_policy
linkinfo_get_policy[ETHTOOL_A_LINKINFO_MAX + 1] = {
[ETHTOOL_A_LINKINFO_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKINFO_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_LINKINFO_PORT] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKINFO_PHYADDR] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKINFO_TP_MDIX] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKINFO_TRANSCEIVER] = { .type = NLA_REJECT },
};
static int linkinfo_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
struct genl_info *info)
{
struct linkinfo_reply_data *data = LINKINFO_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
int ret;
data->lsettings = &data->ksettings.base;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
ret = __ethtool_get_link_ksettings(dev, &data->ksettings);
if (ret < 0 && info)
GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
ethnl_ops_complete(dev);
return ret;
}
static int linkinfo_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
return nla_total_size(sizeof(u8)) /* LINKINFO_PORT */
+ nla_total_size(sizeof(u8)) /* LINKINFO_PHYADDR */
+ nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX */
+ nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX_CTRL */
+ nla_total_size(sizeof(u8)) /* LINKINFO_TRANSCEIVER */
+ 0;
}
static int linkinfo_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct linkinfo_reply_data *data = LINKINFO_REPDATA(reply_base);
if (nla_put_u8(skb, ETHTOOL_A_LINKINFO_PORT, data->lsettings->port) ||
nla_put_u8(skb, ETHTOOL_A_LINKINFO_PHYADDR,
data->lsettings->phy_address) ||
nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX,
data->lsettings->eth_tp_mdix) ||
nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX_CTRL,
data->lsettings->eth_tp_mdix_ctrl) ||
nla_put_u8(skb, ETHTOOL_A_LINKINFO_TRANSCEIVER,
data->lsettings->transceiver))
return -EMSGSIZE;
return 0;
}
const struct ethnl_request_ops ethnl_linkinfo_request_ops = {
.request_cmd = ETHTOOL_MSG_LINKINFO_GET,
.reply_cmd = ETHTOOL_MSG_LINKINFO_GET_REPLY,
.hdr_attr = ETHTOOL_A_LINKINFO_HEADER,
.max_attr = ETHTOOL_A_LINKINFO_MAX,
.req_info_size = sizeof(struct linkinfo_req_info),
.reply_data_size = sizeof(struct linkinfo_reply_data),
.request_policy = linkinfo_get_policy,
.prepare_data = linkinfo_prepare_data,
.reply_size = linkinfo_reply_size,
.fill_reply = linkinfo_fill_reply,
};
/* LINKINFO_SET */
static const struct nla_policy
linkinfo_set_policy[ETHTOOL_A_LINKINFO_MAX + 1] = {
[ETHTOOL_A_LINKINFO_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKINFO_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_LINKINFO_PORT] = { .type = NLA_U8 },
[ETHTOOL_A_LINKINFO_PHYADDR] = { .type = NLA_U8 },
[ETHTOOL_A_LINKINFO_TP_MDIX] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL] = { .type = NLA_U8 },
[ETHTOOL_A_LINKINFO_TRANSCEIVER] = { .type = NLA_REJECT },
};
int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *tb[ETHTOOL_A_LINKINFO_MAX + 1];
struct ethtool_link_ksettings ksettings = {};
struct ethtool_link_settings *lsettings;
struct ethnl_req_info req_info = {};
struct net_device *dev;
bool mod = false;
int ret;
ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
ETHTOOL_A_LINKINFO_MAX, linkinfo_set_policy,
info->extack);
if (ret < 0)
return ret;
ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKINFO_HEADER],
genl_info_net(info), info->extack, true);
if (ret < 0)
return ret;
dev = req_info.dev;
if (!dev->ethtool_ops->get_link_ksettings ||
!dev->ethtool_ops->set_link_ksettings)
return -EOPNOTSUPP;
rtnl_lock();
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
ret = __ethtool_get_link_ksettings(dev, &ksettings);
if (ret < 0) {
if (info)
GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
goto out_ops;
}
lsettings = &ksettings.base;
ethnl_update_u8(&lsettings->port, tb[ETHTOOL_A_LINKINFO_PORT], &mod);
ethnl_update_u8(&lsettings->phy_address, tb[ETHTOOL_A_LINKINFO_PHYADDR],
&mod);
ethnl_update_u8(&lsettings->eth_tp_mdix_ctrl,
tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL], &mod);
ret = 0;
if (!mod)
goto out_ops;
ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings);
if (ret < 0)
GENL_SET_ERR_MSG(info, "link settings update failed");
else
ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
out_ops:
ethnl_ops_complete(dev);
out_rtnl:
rtnl_unlock();
dev_put(dev);
return ret;
}
// SPDX-License-Identifier: GPL-2.0-only
#include "netlink.h"
#include "common.h"
#include "bitset.h"
struct linkmodes_req_info {
struct ethnl_req_info base;
};
struct linkmodes_reply_data {
struct ethnl_reply_data base;
struct ethtool_link_ksettings ksettings;
struct ethtool_link_settings *lsettings;
bool peer_empty;
};
#define LINKMODES_REPDATA(__reply_base) \
container_of(__reply_base, struct linkmodes_reply_data, base)
static const struct nla_policy
linkmodes_get_policy[ETHTOOL_A_LINKMODES_MAX + 1] = {
[ETHTOOL_A_LINKMODES_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKMODES_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_LINKMODES_AUTONEG] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKMODES_OURS] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKMODES_PEER] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKMODES_SPEED] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKMODES_DUPLEX] = { .type = NLA_REJECT },
};
static int linkmodes_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
struct genl_info *info)
{
struct linkmodes_reply_data *data = LINKMODES_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
int ret;
data->lsettings = &data->ksettings.base;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
ret = __ethtool_get_link_ksettings(dev, &data->ksettings);
if (ret < 0 && info) {
GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
goto out;
}
data->peer_empty =
bitmap_empty(data->ksettings.link_modes.lp_advertising,
__ETHTOOL_LINK_MODE_MASK_NBITS);
out:
ethnl_ops_complete(dev);
return ret;
}
static int linkmodes_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct linkmodes_reply_data *data = LINKMODES_REPDATA(reply_base);
const struct ethtool_link_ksettings *ksettings = &data->ksettings;
bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
int len, ret;
len = nla_total_size(sizeof(u8)) /* LINKMODES_AUTONEG */
+ nla_total_size(sizeof(u32)) /* LINKMODES_SPEED */
+ nla_total_size(sizeof(u8)) /* LINKMODES_DUPLEX */
+ 0;
ret = ethnl_bitset_size(ksettings->link_modes.advertising,
ksettings->link_modes.supported,
__ETHTOOL_LINK_MODE_MASK_NBITS,
link_mode_names, compact);
if (ret < 0)
return ret;
len += ret;
if (!data->peer_empty) {
ret = ethnl_bitset_size(ksettings->link_modes.lp_advertising,
NULL, __ETHTOOL_LINK_MODE_MASK_NBITS,
link_mode_names, compact);
if (ret < 0)
return ret;
len += ret;
}
return len;
}
static int linkmodes_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct linkmodes_reply_data *data = LINKMODES_REPDATA(reply_base);
const struct ethtool_link_ksettings *ksettings = &data->ksettings;
const struct ethtool_link_settings *lsettings = &ksettings->base;
bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
int ret;
if (nla_put_u8(skb, ETHTOOL_A_LINKMODES_AUTONEG, lsettings->autoneg))
return -EMSGSIZE;
ret = ethnl_put_bitset(skb, ETHTOOL_A_LINKMODES_OURS,
ksettings->link_modes.advertising,
ksettings->link_modes.supported,
__ETHTOOL_LINK_MODE_MASK_NBITS, link_mode_names,
compact);
if (ret < 0)
return -EMSGSIZE;
if (!data->peer_empty) {
ret = ethnl_put_bitset(skb, ETHTOOL_A_LINKMODES_PEER,
ksettings->link_modes.lp_advertising,
NULL, __ETHTOOL_LINK_MODE_MASK_NBITS,
link_mode_names, compact);
if (ret < 0)
return -EMSGSIZE;
}
if (nla_put_u32(skb, ETHTOOL_A_LINKMODES_SPEED, lsettings->speed) ||
nla_put_u8(skb, ETHTOOL_A_LINKMODES_DUPLEX, lsettings->duplex))
return -EMSGSIZE;
return 0;
}
const struct ethnl_request_ops ethnl_linkmodes_request_ops = {
.request_cmd = ETHTOOL_MSG_LINKMODES_GET,
.reply_cmd = ETHTOOL_MSG_LINKMODES_GET_REPLY,
.hdr_attr = ETHTOOL_A_LINKMODES_HEADER,
.max_attr = ETHTOOL_A_LINKMODES_MAX,
.req_info_size = sizeof(struct linkmodes_req_info),
.reply_data_size = sizeof(struct linkmodes_reply_data),
.request_policy = linkmodes_get_policy,
.prepare_data = linkmodes_prepare_data,
.reply_size = linkmodes_reply_size,
.fill_reply = linkmodes_fill_reply,
};
/* LINKMODES_SET */
struct link_mode_info {
int speed;
u8 duplex;
};
#define __DEFINE_LINK_MODE_PARAMS(_speed, _type, _duplex) \
[ETHTOOL_LINK_MODE(_speed, _type, _duplex)] = { \
.speed = SPEED_ ## _speed, \
.duplex = __DUPLEX_ ## _duplex \
}
#define __DUPLEX_Half DUPLEX_HALF
#define __DUPLEX_Full DUPLEX_FULL
#define __DEFINE_SPECIAL_MODE_PARAMS(_mode) \
[ETHTOOL_LINK_MODE_ ## _mode ## _BIT] = { \
.speed = SPEED_UNKNOWN, \
.duplex = DUPLEX_UNKNOWN, \
}
static const struct link_mode_info link_mode_params[] = {
__DEFINE_LINK_MODE_PARAMS(10, T, Half),
__DEFINE_LINK_MODE_PARAMS(10, T, Full),
__DEFINE_LINK_MODE_PARAMS(100, T, Half),
__DEFINE_LINK_MODE_PARAMS(100, T, Full),
__DEFINE_LINK_MODE_PARAMS(1000, T, Half),
__DEFINE_LINK_MODE_PARAMS(1000, T, Full),
__DEFINE_SPECIAL_MODE_PARAMS(Autoneg),
__DEFINE_SPECIAL_MODE_PARAMS(TP),
__DEFINE_SPECIAL_MODE_PARAMS(AUI),
__DEFINE_SPECIAL_MODE_PARAMS(MII),
__DEFINE_SPECIAL_MODE_PARAMS(FIBRE),
__DEFINE_SPECIAL_MODE_PARAMS(BNC),
__DEFINE_LINK_MODE_PARAMS(10000, T, Full),
__DEFINE_SPECIAL_MODE_PARAMS(Pause),
__DEFINE_SPECIAL_MODE_PARAMS(Asym_Pause),
__DEFINE_LINK_MODE_PARAMS(2500, X, Full),
__DEFINE_SPECIAL_MODE_PARAMS(Backplane),
__DEFINE_LINK_MODE_PARAMS(1000, KX, Full),
__DEFINE_LINK_MODE_PARAMS(10000, KX4, Full),
__DEFINE_LINK_MODE_PARAMS(10000, KR, Full),
[ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = {
.speed = SPEED_10000,
.duplex = DUPLEX_FULL,
},
__DEFINE_LINK_MODE_PARAMS(20000, MLD2, Full),
__DEFINE_LINK_MODE_PARAMS(20000, KR2, Full),
__DEFINE_LINK_MODE_PARAMS(40000, KR4, Full),
__DEFINE_LINK_MODE_PARAMS(40000, CR4, Full),
__DEFINE_LINK_MODE_PARAMS(40000, SR4, Full),
__DEFINE_LINK_MODE_PARAMS(40000, LR4, Full),
__DEFINE_LINK_MODE_PARAMS(56000, KR4, Full),
__DEFINE_LINK_MODE_PARAMS(56000, CR4, Full),
__DEFINE_LINK_MODE_PARAMS(56000, SR4, Full),
__DEFINE_LINK_MODE_PARAMS(56000, LR4, Full),
__DEFINE_LINK_MODE_PARAMS(25000, CR, Full),
__DEFINE_LINK_MODE_PARAMS(25000, KR, Full),
__DEFINE_LINK_MODE_PARAMS(25000, SR, Full),
__DEFINE_LINK_MODE_PARAMS(50000, CR2, Full),
__DEFINE_LINK_MODE_PARAMS(50000, KR2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, KR4, Full),
__DEFINE_LINK_MODE_PARAMS(100000, SR4, Full),
__DEFINE_LINK_MODE_PARAMS(100000, CR4, Full),
__DEFINE_LINK_MODE_PARAMS(100000, LR4_ER4, Full),
__DEFINE_LINK_MODE_PARAMS(50000, SR2, Full),
__DEFINE_LINK_MODE_PARAMS(1000, X, Full),
__DEFINE_LINK_MODE_PARAMS(10000, CR, Full),
__DEFINE_LINK_MODE_PARAMS(10000, SR, Full),
__DEFINE_LINK_MODE_PARAMS(10000, LR, Full),
__DEFINE_LINK_MODE_PARAMS(10000, LRM, Full),
__DEFINE_LINK_MODE_PARAMS(10000, ER, Full),
__DEFINE_LINK_MODE_PARAMS(2500, T, Full),
__DEFINE_LINK_MODE_PARAMS(5000, T, Full),
__DEFINE_SPECIAL_MODE_PARAMS(FEC_NONE),
__DEFINE_SPECIAL_MODE_PARAMS(FEC_RS),
__DEFINE_SPECIAL_MODE_PARAMS(FEC_BASER),
__DEFINE_LINK_MODE_PARAMS(50000, KR, Full),
__DEFINE_LINK_MODE_PARAMS(50000, SR, Full),
__DEFINE_LINK_MODE_PARAMS(50000, CR, Full),
__DEFINE_LINK_MODE_PARAMS(50000, LR_ER_FR, Full),
__DEFINE_LINK_MODE_PARAMS(50000, DR, Full),
__DEFINE_LINK_MODE_PARAMS(100000, KR2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, SR2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, CR2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, LR2_ER2_FR2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, DR2, Full),
__DEFINE_LINK_MODE_PARAMS(200000, KR4, Full),
__DEFINE_LINK_MODE_PARAMS(200000, SR4, Full),
__DEFINE_LINK_MODE_PARAMS(200000, LR4_ER4_FR4, Full),
__DEFINE_LINK_MODE_PARAMS(200000, DR4, Full),
__DEFINE_LINK_MODE_PARAMS(200000, CR4, Full),
__DEFINE_LINK_MODE_PARAMS(100, T1, Full),
__DEFINE_LINK_MODE_PARAMS(1000, T1, Full),
__DEFINE_LINK_MODE_PARAMS(400000, KR8, Full),
__DEFINE_LINK_MODE_PARAMS(400000, SR8, Full),
__DEFINE_LINK_MODE_PARAMS(400000, LR8_ER8_FR8, Full),
__DEFINE_LINK_MODE_PARAMS(400000, DR8, Full),
__DEFINE_LINK_MODE_PARAMS(400000, CR8, Full),
};
static const struct nla_policy
linkmodes_set_policy[ETHTOOL_A_LINKMODES_MAX + 1] = {
[ETHTOOL_A_LINKMODES_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKMODES_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_LINKMODES_AUTONEG] = { .type = NLA_U8 },
[ETHTOOL_A_LINKMODES_OURS] = { .type = NLA_NESTED },
[ETHTOOL_A_LINKMODES_PEER] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKMODES_SPEED] = { .type = NLA_U32 },
[ETHTOOL_A_LINKMODES_DUPLEX] = { .type = NLA_U8 },
};
/* Set advertised link modes to all supported modes matching requested speed
* and duplex values. Called when autonegotiation is on, speed or duplex is
* requested but no link mode change. This is done in userspace with ioctl()
* interface, move it into kernel for netlink.
* Returns true if advertised modes bitmap was modified.
*/
static bool ethnl_auto_linkmodes(struct ethtool_link_ksettings *ksettings,
bool req_speed, bool req_duplex)
{
unsigned long *advertising = ksettings->link_modes.advertising;
unsigned long *supported = ksettings->link_modes.supported;
DECLARE_BITMAP(old_adv, __ETHTOOL_LINK_MODE_MASK_NBITS);
unsigned int i;
BUILD_BUG_ON(ARRAY_SIZE(link_mode_params) !=
__ETHTOOL_LINK_MODE_MASK_NBITS);
bitmap_copy(old_adv, advertising, __ETHTOOL_LINK_MODE_MASK_NBITS);
for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) {
const struct link_mode_info *info = &link_mode_params[i];
if (info->speed == SPEED_UNKNOWN)
continue;
if (test_bit(i, supported) &&
(!req_speed || info->speed == ksettings->base.speed) &&
(!req_duplex || info->duplex == ksettings->base.duplex))
set_bit(i, advertising);
else
clear_bit(i, advertising);
}
return !bitmap_equal(old_adv, advertising,
__ETHTOOL_LINK_MODE_MASK_NBITS);
}
static int ethnl_update_linkmodes(struct genl_info *info, struct nlattr **tb,
struct ethtool_link_ksettings *ksettings,
bool *mod)
{
struct ethtool_link_settings *lsettings = &ksettings->base;
bool req_speed, req_duplex;
int ret;
*mod = false;
req_speed = tb[ETHTOOL_A_LINKMODES_SPEED];
req_duplex = tb[ETHTOOL_A_LINKMODES_DUPLEX];
ethnl_update_u8(&lsettings->autoneg, tb[ETHTOOL_A_LINKMODES_AUTONEG],
mod);
ret = ethnl_update_bitset(ksettings->link_modes.advertising,
__ETHTOOL_LINK_MODE_MASK_NBITS,
tb[ETHTOOL_A_LINKMODES_OURS], link_mode_names,
info->extack, mod);
if (ret < 0)
return ret;
ethnl_update_u32(&lsettings->speed, tb[ETHTOOL_A_LINKMODES_SPEED],
mod);
ethnl_update_u8(&lsettings->duplex, tb[ETHTOOL_A_LINKMODES_DUPLEX],
mod);
if (!tb[ETHTOOL_A_LINKMODES_OURS] && lsettings->autoneg &&
(req_speed || req_duplex) &&
ethnl_auto_linkmodes(ksettings, req_speed, req_duplex))
*mod = true;
return 0;
}
int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1];
struct ethtool_link_ksettings ksettings = {};
struct ethtool_link_settings *lsettings;
struct ethnl_req_info req_info = {};
struct net_device *dev;
bool mod = false;
int ret;
ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
ETHTOOL_A_LINKMODES_MAX, linkmodes_set_policy,
info->extack);
if (ret < 0)
return ret;
ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKMODES_HEADER],
genl_info_net(info), info->extack, true);
if (ret < 0)
return ret;
dev = req_info.dev;
if (!dev->ethtool_ops->get_link_ksettings ||
!dev->ethtool_ops->set_link_ksettings)
return -EOPNOTSUPP;
rtnl_lock();
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
ret = __ethtool_get_link_ksettings(dev, &ksettings);
if (ret < 0) {
if (info)
GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
goto out_ops;
}
lsettings = &ksettings.base;
ret = ethnl_update_linkmodes(info, tb, &ksettings, &mod);
if (ret < 0)
goto out_ops;
if (mod) {
ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings);
if (ret < 0)
GENL_SET_ERR_MSG(info, "link settings update failed");
else
ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF, NULL);
}
out_ops:
ethnl_ops_complete(dev);
out_rtnl:
rtnl_unlock();
dev_put(dev);
return ret;
}
// SPDX-License-Identifier: GPL-2.0-only
#include "netlink.h"
#include "common.h"
struct linkstate_req_info {
struct ethnl_req_info base;
};
struct linkstate_reply_data {
struct ethnl_reply_data base;
int link;
};
#define LINKSTATE_REPDATA(__reply_base) \
container_of(__reply_base, struct linkstate_reply_data, base)
static const struct nla_policy
linkstate_get_policy[ETHTOOL_A_LINKSTATE_MAX + 1] = {
[ETHTOOL_A_LINKSTATE_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_LINKSTATE_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_LINKSTATE_LINK] = { .type = NLA_REJECT },
};
static int linkstate_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
struct genl_info *info)
{
struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
int ret;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
data->link = __ethtool_get_link(dev);
ethnl_ops_complete(dev);
return 0;
}
static int linkstate_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
return nla_total_size(sizeof(u8)) /* LINKSTATE_LINK */
+ 0;
}
static int linkstate_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
struct linkstate_reply_data *data = LINKSTATE_REPDATA(reply_base);
if (data->link >= 0 &&
nla_put_u8(skb, ETHTOOL_A_LINKSTATE_LINK, !!data->link))
return -EMSGSIZE;
return 0;
}
const struct ethnl_request_ops ethnl_linkstate_request_ops = {
.request_cmd = ETHTOOL_MSG_LINKSTATE_GET,
.reply_cmd = ETHTOOL_MSG_LINKSTATE_GET_REPLY,
.hdr_attr = ETHTOOL_A_LINKSTATE_HEADER,
.max_attr = ETHTOOL_A_LINKSTATE_MAX,
.req_info_size = sizeof(struct linkstate_req_info),
.reply_data_size = sizeof(struct linkstate_reply_data),
.request_policy = linkstate_get_policy,
.prepare_data = linkstate_prepare_data,
.reply_size = linkstate_reply_size,
.fill_reply = linkstate_fill_reply,
};
// SPDX-License-Identifier: GPL-2.0-only
#include <net/sock.h>
#include <linux/ethtool_netlink.h>
#include "netlink.h"
static struct genl_family ethtool_genl_family;
static bool ethnl_ok __read_mostly;
static u32 ethnl_bcast_seq;
static const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_MAX + 1] = {
[ETHTOOL_A_HEADER_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_HEADER_DEV_INDEX] = { .type = NLA_U32 },
[ETHTOOL_A_HEADER_DEV_NAME] = { .type = NLA_NUL_STRING,
.len = ALTIFNAMSIZ - 1 },
[ETHTOOL_A_HEADER_FLAGS] = { .type = NLA_U32 },
};
/**
* ethnl_parse_header() - parse request header
* @req_info: structure to put results into
* @header: nest attribute with request header
* @net: request netns
* @extack: netlink extack for error reporting
* @require_dev: fail if no device identified in header
*
* Parse request header in nested attribute @nest and puts results into
* the structure pointed to by @req_info. Extack from @info is used for error
* reporting. If req_info->dev is not null on return, reference to it has
* been taken. If error is returned, *req_info is null initialized and no
* reference is held.
*
* Return: 0 on success or negative error code
*/
int ethnl_parse_header(struct ethnl_req_info *req_info,
const struct nlattr *header, struct net *net,
struct netlink_ext_ack *extack, bool require_dev)
{
struct nlattr *tb[ETHTOOL_A_HEADER_MAX + 1];
const struct nlattr *devname_attr;
struct net_device *dev = NULL;
int ret;
if (!header) {
NL_SET_ERR_MSG(extack, "request header missing");
return -EINVAL;
}
ret = nla_parse_nested(tb, ETHTOOL_A_HEADER_MAX, header,
ethnl_header_policy, extack);
if (ret < 0)
return ret;
devname_attr = tb[ETHTOOL_A_HEADER_DEV_NAME];
if (tb[ETHTOOL_A_HEADER_DEV_INDEX]) {
u32 ifindex = nla_get_u32(tb[ETHTOOL_A_HEADER_DEV_INDEX]);
dev = dev_get_by_index(net, ifindex);
if (!dev) {
NL_SET_ERR_MSG_ATTR(extack,
tb[ETHTOOL_A_HEADER_DEV_INDEX],
"no device matches ifindex");
return -ENODEV;
}
/* if both ifindex and ifname are passed, they must match */
if (devname_attr &&
strncmp(dev->name, nla_data(devname_attr), IFNAMSIZ)) {
dev_put(dev);
NL_SET_ERR_MSG_ATTR(extack, header,
"ifindex and name do not match");
return -ENODEV;
}
} else if (devname_attr) {
dev = dev_get_by_name(net, nla_data(devname_attr));
if (!dev) {
NL_SET_ERR_MSG_ATTR(extack, devname_attr,
"no device matches name");
return -ENODEV;
}
} else if (require_dev) {
NL_SET_ERR_MSG_ATTR(extack, header,
"neither ifindex nor name specified");
return -EINVAL;
}
if (dev && !netif_device_present(dev)) {
dev_put(dev);
NL_SET_ERR_MSG(extack, "device not present");
return -ENODEV;
}
req_info->dev = dev;
if (tb[ETHTOOL_A_HEADER_FLAGS])
req_info->flags = nla_get_u32(tb[ETHTOOL_A_HEADER_FLAGS]);
return 0;
}
/**
* ethnl_fill_reply_header() - Put common header into a reply message
* @skb: skb with the message
* @dev: network device to describe in header
* @attrtype: attribute type to use for the nest
*
* Create a nested attribute with attributes describing given network device.
*
* Return: 0 on success, error value (-EMSGSIZE only) on error
*/
int ethnl_fill_reply_header(struct sk_buff *skb, struct net_device *dev,
u16 attrtype)
{
struct nlattr *nest;
if (!dev)
return 0;
nest = nla_nest_start(skb, attrtype);
if (!nest)
return -EMSGSIZE;
if (nla_put_u32(skb, ETHTOOL_A_HEADER_DEV_INDEX, (u32)dev->ifindex) ||
nla_put_string(skb, ETHTOOL_A_HEADER_DEV_NAME, dev->name))
goto nla_put_failure;
/* If more attributes are put into reply header, ethnl_header_size()
* must be updated to account for them.
*/
nla_nest_end(skb, nest);
return 0;
nla_put_failure:
nla_nest_cancel(skb, nest);
return -EMSGSIZE;
}
/**
* ethnl_reply_init() - Create skb for a reply and fill device identification
* @payload: payload length (without netlink and genetlink header)
* @dev: device the reply is about (may be null)
* @cmd: ETHTOOL_MSG_* message type for reply
* @info: genetlink info of the received packet we respond to
* @ehdrp: place to store payload pointer returned by genlmsg_new()
*
* Return: pointer to allocated skb on success, NULL on error
*/
struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
u16 hdr_attrtype, struct genl_info *info,
void **ehdrp)
{
struct sk_buff *skb;
skb = genlmsg_new(payload, GFP_KERNEL);
if (!skb)
goto err;
*ehdrp = genlmsg_put_reply(skb, info, &ethtool_genl_family, 0, cmd);
if (!*ehdrp)
goto err_free;
if (dev) {
int ret;
ret = ethnl_fill_reply_header(skb, dev, hdr_attrtype);
if (ret < 0)
goto err_free;
}
return skb;
err_free:
nlmsg_free(skb);
err:
if (info)
GENL_SET_ERR_MSG(info, "failed to setup reply message");
return NULL;
}
static void *ethnl_bcastmsg_put(struct sk_buff *skb, u8 cmd)
{
return genlmsg_put(skb, 0, ++ethnl_bcast_seq, &ethtool_genl_family, 0,
cmd);
}
static int ethnl_multicast(struct sk_buff *skb, struct net_device *dev)
{
return genlmsg_multicast_netns(&ethtool_genl_family, dev_net(dev), skb,
0, ETHNL_MCGRP_MONITOR, GFP_KERNEL);
}
/* GET request helpers */
/**
* struct ethnl_dump_ctx - context structure for generic dumpit() callback
* @ops: request ops of currently processed message type
* @req_info: parsed request header of processed request
* @pos_hash: saved iteration position - hashbucket
* @pos_idx: saved iteration position - index
*
* These parameters are kept in struct netlink_callback as context preserved
* between iterations. They are initialized by ethnl_default_start() and used
* in ethnl_default_dumpit() and ethnl_default_done().
*/
struct ethnl_dump_ctx {
const struct ethnl_request_ops *ops;
struct ethnl_req_info *req_info;
struct ethnl_reply_data *reply_data;
int pos_hash;
int pos_idx;
};
static const struct ethnl_request_ops *
ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_STRSET_GET] = &ethnl_strset_request_ops,
[ETHTOOL_MSG_LINKINFO_GET] = &ethnl_linkinfo_request_ops,
[ETHTOOL_MSG_LINKMODES_GET] = &ethnl_linkmodes_request_ops,
[ETHTOOL_MSG_LINKSTATE_GET] = &ethnl_linkstate_request_ops,
};
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
{
return (struct ethnl_dump_ctx *)cb->ctx;
}
/**
* ethnl_default_parse() - Parse request message
* @req_info: pointer to structure to put data into
* @nlhdr: pointer to request message header
* @net: request netns
* @request_ops: struct request_ops for request type
* @extack: netlink extack for error reporting
* @require_dev: fail if no device identified in header
*
* Parse universal request header and call request specific ->parse_request()
* callback (if defined) to parse the rest of the message.
*
* Return: 0 on success or negative error code
*/
static int ethnl_default_parse(struct ethnl_req_info *req_info,
const struct nlmsghdr *nlhdr, struct net *net,
const struct ethnl_request_ops *request_ops,
struct netlink_ext_ack *extack, bool require_dev)
{
struct nlattr **tb;
int ret;
tb = kmalloc_array(request_ops->max_attr + 1, sizeof(tb[0]),
GFP_KERNEL);
if (!tb)
return -ENOMEM;
ret = nlmsg_parse(nlhdr, GENL_HDRLEN, tb, request_ops->max_attr,
request_ops->request_policy, extack);
if (ret < 0)
goto out;
ret = ethnl_parse_header(req_info, tb[request_ops->hdr_attr], net,
extack, require_dev);
if (ret < 0)
goto out;
if (request_ops->parse_request) {
ret = request_ops->parse_request(req_info, tb, extack);
if (ret < 0)
goto out;
}
ret = 0;
out:
kfree(tb);
return ret;
}
/**
* ethnl_init_reply_data() - Initialize reply data for GET request
* @req_info: pointer to embedded struct ethnl_req_info
* @ops: instance of struct ethnl_request_ops describing the layout
* @dev: network device to initialize the reply for
*
* Fills the reply data part with zeros and sets the dev member. Must be called
* before calling the ->fill_reply() callback (for each iteration when handling
* dump requests).
*/
static void ethnl_init_reply_data(struct ethnl_reply_data *reply_data,
const struct ethnl_request_ops *ops,
struct net_device *dev)
{
memset(reply_data, 0, ops->reply_data_size);
reply_data->dev = dev;
}
/* default ->doit() handler for GET type requests */
static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info)
{
struct ethnl_reply_data *reply_data = NULL;
struct ethnl_req_info *req_info = NULL;
const u8 cmd = info->genlhdr->cmd;
const struct ethnl_request_ops *ops;
struct sk_buff *rskb;
void *reply_payload;
int reply_len;
int ret;
ops = ethnl_default_requests[cmd];
if (WARN_ONCE(!ops, "cmd %u has no ethnl_request_ops\n", cmd))
return -EOPNOTSUPP;
req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
if (!req_info)
return -ENOMEM;
reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
if (!reply_data) {
kfree(req_info);
return -ENOMEM;
}
ret = ethnl_default_parse(req_info, info->nlhdr, genl_info_net(info), ops,
info->extack, !ops->allow_nodev_do);
if (ret < 0)
goto err_dev;
ethnl_init_reply_data(reply_data, ops, req_info->dev);
rtnl_lock();
ret = ops->prepare_data(req_info, reply_data, info);
rtnl_unlock();
if (ret < 0)
goto err_cleanup;
reply_len = ops->reply_size(req_info, reply_data);
if (ret < 0)
goto err_cleanup;
ret = -ENOMEM;
rskb = ethnl_reply_init(reply_len, req_info->dev, ops->reply_cmd,
ops->hdr_attr, info, &reply_payload);
if (!rskb)
goto err_cleanup;
ret = ops->fill_reply(rskb, req_info, reply_data);
if (ret < 0)
goto err_msg;
if (ops->cleanup_data)
ops->cleanup_data(reply_data);
genlmsg_end(rskb, reply_payload);
if (req_info->dev)
dev_put(req_info->dev);
kfree(reply_data);
kfree(req_info);
return genlmsg_reply(rskb, info);
err_msg:
WARN_ONCE(ret == -EMSGSIZE, "calculated message payload length (%d) not sufficient\n", reply_len);
nlmsg_free(rskb);
err_cleanup:
if (ops->cleanup_data)
ops->cleanup_data(reply_data);
err_dev:
if (req_info->dev)
dev_put(req_info->dev);
kfree(reply_data);
kfree(req_info);
return ret;
}
static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev,
const struct ethnl_dump_ctx *ctx)
{
int ret;
ethnl_init_reply_data(ctx->reply_data, ctx->ops, dev);
rtnl_lock();
ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, NULL);
rtnl_unlock();
if (ret < 0)
goto out;
ret = ethnl_fill_reply_header(skb, dev, ctx->ops->hdr_attr);
if (ret < 0)
goto out;
ret = ctx->ops->fill_reply(skb, ctx->req_info, ctx->reply_data);
out:
if (ctx->ops->cleanup_data)
ctx->ops->cleanup_data(ctx->reply_data);
ctx->reply_data->dev = NULL;
return ret;
}
/* Default ->dumpit() handler for GET requests. Device iteration copied from
* rtnl_dump_ifinfo(); we have to be more careful about device hashtable
* persistence as we cannot guarantee to hold RTNL lock through the whole
* function as rtnetnlink does.
*/
static int ethnl_default_dumpit(struct sk_buff *skb,
struct netlink_callback *cb)
{
struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
struct net *net = sock_net(skb->sk);
int s_idx = ctx->pos_idx;
int h, idx = 0;
int ret = 0;
void *ehdr;
rtnl_lock();
for (h = ctx->pos_hash; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
struct hlist_head *head;
struct net_device *dev;
unsigned int seq;
head = &net->dev_index_head[h];
restart_chain:
seq = net->dev_base_seq;
cb->seq = seq;
idx = 0;
hlist_for_each_entry(dev, head, index_hlist) {
if (idx < s_idx)
goto cont;
dev_hold(dev);
rtnl_unlock();
ehdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq,
&ethtool_genl_family, 0,
ctx->ops->reply_cmd);
if (!ehdr) {
dev_put(dev);
ret = -EMSGSIZE;
goto out;
}
ret = ethnl_default_dump_one(skb, dev, ctx);
dev_put(dev);
if (ret < 0) {
genlmsg_cancel(skb, ehdr);
if (ret == -EOPNOTSUPP)
goto lock_and_cont;
if (likely(skb->len))
ret = skb->len;
goto out;
}
genlmsg_end(skb, ehdr);
lock_and_cont:
rtnl_lock();
if (net->dev_base_seq != seq) {
s_idx = idx + 1;
goto restart_chain;
}
cont:
idx++;
}
}
rtnl_unlock();
out:
ctx->pos_hash = h;
ctx->pos_idx = idx;
nl_dump_check_consistent(cb, nlmsg_hdr(skb));
return ret;
}
/* generic ->start() handler for GET requests */
static int ethnl_default_start(struct netlink_callback *cb)
{
struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
struct ethnl_reply_data *reply_data;
const struct ethnl_request_ops *ops;
struct ethnl_req_info *req_info;
struct genlmsghdr *ghdr;
int ret;
BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
ghdr = nlmsg_data(cb->nlh);
ops = ethnl_default_requests[ghdr->cmd];
if (WARN_ONCE(!ops, "cmd %u has no ethnl_request_ops\n", ghdr->cmd))
return -EOPNOTSUPP;
req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
if (!req_info)
return -ENOMEM;
reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
if (!reply_data) {
kfree(req_info);
return -ENOMEM;
}
ret = ethnl_default_parse(req_info, cb->nlh, sock_net(cb->skb->sk), ops,
cb->extack, false);
if (req_info->dev) {
/* We ignore device specification in dump requests but as the
* same parser as for non-dump (doit) requests is used, it
* would take reference to the device if it finds one
*/
dev_put(req_info->dev);
req_info->dev = NULL;
}
if (ret < 0)
return ret;
ctx->ops = ops;
ctx->req_info = req_info;
ctx->reply_data = reply_data;
ctx->pos_hash = 0;
ctx->pos_idx = 0;
return 0;
}
/* default ->done() handler for GET requests */
static int ethnl_default_done(struct netlink_callback *cb)
{
struct ethnl_dump_ctx *ctx = ethnl_dump_context(cb);
kfree(ctx->reply_data);
kfree(ctx->req_info);
return 0;
}
static const struct ethnl_request_ops *
ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = {
[ETHTOOL_MSG_LINKINFO_NTF] = &ethnl_linkinfo_request_ops,
[ETHTOOL_MSG_LINKMODES_NTF] = &ethnl_linkmodes_request_ops,
};
/* default notification handler */
static void ethnl_default_notify(struct net_device *dev, unsigned int cmd,
const void *data)
{
struct ethnl_reply_data *reply_data;
const struct ethnl_request_ops *ops;
struct ethnl_req_info *req_info;
struct sk_buff *skb;
void *reply_payload;
int reply_len;
int ret;
if (WARN_ONCE(cmd > ETHTOOL_MSG_KERNEL_MAX ||
!ethnl_default_notify_ops[cmd],
"unexpected notification type %u\n", cmd))
return;
ops = ethnl_default_notify_ops[cmd];
req_info = kzalloc(ops->req_info_size, GFP_KERNEL);
if (!req_info)
return;
reply_data = kmalloc(ops->reply_data_size, GFP_KERNEL);
if (!reply_data) {
kfree(req_info);
return;
}
req_info->dev = dev;
req_info->flags |= ETHTOOL_FLAG_COMPACT_BITSETS;
ethnl_init_reply_data(reply_data, ops, dev);
ret = ops->prepare_data(req_info, reply_data, NULL);
if (ret < 0)
goto err_cleanup;
reply_len = ops->reply_size(req_info, reply_data);
if (ret < 0)
goto err_cleanup;
ret = -ENOMEM;
skb = genlmsg_new(reply_len, GFP_KERNEL);
if (!skb)
goto err_cleanup;
reply_payload = ethnl_bcastmsg_put(skb, cmd);
if (!reply_payload)
goto err_skb;
ret = ethnl_fill_reply_header(skb, dev, ops->hdr_attr);
if (ret < 0)
goto err_msg;
ret = ops->fill_reply(skb, req_info, reply_data);
if (ret < 0)
goto err_msg;
if (ops->cleanup_data)
ops->cleanup_data(reply_data);
genlmsg_end(skb, reply_payload);
kfree(reply_data);
kfree(req_info);
ethnl_multicast(skb, dev);
return;
err_msg:
WARN_ONCE(ret == -EMSGSIZE,
"calculated message payload length (%d) not sufficient\n",
reply_len);
err_skb:
nlmsg_free(skb);
err_cleanup:
if (ops->cleanup_data)
ops->cleanup_data(reply_data);
kfree(reply_data);
kfree(req_info);
return;
}
/* notifications */
typedef void (*ethnl_notify_handler_t)(struct net_device *dev, unsigned int cmd,
const void *data);
static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
[ETHTOOL_MSG_LINKINFO_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_LINKMODES_NTF] = ethnl_default_notify,
};
void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data)
{
if (unlikely(!ethnl_ok))
return;
ASSERT_RTNL();
if (likely(cmd < ARRAY_SIZE(ethnl_notify_handlers) &&
ethnl_notify_handlers[cmd]))
ethnl_notify_handlers[cmd](dev, cmd, data);
else
WARN_ONCE(1, "notification %u not implemented (dev=%s)\n",
cmd, netdev_name(dev));
}
EXPORT_SYMBOL(ethtool_notify);
/* genetlink setup */
static const struct genl_ops ethtool_genl_ops[] = {
{
.cmd = ETHTOOL_MSG_STRSET_GET,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
},
{
.cmd = ETHTOOL_MSG_LINKINFO_GET,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
},
{
.cmd = ETHTOOL_MSG_LINKINFO_SET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_set_linkinfo,
},
{
.cmd = ETHTOOL_MSG_LINKMODES_GET,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
},
{
.cmd = ETHTOOL_MSG_LINKMODES_SET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_set_linkmodes,
},
{
.cmd = ETHTOOL_MSG_LINKSTATE_GET,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
},
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
[ETHNL_MCGRP_MONITOR] = { .name = ETHTOOL_MCGRP_MONITOR_NAME },
};
static struct genl_family ethtool_genl_family = {
.name = ETHTOOL_GENL_NAME,
.version = ETHTOOL_GENL_VERSION,
.netnsok = true,
.parallel_ops = true,
.ops = ethtool_genl_ops,
.n_ops = ARRAY_SIZE(ethtool_genl_ops),
.mcgrps = ethtool_nl_mcgrps,
.n_mcgrps = ARRAY_SIZE(ethtool_nl_mcgrps),
};
/* module setup */
static int __init ethnl_init(void)
{
int ret;
ret = genl_register_family(&ethtool_genl_family);
if (WARN(ret < 0, "ethtool: genetlink family registration failed"))
return ret;
ethnl_ok = true;
return 0;
}
subsys_initcall(ethnl_init);
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _NET_ETHTOOL_NETLINK_H
#define _NET_ETHTOOL_NETLINK_H
#include <linux/ethtool_netlink.h>
#include <linux/netdevice.h>
#include <net/genetlink.h>
#include <net/sock.h>
struct ethnl_req_info;
int ethnl_parse_header(struct ethnl_req_info *req_info,
const struct nlattr *nest, struct net *net,
struct netlink_ext_ack *extack, bool require_dev);
int ethnl_fill_reply_header(struct sk_buff *skb, struct net_device *dev,
u16 attrtype);
struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
u16 hdr_attrtype, struct genl_info *info,
void **ehdrp);
/**
* ethnl_strz_size() - calculate attribute length for fixed size string
* @s: ETH_GSTRING_LEN sized string (may not be null terminated)
*
* Return: total length of an attribute with null terminated string from @s
*/
static inline int ethnl_strz_size(const char *s)
{
return nla_total_size(strnlen(s, ETH_GSTRING_LEN) + 1);
}
/**
* ethnl_put_strz() - put string attribute with fixed size string
* @skb: skb with the message
* @attrype: attribute type
* @s: ETH_GSTRING_LEN sized string (may not be null terminated)
*
* Puts an attribute with null terminated string from @s into the message.
*
* Return: 0 on success, negative error code on failure
*/
static inline int ethnl_put_strz(struct sk_buff *skb, u16 attrtype,
const char *s)
{
unsigned int len = strnlen(s, ETH_GSTRING_LEN);
struct nlattr *attr;
attr = nla_reserve(skb, attrtype, len + 1);
if (!attr)
return -EMSGSIZE;
memcpy(nla_data(attr), s, len);
((char *)nla_data(attr))[len] = '\0';
return 0;
}
/**
* ethnl_update_u32() - update u32 value from NLA_U32 attribute
* @dst: value to update
* @attr: netlink attribute with new value or null
* @mod: pointer to bool for modification tracking
*
* Copy the u32 value from NLA_U32 netlink attribute @attr into variable
* pointed to by @dst; do nothing if @attr is null. Bool pointed to by @mod
* is set to true if this function changed the value of *dst, otherwise it
* is left as is.
*/
static inline void ethnl_update_u32(u32 *dst, const struct nlattr *attr,
bool *mod)
{
u32 val;
if (!attr)
return;
val = nla_get_u32(attr);
if (*dst == val)
return;
*dst = val;
*mod = true;
}
/**
* ethnl_update_u8() - update u8 value from NLA_U8 attribute
* @dst: value to update
* @attr: netlink attribute with new value or null
* @mod: pointer to bool for modification tracking
*
* Copy the u8 value from NLA_U8 netlink attribute @attr into variable
* pointed to by @dst; do nothing if @attr is null. Bool pointed to by @mod
* is set to true if this function changed the value of *dst, otherwise it
* is left as is.
*/
static inline void ethnl_update_u8(u8 *dst, const struct nlattr *attr,
bool *mod)
{
u8 val;
if (!attr)
return;
val = nla_get_u8(attr);
if (*dst == val)
return;
*dst = val;
*mod = true;
}
/**
* ethnl_update_bool32() - update u32 used as bool from NLA_U8 attribute
* @dst: value to update
* @attr: netlink attribute with new value or null
* @mod: pointer to bool for modification tracking
*
* Use the u8 value from NLA_U8 netlink attribute @attr to set u32 variable
* pointed to by @dst to 0 (if zero) or 1 (if not); do nothing if @attr is
* null. Bool pointed to by @mod is set to true if this function changed the
* logical value of *dst, otherwise it is left as is.
*/
static inline void ethnl_update_bool32(u32 *dst, const struct nlattr *attr,
bool *mod)
{
u8 val;
if (!attr)
return;
val = !!nla_get_u8(attr);
if (!!*dst == val)
return;
*dst = val;
*mod = true;
}
/**
* ethnl_update_binary() - update binary data from NLA_BINARY atribute
* @dst: value to update
* @len: destination buffer length
* @attr: netlink attribute with new value or null
* @mod: pointer to bool for modification tracking
*
* Use the u8 value from NLA_U8 netlink attribute @attr to rewrite data block
* of length @len at @dst by attribute payload; do nothing if @attr is null.
* Bool pointed to by @mod is set to true if this function changed the logical
* value of *dst, otherwise it is left as is.
*/
static inline void ethnl_update_binary(void *dst, unsigned int len,
const struct nlattr *attr, bool *mod)
{
if (!attr)
return;
if (nla_len(attr) < len)
len = nla_len(attr);
if (!memcmp(dst, nla_data(attr), len))
return;
memcpy(dst, nla_data(attr), len);
*mod = true;
}
/**
* ethnl_update_bitfield32() - update u32 value from NLA_BITFIELD32 attribute
* @dst: value to update
* @attr: netlink attribute with new value or null
* @mod: pointer to bool for modification tracking
*
* Update bits in u32 value which are set in attribute's mask to values from
* attribute's value. Do nothing if @attr is null or the value wouldn't change;
* otherwise, set bool pointed to by @mod to true.
*/
static inline void ethnl_update_bitfield32(u32 *dst, const struct nlattr *attr,
bool *mod)
{
struct nla_bitfield32 change;
u32 newval;
if (!attr)
return;
change = nla_get_bitfield32(attr);
newval = (*dst & ~change.selector) | (change.value & change.selector);
if (*dst == newval)
return;
*dst = newval;
*mod = true;
}
/**
* ethnl_reply_header_size() - total size of reply header
*
* This is an upper estimate so that we do not need to hold RTNL lock longer
* than necessary (to prevent rename between size estimate and composing the
* message). Accounts only for device ifindex and name as those are the only
* attributes ethnl_fill_reply_header() puts into the reply header.
*/
static inline unsigned int ethnl_reply_header_size(void)
{
return nla_total_size(nla_total_size(sizeof(u32)) +
nla_total_size(IFNAMSIZ));
}
/* GET request handling */
/* Unified processing of GET requests uses two data structures: request info
* and reply data. Request info holds information parsed from client request
* and its stays constant through all request processing. Reply data holds data
* retrieved from ethtool_ops callbacks or other internal sources which is used
* to compose the reply. When processing a dump request, request info is filled
* only once (when the request message is parsed) but reply data is filled for
* each reply message.
*
* Both structures consist of part common for all request types (struct
* ethnl_req_info and struct ethnl_reply_data defined below) and optional
* parts specific for each request type. Common part always starts at offset 0.
*/
/**
* struct ethnl_req_info - base type of request information for GET requests
* @dev: network device the request is for (may be null)
* @flags: request flags common for all request types
*
* This is a common base for request specific structures holding data from
* parsed userspace request. These always embed struct ethnl_req_info at
* zero offset.
*/
struct ethnl_req_info {
struct net_device *dev;
u32 flags;
};
/**
* struct ethnl_reply_data - base type of reply data for GET requests
* @dev: device for current reply message; in single shot requests it is
* equal to &ethnl_req_info.dev; in dumps it's different for each
* reply message
*
* This is a common base for request specific structures holding data for
* kernel reply message. These always embed struct ethnl_reply_data at zero
* offset.
*/
struct ethnl_reply_data {
struct net_device *dev;
};
static inline int ethnl_ops_begin(struct net_device *dev)
{
if (dev && dev->ethtool_ops->begin)
return dev->ethtool_ops->begin(dev);
else
return 0;
}
static inline void ethnl_ops_complete(struct net_device *dev)
{
if (dev && dev->ethtool_ops->complete)
dev->ethtool_ops->complete(dev);
}
/**
* struct ethnl_request_ops - unified handling of GET requests
* @request_cmd: command id for request (GET)
* @reply_cmd: command id for reply (GET_REPLY)
* @hdr_attr: attribute type for request header
* @max_attr: maximum (top level) attribute type
* @req_info_size: size of request info
* @reply_data_size: size of reply data
* @request_policy: netlink policy for message contents
* @allow_nodev_do: allow non-dump request with no device identification
* @parse_request:
* Parse request except common header (struct ethnl_req_info). Common
* header is already filled on entry, the rest up to @repdata_offset
* is zero initialized. This callback should only modify type specific
* request info by parsed attributes from request message.
* @prepare_data:
* Retrieve and prepare data needed to compose a reply message. Calls to
* ethtool_ops handlers are limited to this callback. Common reply data
* (struct ethnl_reply_data) is filled on entry, type specific part after
* it is zero initialized. This callback should only modify the type
* specific part of reply data. Device identification from struct
* ethnl_reply_data is to be used as for dump requests, it iterates
* through network devices while dev member of struct ethnl_req_info
* points to the device from client request.
* @reply_size:
* Estimate reply message size. Returned value must be sufficient for
* message payload without common reply header. The callback may returned
* estimate higher than actual message size if exact calculation would
* not be worth the saved memory space.
* @fill_reply:
* Fill reply message payload (except for common header) from reply data.
* The callback must not generate more payload than previously called
* ->reply_size() estimated.
* @cleanup_data:
* Optional cleanup called when reply data is no longer needed. Can be
* used e.g. to free any additional data structures outside the main
* structure which were allocated by ->prepare_data(). When processing
* dump requests, ->cleanup() is called for each message.
*
* Description of variable parts of GET request handling when using the
* unified infrastructure. When used, a pointer to an instance of this
* structure is to be added to &ethnl_default_requests array and generic
* handlers ethnl_default_doit(), ethnl_default_dumpit(),
* ethnl_default_start() and ethnl_default_done() used in @ethtool_genl_ops;
* ethnl_default_notify() can be used in @ethnl_notify_handlers to send
* notifications of the corresponding type.
*/
struct ethnl_request_ops {
u8 request_cmd;
u8 reply_cmd;
u16 hdr_attr;
unsigned int max_attr;
unsigned int req_info_size;
unsigned int reply_data_size;
const struct nla_policy *request_policy;
bool allow_nodev_do;
int (*parse_request)(struct ethnl_req_info *req_info,
struct nlattr **tb,
struct netlink_ext_ack *extack);
int (*prepare_data)(const struct ethnl_req_info *req_info,
struct ethnl_reply_data *reply_data,
struct genl_info *info);
int (*reply_size)(const struct ethnl_req_info *req_info,
const struct ethnl_reply_data *reply_data);
int (*fill_reply)(struct sk_buff *skb,
const struct ethnl_req_info *req_info,
const struct ethnl_reply_data *reply_data);
void (*cleanup_data)(struct ethnl_reply_data *reply_data);
};
/* request handlers */
extern const struct ethnl_request_ops ethnl_strset_request_ops;
extern const struct ethnl_request_ops ethnl_linkinfo_request_ops;
extern const struct ethnl_request_ops ethnl_linkmodes_request_ops;
extern const struct ethnl_request_ops ethnl_linkstate_request_ops;
int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);
#endif /* _NET_ETHTOOL_NETLINK_H */
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/ethtool.h>
#include <linux/phy.h>
#include "netlink.h"
#include "common.h"
struct strset_info {
bool per_dev;
bool free_strings;
unsigned int count;
const char (*strings)[ETH_GSTRING_LEN];
};
static const struct strset_info info_template[] = {
[ETH_SS_TEST] = {
.per_dev = true,
},
[ETH_SS_STATS] = {
.per_dev = true,
},
[ETH_SS_PRIV_FLAGS] = {
.per_dev = true,
},
[ETH_SS_FEATURES] = {
.per_dev = false,
.count = ARRAY_SIZE(netdev_features_strings),
.strings = netdev_features_strings,
},
[ETH_SS_RSS_HASH_FUNCS] = {
.per_dev = false,
.count = ARRAY_SIZE(rss_hash_func_strings),
.strings = rss_hash_func_strings,
},
[ETH_SS_TUNABLES] = {
.per_dev = false,
.count = ARRAY_SIZE(tunable_strings),
.strings = tunable_strings,
},
[ETH_SS_PHY_STATS] = {
.per_dev = true,
},
[ETH_SS_PHY_TUNABLES] = {
.per_dev = false,
.count = ARRAY_SIZE(phy_tunable_strings),
.strings = phy_tunable_strings,
},
[ETH_SS_LINK_MODES] = {
.per_dev = false,
.count = __ETHTOOL_LINK_MODE_MASK_NBITS,
.strings = link_mode_names,
},
};
struct strset_req_info {
struct ethnl_req_info base;
u32 req_ids;
bool counts_only;
};
#define STRSET_REQINFO(__req_base) \
container_of(__req_base, struct strset_req_info, base)
struct strset_reply_data {
struct ethnl_reply_data base;
struct strset_info sets[ETH_SS_COUNT];
};
#define STRSET_REPDATA(__reply_base) \
container_of(__reply_base, struct strset_reply_data, base)
static const struct nla_policy strset_get_policy[ETHTOOL_A_STRSET_MAX + 1] = {
[ETHTOOL_A_STRSET_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_STRSET_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_STRSET_STRINGSETS] = { .type = NLA_NESTED },
};
static const struct nla_policy
get_stringset_policy[ETHTOOL_A_STRINGSET_MAX + 1] = {
[ETHTOOL_A_STRINGSET_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_STRINGSET_ID] = { .type = NLA_U32 },
[ETHTOOL_A_STRINGSET_COUNT] = { .type = NLA_REJECT },
[ETHTOOL_A_STRINGSET_STRINGS] = { .type = NLA_REJECT },
};
/**
* strset_include() - test if a string set should be included in reply
* @data: pointer to request data structure
* @id: id of string set to check (ETH_SS_* constants)
*/
static bool strset_include(const struct strset_req_info *info,
const struct strset_reply_data *data, u32 id)
{
bool per_dev;
BUILD_BUG_ON(ETH_SS_COUNT >= BITS_PER_BYTE * sizeof(info->req_ids));
if (info->req_ids)
return info->req_ids & (1U << id);
per_dev = data->sets[id].per_dev;
if (!per_dev && !data->sets[id].strings)
return false;
return data->base.dev ? per_dev : !per_dev;
}
static int strset_get_id(const struct nlattr *nest, u32 *val,
struct netlink_ext_ack *extack)
{
struct nlattr *tb[ETHTOOL_A_STRINGSET_MAX + 1];
int ret;
ret = nla_parse_nested(tb, ETHTOOL_A_STRINGSET_MAX, nest,
get_stringset_policy, extack);
if (ret < 0)
return ret;
if (!tb[ETHTOOL_A_STRINGSET_ID])
return -EINVAL;
*val = nla_get_u32(tb[ETHTOOL_A_STRINGSET_ID]);
return 0;
}
static const struct nla_policy
strset_stringsets_policy[ETHTOOL_A_STRINGSETS_MAX + 1] = {
[ETHTOOL_A_STRINGSETS_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_STRINGSETS_STRINGSET] = { .type = NLA_NESTED },
};
static int strset_parse_request(struct ethnl_req_info *req_base,
struct nlattr **tb,
struct netlink_ext_ack *extack)
{
struct strset_req_info *req_info = STRSET_REQINFO(req_base);
struct nlattr *nest = tb[ETHTOOL_A_STRSET_STRINGSETS];
struct nlattr *attr;
int rem, ret;
if (!nest)
return 0;
ret = nla_validate_nested(nest, ETHTOOL_A_STRINGSETS_MAX,
strset_stringsets_policy, extack);
if (ret < 0)
return ret;
req_info->counts_only = tb[ETHTOOL_A_STRSET_COUNTS_ONLY];
nla_for_each_nested(attr, nest, rem) {
u32 id;
if (WARN_ONCE(nla_type(attr) != ETHTOOL_A_STRINGSETS_STRINGSET,
"unexpected attrtype %u in ETHTOOL_A_STRSET_STRINGSETS\n",
nla_type(attr)))
return -EINVAL;
ret = strset_get_id(attr, &id, extack);
if (ret < 0)
return ret;
if (ret >= ETH_SS_COUNT) {
NL_SET_ERR_MSG_ATTR(extack, attr,
"unknown string set id");
return -EOPNOTSUPP;
}
req_info->req_ids |= (1U << id);
}
return 0;
}
static void strset_cleanup_data(struct ethnl_reply_data *reply_base)
{
struct strset_reply_data *data = STRSET_REPDATA(reply_base);
unsigned int i;
for (i = 0; i < ETH_SS_COUNT; i++)
if (data->sets[i].free_strings) {
kfree(data->sets[i].strings);
data->sets[i].strings = NULL;
data->sets[i].free_strings = false;
}
}
static int strset_prepare_set(struct strset_info *info, struct net_device *dev,
unsigned int id, bool counts_only)
{
const struct ethtool_ops *ops = dev->ethtool_ops;
void *strings;
int count, ret;
if (id == ETH_SS_PHY_STATS && dev->phydev &&
!ops->get_ethtool_phy_stats)
ret = phy_ethtool_get_sset_count(dev->phydev);
else if (ops->get_sset_count && ops->get_strings)
ret = ops->get_sset_count(dev, id);
else
ret = -EOPNOTSUPP;
if (ret <= 0) {
info->count = 0;
return 0;
}
count = ret;
if (!counts_only) {
strings = kcalloc(count, ETH_GSTRING_LEN, GFP_KERNEL);
if (!strings)
return -ENOMEM;
if (id == ETH_SS_PHY_STATS && dev->phydev &&
!ops->get_ethtool_phy_stats)
phy_ethtool_get_strings(dev->phydev, strings);
else
ops->get_strings(dev, id, strings);
info->strings = strings;
info->free_strings = true;
}
info->count = count;
return 0;
}
static int strset_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
struct genl_info *info)
{
const struct strset_req_info *req_info = STRSET_REQINFO(req_base);
struct strset_reply_data *data = STRSET_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
unsigned int i;
int ret;
BUILD_BUG_ON(ARRAY_SIZE(info_template) != ETH_SS_COUNT);
memcpy(&data->sets, &info_template, sizeof(data->sets));
if (!dev) {
for (i = 0; i < ETH_SS_COUNT; i++) {
if ((req_info->req_ids & (1U << i)) &&
data->sets[i].per_dev) {
if (info)
GENL_SET_ERR_MSG(info, "requested per device strings without dev");
return -EINVAL;
}
}
}
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto err_strset;
for (i = 0; i < ETH_SS_COUNT; i++) {
if (!strset_include(req_info, data, i) ||
!data->sets[i].per_dev)
continue;
ret = strset_prepare_set(&data->sets[i], dev, i,
req_info->counts_only);
if (ret < 0)
goto err_ops;
}
ethnl_ops_complete(dev);
return 0;
err_ops:
ethnl_ops_complete(dev);
err_strset:
strset_cleanup_data(reply_base);
return ret;
}
/* calculate size of ETHTOOL_A_STRSET_STRINGSET nest for one string set */
static int strset_set_size(const struct strset_info *info, bool counts_only)
{
unsigned int len = 0;
unsigned int i;
if (info->count == 0)
return 0;
if (counts_only)
return nla_total_size(2 * nla_total_size(sizeof(u32)));
for (i = 0; i < info->count; i++) {
const char *str = info->strings[i];
/* ETHTOOL_A_STRING_INDEX, ETHTOOL_A_STRING_VALUE, nest */
len += nla_total_size(nla_total_size(sizeof(u32)) +
ethnl_strz_size(str));
}
/* ETHTOOL_A_STRINGSET_ID, ETHTOOL_A_STRINGSET_COUNT */
len = 2 * nla_total_size(sizeof(u32)) + nla_total_size(len);
return nla_total_size(len);
}
static int strset_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct strset_req_info *req_info = STRSET_REQINFO(req_base);
const struct strset_reply_data *data = STRSET_REPDATA(reply_base);
unsigned int i;
int len = 0;
int ret;
len += ethnl_reply_header_size();
for (i = 0; i < ETH_SS_COUNT; i++) {
const struct strset_info *set_info = &data->sets[i];
if (!strset_include(req_info, data, i))
continue;
ret = strset_set_size(set_info, req_info->counts_only);
if (ret < 0)
return ret;
len += ret;
}
return len;
}
/* fill one string into reply */
static int strset_fill_string(struct sk_buff *skb,
const struct strset_info *set_info, u32 idx)
{
struct nlattr *string_attr;
const char *value;
value = set_info->strings[idx];
string_attr = nla_nest_start(skb, ETHTOOL_A_STRINGS_STRING);
if (!string_attr)
return -EMSGSIZE;
if (nla_put_u32(skb, ETHTOOL_A_STRING_INDEX, idx) ||
ethnl_put_strz(skb, ETHTOOL_A_STRING_VALUE, value))
goto nla_put_failure;
nla_nest_end(skb, string_attr);
return 0;
nla_put_failure:
nla_nest_cancel(skb, string_attr);
return -EMSGSIZE;
}
/* fill one string set into reply */
static int strset_fill_set(struct sk_buff *skb,
const struct strset_info *set_info, u32 id,
bool counts_only)
{
struct nlattr *stringset_attr;
struct nlattr *strings_attr;
unsigned int i;
if (!set_info->per_dev && !set_info->strings)
return -EOPNOTSUPP;
if (set_info->count == 0)
return 0;
stringset_attr = nla_nest_start(skb, ETHTOOL_A_STRINGSETS_STRINGSET);
if (!stringset_attr)
return -EMSGSIZE;
if (nla_put_u32(skb, ETHTOOL_A_STRINGSET_ID, id) ||
nla_put_u32(skb, ETHTOOL_A_STRINGSET_COUNT, set_info->count))
goto nla_put_failure;
if (!counts_only) {
strings_attr = nla_nest_start(skb, ETHTOOL_A_STRINGSET_STRINGS);
if (!strings_attr)
goto nla_put_failure;
for (i = 0; i < set_info->count; i++) {
if (strset_fill_string(skb, set_info, i) < 0)
goto nla_put_failure;
}
nla_nest_end(skb, strings_attr);
}
nla_nest_end(skb, stringset_attr);
return 0;
nla_put_failure:
nla_nest_cancel(skb, stringset_attr);
return -EMSGSIZE;
}
static int strset_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct strset_req_info *req_info = STRSET_REQINFO(req_base);
const struct strset_reply_data *data = STRSET_REPDATA(reply_base);
struct nlattr *nest;
unsigned int i;
int ret;
nest = nla_nest_start(skb, ETHTOOL_A_STRSET_STRINGSETS);
if (!nest)
return -EMSGSIZE;
for (i = 0; i < ETH_SS_COUNT; i++) {
if (strset_include(req_info, data, i)) {
ret = strset_fill_set(skb, &data->sets[i], i,
req_info->counts_only);
if (ret < 0)
goto nla_put_failure;
}
}
nla_nest_end(skb, nest);
return 0;
nla_put_failure:
nla_nest_cancel(skb, nest);
return ret;
}
const struct ethnl_request_ops ethnl_strset_request_ops = {
.request_cmd = ETHTOOL_MSG_STRSET_GET,
.reply_cmd = ETHTOOL_MSG_STRSET_GET_REPLY,
.hdr_attr = ETHTOOL_A_STRSET_HEADER,
.max_attr = ETHTOOL_A_STRSET_MAX,
.req_info_size = sizeof(struct strset_req_info),
.reply_data_size = sizeof(struct strset_reply_data),
.request_policy = strset_get_policy,
.allow_nodev_do = true,
.parse_request = strset_parse_request,
.prepare_data = strset_prepare_data,
.reply_size = strset_reply_size,
.fill_reply = strset_fill_reply,
.cleanup_data = strset_cleanup_data,
};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册