summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDamjan Marion <damarion@cisco.com>2023-11-13 17:33:32 +0000
committerAndrew Yourtchenko <ayourtch@gmail.com>2023-11-16 16:01:35 +0000
commit69768d99eed0f44f6955370cb1ad83b8b73e5368 (patch)
tree00c689be86b41fe36aae2a9e6e0d3422e1272d81 /src
parentf3be34e4433bb86509044290fa3cced543697a31 (diff)
dev: device and port specific args
Type: improvement Change-Id: I26124a50d8e05d6f01a2e6dbc4bc8183fb5a09c4 Signed-off-by: Damjan Marion <damarion@cisco.com>
Diffstat (limited to 'src')
-rw-r--r--src/vnet/CMakeLists.txt1
-rw-r--r--src/vnet/dev/api.c19
-rw-r--r--src/vnet/dev/args.c237
-rw-r--r--src/vnet/dev/args.h74
-rw-r--r--src/vnet/dev/config.c8
-rw-r--r--src/vnet/dev/dev.c1
-rw-r--r--src/vnet/dev/dev.h14
-rw-r--r--src/vnet/dev/dev_funcs.h30
-rw-r--r--src/vnet/dev/errors.h1
-rw-r--r--src/vnet/dev/format.c19
-rw-r--r--src/vnet/dev/port.c7
11 files changed, 404 insertions, 7 deletions
diff --git a/src/vnet/CMakeLists.txt b/src/vnet/CMakeLists.txt
index 5236d7e67b5..1e4bb155336 100644
--- a/src/vnet/CMakeLists.txt
+++ b/src/vnet/CMakeLists.txt
@@ -27,6 +27,7 @@ list(APPEND VNET_SOURCES
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 73d315db519..260bc7b2e22 100644
--- a/src/vnet/dev/api.c
+++ b/src/vnet/dev/api.c
@@ -90,6 +90,17 @@ vnet_dev_api_attach (vlib_main_t *vm, vnet_dev_api_attach_args_t *args)
}
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_api_create_port_if (vlib_main_t *vm,
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 @@ vnet_dev_api_create_port_if (vlib_main_t *vm,
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 00000000000..e302517cc61
--- /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 00000000000..0c49d1fcfe2
--- /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 091d83ecfec..3026eaadca7 100644
--- a/src/vnet/dev/config.c
+++ b/src/vnet/dev/config.c
@@ -41,6 +41,9 @@ vnet_dev_config_one_interface (vlib_main_t *vm, unformat_input_t *input,
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 @@ vnet_dev_config_one_interface (vlib_main_t *vm, unformat_input_t *input,
}
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 @@ vnet_dev_config_one_device (vlib_main_t *vm, unformat_input_t *input,
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 @@ vnet_dev_config_one_device (vlib_main_t *vm, unformat_input_t *input,
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 9b99fe432a4..0e04e9ab64b 100644
--- a/src/vnet/dev/dev.c
+++ b/src/vnet/dev/dev.c
@@ -160,6 +160,7 @@ vnet_dev_free (vlib_main_t *vm, vnet_dev_t *dev)
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 e3421b91978..c18d29a8d5b 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 @@ typedef struct vnet_dev_port
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 @@ typedef struct vnet_dev
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 @@ struct vnet_dev_driver_registration
vnet_dev_match_t *match;
int priority;
vnet_dev_ops_t ops;
+ vnet_dev_arg_t *args;
};
typedef struct
@@ -432,6 +436,7 @@ typedef struct
{
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 @@ typedef struct
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 33159ffa65d..a74d3399511 100644
--- a/src/vnet/dev/dev_funcs.h
+++ b/src/vnet/dev/dev_funcs.h
@@ -289,4 +289,34 @@ vnet_dev_set_hw_addr_eth_mac (vnet_dev_hw_addr_t *addr, const u8 *eth_mac_addr)
*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 2256e1e4203..1f45ce2ba97 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 8816c0e552c..944da0698ea 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 @@ format_vnet_dev_info (u8 *s, va_list *args)
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_vnet_dev_port_info (u8 *s, va_list *args)
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 350c1d84c73..b3f40354c7a 100644
--- a/src/vnet/dev/port.c
+++ b/src/vnet/dev/port.c
@@ -93,6 +93,7 @@ vnet_dev_port_free (vlib_main_t *vm, vnet_dev_port_t *port)
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 @@ vnet_dev_port_add (vlib_main_t *vm, vnet_dev_t *dev, vnet_dev_port_id_t id,
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_if_remove (vlib_main_t *vm, vnet_dev_port_t *port)
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