dev: device and port specific args

Type: improvement
Change-Id: I26124a50d8e05d6f01a2e6dbc4bc8183fb5a09c4
Signed-off-by: Damjan Marion <damarion@cisco.com>
diff --git a/src/vnet/CMakeLists.txt b/src/vnet/CMakeLists.txt
index 5236d7e..1e4bb15 100644
--- a/src/vnet/CMakeLists.txt
+++ b/src/vnet/CMakeLists.txt
@@ -27,6 +27,7 @@
   devices/devices.c
   devices/netlink.c
   dev/api.c
+  dev/args.c
   dev/cli.c
   dev/config.c
   dev/counters.c
diff --git a/src/vnet/dev/api.c b/src/vnet/dev/api.c
index 73d315d..260bc7b 100644
--- a/src/vnet/dev/api.c
+++ b/src/vnet/dev/api.c
@@ -90,6 +90,17 @@
     }
   dev->description = dev_desc;
 
+  for (vnet_dev_arg_t *a = driver->registration->args;
+       a->type != VNET_DEV_ARG_END; a++)
+    vec_add1 (dev->args, *a);
+
+  if (args->args)
+    {
+      if ((rv = vnet_dev_arg_parse (vm, dev, dev->args, args->args)) !=
+	  VNET_DEV_OK)
+	goto done;
+    }
+
   if ((args->flags.e & VNET_DEV_F_NO_STATS) == 0)
     dev->poll_stats = 1;
 
@@ -144,6 +155,7 @@
   vnet_dev_port_t *port = 0;
   u16 n_threads = vlib_get_n_threads ();
   int default_is_intr_mode;
+  vnet_dev_rv_t rv;
 
   log_debug (dev,
 	     "create_port_if: device '%s' port %u intf_name '%s' num_rx_q %u "
@@ -169,6 +181,13 @@
   if (port->interface_created)
     return VNET_DEV_ERR_ALREADY_EXISTS;
 
+  if (args->args)
+    {
+      rv = vnet_dev_arg_parse (vm, dev, port->args, args->args);
+      if (rv != VNET_DEV_OK)
+	return rv;
+    }
+
   default_is_intr_mode = (args->flags.e & VNET_DEV_PORT_F_INTERRUPT_MODE) != 0;
   if (default_is_intr_mode && port->attr.caps.interrupt_mode == 0)
     {
diff --git a/src/vnet/dev/args.c b/src/vnet/dev/args.c
new file mode 100644
index 0000000..e302517
--- /dev/null
+++ b/src/vnet/dev/args.c
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2023 Cisco Systems, Inc.
+ */
+
+#include "vppinfra/pool.h"
+#include <vnet/vnet.h>
+#include <vnet/dev/dev.h>
+#include <vnet/dev/counters.h>
+#include <vnet/dev/log.h>
+#include <vnet/dev/types.h>
+#include <vppinfra/format_table.h>
+
+VLIB_REGISTER_LOG_CLASS (dev_log, static) = {
+  .class_name = "dev",
+  .subclass_name = "args",
+};
+
+void
+vnet_dev_arg_clear_value (vnet_dev_arg_t *a)
+{
+  if (a->type == VNET_DEV_ARG_TYPE_STRING)
+    vec_free (a->val.string);
+  a->val = (typeof (a->val)){};
+  a->val_set = 0;
+}
+
+void
+vnet_dev_arg_free (vnet_dev_arg_t **vp)
+{
+  vnet_dev_arg_t *v;
+  vec_foreach (v, *vp)
+    vnet_dev_arg_clear_value (v);
+  vec_free (*vp);
+}
+
+vnet_dev_rv_t
+vnet_dev_arg_parse (vlib_main_t *vm, vnet_dev_t *dev, vnet_dev_arg_t *args,
+		    u8 *str)
+{
+  vnet_dev_rv_t rv = VNET_DEV_OK;
+  unformat_input_t in;
+  u8 *name = 0;
+  u8 *err = 0;
+
+  log_debug (dev, "input '%v'", str);
+  if (args == 0)
+    return rv;
+
+  unformat_init_string (&in, (char *) str, vec_len (str));
+
+  while (unformat (&in, "%U=", unformat_token, "a-zA-Z0-9_", &name))
+    {
+      vnet_dev_arg_t *a = args;
+      vec_add1 (name, 0);
+      while (a < vec_end (args))
+	if (strcmp (a->name, (char *) name) == 0)
+	  break;
+	else
+	  a++;
+
+      if (a->type == VNET_DEV_ARG_TYPE_BOOL)
+	{
+
+	  if (unformat (&in, "true") || unformat (&in, "1") ||
+	      unformat (&in, "on") || unformat (&in, "yes"))
+	    a->val.boolean = 1;
+	  else if (unformat (&in, "false") || unformat (&in, "0") ||
+		   unformat (&in, "off") || unformat (&in, "no"))
+	    a->val.boolean = 0;
+	  else
+	    {
+	      log_err (dev, "unable to parse args: %U", format_unformat_error,
+		       &in);
+	      err = format (
+		0,
+		"boolean value expected ('yes', 'no', '0', '1', 'on', "
+		"'off', 'true' or 'false') for argument '%s', found '%U'",
+		a->name, format_unformat_error, &in);
+	      goto done;
+	    }
+	}
+      else if (a->type == VNET_DEV_ARG_TYPE_UINT32)
+	{
+	  u32 val, min = 0, max = CLIB_U32_MAX;
+	  if (!unformat (&in, "%u", &val))
+	    {
+	      err = format (0,
+			    "unsigned integer in range %u - %u expected for "
+			    "argument '%s', found '%U'",
+			    min, max, a->name, format_unformat_error, &in);
+	      goto done;
+	    }
+
+	  if (a->min || a->max)
+	    {
+	      min = a->min;
+	      max = a->max;
+	    }
+
+	  if (val < min || val > max)
+	    {
+	      err = format (0,
+			    "unsigned integer in range %u - %u expected for "
+			    "argument '%s', found '%u'",
+			    min, max, a->name, val);
+	      goto done;
+	    }
+	  a->val.uint32 = val;
+	}
+      else if (a->type == VNET_DEV_ARG_TYPE_STRING)
+	{
+	  if (!unformat (&in, "%U", unformat_double_quoted_string,
+			 &a->val.string))
+	    {
+	      err = format (
+		0,
+		"double quoted string expected for argument '%s', found '%U'",
+		a->name, format_unformat_error, &in);
+	      goto done;
+	    }
+
+	  if (a->min && vec_len (a->val.string) < a->min)
+	    {
+	      err =
+		format (0, "string '%v' too short, must be at least %u chars",
+			a->val.string, a->min);
+	      goto done;
+	    }
+	  if (a->max && vec_len (a->val.string) > a->max)
+	    {
+	      err = format (
+		0, "string '%v' too long, must be no longer than %u chars",
+		a->val.string, a->max);
+	      goto done;
+	    }
+	}
+      else
+	{
+	  err = format (0, "unknown argument '%s'", name);
+	  goto done;
+	}
+
+      a->val_set = 1;
+      log_debug (dev, "name '%s' type %U value %U", name,
+		 format_vnet_dev_arg_type, a->type, format_vnet_dev_arg_value,
+		 a->type, &a->val);
+      vec_free (name);
+      unformat (&in, ",");
+    }
+
+  if (unformat_check_input (&in) != UNFORMAT_END_OF_INPUT)
+    err = format (0, "unable to parse argument name '%U'",
+		  format_unformat_error, &in);
+
+done:
+  if (err)
+    {
+      vnet_dev_arg_t *a = 0;
+      log_err (dev, "%v", err);
+      vec_free (err);
+      vec_foreach (a, args)
+	vnet_dev_arg_clear_value (a);
+      rv = VNET_DEV_ERR_INVALID_ARG;
+    }
+
+  vec_free (name);
+  unformat_free (&in);
+  return rv;
+}
+
+u8 *
+format_vnet_dev_arg_type (u8 *s, va_list *args)
+{
+  vnet_dev_arg_type_t t = va_arg (*args, u32);
+  switch (t)
+    {
+#define _(n, f, val)                                                          \
+  case VNET_DEV_ARG_TYPE_##n:                                                 \
+    return format (s, #n);
+      foreach_vnet_dev_arg_type
+#undef _
+	default : ASSERT (0);
+      break;
+    }
+  return s;
+}
+
+u8 *
+format_vnet_dev_arg_value (u8 *s, va_list *args)
+{
+  vnet_dev_arg_type_t t = va_arg (*args, u32);
+  vnet_dev_arg_value_t *v = va_arg (*args, vnet_dev_arg_value_t *);
+
+  switch (t)
+    {
+#define _(n, f, value)                                                        \
+  case VNET_DEV_ARG_TYPE_##n:                                                 \
+    s = format (s, f, v->value);                                              \
+    break;
+      foreach_vnet_dev_arg_type
+#undef _
+	default : break;
+    }
+  return s;
+}
+
+u8 *
+format_vnet_dev_args (u8 *s, va_list *va)
+{
+  vnet_dev_arg_t *a, *args = va_arg (*va, vnet_dev_arg_t *);
+  table_t t = { .no_ansi = 1 };
+
+  table_add_header_col (&t, 4, "Name", "Value", "Default", "Description");
+  table_set_cell_align (&t, -1, 0, TTAA_LEFT);
+  table_set_cell_align (&t, -1, 3, TTAA_LEFT);
+  vec_foreach (a, args)
+    {
+      int r = a - args;
+      table_format_cell (&t, r, 0, "%s", a->name);
+      if (a->val_set)
+	table_format_cell (&t, r, 1, "%U", format_vnet_dev_arg_value, a->type,
+			   &a->val);
+      else
+	table_format_cell (&t, r, 1, "<not set>");
+
+      table_format_cell (&t, r, 2, "%U", format_vnet_dev_arg_value, a->type,
+			 &a->default_val);
+      table_format_cell (&t, r, 3, "%s", a->desc);
+      table_set_cell_align (&t, r, 0, TTAA_LEFT);
+      table_set_cell_align (&t, r, 3, TTAA_LEFT);
+    }
+
+  s = format (s, "%U", format_table, &t);
+
+  table_free (&t);
+  return s;
+}
diff --git a/src/vnet/dev/args.h b/src/vnet/dev/args.h
new file mode 100644
index 0000000..0c49d1f
--- /dev/null
+++ b/src/vnet/dev/args.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2023 Cisco Systems, Inc.
+ */
+
+#ifndef _VNET_DEV_ARGS_H_
+#define _VNET_DEV_ARGS_H_
+
+#include <vppinfra/clib.h>
+#include <vnet/dev/errors.h>
+
+#define foreach_vnet_dev_arg_type                                             \
+  _ (BOOL, "%u", boolean)                                                     \
+  _ (UINT32, "%u", uint32)                                                    \
+  _ (STRING, "\'%v\'", string)
+
+typedef enum
+{
+  VNET_DEV_ARG_END,
+#define _(n, f, v) VNET_DEV_ARG_TYPE_##n,
+  foreach_vnet_dev_arg_type
+#undef _
+} __clib_packed vnet_dev_arg_type_t;
+
+typedef union
+{
+  u8 boolean;
+  u8 uint32;
+  u8 *string;
+} vnet_dev_arg_value_t;
+
+typedef struct
+{
+  char *name;
+  char *desc;
+  vnet_dev_arg_type_t type;
+  u8 val_set;
+  u32 min;
+  u32 max;
+  u64 id;
+  vnet_dev_arg_value_t val;
+  vnet_dev_arg_value_t default_val;
+} vnet_dev_arg_t;
+
+#define VNET_DEV_ARG_BOOL(ud, n, d, ...)                                      \
+  {                                                                           \
+    .type = VNET_DEV_ARG_TYPE_BOOL, .id = ud, .name = n, .desc = d,           \
+    __VA_ARGS__                                                               \
+  }
+#define VNET_DEV_ARG_UINT32(ud, n, d, ...)                                    \
+  {                                                                           \
+    .type = VNET_DEV_ARG_TYPE_UINT32, .id = ud, .name = n, .desc = d,         \
+    __VA_ARGS__                                                               \
+  }
+#define VNET_DEV_ARG_STRING(ud, n, d, ...)                                    \
+  {                                                                           \
+    .type = VNET_DEV_ARG_TYPE_STRING, .id = ud, .name = n, .desc = d,         \
+    __VA_ARGS__                                                               \
+  }
+#define VNET_DEV_ARG_END()                                                    \
+  {                                                                           \
+    .type = VNET_DEV_ARG_END                                                  \
+  }
+
+#define VNET_DEV_ARGS(...)                                                    \
+  (vnet_dev_arg_t[]) { __VA_ARGS__, VNET_DEV_ARG_END () }
+
+#define foreach_vnet_dev_args(a, d)                                           \
+  for (typeof ((d)->args[0]) *(a) = (d)->args; (a) < vec_end ((d)->args);     \
+       (a)++)
+#define foreach_vnet_dev_port_args(a, p)                                      \
+  for (typeof ((p)->args[0]) *(a) = (p)->args; (a) < vec_end ((p)->args);     \
+       (a)++)
+
+#endif /* _VNET_DEV_ARGS_H_ */
diff --git a/src/vnet/dev/config.c b/src/vnet/dev/config.c
index 091d83e..3026eaa 100644
--- a/src/vnet/dev/config.c
+++ b/src/vnet/dev/config.c
@@ -41,6 +41,9 @@
       else if (unformat (input, "flags %U", unformat_vnet_dev_port_flags,
 			 &args->flags))
 	;
+      else if (unformat (input, "args %U", unformat_single_quoted_string,
+			 &args->args))
+	;
       else
 	{
 	  err = clib_error_return (0, "unknown input '%U'",
@@ -50,7 +53,6 @@
     }
   return err;
 }
-
 static clib_error_t *
 vnet_dev_config_one_device (vlib_main_t *vm, unformat_input_t *input,
 			    char *device_id)
@@ -71,6 +73,9 @@
       else if (unformat (input, "flags %U", unformat_vnet_dev_flags,
 			 &args.flags))
 	;
+      else if (unformat (input, "args %U", unformat_single_quoted_string,
+			 &args.args))
+	;
       else if (unformat (input, "port %u %U", &n, unformat_vlib_cli_sub_input,
 			 &sub_input))
 	{
@@ -96,6 +101,7 @@
 
       clib_memcpy (args.device_id, device_id, sizeof (args.device_id));
       rv = vnet_dev_api_attach (vm, &args);
+      vec_free (args.args);
 
       if (rv == VNET_DEV_OK)
 	{
diff --git a/src/vnet/dev/dev.c b/src/vnet/dev/dev.c
index 9b99fe4..0e04e9a 100644
--- a/src/vnet/dev/dev.c
+++ b/src/vnet/dev/dev.c
@@ -160,6 +160,7 @@
   pool_free (dev->ports);
   pool_free (dev->periodic_ops);
   hash_unset (dm->device_index_by_id, dev->device_id);
+  vnet_dev_arg_free (&dev->args);
   pool_put_index (dm->devices, dev->index);
 }
 
diff --git a/src/vnet/dev/dev.h b/src/vnet/dev/dev.h
index e3421b9..c18d29a 100644
--- a/src/vnet/dev/dev.h
+++ b/src/vnet/dev/dev.h
@@ -10,6 +10,7 @@
 #include <vppinfra/format.h>
 #include <vnet/vnet.h>
 #include <vnet/dev/types.h>
+#include <vnet/dev/args.h>
 
 #define VNET_DEV_DEVICE_ID_PREFIX_DELIMITER "/"
 
@@ -287,6 +288,7 @@
   vnet_dev_rx_queue_t **rx_queues;
   vnet_dev_tx_queue_t **tx_queues;
   vnet_dev_port_ops_t port_ops;
+  vnet_dev_arg_t *args;
   vnet_dev_rx_queue_ops_t rx_queue_ops;
   vnet_dev_tx_queue_ops_t tx_queue_ops;
   vnet_dev_node_t rx_node;
@@ -338,6 +340,7 @@
   vnet_dev_port_t **ports;
   vnet_dev_periodic_op_t *periodic_ops;
   u8 *description;
+  vnet_dev_arg_t *args;
   u8 __clib_aligned (16)
   data[];
 } vnet_dev_t;
@@ -386,6 +389,7 @@
   vnet_dev_match_t *match;
   int priority;
   vnet_dev_ops_t ops;
+  vnet_dev_arg_t *args;
 };
 
 typedef struct
@@ -432,6 +436,7 @@
   {
     vnet_dev_port_attr_t attr;
     vnet_dev_port_ops_t ops;
+    vnet_dev_arg_t *args;
     u16 data_size;
     void *initial_data;
   } port;
@@ -469,6 +474,15 @@
   u32 link_speed;
 } vnet_dev_port_state_changes_t;
 
+/* args.c */
+vnet_dev_rv_t vnet_dev_arg_parse (vlib_main_t *, vnet_dev_t *,
+				  vnet_dev_arg_t *, u8 *);
+void vnet_dev_arg_free (vnet_dev_arg_t **);
+void vnet_dev_arg_clear_value (vnet_dev_arg_t *);
+format_function_t format_vnet_dev_arg_type;
+format_function_t format_vnet_dev_arg_value;
+format_function_t format_vnet_dev_args;
+
 /* dev.c */
 vnet_dev_t *vnet_dev_alloc (vlib_main_t *, vnet_dev_device_id_t,
 			    vnet_dev_driver_t *);
diff --git a/src/vnet/dev/dev_funcs.h b/src/vnet/dev/dev_funcs.h
index 33159ff..a74d339 100644
--- a/src/vnet/dev/dev_funcs.h
+++ b/src/vnet/dev/dev_funcs.h
@@ -289,4 +289,34 @@
   *addr = ha;
 }
 
