mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-17 02:36:21 +00:00
fd77be7bd4
Implement EEE_SET netlink request to set EEE settings of a network device. These are traditionally set with ETHTOOL_SEEE ioctl request. The netlink interface allows setting the EEE status for all link modes supported by kernel but only first 32 link modes can be set at the moment as only those are supported by the ethtool_ops callback. Signed-off-by: Michal Kubecek <mkubecek@suse.cz> Signed-off-by: David S. Miller <davem@davemloft.net>
204 lines
5.7 KiB
C
204 lines
5.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include "netlink.h"
|
|
#include "common.h"
|
|
#include "bitset.h"
|
|
|
|
#define EEE_MODES_COUNT \
|
|
(sizeof_field(struct ethtool_eee, supported) * BITS_PER_BYTE)
|
|
|
|
struct eee_req_info {
|
|
struct ethnl_req_info base;
|
|
};
|
|
|
|
struct eee_reply_data {
|
|
struct ethnl_reply_data base;
|
|
struct ethtool_eee eee;
|
|
};
|
|
|
|
#define EEE_REPDATA(__reply_base) \
|
|
container_of(__reply_base, struct eee_reply_data, base)
|
|
|
|
static const struct nla_policy
|
|
eee_get_policy[ETHTOOL_A_EEE_MAX + 1] = {
|
|
[ETHTOOL_A_EEE_UNSPEC] = { .type = NLA_REJECT },
|
|
[ETHTOOL_A_EEE_HEADER] = { .type = NLA_NESTED },
|
|
[ETHTOOL_A_EEE_MODES_OURS] = { .type = NLA_REJECT },
|
|
[ETHTOOL_A_EEE_MODES_PEER] = { .type = NLA_REJECT },
|
|
[ETHTOOL_A_EEE_ACTIVE] = { .type = NLA_REJECT },
|
|
[ETHTOOL_A_EEE_ENABLED] = { .type = NLA_REJECT },
|
|
[ETHTOOL_A_EEE_TX_LPI_ENABLED] = { .type = NLA_REJECT },
|
|
[ETHTOOL_A_EEE_TX_LPI_TIMER] = { .type = NLA_REJECT },
|
|
};
|
|
|
|
static int eee_prepare_data(const struct ethnl_req_info *req_base,
|
|
struct ethnl_reply_data *reply_base,
|
|
struct genl_info *info)
|
|
{
|
|
struct eee_reply_data *data = EEE_REPDATA(reply_base);
|
|
struct net_device *dev = reply_base->dev;
|
|
int ret;
|
|
|
|
if (!dev->ethtool_ops->get_eee)
|
|
return -EOPNOTSUPP;
|
|
ret = ethnl_ops_begin(dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = dev->ethtool_ops->get_eee(dev, &data->eee);
|
|
ethnl_ops_complete(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int eee_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 eee_reply_data *data = EEE_REPDATA(reply_base);
|
|
const struct ethtool_eee *eee = &data->eee;
|
|
int len = 0;
|
|
int ret;
|
|
|
|
BUILD_BUG_ON(sizeof(eee->advertised) * BITS_PER_BYTE !=
|
|
EEE_MODES_COUNT);
|
|
BUILD_BUG_ON(sizeof(eee->lp_advertised) * BITS_PER_BYTE !=
|
|
EEE_MODES_COUNT);
|
|
|
|
/* MODES_OURS */
|
|
ret = ethnl_bitset32_size(&eee->advertised, &eee->supported,
|
|
EEE_MODES_COUNT, link_mode_names, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
len += ret;
|
|
/* MODES_PEERS */
|
|
ret = ethnl_bitset32_size(&eee->lp_advertised, NULL,
|
|
EEE_MODES_COUNT, link_mode_names, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
len += ret;
|
|
|
|
len += nla_total_size(sizeof(u8)) + /* _EEE_ACTIVE */
|
|
nla_total_size(sizeof(u8)) + /* _EEE_ENABLED */
|
|
nla_total_size(sizeof(u8)) + /* _EEE_TX_LPI_ENABLED */
|
|
nla_total_size(sizeof(u32)); /* _EEE_TX_LPI_TIMER */
|
|
|
|
return len;
|
|
}
|
|
|
|
static int eee_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 eee_reply_data *data = EEE_REPDATA(reply_base);
|
|
const struct ethtool_eee *eee = &data->eee;
|
|
int ret;
|
|
|
|
ret = ethnl_put_bitset32(skb, ETHTOOL_A_EEE_MODES_OURS,
|
|
&eee->advertised, &eee->supported,
|
|
EEE_MODES_COUNT, link_mode_names, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = ethnl_put_bitset32(skb, ETHTOOL_A_EEE_MODES_PEER,
|
|
&eee->lp_advertised, NULL, EEE_MODES_COUNT,
|
|
link_mode_names, compact);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (nla_put_u8(skb, ETHTOOL_A_EEE_ACTIVE, !!eee->eee_active) ||
|
|
nla_put_u8(skb, ETHTOOL_A_EEE_ENABLED, !!eee->eee_enabled) ||
|
|
nla_put_u8(skb, ETHTOOL_A_EEE_TX_LPI_ENABLED,
|
|
!!eee->tx_lpi_enabled) ||
|
|
nla_put_u32(skb, ETHTOOL_A_EEE_TX_LPI_TIMER, eee->tx_lpi_timer))
|
|
return -EMSGSIZE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct ethnl_request_ops ethnl_eee_request_ops = {
|
|
.request_cmd = ETHTOOL_MSG_EEE_GET,
|
|
.reply_cmd = ETHTOOL_MSG_EEE_GET_REPLY,
|
|
.hdr_attr = ETHTOOL_A_EEE_HEADER,
|
|
.max_attr = ETHTOOL_A_EEE_MAX,
|
|
.req_info_size = sizeof(struct eee_req_info),
|
|
.reply_data_size = sizeof(struct eee_reply_data),
|
|
.request_policy = eee_get_policy,
|
|
|
|
.prepare_data = eee_prepare_data,
|
|
.reply_size = eee_reply_size,
|
|
.fill_reply = eee_fill_reply,
|
|
};
|
|
|
|
/* EEE_SET */
|
|
|
|
static const struct nla_policy
|
|
eee_set_policy[ETHTOOL_A_EEE_MAX + 1] = {
|
|
[ETHTOOL_A_EEE_UNSPEC] = { .type = NLA_REJECT },
|
|
[ETHTOOL_A_EEE_HEADER] = { .type = NLA_NESTED },
|
|
[ETHTOOL_A_EEE_MODES_OURS] = { .type = NLA_NESTED },
|
|
[ETHTOOL_A_EEE_MODES_PEER] = { .type = NLA_REJECT },
|
|
[ETHTOOL_A_EEE_ACTIVE] = { .type = NLA_REJECT },
|
|
[ETHTOOL_A_EEE_ENABLED] = { .type = NLA_U8 },
|
|
[ETHTOOL_A_EEE_TX_LPI_ENABLED] = { .type = NLA_U8 },
|
|
[ETHTOOL_A_EEE_TX_LPI_TIMER] = { .type = NLA_U32 },
|
|
};
|
|
|
|
int ethnl_set_eee(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
struct nlattr *tb[ETHTOOL_A_EEE_MAX + 1];
|
|
struct ethtool_eee eee = {};
|
|
struct ethnl_req_info req_info = {};
|
|
const struct ethtool_ops *ops;
|
|
struct net_device *dev;
|
|
bool mod = false;
|
|
int ret;
|
|
|
|
ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb, ETHTOOL_A_EEE_MAX,
|
|
eee_set_policy, info->extack);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = ethnl_parse_header_dev_get(&req_info,
|
|
tb[ETHTOOL_A_EEE_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_eee || !ops->set_eee)
|
|
goto out_dev;
|
|
|
|
rtnl_lock();
|
|
ret = ethnl_ops_begin(dev);
|
|
if (ret < 0)
|
|
goto out_rtnl;
|
|
ret = ops->get_eee(dev, &eee);
|
|
if (ret < 0)
|
|
goto out_ops;
|
|
|
|
ret = ethnl_update_bitset32(&eee.advertised, EEE_MODES_COUNT,
|
|
tb[ETHTOOL_A_EEE_MODES_OURS],
|
|
link_mode_names, info->extack, &mod);
|
|
if (ret < 0)
|
|
goto out_ops;
|
|
ethnl_update_bool32(&eee.eee_enabled, tb[ETHTOOL_A_EEE_ENABLED], &mod);
|
|
ethnl_update_bool32(&eee.tx_lpi_enabled,
|
|
tb[ETHTOOL_A_EEE_TX_LPI_ENABLED], &mod);
|
|
ethnl_update_bool32(&eee.tx_lpi_timer, tb[ETHTOOL_A_EEE_TX_LPI_TIMER],
|
|
&mod);
|
|
ret = 0;
|
|
if (!mod)
|
|
goto out_ops;
|
|
|
|
ret = dev->ethtool_ops->set_eee(dev, &eee);
|
|
|
|
out_ops:
|
|
ethnl_ops_complete(dev);
|
|
out_rtnl:
|
|
rtnl_unlock();
|
|
out_dev:
|
|
dev_put(dev);
|
|
return ret;
|
|
}
|