diff mbox series

[v17,02/20] netlink: add new family to manage ULP_DDP enablement and stats

Message ID 20231024125445.2632-3-aaptel@nvidia.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series nvme-tcp receive offloads | expand

Checks

Context Check Description
netdev/series_format fail Series longer than 15 patches (and no cover letter)
netdev/tree_selection success Guessed tree name to be net-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1365 this patch: 1365
netdev/cc_maintainers success CCed 7 of 7 maintainers
netdev/build_clang success Errors and warnings before: 1389 this patch: 1389
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1390 this patch: 1390
netdev/checkpatch warning WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 82 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: line length of 86 exceeds 80 columns WARNING: line length of 87 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Aurelien Aptel Oct. 24, 2023, 12:54 p.m. UTC
Add a new netlink family to get/set ULP DDP capabilities on a network
device and to retrieve statistics.

The messages use the genetlink infrastructure and are specified in a
YAML file which was used to generate some of the files in this commit:

./tools/net/ynl/ynl-gen-c.py --mode kernel \
    --spec ./Documentation/netlink/specs/ulp_ddp.yaml --header \
    -o net/core/ulp_ddp_gen_nl.h
./tools/net/ynl/ynl-gen-c.py --mode kernel \
    --spec ./Documentation/netlink/specs/ulp_ddp.yaml --source \
    -o net/core/ulp_ddp_gen_nl.c
./tools/net/ynl/ynl-gen-c.py --mode uapi \
    --spec ./Documentation/netlink/specs/ulp_ddp.yaml --header \
    > include/uapi/linux/ulp_ddp_nl.h

Signed-off-by: Shai Malin <smalin@nvidia.com>
Signed-off-by: Aurelien Aptel <aaptel@nvidia.com>
---
 Documentation/netlink/specs/ulp_ddp.yaml | 183 +++++++++++
 include/uapi/linux/ulp_ddp_nl.h          |  59 ++++
 net/core/Makefile                        |   2 +-
 net/core/ulp_ddp_gen_nl.c                |  85 +++++
 net/core/ulp_ddp_gen_nl.h                |  32 ++
 net/core/ulp_ddp_nl.c                    | 388 +++++++++++++++++++++++
 6 files changed, 748 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/netlink/specs/ulp_ddp.yaml
 create mode 100644 include/uapi/linux/ulp_ddp_nl.h
 create mode 100644 net/core/ulp_ddp_gen_nl.c
 create mode 100644 net/core/ulp_ddp_gen_nl.h
 create mode 100644 net/core/ulp_ddp_nl.c

Comments

Jiri Pirko Oct. 24, 2023, 1:58 p.m. UTC | #1
Tue, Oct 24, 2023 at 02:54:27PM CEST, aaptel@nvidia.com wrote:
>Add a new netlink family to get/set ULP DDP capabilities on a network
>device and to retrieve statistics.
>
>The messages use the genetlink infrastructure and are specified in a
>YAML file which was used to generate some of the files in this commit:
>
>./tools/net/ynl/ynl-gen-c.py --mode kernel \
>    --spec ./Documentation/netlink/specs/ulp_ddp.yaml --header \
>    -o net/core/ulp_ddp_gen_nl.h
>./tools/net/ynl/ynl-gen-c.py --mode kernel \
>    --spec ./Documentation/netlink/specs/ulp_ddp.yaml --source \
>    -o net/core/ulp_ddp_gen_nl.c
>./tools/net/ynl/ynl-gen-c.py --mode uapi \
>    --spec ./Documentation/netlink/specs/ulp_ddp.yaml --header \
>    > include/uapi/linux/ulp_ddp_nl.h
>
>Signed-off-by: Shai Malin <smalin@nvidia.com>
>Signed-off-by: Aurelien Aptel <aaptel@nvidia.com>
>---
> Documentation/netlink/specs/ulp_ddp.yaml | 183 +++++++++++
> include/uapi/linux/ulp_ddp_nl.h          |  59 ++++
> net/core/Makefile                        |   2 +-
> net/core/ulp_ddp_gen_nl.c                |  85 +++++
> net/core/ulp_ddp_gen_nl.h                |  32 ++
> net/core/ulp_ddp_nl.c                    | 388 +++++++++++++++++++++++
> 6 files changed, 748 insertions(+), 1 deletion(-)
> create mode 100644 Documentation/netlink/specs/ulp_ddp.yaml
> create mode 100644 include/uapi/linux/ulp_ddp_nl.h
> create mode 100644 net/core/ulp_ddp_gen_nl.c
> create mode 100644 net/core/ulp_ddp_gen_nl.h
> create mode 100644 net/core/ulp_ddp_nl.c
>
>diff --git a/Documentation/netlink/specs/ulp_ddp.yaml b/Documentation/netlink/specs/ulp_ddp.yaml
>new file mode 100644
>index 000000000000..882aa4e52992
>--- /dev/null
>+++ b/Documentation/netlink/specs/ulp_ddp.yaml
>@@ -0,0 +1,183 @@
>+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
>+#
>+# Author: Aurelien Aptel <aaptel@nvidia.com>
>+#
>+# Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES.  All rights reserved.
>+#
>+
>+name: ulp_ddp
>+
>+protocol: genetlink
>+
>+doc: Netlink protocol to manage ULP DPP on network devices.
>+
>+definitions:
>+  -
>+    type: enum
>+    name: cap
>+    entries:
>+      - nvme-tcp
>+      - nvme-tcp-ddgst-rx
>+
>+uapi-header: linux/ulp_ddp_nl.h

Not needed.
Hmm, Jakub, why this is not only allowed in genetlink-legacy?


>+
>+attribute-sets:
>+  -
>+    name: stat

"stats"?


>+    attributes:
>+      -
>+        name: ifindex
>+        doc: interface index of the net device.
>+        type: u32
>+      -
>+        name: pad
>+        type: pad
>+      -
>+        name: rx-nvmeotcp-sk-add
>+        doc: Sockets successfully configured for NVMeTCP offloading.
>+        type: u64
>+      -
>+        name: rx-nvmeotcp-sk-add-fail

"nvmeotcp" should stand for "nvme over tcp"? Why not to name it just
"rx-nvem-tcp-x"?


>+        doc: Sockets failed to be configured for NVMeTCP offloading.
>+        type: u64
>+      -
>+        name: rx-nvmeotcp-sk-del
>+        doc: Sockets with NVMeTCP offloading configuration removed.
>+        type: u64
>+      -
>+        name: rx-nvmeotcp-setup
>+        doc: NVMe-TCP IOs successfully configured for Rx Direct Data Placement.
>+        type: u64
>+      -
>+        name: rx-nvmeotcp-setup-fail
>+        doc: NVMe-TCP IOs failed to be configured for Rx Direct Data Placement.
>+        type: u64
>+      -
>+        name: rx-nvmeotcp-teardown
>+        doc: NVMe-TCP IOs with Rx Direct Data Placement configuration removed.
>+        type: u64
>+      -
>+        name: rx-nvmeotcp-drop
>+        doc: Packets failed the NVMeTCP offload validation.
>+        type: u64
>+      -
>+        name: rx-nvmeotcp-resync
>+        doc: >
>+          NVMe-TCP resync operations were processed due to Rx TCP packets
>+          re-ordering.
>+        type: u64
>+      -
>+        name: rx-nvmeotcp-packets
>+        doc: TCP packets successfully processed by the NVMeTCP offload.
>+        type: u64
>+      -
>+        name: rx-nvmeotcp-bytes
>+        doc: Bytes were successfully processed by the NVMeTCP offload.
>+        type: u64
>+  -
>+    name: dev

If this is attribute set for "caps-get"/"caps-set" only, why it is not
called "caps"?


>+    attributes:
>+      -
>+        name: ifindex
>+        doc: interface index of the net device.
>+        type: u32
>+      -
>+        name: hw
>+        doc: bitmask of the capabilities supported by the device.
>+        type: u64
>+        enum: cap
>+        enum-as-flags: true
>+      -
>+        name: active
>+        doc: bitmask of the capabilities currently enabled on the device.
>+        type: u64
>+        enum: cap
>+        enum-as-flags: true
>+      -
>+        name: wanted

For all caps related attrs, either put "caps" into the name or do that
and put it in a caps nest


>+        doc: >
>+          new active bit values of the capabilities we want to set on the
>+          device.
>+        type: u64
>+        enum: cap
>+        enum-as-flags: true
>+      -
>+        name: wanted_mask
>+        doc: bitmask of the meaningful bits in the wanted field.
>+        type: u64
>+        enum: cap
>+        enum-as-flags: true
>+      -
>+        name: pad
>+        type: pad
>+
>+operations:
>+  list:
>+    -
>+      name: get
>+      doc: Get ULP DDP capabilities.

This is for capabalities only, nothing else?
If yes, why not to name the op "caps-get"/"caps-set"?
If not and this is related to "dev", name it perhaps "dev-get"?
I mean, you should be able to align the op name and attribute set name.


>+      attribute-set: dev
>+      do:
>+        request:
>+          attributes:
>+            - ifindex
>+        reply: &dev-all
>+          attributes:
>+            - ifindex
>+            - hw
>+            - active
>+        pre: ulp_ddp_get_netdev
>+        post: ulp_ddp_put_netdev
>+      dump:
>+        reply: *dev-all
>+    -
>+      name: stats

"stats-get" ?


