From: "Björn Töpel" <bjorn@kernel.org>
To: netdev@vger.kernel.org, Donald Hunter <donald.hunter@gmail.com>,
Jakub Kicinski <kuba@kernel.org>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
Saeed Mahameed <saeedm@nvidia.com>,
Tariq Toukan <tariqt@nvidia.com>,
Leon Romanovsky <leon@kernel.org>,
Andrew Lunn <andrew+netdev@lunn.ch>
Cc: "Björn Töpel" <bjorn@kernel.org>,
"Maxime Chevallier" <maxime.chevallier@bootlin.com>,
"Andrew Lunn" <andrew@lunn.ch>,
"Michael Chan" <michael.chan@broadcom.com>,
"Hariprasad Kelam" <hkelam@marvell.com>,
"Ido Schimmel" <idosch@nvidia.com>,
"Danielle Ratson" <danieller@nvidia.com>,
linux-kernel@vger.kernel.org, linux-rdma@vger.kernel.org,
"Russell King" <linux@armlinux.org.uk>
Subject: [RFC net-next v2 3/6] ethtool: add CMIS loopback helpers for module loopback control
Date: Sun, 8 Mar 2026 13:40:09 +0100 [thread overview]
Message-ID: <20260308124016.3134012-4-bjorn@kernel.org> (raw)
In-Reply-To: <20260308124016.3134012-1-bjorn@kernel.org>
Add CMIS loopback functions and wire them into loopback.c for the
MODULE component:
- ethtool_cmis_get_loopback(): reads Page 13h capabilities and
current state, appends one entry per supported loopback point
("cmis-host" and/or "cmis-media").
- ethtool_cmis_set_loopback_one(): resolves name to a pair of control
byte indices, validates direction, and writes the Page 13h control
bytes (0xFF = all lanes on, 0x00 = off).
Directions are mutually exclusive: switching from near-end to far-end
first disables the active direction in a separate EEPROM write, then
enables the new one. Requesting multiple direction flags is rejected.
CMIS register mapping (Page 13h, Bytes 180-183):
- MODULE, "cmis-host", near-end -> Host Side Input (Byte 183)
- MODULE, "cmis-host", far-end -> Host Side Output (Byte 182)
- MODULE, "cmis-media", near-end -> Media Side Input (Byte 181)
- MODULE, "cmis-media", far-end -> Media Side Output (Byte 180)
The helpers work entirely over get/set_module_eeprom_by_page, so any
driver with EEPROM page access gets module loopback without new
ethtool_ops or driver changes. SET is rejected when firmware flashing
is in progress or the interface is UP.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
net/ethtool/Makefile | 2 +-
net/ethtool/cmis_loopback.c | 338 ++++++++++++++++++++++++++++++++++++
net/ethtool/loopback.c | 4 +-
net/ethtool/netlink.h | 5 +
4 files changed, 347 insertions(+), 2 deletions(-)
create mode 100644 net/ethtool/cmis_loopback.c
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index ef534b55d724..2f821c7875e1 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -9,4 +9,4 @@ ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o rss.o \
channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \
tunnels.o fec.o eeprom.o stats.o phc_vclocks.o mm.o \
module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o \
- phy.o tsconfig.o mse.o loopback.o
+ phy.o tsconfig.o mse.o loopback.o cmis_loopback.o
diff --git a/net/ethtool/cmis_loopback.c b/net/ethtool/cmis_loopback.c
new file mode 100644
index 000000000000..2114c85f507f
--- /dev/null
+++ b/net/ethtool/cmis_loopback.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/* CMIS loopback helpers for drivers implementing ethtool
+ * get/set_loopback.
+ *
+ * Maps the generic ethtool loopback model to CMIS Page 13h registers
+ * (CMIS 5.3, Table 8-128).
+ *
+ * Capabilities are read from Page 13h Byte 128, with Page 13h
+ * availability checked via Page 01h Byte 142 bit 5.
+ */
+
+#include <linux/ethtool.h>
+#include <linux/sfp.h>
+
+#include "common.h"
+#include "module_fw.h"
+#include "cmis.h"
+
+/* CMIS Page 00h, Byte 0: Physical module identifier */
+#define CMIS_PHYS_ID_PAGE 0x00
+#define CMIS_PHYS_ID_OFFSET 0x00
+
+/* CMIS Page 01h, Byte 142: Diagnostic Pages Support */
+#define CMIS_DIAG_SUPPORT_PAGE 0x01
+#define CMIS_DIAG_SUPPORT_OFFSET 0x8E
+#define CMIS_DIAG_PAGE13_BIT BIT(5)
+
+/* CMIS Page 13h, Byte 128: Loopback Capability Advertisement */
+#define CMIS_LB_CAPS_PAGE 0x13
+#define CMIS_LB_CAPS_OFFSET 0x80
+#define CMIS_LB_CAP_MEDIA_OUTPUT BIT(0)
+#define CMIS_LB_CAP_MEDIA_INPUT BIT(1)
+#define CMIS_LB_CAP_HOST_OUTPUT BIT(2)
+#define CMIS_LB_CAP_HOST_INPUT BIT(3)
+
+/* CMIS Page 13h, Bytes 180-183: Per-Lane Loopback Control
+ * Byte 180 (0xB4): Media Side Output -> MODULE, "cmis-media", far-end
+ * Byte 181 (0xB5): Media Side Input -> MODULE, "cmis-media", near-end
+ * Byte 182 (0xB6): Host Side Output -> MODULE, "cmis-host", far-end
+ * Byte 183 (0xB7): Host Side Input -> MODULE, "cmis-host", near-end
+ */
+#define CMIS_LB_CTRL_PAGE 0x13
+#define CMIS_LB_CTRL_OFFSET 0xB4
+#define CMIS_LB_CTRL_LEN 4
+#define CMIS_LB_CTRL_IDX_MEDIA_OUTPUT 0
+#define CMIS_LB_CTRL_IDX_MEDIA_INPUT 1
+#define CMIS_LB_CTRL_IDX_HOST_OUTPUT 2
+#define CMIS_LB_CTRL_IDX_HOST_INPUT 3
+
+#define CMIS_LB_NAME_HOST "cmis-host"
+#define CMIS_LB_NAME_MEDIA "cmis-media"
+
+static bool cmis_is_module(u8 phys_id)
+{
+ switch (phys_id) {
+ case SFF8024_ID_QSFP_DD:
+ case SFF8024_ID_OSFP:
+ case SFF8024_ID_DSFP:
+ case SFF8024_ID_QSFP_PLUS_CMIS:
+ case SFF8024_ID_SFP_DD_CMIS:
+ case SFF8024_ID_SFP_PLUS_CMIS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * cmis_loopback_caps - Read CMIS loopback capability mask
+ * @dev: Network device
+ *
+ * Return: >0 capability bitmask, 0 if not a CMIS module or no Page
+ * 13h, negative errno on failure.
+ */
+static int cmis_loopback_caps(struct net_device *dev)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ struct ethtool_module_eeprom page = {};
+ int ret;
+ u8 val;
+
+ if (!ops->get_module_eeprom_by_page)
+ return 0;
+
+ /* Read physical identifier */
+ ethtool_cmis_page_init(&page, CMIS_PHYS_ID_PAGE,
+ CMIS_PHYS_ID_OFFSET, sizeof(val));
+ page.data = &val;
+ ret = ops->get_module_eeprom_by_page(dev, &page, NULL);
+ if (ret < 0)
+ return ret;
+ if (!cmis_is_module(val))
+ return 0;
+
+ /* Check Page 13h availability */
+ ethtool_cmis_page_init(&page, CMIS_DIAG_SUPPORT_PAGE,
+ CMIS_DIAG_SUPPORT_OFFSET, sizeof(val));
+ page.data = &val;
+ ret = ops->get_module_eeprom_by_page(dev, &page, NULL);
+ if (ret < 0)
+ return ret;
+ if (!(val & CMIS_DIAG_PAGE13_BIT))
+ return 0;
+
+ /* Read capability byte */
+ ethtool_cmis_page_init(&page, CMIS_LB_CAPS_PAGE,
+ CMIS_LB_CAPS_OFFSET, sizeof(val));
+ page.data = &val;
+ ret = ops->get_module_eeprom_by_page(dev, &page, NULL);
+ if (ret < 0)
+ return ret;
+
+ return val & (CMIS_LB_CAP_MEDIA_OUTPUT | CMIS_LB_CAP_MEDIA_INPUT |
+ CMIS_LB_CAP_HOST_OUTPUT | CMIS_LB_CAP_HOST_INPUT);
+}
+
+/**
+ * ethtool_cmis_get_loopback - Append CMIS module loopback entries to cfg
+ * @dev: Network device with get_module_eeprom_by_page support
+ * @cfg: Loopback configuration; MODULE entries are appended
+ *
+ * Reads CMIS module capabilities and current loopback state from Page
+ * 13h, then appends one entry for each supported loopback point.
+ * Returns 0 without adding entries if the module is not CMIS or does
+ * not advertise loopback support.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int ethtool_cmis_get_loopback(struct net_device *dev,
+ struct ethtool_loopback_cfg *cfg)
+{
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ struct ethtool_module_eeprom page = {};
+ struct ethtool_loopback_entry host = {
+ .component = ETHTOOL_LOOPBACK_COMPONENT_MODULE,
+ .name = CMIS_LB_NAME_HOST,
+ };
+ struct ethtool_loopback_entry media = {
+ .component = ETHTOOL_LOOPBACK_COMPONENT_MODULE,
+ .name = CMIS_LB_NAME_MEDIA,
+ };
+ int caps, ret, h = 0, m = 0;
+ u8 ctrl[CMIS_LB_CTRL_LEN];
+
+ if (dev->ethtool->module_fw_flash_in_progress)
+ return -EBUSY;
+
+ caps = cmis_loopback_caps(dev);
+ if (caps <= 0)
+ return caps;
+
+ /* Read all four control bytes in one access */
+ ethtool_cmis_page_init(&page, CMIS_LB_CTRL_PAGE,
+ CMIS_LB_CTRL_OFFSET, sizeof(ctrl));
+ page.data = ctrl;
+ ret = ops->get_module_eeprom_by_page(dev, &page, NULL);
+ if (ret < 0)
+ return ret;
+
+ if (caps & CMIS_LB_CAP_HOST_INPUT) {
+ h = 1;
+ host.supported |= ETHTOOL_LOOPBACK_DIRECTION_NEAR_END;
+ if (ctrl[CMIS_LB_CTRL_IDX_HOST_INPUT])
+ host.direction |= ETHTOOL_LOOPBACK_DIRECTION_NEAR_END;
+ }
+ if (caps & CMIS_LB_CAP_HOST_OUTPUT) {
+ h = 1;
+ host.supported |= ETHTOOL_LOOPBACK_DIRECTION_FAR_END;
+ if (ctrl[CMIS_LB_CTRL_IDX_HOST_OUTPUT])
+ host.direction |= ETHTOOL_LOOPBACK_DIRECTION_FAR_END;
+ }
+ if (caps & CMIS_LB_CAP_MEDIA_INPUT) {
+ m = 1;
+ media.supported |= ETHTOOL_LOOPBACK_DIRECTION_NEAR_END;
+ if (ctrl[CMIS_LB_CTRL_IDX_MEDIA_INPUT])
+ media.direction |= ETHTOOL_LOOPBACK_DIRECTION_NEAR_END;
+ }
+ if (caps & CMIS_LB_CAP_MEDIA_OUTPUT) {
+ m = 1;
+ media.supported |= ETHTOOL_LOOPBACK_DIRECTION_FAR_END;
+ if (ctrl[CMIS_LB_CTRL_IDX_MEDIA_OUTPUT])
+ media.direction |= ETHTOOL_LOOPBACK_DIRECTION_FAR_END;
+ }
+
+ if (cfg->n_entries + h + m > ETHTOOL_LOOPBACK_MAX_ENTRIES)
+ return -ENOMEM;
+
+ if (h) {
+ memcpy(&cfg->entries[cfg->n_entries], &host, sizeof(host));
+ cfg->n_entries++;
+ }
+
+ if (m) {
+ memcpy(&cfg->entries[cfg->n_entries], &media, sizeof(media));
+ cfg->n_entries++;
+ }
+
+ return 0;
+}
+
+/**
+ * ethtool_cmis_set_loopback_one - Apply one MODULE loopback entry to CMIS
+ * @dev: Network device with get/set_module_eeprom_by_page support
+ * @entry: Loopback entry to apply (must be MODULE component)
+ * @extack: Netlink extended ack for error reporting
+ *
+ * Matches the entry against CMIS loopback points by name and
+ * direction, then reads, modifies, and writes the corresponding Page
+ * 13h control byte (0xFF for all-lanes enable, 0x00 for disable).
+ *
+ * When disabling (direction == 0), all loopback points matching the
+ * name are disabled regardless of their direction. When enabling,
+ * only the specific direction is activated.
+ *
+ * Return: 1 if hardware state changed, 0 if already in requested state,
+ * negative errno on failure.
+ */
+int ethtool_cmis_set_loopback_one(struct net_device *dev,
+ const struct ethtool_loopback_entry *entry,
+ struct netlink_ext_ack *extack)
+{
+ struct ethtool_module_eeprom page = {};
+ u8 ctrl[CMIS_LB_CTRL_LEN];
+ int near_idx, far_idx;
+ u8 near_cap, far_cap;
+ bool mod = false;
+ int caps, ret;
+
+ if (!dev->ethtool_ops->set_module_eeprom_by_page) {
+ NL_SET_ERR_MSG(extack,
+ "Module EEPROM write access not supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (dev->ethtool->module_fw_flash_in_progress) {
+ NL_SET_ERR_MSG(extack,
+ "Module firmware flashing is in progress");
+ return -EBUSY;
+ }
+
+ if (dev->flags & IFF_UP) {
+ NL_SET_ERR_MSG(extack,
+ "Netdevice is up, module loopback change not permitted");
+ return -EBUSY;
+ }
+
+ if (entry->direction && !is_power_of_2(entry->direction)) {
+ NL_SET_ERR_MSG(extack,
+ "Only one loopback direction may be enabled at a time");
+ return -EINVAL;
+ }
+
+ if (strcmp(entry->name, CMIS_LB_NAME_HOST) == 0) {
+ near_idx = CMIS_LB_CTRL_IDX_HOST_INPUT;
+ far_idx = CMIS_LB_CTRL_IDX_HOST_OUTPUT;
+ near_cap = CMIS_LB_CAP_HOST_INPUT;
+ far_cap = CMIS_LB_CAP_HOST_OUTPUT;
+ } else if (strcmp(entry->name, CMIS_LB_NAME_MEDIA) == 0) {
+ near_idx = CMIS_LB_CTRL_IDX_MEDIA_INPUT;
+ far_idx = CMIS_LB_CTRL_IDX_MEDIA_OUTPUT;
+ near_cap = CMIS_LB_CAP_MEDIA_INPUT;
+ far_cap = CMIS_LB_CAP_MEDIA_OUTPUT;
+ } else {
+ NL_SET_ERR_MSG(extack, "Unknown CMIS loopback name");
+ return -EINVAL;
+ }
+
+ caps = cmis_loopback_caps(dev);
+ if (caps < 0)
+ return caps;
+ if (!caps) {
+ NL_SET_ERR_MSG(extack, "Module does not support CMIS loopback");
+ return -EOPNOTSUPP;
+ }
+
+ /* Read current control bytes */
+ ethtool_cmis_page_init(&page, CMIS_LB_CTRL_PAGE,
+ CMIS_LB_CTRL_OFFSET, sizeof(ctrl));
+ page.data = ctrl;
+ ret = dev->ethtool_ops->get_module_eeprom_by_page(dev, &page, NULL);
+ if (ret < 0)
+ return ret;
+
+ if (!entry->direction) {
+ /* Disable both directions */
+ if (ctrl[near_idx]) {
+ ctrl[near_idx] = 0x00;
+ mod = true;
+ }
+ if (ctrl[far_idx]) {
+ ctrl[far_idx] = 0x00;
+ mod = true;
+ }
+ } else {
+ int enable_idx, disable_idx;
+ u8 enable_cap;
+
+ if (entry->direction & ETHTOOL_LOOPBACK_DIRECTION_NEAR_END) {
+ enable_idx = near_idx;
+ enable_cap = near_cap;
+ disable_idx = far_idx;
+ } else {
+ enable_idx = far_idx;
+ enable_cap = far_cap;
+ disable_idx = near_idx;
+ }
+
+ if (!(caps & enable_cap)) {
+ NL_SET_ERR_MSG(extack,
+ "Loopback mode not supported by module");
+ return -EOPNOTSUPP;
+ }
+
+ /* Disable opposite direction first (mutual exclusivity) */
+ if (ctrl[disable_idx]) {
+ ctrl[disable_idx] = 0x00;
+ ret = dev->ethtool_ops->set_module_eeprom_by_page(dev,
+ &page,
+ extack);
+ if (ret < 0)
+ return ret;
+ mod = true;
+ }
+
+ if (ctrl[enable_idx] != 0xFF) {
+ ctrl[enable_idx] = 0xFF;
+ mod = true;
+ }
+ }
+
+ if (!mod)
+ return 0;
+
+ ret = dev->ethtool_ops->set_module_eeprom_by_page(dev, &page, extack);
+
+ return ret < 0 ? ret : 1;
+}
diff --git a/net/ethtool/loopback.c b/net/ethtool/loopback.c
index 1c6d27857f8a..8a6f14f4b8cb 100644
--- a/net/ethtool/loopback.c
+++ b/net/ethtool/loopback.c
@@ -37,7 +37,7 @@ const struct nla_policy ethnl_loopback_get_policy[] = {
static int loopback_get_entries(struct net_device *dev,
struct ethtool_loopback_cfg *cfg)
{
- return 0;
+ return ethtool_cmis_get_loopback(dev, cfg);
}
static int loopback_prepare_data(const struct ethnl_req_info *req_base,
@@ -181,6 +181,8 @@ static int loopback_set_one(struct net_device *dev,
struct netlink_ext_ack *extack)
{
switch (entry->component) {
+ case ETHTOOL_LOOPBACK_COMPONENT_MODULE:
+ return ethtool_cmis_set_loopback_one(dev, entry, extack);
default:
return -EOPNOTSUPP;
}
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 5660ce494916..707363462c12 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -517,6 +517,11 @@ int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ethnl_tsinfo_done(struct netlink_callback *cb);
int ethnl_rss_create_doit(struct sk_buff *skb, struct genl_info *info);
int ethnl_rss_delete_doit(struct sk_buff *skb, struct genl_info *info);
+int ethtool_cmis_get_loopback(struct net_device *dev,
+ struct ethtool_loopback_cfg *cfg);
+int ethtool_cmis_set_loopback_one(struct net_device *dev,
+ const struct ethtool_loopback_entry *entry,
+ struct netlink_ext_ack *extack);
extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];
--
2.53.0
next prev parent reply other threads:[~2026-03-08 12:40 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-08 12:40 [RFC net-next v2 0/6] ethtool: Generic loopback support Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 1/6] ethtool: Add loopback netlink UAPI definitions Björn Töpel
2026-03-09 14:16 ` Maxime Chevallier
2026-03-09 14:59 ` Björn Töpel
2026-03-09 16:45 ` Andrew Lunn
2026-03-10 10:23 ` Maxime Chevallier
2026-03-10 13:56 ` Andrew Lunn
2026-03-08 12:40 ` [RFC net-next v2 2/6] ethtool: Add loopback GET/SET netlink implementation Björn Töpel
2026-03-09 7:34 ` Maxime Chevallier
2026-03-09 8:21 ` Björn Töpel
2026-03-09 14:51 ` Björn Töpel
2026-03-09 16:14 ` Maxime Chevallier
2026-03-08 12:40 ` Björn Töpel [this message]
2026-03-08 12:40 ` [RFC net-next v2 4/6] selftests: drv-net: Add loopback driver test Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 5/6] netdevsim: Add module EEPROM simulation via debugfs Björn Töpel
2026-03-08 12:40 ` [RFC net-next v2 6/6] selftests: drv-net: Add CMIS loopback netdevsim test Björn Töpel
2026-03-09 13:49 ` [RFC net-next v2 0/6] ethtool: Generic loopback support Naveen Mamindlapalli
2026-03-09 14:55 ` Björn Töpel
2026-03-10 7:35 ` Naveen Mamindlapalli
2026-03-10 14:00 ` Andrew Lunn
2026-03-11 5:59 ` Naveen Mamindlapalli
2026-03-11 12:32 ` Andrew Lunn
2026-03-11 16:52 ` Russell King (Oracle)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260308124016.3134012-4-bjorn@kernel.org \
--to=bjorn@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=andrew@lunn.ch \
--cc=danieller@nvidia.com \
--cc=davem@davemloft.net \
--cc=donald.hunter@gmail.com \
--cc=edumazet@google.com \
--cc=hkelam@marvell.com \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=kuba@kernel.org \
--cc=leon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=maxime.chevallier@bootlin.com \
--cc=michael.chan@broadcom.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=saeedm@nvidia.com \
--cc=tariqt@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox