[2/6] app/testpmd: register driver specific commands

Message ID 20220523071031.1868862-3-david.marchand@redhat.com (mailing list archive)
State Superseded, archived
Delegated to: Andrew Rybchenko
Headers
Series Split driver specific commands out of testpmd |

Checks

Context Check Description
ci/checkpatch warning coding style issues

Commit Message

David Marchand May 23, 2022, 7:10 a.m. UTC
  Introduce a testpmd API so that drivers can register specific commands.

A driver can list some files to compile with testpmd, by setting them
in the testpmd_sources (driver local) meson variable.
drivers/meson.build then takes care of appending this to a global meson
variable, and adding the driver to testpmd dependency.

Note: testpmd.h is fixed to that it is self sufficient when being
included.

Signed-off-by: David Marchand <david.marchand@redhat.com>
---
Changes since RFC v2:
- fixed registering issue (again..),
- updated internals and names of structure so that a usage string is
  associated to each cmdline ctx,
- updated documentation,

Changes since RFC v1:
- fixed registering issue,

---
 app/test-pmd/cmdline.c                      | 85 +++++++++++++++++++--
 app/test-pmd/meson.build                    |  5 ++
 app/test-pmd/testpmd.c                      |  4 +
 app/test-pmd/testpmd.h                      | 23 ++++++
 doc/guides/testpmd_app_ug/testpmd_funcs.rst | 27 ++++---
 drivers/meson.build                         |  5 ++
 meson.build                                 |  2 +
 7 files changed, 134 insertions(+), 17 deletions(-)
  

Comments

Ferruh Yigit May 23, 2022, 6:10 p.m. UTC | #1
On 5/23/2022 8:10 AM, David Marchand wrote:
> +int
> +init_cmdline(void)
> +{
> +       struct testpmd_commands *c;
> +       cmdline_parse_ctx_t *ctx;
> +       unsigned int count = 0;
> +       unsigned int i;
> +
> +       /* initialize non-constant commands */
> +       cmd_set_fwd_mode_init();
> +       cmd_set_fwd_retry_mode_init();
> +
> +       main_ctx = NULL;
> +       for (i = 0; builtin_ctx[i] != NULL; i++) {
> +               ctx = realloc(main_ctx, (count + i + 1) * sizeof(*ctx));
> +               if (ctx == NULL)
> +                       goto err;
> +               main_ctx = ctx;

Instead of 'realloc()', check and assign pointer in each entry, what do 
you think about first calculate the size and alloc once, both for 
'builtin_ctx' & driver specific command?
But of course trade off is to loop twice in that case.

> +               main_ctx[count + i] = builtin_ctx[i];
> +       }

no need to use 'count' in this loop, since it is always '0'.

> +       count += i;
> +
> +       TAILQ_FOREACH(c, &commands_head, next) {
> +               for (i = 0; c->commands[i].ctx != NULL; i++) {
> +                       ctx = realloc(main_ctx, (count + i + 1) * sizeof(*ctx));
> +                       if (ctx == NULL)
> +                               goto err;
> +                       main_ctx = ctx;
> +                       main_ctx[count + i] = c->commands[i].ctx;
> +               }
> +               count += i;
> +       }
> +
> +       /* cmdline expects a NULL terminated array */
> +       ctx = realloc(main_ctx, (count + 1) * sizeof(*ctx));
> +       if (ctx == NULL)
> +               goto err;
> +       main_ctx = ctx;
> +       main_ctx[count] = NULL;
> +       count += 1;

Above block also can be avoided if size calculated in advance.

> +
> +       return 0;
> +err:
> +       free(main_ctx);
> +       return -1;
> +}
> +
  
