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: 24+ 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-09 3:56 ` kernel test robot
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.