>+      doc: Get ULP DDP stats.
>+      attribute-set: stat
>+      do:
>+        request:
>+          attributes:
>+            - ifindex
>+        reply: &stats-all
>+          attributes:
>+            - ifindex
>+            - rx-nvmeotcp-sk-add
>+            - rx-nvmeotcp-sk-add-fail
>+            - rx-nvmeotcp-sk-del
>+            - rx-nvmeotcp-setup
>+            - rx-nvmeotcp-setup-fail
>+            - rx-nvmeotcp-teardown
>+            - rx-nvmeotcp-drop
>+            - rx-nvmeotcp-resync
>+            - rx-nvmeotcp-packets
>+            - rx-nvmeotcp-bytes
>+        pre: ulp_ddp_get_netdev
>+        post: ulp_ddp_put_netdev
>+      dump:
>+        reply: *stats-all
>+    -
>+      name: set
>+      doc: Set ULP DDP capabilities.
>+      attribute-set: dev
>+      do:
>+        request:
>+          attributes:
>+            - ifindex
>+            - wanted
>+            - wanted_mask
>+        reply:
>+          attributes:
>+            - ifindex
>+            - hw
>+            - active
>+        pre: ulp_ddp_get_netdev
>+        post: ulp_ddp_put_netdev
>+    -
>+      name: set-ntf
>+      doc: Notification for change in ULP DDP capabilities.
>+      notify: get
>+
>+mcast-groups:
>+  list:
>+    -
>+      name: mgmt
>diff --git a/include/uapi/linux/ulp_ddp_nl.h b/include/uapi/linux/ulp_ddp_nl.h
>new file mode 100644
>index 000000000000..fc63749c9251
>--- /dev/null
>+++ b/include/uapi/linux/ulp_ddp_nl.h
>@@ -0,0 +1,59 @@
>+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
>+/* Do not edit directly, auto-generated from: */
>+/*	Documentation/netlink/specs/ulp_ddp.yaml */
>+/* YNL-GEN uapi header */
>+
>+#ifndef _UAPI_LINUX_ULP_DDP_H
>+#define _UAPI_LINUX_ULP_DDP_H
>+
>+#define ULP_DDP_FAMILY_NAME	"ulp_ddp"
>+#define ULP_DDP_FAMILY_VERSION	1
>+
>+enum ulp_ddp_cap {
>+	ULP_DDP_CAP_NVME_TCP,
>+	ULP_DDP_CAP_NVME_TCP_DDGST_RX,
>+};
>+
>+enum {
>+	ULP_DDP_A_STAT_IFINDEX = 1,
>+	ULP_DDP_A_STAT_PAD,
>+	ULP_DDP_A_STAT_RX_NVMEOTCP_SK_ADD,
>+	ULP_DDP_A_STAT_RX_NVMEOTCP_SK_ADD_FAIL,
>+	ULP_DDP_A_STAT_RX_NVMEOTCP_SK_DEL,
>+	ULP_DDP_A_STAT_RX_NVMEOTCP_SETUP,
>+	ULP_DDP_A_STAT_RX_NVMEOTCP_SETUP_FAIL,
>+	ULP_DDP_A_STAT_RX_NVMEOTCP_TEARDOWN,
>+	ULP_DDP_A_STAT_RX_NVMEOTCP_DROP,
>+	ULP_DDP_A_STAT_RX_NVMEOTCP_RESYNC,
>+	ULP_DDP_A_STAT_RX_NVMEOTCP_PACKETS,
>+	ULP_DDP_A_STAT_RX_NVMEOTCP_BYTES,
>+
>+	__ULP_DDP_A_STAT_MAX,
>+	ULP_DDP_A_STAT_MAX = (__ULP_DDP_A_STAT_MAX - 1)
>+};
>+
>+enum {
>+	ULP_DDP_A_DEV_IFINDEX = 1,
>+	ULP_DDP_A_DEV_HW,
>+	ULP_DDP_A_DEV_ACTIVE,
>+	ULP_DDP_A_DEV_WANTED,
>+	ULP_DDP_A_DEV_WANTED_MASK,
>+	ULP_DDP_A_DEV_PAD,
>+
>+	__ULP_DDP_A_DEV_MAX,
>+	ULP_DDP_A_DEV_MAX = (__ULP_DDP_A_DEV_MAX - 1)
>+};
>+
>+enum {
>+	ULP_DDP_CMD_GET = 1,
>+	ULP_DDP_CMD_STATS,
>+	ULP_DDP_CMD_SET,
>+	ULP_DDP_CMD_SET_NTF,
>+
>+	__ULP_DDP_CMD_MAX,
>+	ULP_DDP_CMD_MAX = (__ULP_DDP_CMD_MAX - 1)
>+};
>+
>+#define ULP_DDP_MCGRP_MGMT	"mgmt"
>+
>+#endif /* _UAPI_LINUX_ULP_DDP_H */
>diff --git a/net/core/Makefile b/net/core/Makefile
>index b6a16e7c955a..1aff91f0fce0 100644
>--- a/net/core/Makefile
>+++ b/net/core/Makefile
>@@ -18,7 +18,7 @@ obj-y		     += dev.o dev_addr_lists.o dst.o netevent.o \
> obj-$(CONFIG_NETDEV_ADDR_LIST_TEST) += dev_addr_lists_test.o
> 
> obj-y += net-sysfs.o
>-obj-$(CONFIG_ULP_DDP) += ulp_ddp.o
>+obj-$(CONFIG_ULP_DDP) += ulp_ddp.o ulp_ddp_nl.o ulp_ddp_gen_nl.o
> obj-$(CONFIG_PAGE_POOL) += page_pool.o
> obj-$(CONFIG_PROC_FS) += net-procfs.o
> obj-$(CONFIG_NET_PKTGEN) += pktgen.o
>diff --git a/net/core/ulp_ddp_gen_nl.c b/net/core/ulp_ddp_gen_nl.c
>new file mode 100644
>index 000000000000..505bdc69b215
>--- /dev/null
>+++ b/net/core/ulp_ddp_gen_nl.c
>@@ -0,0 +1,85 @@
>+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
>+/* Do not edit directly, auto-generated from: */
>+/*	Documentation/netlink/specs/ulp_ddp.yaml */
>+/* YNL-GEN kernel source */
>+
>+#include <net/netlink.h>
>+#include <net/genetlink.h>
>+
>+#include "ulp_ddp_gen_nl.h"
>+
>+#include <uapi/linux/ulp_ddp_nl.h>
>+
>+/* ULP_DDP_CMD_GET - do */
>+static const struct nla_policy ulp_ddp_get_nl_policy[ULP_DDP_A_DEV_IFINDEX + 1] = {
>+	[ULP_DDP_A_DEV_IFINDEX] = { .type = NLA_U32, },
>+};
>+
>+/* ULP_DDP_CMD_STATS - do */
>+static const struct nla_policy ulp_ddp_stats_nl_policy[ULP_DDP_A_STAT_IFINDEX + 1] = {
>+	[ULP_DDP_A_STAT_IFINDEX] = { .type = NLA_U32, },
>+};
>+
>+/* ULP_DDP_CMD_SET - do */
>+static const struct nla_policy ulp_ddp_set_nl_policy[ULP_DDP_A_DEV_WANTED_MASK + 1] = {
>+	[ULP_DDP_A_DEV_IFINDEX] = { .type = NLA_U32, },
>+	[ULP_DDP_A_DEV_WANTED] = NLA_POLICY_MASK(NLA_U64, 0x3),
>+	[ULP_DDP_A_DEV_WANTED_MASK] = NLA_POLICY_MASK(NLA_U64, 0x3),
>+};
>+
>+/* Ops table for ulp_ddp */
>+static const struct genl_split_ops ulp_ddp_nl_ops[] = {
>+	{
>+		.cmd		= ULP_DDP_CMD_GET,
>+		.pre_doit	= ulp_ddp_get_netdev,
>+		.doit		= ulp_ddp_nl_get_doit,
>+		.post_doit	= ulp_ddp_put_netdev,
>+		.policy		= ulp_ddp_get_nl_policy,
>+		.maxattr	= ULP_DDP_A_DEV_IFINDEX,
>+		.flags		= GENL_CMD_CAP_DO,
>+	},
>+	{
>+		.cmd	= ULP_DDP_CMD_GET,
>+		.dumpit	= ulp_ddp_nl_get_dumpit,
>+		.flags	= GENL_CMD_CAP_DUMP,
>+	},
>+	{
>+		.cmd		= ULP_DDP_CMD_STATS,
>+		.pre_doit	= ulp_ddp_get_netdev,
>+		.doit		= ulp_ddp_nl_stats_doit,
>+		.post_doit	= ulp_ddp_put_netdev,
>+		.policy		= ulp_ddp_stats_nl_policy,
>+		.maxattr	= ULP_DDP_A_STAT_IFINDEX,
>+		.flags		= GENL_CMD_CAP_DO,
>+	},
>+	{
>+		.cmd	= ULP_DDP_CMD_STATS,
>+		.dumpit	= ulp_ddp_nl_stats_dumpit,
>+		.flags	= GENL_CMD_CAP_DUMP,
>+	},
>+	{
>+		.cmd		= ULP_DDP_CMD_SET,
>+		.pre_doit	= ulp_ddp_get_netdev,
>+		.doit		= ulp_ddp_nl_set_doit,
>+		.post_doit	= ulp_ddp_put_netdev,
>+		.policy		= ulp_ddp_set_nl_policy,
>+		.maxattr	= ULP_DDP_A_DEV_WANTED_MASK,
>+		.flags		= GENL_CMD_CAP_DO,
>+	},
>+};
>+
>+static const struct genl_multicast_group ulp_ddp_nl_mcgrps[] = {
>+	[ULP_DDP_NLGRP_MGMT] = { "mgmt", },
>+};
>+
>+struct genl_family ulp_ddp_nl_family __ro_after_init = {
>+	.name		= ULP_DDP_FAMILY_NAME,
>+	.version	= ULP_DDP_FAMILY_VERSION,
>+	.netnsok	= true,
>+	.parallel_ops	= true,
>+	.module		= THIS_MODULE,
>+	.split_ops	= ulp_ddp_nl_ops,
>+	.n_split_ops	= ARRAY_SIZE(ulp_ddp_nl_ops),
>+	.mcgrps		= ulp_ddp_nl_mcgrps,
>+	.n_mcgrps	= ARRAY_SIZE(ulp_ddp_nl_mcgrps),
>+};
>diff --git a/net/core/ulp_ddp_gen_nl.h b/net/core/ulp_ddp_gen_nl.h
>new file mode 100644
>index 000000000000..277fb9dbfdcd
>--- /dev/null
>+++ b/net/core/ulp_ddp_gen_nl.h
>@@ -0,0 +1,32 @@
>+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
>+/* Do not edit directly, auto-generated from: */
>+/*	Documentation/netlink/specs/ulp_ddp.yaml */
>+/* YNL-GEN kernel header */
>+
>+#ifndef _LINUX_ULP_DDP_GEN_H
>+#define _LINUX_ULP_DDP_GEN_H
>+
>+#include <net/netlink.h>
>+#include <net/genetlink.h>
>+
>+#include <uapi/linux/ulp_ddp_nl.h>
>+
>+int ulp_ddp_get_netdev(const struct genl_split_ops *ops, struct sk_buff *skb,
>+		       struct genl_info *info);
>+void
>+ulp_ddp_put_netdev(const struct genl_split_ops *ops, struct sk_buff *skb,
>+		   struct genl_info *info);
>+
>+int ulp_ddp_nl_get_doit(struct sk_buff *skb, struct genl_info *info);
>+int ulp_ddp_nl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
>+int ulp_ddp_nl_stats_doit(struct sk_buff *skb, struct genl_info *info);
>+int ulp_ddp_nl_stats_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
>+int ulp_ddp_nl_set_doit(struct sk_buff *skb, struct genl_info *info);
>+
>+enum {
>+	ULP_DDP_NLGRP_MGMT,
>+};
>+
>+extern struct genl_family ulp_ddp_nl_family;
>+
>+#endif /* _LINUX_ULP_DDP_GEN_H */
>diff --git a/net/core/ulp_ddp_nl.c b/net/core/ulp_ddp_nl.c
>new file mode 100644
>index 000000000000..55e5c51b6d88
>--- /dev/null
>+++ b/net/core/ulp_ddp_nl.c
>@@ -0,0 +1,388 @@
>+// SPDX-License-Identifier: GPL-2.0
>+/*
>+ * ulp_ddp.c
>+ *    Author: Aurelien Aptel <aaptel@nvidia.com>
>+ *    Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES.  All rights reserved.
>+ */
>+#include <net/ulp_ddp.h>
>+#include "ulp_ddp_gen_nl.h"
>+
>+#define ULP_DDP_STATS_CNT (sizeof(struct netlink_ulp_ddp_stats) / sizeof(u64))
>+
>+struct reply_data {

Some sane prefix for structs and fuctions would be nice. That applies to
the whole code.

What's "data"? Reading the code, this sounds very vague. Try to be more
precise in struct and functions naming.



>+	struct net_device *dev;
>+	netdevice_tracker tracker;
>+	void *hdr;
>+	u32 ifindex;
>+	DECLARE_BITMAP(hw, ULP_DDP_C_COUNT);
>+	DECLARE_BITMAP(active, ULP_DDP_C_COUNT);
>+	struct netlink_ulp_ddp_stats stats;
>+};
>+
>+static size_t reply_size(int cmd)
>+{
>+	size_t len = 0;
>+
>+	BUILD_BUG_ON(ULP_DDP_C_COUNT > 64);
>+
>+	/* ifindex */
>+	len += nla_total_size(sizeof(u32));
>+
>+	switch (cmd) {
>+	case ULP_DDP_CMD_GET:
>+	case ULP_DDP_CMD_SET:
>+	case ULP_DDP_CMD_SET_NTF:
>+		/* hw */
>+		len += nla_total_size_64bit(sizeof(u64));
>+
>+		/* active */
>+		len += nla_total_size_64bit(sizeof(u64));
>+		break;
>+	case ULP_DDP_CMD_STATS:
>+		/* stats */
>+		len += nla_total_size_64bit(sizeof(u64)) * ULP_DDP_STATS_CNT;
>+		break;
>+	}
>+
>+	return len;
>+}
>+
>+/* pre_doit */
>+int ulp_ddp_get_netdev(const struct genl_split_ops *ops,
>+		       struct sk_buff *skb, struct genl_info *info)

