[PATCH ethtool-next v2 3/3] ethtool: Introduce a command to list PHYs

Maxime Chevallier
2024-08-28T15:25:22Z

It is now possible to list all Ethernet PHYs that are present behind a
given interface, since the following linux commit :
63d5eaf35ac3 ("net: ethtool: Introduce a command to list PHYs on an interface")

This command relies on the netlink DUMP command to list them, by allowing to
pass an interface name/id as a parameter in the DUMP request to only
list PHYs on one interface.

Therefore, we introduce a new helper function to prepare a interface-filtered
dump request (the filter can be empty, to perform an unfiltered dump),
and then uses it to implement PHY enumeration through the --show-phys
command.

Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
Makefile.am | 1 +
ethtool.8.in | 36 +++++++++++++++
ethtool.c | 5 ++
netlink/extapi.h | 1 +
netlink/nlsock.c | 37 +++++++++++++++
netlink/nlsock.h | 2 +
netlink/phy.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 198 insertions(+)
create mode 100644 netlink/phy.c

diff --git a/Makefile.am b/Makefile.am
index 5a61a9a..862886b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -48,6 +48,7 @@ ethtool_SOURCES += \
netlink/desc-rtnl.c netlink/cable_test.c netlink/tunnels.c \
netlink/plca.c \
netlink/pse-pd.c \
+ netlink/phy.c \
uapi/linux/ethtool_netlink.h \
uapi/linux/netlink.h uapi/linux/genetlink.h \
uapi/linux/rtnetlink.h uapi/linux/if_link.h \
diff --git a/ethtool.8.in b/ethtool.8.in
index a455b5d..816ac22 100644
--- a/ethtool.8.in
+++ b/ethtool.8.in
@@ -547,6 +547,9 @@ ethtool \- query or control network driver and hardware settings
.IR FILE
.RB [ pass
.IR PASS ]
+.HP
+.B ethtool \-\-show\-phys
+.I devname
.
.\" Adjust lines (i.e. full justification) and hyphenate.
.ad
@@ -1822,6 +1825,39 @@ is downloaded to the transceiver module, validated, run and committed.
Optional transceiver module password that might be required as part of the
transceiver module firmware update process.