David Marchand May 24, 2022, 10:53 a.m. UTC | #2
On Mon, May 23, 2022 at 8:10 PM Ferruh Yigit <ferruh.yigit@amd.com> wrote:
>
> On 5/23/2022 8:10 AM, David Marchand wrote:
> > +int
> > +init_cmdline(void)
> > +{
> > +       struct testpmd_commands *c;
> > +       cmdline_parse_ctx_t *ctx;
> > +       unsigned int count = 0;
> > +       unsigned int i;
> > +
> > +       /* initialize non-constant commands */
> > +       cmd_set_fwd_mode_init();
> > +       cmd_set_fwd_retry_mode_init();
> > +
> > +       main_ctx = NULL;
> > +       for (i = 0; builtin_ctx[i] != NULL; i++) {
> > +               ctx = realloc(main_ctx, (count + i + 1) * sizeof(*ctx));
> > +               if (ctx == NULL)
> > +                       goto err;
> > +               main_ctx = ctx;
>
> Instead of 'realloc()', check and assign pointer in each entry, what do
> you think about first calculate the size and alloc once, both for
> 'builtin_ctx' & driver specific command?
> But of course trade off is to loop twice in that case.

Yes, I hesitated because of the duplicated loop (and I found no
"elegant" macro).
I'm ok with the suggestion, this makes the code simpler.


int
init_cmdline(void)
{
        struct testpmd_commands *c;
        unsigned int count;
        unsigned int i;

        /* initialize non-constant commands */
        cmd_set_fwd_mode_init();
        cmd_set_fwd_retry_mode_init();

        count = 0;
        for (i = 0; builtin_ctx[i] != NULL; i++, count++)
                ;
        TAILQ_FOREACH(c, &commands_head, next) {
                for (i = 0; c->commands[i].ctx != NULL; i++, count++)
                        ;
        }

        /* cmdline expects a NULL terminated array */
        main_ctx = calloc(count + 1, sizeof(main_ctx[0]));
        if (main_ctx == NULL)
                return -1;

        count = 0;
        for (i = 0; builtin_ctx[i] != NULL; i++, count++)
                main_ctx[count] = builtin_ctx[i];
        TAILQ_FOREACH(c, &commands_head, next) {
                for (i = 0; c->commands[i].ctx != NULL; i++, count++)
                        main_ctx[count] = c->commands[i].ctx;
        }

        return 0;
}
  
Ferruh Yigit May 24, 2022, 11:43 a.m. UTC | #3
On 5/24/2022 11:53 AM, David Marchand wrote:
> [CAUTION: External Email]
> 
> On Mon, May 23, 2022 at 8:10 PM Ferruh Yigit <ferruh.yigit@amd.com> wrote:
>>
>> On 5/23/2022 8:10 AM, David Marchand wrote:
>>> +int
>>> +init_cmdline(void)
>>> +{
>>> +       struct testpmd_commands *c;
>>> +       cmdline_parse_ctx_t *ctx;
>>> +       unsigned int count = 0;
>>> +       unsigned int i;
>>> +
>>> +       /* initialize non-constant commands */
>>> +       cmd_set_fwd_mode_init();
>>> +       cmd_set_fwd_retry_mode_init();
>>> +
>>> +       main_ctx = NULL;
>>> +       for (i = 0; builtin_ctx[i] != NULL; i++) {
>>> +               ctx = realloc(main_ctx, (count + i + 1) * sizeof(*ctx));
>>> +               if (ctx == NULL)
>>> +                       goto err;
>>> +               main_ctx = ctx;
>>
>> Instead of 'realloc()', check and assign pointer in each entry, what do
>> you think about first calculate the size and alloc once, both for
>> 'builtin_ctx' & driver specific command?
>> But of course trade off is to loop twice in that case.
> 
> Yes, I hesitated because of the duplicated loop (and I found no
> "elegant" macro).
> I'm ok with the suggestion, this makes the code simpler.
> 
> 
> int
> init_cmdline(void)
> {
>          struct testpmd_commands *c;
>          unsigned int count;
>          unsigned int i;
> 
>          /* initialize non-constant commands */
>          cmd_set_fwd_mode_init();
>          cmd_set_fwd_retry_mode_init();
> 
>          count = 0;
>          for (i = 0; builtin_ctx[i] != NULL; i++, count++)
>                  ;
>          TAILQ_FOREACH(c, &commands_head, next) {
>                  for (i = 0; c->commands[i].ctx != NULL; i++, count++)
>                          ;
>          }
> 
>          /* cmdline expects a NULL terminated array */
>          main_ctx = calloc(count + 1, sizeof(main_ctx[0]));
>          if (main_ctx == NULL)
>                  return -1;
> 
>          count = 0;
>          for (i = 0; builtin_ctx[i] != NULL; i++, count++)
>                  main_ctx[count] = builtin_ctx[i];
>          TAILQ_FOREACH(c, &commands_head, next) {
>                  for (i = 0; c->commands[i].ctx != NULL; i++, count++)
>                          main_ctx[count] = c->commands[i].ctx;
>          }
> 
>          return 0;
> }
> 

ack
  
Thomas Monjalon May 24, 2022, 5:21 p.m. UTC | #4
23/05/2022 09:10, David Marchand:
> Introduce a testpmd API so that drivers can register specific commands.
> 
> A driver can list some files to compile with testpmd, by setting them
> in the testpmd_sources (driver local) meson variable.
> drivers/meson.build then takes care of appending this to a global meson
> variable, and adding the driver to testpmd dependency.
> 
> Note: testpmd.h is fixed to that it is self sufficient when being
> included.
> 
> Signed-off-by: David Marchand <david.marchand@redhat.com>
> ---
[...]
> +	main_ctx = NULL;
> +	for (i = 0; builtin_ctx[i] != NULL; i++) {
> +		ctx = realloc(main_ctx, (count + i + 1) * sizeof(*ctx));
> +		if (ctx == NULL)
> +			goto err;
> +		main_ctx = ctx;
> +		main_ctx[count + i] = builtin_ctx[i];
> +	}
> +	count += i;
> +
> +	TAILQ_FOREACH(c, &commands_head, next) {
> +		for (i = 0; c->commands[i].ctx != NULL; i++) {
> +			ctx = realloc(main_ctx, (count + i + 1) * sizeof(*ctx));
> +			if (ctx == NULL)
> +				goto err;
> +			main_ctx = ctx;
> +			main_ctx[count + i] = c->commands[i].ctx;
> +		}
> +		count += i;
> +	}
> +
> +	/* cmdline expects a NULL terminated array */
> +	ctx = realloc(main_ctx, (count + 1) * sizeof(*ctx));
> +	if (ctx == NULL)
> +		goto err;
> +	main_ctx = ctx;
> +	main_ctx[count] = NULL;
> +	count += 1;

As Ferruh already said, there's a lot of realloc here.

[...]
> +# Driver specific sources include some testpmd headers.

Suggested reword:
	Driver-specific commands are located in driver directories.

> +includes = include_directories('.')
> +sources += testpmd_drivers_sources
> +deps += testpmd_drivers_deps

[...]
> --- a/app/test-pmd/testpmd.h
> +++ b/app/test-pmd/testpmd.h
> +/* For registering testpmd commands. */
> +struct testpmd_commands {
> +	TAILQ_ENTRY(testpmd_commands) next;
> +	struct {
> +		cmdline_parse_inst_t *ctx;
> +		const char *help;
> +	} commands[];
> +};
> +
> +extern void testpmd_add_commands(struct testpmd_commands *c);

Why extern?

> +#define TESTPMD_ADD_DRIVER_COMMANDS(c) \

Why not TESTPMD_ADD_COMMANDS?

> +RTE_INIT(__##c) \
> +{ \
> +	testpmd_add_commands(&c); \
> +}
[...]
> --- a/drivers/meson.build
> +++ b/drivers/meson.build
> +        if testpmd_sources.length() != 0
> +            testpmd_drivers_sources += testpmd_sources
> +            testpmd_drivers_deps += dependency_name
> +        endif

Are you sure the check is required?
What happens if adding an empty string?

[...]
> --- a/meson.build
> +++ b/meson.build
> @@ -42,6 +42,8 @@ dpdk_drivers = []
>  dpdk_extra_ldflags = []
>  dpdk_libs_disabled = []
>  dpdk_drvs_disabled = []
> +testpmd_drivers_sources = []
> +testpmd_drivers_deps = []

It's polluting a bit the global meson namespace for testpmd
but I think it's worth, I like the idea.

I hope we can merge the infrastucture patches soon.
We can postpone a bit the move of existing drivers
because some are not so obvious.
At least we should make it mandatory now for all new driver-specific commands.

Thanks
  
David Marchand May 24, 2022, 5:44 p.m. UTC | #5
On Tue, May 24, 2022 at 7:21 PM Thomas Monjalon <thomas@monjalon.net> wrote:
>
> 23/05/2022 09:10, David Marchand:
> > Introduce a testpmd API so that drivers can register specific commands.
> >
> > A driver can list some files to compile with testpmd, by setting them
> > in the testpmd_sources (driver local) meson variable.
> > drivers/meson.build then takes care of appending this to a global meson
> > variable, and adding the driver to testpmd dependency.
> >
> > Note: testpmd.h is fixed to that it is self sufficient when being
> > included.
> >
> > Signed-off-by: David Marchand <david.marchand@redhat.com>
> > ---
> [...]
> > +     main_ctx = NULL;
> > +     for (i = 0; builtin_ctx[i] != NULL; i++) {
> > +             ctx = realloc(main_ctx, (count + i + 1) * sizeof(*ctx));
> > +             if (ctx == NULL)
> > +                     goto err;
> > +             main_ctx = ctx;
> > +             main_ctx[count + i] = builtin_ctx[i];
> > +     }
> > +     count += i;
> > +
> > +     TAILQ_FOREACH(c, &commands_head, next) {
> > +             for (i = 0; c->commands[i].ctx != NULL; i++) {
> > +                     ctx = realloc(main_ctx, (count + i + 1) * sizeof(*ctx));
> > +                     if (ctx == NULL)
> > +                             goto err;
> > +                     main_ctx = ctx;
> > +                     main_ctx[count + i] = c->commands[i].ctx;
> > +             }
> > +             count += i;
> > +     }
> > +
> > +     /* cmdline expects a NULL terminated array */
> > +     ctx = realloc(main_ctx, (count + 1) * sizeof(*ctx));
> > +     if (ctx == NULL)
> > +             goto err;
> > +     main_ctx = ctx;
> > +     main_ctx[count] = NULL;
> > +     count += 1;
>
> As Ferruh already said, there's a lot of realloc here.

Yes, it will be fixed in next revision.


>
> [...]
> > +# Driver specific sources include some testpmd headers.
>
> Suggested reword:
>         Driver-specific commands are located in driver directories.

At the moment, it's only about testpmd commands.
Maybe in the future we can have other specific code in those driver sources.
That's why I preferred a generic term.


>
> > +includes = include_directories('.')
> > +sources += testpmd_drivers_sources
> > +deps += testpmd_drivers_deps
>
> [...]
> > --- a/app/test-pmd/testpmd.h
> > +++ b/app/test-pmd/testpmd.h
> > +/* For registering testpmd commands. */
> > +struct testpmd_commands {
> > +     TAILQ_ENTRY(testpmd_commands) next;
> > +     struct {
> > +             cmdline_parse_inst_t *ctx;
> > +             const char *help;
> > +     } commands[];
> > +};
> > +
> > +extern void testpmd_add_commands(struct testpmd_commands *c);
>
> Why extern?

It can probably be removed.


>
> > +#define TESTPMD_ADD_DRIVER_COMMANDS(c) \
>
> Why not TESTPMD_ADD_COMMANDS?

I forgot to align both the function and macro.
Please note though that for now, the registered commands through this
API get displayed in the "Driver specific" usage section.
So maybe the API should state it is about driver specific commands.

WDYT?


>
> > +RTE_INIT(__##c) \
> > +{ \
> > +     testpmd_add_commands(&c); \
> > +}
> [...]
> > --- a/drivers/meson.build
> > +++ b/drivers/meson.build
> > +        if testpmd_sources.length() != 0
> > +            testpmd_drivers_sources += testpmd_sources
> > +            testpmd_drivers_deps += dependency_name
> > +        endif
>
> Are you sure the check is required?
> What happens if adding an empty string?

We don't want to push a driver dependency to testpmd if there is nothing to add.


>
> [...]
> > --- a/meson.build
> > +++ b/meson.build
> > @@ -42,6 +42,8 @@ dpdk_drivers = []
> >  dpdk_extra_ldflags = []
> >  dpdk_libs_disabled = []
> >  dpdk_drvs_disabled = []
> > +testpmd_drivers_sources = []
> > +testpmd_drivers_deps = []
>
> It's polluting a bit the global meson namespace for testpmd
> but I think it's worth, I like the idea.
>
> I hope we can merge the infrastucture patches soon.
> We can postpone a bit the move of existing drivers
> because some are not so obvious.
> At least we should make it mandatory now for all new driver-specific commands.

I'm okay with the approach.
I'll prepare a new revision with only the API part, then and respin
the drivers updates later.
  
Thomas Monjalon May 24, 2022, 5:51 p.m. UTC | #6
24/05/2022 19:44, David Marchand:
> On Tue, May 24, 2022 at 7:21 PM Thomas Monjalon <thomas@monjalon.net> wrote:
> > 23/05/2022 09:10, David Marchand:
> > > +# Driver specific sources include some testpmd headers.
> >
> > Suggested reword:
> >         Driver-specific commands are located in driver directories.
> 
> At the moment, it's only about testpmd commands.
> Maybe in the future we can have other specific code in those driver sources.
> That's why I preferred a generic term.

It is a comment in the context of testpmd here.

[...]
> > > +#define TESTPMD_ADD_DRIVER_COMMANDS(c) \
> >
> > Why not TESTPMD_ADD_COMMANDS?
> 
> I forgot to align both the function and macro.
> Please note though that for now, the registered commands through this
> API get displayed in the "Driver specific" usage section.
> So maybe the API should state it is about driver specific commands.
> 
> WDYT?

You're right, it should be driver-specific commands.

> > [...]
> > > --- a/drivers/meson.build
> > > +++ b/drivers/meson.build
> > > +        if testpmd_sources.length() != 0
> > > +            testpmd_drivers_sources += testpmd_sources
> > > +            testpmd_drivers_deps += dependency_name
> > > +        endif
> >
> > Are you sure the check is required?
> > What happens if adding an empty string?
> 
> We don't want to push a driver dependency to testpmd if there is nothing to add.

Oh yes, OK.
  

Patch

diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 498fe2c2b7..cd7724d4e3 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -69,6 +69,9 @@ 
 #include "bpf_cmd.h"
 
 static struct cmdline *testpmd_cl;
+static cmdline_parse_ctx_t *main_ctx;
+static TAILQ_HEAD(, testpmd_commands) commands_head =
+	TAILQ_HEAD_INITIALIZER(commands_head);
 
 static void cmd_reconfig_device_queue(portid_t id, uint8_t dev, uint8_t queue);
 
@@ -93,7 +96,8 @@  static void cmd_help_brief_parsed(__rte_unused void *parsed_result,
 		"    help registers                  : Reading and setting port registers.\n"
 		"    help filters                    : Filters configuration help.\n"
 		"    help traffic_management         : Traffic Management commands.\n"
-		"    help devices                    : Device related cmds.\n"
+		"    help devices                    : Device related commands.\n"
+		"    help drivers                    : Driver specific commands.\n"
 		"    help all                        : All of the above sections.\n\n"
 	);
 
@@ -1177,6 +1181,21 @@  static void cmd_help_long_parsed(void *parsed_result,
 		);
 	}
 
+	if (show_all || !strcmp(res->section, "drivers")) {
+		struct testpmd_commands *c;
+		unsigned int i;
+
+		cmdline_printf(
+			cl,
+			"\n"
+			"Driver specific:\n"
+			"----------------\n"
+		);
+		TAILQ_FOREACH(c, &commands_head, next) {
+			for (i = 0; c->commands[i].ctx != NULL; i++)
+				cmdline_printf(cl, "%s\n", c->commands[i].help);
+		}
+	}
 }
 
 static cmdline_parse_token_string_t cmd_help_long_help =
@@ -1184,14 +1203,14 @@  static cmdline_parse_token_string_t cmd_help_long_help =
 
 static cmdline_parse_token_string_t cmd_help_long_section =
 	TOKEN_STRING_INITIALIZER(struct cmd_help_long_result, section,
-			"all#control#display#config#"
-			"ports#registers#filters#traffic_management#devices");
+		"all#control#display#config#ports#registers#"
+		"filters#traffic_management#devices#drivers");
 
 static cmdline_parse_inst_t cmd_help_long = {
 	.f = cmd_help_long_parsed,
 	.data = NULL,
 	.help_str = "help all|control|display|config|ports|register|"
-		"filters|traffic_management|devices: "
+		"filters|traffic_management|devices|drivers: "
 		"Show help",
 	.tokens = {
 		(void *)&cmd_help_long_help,
@@ -17800,7 +17819,7 @@  static cmdline_parse_inst_t cmd_show_port_flow_transfer_proxy = {
 /* ******************************************************************************** */
 
 /* list of instructions */
-static cmdline_parse_ctx_t main_ctx[] = {
+static cmdline_parse_ctx_t builtin_ctx[] = {
 	(cmdline_parse_inst_t *)&cmd_help_brief,
 	(cmdline_parse_inst_t *)&cmd_help_long,
 	(cmdline_parse_inst_t *)&cmd_quit,
@@ -18086,6 +18105,59 @@  static cmdline_parse_ctx_t main_ctx[] = {
 	NULL,
 };
 
+void
+testpmd_add_commands(struct testpmd_commands *c)
+{
+	TAILQ_INSERT_TAIL(&commands_head, c, next);
+}
+
+int
+init_cmdline(void)
+{
+	struct testpmd_commands *c;
+	cmdline_parse_ctx_t *ctx;
+	unsigned int count = 0;
+	unsigned int i;
+
+	/* initialize non-constant commands */
+	cmd_set_fwd_mode_init();
+	cmd_set_fwd_retry_mode_init();
+
+	main_ctx = NULL;
+	for (i = 0; builtin_ctx[i] != NULL; i++) {
+		ctx = realloc(main_ctx, (count + i + 1) * sizeof(*ctx));
+		if (ctx == NULL)
+			goto err;
+		main_ctx = ctx;
+		main_ctx[count + i] = builtin_ctx[i];
+	}
+	count += i;
+
+	TAILQ_FOREACH(c, &commands_head, next) {
+		for (i = 0; c->commands[i].ctx != NULL; i++) {
+			ctx = realloc(main_ctx, (count + i + 1) * sizeof(*ctx));
+			if (ctx == NULL)
+				goto err;
+			main_ctx = ctx;
+			main_ctx[count + i] = c->commands[i].ctx;
+		}
+		count += i;
+	}
+
+	/* cmdline expects a NULL terminated array */
+	ctx = realloc(main_ctx, (count + 1) * sizeof(*ctx));
+	if (ctx == NULL)
+		goto err;
+	main_ctx = ctx;
+	main_ctx[count] = NULL;
+	count += 1;
+
+	return 0;
+err:
+	free(main_ctx);
+	return -1;
+}
+
 /* read cmdline commands from file */
 void
 cmdline_read_from_file(const char *filename)
@@ -18113,9 +18185,6 @@  void
 prompt(void)
 {
 	int ret;
-	/* initialize non-constant commands */
-	cmd_set_fwd_mode_init();
-	cmd_set_fwd_retry_mode_init();
 
 	testpmd_cl = cmdline_stdin_new(main_ctx, "testpmd> ");
 	if (testpmd_cl == NULL)
diff --git a/app/test-pmd/meson.build b/app/test-pmd/meson.build
index 43130c8856..46a7511e9a 100644
--- a/app/test-pmd/meson.build
+++ b/app/test-pmd/meson.build
@@ -73,3 +73,8 @@  endif
 if dpdk_conf.has('RTE_NET_DPAA')
     deps += ['bus_dpaa', 'mempool_dpaa', 'net_dpaa']
 endif
+
+# Driver specific sources include some testpmd headers.
+includes = include_directories('.')
+sources += testpmd_drivers_sources
+deps += testpmd_drivers_deps
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index 79bb23264b..93ec930b15 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -4275,6 +4275,10 @@  main(int argc, char** argv)
 	}
 #endif
 #ifdef RTE_LIB_CMDLINE
+	if (init_cmdline() != 0)
+		rte_exit(EXIT_FAILURE,
+			"Could not initialise cmdline context.\n");
+
 	if (strlen(cmdline_filename) != 0)
 		cmdline_read_from_file(cmdline_filename);
 
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 31f766c965..54d2cd09e9 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -16,7 +16,13 @@ 
 #include <rte_gso.h>
 #endif
 #include <rte_os_shim.h>
+#include <rte_ethdev.h>
+#include <rte_flow.h>
+#include <rte_mbuf_dyn.h>
+
 #include <cmdline.h>
+#include <cmdline_parse.h>
+
 #include <sys/queue.h>
 #ifdef RTE_HAS_JANSSON
 #include <jansson.h>
@@ -866,6 +872,7 @@  unsigned int parse_item_list(const char *str, const char *item_name,
 			unsigned int *parsed_items, int check_unique_values);
 void launch_args_parse(int argc, char** argv);
 void cmdline_read_from_file(const char *filename);
+int init_cmdline(void);
 void prompt(void);
 void prompt_exit(void);
 void nic_stats_display(portid_t port_id);
@@ -1169,6 +1176,22 @@  extern int flow_parse(const char *src, void *result, unsigned int size,
 		      struct rte_flow_item **pattern,
 		      struct rte_flow_action **actions);
 
+/* For registering testpmd commands. */
+struct testpmd_commands {
+	TAILQ_ENTRY(testpmd_commands) next;
+	struct {
+		cmdline_parse_inst_t *ctx;
+		const char *help;
+	} commands[];
+};
+
+extern void testpmd_add_commands(struct testpmd_commands *c);
+#define TESTPMD_ADD_DRIVER_COMMANDS(c) \
+RTE_INIT(__##c) \
+{ \
+	testpmd_add_commands(&c); \
+}
+
 /*
  * Work-around of a compilation error with ICC on invocations of the
  * rte_be_to_cpu_16() function.
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index 1083c6d538..bbeba554eb 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -49,15 +49,18 @@  These are divided into sections and can be accessed using help, help section or
 .. code-block:: console
 
    testpmd> help
-
-       help control    : Start and stop forwarding.
-       help display    : Displaying port, stats and config information.
-       help config     : Configuration information.
-       help ports      : Configuring ports.
-       help registers  : Reading and setting port registers.
-       help filters    : Filters configuration help.
-       help all        : All of the above sections.
-
+       Help is available for the following sections:
+
+           help control                    : Start and stop forwarding.
+           help display                    : Displaying port, stats and config information.
+           help config                     : Configuration information.
+           help ports                      : Configuring ports.
+           help registers                  : Reading and setting port registers.
+           help filters                    : Filters configuration help.
+           help traffic_management         : Traffic Management commands.
+           help devices                    : Device related commands.
+           help drivers                    : Driver specific commands.
+           help all                        : All of the above sections.
 
 Command File Functions
 ----------------------
@@ -5702,3 +5705,9 @@  Flex pattern can be shared between ports.
 
    testpmd> flow create 0 ingress pattern eth / ipv4 / udp / flex item is 3 pattern is 2 / end actions mark id 1 / queue index 0 / end
    Flow rule #0 created
+
+Driver specific commands
+------------------------
+
+Some drivers provide specific features.
+See:
diff --git a/drivers/meson.build b/drivers/meson.build
index 1d8123b00c..4daa2658b7 100644
--- a/drivers/meson.build
+++ b/drivers/meson.build
@@ -102,6 +102,7 @@  foreach subpath:subdirs
         # static builds.
         ext_deps = []
         pkgconfig_extra_libs = []
+        testpmd_sources = []
 
         if not enable_drivers.contains(drv_path)
             build = false
@@ -246,6 +247,10 @@  foreach subpath:subdirs
         set_variable('shared_@0@'.format(lib_name), shared_dep)
         set_variable('static_@0@'.format(lib_name), static_dep)
         dependency_name = ''.join(lib_name.split('rte_'))
+        if testpmd_sources.length() != 0
+            testpmd_drivers_sources += testpmd_sources
+            testpmd_drivers_deps += dependency_name
+        endif
         if developer_mode
             message('drivers/@0@: Defining dependency "@1@"'.format(
                     drv_path, dependency_name))
diff --git a/meson.build b/meson.build
index 937f6110c0..5561171617 100644
--- a/meson.build
+++ b/meson.build
@@ -42,6 +42,8 @@  dpdk_drivers = []
 dpdk_extra_ldflags = []
 dpdk_libs_disabled = []
 dpdk_drvs_disabled = []
+testpmd_drivers_sources = []
+testpmd_drivers_deps = []
 abi_version_file = files('ABI_VERSION')
 
 if host_machine.cpu_family().startswith('x86')