Skip to content

Commit

Permalink
ethtool: support FEC settings over netlink
Browse files Browse the repository at this point in the history
Add FEC API to netlink.

This is not a 1-to-1 conversion.

FEC settings already depend on link modes to tell user which
modes are supported. Take this further an use link modes for
manual configuration. Old struct ethtool_fecparam is still
used to talk to the drivers, so we need to translate back
and forth. We can revisit the internal API if number of FEC
encodings starts to grow.

Enforce only one active FEC bit (by using a bit position
rather than another mask).

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Jakub Kicinski authored and David S. Miller committed Mar 31, 2021
1 parent 2811005 commit 1e5d1f6
Show file tree
Hide file tree
Showing 6 changed files with 339 additions and 3 deletions.
62 changes: 60 additions & 2 deletions Documentation/networking/ethtool-netlink.rst
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ Userspace to kernel:
``ETHTOOL_MSG_CABLE_TEST_ACT`` action start cable test
``ETHTOOL_MSG_CABLE_TEST_TDR_ACT`` action start raw TDR cable test
``ETHTOOL_MSG_TUNNEL_INFO_GET`` get tunnel offload info
``ETHTOOL_MSG_FEC_GET`` get FEC settings
``ETHTOOL_MSG_FEC_SET`` set FEC settings
===================================== ================================

Kernel to userspace:
Expand Down Expand Up @@ -242,6 +244,8 @@ Kernel to userspace:
``ETHTOOL_MSG_CABLE_TEST_NTF`` Cable test results
``ETHTOOL_MSG_CABLE_TEST_TDR_NTF`` Cable test TDR results
``ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY`` tunnel offload info
``ETHTOOL_MSG_FEC_GET_REPLY`` FEC settings
``ETHTOOL_MSG_FEC_NTF`` FEC settings
===================================== =================================

``GET`` requests are sent by userspace applications to retrieve device
Expand Down Expand Up @@ -1280,6 +1284,60 @@ Kernel response contents:
For UDP tunnel table empty ``ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES`` indicates that
the table contains static entries, hard-coded by the NIC.

FEC_GET
=======

Gets FEC configuration and state like ``ETHTOOL_GFECPARAM`` ioctl request.

Request contents:

===================================== ====== ==========================
``ETHTOOL_A_FEC_HEADER`` nested request header
===================================== ====== ==========================

Kernel response contents:

===================================== ====== ==========================
``ETHTOOL_A_FEC_HEADER`` nested request header
``ETHTOOL_A_FEC_MODES`` bitset configured modes
``ETHTOOL_A_FEC_AUTO`` bool FEC mode auto selection
``ETHTOOL_A_FEC_ACTIVE`` u32 index of active FEC mode
===================================== ====== ==========================

``ETHTOOL_A_FEC_ACTIVE`` is the bit index of the FEC link mode currently
active on the interface. This attribute may not be present if device does
not support FEC.

``ETHTOOL_A_FEC_MODES`` and ``ETHTOOL_A_FEC_AUTO`` are only meaningful when
autonegotiation is disabled. If ``ETHTOOL_A_FEC_AUTO`` is non-zero driver will
select the FEC mode automatically based on the parameters of the SFP module.
This is equivalent to the ``ETHTOOL_FEC_AUTO`` bit of the ioctl interface.
``ETHTOOL_A_FEC_MODES`` carry the current FEC configuration using link mode
bits (rather than old ``ETHTOOL_FEC_*`` bits).

FEC_SET
=======

Sets FEC parameters like ``ETHTOOL_SFECPARAM`` ioctl request.

Request contents:

===================================== ====== ==========================
``ETHTOOL_A_FEC_HEADER`` nested request header
``ETHTOOL_A_FEC_MODES`` bitset configured modes
``ETHTOOL_A_FEC_AUTO`` bool FEC mode auto selection
===================================== ====== ==========================

``FEC_SET`` is only meaningful when autonegotiation is disabled. Otherwise
FEC mode is selected as part of autonegotiation.

``ETHTOOL_A_FEC_MODES`` selects which FEC mode should be used. It's recommended
to set only one bit, if multiple bits are set driver may choose between them
in an implementation specific way.

``ETHTOOL_A_FEC_AUTO`` requests the driver to choose FEC mode based on SFP
module parameters. This does not mean autonegotiation.

Request translation
===================

Expand Down Expand Up @@ -1373,8 +1431,8 @@ are netlink only.
``ETHTOOL_MSG_LINKMODES_SET``
``ETHTOOL_PHY_GTUNABLE`` n/a
``ETHTOOL_PHY_STUNABLE`` n/a
``ETHTOOL_GFECPARAM`` n/a
``ETHTOOL_SFECPARAM`` n/a
``ETHTOOL_GFECPARAM`` ``ETHTOOL_MSG_FEC_GET``
``ETHTOOL_SFECPARAM`` ``ETHTOOL_MSG_FEC_SET``
n/a ''ETHTOOL_MSG_CABLE_TEST_ACT''
n/a ''ETHTOOL_MSG_CABLE_TEST_TDR_ACT''
n/a ``ETHTOOL_MSG_TUNNEL_INFO_GET``
Expand Down
17 changes: 17 additions & 0 deletions include/uapi/linux/ethtool_netlink.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ enum {
ETHTOOL_MSG_CABLE_TEST_ACT,
ETHTOOL_MSG_CABLE_TEST_TDR_ACT,
ETHTOOL_MSG_TUNNEL_INFO_GET,
ETHTOOL_MSG_FEC_GET,
ETHTOOL_MSG_FEC_SET,

/* add new constants above here */
__ETHTOOL_MSG_USER_CNT,
Expand Down Expand Up @@ -80,6 +82,8 @@ enum {
ETHTOOL_MSG_CABLE_TEST_NTF,
ETHTOOL_MSG_CABLE_TEST_TDR_NTF,
ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY,
ETHTOOL_MSG_FEC_GET_REPLY,
ETHTOOL_MSG_FEC_NTF,

/* add new constants above here */
__ETHTOOL_MSG_KERNEL_CNT,
Expand Down Expand Up @@ -629,6 +633,19 @@ enum {
ETHTOOL_A_TUNNEL_INFO_MAX = (__ETHTOOL_A_TUNNEL_INFO_CNT - 1)
};

/* FEC */

enum {
ETHTOOL_A_FEC_UNSPEC,
ETHTOOL_A_FEC_HEADER, /* nest - _A_HEADER_* */
ETHTOOL_A_FEC_MODES, /* bitset */
ETHTOOL_A_FEC_AUTO, /* u8 */
ETHTOOL_A_FEC_ACTIVE, /* u32 */

__ETHTOOL_A_FEC_CNT,
ETHTOOL_A_FEC_MAX = (__ETHTOOL_A_FEC_CNT - 1)
};

/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1
Expand Down
2 changes: 1 addition & 1 deletion net/ethtool/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ obj-$(CONFIG_ETHTOOL_NETLINK) += ethtool_nl.o
ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o \
linkstate.o debug.o wol.o features.o privflags.o rings.o \
channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \
tunnels.o
tunnels.o fec.o
238 changes: 238 additions & 0 deletions net/ethtool/fec.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-2.0-only

#include "netlink.h"
#include "common.h"
#include "bitset.h"

struct fec_req_info {
struct ethnl_req_info base;
};

struct fec_reply_data {
struct ethnl_reply_data base;
__ETHTOOL_DECLARE_LINK_MODE_MASK(fec_link_modes);
u32 active_fec;
u8 fec_auto;
};

#define FEC_REPDATA(__reply_base) \
container_of(__reply_base, struct fec_reply_data, base)

#define ETHTOOL_FEC_MASK ((ETHTOOL_FEC_LLRS << 1) - 1)

const struct nla_policy ethnl_fec_get_policy[ETHTOOL_A_FEC_HEADER + 1] = {
[ETHTOOL_A_FEC_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
};

static void
ethtool_fec_to_link_modes(u32 fec, unsigned long *link_modes, u8 *fec_auto)
{
if (fec_auto)
*fec_auto = !!(fec & ETHTOOL_FEC_AUTO);

if (fec & ETHTOOL_FEC_OFF)
__set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, link_modes);
if (fec & ETHTOOL_FEC_RS)
__set_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT, link_modes);
if (fec & ETHTOOL_FEC_BASER)
__set_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT, link_modes);
if (fec & ETHTOOL_FEC_LLRS)
__set_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT, link_modes);
}

static int
ethtool_link_modes_to_fecparam(struct ethtool_fecparam *fec,
unsigned long *link_modes, u8 fec_auto)
{
memset(fec, 0, sizeof(*fec));

if (fec_auto)
fec->fec |= ETHTOOL_FEC_AUTO;

if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, link_modes))
fec->fec |= ETHTOOL_FEC_OFF;
if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT, link_modes))
fec->fec |= ETHTOOL_FEC_RS;
if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT, link_modes))
fec->fec |= ETHTOOL_FEC_BASER;
if (__test_and_clear_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT, link_modes))
fec->fec |= ETHTOOL_FEC_LLRS;

if (!bitmap_empty(link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS))
return -EINVAL;

return 0;
}

static int fec_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
struct genl_info *info)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(active_fec_modes) = {};
struct fec_reply_data *data = FEC_REPDATA(reply_base);
struct net_device *dev = reply_base->dev;
struct ethtool_fecparam fec = {};
int ret;

if (!dev->ethtool_ops->get_fecparam)
return -EOPNOTSUPP;
ret = ethnl_ops_begin(dev);
if (ret < 0)
return ret;
ret = dev->ethtool_ops->get_fecparam(dev, &fec);
ethnl_ops_complete(dev);
if (ret)
return ret;

WARN_ON_ONCE(fec.reserved);

ethtool_fec_to_link_modes(fec.fec, data->fec_link_modes,
&data->fec_auto);

ethtool_fec_to_link_modes(fec.active_fec, active_fec_modes, NULL);
data->active_fec = find_first_bit(active_fec_modes,
__ETHTOOL_LINK_MODE_MASK_NBITS);
/* Don't report attr if no FEC mode set. Note that
* ethtool_fecparam_to_link_modes() ignores NONE and AUTO.
*/
if (data->active_fec == __ETHTOOL_LINK_MODE_MASK_NBITS)
data->active_fec = 0;

return 0;
}

static int fec_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
const struct fec_reply_data *data = FEC_REPDATA(reply_base);
int len = 0;
int ret;

ret = ethnl_bitset_size(data->fec_link_modes, NULL,
__ETHTOOL_LINK_MODE_MASK_NBITS,
link_mode_names, compact);
if (ret < 0)
return ret;
len += ret;

len += nla_total_size(sizeof(u8)) + /* _FEC_AUTO */
nla_total_size(sizeof(u32)); /* _FEC_ACTIVE */

return len;
}

static int fec_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
const struct fec_reply_data *data = FEC_REPDATA(reply_base);
int ret;

ret = ethnl_put_bitset(skb, ETHTOOL_A_FEC_MODES,
data->fec_link_modes, NULL,
__ETHTOOL_LINK_MODE_MASK_NBITS,
link_mode_names, compact);
if (ret < 0)
return ret;

if (nla_put_u8(skb, ETHTOOL_A_FEC_AUTO, data->fec_auto) ||
(data->active_fec &&
nla_put_u32(skb, ETHTOOL_A_FEC_ACTIVE, data->active_fec)))
return -EMSGSIZE;

return 0;
}

const struct ethnl_request_ops ethnl_fec_request_ops = {
.request_cmd = ETHTOOL_MSG_FEC_GET,
.reply_cmd = ETHTOOL_MSG_FEC_GET_REPLY,
.hdr_attr = ETHTOOL_A_FEC_HEADER,
.req_info_size = sizeof(struct fec_req_info),
.reply_data_size = sizeof(struct fec_reply_data),

.prepare_data = fec_prepare_data,
.reply_size = fec_reply_size,
.fill_reply = fec_fill_reply,
};

/* FEC_SET */

const struct nla_policy ethnl_fec_set_policy[ETHTOOL_A_FEC_AUTO + 1] = {
[ETHTOOL_A_FEC_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
[ETHTOOL_A_FEC_MODES] = { .type = NLA_NESTED },
[ETHTOOL_A_FEC_AUTO] = NLA_POLICY_MAX(NLA_U8, 1),
};

int ethnl_set_fec(struct sk_buff *skb, struct genl_info *info)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(fec_link_modes) = {};
struct ethnl_req_info req_info = {};
struct nlattr **tb = info->attrs;
struct ethtool_fecparam fec = {};
const struct ethtool_ops *ops;
struct net_device *dev;
bool mod = false;
u8 fec_auto;
int ret;

ret = ethnl_parse_header_dev_get(&req_info, tb[ETHTOOL_A_FEC_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_fecparam || !ops->set_fecparam)
goto out_dev;

rtnl_lock();
ret = ethnl_ops_begin(dev);
if (ret < 0)
goto out_rtnl;
ret = ops->get_fecparam(dev, &fec);
if (ret < 0)
goto out_ops;

ethtool_fec_to_link_modes(fec.fec, fec_link_modes, &fec_auto);

ret = ethnl_update_bitset(fec_link_modes,
__ETHTOOL_LINK_MODE_MASK_NBITS,
tb[ETHTOOL_A_FEC_MODES],
link_mode_names, info->extack, &mod);
if (ret < 0)
goto out_ops;
ethnl_update_u8(&fec_auto, tb[ETHTOOL_A_FEC_AUTO], &mod);

ret = 0;
if (!mod)
goto out_ops;

ret = ethtool_link_modes_to_fecparam(&fec, fec_link_modes, fec_auto);
if (ret) {
NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_FEC_MODES],
"invalid FEC modes requested");
goto out_ops;
}
if (!fec.fec) {
ret = -EINVAL;
NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_FEC_MODES],
"no FEC modes set");
goto out_ops;
}

ret = dev->ethtool_ops->set_fecparam(dev, &fec);
if (ret < 0)
goto out_ops;
ethtool_notify(dev, ETHTOOL_MSG_FEC_NTF, NULL);

out_ops:
ethnl_ops_complete(dev);
out_rtnl:
rtnl_unlock();
out_dev:
dev_put(dev);
return ret;
}
Loading

0 comments on commit 1e5d1f6

Please sign in to comment.