+static_always_inline vnet_dev_arg_t *
+vnet_dev_get_port_arg_by_id (vnet_dev_port_t *port, u32 id)
+{
+  foreach_vnet_dev_port_args (a, port)
+    if (a->id == id)
+      return a;
+  return 0;
+}
+
+static_always_inline int
+vnet_dev_arg_get_bool (vnet_dev_arg_t *arg)
+{
+  ASSERT (arg->type == VNET_DEV_ARG_TYPE_BOOL);
+  return arg->val_set ? arg->val.boolean : arg->default_val.boolean;
+}
+
+static_always_inline u32
+vnet_dev_arg_get_uint32 (vnet_dev_arg_t *arg)
+{
+  ASSERT (arg->type == VNET_DEV_ARG_TYPE_UINT32);
+  return arg->val_set ? arg->val.uint32 : arg->default_val.uint32;
+}
+
+static_always_inline u8 *
+vnet_dev_arg_get_string (vnet_dev_arg_t *arg)
+{
+  ASSERT (arg->type == VNET_DEV_ARG_TYPE_STRING);
+  return arg->val_set ? arg->val.string : arg->default_val.string;
+}
+
 #endif /* _VNET_DEV_FUNCS_H_ */
diff --git a/src/vnet/dev/errors.h b/src/vnet/dev/errors.h
index 2256e1e..1f45ce2 100644
--- a/src/vnet/dev/errors.h
+++ b/src/vnet/dev/errors.h
@@ -14,6 +14,7 @@
   _ (DEVICE_NO_REPLY, "no reply from device")                                 \
   _ (DMA_MEM_ALLOC_FAIL, "DMA memory allocation error")                       \
   _ (DRIVER_NOT_AVAILABLE, "driver not available")                            \
