Commit 82a9822b authored by David S. Miller's avatar David S. Miller

Merge branch 'ethtool-netlink-interface-part-3'

Michal Kubecek says:

====================
ethtool netlink interface, part 3

Implementation of more netlink request types:

  - netdev features (ethtool -k/-K, patches 3-6)
  - private flags (--show-priv-flags / --set-priv-flags, patches 7-9)
  - ring sizes (ethtool -g/-G, patches 10-12)
  - channel counts (ethtool -l/-L, patches 13-15)

Patch 1 is a style cleanup suggested in part 2 review and patch 2 updates
the mapping between netdev features and legacy ioctl requests (which are
still used by ethtool for backward compatibility).

Changes in v2:
  - fix netdev reference leaks in error path of ethnl_set_rings() and
    ethnl_set_channels() (found by Jakub Kicinski)
  - use __set_bit() rather than set_bit() (suggested by David Miller)
  - in replies to RINGS_GET and CHANNELS_GET requests, omit ring and
    channel types not supported by driver/device (suggested by Jakub
    Kicinski)
  - more descriptive message size calculations in rings_reply_size() and
    channels_reply_size() (suggested by Jakub Kicinski)
  - coding style cleanup (suggested by Jakub Kicinski)
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents f8ab3047 546379b9
...@@ -189,6 +189,14 @@ Userspace to kernel: ...@@ -189,6 +189,14 @@ Userspace to kernel:
``ETHTOOL_MSG_DEBUG_SET`` set debugging settings ``ETHTOOL_MSG_DEBUG_SET`` set debugging settings
``ETHTOOL_MSG_WOL_GET`` get wake-on-lan settings ``ETHTOOL_MSG_WOL_GET`` get wake-on-lan settings
``ETHTOOL_MSG_WOL_SET`` set wake-on-lan settings ``ETHTOOL_MSG_WOL_SET`` set wake-on-lan settings
``ETHTOOL_MSG_FEATURES_GET`` get device features
``ETHTOOL_MSG_FEATURES_SET`` set device features
``ETHTOOL_MSG_PRIVFLAGS_GET`` get private flags
``ETHTOOL_MSG_PRIVFLAGS_SET`` set private flags
``ETHTOOL_MSG_RINGS_GET`` get ring sizes
``ETHTOOL_MSG_RINGS_SET`` set ring sizes
``ETHTOOL_MSG_CHANNELS_GET`` get channel counts
``ETHTOOL_MSG_CHANNELS_SET`` set channel counts
===================================== ================================ ===================================== ================================
Kernel to userspace: Kernel to userspace:
...@@ -204,6 +212,15 @@ Kernel to userspace: ...@@ -204,6 +212,15 @@ Kernel to userspace:
``ETHTOOL_MSG_DEBUG_NTF`` debugging settings notification ``ETHTOOL_MSG_DEBUG_NTF`` debugging settings notification
``ETHTOOL_MSG_WOL_GET_REPLY`` wake-on-lan settings ``ETHTOOL_MSG_WOL_GET_REPLY`` wake-on-lan settings
``ETHTOOL_MSG_WOL_NTF`` wake-on-lan settings notification ``ETHTOOL_MSG_WOL_NTF`` wake-on-lan settings notification
``ETHTOOL_MSG_FEATURES_GET_REPLY`` device features
``ETHTOOL_MSG_FEATURES_SET_REPLY`` optional reply to FEATURES_SET
``ETHTOOL_MSG_FEATURES_NTF`` netdev features notification
``ETHTOOL_MSG_PRIVFLAGS_GET_REPLY`` private flags
``ETHTOOL_MSG_PRIVFLAGS_NTF`` private flags
``ETHTOOL_MSG_RINGS_GET_REPLY`` ring sizes
``ETHTOOL_MSG_RINGS_NTF`` ring sizes
``ETHTOOL_MSG_CHANNELS_GET_REPLY`` channel counts
``ETHTOOL_MSG_CHANNELS_NTF`` channel counts
===================================== ================================= ===================================== =================================
``GET`` requests are sent by userspace applications to retrieve device ``GET`` requests are sent by userspace applications to retrieve device
...@@ -521,6 +538,213 @@ Request contents: ...@@ -521,6 +538,213 @@ Request contents:
``WAKE_MAGICSECURE`` mode. ``WAKE_MAGICSECURE`` mode.
FEATURES_GET
============
Gets netdev features like ``ETHTOOL_GFEATURES`` ioctl request.
Request contents:
==================================== ====== ==========================
``ETHTOOL_A_FEATURES_HEADER`` nested request header
==================================== ====== ==========================
Kernel response contents:
==================================== ====== ==========================
``ETHTOOL_A_FEATURES_HEADER`` nested reply header
``ETHTOOL_A_FEATURES_HW`` bitset dev->hw_features
``ETHTOOL_A_FEATURES_WANTED`` bitset dev->wanted_features
``ETHTOOL_A_FEATURES_ACTIVE`` bitset dev->features
``ETHTOOL_A_FEATURES_NOCHANGE`` bitset NETIF_F_NEVER_CHANGE
==================================== ====== ==========================
Bitmaps in kernel response have the same meaning as bitmaps used in ioctl
interference but attribute names are different (they are based on
corresponding members of struct net_device). Legacy "flags" are not provided,
if userspace needs them (most likely only ethtool for backward compatibility),
it can calculate their values from related feature bits itself.
ETHA_FEATURES_HW uses mask consisting of all features recognized by kernel (to
provide all names when using verbose bitmap format), the other three use no
mask (simple bit lists).
FEATURES_SET
============
Request to set netdev features like ``ETHTOOL_SFEATURES`` ioctl request.
Request contents:
==================================== ====== ==========================
``ETHTOOL_A_FEATURES_HEADER`` nested request header
``ETHTOOL_A_FEATURES_WANTED`` bitset requested features
==================================== ====== ==========================
Kernel response contents:
==================================== ====== ==========================
``ETHTOOL_A_FEATURES_HEADER`` nested reply header
``ETHTOOL_A_FEATURES_WANTED`` bitset diff wanted vs. result
``ETHTOOL_A_FEATURES_ACTIVE`` bitset diff old vs. new active
==================================== ====== ==========================
Request constains only one bitset which can be either value/mask pair (request
to change specific feature bits and leave the rest) or only a value (request
to set all features to specified set).
As request is subject to netdev_change_features() sanity checks, optional
kernel reply (can be suppressed by ``ETHTOOL_FLAG_OMIT_REPLY`` flag in request
header) informs client about the actual result. ``ETHTOOL_A_FEATURES_WANTED``
reports the difference between client request and actual result: mask consists
of bits which differ between requested features and result (dev->features
after the operation), value consists of values of these bits in the request
(i.e. negated values from resulting features). ``ETHTOOL_A_FEATURES_ACTIVE``
reports the difference between old and new dev->features: mask consists of
bits which have changed, values are their values in new dev->features (after
the operation).
``ETHTOOL_MSG_FEATURES_NTF`` notification is sent not only if device features
are modified using ``ETHTOOL_MSG_FEATURES_SET`` request or on of ethtool ioctl
request but also each time features are modified with netdev_update_features()
or netdev_change_features().
PRIVFLAGS_GET
=============
Gets private flags like ``ETHTOOL_GPFLAGS`` ioctl request.
Request contents:
==================================== ====== ==========================
``ETHTOOL_A_PRIVFLAGS_HEADER`` nested request header
==================================== ====== ==========================
Kernel response contents:
==================================== ====== ==========================
``ETHTOOL_A_PRIVFLAGS_HEADER`` nested reply header
``ETHTOOL_A_PRIVFLAGS_FLAGS`` bitset private flags
==================================== ====== ==========================
``ETHTOOL_A_PRIVFLAGS_FLAGS`` is a bitset with values of device private flags.
These flags are defined by driver, their number and names (and also meaning)
are device dependent. For compact bitset format, names can be retrieved as
``ETH_SS_PRIV_FLAGS`` string set. If verbose bitset format is requested,
response uses all private flags supported by the device as mask so that client
gets the full information without having to fetch the string set with names.
PRIVFLAGS_SET
=============
Sets or modifies values of device private flags like ``ETHTOOL_SPFLAGS``
ioctl request.
Request contents:
==================================== ====== ==========================
``ETHTOOL_A_PRIVFLAGS_HEADER`` nested request header
``ETHTOOL_A_PRIVFLAGS_FLAGS`` bitset private flags
==================================== ====== ==========================
``ETHTOOL_A_PRIVFLAGS_FLAGS`` can either set the whole set of private flags or
modify only values of some of them.
RINGS_GET
=========
Gets ring sizes like ``ETHTOOL_GRINGPARAM`` ioctl request.
Request contents:
==================================== ====== ==========================
``ETHTOOL_A_RINGS_HEADER`` nested request header
==================================== ====== ==========================
Kernel response contents:
==================================== ====== ==========================
``ETHTOOL_A_RINGS_HEADER`` nested reply header
``ETHTOOL_A_RINGS_RX_MAX`` u32 max size of RX ring
``ETHTOOL_A_RINGS_RX_MINI_MAX`` u32 max size of RX mini ring
``ETHTOOL_A_RINGS_RX_JUMBO_MAX`` u32 max size of RX jumbo ring
``ETHTOOL_A_RINGS_TX_MAX`` u32 max size of TX ring
``ETHTOOL_A_RINGS_RX`` u32 size of RX ring
``ETHTOOL_A_RINGS_RX_MINI`` u32 size of RX mini ring
``ETHTOOL_A_RINGS_RX_JUMBO`` u32 size of RX jumbo ring
``ETHTOOL_A_RINGS_TX`` u32 size of TX ring
==================================== ====== ==========================
RINGS_SET
=========
Sets ring sizes like ``ETHTOOL_SRINGPARAM`` ioctl request.
Request contents:
==================================== ====== ==========================
``ETHTOOL_A_RINGS_HEADER`` nested reply header
``ETHTOOL_A_RINGS_RX`` u32 size of RX ring
``ETHTOOL_A_RINGS_RX_MINI`` u32 size of RX mini ring
``ETHTOOL_A_RINGS_RX_JUMBO`` u32 size of RX jumbo ring
``ETHTOOL_A_RINGS_TX`` u32 size of TX ring
==================================== ====== ==========================
Kernel checks that requested ring sizes do not exceed limits reported by
driver. Driver may impose additional constraints and may not suspport all
attributes.
CHANNELS_GET
============
Gets channel counts like ``ETHTOOL_GCHANNELS`` ioctl request.
Request contents:
==================================== ====== ==========================
``ETHTOOL_A_CHANNELS_HEADER`` nested request header
==================================== ====== ==========================
Kernel response contents:
===================================== ====== ==========================
``ETHTOOL_A_CHANNELS_HEADER`` nested reply header
``ETHTOOL_A_CHANNELS_RX_MAX`` u32 max receive channels
``ETHTOOL_A_CHANNELS_TX_MAX`` u32 max transmit channels
``ETHTOOL_A_CHANNELS_OTHER_MAX`` u32 max other channels
``ETHTOOL_A_CHANNELS_COMBINED_MAX`` u32 max combined channels
``ETHTOOL_A_CHANNELS_RX_COUNT`` u32 receive channel count
``ETHTOOL_A_CHANNELS_TX_COUNT`` u32 transmit channel count
``ETHTOOL_A_CHANNELS_OTHER_COUNT`` u32 other channel count
``ETHTOOL_A_CHANNELS_COMBINED_COUNT`` u32 combined channel count
===================================== ====== ==========================
CHANNELS_SET
============
Sets channel counts like ``ETHTOOL_SCHANNELS`` ioctl request.
Request contents:
===================================== ====== ==========================
``ETHTOOL_A_CHANNELS_HEADER`` nested request header
``ETHTOOL_A_CHANNELS_RX_COUNT`` u32 receive channel count
``ETHTOOL_A_CHANNELS_TX_COUNT`` u32 transmit channel count
``ETHTOOL_A_CHANNELS_OTHER_COUNT`` u32 other channel count
``ETHTOOL_A_CHANNELS_COMBINED_COUNT`` u32 combined channel count
===================================== ====== ==========================
Kernel checks that requested channel counts do not exceed limits reported by
driver. Driver may impose additional constraints and may not suspport all
attributes.
Request translation Request translation
=================== ===================
...@@ -547,35 +771,35 @@ have their netlink replacement yet. ...@@ -547,35 +771,35 @@ have their netlink replacement yet.
``ETHTOOL_SEEPROM`` n/a ``ETHTOOL_SEEPROM`` n/a
``ETHTOOL_GCOALESCE`` n/a ``ETHTOOL_GCOALESCE`` n/a
``ETHTOOL_SCOALESCE`` n/a ``ETHTOOL_SCOALESCE`` n/a
``ETHTOOL_GRINGPARAM`` n/a ``ETHTOOL_GRINGPARAM`` ``ETHTOOL_MSG_RINGS_GET``
``ETHTOOL_SRINGPARAM`` n/a ``ETHTOOL_SRINGPARAM`` ``ETHTOOL_MSG_RINGS_SET``
``ETHTOOL_GPAUSEPARAM`` n/a ``ETHTOOL_GPAUSEPARAM`` n/a
``ETHTOOL_SPAUSEPARAM`` n/a ``ETHTOOL_SPAUSEPARAM`` n/a
``ETHTOOL_GRXCSUM`` n/a ``ETHTOOL_GRXCSUM`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_SRXCSUM`` n/a ``ETHTOOL_SRXCSUM`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GTXCSUM`` n/a ``ETHTOOL_GTXCSUM`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_STXCSUM`` n/a ``ETHTOOL_STXCSUM`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GSG`` n/a ``ETHTOOL_GSG`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_SSG`` n/a ``ETHTOOL_SSG`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_TEST`` n/a ``ETHTOOL_TEST`` n/a
``ETHTOOL_GSTRINGS`` ``ETHTOOL_MSG_STRSET_GET`` ``ETHTOOL_GSTRINGS`` ``ETHTOOL_MSG_STRSET_GET``
``ETHTOOL_PHYS_ID`` n/a ``ETHTOOL_PHYS_ID`` n/a
``ETHTOOL_GSTATS`` n/a ``ETHTOOL_GSTATS`` n/a
``ETHTOOL_GTSO`` n/a ``ETHTOOL_GTSO`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_STSO`` n/a ``ETHTOOL_STSO`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GPERMADDR`` rtnetlink ``RTM_GETLINK`` ``ETHTOOL_GPERMADDR`` rtnetlink ``RTM_GETLINK``
``ETHTOOL_GUFO`` n/a ``ETHTOOL_GUFO`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_SUFO`` n/a ``ETHTOOL_SUFO`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GGSO`` n/a ``ETHTOOL_GGSO`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_SGSO`` n/a ``ETHTOOL_SGSO`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GFLAGS`` n/a ``ETHTOOL_GFLAGS`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_SFLAGS`` n/a ``ETHTOOL_SFLAGS`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GPFLAGS`` n/a ``ETHTOOL_GPFLAGS`` ``ETHTOOL_MSG_PRIVFLAGS_GET``
``ETHTOOL_SPFLAGS`` n/a ``ETHTOOL_SPFLAGS`` ``ETHTOOL_MSG_PRIVFLAGS_SET``
``ETHTOOL_GRXFH`` n/a ``ETHTOOL_GRXFH`` n/a
``ETHTOOL_SRXFH`` n/a ``ETHTOOL_SRXFH`` n/a
``ETHTOOL_GGRO`` n/a ``ETHTOOL_GGRO`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_SGRO`` n/a ``ETHTOOL_SGRO`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GRXRINGS`` n/a ``ETHTOOL_GRXRINGS`` n/a
``ETHTOOL_GRXCLSRLCNT`` n/a ``ETHTOOL_GRXCLSRLCNT`` n/a
``ETHTOOL_GRXCLSRULE`` n/a ``ETHTOOL_GRXCLSRULE`` n/a
...@@ -589,10 +813,10 @@ have their netlink replacement yet. ...@@ -589,10 +813,10 @@ have their netlink replacement yet.
``ETHTOOL_GSSET_INFO`` ``ETHTOOL_MSG_STRSET_GET`` ``ETHTOOL_GSSET_INFO`` ``ETHTOOL_MSG_STRSET_GET``
``ETHTOOL_GRXFHINDIR`` n/a ``ETHTOOL_GRXFHINDIR`` n/a
``ETHTOOL_SRXFHINDIR`` n/a ``ETHTOOL_SRXFHINDIR`` n/a
``ETHTOOL_GFEATURES`` n/a ``ETHTOOL_GFEATURES`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_SFEATURES`` n/a ``ETHTOOL_SFEATURES`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GCHANNELS`` n/a ``ETHTOOL_GCHANNELS`` ``ETHTOOL_MSG_CHANNELS_GET``
``ETHTOOL_SCHANNELS`` n/a ``ETHTOOL_SCHANNELS`` ``ETHTOOL_MSG_CHANNELS_SET``
``ETHTOOL_SET_DUMP`` n/a ``ETHTOOL_SET_DUMP`` n/a
``ETHTOOL_GET_DUMP_FLAG`` n/a ``ETHTOOL_GET_DUMP_FLAG`` n/a
``ETHTOOL_GET_DUMP_DATA`` n/a ``ETHTOOL_GET_DUMP_DATA`` n/a
......
...@@ -24,6 +24,14 @@ enum { ...@@ -24,6 +24,14 @@ enum {
ETHTOOL_MSG_DEBUG_SET, ETHTOOL_MSG_DEBUG_SET,
ETHTOOL_MSG_WOL_GET, ETHTOOL_MSG_WOL_GET,
ETHTOOL_MSG_WOL_SET, ETHTOOL_MSG_WOL_SET,
ETHTOOL_MSG_FEATURES_GET,
ETHTOOL_MSG_FEATURES_SET,
ETHTOOL_MSG_PRIVFLAGS_GET,
ETHTOOL_MSG_PRIVFLAGS_SET,
ETHTOOL_MSG_RINGS_GET,
ETHTOOL_MSG_RINGS_SET,
ETHTOOL_MSG_CHANNELS_GET,
ETHTOOL_MSG_CHANNELS_SET,
/* add new constants above here */ /* add new constants above here */
__ETHTOOL_MSG_USER_CNT, __ETHTOOL_MSG_USER_CNT,
...@@ -43,6 +51,15 @@ enum { ...@@ -43,6 +51,15 @@ enum {
ETHTOOL_MSG_DEBUG_NTF, ETHTOOL_MSG_DEBUG_NTF,
ETHTOOL_MSG_WOL_GET_REPLY, ETHTOOL_MSG_WOL_GET_REPLY,
ETHTOOL_MSG_WOL_NTF, ETHTOOL_MSG_WOL_NTF,
ETHTOOL_MSG_FEATURES_GET_REPLY,
ETHTOOL_MSG_FEATURES_SET_REPLY,
ETHTOOL_MSG_FEATURES_NTF,
ETHTOOL_MSG_PRIVFLAGS_GET_REPLY,
ETHTOOL_MSG_PRIVFLAGS_NTF,
ETHTOOL_MSG_RINGS_GET_REPLY,
ETHTOOL_MSG_RINGS_NTF,
ETHTOOL_MSG_CHANNELS_GET_REPLY,
ETHTOOL_MSG_CHANNELS_NTF,
/* add new constants above here */ /* add new constants above here */
__ETHTOOL_MSG_KERNEL_CNT, __ETHTOOL_MSG_KERNEL_CNT,
...@@ -228,6 +245,71 @@ enum { ...@@ -228,6 +245,71 @@ enum {
ETHTOOL_A_WOL_MAX = __ETHTOOL_A_WOL_CNT - 1 ETHTOOL_A_WOL_MAX = __ETHTOOL_A_WOL_CNT - 1
}; };
/* FEATURES */
enum {
ETHTOOL_A_FEATURES_UNSPEC,
ETHTOOL_A_FEATURES_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_FEATURES_HW, /* bitset */
ETHTOOL_A_FEATURES_WANTED, /* bitset */
ETHTOOL_A_FEATURES_ACTIVE, /* bitset */
ETHTOOL_A_FEATURES_NOCHANGE, /* bitset */
/* add new constants above here */
__ETHTOOL_A_FEATURES_CNT,
ETHTOOL_A_FEATURES_MAX = __ETHTOOL_A_FEATURES_CNT - 1
};
/* PRIVFLAGS */
enum {
ETHTOOL_A_PRIVFLAGS_UNSPEC,
ETHTOOL_A_PRIVFLAGS_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_PRIVFLAGS_FLAGS, /* bitset */
/* add new constants above here */
__ETHTOOL_A_PRIVFLAGS_CNT,
ETHTOOL_A_PRIVFLAGS_MAX = __ETHTOOL_A_PRIVFLAGS_CNT - 1
};
/* RINGS */
enum {
ETHTOOL_A_RINGS_UNSPEC,
ETHTOOL_A_RINGS_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_RINGS_RX_MAX, /* u32 */
ETHTOOL_A_RINGS_RX_MINI_MAX, /* u32 */
ETHTOOL_A_RINGS_RX_JUMBO_MAX, /* u32 */
ETHTOOL_A_RINGS_TX_MAX, /* u32 */
ETHTOOL_A_RINGS_RX, /* u32 */
ETHTOOL_A_RINGS_RX_MINI, /* u32 */
ETHTOOL_A_RINGS_RX_JUMBO, /* u32 */
ETHTOOL_A_RINGS_TX, /* u32 */
/* add new constants above here */
__ETHTOOL_A_RINGS_CNT,
ETHTOOL_A_RINGS_MAX = (__ETHTOOL_A_RINGS_CNT - 1)
};
/* CHANNELS */
enum {
ETHTOOL_A_CHANNELS_UNSPEC,
ETHTOOL_A_CHANNELS_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_CHANNELS_RX_MAX, /* u32 */
ETHTOOL_A_CHANNELS_TX_MAX, /* u32 */
ETHTOOL_A_CHANNELS_OTHER_MAX, /* u32 */
ETHTOOL_A_CHANNELS_COMBINED_MAX, /* u32 */
ETHTOOL_A_CHANNELS_RX_COUNT, /* u32 */
ETHTOOL_A_CHANNELS_TX_COUNT, /* u32 */
ETHTOOL_A_CHANNELS_OTHER_COUNT, /* u32 */
ETHTOOL_A_CHANNELS_COMBINED_COUNT, /* u32 */
/* add new constants above here */
__ETHTOOL_A_CHANNELS_CNT,
ETHTOOL_A_CHANNELS_MAX = (__ETHTOOL_A_CHANNELS_CNT - 1)
};
/* generic netlink info */ /* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool" #define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1 #define ETHTOOL_GENL_VERSION 1
......
...@@ -5,4 +5,5 @@ obj-y += ioctl.o common.o ...@@ -5,4 +5,5 @@ obj-y += ioctl.o common.o
obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o
ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o \ ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o \
linkstate.o debug.o wol.o linkstate.o debug.o wol.o features.o privflags.o rings.o \
channels.o
...@@ -588,6 +588,100 @@ int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits, ...@@ -588,6 +588,100 @@ int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits,
return 0; return 0;
} }
/**
* ethnl_parse_bitset() - Compute effective value and mask from bitset nest
* @val: unsigned long based bitmap to put value into
* @mask: unsigned long based bitmap to put mask into
* @nbits: size of @val and @mask bitmaps
* @attr: nest attribute to parse and apply
* @names: array of bit names; may be null for compact format
* @extack: extack for error reporting
*
* Provide @nbits size long bitmaps for value and mask so that
* x = (val & mask) | (x & ~mask) would modify any @nbits sized bitmap x
* the same way ethnl_update_bitset() with the same bitset attribute would.
*
* Return: negative error code on failure, 0 on success
*/
int ethnl_parse_bitset(unsigned long *val, unsigned long *mask,
unsigned int nbits, const struct nlattr *attr,
ethnl_string_array_t names,
struct netlink_ext_ack *extack)
{
struct nlattr *tb[ETHTOOL_A_BITSET_MAX + 1];
const struct nlattr *bit_attr;
bool no_mask;
int rem;
int ret;
if (!attr)
return 0;
ret = nla_parse_nested(tb, ETHTOOL_A_BITSET_MAX, attr, bitset_policy,
extack);
if (ret < 0)
return ret;
no_mask = tb[ETHTOOL_A_BITSET_NOMASK];
if (!tb[ETHTOOL_A_BITSET_BITS]) {
unsigned int change_bits;
ret = ethnl_compact_sanity_checks(nbits, attr, tb, extack);
if (ret < 0)
return ret;
change_bits = nla_get_u32(tb[ETHTOOL_A_BITSET_SIZE]);
bitmap_from_arr32(val, nla_data(tb[ETHTOOL_A_BITSET_VALUE]),
change_bits);
if (change_bits < nbits)
bitmap_clear(val, change_bits, nbits - change_bits);
if (no_mask) {
bitmap_fill(mask, nbits);
} else {
bitmap_from_arr32(mask,
nla_data(tb[ETHTOOL_A_BITSET_MASK]),
change_bits);
if (change_bits < nbits)
bitmap_clear(mask, change_bits,
nbits - change_bits);
}
return 0;
}
if (tb[ETHTOOL_A_BITSET_VALUE]) {
NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_VALUE],
"value only allowed in compact bitset");
return -EINVAL;
}
if (tb[ETHTOOL_A_BITSET_MASK]) {
NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_BITSET_MASK],
"mask only allowed in compact bitset");
return -EINVAL;
}
bitmap_zero(val, nbits);
if (no_mask)
bitmap_fill(mask, nbits);
else
bitmap_zero(mask, nbits);
nla_for_each_nested(bit_attr, tb[ETHTOOL_A_BITSET_BITS], rem) {
unsigned int idx;
bool bit_val;
ret = ethnl_parse_bit(&idx, &bit_val, nbits, bit_attr, no_mask,
names, extack);
if (ret < 0)
return ret;
if (bit_val)
__set_bit(idx, val);
if (!no_mask)
__set_bit(idx, mask);
}
return 0;
}
#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN) #if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
/* 64-bit big endian architectures are the only case when u32 based bitmaps /* 64-bit big endian architectures are the only case when u32 based bitmaps
......
...@@ -26,5 +26,9 @@ int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits, ...@@ -26,5 +26,9 @@ int ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits, int ethnl_update_bitset32(u32 *bitmap, unsigned int nbits,
const struct nlattr *attr, ethnl_string_array_t names, const struct nlattr *attr, ethnl_string_array_t names,
struct netlink_ext_ack *extack, bool *mod); struct netlink_ext_ack *extack, bool *mod);
int ethnl_parse_bitset(unsigned long *val, unsigned long *mask,
unsigned int nbits, const struct nlattr *attr,
ethnl_string_array_t names,
struct netlink_ext_ack *extack);
#endif /* _NET_ETHTOOL_BITSET_H */ #endif /* _NET_ETHTOOL_BITSET_H */
// SPDX-License-Identifier: GPL-2.0-only
#include <net/xdp_sock.h>
#include "netlink.h"
#include "common.h"
struct channels_req_info {
struct ethnl_req_info base;
};
struct channels_reply_data {
struct ethnl_reply_data base;
struct ethtool_channels channels;
};
#define CHANNELS_REPDATA(__reply_base) \
container_of(__reply_base, struct channels_reply_data, base)
static const struct nla_policy
channels_get_policy[ETHTOOL_A_CHANNELS_MAX + 1] = {
[ETHTOOL_A_CHANNELS_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_CHANNELS_RX_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_TX_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_OTHER_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_COMBINED_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_RX_COUNT] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_TX_COUNT] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_OTHER_COUNT] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_COMBINED_COUNT] = { .type = NLA_REJECT },
};
static int channels_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
struct genl_info *info)
{
struct channels_reply_data *data = CHANNELS_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
int ret;
if (!dev->ethtool_ops->get_channels)
return -EOPNOTSUPP;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
dev->ethtool_ops->get_channels(dev, &data->channels);
ethnl_ops_complete(dev);
return 0;
}
static int channels_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
return nla_total_size(sizeof(u32)) + /* _CHANNELS_RX_MAX */
nla_total_size(sizeof(u32)) + /* _CHANNELS_TX_MAX */
nla_total_size(sizeof(u32)) + /* _CHANNELS_OTHER_MAX */
nla_total_size(sizeof(u32)) + /* _CHANNELS_COMBINED_MAX */
nla_total_size(sizeof(u32)) + /* _CHANNELS_RX_COUNT */
nla_total_size(sizeof(u32)) + /* _CHANNELS_TX_COUNT */
nla_total_size(sizeof(u32)) + /* _CHANNELS_OTHER_COUNT */
nla_total_size(sizeof(u32)); /* _CHANNELS_COMBINED_COUNT */
}
static int channels_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct channels_reply_data *data = CHANNELS_REPDATA(reply_base);
const struct ethtool_channels *channels = &data->channels;
if ((channels->max_rx &&
(nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_MAX,
channels->max_rx) ||
nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_COUNT,
channels->rx_count))) ||
(channels->max_tx &&
(nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_MAX,
channels->max_tx) ||
nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_COUNT,
channels->tx_count))) ||
(channels->max_other &&
(nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_MAX,
channels->max_other) ||
nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_COUNT,
channels->other_count))) ||
(channels->max_combined &&
(nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_MAX,
channels->max_combined) ||
nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_COUNT,
channels->combined_count))))
return -EMSGSIZE;
return 0;
}
const struct ethnl_request_ops ethnl_channels_request_ops = {
.request_cmd = ETHTOOL_MSG_CHANNELS_GET,
.reply_cmd = ETHTOOL_MSG_CHANNELS_GET_REPLY,
.hdr_attr = ETHTOOL_A_CHANNELS_HEADER,
.max_attr = ETHTOOL_A_CHANNELS_MAX,
.req_info_size = sizeof(struct channels_req_info),
.reply_data_size = sizeof(struct channels_reply_data),
.request_policy = channels_get_policy,
.prepare_data = channels_prepare_data,
.reply_size = channels_reply_size,
.fill_reply = channels_fill_reply,
};
/* CHANNELS_SET */
static const struct nla_policy
channels_set_policy[ETHTOOL_A_CHANNELS_MAX + 1] = {
[ETHTOOL_A_CHANNELS_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_CHANNELS_RX_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_TX_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_OTHER_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_COMBINED_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_CHANNELS_RX_COUNT] = { .type = NLA_U32 },
[ETHTOOL_A_CHANNELS_TX_COUNT] = { .type = NLA_U32 },
[ETHTOOL_A_CHANNELS_OTHER_COUNT] = { .type = NLA_U32 },
[ETHTOOL_A_CHANNELS_COMBINED_COUNT] = { .type = NLA_U32 },
};
int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *tb[ETHTOOL_A_CHANNELS_MAX + 1];
unsigned int from_channel, old_total, i;
struct ethtool_channels channels = {};
struct ethnl_req_info req_info = {};
const struct nlattr *err_attr;
const struct ethtool_ops *ops;
struct net_device *dev;
u32 max_rx_in_use = 0;
bool mod = false;
int ret;
ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
ETHTOOL_A_CHANNELS_MAX, channels_set_policy,
info->extack);
if (ret < 0)
return ret;
ret = ethnl_parse_header_dev_get(&req_info,
tb[ETHTOOL_A_CHANNELS_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0)
return ret;
dev = req_info.dev;
ops = dev->ethtool_ops;
ret = -EOPNOTSUPP;
if (!ops->get_channels || !ops->set_channels)
goto out_dev;
rtnl_lock();
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
ops->get_channels(dev, &channels);
old_total = channels.combined_count +
max(channels.rx_count, channels.tx_count);
ethnl_update_u32(&channels.rx_count, tb[ETHTOOL_A_CHANNELS_RX_COUNT],
&mod);
ethnl_update_u32(&channels.tx_count, tb[ETHTOOL_A_CHANNELS_TX_COUNT],
&mod);
ethnl_update_u32(&channels.other_count,
tb[ETHTOOL_A_CHANNELS_OTHER_COUNT], &mod);
ethnl_update_u32(&channels.combined_count,
tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT], &mod);
ret = 0;
if (!mod)
goto out_ops;
/* ensure new channel counts are within limits */
if (channels.rx_count > channels.max_rx)
err_attr = tb[ETHTOOL_A_CHANNELS_RX_COUNT];
else if (channels.tx_count > channels.max_tx)
err_attr = tb[ETHTOOL_A_CHANNELS_TX_COUNT];
else if (channels.other_count > channels.max_other)
err_attr = tb[ETHTOOL_A_CHANNELS_OTHER_COUNT];
else if (channels.combined_count > channels.max_combined)
err_attr = tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT];
else
err_attr = NULL;
if (err_attr) {
ret = -EINVAL;
NL_SET_ERR_MSG_ATTR(info->extack, err_attr,
"requested channel count exceeeds maximum");
goto out_ops;
}
/* ensure the new Rx count fits within the configured Rx flow
* indirection table settings
*/
if (netif_is_rxfh_configured(dev) &&
!ethtool_get_max_rxfh_channel(dev, &max_rx_in_use) &&
(channels.combined_count + channels.rx_count) <= max_rx_in_use) {
GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing indirection table settings");
return -EINVAL;
}
/* Disabling channels, query zero-copy AF_XDP sockets */
from_channel = channels.combined_count +
min(channels.rx_count, channels.tx_count);
for (i = from_channel; i < old_total; i++)
if (xdp_get_umem_from_qid(dev, i)) {
GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing zerocopy AF_XDP sockets");
return -EINVAL;
}
ret = dev->ethtool_ops->set_channels(dev, &channels);
if (ret < 0)
goto out_ops;
ethtool_notify(dev, ETHTOOL_MSG_CHANNELS_NTF, NULL);
out_ops:
ethnl_ops_complete(dev);
out_rtnl:
rtnl_unlock();
out_dev:
dev_put(dev);
return ret;
}
...@@ -258,3 +258,34 @@ int __ethtool_get_link(struct net_device *dev) ...@@ -258,3 +258,34 @@ int __ethtool_get_link(struct net_device *dev)
return netif_running(dev) && dev->ethtool_ops->get_link(dev); return netif_running(dev) && dev->ethtool_ops->get_link(dev);
} }
int ethtool_get_max_rxfh_channel(struct net_device *dev, u32 *max)
{
u32 dev_size, current_max = 0;
u32 *indir;
int ret;
if (!dev->ethtool_ops->get_rxfh_indir_size ||
!dev->ethtool_ops->get_rxfh)
return -EOPNOTSUPP;
dev_size = dev->ethtool_ops->get_rxfh_indir_size(dev);
if (dev_size == 0)
return -EOPNOTSUPP;
indir = kcalloc(dev_size, sizeof(indir[0]), GFP_USER);
if (!indir)
return -ENOMEM;
ret = dev->ethtool_ops->get_rxfh(dev, indir, NULL, NULL);
if (ret)
goto out;
while (dev_size--)
current_max = max(current_max, indir[dev_size]);
*max = current_max;
out:
kfree(indir);
return ret;
}
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/ethtool.h> #include <linux/ethtool.h>
#define ETHTOOL_DEV_FEATURE_WORDS DIV_ROUND_UP(NETDEV_FEATURE_COUNT, 32)
/* compose link mode index from speed, type and duplex */ /* compose link mode index from speed, type and duplex */
#define ETHTOOL_LINK_MODE(speed, type, duplex) \ #define ETHTOOL_LINK_MODE(speed, type, duplex) \
ETHTOOL_LINK_MODE_ ## speed ## base ## type ## _ ## duplex ## _BIT ETHTOOL_LINK_MODE_ ## speed ## base ## type ## _ ## duplex ## _BIT
...@@ -27,5 +29,6 @@ int __ethtool_get_link(struct net_device *dev); ...@@ -27,5 +29,6 @@ int __ethtool_get_link(struct net_device *dev);
bool convert_legacy_settings_to_link_ksettings( bool convert_legacy_settings_to_link_ksettings(
struct ethtool_link_ksettings *link_ksettings, struct ethtool_link_ksettings *link_ksettings,
const struct ethtool_cmd *legacy_settings); const struct ethtool_cmd *legacy_settings);
int ethtool_get_max_rxfh_channel(struct net_device *dev, u32 *max);
#endif /* _ETHTOOL_COMMON_H */ #endif /* _ETHTOOL_COMMON_H */
...@@ -102,8 +102,10 @@ int ethnl_set_debug(struct sk_buff *skb, struct genl_info *info) ...@@ -102,8 +102,10 @@ int ethnl_set_debug(struct sk_buff *skb, struct genl_info *info)
info->extack); info->extack);
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_DEBUG_HEADER], ret = ethnl_parse_header_dev_get(&req_info,
genl_info_net(info), info->extack, true); tb[ETHTOOL_A_DEBUG_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0) if (ret < 0)
return ret; return ret;
dev = req_info.dev; dev = req_info.dev;
......
// SPDX-License-Identifier: GPL-2.0-only
#include "netlink.h"
#include "common.h"
#include "bitset.h"
struct features_req_info {
struct ethnl_req_info base;
};
struct features_reply_data {
struct ethnl_reply_data base;
u32 hw[ETHTOOL_DEV_FEATURE_WORDS];
u32 wanted[ETHTOOL_DEV_FEATURE_WORDS];
u32 active[ETHTOOL_DEV_FEATURE_WORDS];
u32 nochange[ETHTOOL_DEV_FEATURE_WORDS];
u32 all[ETHTOOL_DEV_FEATURE_WORDS];
};
#define FEATURES_REPDATA(__reply_base) \
container_of(__reply_base, struct features_reply_data, base)
static const struct nla_policy
features_get_policy[ETHTOOL_A_FEATURES_MAX + 1] = {
[ETHTOOL_A_FEATURES_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_FEATURES_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_FEATURES_HW] = { .type = NLA_REJECT },
[ETHTOOL_A_FEATURES_WANTED] = { .type = NLA_REJECT },
[ETHTOOL_A_FEATURES_ACTIVE] = { .type = NLA_REJECT },
[ETHTOOL_A_FEATURES_NOCHANGE] = { .type = NLA_REJECT },
};
static void ethnl_features_to_bitmap32(u32 *dest, netdev_features_t src)
{
unsigned int i;
for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; i++)
dest[i] = src >> (32 * i);
}
static int features_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
struct genl_info *info)
{
struct features_reply_data *data = FEATURES_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
netdev_features_t all_features;
ethnl_features_to_bitmap32(data->hw, dev->hw_features);
ethnl_features_to_bitmap32(data->wanted, dev->wanted_features);
ethnl_features_to_bitmap32(data->active, dev->features);
ethnl_features_to_bitmap32(data->nochange, NETIF_F_NEVER_CHANGE);
all_features = GENMASK_ULL(NETDEV_FEATURE_COUNT - 1, 0);
ethnl_features_to_bitmap32(data->all, all_features);
return 0;
}
static int features_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct features_reply_data *data = FEATURES_REPDATA(reply_base);
bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
unsigned int len = 0;
int ret;
ret = ethnl_bitset32_size(data->hw, data->all, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
return ret;
len += ret;
ret = ethnl_bitset32_size(data->wanted, NULL, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
return ret;
len += ret;
ret = ethnl_bitset32_size(data->active, NULL, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
return ret;
len += ret;
ret = ethnl_bitset32_size(data->nochange, NULL, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
return ret;
len += ret;
return len;
}
static int features_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct features_reply_data *data = FEATURES_REPDATA(reply_base);
bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
int ret;
ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_HW, data->hw,
data->all, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
return ret;
ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_WANTED, data->wanted,
NULL, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
return ret;
ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_ACTIVE, data->active,
NULL, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
return ret;
return ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_NOCHANGE,
data->nochange, NULL, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
}
const struct ethnl_request_ops ethnl_features_request_ops = {
.request_cmd = ETHTOOL_MSG_FEATURES_GET,
.reply_cmd = ETHTOOL_MSG_FEATURES_GET_REPLY,
.hdr_attr = ETHTOOL_A_FEATURES_HEADER,
.max_attr = ETHTOOL_A_FEATURES_MAX,
.req_info_size = sizeof(struct features_req_info),
.reply_data_size = sizeof(struct features_reply_data),
.request_policy = features_get_policy,
.prepare_data = features_prepare_data,
.reply_size = features_reply_size,
.fill_reply = features_fill_reply,
};
/* FEATURES_SET */
static const struct nla_policy
features_set_policy[ETHTOOL_A_FEATURES_MAX + 1] = {
[ETHTOOL_A_FEATURES_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_FEATURES_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_FEATURES_HW] = { .type = NLA_REJECT },
[ETHTOOL_A_FEATURES_WANTED] = { .type = NLA_NESTED },
[ETHTOOL_A_FEATURES_ACTIVE] = { .type = NLA_REJECT },
[ETHTOOL_A_FEATURES_NOCHANGE] = { .type = NLA_REJECT },
};
static void ethnl_features_to_bitmap(unsigned long *dest, netdev_features_t val)
{
const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
unsigned int i;
bitmap_zero(dest, NETDEV_FEATURE_COUNT);
for (i = 0; i < words; i++)
dest[i] = (unsigned long)(val >> (i * BITS_PER_LONG));
}
static netdev_features_t ethnl_bitmap_to_features(unsigned long *src)
{
const unsigned int nft_bits = sizeof(netdev_features_t) * BITS_PER_BYTE;
const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
netdev_features_t ret = 0;
unsigned int i;
for (i = 0; i < words; i++)
ret |= (netdev_features_t)(src[i]) << (i * BITS_PER_LONG);
ret &= ~(netdev_features_t)0 >> (nft_bits - NETDEV_FEATURE_COUNT);
return ret;
}
static int features_send_reply(struct net_device *dev, struct genl_info *info,
const unsigned long *wanted,
const unsigned long *wanted_mask,
const unsigned long *active,
const unsigned long *active_mask, bool compact)
{
struct sk_buff *rskb;
void *reply_payload;
int reply_len = 0;
int ret;
reply_len = ethnl_reply_header_size();
ret = ethnl_bitset_size(wanted, wanted_mask, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
goto err;
reply_len += ret;
ret = ethnl_bitset_size(active, active_mask, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
goto err;
reply_len += ret;
ret = -ENOMEM;
rskb = ethnl_reply_init(reply_len, dev, ETHTOOL_MSG_FEATURES_SET_REPLY,
ETHTOOL_A_FEATURES_HEADER, info,
&reply_payload);
if (!rskb)
goto err;
ret = ethnl_put_bitset(rskb, ETHTOOL_A_FEATURES_WANTED, wanted,
wanted_mask, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
goto nla_put_failure;
ret = ethnl_put_bitset(rskb, ETHTOOL_A_FEATURES_ACTIVE, active,
active_mask, NETDEV_FEATURE_COUNT,
netdev_features_strings, compact);
if (ret < 0)
goto nla_put_failure;
genlmsg_end(rskb, reply_payload);
ret = genlmsg_reply(rskb, info);
return ret;
nla_put_failure:
nlmsg_free(rskb);
WARN_ONCE(1, "calculated message payload length (%d) not sufficient\n",
reply_len);
err:
GENL_SET_ERR_MSG(info, "failed to send reply message");
return ret;
}
int ethnl_set_features(struct sk_buff *skb, struct genl_info *info)
{
DECLARE_BITMAP(wanted_diff_mask, NETDEV_FEATURE_COUNT);
DECLARE_BITMAP(active_diff_mask, NETDEV_FEATURE_COUNT);
DECLARE_BITMAP(old_active, NETDEV_FEATURE_COUNT);
DECLARE_BITMAP(new_active, NETDEV_FEATURE_COUNT);
DECLARE_BITMAP(req_wanted, NETDEV_FEATURE_COUNT);
DECLARE_BITMAP(req_mask, NETDEV_FEATURE_COUNT);
struct nlattr *tb[ETHTOOL_A_FEATURES_MAX + 1];
struct ethnl_req_info req_info = {};
struct net_device *dev;
bool mod;
int ret;
ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
ETHTOOL_A_FEATURES_MAX, features_set_policy,
info->extack);
if (ret < 0)
return ret;
if (!tb[ETHTOOL_A_FEATURES_WANTED])
return -EINVAL;
ret = ethnl_parse_header_dev_get(&req_info,
tb[ETHTOOL_A_FEATURES_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0)
return ret;
dev = req_info.dev;
rtnl_lock();
ethnl_features_to_bitmap(old_active, dev->features);
ret = ethnl_parse_bitset(req_wanted, req_mask, NETDEV_FEATURE_COUNT,
tb[ETHTOOL_A_FEATURES_WANTED],
netdev_features_strings, info->extack);
if (ret < 0)
goto out_rtnl;
if (ethnl_bitmap_to_features(req_mask) & ~NETIF_F_ETHTOOL_BITS) {
GENL_SET_ERR_MSG(info, "attempt to change non-ethtool features");
ret = -EINVAL;
goto out_rtnl;
}
/* set req_wanted bits not in req_mask from old_active */
bitmap_and(req_wanted, req_wanted, req_mask, NETDEV_FEATURE_COUNT);
bitmap_andnot(new_active, old_active, req_mask, NETDEV_FEATURE_COUNT);
bitmap_or(req_wanted, new_active, req_wanted, NETDEV_FEATURE_COUNT);
if (bitmap_equal(req_wanted, old_active, NETDEV_FEATURE_COUNT)) {
ret = 0;
goto out_rtnl;
}
dev->wanted_features = ethnl_bitmap_to_features(req_wanted);
__netdev_update_features(dev);
ethnl_features_to_bitmap(new_active, dev->features);
mod = !bitmap_equal(old_active, new_active, NETDEV_FEATURE_COUNT);
ret = 0;
if (!(req_info.flags & ETHTOOL_FLAG_OMIT_REPLY)) {
bool compact = req_info.flags & ETHTOOL_FLAG_COMPACT_BITSETS;
bitmap_xor(wanted_diff_mask, req_wanted, new_active,
NETDEV_FEATURE_COUNT);
bitmap_xor(active_diff_mask, old_active, new_active,
NETDEV_FEATURE_COUNT);
bitmap_and(wanted_diff_mask, wanted_diff_mask, req_mask,
NETDEV_FEATURE_COUNT);
bitmap_and(req_wanted, req_wanted, wanted_diff_mask,
NETDEV_FEATURE_COUNT);
bitmap_and(new_active, new_active, active_diff_mask,
NETDEV_FEATURE_COUNT);
ret = features_send_reply(dev, info, req_wanted,
wanted_diff_mask, new_active,
active_diff_mask, compact);
}
if (mod)
ethtool_notify(dev, ETHTOOL_MSG_FEATURES_NTF, NULL);
out_rtnl:
rtnl_unlock();
dev_put(dev);
return ret;
}
...@@ -56,8 +56,6 @@ EXPORT_SYMBOL(ethtool_op_get_ts_info); ...@@ -56,8 +56,6 @@ EXPORT_SYMBOL(ethtool_op_get_ts_info);
/* Handlers for each ethtool command */ /* Handlers for each ethtool command */
#define ETHTOOL_DEV_FEATURE_WORDS ((NETDEV_FEATURE_COUNT + 31) / 32)
static int ethtool_get_features(struct net_device *dev, void __user *useraddr) static int ethtool_get_features(struct net_device *dev, void __user *useraddr)
{ {
struct ethtool_gfeatures cmd = { struct ethtool_gfeatures cmd = {
...@@ -198,13 +196,14 @@ static netdev_features_t ethtool_get_feature_mask(u32 eth_cmd) ...@@ -198,13 +196,14 @@ static netdev_features_t ethtool_get_feature_mask(u32 eth_cmd)
switch (eth_cmd) { switch (eth_cmd) {
case ETHTOOL_GTXCSUM: case ETHTOOL_GTXCSUM:
case ETHTOOL_STXCSUM: case ETHTOOL_STXCSUM:
return NETIF_F_CSUM_MASK | NETIF_F_SCTP_CRC; return NETIF_F_CSUM_MASK | NETIF_F_FCOE_CRC_BIT |
NETIF_F_SCTP_CRC;
case ETHTOOL_GRXCSUM: case ETHTOOL_GRXCSUM:
case ETHTOOL_SRXCSUM: case ETHTOOL_SRXCSUM:
return NETIF_F_RXCSUM; return NETIF_F_RXCSUM;
case ETHTOOL_GSG: case ETHTOOL_GSG:
case ETHTOOL_SSG: case ETHTOOL_SSG:
return NETIF_F_SG; return NETIF_F_SG | NETIF_F_FRAGLIST;
case ETHTOOL_GTSO: case ETHTOOL_GTSO:
case ETHTOOL_STSO: case ETHTOOL_STSO:
return NETIF_F_ALL_TSO; return NETIF_F_ALL_TSO;
...@@ -930,37 +929,6 @@ void netdev_rss_key_fill(void *buffer, size_t len) ...@@ -930,37 +929,6 @@ void netdev_rss_key_fill(void *buffer, size_t len)
} }
EXPORT_SYMBOL(netdev_rss_key_fill); EXPORT_SYMBOL(netdev_rss_key_fill);
static int ethtool_get_max_rxfh_channel(struct net_device *dev, u32 *max)
{
u32 dev_size, current_max = 0;
u32 *indir;
int ret;
if (!dev->ethtool_ops->get_rxfh_indir_size ||
!dev->ethtool_ops->get_rxfh)
return -EOPNOTSUPP;
dev_size = dev->ethtool_ops->get_rxfh_indir_size(dev);
if (dev_size == 0)
return -EOPNOTSUPP;
indir = kcalloc(dev_size, sizeof(indir[0]), GFP_USER);
if (!indir)
return -ENOMEM;
ret = dev->ethtool_ops->get_rxfh(dev, indir, NULL, NULL);
if (ret)
goto out;
while (dev_size--)
current_max = max(current_max, indir[dev_size]);
*max = current_max;
out:
kfree(indir);
return ret;
}
static noinline_for_stack int ethtool_get_rxfh_indir(struct net_device *dev, static noinline_for_stack int ethtool_get_rxfh_indir(struct net_device *dev,
void __user *useraddr) void __user *useraddr)
{ {
...@@ -1636,6 +1604,7 @@ static int ethtool_get_ringparam(struct net_device *dev, void __user *useraddr) ...@@ -1636,6 +1604,7 @@ static int ethtool_get_ringparam(struct net_device *dev, void __user *useraddr)
static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr) static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)
{ {
struct ethtool_ringparam ringparam, max = { .cmd = ETHTOOL_GRINGPARAM }; struct ethtool_ringparam ringparam, max = { .cmd = ETHTOOL_GRINGPARAM };
int ret;
if (!dev->ethtool_ops->set_ringparam || !dev->ethtool_ops->get_ringparam) if (!dev->ethtool_ops->set_ringparam || !dev->ethtool_ops->get_ringparam)
return -EOPNOTSUPP; return -EOPNOTSUPP;
...@@ -1652,7 +1621,10 @@ static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr) ...@@ -1652,7 +1621,10 @@ static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)
ringparam.tx_pending > max.tx_max_pending) ringparam.tx_pending > max.tx_max_pending)
return -EINVAL; return -EINVAL;
return dev->ethtool_ops->set_ringparam(dev, &ringparam); ret = dev->ethtool_ops->set_ringparam(dev, &ringparam);
if (!ret)
ethtool_notify(dev, ETHTOOL_MSG_RINGS_NTF, NULL);
return ret;
} }
static noinline_for_stack int ethtool_get_channels(struct net_device *dev, static noinline_for_stack int ethtool_get_channels(struct net_device *dev,
...@@ -1677,6 +1649,7 @@ static noinline_for_stack int ethtool_set_channels(struct net_device *dev, ...@@ -1677,6 +1649,7 @@ static noinline_for_stack int ethtool_set_channels(struct net_device *dev,
u16 from_channel, to_channel; u16 from_channel, to_channel;
u32 max_rx_in_use = 0; u32 max_rx_in_use = 0;
unsigned int i; unsigned int i;
int ret;
if (!dev->ethtool_ops->set_channels || !dev->ethtool_ops->get_channels) if (!dev->ethtool_ops->set_channels || !dev->ethtool_ops->get_channels)
return -EOPNOTSUPP; return -EOPNOTSUPP;
...@@ -1708,7 +1681,10 @@ static noinline_for_stack int ethtool_set_channels(struct net_device *dev, ...@@ -1708,7 +1681,10 @@ static noinline_for_stack int ethtool_set_channels(struct net_device *dev,
if (xdp_get_umem_from_qid(dev, i)) if (xdp_get_umem_from_qid(dev, i))
return -EINVAL; return -EINVAL;
return dev->ethtool_ops->set_channels(dev, &channels); ret = dev->ethtool_ops->set_channels(dev, &channels);
if (!ret)
ethtool_notify(dev, ETHTOOL_MSG_CHANNELS_NTF, NULL);
return ret;
} }
static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr) static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr)
...@@ -2717,6 +2693,8 @@ int dev_ethtool(struct net *net, struct ifreq *ifr) ...@@ -2717,6 +2693,8 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
case ETHTOOL_GPFLAGS: case ETHTOOL_GPFLAGS:
rc = ethtool_get_value(dev, useraddr, ethcmd, rc = ethtool_get_value(dev, useraddr, ethcmd,
dev->ethtool_ops->get_priv_flags); dev->ethtool_ops->get_priv_flags);
if (!rc)
ethtool_notify(dev, ETHTOOL_MSG_PRIVFLAGS_NTF, NULL);
break; break;
case ETHTOOL_SPFLAGS: case ETHTOOL_SPFLAGS:
rc = ethtool_set_value(dev, useraddr, rc = ethtool_set_value(dev, useraddr,
......
...@@ -121,8 +121,10 @@ int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info) ...@@ -121,8 +121,10 @@ int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info)
info->extack); info->extack);
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKINFO_HEADER], ret = ethnl_parse_header_dev_get(&req_info,
genl_info_net(info), info->extack, true); tb[ETHTOOL_A_LINKINFO_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0) if (ret < 0)
return ret; return ret;
dev = req_info.dev; dev = req_info.dev;
......
...@@ -334,8 +334,10 @@ int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info) ...@@ -334,8 +334,10 @@ int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info)
info->extack); info->extack);
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKMODES_HEADER], ret = ethnl_parse_header_dev_get(&req_info,
genl_info_net(info), info->extack, true); tb[ETHTOOL_A_LINKMODES_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0) if (ret < 0)
return ret; return ret;
dev = req_info.dev; dev = req_info.dev;
......
...@@ -18,7 +18,7 @@ static const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_MAX + 1] = { ...@@ -18,7 +18,7 @@ static const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_MAX + 1] = {
}; };
/** /**
* ethnl_parse_header() - parse request header * ethnl_parse_header_dev_get() - parse request header
* @req_info: structure to put results into * @req_info: structure to put results into
* @header: nest attribute with request header * @header: nest attribute with request header
* @net: request netns * @net: request netns
...@@ -33,7 +33,7 @@ static const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_MAX + 1] = { ...@@ -33,7 +33,7 @@ static const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_MAX + 1] = {
* *
* Return: 0 on success or negative error code * Return: 0 on success or negative error code
*/ */
int ethnl_parse_header(struct ethnl_req_info *req_info, int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
const struct nlattr *header, struct net *net, const struct nlattr *header, struct net *net,
struct netlink_ext_ack *extack, bool require_dev) struct netlink_ext_ack *extack, bool require_dev)
{ {
...@@ -215,6 +215,10 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = { ...@@ -215,6 +215,10 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_LINKSTATE_GET] = &ethnl_linkstate_request_ops, [ETHTOOL_MSG_LINKSTATE_GET] = &ethnl_linkstate_request_ops,
[ETHTOOL_MSG_DEBUG_GET] = &ethnl_debug_request_ops, [ETHTOOL_MSG_DEBUG_GET] = &ethnl_debug_request_ops,
[ETHTOOL_MSG_WOL_GET] = &ethnl_wol_request_ops, [ETHTOOL_MSG_WOL_GET] = &ethnl_wol_request_ops,
[ETHTOOL_MSG_FEATURES_GET] = &ethnl_features_request_ops,
[ETHTOOL_MSG_PRIVFLAGS_GET] = &ethnl_privflags_request_ops,
[ETHTOOL_MSG_RINGS_GET] = &ethnl_rings_request_ops,
[ETHTOOL_MSG_CHANNELS_GET] = &ethnl_channels_request_ops,
}; };
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb) static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
...@@ -253,8 +257,8 @@ static int ethnl_default_parse(struct ethnl_req_info *req_info, ...@@ -253,8 +257,8 @@ static int ethnl_default_parse(struct ethnl_req_info *req_info,
request_ops->request_policy, extack); request_ops->request_policy, extack);
if (ret < 0) if (ret < 0)
goto out; goto out;
ret = ethnl_parse_header(req_info, tb[request_ops->hdr_attr], net, ret = ethnl_parse_header_dev_get(req_info, tb[request_ops->hdr_attr],
extack, require_dev); net, extack, require_dev);
if (ret < 0) if (ret < 0)
goto out; goto out;
...@@ -527,6 +531,10 @@ ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = { ...@@ -527,6 +531,10 @@ ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = {
[ETHTOOL_MSG_LINKMODES_NTF] = &ethnl_linkmodes_request_ops, [ETHTOOL_MSG_LINKMODES_NTF] = &ethnl_linkmodes_request_ops,
[ETHTOOL_MSG_DEBUG_NTF] = &ethnl_debug_request_ops, [ETHTOOL_MSG_DEBUG_NTF] = &ethnl_debug_request_ops,
[ETHTOOL_MSG_WOL_NTF] = &ethnl_wol_request_ops, [ETHTOOL_MSG_WOL_NTF] = &ethnl_wol_request_ops,
[ETHTOOL_MSG_FEATURES_NTF] = &ethnl_features_request_ops,
[ETHTOOL_MSG_PRIVFLAGS_NTF] = &ethnl_privflags_request_ops,
[ETHTOOL_MSG_RINGS_NTF] = &ethnl_rings_request_ops,
[ETHTOOL_MSG_CHANNELS_NTF] = &ethnl_channels_request_ops,
}; };
/* default notification handler */ /* default notification handler */
...@@ -612,6 +620,10 @@ static const ethnl_notify_handler_t ethnl_notify_handlers[] = { ...@@ -612,6 +620,10 @@ static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
[ETHTOOL_MSG_LINKMODES_NTF] = ethnl_default_notify, [ETHTOOL_MSG_LINKMODES_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_DEBUG_NTF] = ethnl_default_notify, [ETHTOOL_MSG_DEBUG_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_WOL_NTF] = ethnl_default_notify, [ETHTOOL_MSG_WOL_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_FEATURES_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_PRIVFLAGS_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_RINGS_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_CHANNELS_NTF] = ethnl_default_notify,
}; };
void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data) void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data)
...@@ -629,6 +641,29 @@ void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data) ...@@ -629,6 +641,29 @@ void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data)
} }
EXPORT_SYMBOL(ethtool_notify); EXPORT_SYMBOL(ethtool_notify);
static void ethnl_notify_features(struct netdev_notifier_info *info)
{
struct net_device *dev = netdev_notifier_info_to_dev(info);
ethtool_notify(dev, ETHTOOL_MSG_FEATURES_NTF, NULL);
}
static int ethnl_netdev_event(struct notifier_block *this, unsigned long event,
void *ptr)
{
switch (event) {
case NETDEV_FEAT_CHANGE:
ethnl_notify_features(ptr);
break;
}
return NOTIFY_DONE;
}
static struct notifier_block ethnl_netdev_notifier = {
.notifier_call = ethnl_netdev_event,
};
/* genetlink setup */ /* genetlink setup */
static const struct genl_ops ethtool_genl_ops[] = { static const struct genl_ops ethtool_genl_ops[] = {
...@@ -695,6 +730,54 @@ static const struct genl_ops ethtool_genl_ops[] = { ...@@ -695,6 +730,54 @@ static const struct genl_ops ethtool_genl_ops[] = {
.flags = GENL_UNS_ADMIN_PERM, .flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_set_wol, .doit = ethnl_set_wol,
}, },
{
.cmd = ETHTOOL_MSG_FEATURES_GET,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
},
{
.cmd = ETHTOOL_MSG_FEATURES_SET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_set_features,
},
{
.cmd = ETHTOOL_MSG_PRIVFLAGS_GET,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
},
{
.cmd = ETHTOOL_MSG_PRIVFLAGS_SET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_set_privflags,
},
{
.cmd = ETHTOOL_MSG_RINGS_GET,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
},
{
.cmd = ETHTOOL_MSG_RINGS_SET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_set_rings,
},
{
.cmd = ETHTOOL_MSG_CHANNELS_GET,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
},
{
.cmd = ETHTOOL_MSG_CHANNELS_SET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_set_channels,
},
}; };
static const struct genl_multicast_group ethtool_nl_mcgrps[] = { static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
...@@ -723,7 +806,9 @@ static int __init ethnl_init(void) ...@@ -723,7 +806,9 @@ static int __init ethnl_init(void)
return ret; return ret;
ethnl_ok = true; ethnl_ok = true;
return 0; ret = register_netdevice_notifier(&ethnl_netdev_notifier);
WARN(ret < 0, "ethtool: net device notifier registration failed");
return ret;
} }
subsys_initcall(ethnl_init); subsys_initcall(ethnl_init);
...@@ -10,9 +10,10 @@ ...@@ -10,9 +10,10 @@
struct ethnl_req_info; struct ethnl_req_info;
int ethnl_parse_header(struct ethnl_req_info *req_info, int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info,
const struct nlattr *nest, struct net *net, const struct nlattr *nest, struct net *net,
struct netlink_ext_ack *extack, bool require_dev); struct netlink_ext_ack *extack,
bool require_dev);
int ethnl_fill_reply_header(struct sk_buff *skb, struct net_device *dev, int ethnl_fill_reply_header(struct sk_buff *skb, struct net_device *dev,
u16 attrtype); u16 attrtype);
struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd, struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
...@@ -336,10 +337,18 @@ extern const struct ethnl_request_ops ethnl_linkmodes_request_ops; ...@@ -336,10 +337,18 @@ extern const struct ethnl_request_ops ethnl_linkmodes_request_ops;
extern const struct ethnl_request_ops ethnl_linkstate_request_ops; extern const struct ethnl_request_ops ethnl_linkstate_request_ops;
extern const struct ethnl_request_ops ethnl_debug_request_ops; extern const struct ethnl_request_ops ethnl_debug_request_ops;
extern const struct ethnl_request_ops ethnl_wol_request_ops; extern const struct ethnl_request_ops ethnl_wol_request_ops;
extern const struct ethnl_request_ops ethnl_features_request_ops;
extern const struct ethnl_request_ops ethnl_privflags_request_ops;
extern const struct ethnl_request_ops ethnl_rings_request_ops;
extern const struct ethnl_request_ops ethnl_channels_request_ops;
int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info); int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info); int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_debug(struct sk_buff *skb, struct genl_info *info); int ethnl_set_debug(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_wol(struct sk_buff *skb, struct genl_info *info); int ethnl_set_wol(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_features(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_privflags(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_rings(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info);
#endif /* _NET_ETHTOOL_NETLINK_H */ #endif /* _NET_ETHTOOL_NETLINK_H */
// SPDX-License-Identifier: GPL-2.0-only
#include "netlink.h"
#include "common.h"
#include "bitset.h"
struct privflags_req_info {
struct ethnl_req_info base;
};
struct privflags_reply_data {
struct ethnl_reply_data base;
const char (*priv_flag_names)[ETH_GSTRING_LEN];
unsigned int n_priv_flags;
u32 priv_flags;
};
#define PRIVFLAGS_REPDATA(__reply_base) \
container_of(__reply_base, struct privflags_reply_data, base)
static const struct nla_policy
privflags_get_policy[ETHTOOL_A_PRIVFLAGS_MAX + 1] = {
[ETHTOOL_A_PRIVFLAGS_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_PRIVFLAGS_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_PRIVFLAGS_FLAGS] = { .type = NLA_REJECT },
};
static int ethnl_get_priv_flags_info(struct net_device *dev,
unsigned int *count,
const char (**names)[ETH_GSTRING_LEN])
{
const struct ethtool_ops *ops = dev->ethtool_ops;
int nflags;
nflags = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
if (nflags < 0)
return nflags;
if (names) {
*names = kcalloc(nflags, ETH_GSTRING_LEN, GFP_KERNEL);
if (!*names)
return -ENOMEM;
ops->get_strings(dev, ETH_SS_PRIV_FLAGS, (u8 *)*names);
}
/* We can pass more than 32 private flags to userspace via netlink but
* we cannot get more with ethtool_ops::get_priv_flags(). Note that we
* must not adjust nflags before allocating the space for flag names
* as the buffer must be large enough for all flags.
*/
if (WARN_ONCE(nflags > 32,
"device %s reports more than 32 private flags (%d)\n",
netdev_name(dev), nflags))
nflags = 32;
*count = nflags;
return 0;
}
static int privflags_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
struct genl_info *info)
{
struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
const char (*names)[ETH_GSTRING_LEN];
const struct ethtool_ops *ops;
unsigned int nflags;
int ret;
ops = dev->ethtool_ops;
if (!ops->get_priv_flags || !ops->get_sset_count || !ops->get_strings)
return -EOPNOTSUPP;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
ret = ethnl_get_priv_flags_info(dev, &nflags, &names);
if (ret < 0)
goto out_ops;
data->priv_flags = ops->get_priv_flags(dev);
data->priv_flag_names = names;
data->n_priv_flags = nflags;
out_ops:
ethnl_ops_complete(dev);
return ret;
}
static int privflags_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags);
return ethnl_bitset32_size(&data->priv_flags, &all_flags,
data->n_priv_flags,
data->priv_flag_names, compact);
}
static int privflags_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_base);
bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
const u32 all_flags = ~(u32)0 >> (32 - data->n_priv_flags);
return ethnl_put_bitset32(skb, ETHTOOL_A_PRIVFLAGS_FLAGS,
&data->priv_flags, &all_flags,
data->n_priv_flags, data->priv_flag_names,
compact);
}
static void privflags_cleanup_data(struct ethnl_reply_data *reply_data)
{
struct privflags_reply_data *data = PRIVFLAGS_REPDATA(reply_data);
kfree(data->priv_flag_names);
}
const struct ethnl_request_ops ethnl_privflags_request_ops = {
.request_cmd = ETHTOOL_MSG_PRIVFLAGS_GET,
.reply_cmd = ETHTOOL_MSG_PRIVFLAGS_GET_REPLY,
.hdr_attr = ETHTOOL_A_PRIVFLAGS_HEADER,
.max_attr = ETHTOOL_A_PRIVFLAGS_MAX,
.req_info_size = sizeof(struct privflags_req_info),
.reply_data_size = sizeof(struct privflags_reply_data),
.request_policy = privflags_get_policy,
.prepare_data = privflags_prepare_data,
.reply_size = privflags_reply_size,
.fill_reply = privflags_fill_reply,
.cleanup_data = privflags_cleanup_data,
};
/* PRIVFLAGS_SET */
static const struct nla_policy
privflags_set_policy[ETHTOOL_A_PRIVFLAGS_MAX + 1] = {
[ETHTOOL_A_PRIVFLAGS_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_PRIVFLAGS_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_PRIVFLAGS_FLAGS] = { .type = NLA_NESTED },
};
int ethnl_set_privflags(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *tb[ETHTOOL_A_PRIVFLAGS_MAX + 1];
const char (*names)[ETH_GSTRING_LEN] = NULL;
struct ethnl_req_info req_info = {};
const struct ethtool_ops *ops;
struct net_device *dev;
unsigned int nflags;
bool mod = false;
bool compact;
u32 flags;
int ret;
ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
ETHTOOL_A_PRIVFLAGS_MAX, privflags_set_policy,
info->extack);
if (ret < 0)
return ret;
if (!tb[ETHTOOL_A_PRIVFLAGS_FLAGS])
return -EINVAL;
ret = ethnl_bitset_is_compact(tb[ETHTOOL_A_PRIVFLAGS_FLAGS], &compact);
if (ret < 0)
return ret;
ret = ethnl_parse_header_dev_get(&req_info,
tb[ETHTOOL_A_PRIVFLAGS_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0)
return ret;
dev = req_info.dev;
ops = dev->ethtool_ops;
if (!ops->get_priv_flags || !ops->set_priv_flags ||
!ops->get_sset_count || !ops->get_strings)
return -EOPNOTSUPP;
rtnl_lock();
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
ret = ethnl_get_priv_flags_info(dev, &nflags, compact ? NULL : &names);
if (ret < 0)
goto out_ops;
flags = ops->get_priv_flags(dev);
ret = ethnl_update_bitset32(&flags, nflags,
tb[ETHTOOL_A_PRIVFLAGS_FLAGS], names,
info->extack, &mod);
if (ret < 0 || !mod)
goto out_free;
ret = ops->set_priv_flags(dev, flags);
if (ret < 0)
goto out_free;
ethtool_notify(dev, ETHTOOL_MSG_PRIVFLAGS_NTF, NULL);
out_free:
kfree(names);
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 rings_req_info {
struct ethnl_req_info base;
};
struct rings_reply_data {
struct ethnl_reply_data base;
struct ethtool_ringparam ringparam;
};
#define RINGS_REPDATA(__reply_base) \
container_of(__reply_base, struct rings_reply_data, base)
static const struct nla_policy
rings_get_policy[ETHTOOL_A_RINGS_MAX + 1] = {
[ETHTOOL_A_RINGS_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_RINGS_RX_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_RX_MINI_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_RX_JUMBO_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_TX_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_RX] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_RX_MINI] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_RX_JUMBO] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_TX] = { .type = NLA_REJECT },
};
static int rings_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
struct genl_info *info)
{
struct rings_reply_data *data = RINGS_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
int ret;
if (!dev->ethtool_ops->get_ringparam)
return -EOPNOTSUPP;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
dev->ethtool_ops->get_ringparam(dev, &data->ringparam);
ethnl_ops_complete(dev);
return 0;
}
static int rings_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
return nla_total_size(sizeof(u32)) + /* _RINGS_RX_MAX */
nla_total_size(sizeof(u32)) + /* _RINGS_RX_MINI_MAX */
nla_total_size(sizeof(u32)) + /* _RINGS_RX_JUMBO_MAX */
nla_total_size(sizeof(u32)) + /* _RINGS_TX_MAX */
nla_total_size(sizeof(u32)) + /* _RINGS_RX */
nla_total_size(sizeof(u32)) + /* _RINGS_RX_MINI */
nla_total_size(sizeof(u32)) + /* _RINGS_RX_JUMBO */
nla_total_size(sizeof(u32)); /* _RINGS_TX */
}
static int rings_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct rings_reply_data *data = RINGS_REPDATA(reply_base);
const struct ethtool_ringparam *ringparam = &data->ringparam;
if ((ringparam->rx_max_pending &&
(nla_put_u32(skb, ETHTOOL_A_RINGS_RX_MAX,
ringparam->rx_max_pending) ||
nla_put_u32(skb, ETHTOOL_A_RINGS_RX,
ringparam->rx_pending))) ||
(ringparam->rx_mini_max_pending &&
(nla_put_u32(skb, ETHTOOL_A_RINGS_RX_MINI_MAX,
ringparam->rx_mini_max_pending) ||
nla_put_u32(skb, ETHTOOL_A_RINGS_RX_MINI,
ringparam->rx_mini_pending))) ||
(ringparam->rx_jumbo_max_pending &&
(nla_put_u32(skb, ETHTOOL_A_RINGS_RX_JUMBO_MAX,
ringparam->rx_jumbo_max_pending) ||
nla_put_u32(skb, ETHTOOL_A_RINGS_RX_JUMBO,
ringparam->rx_jumbo_pending))) ||
(ringparam->tx_max_pending &&
(nla_put_u32(skb, ETHTOOL_A_RINGS_TX_MAX,
ringparam->tx_max_pending) ||
nla_put_u32(skb, ETHTOOL_A_RINGS_TX,
ringparam->tx_pending))))
return -EMSGSIZE;
return 0;
}
const struct ethnl_request_ops ethnl_rings_request_ops = {
.request_cmd = ETHTOOL_MSG_RINGS_GET,
.reply_cmd = ETHTOOL_MSG_RINGS_GET_REPLY,
.hdr_attr = ETHTOOL_A_RINGS_HEADER,
.max_attr = ETHTOOL_A_RINGS_MAX,
.req_info_size = sizeof(struct rings_req_info),
.reply_data_size = sizeof(struct rings_reply_data),
.request_policy = rings_get_policy,
.prepare_data = rings_prepare_data,
.reply_size = rings_reply_size,
.fill_reply = rings_fill_reply,
};
/* RINGS_SET */
static const struct nla_policy
rings_set_policy[ETHTOOL_A_RINGS_MAX + 1] = {
[ETHTOOL_A_RINGS_UNSPEC] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_HEADER] = { .type = NLA_NESTED },
[ETHTOOL_A_RINGS_RX_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_RX_MINI_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_RX_JUMBO_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_TX_MAX] = { .type = NLA_REJECT },
[ETHTOOL_A_RINGS_RX] = { .type = NLA_U32 },
[ETHTOOL_A_RINGS_RX_MINI] = { .type = NLA_U32 },
[ETHTOOL_A_RINGS_RX_JUMBO] = { .type = NLA_U32 },
[ETHTOOL_A_RINGS_TX] = { .type = NLA_U32 },
};
int ethnl_set_rings(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *tb[ETHTOOL_A_RINGS_MAX + 1];
struct ethtool_ringparam ringparam = {};
struct ethnl_req_info req_info = {};
const struct nlattr *err_attr;
const struct ethtool_ops *ops;
struct net_device *dev;
bool mod = false;
int ret;
ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
ETHTOOL_A_RINGS_MAX, rings_set_policy,
info->extack);
if (ret < 0)
return ret;
ret = ethnl_parse_header_dev_get(&req_info,
tb[ETHTOOL_A_RINGS_HEADER],
genl_info_net(info), info->extack,
true);
if (ret < 0)
return ret;
dev = req_info.dev;
ops = dev->ethtool_ops;
ret = -EOPNOTSUPP;
if (!ops->get_ringparam || !ops->set_ringparam)
goto out_dev;
rtnl_lock();
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
ops->get_ringparam(dev, &ringparam);
ethnl_update_u32(&ringparam.rx_pending, tb[ETHTOOL_A_RINGS_RX], &mod);
ethnl_update_u32(&ringparam.rx_mini_pending,
tb[ETHTOOL_A_RINGS_RX_MINI], &mod);
ethnl_update_u32(&ringparam.rx_jumbo_pending,
tb[ETHTOOL_A_RINGS_RX_JUMBO], &mod);
ethnl_update_u32(&ringparam.tx_pending, tb[ETHTOOL_A_RINGS_TX], &mod);
ret = 0;
if (!mod)
goto out_ops;
/* ensure new ring parameters are within limits */
if (ringparam.rx_pending > ringparam.rx_max_pending)
err_attr = tb[ETHTOOL_A_RINGS_RX];
else if (ringparam.rx_mini_pending > ringparam.rx_mini_max_pending)
err_attr = tb[ETHTOOL_A_RINGS_RX_MINI];
else if (ringparam.rx_jumbo_pending > ringparam.rx_jumbo_max_pending)
err_attr = tb[ETHTOOL_A_RINGS_RX_JUMBO];
else if (ringparam.tx_pending > ringparam.tx_max_pending)
err_attr = tb[ETHTOOL_A_RINGS_TX];
else
err_attr = NULL;
if (err_attr) {
ret = -EINVAL;
NL_SET_ERR_MSG_ATTR(info->extack, err_attr,
"requested ring size exceeeds maximum");
goto out_ops;
}
ret = dev->ethtool_ops->set_ringparam(dev, &ringparam);
if (ret < 0)
goto out_ops;
ethtool_notify(dev, ETHTOOL_MSG_RINGS_NTF, NULL);
out_ops:
ethnl_ops_complete(dev);
out_rtnl:
rtnl_unlock();
out_dev:
dev_put(dev);
return ret;
}
...@@ -123,8 +123,9 @@ int ethnl_set_wol(struct sk_buff *skb, struct genl_info *info) ...@@ -123,8 +123,9 @@ int ethnl_set_wol(struct sk_buff *skb, struct genl_info *info)
wol_set_policy, info->extack); wol_set_policy, info->extack);
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_WOL_HEADER], ret = ethnl_parse_header_dev_get(&req_info, tb[ETHTOOL_A_WOL_HEADER],
genl_info_net(info), info->extack, true); genl_info_net(info), info->extack,
true);
if (ret < 0) if (ret < 0)
return ret; return ret;
dev = req_info.dev; dev = req_info.dev;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment