summaryrefslogtreecommitdiffstats
path: root/netlink/cable_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'netlink/cable_test.c')
-rw-r--r--netlink/cable_test.c595
1 files changed, 595 insertions, 0 deletions
diff --git a/netlink/cable_test.c b/netlink/cable_test.c
new file mode 100644
index 0000000..9305a47
--- /dev/null
+++ b/netlink/cable_test.c
@@ -0,0 +1,595 @@
+/*
+ * cable_test.c - netlink implementation of cable test command
+ *
+ * Implementation of ethtool --cable-test <dev>
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+struct cable_test_context {
+ bool breakout;
+};
+
+static int nl_get_cable_test_result(const struct nlattr *nest, uint8_t *pair,
+ uint16_t *code)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_RESULT_MAX+1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 ||
+ !tb[ETHTOOL_A_CABLE_RESULT_PAIR] ||
+ !tb[ETHTOOL_A_CABLE_RESULT_CODE])
+ return -EFAULT;
+
+ *pair = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_RESULT_PAIR]);
+ *code = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_RESULT_CODE]);
+
+ return 0;
+}
+
+static int nl_get_cable_test_fault_length(const struct nlattr *nest,
+ uint8_t *pair, unsigned int *cm)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_FAULT_LENGTH_MAX+1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 ||
+ !tb[ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR] ||
+ !tb[ETHTOOL_A_CABLE_FAULT_LENGTH_CM])
+ return -EFAULT;
+
+ *pair = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_FAULT_LENGTH_PAIR]);
+ *cm = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_FAULT_LENGTH_CM]);
+
+ return 0;
+}
+
+static char *nl_code2txt(uint16_t code)
+{
+ switch (code) {
+ case ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC:
+ default:
+ return "Unknown";
+ case ETHTOOL_A_CABLE_RESULT_CODE_OK:
+ return "OK";
+ case ETHTOOL_A_CABLE_RESULT_CODE_OPEN:
+ return "Open Circuit";
+ case ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT:
+ return "Short within Pair";
+ case ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT:
+ return "Short to another pair";
+ }
+}
+
+static char *nl_pair2txt(uint8_t pair)
+{
+ switch (pair) {
+ case ETHTOOL_A_CABLE_PAIR_A:
+ return "Pair A";
+ case ETHTOOL_A_CABLE_PAIR_B:
+ return "Pair B";
+ case ETHTOOL_A_CABLE_PAIR_C:
+ return "Pair C";
+ case ETHTOOL_A_CABLE_PAIR_D:
+ return "Pair D";
+ default:
+ return "Unexpected pair";
+ }
+}
+
+static int nl_cable_test_ntf_attr(struct nlattr *evattr)
+{
+ unsigned int cm;
+ uint16_t code;
+ uint8_t pair;
+ int ret;
+
+ switch (mnl_attr_get_type(evattr)) {
+ case ETHTOOL_A_CABLE_NEST_RESULT:
+ ret = nl_get_cable_test_result(evattr, &pair, &code);
+ if (ret < 0)
+ return ret;
+
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "pair", "%s ", nl_pair2txt(pair));
+ print_string(PRINT_ANY, "code", "code %s\n", nl_code2txt(code));
+ close_json_object();
+ break;
+
+ case ETHTOOL_A_CABLE_NEST_FAULT_LENGTH:
+ ret = nl_get_cable_test_fault_length(evattr, &pair, &cm);
+ if (ret < 0)
+ return ret;
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "pair", "%s, ", nl_pair2txt(pair));
+ print_float(PRINT_ANY, "length", "fault length: %0.2fm\n",
+ (float)cm / 100);
+ close_json_object();
+ break;
+ }
+ return 0;
+}
+
+static void cable_test_ntf_nest(const struct nlattr *nest)
+{
+ struct nlattr *pos;
+ int ret;
+
+ mnl_attr_for_each_nested(pos, nest) {
+ ret = nl_cable_test_ntf_attr(pos);
+ if (ret < 0)
+ return;
+ }
+}
+
+/* Returns MNL_CB_STOP when the test is complete. Used when executing
+ * a test, but not suitable for monitor.
+ */
+static int cable_test_ntf_stop_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_TEST_NTF_MAX + 1] = {};
+ u8 status = ETHTOOL_A_CABLE_TEST_NTF_STATUS_UNSPEC;
+ struct cable_test_context *ctctx;
+ struct nl_context *nlctx = data;
+ DECLARE_ATTR_TB_INFO(tb);
+ bool silent;
+ int err_ret;
+ int ret;
+
+ ctctx = nlctx->cmd_private;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_CABLE_TEST_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS])
+ status = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS]);
+
+ switch (status) {
+ case ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED:
+ print_string(PRINT_FP, "status",
+ "Cable test started for device %s.\n",
+ nlctx->devname);
+ break;
+ case ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED:
+ print_string(PRINT_FP, "status",
+ "Cable test completed for device %s.\n",
+ nlctx->devname);
+ break;
+ default:
+ break;
+ }
+
+ if (tb[ETHTOOL_A_CABLE_TEST_NTF_NEST])
+ cable_test_ntf_nest(tb[ETHTOOL_A_CABLE_TEST_NTF_NEST]);
+
+ if (status == ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED) {
+ if (ctctx)
+ ctctx->breakout = true;
+ return MNL_CB_STOP;
+ }
+
+ return MNL_CB_OK;
+}
+
+/* Wrapper around cable_test_ntf_stop_cb() which does not return STOP,
+ * used for monitor
+ */
+int cable_test_ntf_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ int status = cable_test_ntf_stop_cb(nlhdr, data);
+
+ if (status == MNL_CB_STOP)
+ status = MNL_CB_OK;
+
+ return status;
+}
+
+static int nl_cable_test_results_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+
+ if (ghdr->cmd != ETHTOOL_MSG_CABLE_TEST_NTF)
+ return MNL_CB_OK;
+
+ return cable_test_ntf_stop_cb(nlhdr, data);
+}
+
+/* Receive the broadcasted messages until we get the cable test
+ * results
+ */
+static int nl_cable_test_process_results(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ struct cable_test_context ctctx;
+ int err;
+
+ nlctx->is_monitor = true;
+ nlsk->port = 0;
+ nlsk->seq = 0;
+ nlctx->filter_devname = ctx->devname;
+
+ ctctx.breakout = false;
+ nlctx->cmd_private = &ctctx;
+
+ while (!ctctx.breakout) {
+ err = nlsock_process_reply(nlsk, nl_cable_test_results_cb,
+ nlctx);
+ if (err)
+ return err;
+ }
+
+ return err;
+}
+
+int nl_cable_test(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ uint32_t grpid = nlctx->ethnl_mongrp;
+ int ret;
+
+ /* Join the multicast group so we can receive the results in a
+ * race free way.
+ */
+ if (!grpid) {
+ fprintf(stderr, "multicast group 'monitor' not found\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = mnl_socket_setsockopt(nlsk->sk, NETLINK_ADD_MEMBERSHIP,
+ &grpid, sizeof(grpid));
+ if (ret < 0)
+ return ret;
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_CABLE_TEST_ACT,
+ ETHTOOL_A_CABLE_TEST_HEADER, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ fprintf(stderr, "Cannot start cable test\n");
+ else {
+ new_json_obj(ctx->json);
+
+ ret = nl_cable_test_process_results(ctx);
+
+ delete_json_obj();
+ }
+
+ return ret;
+}
+
+static int nl_get_cable_test_tdr_amplitude(const struct nlattr *nest,
+ uint8_t *pair, int16_t *mV)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_AMPLITUDE_MAX+1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ uint16_t mV_unsigned;
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 ||
+ !tb[ETHTOOL_A_CABLE_AMPLITUDE_PAIR] ||
+ !tb[ETHTOOL_A_CABLE_AMPLITUDE_mV])
+ return -EFAULT;
+
+ *pair = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_AMPLITUDE_PAIR]);
+ mV_unsigned = mnl_attr_get_u16(tb[ETHTOOL_A_CABLE_AMPLITUDE_mV]);
+ *mV = (int16_t)(mV_unsigned);
+
+ return 0;
+}
+
+static int nl_get_cable_test_tdr_pulse(const struct nlattr *nest, uint16_t *mV)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_PULSE_MAX+1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 ||
+ !tb[ETHTOOL_A_CABLE_PULSE_mV])
+ return -EFAULT;
+
+ *mV = mnl_attr_get_u16(tb[ETHTOOL_A_CABLE_PULSE_mV]);
+
+ return 0;
+}
+
+static int nl_get_cable_test_tdr_step(const struct nlattr *nest,
+ uint32_t *first, uint32_t *last,
+ uint32_t *step)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_STEP_MAX+1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+ if (ret < 0 ||
+ !tb[ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE] ||
+ !tb[ETHTOOL_A_CABLE_STEP_LAST_DISTANCE] ||
+ !tb[ETHTOOL_A_CABLE_STEP_STEP_DISTANCE])
+ return -EFAULT;
+
+ *first = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_STEP_FIRST_DISTANCE]);
+ *last = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_STEP_LAST_DISTANCE]);
+ *step = mnl_attr_get_u32(tb[ETHTOOL_A_CABLE_STEP_STEP_DISTANCE]);
+
+ return 0;
+}
+
+static int nl_cable_test_tdr_ntf_attr(struct nlattr *evattr)
+{
+ uint32_t first, last, step;
+ uint8_t pair;
+ int ret;
+
+ switch (mnl_attr_get_type(evattr)) {
+ case ETHTOOL_A_CABLE_TDR_NEST_AMPLITUDE: {
+ int16_t mV;
+
+ ret = nl_get_cable_test_tdr_amplitude(
+ evattr, &pair, &mV);
+ if (ret < 0)
+ return ret;
+
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "pair", "%s ", nl_pair2txt(pair));
+ print_int(PRINT_ANY, "amplitude", "Amplitude %4d\n", mV);
+ close_json_object();
+ break;
+ }
+ case ETHTOOL_A_CABLE_TDR_NEST_PULSE: {
+ uint16_t mV;
+
+ ret = nl_get_cable_test_tdr_pulse(evattr, &mV);
+ if (ret < 0)
+ return ret;
+
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "pulse", "TDR Pulse %dmV\n", mV);
+ close_json_object();
+ break;
+ }
+ case ETHTOOL_A_CABLE_TDR_NEST_STEP:
+ ret = nl_get_cable_test_tdr_step(evattr, &first, &last, &step);
+ if (ret < 0)
+ return ret;
+
+ open_json_object(NULL);
+ print_float(PRINT_ANY, "first", "Step configuration: %.2f-",
+ (float)first / 100);
+ print_float(PRINT_ANY, "last", "%.2f meters ",
+ (float)last / 100);
+ print_float(PRINT_ANY, "step", "in %.2fm steps\n",
+ (float)step / 100);
+ close_json_object();
+ break;
+ }
+ return 0;
+}
+
+static void cable_test_tdr_ntf_nest(const struct nlattr *nest)
+{
+ struct nlattr *pos;
+ int ret;
+
+ mnl_attr_for_each_nested(pos, nest) {
+ ret = nl_cable_test_tdr_ntf_attr(pos);
+ if (ret < 0)
+ return;
+ }
+}
+
+/* Returns MNL_CB_STOP when the test is complete. Used when executing
+ * a test, but not suitable for monitor.
+ */
+int cable_test_tdr_ntf_stop_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_CABLE_TEST_TDR_NTF_MAX + 1] = {};
+ u8 status = ETHTOOL_A_CABLE_TEST_NTF_STATUS_UNSPEC;
+ struct cable_test_context *ctctx;
+ struct nl_context *nlctx = data;
+
+ DECLARE_ATTR_TB_INFO(tb);
+ bool silent;
+ int err_ret;
+ int ret;
+
+ ctctx = nlctx->cmd_private;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return err_ret;
+
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_CABLE_TEST_TDR_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS])
+ status = mnl_attr_get_u8(tb[ETHTOOL_A_CABLE_TEST_NTF_STATUS]);
+
+ switch (status) {
+ case ETHTOOL_A_CABLE_TEST_NTF_STATUS_STARTED:
+ print_string(PRINT_FP, "status",
+ "Cable test TDR started for device %s.\n",
+ nlctx->devname);
+ break;
+ case ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED:
+ print_string(PRINT_FP, "status",
+ "Cable test TDR completed for device %s.\n",
+ nlctx->devname);
+ break;
+ default:
+ break;
+ }
+
+ if (tb[ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST])
+ cable_test_tdr_ntf_nest(tb[ETHTOOL_A_CABLE_TEST_TDR_NTF_NEST]);
+
+ if (status == ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED) {
+ if (ctctx)
+ ctctx->breakout = true;
+ return MNL_CB_STOP;
+ }
+
+ return MNL_CB_OK;
+}
+
+/* Wrapper around cable_test_tdr_ntf_stop_cb() which does not return
+ * STOP, used for monitor
+ */
+int cable_test_tdr_ntf_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ int status = cable_test_tdr_ntf_stop_cb(nlhdr, data);
+
+ if (status == MNL_CB_STOP)
+ status = MNL_CB_OK;
+
+ return status;
+}
+
+static int nl_cable_test_tdr_results_cb(const struct nlmsghdr *nlhdr,
+ void *data)
+{
+ const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+
+ if (ghdr->cmd != ETHTOOL_MSG_CABLE_TEST_TDR_NTF)
+ return MNL_CB_OK;
+
+ cable_test_tdr_ntf_cb(nlhdr, data);
+
+ return MNL_CB_STOP;
+}
+
+/* Receive the broadcasted messages until we get the cable test
+ * results
+ */
+static int nl_cable_test_tdr_process_results(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ struct cable_test_context ctctx;
+ int err;
+
+ nlctx->is_monitor = true;
+ nlsk->port = 0;
+ nlsk->seq = 0;
+ nlctx->filter_devname = ctx->devname;
+
+ ctctx.breakout = false;
+ nlctx->cmd_private = &ctctx;
+
+ while (!ctctx.breakout) {
+ err = nlsock_process_reply(nlsk, nl_cable_test_tdr_results_cb,
+ nlctx);
+ if (err)
+ return err;
+ }
+
+ return err;
+}
+
+static const struct param_parser tdr_params[] = {
+ {
+ .arg = "first",
+ .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_FIRST,
+ .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
+ .handler = nl_parse_direct_m2cm,
+ },
+ {
+ .arg = "last",
+ .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_LAST,
+ .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
+ .handler = nl_parse_direct_m2cm,
+ },
+ {
+ .arg = "step",
+ .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_STEP,
+ .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
+ .handler = nl_parse_direct_m2cm,
+ },
+ {
+ .arg = "pair",
+ .type = ETHTOOL_A_CABLE_TEST_TDR_CFG_PAIR,
+ .group = ETHTOOL_A_CABLE_TEST_TDR_CFG,
+ .handler = nl_parse_direct_u8,
+ },
+ {}
+};
+
+int nl_cable_test_tdr(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ uint32_t grpid = nlctx->ethnl_mongrp;
+ struct nl_msg_buff *msgbuff;
+ int ret;
+
+ nlctx->cmd = "--cable-test-tdr";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ msgbuff = &nlsk->msgbuff;
+
+ /* Join the multicast group so we can receive the results in a
+ * race free way.
+ */
+ if (!grpid) {
+ fprintf(stderr, "multicast group 'monitor' not found\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = mnl_socket_setsockopt(nlsk->sk, NETLINK_ADD_MEMBERSHIP,
+ &grpid, sizeof(grpid));
+ if (ret < 0)
+ return ret;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_CABLE_TEST_TDR_ACT,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return 2;
+
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_CABLE_TEST_TDR_HEADER,
+ ctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nl_parser(nlctx, tdr_params, NULL, PARSER_GROUP_NEST, NULL);
+ if (ret < 0)
+ return ret;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ fprintf(stderr, "Cannot start cable test TDR\n");
+ else {
+ new_json_obj(ctx->json);
+
+ ret = nl_cable_test_tdr_process_results(ctx);
+
+ delete_json_obj();
+ }
+
+ return ret;
+}