Could you perhaps check ulp_ddp_caps here instead of doing it on multiple
places over and over. Of course fill-up a proper extack message in case
the check fails.

Note that after this change you need to check the ops where you iterate
over netdevs (in dumps) too.


>+{
>+	struct reply_data *data;
>+
>+	if (GENL_REQ_ATTR_CHECK(info, ULP_DDP_A_DEV_IFINDEX))
>+		return -EINVAL;
>+
>+	data = kzalloc(sizeof(*data), GFP_KERNEL);
>+	if (!data)
>+		return -ENOMEM;
>+
>+	data->ifindex = nla_get_u32(info->attrs[ULP_DDP_A_DEV_IFINDEX]);
>+	data->dev = netdev_get_by_index(genl_info_net(info),
>+					data->ifindex,
>+					&data->tracker,
>+					GFP_KERNEL);
>+	if (!data->dev) {
>+		kfree(data);
>+		NL_SET_BAD_ATTR(info->extack,
>+				info->attrs[ULP_DDP_A_DEV_IFINDEX]);

Fill-up a meaningful extack message as well please using
NL_SET_ERR_MSG()


>+		return -ENOENT;

		return -ENODEV;
Maybe?


>+	}
>+
>+	info->user_ptr[0] = data;
>+	return 0;
>+}
>+
>+/* post_doit */
>+void ulp_ddp_put_netdev(const struct genl_split_ops *ops, struct sk_buff *skb,
>+			struct genl_info *info)
>+{
>+	struct reply_data *data = info->user_ptr[0];
>+
>+	if (data) {

How "data" could be NULL here?

>+		if (data->dev)

How "data->dev" could be NULL here?


>+			netdev_put(data->dev, &data->tracker);
>+		kfree(data);
>+	}
>+}
>+
>+static int prepare_data(struct reply_data *data, int cmd)
>+{
>+	const struct ulp_ddp_dev_ops *ops = data->dev->netdev_ops->ulp_ddp_ops;
>+	struct ulp_ddp_netdev_caps *caps = &data->dev->ulp_ddp_caps;
>+
>+	if (!ops)
>+		return -EOPNOTSUPP;
>+
>+	switch (cmd) {
>+	case ULP_DDP_CMD_GET:
>+	case ULP_DDP_CMD_SET:
>+	case ULP_DDP_CMD_SET_NTF:
>+		bitmap_copy(data->hw, caps->hw, ULP_DDP_C_COUNT);
>+		bitmap_copy(data->active, caps->active, ULP_DDP_C_COUNT);
>+		break;
>+	case ULP_DDP_CMD_STATS:
>+		ops->get_stats(data->dev, &data->stats);
>+		break;
>+	}
>+
>+	return 0;
>+}
>+
>+static int fill_data(struct sk_buff *rsp, struct reply_data *data, int cmd,
>+		     const struct genl_info *info)
>+{
>+	u64 *val = (u64 *)&data->stats;

Ugh.


>+	int attr, i;
>+
>+	data->hdr = genlmsg_iput(rsp, info);
>+	if (!data->hdr)
>+		return -EMSGSIZE;
>+
>+	switch (cmd) {
>+	case ULP_DDP_CMD_GET:
>+	case ULP_DDP_CMD_SET:
>+	case ULP_DDP_CMD_SET_NTF:
>+		if (nla_put_u32(rsp, ULP_DDP_A_DEV_IFINDEX, data->ifindex) ||
>+		    nla_put_u64_64bit(rsp, ULP_DDP_A_DEV_HW,
>+				      data->hw[0], ULP_DDP_A_DEV_PAD) ||
>+		    nla_put_u64_64bit(rsp, ULP_DDP_A_DEV_ACTIVE,
>+				      data->active[0], ULP_DDP_A_DEV_PAD))
>+			goto err_cancel_msg;
>+		break;
>+	case ULP_DDP_CMD_STATS:
>+		if (nla_put_u32(rsp, ULP_DDP_A_STAT_IFINDEX, data->ifindex))
>+			goto err_cancel_msg;
>+
>+		attr = ULP_DDP_A_STAT_PAD + 1;

Ugh again.


>+		for (i = 0; i < ULP_DDP_STATS_CNT; i++, attr++)
>+			if (nla_put_u64_64bit(rsp, attr, val[i],

This rely on a struct layout is dangerous and may easily lead in future
to put attrs which are not define in enum. Please properly put each stat
by using attr enum value.


>+					      ULP_DDP_A_STAT_PAD))
>+				goto err_cancel_msg;
>+	}
>+	genlmsg_end(rsp, data->hdr);
>+
>+	return 0;
>+
>+err_cancel_msg:
>+	genlmsg_cancel(rsp, data->hdr);
>+
>+	return -EMSGSIZE;
>+}
>+
>+int ulp_ddp_nl_get_doit(struct sk_buff *req, struct genl_info *info)
>+{
>+	struct reply_data *data = info->user_ptr[0];
>+	struct sk_buff *rsp;
>+	int ret = 0;
>+
>+	ret = prepare_data(data, ULP_DDP_CMD_GET);
>+	if (ret)
>+		return ret;
>+
>+	rsp = genlmsg_new(reply_size(ULP_DDP_CMD_GET), GFP_KERNEL);
>+	if (!rsp)
>+		return -EMSGSIZE;
>+
>+	ret = fill_data(rsp, data, ULP_DDP_CMD_GET, info);
>+	if (ret < 0)
>+		goto err_rsp;
>+
>+	return genlmsg_reply(rsp, info);
>+
>+err_rsp:
>+	nlmsg_free(rsp);
>+	return ret;
>+}
>+
>+static void ulp_ddp_nl_notify_dev(struct reply_data *data)
>+{
>+	struct genl_info info;
>+	struct sk_buff *ntf;
>+
>+	if (!genl_has_listeners(&ulp_ddp_nl_family, dev_net(data->dev),
>+				ULP_DDP_NLGRP_MGMT))
>+		return;
>+
>+	genl_info_init_ntf(&info, &ulp_ddp_nl_family, ULP_DDP_CMD_SET_NTF);
>+	ntf = genlmsg_new(reply_size(ULP_DDP_CMD_GET), GFP_KERNEL);
>+	if (!ntf)
>+		return;
>+
>+	if (fill_data(ntf, data, ULP_DDP_CMD_SET_NTF, &info)) {
>+		nlmsg_free(ntf);
>+		return;
>+	}
>+
>+	genlmsg_multicast_netns(&ulp_ddp_nl_family, dev_net(data->dev), ntf,
>+				0, ULP_DDP_NLGRP_MGMT, GFP_KERNEL);
>+}
>+
>+static int apply_bits(struct reply_data *data,
>+		      unsigned long *req_wanted,
>+		      unsigned long *req_mask,
>+		      struct netlink_ext_ack *extack)
>+{
>+	DECLARE_BITMAP(old_active, ULP_DDP_C_COUNT);
>+	DECLARE_BITMAP(new_active, ULP_DDP_C_COUNT);
>+	DECLARE_BITMAP(all_bits, ULP_DDP_C_COUNT);
>+	DECLARE_BITMAP(tmp, ULP_DDP_C_COUNT);
>+	const struct ulp_ddp_dev_ops *ops;
>+	struct ulp_ddp_netdev_caps *caps;
>+	int ret;
>+
>+	caps = &data->dev->ulp_ddp_caps;
>+	ops = data->dev->netdev_ops->ulp_ddp_ops;
>+
>+	if (!ops)
>+		return -EOPNOTSUPP;
>+
>+	/* if (req_mask & ~all_bits) */
>+	bitmap_fill(all_bits, ULP_DDP_C_COUNT);
>+	bitmap_andnot(tmp, req_mask, all_bits, ULP_DDP_C_COUNT);
>+	if (!bitmap_empty(tmp, ULP_DDP_C_COUNT))

Please make sure you always fillup a proper extack message, always.


>+		return -EINVAL;
>+
>+	/* new_active = (old_active & ~req_mask) | (wanted & req_mask)
>+	 * new_active &= caps_hw
>+	 */
>+	bitmap_copy(old_active, caps->active, ULP_DDP_C_COUNT);
>+	bitmap_and(req_wanted, req_wanted, req_mask, ULP_DDP_C_COUNT);
>+	bitmap_andnot(new_active, old_active, req_mask, ULP_DDP_C_COUNT);
>+	bitmap_or(new_active, new_active, req_wanted, ULP_DDP_C_COUNT);
>+	bitmap_and(new_active, new_active, caps->hw, ULP_DDP_C_COUNT);
>+	if (!bitmap_equal(old_active, new_active, ULP_DDP_C_COUNT)) {
>+		ret = ops->set_caps(data->dev, new_active, extack);
>+		if (ret < 0)
>+			return ret;
>+		bitmap_copy(new_active, caps->active, ULP_DDP_C_COUNT);
>+	}
>+
>+	/* return 1 to notify */
>+	return !bitmap_equal(old_active, new_active, ULP_DDP_C_COUNT);
>+}
>+
>+int ulp_ddp_nl_set_doit(struct sk_buff *skb, struct genl_info *info)
>+{
>+	struct reply_data *data = info->user_ptr[0];
>+	unsigned long wanted, wanted_mask;
>+	struct sk_buff *rsp;
>+	bool notify;
>+	int ret;
>+
>+	if (GENL_REQ_ATTR_CHECK(info, ULP_DDP_A_DEV_WANTED) ||
>+	    GENL_REQ_ATTR_CHECK(info, ULP_DDP_A_DEV_WANTED_MASK))
>+		return -EINVAL;
>+
>+	rsp = genlmsg_new(reply_size(ULP_DDP_CMD_STATS), GFP_KERNEL);
>+	if (!rsp)
>+		return -EMSGSIZE;
>+
>+	wanted = nla_get_u64(info->attrs[ULP_DDP_A_DEV_WANTED]);
>+	wanted_mask = nla_get_u64(info->attrs[ULP_DDP_A_DEV_WANTED_MASK]);
>+
>+	ret = apply_bits(data, &wanted, &wanted_mask, info->extack);
>+	if (ret < 0)
>+		goto err_rsp;
>+
>+	notify = !!ret;
>+	ret = prepare_data(data, ULP_DDP_CMD_SET);

Why you send it back for set? (leaving notify aside)


>+	if (ret)
>+		goto err_rsp;
>+
>+	ret = fill_data(rsp, data, ULP_DDP_CMD_SET, info);
>+	if (ret < 0)
>+		goto err_rsp;
>+
>+	ret = genlmsg_reply(rsp, info);
>+	if (notify)
>+		ulp_ddp_nl_notify_dev(data);
>+
>+	return ret;
>+
>+err_rsp:
>+	nlmsg_free(rsp);
>+
>+	return ret;
>+}
>+
>+int ulp_ddp_nl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
>+{
>+	struct net *net = sock_net(skb->sk);
>+	struct net_device *netdev;
>+	struct reply_data data;
>+	int err = 0;
>+
>+	rtnl_lock();
>+	for_each_netdev_dump(net, netdev, cb->args[0]) {

Is this function necessary? I mean, do you have usecase to dump the
the caps for all devices in the netns? If no, remove this dump op.
If yes, could you please do it without holding rtnl? You don't need rtnl
for do ops either.


>+		memset(&data, 0, sizeof(data));
>+		data.dev = netdev;
>+		data.ifindex = netdev->ifindex;
>+
>+		err = prepare_data(&data, ULP_DDP_CMD_GET);
>+		if (err)
>+			continue;
>+
>+		err = fill_data(skb, &data, ULP_DDP_CMD_GET,
>+				genl_info_dump(cb));
>+		if (err < 0)
>+			break;
>+	}
>+	rtnl_unlock();
>+
>+	if (err != -EMSGSIZE)

Doing dump into a single skb means you don't care about scale. That's
wrong.


>+		return err;
>+
>+	return skb->len;
>+}
>+
>+int ulp_ddp_nl_stats_doit(struct sk_buff *skb, struct genl_info *info)
>+{
>+	struct reply_data *data = info->user_ptr[0];
>+	struct sk_buff *rsp;
>+	int ret = 0;
>+
>+	ret = prepare_data(data, ULP_DDP_CMD_STATS);
>+	if (ret)
>+		return ret;
>+
>+	rsp = genlmsg_new(reply_size(ULP_DDP_CMD_STATS), GFP_KERNEL);
>+	if (!rsp)
>+		return -EMSGSIZE;
>+
>+	ret = fill_data(rsp, data, ULP_DDP_CMD_STATS, info);
>+	if (ret < 0)
>+		goto err_rsp;
>+
>+	return genlmsg_reply(rsp, info);
>+
>+err_rsp:
>+	nlmsg_free(rsp);
>+	return ret;
>+}
>+
>+int ulp_ddp_nl_stats_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
>+{
>+	struct net *net = sock_net(skb->sk);
>+	struct net_device *netdev;
>+	struct reply_data data;
>+	int err = 0;
>+
>+	rtnl_lock();
>+	for_each_netdev_dump(net, netdev, cb->args[0]) {

Is this function necessary? I mean, do you have usecase to dump the
stats for all devices in the netns? If no, remove this dump op.
If yes, could you please do it without holding rtnl? You don't need rtnl
for do ops either.


>+		memset(&data, 0, sizeof(data));
>+		data.dev = netdev;
>+		data.ifindex = netdev->ifindex;
>+
>+		err = prepare_data(&data, ULP_DDP_CMD_STATS);
>+		if (err)
>+			continue;
>+
>+		err = fill_data(skb, &data, ULP_DDP_CMD_STATS,
>+				genl_info_dump(cb));
>+		if (err < 0)
>+			break;
>+	}
>+	rtnl_unlock();
>+
>+	if (err != -EMSGSIZE)

Doing dump into a single skb means you don't care about scale. That's
wrong.


>+		return err;
>+
>+	return skb->len;
>+}
>+
>+static int __init ulp_ddp_init(void)
>+{
>+	int err;
>+
>+	err = genl_register_family(&ulp_ddp_nl_family);
>+	if (err)
>+		return err;
>+
>+	return 0;

The whole function reduces just to:
	return genl_register_family(&ulp_ddp_nl_family);


>+}
>+
>+subsys_initcall(ulp_ddp_init);
>-- 
>2.34.1
>
>
Jakub Kicinski Oct. 24, 2023, 2:59 p.m. UTC | #2
On Tue, 24 Oct 2023 15:58:22 +0200 Jiri Pirko wrote:
> >+definitions:
> >+  -
> >+    type: enum
> >+    name: cap
> >+    entries:
> >+      - nvme-tcp
> >+      - nvme-tcp-ddgst-rx
> >+
> >+uapi-header: linux/ulp_ddp_nl.h  
> 
> Not needed.
> Hmm, Jakub, why this is not only allowed in genetlink-legacy?

