forked from torvalds/linux
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ethtool: Add support for configuring frame preemption via netlink
ethtool is gaining support for using netlink as transport for its messages, being an alternative to ioctl() calls. Frame preemption, being new, makes a good target for being added to the list of features that are also supported via the netlink transport. Signed-off-by: Vinicius Costa Gomes <vinicius.gomes@intel.com>
- Loading branch information
1 parent
55c1e82
commit 5d130eb
Showing
5 changed files
with
219 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
// SPDX-License-Identifier: GPL-2.0-only | ||
|
||
#include "netlink.h" | ||
#include "common.h" | ||
|
||
struct preempt_req_info { | ||
struct ethnl_req_info base; | ||
}; | ||
|
||
struct preempt_reply_data { | ||
struct ethnl_reply_data base; | ||
struct ethtool_fp fp; | ||
}; | ||
|
||
#define PREEMPT_REPDATA(__reply_base) \ | ||
container_of(__reply_base, struct preempt_reply_data, base) | ||
|
||
static const struct nla_policy | ||
preempt_get_policy[ETHTOOL_A_PREEMPT_MAX + 1] = { | ||
[ETHTOOL_A_PREEMPT_UNSPEC] = { .type = NLA_REJECT }, | ||
[ETHTOOL_A_PREEMPT_HEADER] = { .type = NLA_NESTED }, | ||
[ETHTOOL_A_PREEMPT_SUPPORTED] = { .type = NLA_REJECT }, | ||
[ETHTOOL_A_PREEMPT_ACTIVE] = { .type = NLA_REJECT }, | ||
[ETHTOOL_A_PREEMPT_MIN_FRAG_SIZE] = { .type = NLA_REJECT }, | ||
[ETHTOOL_A_PREEMPT_QUEUES_SUPPORTED] = { .type = NLA_REJECT }, | ||
[ETHTOOL_A_PREEMPT_QUEUES_PREEMPTIBLE] = { .type = NLA_REJECT }, | ||
}; | ||
|
||
static int preempt_prepare_data(const struct ethnl_req_info *req_base, | ||
struct ethnl_reply_data *reply_base, | ||
struct genl_info *info) | ||
{ | ||
struct preempt_reply_data *data = PREEMPT_REPDATA(reply_base); | ||
struct net_device *dev = reply_base->dev; | ||
int ret; | ||
|
||
if (!dev->ethtool_ops->get_preempt) | ||
return -EOPNOTSUPP; | ||
|
||
ret = ethnl_ops_begin(dev); | ||
if (ret < 0) | ||
return ret; | ||
|
||
ret = dev->ethtool_ops->get_preempt(dev, &data->fp); | ||
ethnl_ops_complete(dev); | ||
|
||
return ret; | ||
} | ||
|
||
static int preempt_reply_size(const struct ethnl_req_info *req_base, | ||
const struct ethnl_reply_data *reply_base) | ||
{ | ||
int len = 0; | ||
|
||
len += nla_total_size(sizeof(u8)); /* _PREEMPT_SUPPORTED */ | ||
len += nla_total_size(sizeof(u8)); /* _PREEMPT_ACTIVE */ | ||
len += nla_total_size(sizeof(u32)); /* _PREEMPT_QUEUES_SUPPORTED */ | ||
len += nla_total_size(sizeof(u32)); /* _PREEMPT_QUEUES_PREEMPTIBLE */ | ||
len += nla_total_size(sizeof(u32)); /* _PREEMPT_MIN_FRAG_SIZE */ | ||
|
||
return len; | ||
} | ||
|
||
static int preempt_fill_reply(struct sk_buff *skb, | ||
const struct ethnl_req_info *req_base, | ||
const struct ethnl_reply_data *reply_base) | ||
{ | ||
const struct preempt_reply_data *data = PREEMPT_REPDATA(reply_base); | ||
const struct ethtool_fp *preempt = &data->fp; | ||
|
||
if (nla_put_u32(skb, ETHTOOL_A_PREEMPT_QUEUES_SUPPORTED, | ||
preempt->supported_queues_mask)) | ||
return -EMSGSIZE; | ||
|
||
if (nla_put_u32(skb, ETHTOOL_A_PREEMPT_QUEUES_PREEMPTIBLE, | ||
preempt->preemptible_queues_mask)) | ||
return -EMSGSIZE; | ||
|
||
if (nla_put_u8(skb, ETHTOOL_A_PREEMPT_ACTIVE, preempt->fp_enabled)) | ||
return -EMSGSIZE; | ||
|
||
if (nla_put_u8(skb, ETHTOOL_A_PREEMPT_SUPPORTED, | ||
preempt->fp_supported)) | ||
return -EMSGSIZE; | ||
|
||
if (nla_put_u32(skb, ETHTOOL_A_PREEMPT_MIN_FRAG_SIZE, | ||
preempt->min_frag_size)) | ||
return -EMSGSIZE; | ||
|
||
return 0; | ||
} | ||
|
||
const struct ethnl_request_ops ethnl_preempt_request_ops = { | ||
.request_cmd = ETHTOOL_MSG_PREEMPT_GET, | ||
.reply_cmd = ETHTOOL_MSG_PREEMPT_GET_REPLY, | ||
.hdr_attr = ETHTOOL_A_PREEMPT_HEADER, | ||
.max_attr = ETHTOOL_A_PREEMPT_MAX, | ||
.req_info_size = sizeof(struct preempt_req_info), | ||
.reply_data_size = sizeof(struct preempt_reply_data), | ||
.request_policy = preempt_get_policy, | ||
|
||
.prepare_data = preempt_prepare_data, | ||
.reply_size = preempt_reply_size, | ||
.fill_reply = preempt_fill_reply, | ||
}; | ||
|
||
static const struct nla_policy | ||
preempt_set_policy[ETHTOOL_A_PREEMPT_MAX + 1] = { | ||
[ETHTOOL_A_PREEMPT_UNSPEC] = { .type = NLA_REJECT }, | ||
[ETHTOOL_A_PREEMPT_HEADER] = { .type = NLA_NESTED }, | ||
[ETHTOOL_A_PREEMPT_SUPPORTED] = { .type = NLA_REJECT }, | ||
[ETHTOOL_A_PREEMPT_ACTIVE] = { .type = NLA_U8 }, | ||
[ETHTOOL_A_PREEMPT_MIN_FRAG_SIZE] = { .type = NLA_U32 }, | ||
[ETHTOOL_A_PREEMPT_QUEUES_SUPPORTED] = { .type = NLA_REJECT }, | ||
[ETHTOOL_A_PREEMPT_QUEUES_PREEMPTIBLE] = { .type = NLA_U32 }, | ||
}; | ||
|
||
int ethnl_set_preempt(struct sk_buff *skb, struct genl_info *info) | ||
{ | ||
struct nlattr *tb[ETHTOOL_A_LINKINFO_MAX + 1]; | ||
struct ethtool_fp preempt = {}; | ||
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_PREEMPT_MAX, preempt_set_policy, | ||
info->extack); | ||
if (ret < 0) | ||
return ret; | ||
|
||
ret = ethnl_parse_header_dev_get(&req_info, | ||
tb[ETHTOOL_A_PREEMPT_HEADER], | ||
genl_info_net(info), info->extack, | ||
true); | ||
if (ret < 0) | ||
return ret; | ||
dev = req_info.dev; | ||
ret = -EOPNOTSUPP; | ||
if (!dev->ethtool_ops->get_preempt || | ||
!dev->ethtool_ops->set_preempt) | ||
goto out_dev; | ||
|
||
rtnl_lock(); | ||
ret = ethnl_ops_begin(dev); | ||
if (ret < 0) | ||
goto out_rtnl; | ||
|
||
ret = dev->ethtool_ops->get_preempt(dev, &preempt); | ||
if (ret < 0) { | ||
if (info) | ||
GENL_SET_ERR_MSG(info, "failed to retrieve frame preemption settings"); | ||
goto out_ops; | ||
} | ||
|
||
ethnl_update_u8(&preempt.fp_enabled, | ||
tb[ETHTOOL_A_PREEMPT_ACTIVE], &mod); | ||
ethnl_update_u32(&preempt.min_frag_size, | ||
tb[ETHTOOL_A_PREEMPT_MIN_FRAG_SIZE], &mod); | ||
ethnl_update_u32(&preempt.preemptible_queues_mask, | ||
tb[ETHTOOL_A_PREEMPT_QUEUES_PREEMPTIBLE], &mod); | ||
|
||
ret = 0; | ||
if (!mod) | ||
goto out_ops; | ||
|
||
ret = dev->ethtool_ops->set_preempt(dev, &preempt); | ||
if (ret < 0) | ||
GENL_SET_ERR_MSG(info, "frame preemption settings update failed"); | ||
else | ||
ethtool_notify(dev, ETHTOOL_MSG_PREEMPT_NTF, NULL); | ||
|
||
out_ops: | ||
ethnl_ops_complete(dev); | ||
out_rtnl: | ||
rtnl_unlock(); | ||
out_dev: | ||
dev_put(dev); | ||
return ret; | ||
} |