diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml
index 7e4a5b4e7162..b33604cccd23 100644
--- a/Documentation/netlink/specs/netdev.yaml
+++ b/Documentation/netlink/specs/netdev.yaml
@@ -40,6 +40,18 @@ definitions:
         doc:
           This feature informs if netdev implements non-linear XDP buffer
           support in ndo_xdp_xmit callback.
+  -
+    type: enum
+    name: xdp-act-bit
+    render-max: true
+    entries:
+      - name: basic-bit
+      - name: redirect-bit
+      - name: ndo-xmit-bit
+      - name: xsk-zerocopy-bit
+      - name: hw-offload-bit
+      - name: rx-sg-bit
+      - name: ndo-xmit-sg-bit
 
 attribute-sets:
   -
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h
index f7fba0dc87e5..2c52adf7cb69 100644
--- a/include/uapi/linux/ethtool.h
+++ b/include/uapi/linux/ethtool.h
@@ -681,6 +681,7 @@ enum ethtool_link_ext_substate_module {
  * @ETH_SS_STATS_ETH_MAC: names of IEEE 802.3 MAC statistics
  * @ETH_SS_STATS_ETH_CTRL: names of IEEE 802.3 MAC Control statistics
  * @ETH_SS_STATS_RMON: names of RMON statistics
+ * @ETH_SS_XDP_FEATURES: names of XDP supported features
  *
  * @ETH_SS_COUNT: number of defined string sets
  */
@@ -706,6 +707,7 @@ enum ethtool_stringset {
 	ETH_SS_STATS_ETH_MAC,
 	ETH_SS_STATS_ETH_CTRL,
 	ETH_SS_STATS_RMON,
+	ETH_SS_XDP_FEATURES,
 
 	/* add new constants above here */
 	ETH_SS_COUNT
@@ -1429,6 +1431,18 @@ struct ethtool_sfeatures {
 	struct ethtool_set_features_block features[];
 };
 
+/**
+ * struct ethtool_xdp_gfeatures - command to get supported XDP features
+ * @cmd: command number = %ETHTOOL_XDP_GFEATURES
+ * size: array size of the features[] array
+ * @features: XDP feature masks
+ */
+struct ethtool_xdp_gfeatures {
+	__u32	cmd;
+	__u32	size;
+	__u32	features[];
+};
+
 /**
  * struct ethtool_ts_info - holds a device's timestamping and PHC association
  * @cmd: command number = %ETHTOOL_GET_TS_INFO
@@ -1670,6 +1684,7 @@ enum ethtool_fec_config_bits {
 #define ETHTOOL_PHY_STUNABLE	0x0000004f /* Set PHY tunable configuration */
 #define ETHTOOL_GFECPARAM	0x00000050 /* Get FEC settings */
 #define ETHTOOL_SFECPARAM	0x00000051 /* Set FEC settings */
+#define ETHTOOL_XDP_GFEATURES	0x00000052 /* Get XDP features */
 
 /* compatibility with older code */
 #define SPARC_ETH_GSET		ETHTOOL_GSET
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index d39ce21381c5..cf859d774e91 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -57,6 +57,7 @@ enum {
 	ETHTOOL_MSG_PLCA_GET_STATUS,
 	ETHTOOL_MSG_MM_GET,
 	ETHTOOL_MSG_MM_SET,
+	ETHTOOL_MSG_XDP_FEATURES_GET,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_USER_CNT,
@@ -109,6 +110,7 @@ enum {
 	ETHTOOL_MSG_PLCA_NTF,
 	ETHTOOL_MSG_MM_GET_REPLY,
 	ETHTOOL_MSG_MM_NTF,
+	ETHTOOL_MSG_XDP_FEATURES_GET_REPLY,
 
 	/* add new constants above here */
 	__ETHTOOL_MSG_KERNEL_CNT,
@@ -973,6 +975,18 @@ enum {
 	ETHTOOL_A_MM_MAX = (__ETHTOOL_A_MM_CNT - 1)
 };
 
+/* XDP */
+
+enum {
+	ETHTOOL_A_XDP_FEATURES_UNSPEC,
+	ETHTOOL_A_XDP_FEATURES_HEADER,		/* nest - _A_HEADER_* */
+	ETHTOOL_A_XDP_FEATURES_DATA,		/* bitsets */
+
+	/* add new constants above here */
+	__ETHTOOL_A_XDP_FEATURES_CNT,
+	ETHTOOL_A_XDP_FEATURES_MAX = __ETHTOOL_A_XDP_FEATURES_CNT - 1
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h
index 497cfc93f2e3..ca7678e5f16f 100644
--- a/include/uapi/linux/netdev.h
+++ b/include/uapi/linux/netdev.h
@@ -37,6 +37,19 @@ enum netdev_xdp_act {
 	NETDEV_XDP_ACT_MASK = 127,
 };
 
+enum netdev_xdp_act_bit {
+	NETDEV_XDP_ACT_BIT_BASIC_BIT,
+	NETDEV_XDP_ACT_BIT_REDIRECT_BIT,
+	NETDEV_XDP_ACT_BIT_NDO_XMIT_BIT,
+	NETDEV_XDP_ACT_BIT_XSK_ZEROCOPY_BIT,
+	NETDEV_XDP_ACT_BIT_HW_OFFLOAD_BIT,
+	NETDEV_XDP_ACT_BIT_RX_SG_BIT,
+	NETDEV_XDP_ACT_BIT_NDO_XMIT_SG_BIT,
+
+	__NETDEV_XDP_ACT_BIT_MAX,
+	NETDEV_XDP_ACT_BIT_MAX = (__NETDEV_XDP_ACT_BIT_MAX - 1)
+};
+
 enum {
 	NETDEV_A_DEV_IFINDEX = 1,
 	NETDEV_A_DEV_PAD,
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index 504f954a1b28..0c5463c07223 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -8,4 +8,4 @@ ethtool_nl-y	:= netlink.o bitset.o strset.o linkinfo.o linkmodes.o rss.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 fec.o eeprom.o stats.o phc_vclocks.o mm.o \
-		   module.o pse-pd.o plca.o mm.o
+		   module.o pse-pd.o plca.o mm.o xdp.o
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 5fb19050991e..2be672c601ad 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -465,6 +465,17 @@ const char udp_tunnel_type_names[][ETH_GSTRING_LEN] = {
 static_assert(ARRAY_SIZE(udp_tunnel_type_names) ==
 	      __ETHTOOL_UDP_TUNNEL_TYPE_CNT);
 
+const char xdp_features_strings[][ETH_GSTRING_LEN] = {
+	[NETDEV_XDP_ACT_BIT_BASIC_BIT] =	"xdp-basic",
+	[NETDEV_XDP_ACT_BIT_REDIRECT_BIT] =	"xdp-redirect",
+	[NETDEV_XDP_ACT_BIT_NDO_XMIT_BIT] =	"xdp-ndo-xmit",
+	[NETDEV_XDP_ACT_BIT_XSK_ZEROCOPY_BIT] =	"xdp-xsk-zerocopy",
+	[NETDEV_XDP_ACT_BIT_HW_OFFLOAD_BIT] =	"xdp-hw-offload",
+	[NETDEV_XDP_ACT_BIT_RX_SG_BIT] =	"xdp-rx-sg",
+	[NETDEV_XDP_ACT_BIT_NDO_XMIT_SG_BIT] =	"xdp-ndo-xmit-sg",
+};
+static_assert(ARRAY_SIZE(xdp_features_strings) == __NETDEV_XDP_ACT_BIT_MAX);
+
 /* return false if legacy contained non-0 deprecated fields
  * maxtxpkt/maxrxpkt. rest of ksettings always updated
  */
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 28b8aaaf9bcb..9c7fd25664cf 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -7,6 +7,7 @@
 #include <linux/ethtool.h>
 
 #define ETHTOOL_DEV_FEATURE_WORDS	DIV_ROUND_UP(NETDEV_FEATURE_COUNT, 32)
+#define ETHTOOL_XDP_FEATURES_WORDS	DIV_ROUND_UP(__NETDEV_XDP_ACT_BIT_MAX, 32)
 
 /* compose link mode index from speed, type and duplex */
 #define ETHTOOL_LINK_MODE(speed, type, duplex) \
@@ -36,6 +37,7 @@ extern const char sof_timestamping_names[][ETH_GSTRING_LEN];
 extern const char ts_tx_type_names[][ETH_GSTRING_LEN];
 extern const char ts_rx_filter_names[][ETH_GSTRING_LEN];
 extern const char udp_tunnel_type_names[][ETH_GSTRING_LEN];
+extern const char xdp_features_strings[__NETDEV_XDP_ACT_BIT_MAX][ETH_GSTRING_LEN];
 
 int __ethtool_get_link(struct net_device *dev);
 
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 646b3e490c71..3d9c14d3ecc8 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -170,6 +170,9 @@ static int __ethtool_get_sset_count(struct net_device *dev, int sset)
 	if (sset == ETH_SS_PHY_TUNABLES)
 		return ARRAY_SIZE(phy_tunable_strings);
 
+	if (sset == ETH_SS_XDP_FEATURES)
+		return __NETDEV_XDP_ACT_BIT_MAX;
+
 	if (sset == ETH_SS_PHY_STATS && dev->phydev &&
 	    !ops->get_ethtool_phy_stats &&
 	    phy_ops && phy_ops->get_sset_count)
@@ -200,6 +203,8 @@ static void __ethtool_get_strings(struct net_device *dev,
 		memcpy(data, tunable_strings, sizeof(tunable_strings));
 	else if (stringset == ETH_SS_PHY_TUNABLES)
 		memcpy(data, phy_tunable_strings, sizeof(phy_tunable_strings));
+	else if (stringset == ETH_SS_XDP_FEATURES)
+		memcpy(data, xdp_features_strings, sizeof(xdp_features_strings));
 	else if (stringset == ETH_SS_PHY_STATS && dev->phydev &&
 		 !ops->get_ethtool_phy_stats && phy_ops &&
 		 phy_ops->get_strings)
@@ -2749,6 +2754,37 @@ static int ethtool_set_fecparam(struct net_device *dev, void __user *useraddr)
 	return dev->ethtool_ops->set_fecparam(dev, &fecparam);
 }
 
+static int ethtool_get_xdp_features(struct net_device *dev,
+				    void __user *useraddr)
+{
+	u32 copy_size, features[ETHTOOL_XDP_FEATURES_WORDS];
+	struct ethtool_gfeatures cmd = {
+		.cmd = ETHTOOL_XDP_GFEATURES,
+		.size = ETHTOOL_XDP_FEATURES_WORDS,
+	};
+	u32 __user *sizeaddr;
+
+	BUILD_BUG_ON(ETHTOOL_XDP_FEATURES_WORDS != 1);
+	features[0] = dev->xdp_features;
+
+	sizeaddr = useraddr + offsetof(struct ethtool_xdp_gfeatures, size);
+	if (get_user(copy_size, sizeaddr))
+		return -EFAULT;
+
+	if (copy_size > ETHTOOL_XDP_FEATURES_WORDS)
+		copy_size = ETHTOOL_XDP_FEATURES_WORDS;
+
+	if (copy_to_user(useraddr, &cmd, sizeof(cmd)))
+		return -EFAULT;
+
+	useraddr += sizeof(cmd);
+	if (copy_to_user(useraddr, features,
+			 array_size(copy_size, sizeof(*features))))
+		return -EFAULT;
+
+	return 0;
+}
+
 /* The main entry point in this file.  Called from net/core/dev_ioctl.c */
 
 static int
@@ -2808,6 +2844,7 @@ __dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
 	case ETHTOOL_PHY_GTUNABLE:
 	case ETHTOOL_GLINKSETTINGS:
 	case ETHTOOL_GFECPARAM:
+	case ETHTOOL_XDP_GFEATURES:
 		break;
 	default:
 		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
@@ -3035,6 +3072,9 @@ __dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr,
 	case ETHTOOL_SFECPARAM:
 		rc = ethtool_set_fecparam(dev, useraddr);
 		break;
+	case ETHTOOL_XDP_GFEATURES:
+		rc = ethtool_get_xdp_features(dev, useraddr);
+		break;
 	default:
 		rc = -EOPNOTSUPP;
 	}
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 08120095cc68..084927dc715e 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -306,6 +306,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
 	[ETHTOOL_MSG_PLCA_GET_STATUS]	= &ethnl_plca_status_request_ops,
 	[ETHTOOL_MSG_MM_GET]		= &ethnl_mm_request_ops,
 	[ETHTOOL_MSG_MM_SET]		= &ethnl_mm_request_ops,
+	[ETHTOOL_MSG_XDP_FEATURES_GET]	= &ethnl_xdp_request_ops,
 };
 
 static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb)
@@ -1156,6 +1157,15 @@ static const struct genl_ops ethtool_genl_ops[] = {
 		.policy = ethnl_mm_set_policy,
 		.maxattr = ARRAY_SIZE(ethnl_mm_set_policy) - 1,
 	},
+	{
+		.cmd	= ETHTOOL_MSG_XDP_FEATURES_GET,
+		.doit	= ethnl_default_doit,
+		.start	= ethnl_default_start,
+		.dumpit	= ethnl_default_dumpit,
+		.done	= ethnl_default_done,
+		.policy = ethnl_xdp_features_get_policy,
+		.maxattr = ARRAY_SIZE(ethnl_xdp_features_get_policy) - 1,
+	}
 };
 
 static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index f7b189ed96b2..b04580a3b6f5 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -395,6 +395,7 @@ extern const struct ethnl_request_ops ethnl_rss_request_ops;
 extern const struct ethnl_request_ops ethnl_plca_cfg_request_ops;
 extern const struct ethnl_request_ops ethnl_plca_status_request_ops;
 extern const struct ethnl_request_ops ethnl_mm_request_ops;
+extern const struct ethnl_request_ops ethnl_xdp_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];
@@ -441,6 +442,7 @@ extern const struct nla_policy ethnl_plca_set_cfg_policy[ETHTOOL_A_PLCA_MAX + 1]
 extern const struct nla_policy ethnl_plca_get_status_policy[ETHTOOL_A_PLCA_HEADER + 1];
 extern const struct nla_policy ethnl_mm_get_policy[ETHTOOL_A_MM_HEADER + 1];
 extern const struct nla_policy ethnl_mm_set_policy[ETHTOOL_A_MM_MAX + 1];
+extern const struct nla_policy ethnl_xdp_features_get_policy[ETHTOOL_A_XDP_FEATURES_HEADER + 1];
 
 int ethnl_set_features(struct sk_buff *skb, struct genl_info *info);
 int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info);
diff --git a/net/ethtool/strset.c b/net/ethtool/strset.c
index 3f7de54d85fb..a148fe3301f1 100644
--- a/net/ethtool/strset.c
+++ b/net/ethtool/strset.c
@@ -105,6 +105,11 @@ static const struct strset_info info_template[] = {
 		.count		= __ETHTOOL_A_STATS_RMON_CNT,
 		.strings	= stats_rmon_names,
 	},
+	[ETH_SS_XDP_FEATURES] = {
+		.per_dev	= false,
+		.count		= ARRAY_SIZE(xdp_features_strings),
+		.strings	= xdp_features_strings,
+	},
 };
 
 struct strset_req_info {
diff --git a/net/ethtool/xdp.c b/net/ethtool/xdp.c
new file mode 100644
index 000000000000..fa11a03bb00d
--- /dev/null
+++ b/net/ethtool/xdp.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "netlink.h"
+#include "common.h"
+#include "bitset.h"
+
+const struct nla_policy ethnl_xdp_features_get_policy[] = {
+	[ETHTOOL_A_XDP_FEATURES_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
+};
+
+struct xdp_feature_req_info {
+	struct ethnl_req_info base;
+};
+
+#define XDP_FEATURES_REPDATA(__reply_base)	\
+	container_of(__reply_base, struct xdp_feature_reply_data, base)
+
+struct xdp_feature_reply_data {
+	struct ethnl_reply_data base;
+	u32 features[ETHTOOL_XDP_FEATURES_WORDS];
+};
+
+static int xdp_features_prepare_data(const struct ethnl_req_info *req_base,
+				     struct ethnl_reply_data *reply_base,
+				     struct genl_info *info)
+{
+	struct xdp_feature_reply_data *data = XDP_FEATURES_REPDATA(reply_base);
+	struct net_device *dev = reply_base->dev;
+
+	BUILD_BUG_ON(ETHTOOL_XDP_FEATURES_WORDS != 1);
+	data->features[0] = dev->xdp_features;
+
+	return 0;
+}
+
+static int xdp_features_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 xdp_feature_reply_data *data;
+
+	data = XDP_FEATURES_REPDATA(reply_base);
+	return ethnl_bitset32_size(data->features, NULL, __NETDEV_XDP_ACT_BIT_MAX,
+				   xdp_features_strings, compact);
+}
+
+static int xdp_features_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 xdp_feature_reply_data *data;
+
+	data = XDP_FEATURES_REPDATA(reply_base);
+	return ethnl_put_bitset32(skb, ETHTOOL_A_XDP_FEATURES_DATA,
+				  data->features, NULL, __NETDEV_XDP_ACT_BIT_MAX,
+				  xdp_features_strings, compact);
+}
+
+const struct ethnl_request_ops ethnl_xdp_request_ops = {
+	.request_cmd		= ETHTOOL_MSG_XDP_FEATURES_GET,
+	.reply_cmd		= ETHTOOL_MSG_XDP_FEATURES_GET_REPLY,
+	.hdr_attr		= ETHTOOL_A_XDP_FEATURES_HEADER,
+	.req_info_size		= sizeof(struct xdp_feature_req_info),
+	.reply_data_size	= sizeof(struct xdp_feature_reply_data),
+	.prepare_data		= xdp_features_prepare_data,
+	.reply_size		= xdp_features_reply_size,
+	.fill_reply		= xdp_features_fill_reply,
+};
diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h
index 497cfc93f2e3..ca7678e5f16f 100644
--- a/tools/include/uapi/linux/netdev.h
+++ b/tools/include/uapi/linux/netdev.h
@@ -37,6 +37,19 @@ enum netdev_xdp_act {
 	NETDEV_XDP_ACT_MASK = 127,
 };
 
+enum netdev_xdp_act_bit {
+	NETDEV_XDP_ACT_BIT_BASIC_BIT,
+	NETDEV_XDP_ACT_BIT_REDIRECT_BIT,
+	NETDEV_XDP_ACT_BIT_NDO_XMIT_BIT,
+	NETDEV_XDP_ACT_BIT_XSK_ZEROCOPY_BIT,
+	NETDEV_XDP_ACT_BIT_HW_OFFLOAD_BIT,
+	NETDEV_XDP_ACT_BIT_RX_SG_BIT,
+	NETDEV_XDP_ACT_BIT_NDO_XMIT_SG_BIT,
+
+	__NETDEV_XDP_ACT_BIT_MAX,
+	NETDEV_XDP_ACT_BIT_MAX = (__NETDEV_XDP_ACT_BIT_MAX - 1)
+};
+
 enum {
 	NETDEV_A_DEV_IFINDEX = 1,
 	NETDEV_A_DEV_PAD,