Agreed that it's not needed here.
There's a non-trivial chance of name collisions between new families 
and existing headers under include/uapi/linux. Since this is a C/C++
-only artifact and "modern" languages will simply ignore it allowing it
seemed like the right choice.

> >+      -
> >+        name: pad
> >+        type: pad
> >+      -
> >+        name: rx-nvmeotcp-sk-add
> >+        doc: Sockets successfully configured for NVMeTCP offloading.
> >+        type: u64

Everything you have as u64 should now be uint, and pads can then go
away.
Aurelien Aptel Oct. 27, 2023, 9:11 a.m. UTC | #3
Hi Jiri,

Jiri Pirko <jiri@resnulli.us> writes:
>>+definitions:
>>+  -
>>+    type: enum
>>+    name: cap
>>+    entries:
>>+      - nvme-tcp
>>+      - nvme-tcp-ddgst-rx
>>+
>>+uapi-header: linux/ulp_ddp_nl.h
>
> Not needed.
> Hmm, Jakub, why this is not only allowed in genetlink-legacy?

Ok, we will remove it and use the default location.

>>+
>>+attribute-sets:
>>+  -
>>+    name: stat
>
> "stats"?

Sure.

>>+    attributes:
>>+      -
>>+        name: ifindex
>>+        doc: interface index of the net device.
>>+        type: u32
>>+      -
>>+        name: pad
>>+        type: pad
>>+      -
>>+        name: rx-nvmeotcp-sk-add
>>+        doc: Sockets successfully configured for NVMeTCP offloading.
>>+        type: u64
>>+      -
>>+        name: rx-nvmeotcp-sk-add-fail
>
> "nvmeotcp" should stand for "nvme over tcp"? Why not to name it just
> "rx-nvem-tcp-x"?