+  _ (INVALID_ARG, "invalid argument")                                         \
   _ (INVALID_BUS, "invalid bus")                                              \
   _ (INVALID_DATA, "invalid data")                                            \
   _ (INVALID_DEVICE_ID, "invalid device id")                                  \
diff --git a/src/vnet/dev/format.c b/src/vnet/dev/format.c
index 8816c0e..944da06 100644
--- a/src/vnet/dev/format.c
+++ b/src/vnet/dev/format.c
@@ -2,9 +2,6 @@
  * Copyright (c) 2023 Cisco Systems, Inc.
  */
 
-#include "vlib/pci/pci.h"
-#include "vnet/dev/counters.h"
-#include "vppinfra/error.h"
 #include <vnet/vnet.h>
 #include <vnet/dev/dev.h>
 #include <vnet/dev/counters.h>
@@ -75,9 +72,14 @@
 
   s = format (s, "\n%UAssigned process node is '%U'", format_white_space,
 	      indent, format_vlib_node_name, vm, dev->process_node_index);
+  if (dev->args)
+    s = format (s, "\n%UDevice Specific Arguments:\n%U%U", format_white_space,
+		indent, format_white_space, indent + 2, format_vnet_dev_args,
+		dev->args);
   if (dev->ops.format_info)
-    s = format (s, "\n%U%U", format_white_space, indent, dev->ops.format_info,
-		a, dev);
+    s =
+      format (s, "\n%UDevice Specific Info:\n%U%U", format_white_space, indent,
+	      format_white_space, indent + 2, dev->ops.format_info, a, dev);
   return s;
 }
 