+.RE
+.TP
+.B \-\-show\-phys
+Show the PHY devices attached to an interface, and the way they link together.
+.RS 4
+.TP
+.B phy_index
+The PHY's index, that identifies it within the network interface. If the
+interface has multiple PHYs, they will each have a unique index on that
+interface. This index can then be used for commands that targets PHYs.
+.TP
+.B drvname
+The name of the driver bound to this PHY device.
+.TP
+.B name
+The PHY's device name, matching the name found in sysfs.
+.TP
+.B downstream_sfp_name
+If the PHY drives an SFP cage, this field contains the name of the associated
+SFP bus.
+.TP
+.B upstream_type \ mac | phy
+Indicates the nature of the device the PHY is attached to.
+.TP
+.B upstream_index
+If the PHY's upstream_type is
+.B phy
+, this field indicates the phy_index of the upstream phy.
+.TP
+.B upstream_sfp_name
+If the PHY is withing an SFP/SFF module, this field contains the name of the
+upstream SFP bus.
+
.SH BUGS
Not supported (in part or whole) on all network drivers.
.SH AUTHOR
diff --git a/ethtool.c b/ethtool.c
index 3bd777e..3591767 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -6254,6 +6254,11 @@ static const struct option args[] = {
.xhelp = " file FILE\n"
" [ pass PASS ]\n"
},
+ {
+ .opts = "--show-phys",
+ .nlfunc = nl_get_phy,
+ .help = "List PHYs"
+ },
{
.opts = "-h|--help",
.no_dev = true,
diff --git a/netlink/extapi.h b/netlink/extapi.h
index c882295..fd99610 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -56,6 +56,7 @@ int nl_set_mm(struct cmd_context *ctx);
int nl_gpse(struct cmd_context *ctx);
int nl_spse(struct cmd_context *ctx);
int nl_flash_module_fw(struct cmd_context *ctx);
+int nl_get_phy(struct cmd_context *ctx);

void nl_monitor_usage(void);

diff --git a/netlink/nlsock.c b/netlink/nlsock.c
index 0b873a3..5450c9b 100644
--- a/netlink/nlsock.c
+++ b/netlink/nlsock.c
@@ -298,6 +298,43 @@ int nlsock_prep_get_request(struct nl_socket *nlsk, unsigned int nlcmd,
return 0;
}

+/**
+ * nlsock_prep_filtered_dump_request() - Initialize a filtered DUMP request
+ * @nlsk: netlink socket
+ * @nlcmd: netlink command
+ * @hdr_attrtype: netlink command header attribute
+ * @flags: netlink command header flags
+ *
+ * Prepare a DUMP request that may include the device index as a filtering
+ * attribute in the header.
+ *
+ * Return: 0 on success, or a negative number on error
+ */
+int nlsock_prep_filtered_dump_request(struct nl_socket *nlsk,
+ unsigned int nlcmd, uint16_t hdr_attrtype,
+ u32 flags)
+{
+ struct nl_context *nlctx = nlsk->nlctx;
+ const char *devname = nlctx->ctx->devname;
+ unsigned int nlm_flags;
+ int ret;
+
+ nlctx->is_dump = true;
+ nlm_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
+
+ if (devname && !strcmp(devname, WILDCARD_DEVNAME))
+ devname = NULL;
+
+ ret = msg_init(nlctx, &nlsk->msgbuff, nlcmd, nlm_flags);
+ if (ret < 0)
+ return ret;
+
+ if (ethnla_fill_header(&nlsk->msgbuff, hdr_attrtype, devname, flags))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
#ifndef TEST_ETHTOOL
/**
* nlsock_sendmsg() - send a netlink message to kernel
diff --git a/netlink/nlsock.h b/netlink/nlsock.h
index b015f86..6a72966 100644
--- a/netlink/nlsock.h
+++ b/netlink/nlsock.h
@@ -38,6 +38,8 @@ int nlsock_init(struct nl_context *nlctx, struct nl_socket **__nlsk,
void nlsock_done(struct nl_socket *nlsk);
int nlsock_prep_get_request(struct nl_socket *nlsk, unsigned int nlcmd,
uint16_t hdr_attrtype, u32 flags);
+int nlsock_prep_filtered_dump_request(struct nl_socket *nlsk, unsigned int nlcmd,
+ uint16_t hdr_attrtype, u32 flags);
ssize_t nlsock_sendmsg(struct nl_socket *nlsk, struct nl_msg_buff *__msgbuff);
int nlsock_send_get_request(struct nl_socket *nlsk, mnl_cb_t cb);
int nlsock_process_reply(struct nl_socket *nlsk, mnl_cb_t reply_cb, void *data);
diff --git a/netlink/phy.c b/netlink/phy.c
new file mode 100644
index 0000000..7578191
--- /dev/null
+++ b/netlink/phy.c
@@ -0,0 +1,116 @@
+/*
+ * phy.c - List PHYs on an interface and their parameters
+ *
+ * Implementation of "ethtool --show-phys <dev>"
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+
+/* PHY_GET / PHY_DUMP */
+
+static const char * phy_upstream_type_to_str(uint8_t upstream_type)
+{
+ switch (upstream_type) {
+ case PHY_UPSTREAM_PHY: return "phy";
+ case PHY_UPSTREAM_MAC: return "mac";
+ default: return "Unknown";
+ }
+}
+
+int phy_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_PHY_MAX + 1] = {};
+ struct nl_context *nlctx = data;
+ DECLARE_ATTR_TB_INFO(tb);
+ uint8_t upstream_type;
+ bool silent;
+ int err_ret;
+ int ret;
+
+ 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_PHY_HEADER]);
+ if (!dev_ok(nlctx))
+ return err_ret;
+
+ if (silent)
+ print_nl();
+
+ open_json_object(NULL);
+
+ print_string(PRINT_ANY, "ifname", "PHY for %s:\n", nlctx->devname);
+
+ show_u32("phy_index", "PHY index: ", tb[ETHTOOL_A_PHY_INDEX]);
+
+ if (tb[ETHTOOL_A_PHY_DRVNAME])
+ print_string(PRINT_ANY, "drvname", "Driver name: %s\n",
+ mnl_attr_get_str(tb[ETHTOOL_A_PHY_DRVNAME]));
+
+ if (tb[ETHTOOL_A_PHY_NAME])
+ print_string(PRINT_ANY, "name", "PHY device name: %s\n",
+ mnl_attr_get_str(tb[ETHTOOL_A_PHY_NAME]));
+
+ if (tb[ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME])
+ print_string(PRINT_ANY, "downstream_sfp_name",
+ "Downstream SFP bus name: %s\n",
+ mnl_attr_get_str(tb[ETHTOOL_A_PHY_DOWNSTREAM_SFP_NAME]));
+
+ if (tb[ETHTOOL_A_PHY_UPSTREAM_TYPE]) {
+ upstream_type = mnl_attr_get_u8(tb[ETHTOOL_A_PHY_UPSTREAM_TYPE]);
+ print_string(PRINT_ANY, "upstream_type", "Upstream type: %s\n",
+ phy_upstream_type_to_str(upstream_type));
+ }
+
+ if (tb[ETHTOOL_A_PHY_UPSTREAM_INDEX])
+ show_u32("upstream_index", "Upstream PHY index: ",
+ tb[ETHTOOL_A_PHY_UPSTREAM_INDEX]);
+
+ if (tb[ETHTOOL_A_PHY_UPSTREAM_SFP_NAME])
+ print_string(PRINT_ANY, "upstream_sfp_name", "Upstream SFP name: %s\n",
+ mnl_attr_get_str(tb[ETHTOOL_A_PHY_UPSTREAM_SFP_NAME]));
+
+ if (!silent)
+ print_nl();
+
+ close_json_object();
+
+ return MNL_CB_OK;
+
+ close_json_object();
+ return err_ret;
+}
+
+int nl_get_phy(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_PHY_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_filtered_dump_request(nlsk, ETHTOOL_MSG_PHY_GET,
+ ETHTOOL_A_PHY_HEADER, 0);
+ if (ret)
+ return ret;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, phy_reply_cb);
+ delete_json_obj();
+ return ret;
+}
--
2.45.2