diff mbox series

[RFC,03/19] ktf: Introduce a generic netlink protocol for test result communication

Message ID a29b3d27234a7ad3f5d6f7571d08167077dc0350.1565676440.git-series.knut.omang@oracle.com (mailing list archive)
State New
Headers show
Series Integration of Kernel Test Framework (KTF) into the kernel tree | expand

Commit Message

Knut Omang Aug. 13, 2019, 6:09 a.m. UTC
The generic netlink protocol used to communicate between
kernel and user space about tests and test results, as well as some
means for configuring tests within the kernel.

Unlike other kernel side test code in the kernel, ktf does not print
anything from inside the kernel (except for optional debugging
features to help "internal" debugging of ktf or ktf tests).
Instead all test results are communicated back to the user space
frontend, which decides how to do the reporting.

ktf_nl.c:        ktf netlink protocol implementation
ktf_unlproto.h:  implements interfaces for user-kernel netlink interactions

Signed-off-by: Knut Omang <knut.omang@oracle.com>
---
 tools/testing/selftests/ktf/kernel/ktf_nl.c       | 516 +++++++++++++++-
 tools/testing/selftests/ktf/kernel/ktf_nl.h       |  15 +-
 tools/testing/selftests/ktf/kernel/ktf_unlproto.h | 105 +++-
 3 files changed, 636 insertions(+)
 create mode 100644 tools/testing/selftests/ktf/kernel/ktf_nl.c
 create mode 100644 tools/testing/selftests/ktf/kernel/ktf_nl.h
 create mode 100644 tools/testing/selftests/ktf/kernel/ktf_unlproto.h

Comments

Brendan Higgins Sept. 9, 2019, 1:28 a.m. UTC | #1
On Tue, Aug 13, 2019 at 08:09:18AM +0200, Knut Omang wrote:
> The generic netlink protocol used to communicate between
> kernel and user space about tests and test results, as well as some
> means for configuring tests within the kernel.
> 
> Unlike other kernel side test code in the kernel, ktf does not print
> anything from inside the kernel (except for optional debugging
> features to help "internal" debugging of ktf or ktf tests).
> Instead all test results are communicated back to the user space
> frontend, which decides how to do the reporting.

So why netlink? Why not just a file interface?

[...]

Cheers
Knut Omang Sept. 10, 2019, 6:30 a.m. UTC | #2
On Sun, 2019-09-08 at 18:28 -0700, Brendan Higgins wrote:
> On Tue, Aug 13, 2019 at 08:09:18AM +0200, Knut Omang wrote:
> > The generic netlink protocol used to communicate between
> > kernel and user space about tests and test results, as well as some
> > means for configuring tests within the kernel.
> > 
> > Unlike other kernel side test code in the kernel, ktf does not print
> > anything from inside the kernel (except for optional debugging
> > features to help "internal" debugging of ktf or ktf tests).
> > Instead all test results are communicated back to the user space
> > frontend, which decides how to do the reporting.
> 
> So why netlink? Why not just a file interface?

Netlink allows more flexibility in that it is bidirectional and asynchronous.
User space may query the kernel for available tests and then decide which tests
to invoke. User land test frameworks like Googletest allows use of wildcards and
exceptions to select particular tests to run. This is in my opinion very
important functionality as we want the tests to be valuable as developer tools,
not just to check the code as part of a later QA cycle. 
Being able to run a single test or a small subset of the tests is very useful.

Wrt test reporting, the kernel side just dispatches off messages about test
results as they are gathered. Compare this to the complexities, side effects and
limitations of printk.

Besides, for hybrid tests, bidirectional communication allows a test to contain
a mix (or a function) of results gathered in the kernel and in user space.

We also use it for network tests, where user space needs to tell the kernel 
what peer(s) to communicate with, and for certain minimal configuration, such as
which device instance to use for device testing. Test nodes may vary in what
they offer of hardware. Although we'd like to minimize the need for
configuration, as results should be easily reproducable, sometimes there is no
good way around.

Thanks,
Knut

> [...]
> 
> Cheers
diff mbox series

Patch

