diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst
index 30b98245979f..fc71d11317c2 100644
--- a/Documentation/networking/ethtool-netlink.rst
+++ b/Documentation/networking/ethtool-netlink.rst
@@ -1279,6 +1279,44 @@ 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.
+PREEMPT_GET
+===========
+
+Get information about frame preemption state.
+
+Request contents:
+
+ ==================================== ====== ==========================
+ ``ETHTOOL_A_CHANNELS_HEADER`` nested request header
+ ==================================== ====== ==========================
+
+Kernel response contents:
+
+ ===================================== ====== ==========================
+ ``ETHTOOL_A_CHANNELS_HEADER`` nested reply header
+ ``ETHTOOL_A_PREEMPT_ENABLED`` u8 frame preemption enabled
+ ``ETHTOOL_A_PREEMPT_ADD_FRAG_SIZE`` u32 Min additional frag size
+ ===================================== ====== ==========================
+
+``ETHTOOL_A_PREEMPT_ADD_FRAG_SIZE`` configures the minimum non-final
+fragment size that the receiver device supports.
+
+PREEMPT_SET
+============
+
+Sets frame preemption parameters.
+
+Request contents:
+
+ ===================================== ====== ==========================
+ ``ETHTOOL_A_CHANNELS_HEADER`` nested reply header
+ ``ETHTOOL_A_PREEMPT_ENABLED`` u8 frame preemption enabled
+ ``ETHTOOL_A_PREEMPT_ADD_FRAG_SIZE`` u32 Min additional frag size
+ ===================================== ====== ==========================
+
+``ETHTOOL_A_PREEMPT_ADD_FRAG_SIZE`` configures the minimum non-final
+fragment size that the receiver device supports.
+
Request translation
===================
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index e3da25b51ae4..f7692a348e9d 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -263,6 +263,22 @@ struct ethtool_pause_stats {
u64 rx_pause_frames;
};
+/**
+ * struct ethtool_fp - Frame Preemption information
+ *
+ * @enabled: Enable frame preemption.
+
+ * @add_frag_size: Minimum size for additional (non-final) fragments
+ * in bytes, for the value defined in the IEEE 802.3-2018 standard see
+ * ethtool_frag_size_to_mult().
+ */
+struct ethtool_fp {
+ u8 enabled;
+ u32 add_frag_size;
+};
+
+struct netlink_ext_ack;
+
/**
* struct ethtool_ops - optional netdev operations
* @supported_coalesce_params: supported types of interrupt coalescing.@@ -406,6 +422,8 @@ struct ethtool_pause_stats {
* @get_ethtool_phy_stats: Return extended statistics about the PHY device.
* This is only useful if the device maintains PHY statistics and
* cannot use the standard PHY library helpers.
+ * @get_preempt: Get the network device Frame Preemption parameters.
+ * @set_preempt: Set the network device Frame Preemption parameters.
*
* All operations are optional (i.e. the function pointer may be set
* to %NULL) and callers must take this into account. Callers must@@ -504,6 +522,10 @@ struct ethtool_ops {
struct ethtool_fecparam *);
int (*set_fecparam)(struct net_device *,
struct ethtool_fecparam *);
+ int (*get_preempt)(struct net_device *,
+ struct ethtool_fp *);
+ int (*set_preempt)(struct net_device *, struct ethtool_fp *,
+ struct netlink_ext_ack *);
void (*get_ethtool_phy_stats)(struct net_device *,
struct ethtool_stats *, u64 *);
int (*get_phy_tunable)(struct net_device *,@@ -533,7 +555,6 @@ int ethtool_virtdev_set_link_ksettings(struct net_device *dev,
const struct ethtool_link_ksettings *cmd,
u32 *dev_speed, u8 *dev_duplex);
-struct netlink_ext_ack;
struct phy_device;
struct phy_tdr_config;
@@ -566,4 +587,20 @@ struct ethtool_phy_ops {
*/
void ethtool_set_ethtool_phy_ops(const struct ethtool_phy_ops *ops);
+/**
+ * Convert from a Frame Preemption Additional Fragment size in bytes
+ * to a multiplier. The multiplier is defined as:
+ * "A 2-bit integer value used to indicate the minimum size of
+ * non-final fragments supported by the receiver on the given port
+ * associated with the local System. This value is expressed in units
+ * of 64 octets of additional fragment length."
+ * Equivalent to `30.14.1.7 aMACMergeAddFragSize` from the IEEE 802.3-2018
+ * standard.
+ *
+ * @frag_size: minimum non-final fragment size in bytes
+ *
+ * Returns the multiplier.
+ */
+u8 ethtool_frag_size_to_mult(u32 frag_size);
+
#endif /* _LINUX_ETHTOOL_H */diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index e2bf36e6964b..bd7a722dc13a 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -42,6 +42,8 @@ enum {
ETHTOOL_MSG_CABLE_TEST_ACT,
ETHTOOL_MSG_CABLE_TEST_TDR_ACT,
ETHTOOL_MSG_TUNNEL_INFO_GET,
+ ETHTOOL_MSG_PREEMPT_GET,
+ ETHTOOL_MSG_PREEMPT_SET,
/* add new constants above here */
__ETHTOOL_MSG_USER_CNT,@@ -80,6 +82,8 @@ enum {
ETHTOOL_MSG_CABLE_TEST_NTF,
ETHTOOL_MSG_CABLE_TEST_TDR_NTF,
ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY,
+ ETHTOOL_MSG_PREEMPT_GET_REPLY,
+ ETHTOOL_MSG_PREEMPT_NTF,
/* add new constants above here */
__ETHTOOL_MSG_KERNEL_CNT,@@ -628,6 +632,19 @@ enum {
ETHTOOL_A_TUNNEL_INFO_MAX = (__ETHTOOL_A_TUNNEL_INFO_CNT - 1)
};
+/* FRAME PREEMPTION */
+
+enum {
+ ETHTOOL_A_PREEMPT_UNSPEC,
+ ETHTOOL_A_PREEMPT_HEADER, /* nest - _A_HEADER_* */
+ ETHTOOL_A_PREEMPT_ENABLED, /* u8 */
+ ETHTOOL_A_PREEMPT_ADD_FRAG_SIZE, /* u32 */
+
+ /* add new constants above here */
+ __ETHTOOL_A_PREEMPT_CNT,
+ ETHTOOL_A_PREEMPT_MAX = (__ETHTOOL_A_PREEMPT_CNT - 1)
+};
+
/* generic netlink info */
#define ETHTOOL_GENL_NAME "ethtool"
#define ETHTOOL_GENL_VERSION 1diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 7a849ff22dad..4e584903e3ef 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -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 preempt.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 24036e3055a1..6b47ee0565f5 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -410,3 +410,13 @@ void ethtool_set_ethtool_phy_ops(const struct ethtool_phy_ops *ops)
rtnl_unlock();
}
EXPORT_SYMBOL_GPL(ethtool_set_ethtool_phy_ops);
+
+u8 ethtool_frag_size_to_mult(u32 frag_size)
+{
+ u8 mult = (frag_size / 64) - 1;
+
+ mult = clamp_t(u8, mult, 0, 3);
+
+ return mult;
+}
+EXPORT_SYMBOL_GPL(ethtool_frag_size_to_mult);diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 50d3c8896f91..bc7d66e3ba38 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -245,6 +245,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_PAUSE_GET] = ðnl_pause_request_ops,
[ETHTOOL_MSG_EEE_GET] = ðnl_eee_request_ops,
[ETHTOOL_MSG_TSINFO_GET] = ðnl_tsinfo_request_ops,
+ [ETHTOOL_MSG_PREEMPT_GET] = ðnl_preempt_request_ops,
};
static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)@@ -551,6 +552,7 @@ ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = {
[ETHTOOL_MSG_COALESCE_NTF] = ðnl_coalesce_request_ops,
[ETHTOOL_MSG_PAUSE_NTF] = ðnl_pause_request_ops,
[ETHTOOL_MSG_EEE_NTF] = ðnl_eee_request_ops,
+ [ETHTOOL_MSG_PREEMPT_NTF] = ðnl_preempt_request_ops,
};
/* default notification handler */@@ -643,6 +645,7 @@ static const ethnl_notify_handler_t ethnl_notify_handlers[] = {
[ETHTOOL_MSG_COALESCE_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_PAUSE_NTF] = ethnl_default_notify,
[ETHTOOL_MSG_EEE_NTF] = ethnl_default_notify,
+ [ETHTOOL_MSG_PREEMPT_NTF] = ethnl_default_notify,
};
void ethtool_notify(struct net_device *dev, unsigned int cmd, const void *data)@@ -912,6 +915,22 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_tunnel_info_get_policy,
.maxattr = ARRAY_SIZE(ethnl_tunnel_info_get_policy) - 1,
},
+ {
+ .cmd = ETHTOOL_MSG_PREEMPT_GET,
+ .doit = ethnl_default_doit,
+ .start = ethnl_default_start,
+ .dumpit = ethnl_default_dumpit,
+ .done = ethnl_default_done,
+ .policy = ethnl_preempt_get_policy,
+ .maxattr = ARRAY_SIZE(ethnl_preempt_get_policy) - 1,
+ },
+ {
+ .cmd = ETHTOOL_MSG_PREEMPT_SET,
+ .flags = GENL_UNS_ADMIN_PERM,
+ .doit = ethnl_set_preempt,
+ .policy = ethnl_preempt_set_policy,
+ .maxattr = ARRAY_SIZE(ethnl_preempt_set_policy) - 1,
+ },
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index d8efec516d86..53d2e722fad3 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -344,6 +344,7 @@ extern const struct ethnl_request_ops ethnl_coalesce_request_ops;
extern const struct ethnl_request_ops ethnl_pause_request_ops;
extern const struct ethnl_request_ops ethnl_eee_request_ops;
extern const struct ethnl_request_ops ethnl_tsinfo_request_ops;
+extern const struct ethnl_request_ops ethnl_preempt_request_ops;
extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS + 1];
extern const struct nla_policy ethnl_header_policy_stats[ETHTOOL_A_HEADER_FLAGS + 1];
@@ -375,6 +376,8 @@ extern const struct nla_policy ethnl_tsinfo_get_policy[ETHTOOL_A_TSINFO_HEADER +
extern const struct nla_policy ethnl_cable_test_act_policy[ETHTOOL_A_CABLE_TEST_HEADER + 1];
extern const struct nla_policy ethnl_cable_test_tdr_act_policy[ETHTOOL_A_CABLE_TEST_TDR_CFG + 1];
extern const struct nla_policy ethnl_tunnel_info_get_policy[ETHTOOL_A_TUNNEL_INFO_HEADER + 1];
+extern const struct nla_policy ethnl_preempt_get_policy[ETHTOOL_A_PREEMPT_HEADER + 1];
+extern const struct nla_policy ethnl_preempt_set_policy[ETHTOOL_A_PREEMPT_ADD_FRAG_SIZE + 1];
int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);
@@ -392,5 +395,6 @@ int ethnl_act_cable_test_tdr(struct sk_buff *skb, struct genl_info *info);
int ethnl_tunnel_info_doit(struct sk_buff *skb, struct genl_info *info);
int ethnl_tunnel_info_start(struct netlink_callback *cb);
int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
+int ethnl_set_preempt(struct sk_buff *skb, struct genl_info *info);
#endif /* _NET_ETHTOOL_NETLINK_H */
diff --git a/net/ethtool/preempt.c b/net/ethtool/preempt.c
new file mode 100644
index 000000000000..4f96d3c2b1d5
--- /dev/null
+++ b/net/ethtool/preempt.c
@@ -0,0 +1,146 @@
+// 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)
+
+const struct nla_policy
+ethnl_preempt_get_policy[] = {
+ [ETHTOOL_A_PREEMPT_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
+};
+
+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_ENABLED */
+ len += nla_total_size(sizeof(u32)); /* _PREEMPT_ADD_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_u8(skb, ETHTOOL_A_PREEMPT_ENABLED, preempt->enabled))
+ return -EMSGSIZE;
+
+ if (nla_put_u32(skb, ETHTOOL_A_PREEMPT_ADD_FRAG_SIZE,
+ preempt->add_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,
+ .req_info_size = sizeof(struct preempt_req_info),
+ .reply_data_size = sizeof(struct preempt_reply_data),
+
+ .prepare_data = preempt_prepare_data,
+ .reply_size = preempt_reply_size,
+ .fill_reply = preempt_fill_reply,
+};
+
+const struct nla_policy
+ethnl_preempt_set_policy[ETHTOOL_A_PREEMPT_MAX + 1] = {
+ [ETHTOOL_A_PREEMPT_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
+ [ETHTOOL_A_PREEMPT_ENABLED] = NLA_POLICY_RANGE(NLA_U8, 0, 1),
+ [ETHTOOL_A_PREEMPT_ADD_FRAG_SIZE] = { .type = NLA_U32 },
+};
+
+int ethnl_set_preempt(struct sk_buff *skb, struct genl_info *info)
+{
+ struct ethnl_req_info req_info = {};
+ struct nlattr **tb = info->attrs;
+ struct ethtool_fp preempt = {};
+ struct net_device *dev;
+ bool mod = false;
+ int 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) {
+ GENL_SET_ERR_MSG(info, "failed to retrieve frame preemption settings");
+ goto out_ops;
+ }
+
+ ethnl_update_u8(&preempt.enabled,
+ tb[ETHTOOL_A_PREEMPT_ENABLED], &mod);
+ ethnl_update_u32(&preempt.add_frag_size,
+ tb[ETHTOOL_A_PREEMPT_ADD_FRAG_SIZE], &mod);
+ ret = 0;
+ if (!mod)
+ goto out_ops;
+
+ ret = dev->ethtool_ops->set_preempt(dev, &preempt, info->extack);
+ if (ret < 0) {
+ GENL_SET_ERR_MSG(info, "frame preemption settings update failed");
+ goto out_ops;
+ }
+
+ 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;
+}--
2.30.0