Ok, we will rename them.

>>+  -
>>+    name: dev
>
> If this is attribute set for "caps-get"/"caps-set" only, why it is not
> called "caps"?

Ok, we will rename it.

>>+    attributes:
>>+      -
>>+        name: ifindex
>>+        doc: interface index of the net device.
>>+        type: u32
>>+      -
>>+        name: hw
>>+        doc: bitmask of the capabilities supported by the device.
>>+        type: u64
>>+        enum: cap
>>+        enum-as-flags: true
>>+      -
>>+        name: active
>>+        doc: bitmask of the capabilities currently enabled on the device.
>>+        type: u64
>>+        enum: cap
>>+        enum-as-flags: true
>>+      -
>>+        name: wanted
>
> For all caps related attrs, either put "caps" into the name or do that
> and put it in a caps nest

We will rename the attribute-set to "caps" and leave hw, active, wanted as-is.

>>+operations:
>>+  list:
>>+    -
>>+      name: get
>>+      doc: Get ULP DDP capabilities.
>
> This is for capabalities only, nothing else?
> If yes, why not to name the op "caps-get"/"caps-set"?
> If not and this is related to "dev", name it perhaps "dev-get"?
> I mean, you should be able to align the op name and attribute set name.

Yes it is for capabilities. We will rename the operations to caps-get/caps-set.

>>+      attribute-set: dev
>>+      do:
>>+        request:
>>+          attributes:
>>+            - ifindex
>>+        reply: &dev-all
>>+          attributes:
>>+            - ifindex
>>+            - hw
>>+            - active
>>+        pre: ulp_ddp_get_netdev
>>+        post: ulp_ddp_put_netdev
>>+      dump:
>>+        reply: *dev-all
>>+    -
>>+      name: stats
>
> "stats-get" ?

Ok

>>+++ b/net/core/ulp_ddp_nl.c
>>@@ -0,0 +1,388 @@
>>+// SPDX-License-Identifier: GPL-2.0
>>+/*
>>+ * ulp_ddp.c
>>+ *    Author: Aurelien Aptel <aaptel@nvidia.com>
>>+ *    Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES.  All rights reserved.
>>+ */
>>+#include <net/ulp_ddp.h>
>>+#include "ulp_ddp_gen_nl.h"
>>+
>>+#define ULP_DDP_STATS_CNT (sizeof(struct netlink_ulp_ddp_stats) / sizeof(u64))
>>+
>>+struct reply_data {
>
> Some sane prefix for structs and fuctions would be nice. That applies to
> the whole code.

Sure, we will add ulp_ddp prefix to all symbols. 

> What's "data"? Reading the code, this sounds very vague. Try to be more
> precise in struct and functions naming.

It's the data used to write the response message.
We will rename it to ulp_ddp_reply_context.

>>+
>>+/* pre_doit */
>>+int ulp_ddp_get_netdev(const struct genl_split_ops *ops,
>>+                     struct sk_buff *skb, struct genl_info *info)
>
> Could you perhaps check ulp_ddp_caps here instead of doing it on multiple
> places over and over. Of course fill-up a proper extack message in case
> the check fails.

Sounds good.

>>+      if (!data->dev) {
>>+              kfree(data);
>>+              NL_SET_BAD_ATTR(info->extack,
>>+                              info->attrs[ULP_DDP_A_DEV_IFINDEX]);
>
> Fill-up a meaningful extack message as well please using
> NL_SET_ERR_MSG()

Ok

>>+              return -ENOENT;
>
>                 return -ENODEV;
> Maybe?

Ok

>>+      struct reply_data *data = info->user_ptr[0];
>>+
>>+      if (data) {
>
> How "data" could be NULL here?
>
>>+              if (data->dev)
>
> How "data->dev" could be NULL here?

It can't, we will remove all the checks.

>>+              for (i = 0; i < ULP_DDP_STATS_CNT; i++, attr++)
>>+                      if (nla_put_u64_64bit(rsp, attr, val[i],
>
> This rely on a struct layout is dangerous and may easily lead in future
> to put attrs which are not define in enum. Please properly put each stat
> by using attr enum value.

Sure, we will explicitely set all stats attributes.

>>+
>>+      /* if (req_mask & ~all_bits) */
>>+      bitmap_fill(all_bits, ULP_DDP_C_COUNT);
>>+      bitmap_andnot(tmp, req_mask, all_bits, ULP_DDP_C_COUNT);
>>+      if (!bitmap_empty(tmp, ULP_DDP_C_COUNT))
>
> Please make sure you always fillup a proper extack message, always.

Noted. This check was redundant so we removed it (bits beyond
ULP_DDP_C_COUNT are not checked).

>>+      notify = !!ret;
>>+      ret = prepare_data(data, ULP_DDP_CMD_SET);
>
> Why you send it back for set? (leaving notify aside)

We send back the final caps so userspace can see and compare what was
requested vs the result of what the driver could enable.
This is following the convention of ethtool features.

>>+int ulp_ddp_nl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
>>+{
>>+      struct net *net = sock_net(skb->sk);
>>+      struct net_device *netdev;
>>+      struct reply_data data;
>>+      int err = 0;
>>+
>>+      rtnl_lock();
>>+      for_each_netdev_dump(net, netdev, cb->args[0]) {
>
> Is this function necessary? I mean, do you have usecase to dump the
> the caps for all devices in the netns? If no, remove this dump op.
> If yes, could you please do it without holding rtnl? You don't need rtnl
> for do ops either.

This is not necessary, we will remove the dump ops.

>>+static int __init ulp_ddp_init(void)
>>+{
>>+      int err;
>>+
>>+      err = genl_register_family(&ulp_ddp_nl_family);
>>+      if (err)
>>+              return err;
>>+
>>+      return 0;
>
> The whole function reduces just to:
>         return genl_register_family(&ulp_ddp_nl_family);

Ok

Thanks
Jiri Pirko Oct. 27, 2023, 9:28 a.m. UTC | #4
Fri, Oct 27, 2023 at 11:11:19AM CEST, aaptel@nvidia.com wrote:

[..]

>
>>>+      notify = !!ret;
>>>+      ret = prepare_data(data, ULP_DDP_CMD_SET);
>>
>> Why you send it back for set? (leaving notify aside)
>
>We send back the final caps so userspace can see and compare what was
>requested vs the result of what the driver could enable.
>This is following the convention of ethtool features.

Okay.

[..]
diff mbox series

Patch

diff --git a/Documentation/netlink/specs/ulp_ddp.yaml b/Documentation/netlink/specs/ulp_ddp.yaml
new file mode 100644
index 000000000000..882aa4e52992
--- /dev/null
+++ b/Documentation/netlink/specs/ulp_ddp.yaml
@@ -0,0 +1,183 @@ 
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+#
+# Author: Aurelien Aptel <aaptel@nvidia.com>
+#
+# Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES.  All rights reserved.
+#
+
+name: ulp_ddp
+
+protocol: genetlink
+
+doc: Netlink protocol to manage ULP DPP on network devices.
+
+definitions:
+  -
+    type: enum
+    name: cap
+    entries:
+      - nvme-tcp
+      - nvme-tcp-ddgst-rx
+
+uapi-header: linux/ulp_ddp_nl.h
+
+attribute-sets:
+  -
+    name: stat
+    attributes:
+      -
+        name: ifindex
+        doc: interface index of the net device.
+        type: u32
+      -
+        name: pad
+        type: pad
+      -
+        name: rx-nvmeotcp-sk-add
+        doc: Sockets successfully configured for NVMeTCP offloading.
+        type: u64
+      -
+        name: rx-nvmeotcp-sk-add-fail
+        doc: Sockets failed to be configured for NVMeTCP offloading.
+        type: u64
+      -
+        name: rx-nvmeotcp-sk-del
+        doc: Sockets with NVMeTCP offloading configuration removed.
+        type: u64
+      -
+        name: rx-nvmeotcp-setup
+        doc: NVMe-TCP IOs successfully configured for Rx Direct Data Placement.
+        type: u64
+      -
+        name: rx-nvmeotcp-setup-fail
+        doc: NVMe-TCP IOs failed to be configured for Rx Direct Data Placement.
+        type: u64
+      -
+        name: rx-nvmeotcp-teardown
+        doc: NVMe-TCP IOs with Rx Direct Data Placement configuration removed.
+        type: u64
+      -
+        name: rx-nvmeotcp-drop
+        doc: Packets failed the NVMeTCP offload validation.
+        type: u64
+      -
+        name: rx-nvmeotcp-resync
+        doc: >
+          NVMe-TCP resync operations were processed due to Rx TCP packets
+          re-ordering.
+        type: u64
+      -
+        name: rx-nvmeotcp-packets
+        doc: TCP packets successfully processed by the NVMeTCP offload.
+        type: u64
+      -
+        name: rx-nvmeotcp-bytes
+        doc: Bytes were successfully processed by the NVMeTCP offload.
+        type: u64
+  -
+    name: dev
+    attributes:
+      -
+        name: ifindex
+        doc: interface index of the net device.
+        type: u32
+      -
+        name: hw
+        doc: bitmask of the capabilities supported by the device.
+        type: u64
+        enum: cap
+        enum-as-flags: true
+      -
+        name: active
+        doc: bitmask of the capabilities currently enabled on the device.
+        type: u64
+        enum: cap
+        enum-as-flags: true
+      -
+        name: wanted
+        doc: >
+          new active bit values of the capabilities we want to set on the
+          device.
+        type: u64
+        enum: cap
+        enum-as-flags: true
+      -
+        name: wanted_mask
+        doc: bitmask of the meaningful bits in the wanted field.
+        type: u64
+        enum: cap
+        enum-as-flags: true
+      -
+        name: pad
+        type: pad
+
+operations:
+  list:
+    -
+      name: get
+      doc: Get ULP DDP capabilities.
+      attribute-set: dev
+      do:
+        request:
+          attributes:
+            - ifindex
+        reply: &dev-all
+          attributes:
+            - ifindex
+            - hw
+            - active
+        pre: ulp_ddp_get_netdev
+        post: ulp_ddp_put_netdev
+      dump:
+        reply: *dev-all
+    -
+      name: stats
+      doc: Get ULP DDP stats.
+      attribute-set: stat
+      do:
+        request:
+          attributes:
+            - ifindex
+        reply: &stats-all
+          attributes:
+            - ifindex
+            - rx-nvmeotcp-sk-add
+            - rx-nvmeotcp-sk-add-fail
+            - rx-nvmeotcp-sk-del
+            - rx-nvmeotcp-setup
+            - rx-nvmeotcp-setup-fail
+            - rx-nvmeotcp-teardown
+            - rx-nvmeotcp-drop
+            - rx-nvmeotcp-resync
+            - rx-nvmeotcp-packets
+            - rx-nvmeotcp-bytes
+        pre: ulp_ddp_get_netdev
+        post: ulp_ddp_put_netdev
+      dump:
+        reply: *stats-all
+    -
+      name: set
+      doc: Set ULP DDP capabilities.
+      attribute-set: dev
+      do:
+        request:
+          attributes:
+            - ifindex
+            - wanted
+            - wanted_mask
+        reply:
+          attributes:
+            - ifindex
+            - hw
+            - active
+        pre: ulp_ddp_get_netdev
+        post: ulp_ddp_put_netdev
+    -
+      name: set-ntf
+      doc: Notification for change in ULP DDP capabilities.
+      notify: get
+
+mcast-groups:
+  list:
+    -
+      name: mgmt
diff --git a/include/uapi/linux/ulp_ddp_nl.h b/include/uapi/linux/ulp_ddp_nl.h
new file mode 100644
index 000000000000..fc63749c9251
--- /dev/null
+++ b/include/uapi/linux/ulp_ddp_nl.h
@@ -0,0 +1,59 @@ 
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/*	Documentation/netlink/specs/ulp_ddp.yaml */
+/* YNL-GEN uapi header */
+
+#ifndef _UAPI_LINUX_ULP_DDP_H
+#define _UAPI_LINUX_ULP_DDP_H
+
+#define ULP_DDP_FAMILY_NAME	"ulp_ddp"
+#define ULP_DDP_FAMILY_VERSION	1
+
+enum ulp_ddp_cap {
+	ULP_DDP_CAP_NVME_TCP,
+	ULP_DDP_CAP_NVME_TCP_DDGST_RX,
+};
+
+enum {
+	ULP_DDP_A_STAT_IFINDEX = 1,
+	ULP_DDP_A_STAT_PAD,
+	ULP_DDP_A_STAT_RX_NVMEOTCP_SK_ADD,
+	ULP_DDP_A_STAT_RX_NVMEOTCP_SK_ADD_FAIL,
+	ULP_DDP_A_STAT_RX_NVMEOTCP_SK_DEL,
+	ULP_DDP_A_STAT_RX_NVMEOTCP_SETUP,
+	ULP_DDP_A_STAT_RX_NVMEOTCP_SETUP_FAIL,
+	ULP_DDP_A_STAT_RX_NVMEOTCP_TEARDOWN,
+	ULP_DDP_A_STAT_RX_NVMEOTCP_DROP,
+	ULP_DDP_A_STAT_RX_NVMEOTCP_RESYNC,
+	ULP_DDP_A_STAT_RX_NVMEOTCP_PACKETS,
+	ULP_DDP_A_STAT_RX_NVMEOTCP_BYTES,
+
+	__ULP_DDP_A_STAT_MAX,
+	ULP_DDP_A_STAT_MAX = (__ULP_DDP_A_STAT_MAX - 1)
+};
+
+enum {
+	ULP_DDP_A_DEV_IFINDEX = 1,
+	ULP_DDP_A_DEV_HW,
+	ULP_DDP_A_DEV_ACTIVE,
+	ULP_DDP_A_DEV_WANTED,
+	ULP_DDP_A_DEV_WANTED_MASK,
+	ULP_DDP_A_DEV_PAD,
+
+	__ULP_DDP_A_DEV_MAX,
+	ULP_DDP_A_DEV_MAX = (__ULP_DDP_A_DEV_MAX - 1)
+};
+
+enum {
+	ULP_DDP_CMD_GET = 1,
+	ULP_DDP_CMD_STATS,
+	ULP_DDP_CMD_SET,
+	ULP_DDP_CMD_SET_NTF,
+
+	__ULP_DDP_CMD_MAX,
+	ULP_DDP_CMD_MAX = (__ULP_DDP_CMD_MAX - 1)
+};
+
+#define ULP_DDP_MCGRP_MGMT	"mgmt"
+
+#endif /* _UAPI_LINUX_ULP_DDP_H */
diff --git a/net/core/Makefile b/net/core/Makefile
index b6a16e7c955a..1aff91f0fce0 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -18,7 +18,7 @@  obj-y		     += dev.o dev_addr_lists.o dst.o netevent.o \
 obj-$(CONFIG_NETDEV_ADDR_LIST_TEST) += dev_addr_lists_test.o
 
 obj-y += net-sysfs.o
-obj-$(CONFIG_ULP_DDP) += ulp_ddp.o
+obj-$(CONFIG_ULP_DDP) += ulp_ddp.o ulp_ddp_nl.o ulp_ddp_gen_nl.o
 obj-$(CONFIG_PAGE_POOL) += page_pool.o
 obj-$(CONFIG_PROC_FS) += net-procfs.o
 obj-$(CONFIG_NET_PKTGEN) += pktgen.o
diff --git a/net/core/ulp_ddp_gen_nl.c b/net/core/ulp_ddp_gen_nl.c
new file mode 100644
index 000000000000..505bdc69b215
--- /dev/null
+++ b/net/core/ulp_ddp_gen_nl.c
@@ -0,0 +1,85 @@ 
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/*	Documentation/netlink/specs/ulp_ddp.yaml */
+/* YNL-GEN kernel source */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "ulp_ddp_gen_nl.h"
+
+#include <uapi/linux/ulp_ddp_nl.h>
+
+/* ULP_DDP_CMD_GET - do */
+static const struct nla_policy ulp_ddp_get_nl_policy[ULP_DDP_A_DEV_IFINDEX + 1] = {
+	[ULP_DDP_A_DEV_IFINDEX] = { .type = NLA_U32, },
+};
+
+/* ULP_DDP_CMD_STATS - do */
+static const struct nla_policy ulp_ddp_stats_nl_policy[ULP_DDP_A_STAT_IFINDEX + 1] = {
+	[ULP_DDP_A_STAT_IFINDEX] = { .type = NLA_U32, },
+};
+
+/* ULP_DDP_CMD_SET - do */
+static const struct nla_policy ulp_ddp_set_nl_policy[ULP_DDP_A_DEV_WANTED_MASK + 1] = {
+	[ULP_DDP_A_DEV_IFINDEX] = { .type = NLA_U32, },
+	[ULP_DDP_A_DEV_WANTED] = NLA_POLICY_MASK(NLA_U64, 0x3),
+	[ULP_DDP_A_DEV_WANTED_MASK] = NLA_POLICY_MASK(NLA_U64, 0x3),
+};
+
+/* Ops table for ulp_ddp */
+static const struct genl_split_ops ulp_ddp_nl_ops[] = {
+	{
+		.cmd		= ULP_DDP_CMD_GET,
+		.pre_doit	= ulp_ddp_get_netdev,
+		.doit		= ulp_ddp_nl_get_doit,
+		.post_doit	= ulp_ddp_put_netdev,
+		.policy		= ulp_ddp_get_nl_policy,
+		.maxattr	= ULP_DDP_A_DEV_IFINDEX,
+		.flags		= GENL_CMD_CAP_DO,
+	},
+	{
+		.cmd	= ULP_DDP_CMD_GET,
+		.dumpit	= ulp_ddp_nl_get_dumpit,
+		.flags	= GENL_CMD_CAP_DUMP,
+	},
+	{
+		.cmd		= ULP_DDP_CMD_STATS,
+		.pre_doit	= ulp_ddp_get_netdev,
+		.doit		= ulp_ddp_nl_stats_doit,
+		.post_doit	= ulp_ddp_put_netdev,
+		.policy		= ulp_ddp_stats_nl_policy,
+		.maxattr	= ULP_DDP_A_STAT_IFINDEX,
+		.flags		= GENL_CMD_CAP_DO,
+	},
+	{
+		.cmd	= ULP_DDP_CMD_STATS,
+		.dumpit	= ulp_ddp_nl_stats_dumpit,
+		.flags	= GENL_CMD_CAP_DUMP,
+	},
+	{
+		.cmd		= ULP_DDP_CMD_SET,
+		.pre_doit	= ulp_ddp_get_netdev,
+		.doit		= ulp_ddp_nl_set_doit,
+		.post_doit	= ulp_ddp_put_netdev,
+		.policy		= ulp_ddp_set_nl_policy,
+		.maxattr	= ULP_DDP_A_DEV_WANTED_MASK,
+		.flags		= GENL_CMD_CAP_DO,
+	},
+};
+
+static const struct genl_multicast_group ulp_ddp_nl_mcgrps[] = {
+	[ULP_DDP_NLGRP_MGMT] = { "mgmt", },
+};
+
+struct genl_family ulp_ddp_nl_family __ro_after_init = {
+	.name		= ULP_DDP_FAMILY_NAME,
+	.version	= ULP_DDP_FAMILY_VERSION,
+	.netnsok	= true,
+	.parallel_ops	= true,
+	.module		= THIS_MODULE,
+	.split_ops	= ulp_ddp_nl_ops,
+	.n_split_ops	= ARRAY_SIZE(ulp_ddp_nl_ops),
+	.mcgrps		= ulp_ddp_nl_mcgrps,
+	.n_mcgrps	= ARRAY_SIZE(ulp_ddp_nl_mcgrps),
+};
diff --git a/net/core/ulp_ddp_gen_nl.h b/net/core/ulp_ddp_gen_nl.h
new file mode 100644
index 000000000000..277fb9dbfdcd
--- /dev/null
+++ b/net/core/ulp_ddp_gen_nl.h
@@ -0,0 +1,32 @@ 
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/*	Documentation/netlink/specs/ulp_ddp.yaml */
+/* YNL-GEN kernel header */
+
+#ifndef _LINUX_ULP_DDP_GEN_H
+#define _LINUX_ULP_DDP_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/ulp_ddp_nl.h>
+
+int ulp_ddp_get_netdev(const struct genl_split_ops *ops, struct sk_buff *skb,
+		       struct genl_info *info);
+void
+ulp_ddp_put_netdev(const struct genl_split_ops *ops, struct sk_buff *skb,
+		   struct genl_info *info);
+
+int ulp_ddp_nl_get_doit(struct sk_buff *skb, struct genl_info *info);
+int ulp_ddp_nl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
+int ulp_ddp_nl_stats_doit(struct sk_buff *skb, struct genl_info *info);
+int ulp_ddp_nl_stats_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
+int ulp_ddp_nl_set_doit(struct sk_buff *skb, struct genl_info *info);
+
+enum {
+	ULP_DDP_NLGRP_MGMT,
+};
+
+extern struct genl_family ulp_ddp_nl_family;
+
+#endif /* _LINUX_ULP_DDP_GEN_H */
diff --git a/net/core/ulp_ddp_nl.c b/net/core/ulp_ddp_nl.c
new file mode 100644
index 000000000000..55e5c51b6d88
--- /dev/null
+++ b/net/core/ulp_ddp_nl.c
@@ -0,0 +1,388 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ulp_ddp.c
+ *    Author: Aurelien Aptel <aaptel@nvidia.com>
+ *    Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES.  All rights reserved.
+ */
+#include <net/ulp_ddp.h>
+#include "ulp_ddp_gen_nl.h"
+
+#define ULP_DDP_STATS_CNT (sizeof(struct netlink_ulp_ddp_stats) / sizeof(u64))
+
+struct reply_data {
+	struct net_device *dev;
+	netdevice_tracker tracker;
+	void *hdr;
+	u32 ifindex;
+	DECLARE_BITMAP(hw, ULP_DDP_C_COUNT);
+	DECLARE_BITMAP(active, ULP_DDP_C_COUNT);
+	struct netlink_ulp_ddp_stats stats;
+};
+
+static size_t reply_size(int cmd)
+{
+	size_t len = 0;
+
+	BUILD_BUG_ON(ULP_DDP_C_COUNT > 64);
+
+	/* ifindex */
+	len += nla_total_size(sizeof(u32));
+
+	switch (cmd) {
+	case ULP_DDP_CMD_GET:
+	case ULP_DDP_CMD_SET:
+	case ULP_DDP_CMD_SET_NTF:
+		/* hw */
+		len += nla_total_size_64bit(sizeof(u64));
+
+		/* active */
+		len += nla_total_size_64bit(sizeof(u64));
+		break;
+	case ULP_DDP_CMD_STATS:
+		/* stats */
+		len += nla_total_size_64bit(sizeof(u64)) * ULP_DDP_STATS_CNT;
+		break;
+	}
+
+	return len;
+}
+
+/* pre_doit */
+int ulp_ddp_get_netdev(const struct genl_split_ops *ops,
+		       struct sk_buff *skb, struct genl_info *info)
+{
+	struct reply_data *data;
+
+	if (GENL_REQ_ATTR_CHECK(info, ULP_DDP_A_DEV_IFINDEX))
+		return -EINVAL;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->ifindex = nla_get_u32(info->attrs[ULP_DDP_A_DEV_IFINDEX]);
+	data->dev = netdev_get_by_index(genl_info_net(info),
+					data->ifindex,
+					&data->tracker,
+					GFP_KERNEL);
+	if (!data->dev) {
+		kfree(data);
+		NL_SET_BAD_ATTR(info->extack,
+				info->attrs[ULP_DDP_A_DEV_IFINDEX]);
+		return -ENOENT;
+	}
+
+	info->user_ptr[0] = data;
+	return 0;
+}
+
+/* post_doit */
+void ulp_ddp_put_netdev(const struct genl_split_ops *ops, struct sk_buff *skb,
+			struct genl_info *info)
+{
+	struct reply_data *data = info->user_ptr[0];
+
+	if (data) {
+		if (data->dev)
+			netdev_put(data->dev, &data->tracker);
+		kfree(data);
+	}
+}
+
+static int prepare_data(struct reply_data *data, int cmd)
+{
+	const struct ulp_ddp_dev_ops *ops = data->dev->netdev_ops->ulp_ddp_ops;
+	struct ulp_ddp_netdev_caps *caps = &data->dev->ulp_ddp_caps;
+
+	if (!ops)
+		return -EOPNOTSUPP;
+
+	switch (cmd) {
+	case ULP_DDP_CMD_GET:
+	case ULP_DDP_CMD_SET:
+	case ULP_DDP_CMD_SET_NTF:
+		bitmap_copy(data->hw, caps->hw, ULP_DDP_C_COUNT);
+		bitmap_copy(data->active, caps->active, ULP_DDP_C_COUNT);
+		break;
+	case ULP_DDP_CMD_STATS:
+		ops->get_stats(data->dev, &data->stats);
+		break;
+	}
+
+	return 0;
+}
+
+static int fill_data(struct sk_buff *rsp, struct reply_data *data, int cmd,
+		     const struct genl_info *info)
+{
+	u64 *val = (u64 *)&data->stats;
+	int attr, i;
+
+	data->hdr = genlmsg_iput(rsp, info);
+	if (!data->hdr)
+		return -EMSGSIZE;
+
+	switch (cmd) {
+	case ULP_DDP_CMD_GET:
+	case ULP_DDP_CMD_SET:
+	case ULP_DDP_CMD_SET_NTF:
+		if (nla_put_u32(rsp, ULP_DDP_A_DEV_IFINDEX, data->ifindex) ||
+		    nla_put_u64_64bit(rsp, ULP_DDP_A_DEV_HW,
+				      data->hw[0], ULP_DDP_A_DEV_PAD) ||
+		    nla_put_u64_64bit(rsp, ULP_DDP_A_DEV_ACTIVE,
+				      data->active[0], ULP_DDP_A_DEV_PAD))
+			goto err_cancel_msg;
+		break;
+	case ULP_DDP_CMD_STATS:
+		if (nla_put_u32(rsp, ULP_DDP_A_STAT_IFINDEX, data->ifindex))
+			goto err_cancel_msg;
+
+		attr = ULP_DDP_A_STAT_PAD + 1;
+		for (i = 0; i < ULP_DDP_STATS_CNT; i++, attr++)
+			if (nla_put_u64_64bit(rsp, attr, val[i],
+					      ULP_DDP_A_STAT_PAD))
+				goto err_cancel_msg;
+	}
+	genlmsg_end(rsp, data->hdr);
+
+	return 0;
+
+err_cancel_msg:
+	genlmsg_cancel(rsp, data->hdr);
+
+	return -EMSGSIZE;
+}
+
+int ulp_ddp_nl_get_doit(struct sk_buff *req, struct genl_info *info)
+{
+	struct reply_data *data = info->user_ptr[0];
+	struct sk_buff *rsp;
+	int ret = 0;
+
+	ret = prepare_data(data, ULP_DDP_CMD_GET);
+	if (ret)
+		return ret;
+
+	rsp = genlmsg_new(reply_size(ULP_DDP_CMD_GET), GFP_KERNEL);
+	if (!rsp)
+		return -EMSGSIZE;
+
+	ret = fill_data(rsp, data, ULP_DDP_CMD_GET, info);
+	if (ret < 0)
+		goto err_rsp;
+
+	return genlmsg_reply(rsp, info);
+
+err_rsp:
+	nlmsg_free(rsp);
+	return ret;
+}
+
+static void ulp_ddp_nl_notify_dev(struct reply_data *data)
+{
+	struct genl_info info;
+	struct sk_buff *ntf;
+
+	if (!genl_has_listeners(&ulp_ddp_nl_family, dev_net(data->dev),
+				ULP_DDP_NLGRP_MGMT))
+		return;
+
+	genl_info_init_ntf(&info, &ulp_ddp_nl_family, ULP_DDP_CMD_SET_NTF);
+	ntf = genlmsg_new(reply_size(ULP_DDP_CMD_GET), GFP_KERNEL);
+	if (!ntf)
+		return;
+
+	if (fill_data(ntf, data, ULP_DDP_CMD_SET_NTF, &info)) {
+		nlmsg_free(ntf);
+		return;
+	}
+
+	genlmsg_multicast_netns(&ulp_ddp_nl_family, dev_net(data->dev), ntf,
+				0, ULP_DDP_NLGRP_MGMT, GFP_KERNEL);
+}
+
+static int apply_bits(struct reply_data *data,
+		      unsigned long *req_wanted,
+		      unsigned long *req_mask,
+		      struct netlink_ext_ack *extack)
+{
+	DECLARE_BITMAP(old_active, ULP_DDP_C_COUNT);
+	DECLARE_BITMAP(new_active, ULP_DDP_C_COUNT);
+	DECLARE_BITMAP(all_bits, ULP_DDP_C_COUNT);
+	DECLARE_BITMAP(tmp, ULP_DDP_C_COUNT);
+	const struct ulp_ddp_dev_ops *ops;
+	struct ulp_ddp_netdev_caps *caps;
+	int ret;
+
+	caps = &data->dev->ulp_ddp_caps;
+	ops = data->dev->netdev_ops->ulp_ddp_ops;
+
+	if (!ops)
+		return -EOPNOTSUPP;
+
+	/* if (req_mask & ~all_bits) */
+	bitmap_fill(all_bits, ULP_DDP_C_COUNT);
+	bitmap_andnot(tmp, req_mask, all_bits, ULP_DDP_C_COUNT);
+	if (!bitmap_empty(tmp, ULP_DDP_C_COUNT))
+		return -EINVAL;
+
+	/* new_active = (old_active & ~req_mask) | (wanted & req_mask)
+	 * new_active &= caps_hw
+	 */
+	bitmap_copy(old_active, caps->active, ULP_DDP_C_COUNT);
+	bitmap_and(req_wanted, req_wanted, req_mask, ULP_DDP_C_COUNT);
+	bitmap_andnot(new_active, old_active, req_mask, ULP_DDP_C_COUNT);
+	bitmap_or(new_active, new_active, req_wanted, ULP_DDP_C_COUNT);
+	bitmap_and(new_active, new_active, caps->hw, ULP_DDP_C_COUNT);
+	if (!bitmap_equal(old_active, new_active, ULP_DDP_C_COUNT)) {
+		ret = ops->set_caps(data->dev, new_active, extack);
+		if (ret < 0)
+			return ret;
+		bitmap_copy(new_active, caps->active, ULP_DDP_C_COUNT);
+	}
+
+	/* return 1 to notify */
+	return !bitmap_equal(old_active, new_active, ULP_DDP_C_COUNT);
+}
+
+int ulp_ddp_nl_set_doit(struct sk_buff *skb, struct genl_info *info)
+{
+	struct reply_data *data = info->user_ptr[0];
+	unsigned long wanted, wanted_mask;
+	struct sk_buff *rsp;
+	bool notify;
+	int ret;
+
+	if (GENL_REQ_ATTR_CHECK(info, ULP_DDP_A_DEV_WANTED) ||
+	    GENL_REQ_ATTR_CHECK(info, ULP_DDP_A_DEV_WANTED_MASK))
+		return -EINVAL;
+
+	rsp = genlmsg_new(reply_size(ULP_DDP_CMD_STATS), GFP_KERNEL);
+	if (!rsp)
+		return -EMSGSIZE;
+
+	wanted = nla_get_u64(info->attrs[ULP_DDP_A_DEV_WANTED]);
+	wanted_mask = nla_get_u64(info->attrs[ULP_DDP_A_DEV_WANTED_MASK]);
+
+	ret = apply_bits(data, &wanted, &wanted_mask, info->extack);
+	if (ret < 0)
+		goto err_rsp;
+
+	notify = !!ret;
+	ret = prepare_data(data, ULP_DDP_CMD_SET);
+	if (ret)
+		goto err_rsp;
+
+	ret = fill_data(rsp, data, ULP_DDP_CMD_SET, info);
+	if (ret < 0)
+		goto err_rsp;
+
+	ret = genlmsg_reply(rsp, info);
+	if (notify)
+		ulp_ddp_nl_notify_dev(data);
+
+	return ret;
+
+err_rsp:
+	nlmsg_free(rsp);
+
+	return ret;
+}
+
+int ulp_ddp_nl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	struct net *net = sock_net(skb->sk);
+	struct net_device *netdev;
+	struct reply_data data;
+	int err = 0;
+
+	rtnl_lock();
+	for_each_netdev_dump(net, netdev, cb->args[0]) {
+		memset(&data, 0, sizeof(data));
+		data.dev = netdev;
+		data.ifindex = netdev->ifindex;
+
+		err = prepare_data(&data, ULP_DDP_CMD_GET);
+		if (err)
+			continue;
+
+		err = fill_data(skb, &data, ULP_DDP_CMD_GET,
+				genl_info_dump(cb));
+		if (err < 0)
+			break;
+	}
+	rtnl_unlock();
+
+	if (err != -EMSGSIZE)
+		return err;
+
+	return skb->len;
+}
+
+int ulp_ddp_nl_stats_doit(struct sk_buff *skb, struct genl_info *info)
+{
+	struct reply_data *data = info->user_ptr[0];
+	struct sk_buff *rsp;
+	int ret = 0;
+
+	ret = prepare_data(data, ULP_DDP_CMD_STATS);
+	if (ret)
+		return ret;
+
+	rsp = genlmsg_new(reply_size(ULP_DDP_CMD_STATS), GFP_KERNEL);
+	if (!rsp)
+		return -EMSGSIZE;
+
+	ret = fill_data(rsp, data, ULP_DDP_CMD_STATS, info);
+	if (ret < 0)
+		goto err_rsp;
+
+	return genlmsg_reply(rsp, info);
+
+err_rsp:
+	nlmsg_free(rsp);
+	return ret;
+}
+
+int ulp_ddp_nl_stats_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	struct net *net = sock_net(skb->sk);
+	struct net_device *netdev;
+	struct reply_data data;
+	int err = 0;
+
+	rtnl_lock();
+	for_each_netdev_dump(net, netdev, cb->args[0]) {
+		memset(&data, 0, sizeof(data));
+		data.dev = netdev;
+		data.ifindex = netdev->ifindex;
+
+		err = prepare_data(&data, ULP_DDP_CMD_STATS);
+		if (err)
+			continue;
+
+		err = fill_data(skb, &data, ULP_DDP_CMD_STATS,
+				genl_info_dump(cb));
+		if (err < 0)
+			break;
+	}
+	rtnl_unlock();
+
+	if (err != -EMSGSIZE)
+		return err;
+
+	return skb->len;
+}
+
+static int __init ulp_ddp_init(void)
+{
+	int err;
+
+	err = genl_register_family(&ulp_ddp_nl_family);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+subsys_initcall(ulp_ddp_init);