diff --git a/tools/testing/selftests/ktf/kernel/ktf_nl.c b/tools/testing/selftests/ktf/kernel/ktf_nl.c
new file mode 100644
index 0000000..56d5f3b
--- /dev/null
+++ b/tools/testing/selftests/ktf/kernel/ktf_nl.c
@@ -0,0 +1,516 @@ 
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ *    Author: Knut Omang <knut.omang@oracle.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * ktf_nl.c: ktf netlink protocol implementation
+ */
+#include <linux/version.h>
+#include <net/netlink.h>
+#include <net/genetlink.h>
+#define NL_INTERNAL 1
+#include "ktf_unlproto.h"
+#include "ktf_test.h"
+#include "ktf_nl.h"
+#include "ktf.h"
+#include "ktf_cov.h"
+
+/* Generic netlink support to communicate with user level
+ * test framework.
+ */
+
+/* Callback functions defined below */
+static int ktf_run(struct sk_buff *skb, struct genl_info *info);
+static int ktf_query(struct sk_buff *skb, struct genl_info *info);
+static int ktf_req(struct sk_buff *skb, struct genl_info *info);
+static int ktf_resp(struct sk_buff *skb, struct genl_info *info);
+static int ktf_cov_cmd(enum ktf_cmd_type type, struct sk_buff *skb,
+		       struct genl_info *info);
+static int ktf_ctx_cfg(struct sk_buff *skb, struct genl_info *info);
+static int send_version_only(struct sk_buff *skb, struct genl_info *info);
+
+/* operation definition */
+static struct genl_ops ktf_ops[] = {
+	{
+		.cmd = KTF_C_REQ,
+		.flags = 0,
+		.doit = ktf_req,
+		.dumpit = NULL,
+	},
+	{
+		.cmd = KTF_C_RESP,
+		.flags = 0,
+		.doit = ktf_resp,
+		.dumpit = NULL,
+	}
+};
+
+/* family definition */
+static struct genl_family ktf_gnl_family = {
+	.module = THIS_MODULE,
+	.hdrsize = 0,
+	.name = "ktf",
+	.version = 1,
+	.maxattr = KTF_A_MAX + 4,
+	.policy = ktf_gnl_policy,
+	.ops = ktf_ops,
+	.n_ops = ARRAY_SIZE(ktf_ops),
+};
+
+/* handler, returns 0 on success, negative
+ * values on failure. It doesn't make much difference
+ * what error values are used, as they are anyway discarded
+ * at the netlink level, but do result in a nonzero return
+ * from nl_wait_for_ack() in user space.
+ */
+static int ktf_req(struct sk_buff *skb, struct genl_info *info)
+{
+	enum ktf_cmd_type type;
+	u64 version;
+
+	/* Dispatch on type of request */
+
+	if (!info->attrs[KTF_A_TYPE] || !info->attrs[KTF_A_VERSION]) {
+		terr("received netlink msg with no type/version!");
+		return -EINVAL;
+	}
+
+	version = nla_get_u64(info->attrs[KTF_A_VERSION]);
+	if (ktf_version_check(version)) {
+		/* a query is the first call for any reasonable application:
+		 * Respond to it with a version only:
+		 */
+		if (nla_get_u32(info->attrs[KTF_A_TYPE]) == KTF_CT_QUERY)
+			return send_version_only(skb, info);
+		return -EINVAL;
+	}
+
+	type = nla_get_u32(info->attrs[KTF_A_TYPE]);
+	switch (type) {
+	case KTF_CT_QUERY:
+		return ktf_query(skb, info);
+	case KTF_CT_RUN:
+		return ktf_run(skb, info);
+	case KTF_CT_COV_ENABLE:
+	case KTF_CT_COV_DISABLE:
+		return ktf_cov_cmd(type, skb, info);
+	case KTF_CT_CTX_CFG:
+		return ktf_ctx_cfg(skb, info);
+	default:
+		terr("received netlink msg with invalid type (%d)", type);
+	}
+	return -EINVAL;
+}
+
+/* Reply with just version information to let user space report the issue: */
+static int send_version_only(struct sk_buff *skb, struct genl_info *info)
+{
+	struct sk_buff *resp_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	void *data;
+	int retval = 0;
+
+	if (!resp_skb)
+		return -ENOMEM;
+	data = genlmsg_put_reply(resp_skb, info, &ktf_gnl_family,
+				 0, KTF_C_RESP);
+	if (!data) {
+		retval = -ENOMEM;
+		goto resp_failure;
+	}
+	nla_put_u32(resp_skb, KTF_A_TYPE, KTF_CT_QUERY);
+	nla_put_u64_64bit(resp_skb, KTF_A_VERSION, KTF_VERSION_LATEST, 0);
+
+	/* Recompute message header */
+	genlmsg_end(resp_skb, data);
+
+	retval = genlmsg_reply(resp_skb, info);
+resp_failure:
+	/* Free buffer if failure */
+	if (retval)
+		nlmsg_free(resp_skb);
+	return retval;
+}
+
+/* Send data about one testcase */
+static int send_test_data(struct sk_buff *resp_skb, struct ktf_case *tc)
+{
+	struct nlattr *nest_attr;
+	struct ktf_test *t;
+	int stat;
+	int cnt = 0;
+
+	stat = nla_put_string(resp_skb, KTF_A_STR, ktf_case_name(tc));
+	if (stat)
+		return stat;
+
+	nest_attr = nla_nest_start(resp_skb, KTF_A_TEST);
+	ktf_testcase_for_each_test(t, tc) {
+		cnt++;
+		/* A test is not valid if the handle requires a context and none is present */
+		if (t->handle->id) {
+			stat = nla_put_u32(resp_skb, KTF_A_HID, t->handle->id);
+			if (stat)
+				goto fail;
+		} else if (t->handle->require_context) {
+			continue;
+		}
+		stat = nla_put_string(resp_skb, KTF_A_STR, t->name);
+		if (stat)
+			goto fail;
+	}
+	nla_nest_end(resp_skb, nest_attr);
+	tlog(T_DEBUG, "Sent data about %d tests", cnt);
+	return 0;
+fail:
+	twarn("Failed with status %d after sending data about %d tests", stat, cnt);
+	/* we hold reference to t here - drop it! */
+	ktf_test_put(t);
+	return stat;
+}
+
+static int send_handle_data(struct sk_buff *resp_skb, struct ktf_handle *handle)
+{
+	struct ktf_context_type *ct;
+	struct nlattr *nest_attr;
+	struct ktf_context *ctx;
+	int stat;
+
+	tlog(T_DEBUG, "Sending context handle %d: ", handle->id);
+
+	/* Send HID */
+	stat = nla_put_u32(resp_skb, KTF_A_HID, handle->id);
+	if (stat)
+		return stat;
+
+	/* Send contexts */
+	nest_attr = nla_nest_start(resp_skb, KTF_A_LIST);
+	if (!nest_attr)
+		return -ENOMEM;
+
+	tlog(T_DEBUG, "Sending context type list");
+	/* Send any context types that user space are allowed to create contexts for */
+	ktf_map_for_each_entry(ct, &handle->ctx_type_map, elem) {
+		if (ct->alloc) {
+			stat = nla_put_string(resp_skb, KTF_A_FILE, ct->name);
+			if (stat)
+				return -ENOMEM;
+		}
+	}
+
+	/* Then send all the contexts themselves */
+	ctx = ktf_find_first_context(handle);
+	while (ctx) {
+		nla_put_string(resp_skb, KTF_A_STR, ktf_context_name(ctx));
+		if (ctx->config_cb) {
+			stat = nla_put_string(resp_skb, KTF_A_MOD, ctx->type->name);
+			if (stat)
+				return stat;
+			stat = nla_put_u32(resp_skb, KTF_A_STAT, ctx->config_errno);
+			if (stat)
+				return stat;
+		}
+		ctx = ktf_find_next_context(ctx);
+	}
+	nla_nest_end(resp_skb, nest_attr);
+	return 0;
+}
+
+static int ktf_query(struct sk_buff *skb, struct genl_info *info)
+{
+	struct sk_buff *resp_skb;
+	void *data;
+	int retval = 0;
+	struct nlattr *nest_attr;
+	struct ktf_handle *handle;
+	struct ktf_case *tc;
+
+	/* No options yet, just send a response */
+	resp_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!resp_skb)
+		return -ENOMEM;
+
+	data = genlmsg_put_reply(resp_skb, info, &ktf_gnl_family,
+				 0, KTF_C_RESP);
+	if (!data) {
+		retval = -ENOMEM;
+		goto resp_failure;
+	}
+
+	nla_put_u64_64bit(resp_skb, KTF_A_VERSION, KTF_VERSION_LATEST, 0);
+
+	/* Add all test sets to the report
+	 *  We send test info as follows:
+	 *    KTF_CT_QUERY hid1 [context1 [context2 ...]] hid2 [context1 [context2 ...]]
+	 *                   testset_num [testset1 [name1 name2 ..] testset2 [name1 name2 ..]]
+	 *  Handle IDs without contexts are not present
+	 */
+	if (!nla_put_u32(resp_skb, KTF_A_TYPE, KTF_CT_QUERY)) {
+		if (!list_empty(&context_handles)) {
+			/* Traverse list of handles with contexts */
+			nest_attr = nla_nest_start(resp_skb, KTF_A_HLIST);
+			list_for_each_entry(handle, &context_handles, handle_list) {
+				retval = send_handle_data(resp_skb, handle);
+				if (retval)
+					goto resp_failure;
+			}
+			nla_nest_end(resp_skb, nest_attr);
+		}
+
+		/* Send total number of tests */
+		tlog(T_DEBUG, "Total #of test cases: %ld", ktf_case_count());
+		nla_put_u32(resp_skb, KTF_A_NUM, ktf_case_count());
+		nest_attr = nla_nest_start(resp_skb, KTF_A_LIST);
+		if (!nest_attr) {
+			retval = -ENOMEM;
+			goto resp_failure;
+		}
+		ktf_for_each_testcase(tc) {
+			retval = send_test_data(resp_skb, tc);
+			if (retval) {
+				retval = -ENOMEM;
+				goto resp_failure;
+			}
+		}
+		nla_nest_end(resp_skb, nest_attr);
+	}
+
+	/* Recompute message header */
+	genlmsg_end(resp_skb, data);
+
+	retval = genlmsg_reply(resp_skb, info);
+resp_failure:
+	if (retval)
+		twarn("Message failure (status %d)", retval);
+	/* Free buffer if failure */
+	if (retval)
+		nlmsg_free(resp_skb);
+	return retval;
+}
+
+static int ktf_run_func(struct sk_buff *skb, const char *ctxname,
+			const char *setname, const char *testname,
+			u32 value, void *oob_data, size_t oob_data_sz)
+{
+	struct ktf_case *testset = ktf_case_find(setname);
+	struct ktf_test *t;
+	int tn = 0;
+
+	if (!testset) {
+		tlog(T_INFO, "No such testset \"%s\"\n", setname);
+		return -EFAULT;
+	}
+
+	/* Execute test functions */
+	ktf_testcase_for_each_test(t, testset) {
+		if (t->fun && strcmp(t->name, testname) == 0) {
+			struct ktf_context *ctx = ktf_find_context(t->handle, ctxname);
+
+			ktf_run_hook(skb, ctx, t, value, oob_data, oob_data_sz);
+		} else if (!t->fun) {
+			tlog(T_DEBUG, "** no function for test %s.%s **", t->tclass, t->name);
+		}
+		tn++;
+	}
+	tlog(T_DEBUG, "Set %s contained %d tests", ktf_case_name(testset), tn);
+	ktf_case_put(testset);
+	return 0;
+}
+
+static int ktf_run(struct sk_buff *skb, struct genl_info *info)
+{
+	u32 value = 0;
+	struct sk_buff *resp_skb;
+	void *data;
+	int retval = 0;
+	struct nlattr *nest_attr, *data_attr;
+	char ctxname_store[KTF_MAX_NAME + 1];
+	char *ctxname = ctxname_store;
+	char setname[KTF_MAX_NAME + 1];
+	char testname[KTF_MAX_NAME + 1];
+	void *oob_data = NULL;
+	size_t oob_data_sz = 0;
+
+	if (info->attrs[KTF_A_STR])
+		nla_strlcpy(ctxname, info->attrs[KTF_A_STR], KTF_MAX_NAME);
+	else
+		ctxname = NULL;
+
+	if (!info->attrs[KTF_A_SNAM])	{
+		terr("received KTF_CT_RUN msg without testset name!");
+		return -EINVAL;
+	}
+	nla_strlcpy(setname, info->attrs[KTF_A_SNAM], KTF_MAX_NAME);
+
+	if (!info->attrs[KTF_A_TNAM])	{  /* Test name wo/context */
+		terr("received KTF_CT_RUN msg without test name!");
+		return -EINVAL;
+	}
+	nla_strlcpy(testname, info->attrs[KTF_A_TNAM], KTF_MAX_NAME);
+
+	if (info->attrs[KTF_A_NUM])	{
+		/* Using NUM field as optional u32 input parameter to test */
+		value = nla_get_u32(info->attrs[KTF_A_NUM]);
+	}
+
+	data_attr = info->attrs[KTF_A_DATA];
+	if (data_attr)	{
+		/* User space sends out-of-band data: */
+		oob_data = nla_memdup(data_attr, GFP_KERNEL);
+		oob_data_sz = nla_len(data_attr);
+	}
+
+	tlog(T_DEBUG, "Request for testset %s, test %s\n", setname, testname);
+
+	/* Start building a response */
+	resp_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!resp_skb)
+		return -ENOMEM;
+
+	data = genlmsg_put_reply(resp_skb, info, &ktf_gnl_family,
+				 0, KTF_C_RESP);
+	if (!data) {
+		retval = -ENOMEM;
+		goto put_fail;
+	}
+
+	nla_put_u32(resp_skb, KTF_A_TYPE, KTF_CT_RUN);
+	nest_attr = nla_nest_start(resp_skb, KTF_A_LIST);
+	retval = ktf_run_func(resp_skb, ctxname, setname, testname, value, oob_data, oob_data_sz);
+	nla_nest_end(resp_skb, nest_attr);
+	nla_put_u32(resp_skb, KTF_A_STAT, retval);
+
+	/* Recompute message header */
+	genlmsg_end(resp_skb, data);
+
+	retval = genlmsg_reply(resp_skb, info);
+	if (!retval)
+		tlog(T_DEBUG, "Sent reply for test %s.%s\n", setname, testname);
+	else
+		twarn("Failed to send reply for test %s.%s - value %d",
+		      setname, testname, retval);
+
+	kfree(oob_data);
+put_fail:
+	/* Free buffer if failure */
+	if (retval)
+		nlmsg_free(resp_skb);
+	return retval;
+}
+
+static int ktf_resp(struct sk_buff *skb, struct genl_info *info)
+{
+	/* not to expect this message here */
+	terr("unexpected netlink RESP msg received");
+	return 0;
+}
+
+static int ktf_cov_cmd(enum ktf_cmd_type type, struct sk_buff *skb,
+		       struct genl_info *info)
+{
+	char *cmd = type == KTF_CT_COV_ENABLE ? "COV_ENABLE" : "COV_DISABLE";
+	char module[KTF_MAX_NAME + 1];
+	struct sk_buff *resp_skb;
+	int retval = 0;
+	void *data;
+	u32 opts = 0;
+
+	if (!info->attrs[KTF_A_MOD])   {
+		terr("received KTF_CT_%s msg without module name!", cmd);
+		return -EINVAL;
+	}
+	nla_strlcpy(module, info->attrs[KTF_A_MOD], KTF_MAX_NAME);
+	if (info->attrs[KTF_A_COVOPT])
+		opts = nla_get_u32(info->attrs[KTF_A_COVOPT]);
+
+	/* Start building a response */
+	resp_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!resp_skb)
+		return -ENOMEM;
+
+	tlog(T_DEBUG, "%s coverage for %s\n", cmd, module);
+	if (type == KTF_CT_COV_ENABLE)
+		retval = ktf_cov_enable(module, opts);
+	else
+		ktf_cov_disable(module);
+
+	data = genlmsg_put_reply(resp_skb, info, &ktf_gnl_family,
+				 0, KTF_C_RESP);
+	if (!data) {
+		retval = -ENOMEM;
+		goto put_fail;
+	}
+	nla_put_u32(resp_skb, KTF_A_TYPE, type);
+	nla_put_u32(resp_skb, KTF_A_STAT, retval);
+	/* Recompute message header */
+	genlmsg_end(resp_skb, data);
+
+	retval = genlmsg_reply(resp_skb, info);
+	if (!retval)
+		tlog(T_DEBUG, "Sent reply for %s module %s\n",
+		     cmd, module);
+	else
+		twarn("Failed to send reply for %s module %s - value %d",
+		      cmd, module, retval);
+put_fail:
+	/* Free buffer if failure */
+	if (retval)
+		nlmsg_free(resp_skb);
+	return retval;
+}
+
+/* Process request to configure a configurable context:
+ * Expected format:  KTF_CT_CTX_CFG hid type_name context_name data
+ * placed in A_HID, A_FILE, A_STR and A_DATA respectively.
+ */
+static int ktf_ctx_cfg(struct sk_buff *skb, struct genl_info *info)
+{
+	char ctxname[KTF_MAX_NAME + 1];
+	char type_name[KTF_MAX_NAME + 1];
+	struct nlattr *data_attr;
+	void *ctx_data = NULL;
+	size_t ctx_data_sz = 0;
+	int hid;
+	struct ktf_handle *handle;
+	struct ktf_context *ctx;
+	int ret;
+
+	if (!info->attrs[KTF_A_STR] || !info->attrs[KTF_A_HID])
+		return -EINVAL;
+	data_attr = info->attrs[KTF_A_DATA];
+	if (!data_attr)
+		return -EINVAL;
+	hid = nla_get_u32(info->attrs[KTF_A_HID]);
+	handle = ktf_handle_find(hid);
+	if (!handle)
+		return -EINVAL;
+	if (info->attrs[KTF_A_FILE])
+		nla_strlcpy(type_name, info->attrs[KTF_A_FILE], KTF_MAX_NAME);
+	else
+		strcpy(type_name, "default");
+	nla_strlcpy(ctxname, info->attrs[KTF_A_STR], KTF_MAX_NAME);
+	tlog(T_DEBUG, "Trying to find/create context %s with type %s\n", ctxname, type_name);
+	ctx = ktf_find_create_context(handle, ctxname, type_name);
+	if (!ctx)
+		return -ENODEV;
+
+	tlog(T_DEBUG, "Received context configuration for context %s, handle %d\n",
+	     ctxname, hid);
+
+	ctx_data = nla_memdup(data_attr, GFP_KERNEL);
+	ctx_data_sz = nla_len(data_attr);
+	ret = ktf_context_set_config(ctx, ctx_data, ctx_data_sz);
+	kfree(ctx_data);
+	return ret;
+}
+
+int ktf_nl_register(void)
+{
+	int stat = genl_register_family(&ktf_gnl_family);
+	return stat;
+}
+
+void ktf_nl_unregister(void)
+{
+	genl_unregister_family(&ktf_gnl_family);
+}
diff --git a/tools/testing/selftests/ktf/kernel/ktf_nl.h b/tools/testing/selftests/ktf/kernel/ktf_nl.h
new file mode 100644
index 0000000..87d8012
--- /dev/null
+++ b/tools/testing/selftests/ktf/kernel/ktf_nl.h
@@ -0,0 +1,15 @@ 
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ *    Author: Knut Omang <knut.omang@oracle.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * nl.h: ktf netlink protocol interface
+ */
+#ifndef KTF_NL_H
+#define KTF_NL_H
+
+int ktf_nl_register(void);
+void ktf_nl_unregister(void);
+
+#endif
diff --git a/tools/testing/selftests/ktf/kernel/ktf_unlproto.h b/tools/testing/selftests/ktf/kernel/ktf_unlproto.h
new file mode 100644
index 0000000..e6d4525
--- /dev/null
+++ b/tools/testing/selftests/ktf/kernel/ktf_unlproto.h
@@ -0,0 +1,105 @@ 
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ *    Author: Knut Omang <knut.omang@oracle.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * ktf_unlproto.h: implements interfaces for user-kernel netlink interactions
+ *   for querying/running tests.
+ */
+#ifndef _KTF_UNLPROTO_H
+#define _KTF_UNLPROTO_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum ktf_cmd_type {
+	KTF_CT_UNSPEC,
+	KTF_CT_QUERY,
+	KTF_CT_RUN,
+	KTF_CT_COV_ENABLE,
+	KTF_CT_COV_DISABLE,
+	KTF_CT_CTX_CFG,
+	KTF_CT_MAX,
+};
+
+/* Netlink protocol definition shared between user and kernel space
+ * Include once per user app as it defines struct values!
+ */
+
+/* supported attributes */
+enum ktf_attr {
+	KTF_A_UNSPEC,
+	KTF_A_TYPE,
+	KTF_A_VERSION,
+	KTF_A_SNAM,   /* Test suite name */
+	KTF_A_TNAM,   /* Test name */
+	KTF_A_NUM,
+	KTF_A_STR,
+	KTF_A_FILE,
+	KTF_A_STAT,
+	KTF_A_LIST,
+	KTF_A_TEST,
+	KTF_A_HID,    /* Test handle ID */
+	KTF_A_HLIST,  /* List of handles repr. as a LIST of contexts for a given HID */
+	KTF_A_MOD,    /* module for coverage analysis, also used for context type */
+	KTF_A_COVOPT, /* options for coverage analysis */
+	KTF_A_DATA,   /* Binary data used by a.o. hybrid tests */
+	KTF_A_MAX
+};
+
+/* attribute policy */
+#ifdef NL_INTERNAL
+static struct nla_policy ktf_gnl_policy[KTF_A_MAX] = {
+	[KTF_A_TYPE]  = { .type = NLA_U32 },
+	[KTF_A_VERSION] = { .type = NLA_U64 },
+	[KTF_A_SNAM]  = { .type = NLA_STRING },
+	[KTF_A_TNAM]  = { .type = NLA_STRING },
+	[KTF_A_NUM]   = { .type = NLA_U32 },
+	[KTF_A_STAT]  = { .type = NLA_U32 },
+	[KTF_A_HID]   = { .type = NLA_U32 },
+	[KTF_A_LIST]  = { .type = NLA_NESTED },
+	[KTF_A_TEST]  = { .type = NLA_NESTED },
+	[KTF_A_HLIST] = { .type = NLA_NESTED },
+	[KTF_A_STR]   = { .type = NLA_STRING },
+	[KTF_A_FILE]  = { .type = NLA_STRING },
+	[KTF_A_MOD]   = { .type = NLA_STRING },
+	[KTF_A_COVOPT] = { .type = NLA_U32 },
+	[KTF_A_DATA] = { .type = NLA_BINARY },
+};
+#endif
+
+/* supported commands */
+enum ktf_cmd {
+	KTF_C_UNSPEC,
+	KTF_C_REQ,
+	KTF_C_RESP,
+	KTF_C_MAX
+};
+
+enum ktf_vshift {
+	KTF_VSHIFT_BUILD = 0,
+	KTF_VSHIFT_MICRO = 16,
+	KTF_VSHIFT_MINOR = 32,
+	KTF_VSHIFT_MAJOR = 48
+};
+
+#define KTF_VERSION(__field, __v) \
+	((__v & (0xffffULL << KTF_VSHIFT_##__field)) \
+	>> KTF_VSHIFT_##__field)
+
+#define KTF_VERSION_SET(__field, __v) \
+	((__v & 0xffffULL) << KTF_VSHIFT_##__field)
+
+#define	KTF_VERSION_LATEST	\
+	(KTF_VERSION_SET(MAJOR, 0ULL) | KTF_VERSION_SET(MINOR, 2ULL) | KTF_VERSION_SET(MICRO, 1ULL))
+
+/* Coverage options */
+#define	KTF_COV_OPT_MEM		0x1
+
+struct nla_policy *ktf_get_gnl_policy(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif