From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 43C4E3EF670; Wed, 25 Mar 2026 14:51:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774450266; cv=none; b=ll/Gj65t0QVtQBVbXleFCwvf8ffOJNo0OFEWFsPJupp5mNlrBXo7Qc1mxHxhEWHrl7kKiRhyV8nV8dmw3WeuxCSDuhsKg6287BPdCpqJIYmwUIjhE0rf+1du4uwPuM3AGytGoxQqBeWgwoMhCrKd4x+Q/On91+/2bm5nyEOJjxo= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774450266; c=relaxed/simple; bh=HeSYe4OnBC7qbdqBXKOewBFNrxyLzh/TVDa3esBsYcE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Hbwn96eVhif+gapk7XOn/zJ/w3AsEbc2uwWWgXdFieGgmZYhshE6EGTTBg00mAtchMS/lWmtgiyo13ZzhFgoQHOQtGgvcKoUFr0E1G8b5+PvtlY+mp2D19Vp7soR7JH0r767ebl/g/MuiPkaY+VB3HVHTHXzkRCUKBS0R4n82vY= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=PRX1nEAL; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="PRX1nEAL" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 90027C4CEF7; Wed, 25 Mar 2026 14:50:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1774450265; bh=HeSYe4OnBC7qbdqBXKOewBFNrxyLzh/TVDa3esBsYcE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PRX1nEALv8cCHAiWDuXOKpObJxe8qXzI2yJfWyXqYFOc6vQbW4Xwbv16etLHPRNL5 p48UCmiFvManohq5fXgKTvLGBMSWCnHXW7DyX0SWmFCFtcelt+fwVs1NMbu5W2fYXg vHBWbzFfZ7jdbVuPG517Er7Dep0CrhOIujEqQwYWmPo3Nux7POQpy0Izox3V6DNfKC eD3GWRJwiKApveU+4ucc8oocDgQVLDCikfQAs1nbD9GkHRgLz6a2uKzadCXRTeXDwu J0VB3MOJTZk8HeL+MF7bHRd/b6TM5KIrJGjFMVcoudWcKSXP7ygUtUnZpgw3w4N+VI +evqDe+4EBtsg== From: =?UTF-8?q?Bj=C3=B6rn=20T=C3=B6pel?= To: netdev@vger.kernel.org, "David S. Miller" , Andrew Lunn , Donald Hunter , Eric Dumazet , Jakub Kicinski , Maxime Chevallier , Naveen Mamindlapalli , Paolo Abeni , Simon Horman Cc: =?UTF-8?q?Bj=C3=B6rn=20T=C3=B6pel?= , Danielle Ratson , Hariprasad Kelam , Ido Schimmel , Kory Maincent , Leon Romanovsky , Michael Chan , Oleksij Rempel , Pavan Chebbi , Piergiorgio Beruto , Russell King , Saeed Mahameed , Shuah Khan , Tariq Toukan , Willem de Bruijn , Kees Cook , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next v2 05/12] ethtool: Add CMIS loopback helpers for module loopback control Date: Wed, 25 Mar 2026 15:50:12 +0100 Message-ID: <20260325145022.2607545-6-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260325145022.2607545-1-bjorn@kernel.org> References: <20260325145022.2607545-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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_get_loopback_by_index(): used to enumerate what the module supports. - ethtool_cmis_set_loopback(): 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 local to remote 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", local -> Host Side Input (Byte 183) - MODULE, "cmis-host", remote -> Host Side Output (Byte 182) - MODULE, "cmis-media", local -> Media Side Input (Byte 181) - MODULE, "cmis-media", remote -> 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 --- net/ethtool/Makefile | 2 +- net/ethtool/cmis_loopback.c | 407 ++++++++++++++++++++++++++++++++++++ net/ethtool/loopback.c | 6 +- net/ethtool/netlink.h | 8 + 4 files changed, 421 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..c4e8808ceaff --- /dev/null +++ b/net/ethtool/cmis_loopback.c @@ -0,0 +1,407 @@ +// 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 +#include + +#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", remote + * Byte 181 (0xb5): Media Side Input -> MODULE, "cmis-media", local + * Byte 182 (0xb6): Host Side Output -> MODULE, "cmis-host", remote + * Byte 183 (0xb7): Host Side Input -> MODULE, "cmis-host", local + */ +#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); +} + +/** + * cmis_loopback_read - Read CMIS loopback capabilities and build entries + * @dev: Network device with get_module_eeprom_by_page support + * @host: Output host loopback entry (populated if host caps exist) + * @media: Output media loopback entry (populated if media caps exist) + * @has_host: Set to true if host loopback is supported + * @has_media: Set to true if media loopback is supported + * + * Common helper that reads CMIS caps and control bytes, then populates + * the host and media entries with current state. + * + * Return: 0 on success, -EOPNOTSUPP if no CMIS loopback support, + * negative errno on failure. + */ +static int cmis_loopback_read(struct net_device *dev, + struct ethtool_loopback_entry *host, + struct ethtool_loopback_entry *media, + bool *has_host, bool *has_media) +{ + const struct ethtool_ops *ops = dev->ethtool_ops; + struct ethtool_module_eeprom page = {}; + u8 ctrl[CMIS_LB_CTRL_LEN]; + int caps, ret; + + *has_host = false; + *has_media = false; + + if (dev->ethtool->module_fw_flash_in_progress) + return -EBUSY; + + caps = cmis_loopback_caps(dev); + if (caps <= 0) + return caps ? caps : -EOPNOTSUPP; + + 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; + + memset(host, 0, sizeof(*host)); + host->component = ETHTOOL_LOOPBACK_COMPONENT_MODULE; + strscpy(host->name, CMIS_LB_NAME_HOST, sizeof(host->name)); + + memset(media, 0, sizeof(*media)); + media->component = ETHTOOL_LOOPBACK_COMPONENT_MODULE; + strscpy(media->name, CMIS_LB_NAME_MEDIA, sizeof(media->name)); + + if (caps & CMIS_LB_CAP_HOST_INPUT) { + *has_host = true; + host->supported |= ETHTOOL_LOOPBACK_DIRECTION_LOCAL; + if (ctrl[CMIS_LB_CTRL_IDX_HOST_INPUT]) + host->direction |= ETHTOOL_LOOPBACK_DIRECTION_LOCAL; + } + if (caps & CMIS_LB_CAP_HOST_OUTPUT) { + *has_host = true; + host->supported |= ETHTOOL_LOOPBACK_DIRECTION_REMOTE; + if (ctrl[CMIS_LB_CTRL_IDX_HOST_OUTPUT]) + host->direction |= ETHTOOL_LOOPBACK_DIRECTION_REMOTE; + } + if (caps & CMIS_LB_CAP_MEDIA_INPUT) { + *has_media = true; + media->supported |= ETHTOOL_LOOPBACK_DIRECTION_LOCAL; + if (ctrl[CMIS_LB_CTRL_IDX_MEDIA_INPUT]) + media->direction |= ETHTOOL_LOOPBACK_DIRECTION_LOCAL; + } + if (caps & CMIS_LB_CAP_MEDIA_OUTPUT) { + *has_media = true; + media->supported |= ETHTOOL_LOOPBACK_DIRECTION_REMOTE; + if (ctrl[CMIS_LB_CTRL_IDX_MEDIA_OUTPUT]) + media->direction |= ETHTOOL_LOOPBACK_DIRECTION_REMOTE; + } + + return 0; +} + +/** + * ethtool_cmis_get_loopback_by_index - Enumerate CMIS loopback entry by index + * @dev: Network device with get_module_eeprom_by_page support + * @index: Zero-based index of the loopback entry to retrieve + * @entry: Output loopback entry + * + * Used by the dump infrastructure to iterate one entry at a time. + * + * Return: 0 on success, -EOPNOTSUPP if the index is out of range or + * no CMIS loopback support, negative errno on failure. + */ +int ethtool_cmis_get_loopback_by_index(struct net_device *dev, u32 index, + struct ethtool_loopback_entry *entry) +{ + struct ethtool_loopback_entry host, media; + bool has_host, has_media; + u32 cur = 0; + int ret; + + ret = cmis_loopback_read(dev, &host, &media, &has_host, &has_media); + if (ret) + return ret; + + if (has_host) { + if (cur == index) { + memcpy(entry, &host, sizeof(*entry)); + return 0; + } + cur++; + } + + if (has_media) { + if (cur == index) { + memcpy(entry, &media, sizeof(*entry)); + return 0; + } + } + + return -EOPNOTSUPP; +} + +/** + * ethtool_cmis_get_loopback - Look up CMIS loopback entry by name + * @dev: Network device with get_module_eeprom_by_page support + * @name: Loopback point name ("cmis-host" or "cmis-media") + * @entry: Output loopback entry + * + * Used by doit requests to look up a specific loopback point. + * + * Return: 0 on success, -EOPNOTSUPP if name doesn't match or no CMIS + * support, negative errno on failure. + */ +int ethtool_cmis_get_loopback(struct net_device *dev, + const char *name, + struct ethtool_loopback_entry *entry) +{ + struct ethtool_loopback_entry host, media; + bool has_host, has_media; + int ret; + + ret = cmis_loopback_read(dev, &host, &media, &has_host, &has_media); + if (ret) + return ret; + + if (has_host && !strcmp(name, CMIS_LB_NAME_HOST)) { + memcpy(entry, &host, sizeof(*entry)); + return 0; + } + + if (has_media && !strcmp(name, CMIS_LB_NAME_MEDIA)) { + memcpy(entry, &media, sizeof(*entry)); + return 0; + } + + return -EOPNOTSUPP; +} + +/** + * ethtool_cmis_set_loopback - 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(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 local_idx, remote_idx; + u8 local_cap, remote_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)) { + local_idx = CMIS_LB_CTRL_IDX_HOST_INPUT; + remote_idx = CMIS_LB_CTRL_IDX_HOST_OUTPUT; + local_cap = CMIS_LB_CAP_HOST_INPUT; + remote_cap = CMIS_LB_CAP_HOST_OUTPUT; + } else if (!strcmp(entry->name, CMIS_LB_NAME_MEDIA)) { + local_idx = CMIS_LB_CTRL_IDX_MEDIA_INPUT; + remote_idx = CMIS_LB_CTRL_IDX_MEDIA_OUTPUT; + local_cap = CMIS_LB_CAP_MEDIA_INPUT; + remote_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[local_idx]) { + ctrl[local_idx] = 0x00; + mod = true; + } + if (ctrl[remote_idx]) { + ctrl[remote_idx] = 0x00; + mod = true; + } + } else { + int enable_idx, disable_idx; + u8 enable_cap; + + if (entry->direction & ETHTOOL_LOOPBACK_DIRECTION_LOCAL) { + enable_idx = local_idx; + enable_cap = local_cap; + disable_idx = remote_idx; + } else { + enable_idx = remote_idx; + enable_cap = remote_cap; + disable_idx = local_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 03b86662dc0f..2d0ad62ce42f 100644 --- a/net/ethtool/loopback.c +++ b/net/ethtool/loopback.c @@ -88,6 +88,8 @@ static int loopback_get(struct net_device *dev, struct ethtool_loopback_entry *entry) { switch (component) { + case ETHTOOL_LOOPBACK_COMPONENT_MODULE: + return ethtool_cmis_get_loopback(dev, name, entry); default: return -EOPNOTSUPP; } @@ -96,7 +98,7 @@ static int loopback_get(struct net_device *dev, static int loopback_get_by_index(struct net_device *dev, u32 index, struct ethtool_loopback_entry *entry) { - return -EOPNOTSUPP; + return ethtool_cmis_get_loopback_by_index(dev, index, entry); } static int loopback_prepare_data(const struct ethnl_req_info *req_base, @@ -233,6 +235,8 @@ static int __loopback_set(struct net_device *dev, struct netlink_ext_ack *extack) { switch (entry->component) { + case ETHTOOL_LOOPBACK_COMPONENT_MODULE: + return ethtool_cmis_set_loopback(dev, entry, extack); default: return -EOPNOTSUPP; } diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h index 693df12eef63..1017d653673d 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -556,6 +556,14 @@ int ethnl_perphy_dump_one_dev(struct sk_buff *skb, struct ethnl_dump_ctx *ctx, unsigned long *pos_sub, const struct genl_info *info); +int ethtool_cmis_get_loopback_by_index(struct net_device *dev, u32 index, + struct ethtool_loopback_entry *entry); +int ethtool_cmis_get_loopback(struct net_device *dev, + const char *name, + struct ethtool_loopback_entry *entry); +int ethtool_cmis_set_loopback(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