blob: a74449be3ffee93e2b227f315d7f2c8fc28097c2 [file] [log] [blame]
/*
* 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"
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;
printf("%s, result: %s\n", nl_pair2txt(pair),
nl_code2txt(code));
break;
case ETHTOOL_A_CABLE_NEST_FAULT_LENGTH:
ret = nl_get_cable_test_fault_length(evattr, &pair, &cm);
if (ret < 0)
return ret;
printf("%s, fault length: %0.2fm\n",
nl_pair2txt(pair), (float)cm / 100);
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:
printf("Cable test started for device %s.\n",
nlctx->devname);
break;
case ETHTOOL_A_CABLE_TEST_NTF_STATUS_COMPLETED:
printf("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;
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
ret = nl_cable_test_process_results(ctx);
return ret;
}