@@ -121,8 +123,13 @@
 	      format_white_space, indent, port->max_rx_frame_size,
 	      port->attr.max_supported_rx_frame_size);
   if (port->port_ops.format_status)
-    s = format (s, "\n%U%U", format_white_space, indent,
+    s = format (s, "\n%UDevice Specific Port Status:\n%U%U",
+		format_white_space, indent, format_white_space, indent + 2,
 		port->port_ops.format_status, a, port);
+  if (port->args)
+    s = format (s, "\n%UDevice Specific Port Arguments:\n%U%U",
+		format_white_space, indent, format_white_space, indent + 2,
+		format_vnet_dev_args, port->args);
 
   s = format (s, "\n%UInterface ", format_white_space, indent);
   if (port->interface_created)
diff --git a/src/vnet/dev/port.c b/src/vnet/dev/port.c
index 350c1d8..b3f4035 100644
--- a/src/vnet/dev/port.c
+++ b/src/vnet/dev/port.c
@@ -93,6 +93,7 @@
   pool_free (port->secondary_hw_addr);
   pool_free (port->rx_queues);
   pool_free (port->tx_queues);
+  vnet_dev_arg_free (&port->args);
   pool_put_index (dev->ports, port->index);
   clib_mem_free (port);
 }
@@ -266,6 +267,9 @@
   port->rx_node = *args->rx_node;
   port->tx_node = *args->tx_node;
 
+  for (vnet_dev_arg_t *a = args->port.args; a->type != VNET_DEV_ARG_END; a++)
+    vec_add1 (port->args, *a);
+
   /* defaults out of port attributes */
   port->max_rx_frame_size = args->port.attr.max_supported_rx_frame_size;
   port->primary_hw_addr = args->port.attr.hw_addr;
@@ -711,6 +715,9 @@
 
   vnet_dev_port_free_counters (vm, port);
 
+  foreach_vnet_dev_port_args (v, port)
+    vnet_dev_arg_clear_value (v);
+
   return VNET_DEV_OK;
 }
 void