From nobody Thu Apr 9 06:36:02 2026 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 C2B71372677; Tue, 10 Mar 2026 10:47:59 +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=1773139679; cv=none; b=Cy7iQBLHA3JPfoUwnjkkM8A9ZV0wknRd8xNEqpmia0pfSJQfc8gMv63lYT0nSZyQ2DOpighEEDaTDdYLImvP9XtkV2/Ojgu/9/aotbHRJYyPwGyBbITe/KXsL5ty0dS7HN1dNweAQFXN3hwZyqENChi6N5/FWOlb84sqSp8U+kU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139679; c=relaxed/simple; bh=JFpaISuWEqbj8p69pQPuKhfzK52I4efiD/t20Kcul/4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=aPEx4ERJaT/66nMP+GtzDOIQQtvhqkKLkXye6KOVaa570s1r+4hadwFyxOyZHbaNdgRlib5VuDfcaTrSZPe7b1GK/DNgQGEzSDl9XAwrBAE5yBZ1NdSpbvmDXSqEBWfTIUp7pUp4efXTa73ZT45YmcZH5K13AfLtOZv5yLpWi4I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=EutdbW2K; 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="EutdbW2K" Received: by smtp.kernel.org (Postfix) with ESMTPSA id ED8BEC19423; Tue, 10 Mar 2026 10:47:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139679; bh=JFpaISuWEqbj8p69pQPuKhfzK52I4efiD/t20Kcul/4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EutdbW2KA38DzfhdRau7XFMC/YW8ntYYueO1787CPAOqJXyb9ghWB7JBsQQiqbx1x qW+bHiLD6+uBxHMyfGbi88BMK8FKWpSBNs9Iu+kpLj4FOTLA6GGT8TLQco302Nbrek W+qEB+raghUeV4MaNT7vdFso824JWzAutfM1u+5zHgi/2EZwmVB1Y+2zqsSWHFJBTH MZHaEjcy2Ja7f4+ckwvdKEZoXHwKph/kaHtIWR7aw/hq+LPcf3uLJcLjUV+5QczPkP B2baEC7AAfgM/47mGCScsZUU5IqlG93h7sLF3gHajltQ2PSNzRjHmgmS2H2FTbIlGl bcXuXtmzNo+jg== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 01/11] ethtool: Add dump_one_dev callback for per-device sub-iteration Date: Tue, 10 Mar 2026 11:47:31 +0100 Message-ID: <20260310104743.907818-2-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The per-PHY specific dump functions share a lot functionality of with the default dumpit infrastructure, but does not share the actual code. By introducing a new sub-iterator function, the two dumpit variants can be folded into one set of functions. Add a new dump_one_dev callback in ethnl_request_ops. When ops->dump_one_dev is set, ethnl_default_start() saves the target device's ifindex for filtered dumps, and ethnl_default_dumpit() delegates per-device iteration to the callback instead of calling ethnl_default_dump_one() directly. No separate start/dumpit/done functions are needed. For the existing per-PHY commands (PSE, PLCA, PHY, MSE), the shared ethnl_perphy_dump_one_dev helper provides the xa_for_each_start loop over the device's PHY topology. This prepares the ethtool infrastructure for other commands that need similar per-device sub-iteration. Suggested-by: Maxime Chevallier Signed-off-by: Bj=C3=B6rn T=C3=B6pel --- net/ethtool/mse.c | 1 + net/ethtool/netlink.c | 261 ++++++++++-------------------------------- net/ethtool/netlink.h | 31 +++++ net/ethtool/phy.c | 1 + net/ethtool/plca.c | 2 + net/ethtool/pse-pd.c | 1 + 6 files changed, 94 insertions(+), 203 deletions(-) diff --git a/net/ethtool/mse.c b/net/ethtool/mse.c index 8cb3fc5e7be4..0f2709b4f7de 100644 --- a/net/ethtool/mse.c +++ b/net/ethtool/mse.c @@ -325,4 +325,5 @@ const struct ethnl_request_ops ethnl_mse_request_ops = =3D { .cleanup_data =3D mse_cleanup_data, .reply_size =3D mse_reply_size, .fill_reply =3D mse_fill_reply, + .dump_one_dev =3D ethnl_perphy_dump_one_dev, }; diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index 6e5f0f4f815a..e740b11a0609 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -345,36 +345,6 @@ int ethnl_multicast(struct sk_buff *skb, struct net_de= vice *dev) =20 /* GET request helpers */ =20 -/** - * struct ethnl_dump_ctx - context structure for generic dumpit() callback - * @ops: request ops of currently processed message type - * @req_info: parsed request header of processed request - * @reply_data: data needed to compose the reply - * @pos_ifindex: saved iteration position - ifindex - * - * These parameters are kept in struct netlink_callback as context preserv= ed - * between iterations. They are initialized by ethnl_default_start() and u= sed - * in ethnl_default_dumpit() and ethnl_default_done(). - */ -struct ethnl_dump_ctx { - const struct ethnl_request_ops *ops; - struct ethnl_req_info *req_info; - struct ethnl_reply_data *reply_data; - unsigned long pos_ifindex; -}; - -/** - * struct ethnl_perphy_dump_ctx - context for dumpit() PHY-aware callbacks - * @ethnl_ctx: generic ethnl context - * @ifindex: For Filtered DUMP requests, the ifindex of the targeted netdev - * @pos_phyindex: iterator position for multi-msg DUMP - */ -struct ethnl_perphy_dump_ctx { - struct ethnl_dump_ctx ethnl_ctx; - unsigned int ifindex; - unsigned long pos_phyindex; -}; - static const struct ethnl_request_ops * ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] =3D { [ETHTOOL_MSG_STRSET_GET] =3D ðnl_strset_request_ops, @@ -428,12 +398,6 @@ static struct ethnl_dump_ctx *ethnl_dump_context(struc= t netlink_callback *cb) return (struct ethnl_dump_ctx *)cb->ctx; } =20 -static struct ethnl_perphy_dump_ctx * -ethnl_perphy_dump_context(struct netlink_callback *cb) -{ - return (struct ethnl_perphy_dump_ctx *)cb->ctx; -} - /** * ethnl_default_parse() - Parse request message * @req_info: pointer to structure to put data into @@ -616,17 +580,41 @@ static int ethnl_default_dumpit(struct sk_buff *skb, struct netlink_callback *cb) { struct ethnl_dump_ctx *ctx =3D ethnl_dump_context(cb); + const struct genl_info *info =3D genl_info_dump(cb); struct net *net =3D sock_net(skb->sk); netdevice_tracker dev_tracker; struct net_device *dev; int ret =3D 0; =20 + if (ctx->ops->dump_one_dev && ctx->ifindex) { + dev =3D netdev_get_by_index(net, ctx->ifindex, &dev_tracker, + GFP_KERNEL); + if (!dev) + return -ENODEV; + + ctx->req_info->dev =3D dev; + ret =3D ctx->ops->dump_one_dev(skb, ctx, &ctx->pos_sub, info); + + if (ret < 0 && ret !=3D -EOPNOTSUPP && likely(skb->len)) + ret =3D skb->len; + + netdev_put(dev, &dev_tracker); + return ret; + } + rcu_read_lock(); for_each_netdev_dump(net, dev, ctx->pos_ifindex) { netdev_hold(dev, &dev_tracker, GFP_ATOMIC); rcu_read_unlock(); =20 - ret =3D ethnl_default_dump_one(skb, dev, ctx, genl_info_dump(cb)); + if (ctx->ops->dump_one_dev) { + ctx->req_info->dev =3D dev; + ret =3D ctx->ops->dump_one_dev(skb, ctx, &ctx->pos_sub, + info); + ctx->req_info->dev =3D NULL; + } else { + ret =3D ethnl_default_dump_one(skb, dev, ctx, info); + } =20 rcu_read_lock(); netdev_put(dev, &dev_tracker); @@ -673,10 +661,13 @@ static int ethnl_default_start(struct netlink_callbac= k *cb) if (ret < 0) goto free_reply_data; if (req_info->dev) { - /* We ignore device specification in dump requests but as the - * same parser as for non-dump (doit) requests is used, it - * would take reference to the device if it finds one - */ + if (ops->dump_one_dev) { + /* Sub-iterator dumps keep track of the dev's ifindex + * so the dumpit handler can grab/release the netdev + * per iteration. + */ + ctx->ifindex =3D req_info->dev->ifindex; + } netdev_put(req_info->dev, &req_info->dev_tracker); req_info->dev =3D NULL; } @@ -696,169 +687,33 @@ static int ethnl_default_start(struct netlink_callba= ck *cb) return ret; } =20 -/* per-PHY ->start() handler for GET requests */ -static int ethnl_perphy_start(struct netlink_callback *cb) +/* Shared dump_one_dev for per-PHY commands (PSE, PLCA, PHY, MSE) */ +int ethnl_perphy_dump_one_dev(struct sk_buff *skb, + struct ethnl_dump_ctx *ctx, + unsigned long *pos_sub, + const struct genl_info *info) { - struct ethnl_perphy_dump_ctx *phy_ctx =3D ethnl_perphy_dump_context(cb); - const struct genl_dumpit_info *info =3D genl_dumpit_info(cb); - struct ethnl_dump_ctx *ctx =3D &phy_ctx->ethnl_ctx; - struct ethnl_reply_data *reply_data; - const struct ethnl_request_ops *ops; - struct ethnl_req_info *req_info; - struct genlmsghdr *ghdr; - int ret; - - BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); - - ghdr =3D nlmsg_data(cb->nlh); - ops =3D ethnl_default_requests[ghdr->cmd]; - if (WARN_ONCE(!ops, "cmd %u has no ethnl_request_ops\n", ghdr->cmd)) - return -EOPNOTSUPP; - req_info =3D kzalloc(ops->req_info_size, GFP_KERNEL); - if (!req_info) - return -ENOMEM; - reply_data =3D kmalloc(ops->reply_data_size, GFP_KERNEL); - if (!reply_data) { - ret =3D -ENOMEM; - goto free_req_info; - } - - /* Unlike per-dev dump, don't ignore dev. The dump handler - * will notice it and dump PHYs from given dev. We only keep track of - * the dev's ifindex, .dumpit() will grab and release the netdev itself. - */ - ret =3D ethnl_default_parse(req_info, &info->info, ops, false); - if (ret < 0) - goto free_reply_data; - if (req_info->dev) { - phy_ctx->ifindex =3D req_info->dev->ifindex; - netdev_put(req_info->dev, &req_info->dev_tracker); - req_info->dev =3D NULL; - } - - ctx->ops =3D ops; - ctx->req_info =3D req_info; - ctx->reply_data =3D reply_data; - ctx->pos_ifindex =3D 0; - - return 0; - -free_reply_data: - kfree(reply_data); -free_req_info: - kfree(req_info); - - return ret; -} - -static int ethnl_perphy_dump_one_dev(struct sk_buff *skb, - struct ethnl_perphy_dump_ctx *ctx, - const struct genl_info *info) -{ - struct ethnl_dump_ctx *ethnl_ctx =3D &ctx->ethnl_ctx; - struct net_device *dev =3D ethnl_ctx->req_info->dev; + struct net_device *dev =3D ctx->req_info->dev; struct phy_device_node *pdn; int ret; =20 if (!dev->link_topo) return 0; =20 - xa_for_each_start(&dev->link_topo->phys, ctx->pos_phyindex, pdn, - ctx->pos_phyindex) { - ethnl_ctx->req_info->phy_index =3D ctx->pos_phyindex; + xa_for_each_start(&dev->link_topo->phys, *pos_sub, pdn, + *pos_sub) { + ctx->req_info->phy_index =3D *pos_sub; =20 /* We can re-use the original dump_one as ->prepare_data in * commands use ethnl_req_get_phydev(), which gets the PHY from * the req_info->phy_index */ - ret =3D ethnl_default_dump_one(skb, dev, ethnl_ctx, info); + ret =3D ethnl_default_dump_one(skb, dev, ctx, info); if (ret) return ret; } =20 - ctx->pos_phyindex =3D 0; - - return 0; -} - -static int ethnl_perphy_dump_all_dev(struct sk_buff *skb, - struct ethnl_perphy_dump_ctx *ctx, - const struct genl_info *info) -{ - struct ethnl_dump_ctx *ethnl_ctx =3D &ctx->ethnl_ctx; - struct net *net =3D sock_net(skb->sk); - netdevice_tracker dev_tracker; - struct net_device *dev; - int ret =3D 0; - - rcu_read_lock(); - for_each_netdev_dump(net, dev, ethnl_ctx->pos_ifindex) { - netdev_hold(dev, &dev_tracker, GFP_ATOMIC); - rcu_read_unlock(); - - /* per-PHY commands use ethnl_req_get_phydev(), which needs the - * net_device in the req_info - */ - ethnl_ctx->req_info->dev =3D dev; - ret =3D ethnl_perphy_dump_one_dev(skb, ctx, info); - - rcu_read_lock(); - netdev_put(dev, &dev_tracker); - ethnl_ctx->req_info->dev =3D NULL; - - if (ret < 0 && ret !=3D -EOPNOTSUPP) { - if (likely(skb->len)) - ret =3D skb->len; - break; - } - ret =3D 0; - } - rcu_read_unlock(); - - return ret; -} - -/* per-PHY ->dumpit() handler for GET requests. */ -static int ethnl_perphy_dumpit(struct sk_buff *skb, - struct netlink_callback *cb) -{ - struct ethnl_perphy_dump_ctx *ctx =3D ethnl_perphy_dump_context(cb); - const struct genl_dumpit_info *info =3D genl_dumpit_info(cb); - struct ethnl_dump_ctx *ethnl_ctx =3D &ctx->ethnl_ctx; - int ret =3D 0; - - if (ctx->ifindex) { - netdevice_tracker dev_tracker; - struct net_device *dev; - - dev =3D netdev_get_by_index(genl_info_net(&info->info), - ctx->ifindex, &dev_tracker, - GFP_KERNEL); - if (!dev) - return -ENODEV; - - ethnl_ctx->req_info->dev =3D dev; - ret =3D ethnl_perphy_dump_one_dev(skb, ctx, genl_info_dump(cb)); - - if (ret < 0 && ret !=3D -EOPNOTSUPP && likely(skb->len)) - ret =3D skb->len; - - netdev_put(dev, &dev_tracker); - } else { - ret =3D ethnl_perphy_dump_all_dev(skb, ctx, genl_info_dump(cb)); - } - - return ret; -} - -/* per-PHY ->done() handler for GET requests */ -static int ethnl_perphy_done(struct netlink_callback *cb) -{ - struct ethnl_perphy_dump_ctx *ctx =3D ethnl_perphy_dump_context(cb); - struct ethnl_dump_ctx *ethnl_ctx =3D &ctx->ethnl_ctx; - - kfree(ethnl_ctx->reply_data); - kfree(ethnl_ctx->req_info); + *pos_sub =3D 0; =20 return 0; } @@ -1420,9 +1275,9 @@ static const struct genl_ops ethtool_genl_ops[] =3D { { .cmd =3D ETHTOOL_MSG_PSE_GET, .doit =3D ethnl_default_doit, - .start =3D ethnl_perphy_start, - .dumpit =3D ethnl_perphy_dumpit, - .done =3D ethnl_perphy_done, + .start =3D ethnl_default_start, + .dumpit =3D ethnl_default_dumpit, + .done =3D ethnl_default_done, .policy =3D ethnl_pse_get_policy, .maxattr =3D ARRAY_SIZE(ethnl_pse_get_policy) - 1, }, @@ -1444,9 +1299,9 @@ static const struct genl_ops ethtool_genl_ops[] =3D { { .cmd =3D ETHTOOL_MSG_PLCA_GET_CFG, .doit =3D ethnl_default_doit, - .start =3D ethnl_perphy_start, - .dumpit =3D ethnl_perphy_dumpit, - .done =3D ethnl_perphy_done, + .start =3D ethnl_default_start, + .dumpit =3D ethnl_default_dumpit, + .done =3D ethnl_default_done, .policy =3D ethnl_plca_get_cfg_policy, .maxattr =3D ARRAY_SIZE(ethnl_plca_get_cfg_policy) - 1, }, @@ -1460,9 +1315,9 @@ static const struct genl_ops ethtool_genl_ops[] =3D { { .cmd =3D ETHTOOL_MSG_PLCA_GET_STATUS, .doit =3D ethnl_default_doit, - .start =3D ethnl_perphy_start, - .dumpit =3D ethnl_perphy_dumpit, - .done =3D ethnl_perphy_done, + .start =3D ethnl_default_start, + .dumpit =3D ethnl_default_dumpit, + .done =3D ethnl_default_done, .policy =3D ethnl_plca_get_status_policy, .maxattr =3D ARRAY_SIZE(ethnl_plca_get_status_policy) - 1, }, @@ -1492,9 +1347,9 @@ static const struct genl_ops ethtool_genl_ops[] =3D { { .cmd =3D ETHTOOL_MSG_PHY_GET, .doit =3D ethnl_default_doit, - .start =3D ethnl_perphy_start, - .dumpit =3D ethnl_perphy_dumpit, - .done =3D ethnl_perphy_done, + .start =3D ethnl_default_start, + .dumpit =3D ethnl_default_dumpit, + .done =3D ethnl_default_done, .policy =3D ethnl_phy_get_policy, .maxattr =3D ARRAY_SIZE(ethnl_phy_get_policy) - 1, }, @@ -1538,9 +1393,9 @@ static const struct genl_ops ethtool_genl_ops[] =3D { { .cmd =3D ETHTOOL_MSG_MSE_GET, .doit =3D ethnl_default_doit, - .start =3D ethnl_perphy_start, - .dumpit =3D ethnl_perphy_dumpit, - .done =3D ethnl_perphy_done, + .start =3D ethnl_default_start, + .dumpit =3D ethnl_default_dumpit, + .done =3D ethnl_default_done, .policy =3D ethnl_mse_get_policy, .maxattr =3D ARRAY_SIZE(ethnl_mse_get_policy) - 1, }, diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h index 89010eaa67df..aa8d51903ecc 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -10,6 +10,28 @@ =20 struct ethnl_req_info; =20 +/** + * struct ethnl_dump_ctx - context structure for generic dumpit() callback + * @ops: request ops of currently processed message type + * @req_info: parsed request header of processed request + * @reply_data: data needed to compose the reply + * @pos_ifindex: saved iteration position - ifindex + * @ifindex: for filtered dump requests, the ifindex of the targeted ne= tdev + * @pos_sub: iterator position for per-device iteration + * + * These parameters are kept in struct netlink_callback as context preserv= ed + * between iterations. They are initialized by ethnl_default_start() and u= sed + * in ethnl_default_dumpit() and ethnl_default_done(). + */ +struct ethnl_dump_ctx { + const struct ethnl_request_ops *ops; + struct ethnl_req_info *req_info; + struct ethnl_reply_data *reply_data; + unsigned long pos_ifindex; + unsigned int ifindex; + unsigned long pos_sub; +}; + u32 ethnl_bcast_seq_next(void); int ethnl_parse_header_dev_get(struct ethnl_req_info *req_info, const struct nlattr *nest, struct net *net, @@ -408,6 +430,11 @@ struct ethnl_request_ops { const struct ethnl_reply_data *reply_data); void (*cleanup_data)(struct ethnl_reply_data *reply_data); =20 + int (*dump_one_dev)(struct sk_buff *skb, + struct ethnl_dump_ctx *ctx, + unsigned long *pos_sub, + const struct genl_info *info); + int (*set_validate)(struct ethnl_req_info *req_info, struct genl_info *info); int (*set)(struct ethnl_req_info *req_info, @@ -514,6 +541,10 @@ int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct ne= tlink_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 ethnl_perphy_dump_one_dev(struct sk_buff *skb, + struct ethnl_dump_ctx *ctx, + unsigned long *pos_sub, + const struct genl_info *info); =20 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_G= STRING_LEN]; diff --git a/net/ethtool/phy.c b/net/ethtool/phy.c index 68372bef4b2f..4158c2abb235 100644 --- a/net/ethtool/phy.c +++ b/net/ethtool/phy.c @@ -162,4 +162,5 @@ const struct ethnl_request_ops ethnl_phy_request_ops = =3D { .reply_size =3D phy_reply_size, .fill_reply =3D phy_fill_reply, .cleanup_data =3D phy_cleanup_data, + .dump_one_dev =3D ethnl_perphy_dump_one_dev, }; diff --git a/net/ethtool/plca.c b/net/ethtool/plca.c index e1f7820a6158..cad0ba476c29 100644 --- a/net/ethtool/plca.c +++ b/net/ethtool/plca.c @@ -188,6 +188,7 @@ const struct ethnl_request_ops ethnl_plca_cfg_request_o= ps =3D { .prepare_data =3D plca_get_cfg_prepare_data, .reply_size =3D plca_get_cfg_reply_size, .fill_reply =3D plca_get_cfg_fill_reply, + .dump_one_dev =3D ethnl_perphy_dump_one_dev, =20 .set =3D ethnl_set_plca, .set_ntf_cmd =3D ETHTOOL_MSG_PLCA_NTF, @@ -268,4 +269,5 @@ const struct ethnl_request_ops ethnl_plca_status_reques= t_ops =3D { .prepare_data =3D plca_get_status_prepare_data, .reply_size =3D plca_get_status_reply_size, .fill_reply =3D plca_get_status_fill_reply, + .dump_one_dev =3D ethnl_perphy_dump_one_dev, }; diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c index 24def9c9dd54..1442a59e033f 100644 --- a/net/ethtool/pse-pd.c +++ b/net/ethtool/pse-pd.c @@ -337,6 +337,7 @@ const struct ethnl_request_ops ethnl_pse_request_ops = =3D { .reply_size =3D pse_reply_size, .fill_reply =3D pse_fill_reply, .cleanup_data =3D pse_cleanup_data, + .dump_one_dev =3D ethnl_perphy_dump_one_dev, =20 .set =3D ethnl_set_pse, /* PSE has no notification */ --=20 2.53.0 From nobody Thu Apr 9 06:36:02 2026 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 BFEE137998F; Tue, 10 Mar 2026 10:48: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=1773139685; cv=none; b=DOHOBDnBVdBM2SuU2bFkDWokTMjKD5YJSlUVN+TWhOpO53jGql6zjlGv7dJ9i9IiOpUIolOYEIKSzhJw8utJzEmoM0H+ax/jVtNpuQRn7Rp+lCz077uMwkfDgqtg4944E0epDsEKjtJ3zqp3tUvDtj+wKxeFMU1ahZ5zOqqbNHQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139685; c=relaxed/simple; bh=AtHcVPCdxnxp6riLXcN6j6+q4JKUbeR0RBrBjwX5kBs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=k8fi2yZY+1Wu6qdeSrh3OhK0FR8xiY0DwxApOkrJT7PyuvHDaaXMqLYLH/JPy3DrCFqIOGkq4ssgSzz37SpgbIHpj5s+EPHS37/Qxbw3JUMzouv5ax40NXCt7m8KHVtDF9dGEUTX04Mk5QFn0umb1dvfwnKtQnqhvoCPW8hUF38= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=XLQBGE6q; 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="XLQBGE6q" Received: by smtp.kernel.org (Postfix) with ESMTPSA id D7B9AC2BC87; Tue, 10 Mar 2026 10:47:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139685; bh=AtHcVPCdxnxp6riLXcN6j6+q4JKUbeR0RBrBjwX5kBs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XLQBGE6qSgIigWymg1e8MXs61PHKjQ0Jstc+Olhj4FHcMH9d4NDQz2NxXMwmX5UrU Wai6x1E/qkw8G1gmc7Y1e275Cw75Bxqgzf+kaLMOmKQNkwNGq71MVpCJNx6hLLZNXG V/n68rZjAG4BgfIvKQ3XYEmMGhr4AGt5NQMcaXBM/klGA7n9TrergSsVXCYwSc78kt LpNuFmUyGMa/tNPMFhPlPNE/C4PMeSBZVXyubxaYT3V1W4HtqaGCdUso+85lafuMke Edbb2tQZ4gTgdnh0N+uLuc1HQO9WO9EkMBPzuRjUP2ib1BS+XcCmFmMgGuUAIUjJxj n/34pebizmNNg== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 02/11] ethtool: Add loopback netlink UAPI definitions Date: Tue, 10 Mar 2026 11:47:32 +0100 Message-ID: <20260310104743.907818-3-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add the netlink YAML spec and auto-generated UAPI header for a unified loopback interface covering MAC, PCS, PHY, and pluggable module components. Each loopback point is described by a nested entry attribute containing: - component where in the path (MAC, PCS, PHY, MODULE) - name subsystem label, e.g. "cmis-host" or "cmis-media" - id optional instance selector (e.g. PHY id, port id) - supported bitmask of supported directions - direction NEAR_END, FAR_END, or 0 (disabled) Signed-off-by: Bj=C3=B6rn T=C3=B6pel --- Documentation/netlink/specs/ethtool.yaml | 123 ++++++++++++++++++ .../uapi/linux/ethtool_netlink_generated.h | 59 +++++++++ 2 files changed, 182 insertions(+) diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netli= nk/specs/ethtool.yaml index 4707063af3b4..8bd14a3c946a 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -211,6 +211,49 @@ definitions: name: discard value: 31 =20 + - + name: loopback-component + type: enum + doc: | + Loopback component. Identifies where in the network path the + loopback is applied. + entries: + - + name: mac + doc: MAC loopback. Loops traffic at the MAC block. + - + name: pcs + doc: | + PCS loopback. Loops traffic at the PCS sublayer between the + MAC and the PHY. + - + name: phy + doc: | + Ethernet PHY loopback. This refers to the Ethernet PHY managed + by phylib, not generic PHY drivers. A Base-T SFP module + containing an Ethernet PHY driven by Linux should report + loopback under this component, not module. + - + name: module + doc: | + Pluggable module (e.g. CMIS (Q)SFP) loopback. Covers loopback + modes controlled via module firmware or EEPROM registers. When + Linux drives an Ethernet PHY inside the module via phylib, use + the phy component instead. + - + name: loopback-direction + type: flags + doc: | + Loopback direction flags. Used as a bitmask in supported, and as + a single value in direction. + entries: + - + name: near-end + doc: Near-end loopback; host-loop-host + - + name: far-end + doc: Far-end loopback; line-loop-line + attribute-sets: - name: header @@ -1903,6 +1946,58 @@ attribute-sets: name: link type: nest nested-attributes: mse-snapshot + - + name: loopback-entry + doc: Per-component loopback configuration entry. + attr-cnt-name: __ethtool-a-loopback-entry-cnt + attributes: + - + name: unspec + type: unused + value: 0 + - + name: component + type: u32 + enum: loopback-component + doc: Loopback component + - + name: id + type: u32 + doc: Optional component instance identifier. + - + name: name + type: string + doc: | + Subsystem-specific name for the loopback point within the + component. + - + name: supported + type: u8 + enum: loopback-direction + enum-as-flags: true + doc: Bitmask of supported loopback directions + - + name: direction + type: u8 + enum: loopback-direction + doc: Current loopback direction, 0 means disabled + - + name: loopback + attr-cnt-name: __ethtool-a-loopback-cnt + attributes: + - + name: unspec + type: unused + value: 0 + - + name: header + type: nest + nested-attributes: header + - + name: entry + type: nest + multi-attr: true + nested-attributes: loopback-entry =20 operations: enum-model: directional @@ -2855,6 +2950,34 @@ operations: - worst-channel - link dump: *mse-get-op + - + name: loopback-get + doc: Get loopback configuration and capabilities. + + attribute-set: loopback + + do: &loopback-get-op + request: + attributes: + - header + reply: + attributes: &loopback + - header + - entry + dump: *loopback-get-op + - + name: loopback-set + doc: Set loopback configuration. + + attribute-set: loopback + + do: + request: + attributes: *loopback + - + name: loopback-ntf + doc: Notification for change in loopback configuration. + notify: loopback-get =20 mcast-groups: list: diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/= linux/ethtool_netlink_generated.h index 114b83017297..d8bff056a4b1 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -78,6 +78,40 @@ enum ethtool_pse_event { ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR =3D 64, }; =20 +/** + * enum ethtool_loopback_component - Loopback component. Identifies where = in + * the network path the loopback is applied. + * @ETHTOOL_LOOPBACK_COMPONENT_MAC: MAC loopback. Loops traffic at the MAC + * block. + * @ETHTOOL_LOOPBACK_COMPONENT_PCS: PCS loopback. Loops traffic at the PCS + * sublayer between the MAC and the PHY. + * @ETHTOOL_LOOPBACK_COMPONENT_PHY: Ethernet PHY loopback. This refers to = the + * Ethernet PHY managed by phylib, not generic PHY drivers. A Base-T SFP + * module containing an Ethernet PHY driven by Linux should report loopb= ack + * under this component, not module. + * @ETHTOOL_LOOPBACK_COMPONENT_MODULE: Pluggable module (e.g. CMIS (Q)SFP) + * loopback. Covers loopback modes controlled via module firmware or EEP= ROM + * registers. When Linux drives an Ethernet PHY inside the module via ph= ylib, + * use the phy component instead. + */ +enum ethtool_loopback_component { + ETHTOOL_LOOPBACK_COMPONENT_MAC, + ETHTOOL_LOOPBACK_COMPONENT_PCS, + ETHTOOL_LOOPBACK_COMPONENT_PHY, + ETHTOOL_LOOPBACK_COMPONENT_MODULE, +}; + +/** + * enum ethtool_loopback_direction - Loopback direction flags. Used as a + * bitmask in supported, and as a single value in direction. + * @ETHTOOL_LOOPBACK_DIRECTION_NEAR_END: Near-end loopback; host-loop-host + * @ETHTOOL_LOOPBACK_DIRECTION_FAR_END: Far-end loopback; line-loop-line + */ +enum ethtool_loopback_direction { + ETHTOOL_LOOPBACK_DIRECTION_NEAR_END =3D 1, + ETHTOOL_LOOPBACK_DIRECTION_FAR_END =3D 2, +}; + enum { ETHTOOL_A_HEADER_UNSPEC, ETHTOOL_A_HEADER_DEV_INDEX, @@ -838,6 +872,27 @@ enum { ETHTOOL_A_MSE_MAX =3D (__ETHTOOL_A_MSE_CNT - 1) }; =20 +enum { + ETHTOOL_A_LOOPBACK_ENTRY_UNSPEC, + ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT, + ETHTOOL_A_LOOPBACK_ENTRY_ID, + ETHTOOL_A_LOOPBACK_ENTRY_NAME, + ETHTOOL_A_LOOPBACK_ENTRY_SUPPORTED, + ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION, + + __ETHTOOL_A_LOOPBACK_ENTRY_CNT, + ETHTOOL_A_LOOPBACK_ENTRY_MAX =3D (__ETHTOOL_A_LOOPBACK_ENTRY_CNT - 1) +}; + +enum { + ETHTOOL_A_LOOPBACK_UNSPEC, + ETHTOOL_A_LOOPBACK_HEADER, + ETHTOOL_A_LOOPBACK_ENTRY, + + __ETHTOOL_A_LOOPBACK_CNT, + ETHTOOL_A_LOOPBACK_MAX =3D (__ETHTOOL_A_LOOPBACK_CNT - 1) +}; + enum { ETHTOOL_MSG_USER_NONE =3D 0, ETHTOOL_MSG_STRSET_GET =3D 1, @@ -891,6 +946,8 @@ enum { ETHTOOL_MSG_RSS_CREATE_ACT, ETHTOOL_MSG_RSS_DELETE_ACT, ETHTOOL_MSG_MSE_GET, + ETHTOOL_MSG_LOOPBACK_GET, + ETHTOOL_MSG_LOOPBACK_SET, =20 __ETHTOOL_MSG_USER_CNT, ETHTOOL_MSG_USER_MAX =3D (__ETHTOOL_MSG_USER_CNT - 1) @@ -952,6 +1009,8 @@ enum { ETHTOOL_MSG_RSS_CREATE_NTF, ETHTOOL_MSG_RSS_DELETE_NTF, ETHTOOL_MSG_MSE_GET_REPLY, + ETHTOOL_MSG_LOOPBACK_GET_REPLY, + ETHTOOL_MSG_LOOPBACK_NTF, =20 __ETHTOOL_MSG_KERNEL_CNT, ETHTOOL_MSG_KERNEL_MAX =3D (__ETHTOOL_MSG_KERNEL_CNT - 1) --=20 2.53.0 From nobody Thu Apr 9 06:36:02 2026 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 EA44337998F; Tue, 10 Mar 2026 10:48:11 +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=1773139692; cv=none; b=UBfmKY5NsOyslFNsN/lz9EU/2yDDIwsrX/29SvcrtGiqaNxIiI+53e4GC6BBz5L/H6exeY6t6yBrygxrvIR0LHSY3pgtvG6W4R1U6AkpGzX+w5wO94T6uBSoGo7kKeHz2nuTVYIF+pgAL/j4PgMRAX+GaAlF/eNVvHApkRMa40A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139692; c=relaxed/simple; bh=4nsuZH1vTh91kwqDNhVaU7ZTf1hSkjMiygJmkEnN7cA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=QCWinZiynBRH4hMnsuzigWd1RbAdeJcCMRs2GLpTogHoruYWXDK5gACbq1+yQTshQLnIYRAthMb7xideHPhIBa4uIBpxW3IqO3KTQL/N3mKjGrV3A1KxVd3mdTc8Y85dcjDjc5Vi5Us2Lwq5EVNMU9x4vGYVhZJDZ8IuUshiw1s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=mOzNxiHP; 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="mOzNxiHP" Received: by smtp.kernel.org (Postfix) with ESMTPSA id C8E96C19423; Tue, 10 Mar 2026 10:48:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139691; bh=4nsuZH1vTh91kwqDNhVaU7ZTf1hSkjMiygJmkEnN7cA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mOzNxiHPjMrgPETE4krlylnzqdRiGRwIEuHVyfJb7UdDpd/ammbsLlcRjmaF+wQhK wExLr3MLcT2U91xxtStrAS0aMC8M6W343weZKXPSDfElbOKCCAI1zE0oQnDaVZkRaq 2+mfX70dVTcX/xMOr0wIDEF9XLzUxa1wKNd/C48Av8dw2faB49Pd7IlEc/GM4WXVTL KxSt0p7IpQ0LjMHJhpfdz8ng9KGZgO8F61U7K12TfhROA/d3niXH2nCobvgo4U6qZy Hf1/OLBuzaLBy4KoLldwv7AFPO5gIiL4vMlOYGrDZi0acurBN/BeGqxHrj9GSuToFt 5mm/yvZgHSLlQ== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 03/11] ethtool: Add loopback GET/SET netlink implementation Date: Tue, 10 Mar 2026 11:47:33 +0100 Message-ID: <20260310104743.907818-4-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add the kernel-side ETHTOOL_MSG_LOOPBACK_GET, ETHTOOL_MSG_LOOPBACK_SET, and ETHTOOL_MSG_LOOPBACK_NTF handlers using the standard ethnl_request_ops infrastructure. GET collects loopback entries from per-component helpers via loopback_get_entries(). SET parses the nested entry attributes, dispatches each to loopback_set_one(), and only sends a notification when the state is changed. No components are wired yet. Signed-off-by: Bj=C3=B6rn T=C3=B6pel --- include/linux/ethtool.h | 16 +++ net/ethtool/Makefile | 2 +- net/ethtool/loopback.c | 305 ++++++++++++++++++++++++++++++++++++++++ net/ethtool/netlink.c | 24 +++- net/ethtool/netlink.h | 6 + 5 files changed, 350 insertions(+), 3 deletions(-) create mode 100644 net/ethtool/loopback.c diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 83c375840835..c9beca11fc40 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -846,6 +846,22 @@ void ethtool_mmsv_set_mm(struct ethtool_mmsv *mmsv, st= ruct ethtool_mm_cfg *cfg); void ethtool_mmsv_init(struct ethtool_mmsv *mmsv, struct net_device *dev, const struct ethtool_mmsv_ops *ops); =20 +/** + * struct ethtool_loopback_entry - Per-component loopback configuration + * @id: Optional component instance identifier, 0 means not specified + * @supported: Bitmask of supported directions + * @component: Loopback component + * @direction: Current loopback direction, 0 means disabled + * @name: Subsystem-specific name for the loopback point + */ +struct ethtool_loopback_entry { + enum ethtool_loopback_component component; + u32 id; + u32 supported; + u32 direction; + char name[ETH_GSTRING_LEN]; +}; + /** * struct ethtool_rxfh_param - RXFH (RSS) parameters * @hfunc: Defines the current RSS hash function used by HW (or to be set = to). diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile index 629c10916670..ef534b55d724 100644 --- a/net/ethtool/Makefile +++ b/net/ethtool/Makefile @@ -9,4 +9,4 @@ ethtool_nl-y :=3D netlink.o bitset.o strset.o linkinfo.o li= nkmodes.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 + phy.o tsconfig.o mse.o loopback.o diff --git a/net/ethtool/loopback.c b/net/ethtool/loopback.c new file mode 100644 index 000000000000..8907dd147404 --- /dev/null +++ b/net/ethtool/loopback.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "netlink.h" +#include "common.h" + +struct loopback_req_info { + struct ethnl_req_info base; + enum ethtool_loopback_component component; + u32 id; + char name[ETH_GSTRING_LEN]; + bool lookup_by_name; + u32 index; +}; + +#define LOOPBACK_REQINFO(__req_base) \ + container_of(__req_base, struct loopback_req_info, base) + +struct loopback_reply_data { + struct ethnl_reply_data base; + struct ethtool_loopback_entry entry; +}; + +#define LOOPBACK_REPDATA(__reply_base) \ + container_of(__reply_base, struct loopback_reply_data, base) + +/* GET */ + +static const struct nla_policy +ethnl_loopback_entry_policy[ETHTOOL_A_LOOPBACK_ENTRY_MAX + 1] =3D { + [ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT] =3D + NLA_POLICY_MAX(NLA_U32, ETHTOOL_LOOPBACK_COMPONENT_MODULE), + [ETHTOOL_A_LOOPBACK_ENTRY_ID] =3D NLA_POLICY_MIN(NLA_U32, 1), + [ETHTOOL_A_LOOPBACK_ENTRY_NAME] =3D { .type =3D NLA_NUL_STRING, + .len =3D ETH_GSTRING_LEN }, + [ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION] =3D + NLA_POLICY_MASK(NLA_U8, ETHTOOL_LOOPBACK_DIRECTION_NEAR_END | + ETHTOOL_LOOPBACK_DIRECTION_FAR_END), +}; + +const struct nla_policy +ethnl_loopback_get_policy[ETHTOOL_A_LOOPBACK_MAX + 1] =3D { + [ETHTOOL_A_LOOPBACK_HEADER] =3D NLA_POLICY_NESTED(ethnl_header_policy), + [ETHTOOL_A_LOOPBACK_ENTRY] =3D + NLA_POLICY_NESTED(ethnl_loopback_entry_policy), +}; + +static int loopback_parse_request(struct ethnl_req_info *req_base, + struct nlattr **tb, + struct netlink_ext_ack *extack) +{ + struct loopback_req_info *req_info =3D LOOPBACK_REQINFO(req_base); + struct nlattr *entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_MAX + 1]; + int ret; + + if (!tb[ETHTOOL_A_LOOPBACK_ENTRY]) + return 0; + + ret =3D nla_parse_nested(entry_tb, ETHTOOL_A_LOOPBACK_ENTRY_MAX, + tb[ETHTOOL_A_LOOPBACK_ENTRY], + ethnl_loopback_entry_policy, extack); + if (ret < 0) + return ret; + + if (!entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT] || + !entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_NAME]) { + NL_SET_ERR_MSG(extack, + "component and name required for loopback lookup"); + return -EINVAL; + } + + req_info->component =3D + nla_get_u32(entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT]); + if (entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_ID]) + req_info->id =3D + nla_get_u32(entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_ID]); + nla_strscpy(req_info->name, entry_tb[ETHTOOL_A_LOOPBACK_ENTRY_NAME], + sizeof(req_info->name)); + req_info->lookup_by_name =3D true; + + return 0; +} + +static int loopback_get(struct net_device *dev, + enum ethtool_loopback_component component, u32 id, + const char *name, + struct ethtool_loopback_entry *entry) +{ + switch (component) { + default: + return -EOPNOTSUPP; + } +} + +static int loopback_get_by_index(struct net_device *dev, u32 index, + struct ethtool_loopback_entry *entry) +{ + return -EOPNOTSUPP; +} + +static int loopback_prepare_data(const struct ethnl_req_info *req_base, + struct ethnl_reply_data *reply_base, + const struct genl_info *info) +{ + const struct loopback_req_info *req_info =3D LOOPBACK_REQINFO(req_base); + struct loopback_reply_data *data =3D LOOPBACK_REPDATA(reply_base); + struct net_device *dev =3D reply_base->dev; + int ret; + + ret =3D ethnl_ops_begin(dev); + if (ret < 0) + return ret; + + if (req_info->lookup_by_name) + ret =3D loopback_get(dev, req_info->component, req_info->id, + req_info->name, &data->entry); + else + ret =3D loopback_get_by_index(dev, req_info->index, &data->entry); + + ethnl_ops_complete(dev); + + return ret; +} + +static int loopback_reply_size(const struct ethnl_req_info *req_base, + const struct ethnl_reply_data *reply_base) +{ + return nla_total_size(0) + /* nest */ + nla_total_size(sizeof(u32)) + /* component */ + nla_total_size(sizeof(u32)) + /* id */ + nla_total_size(sizeof(u8)) + /* supported */ + nla_total_size(sizeof(u8)) + /* direction */ + nla_total_size(ETH_GSTRING_LEN); /* name */ +} + +static int loopback_fill_reply(struct sk_buff *skb, + const struct ethnl_req_info *req_base, + const struct ethnl_reply_data *reply_base) +{ + const struct loopback_reply_data *data =3D LOOPBACK_REPDATA(reply_base); + const struct ethtool_loopback_entry *entry =3D &data->entry; + struct nlattr *nest; + + nest =3D nla_nest_start(skb, ETHTOOL_A_LOOPBACK_ENTRY); + if (!nest) + return -EMSGSIZE; + + if (nla_put_u32(skb, ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT, + entry->component)) + goto err_cancel; + + if (entry->id && + nla_put_u32(skb, ETHTOOL_A_LOOPBACK_ENTRY_ID, entry->id)) + goto err_cancel; + + if (nla_put_u8(skb, ETHTOOL_A_LOOPBACK_ENTRY_SUPPORTED, + entry->supported) || + nla_put_u8(skb, ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION, + entry->direction) || + nla_put_string(skb, ETHTOOL_A_LOOPBACK_ENTRY_NAME, + entry->name)) + goto err_cancel; + + nla_nest_end(skb, nest); + return 0; + +err_cancel: + nla_nest_cancel(skb, nest); + return -EMSGSIZE; +} + +/* SET */ + +const struct nla_policy +ethnl_loopback_set_policy[ETHTOOL_A_LOOPBACK_MAX + 1] =3D { + [ETHTOOL_A_LOOPBACK_HEADER] =3D NLA_POLICY_NESTED(ethnl_header_policy), + [ETHTOOL_A_LOOPBACK_ENTRY] =3D + NLA_POLICY_NESTED(ethnl_loopback_entry_policy), +}; + +static int loopback_parse_entry(struct nlattr *attr, + struct ethtool_loopback_entry *entry, + struct netlink_ext_ack *extack) +{ + struct nlattr *tb[ETHTOOL_A_LOOPBACK_ENTRY_MAX + 1]; + int ret; + + ret =3D nla_parse_nested(tb, ETHTOOL_A_LOOPBACK_ENTRY_MAX, attr, + ethnl_loopback_entry_policy, extack); + if (ret < 0) + return ret; + + if (!tb[ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT]) { + NL_SET_ERR_MSG_ATTR(extack, attr, + "loopback component is required"); + return -EINVAL; + } + + entry->component =3D nla_get_u32(tb[ETHTOOL_A_LOOPBACK_ENTRY_COMPONENT]); + + if (tb[ETHTOOL_A_LOOPBACK_ENTRY_ID]) + entry->id =3D nla_get_u32(tb[ETHTOOL_A_LOOPBACK_ENTRY_ID]); + + if (!tb[ETHTOOL_A_LOOPBACK_ENTRY_NAME]) { + NL_SET_ERR_MSG_ATTR(extack, attr, "loopback name is required"); + return -EINVAL; + } + nla_strscpy(entry->name, tb[ETHTOOL_A_LOOPBACK_ENTRY_NAME], + sizeof(entry->name)); + + if (!tb[ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION]) { + NL_SET_ERR_MSG_ATTR(extack, attr, + "loopback direction is required"); + return -EINVAL; + } + + entry->direction =3D nla_get_u8(tb[ETHTOOL_A_LOOPBACK_ENTRY_DIRECTION]); + + return 0; +} + +static int __loopback_set(struct net_device *dev, + const struct ethtool_loopback_entry *entry, + struct netlink_ext_ack *extack) +{ + switch (entry->component) { + default: + return -EOPNOTSUPP; + } +} + +static int loopback_set(struct ethnl_req_info *req_info, + struct genl_info *info) +{ + struct net_device *dev =3D req_info->dev; + struct ethtool_loopback_entry entry; + int rem, ret, mod =3D 0; + struct nlattr *attr; + bool found =3D false; + + nla_for_each_attr(attr, genlmsg_data(info->genlhdr), + genlmsg_len(info->genlhdr), rem) { + if (nla_type(attr) !=3D ETHTOOL_A_LOOPBACK_ENTRY) + continue; + + found =3D true; + memset(&entry, 0, sizeof(entry)); + ret =3D loopback_parse_entry(attr, &entry, info->extack); + if (ret < 0) + return ret; + + ret =3D __loopback_set(dev, &entry, info->extack); + if (ret < 0) + return ret; + if (ret > 0) + mod =3D 1; + } + + if (!found) { + NL_SET_ERR_MSG(info->extack, "no loopback entries specified"); + return -EINVAL; + } + + return mod; +} + +static int loopback_dump_one_dev(struct sk_buff *skb, + struct ethnl_dump_ctx *ctx, + unsigned long *pos_sub, + const struct genl_info *info) +{ + struct loopback_req_info *req_info =3D + container_of(ctx->req_info, struct loopback_req_info, base); + int ret; + + for (;; (*pos_sub)++) { + req_info->index =3D *pos_sub; + ret =3D ethnl_default_dump_one(skb, ctx->req_info->dev, ctx, + info); + if (ret =3D=3D -EOPNOTSUPP) + break; + if (ret) + return ret; + } + + *pos_sub =3D 0; + + return 0; +} + +const struct ethnl_request_ops ethnl_loopback_request_ops =3D { + .request_cmd =3D ETHTOOL_MSG_LOOPBACK_GET, + .reply_cmd =3D ETHTOOL_MSG_LOOPBACK_GET_REPLY, + .hdr_attr =3D ETHTOOL_A_LOOPBACK_HEADER, + .req_info_size =3D sizeof(struct loopback_req_info), + .reply_data_size =3D sizeof(struct loopback_reply_data), + + .parse_request =3D loopback_parse_request, + .prepare_data =3D loopback_prepare_data, + .reply_size =3D loopback_reply_size, + .fill_reply =3D loopback_fill_reply, + .dump_one_dev =3D loopback_dump_one_dev, + + .set =3D loopback_set, + .set_ntf_cmd =3D ETHTOOL_MSG_LOOPBACK_NTF, +}; diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index e740b11a0609..25b2fca05bd8 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -391,6 +391,8 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] =3D { [ETHTOOL_MSG_TSCONFIG_SET] =3D ðnl_tsconfig_request_ops, [ETHTOOL_MSG_PHY_GET] =3D ðnl_phy_request_ops, [ETHTOOL_MSG_MSE_GET] =3D ðnl_mse_request_ops, + [ETHTOOL_MSG_LOOPBACK_GET] =3D ðnl_loopback_request_ops, + [ETHTOOL_MSG_LOOPBACK_SET] =3D ðnl_loopback_request_ops, }; =20 static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *= cb) @@ -537,8 +539,8 @@ static int ethnl_default_doit(struct sk_buff *skb, stru= ct genl_info *info) return ret; } =20 -static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *= dev, - const struct ethnl_dump_ctx *ctx, +int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev, + const struct ethnl_dump_ctx *ctx, const struct genl_info *info) { void *ehdr; @@ -817,6 +819,7 @@ ethnl_default_notify_ops[ETHTOOL_MSG_KERNEL_MAX + 1] = =3D { [ETHTOOL_MSG_MM_NTF] =3D ðnl_mm_request_ops, [ETHTOOL_MSG_RSS_NTF] =3D ðnl_rss_request_ops, [ETHTOOL_MSG_RSS_CREATE_NTF] =3D ðnl_rss_request_ops, + [ETHTOOL_MSG_LOOPBACK_NTF] =3D ðnl_loopback_request_ops, }; =20 /* default notification handler */ @@ -925,6 +928,7 @@ static const ethnl_notify_handler_t ethnl_notify_handle= rs[] =3D { [ETHTOOL_MSG_MM_NTF] =3D ethnl_default_notify, [ETHTOOL_MSG_RSS_NTF] =3D ethnl_default_notify, [ETHTOOL_MSG_RSS_CREATE_NTF] =3D ethnl_default_notify, + [ETHTOOL_MSG_LOOPBACK_NTF] =3D ethnl_default_notify, }; =20 void ethnl_notify(struct net_device *dev, unsigned int cmd, @@ -1399,6 +1403,22 @@ static const struct genl_ops ethtool_genl_ops[] =3D { .policy =3D ethnl_mse_get_policy, .maxattr =3D ARRAY_SIZE(ethnl_mse_get_policy) - 1, }, + { + .cmd =3D ETHTOOL_MSG_LOOPBACK_GET, + .doit =3D ethnl_default_doit, + .start =3D ethnl_default_start, + .dumpit =3D ethnl_default_dumpit, + .done =3D ethnl_default_done, + .policy =3D ethnl_loopback_get_policy, + .maxattr =3D ARRAY_SIZE(ethnl_loopback_get_policy) - 1, + }, + { + .cmd =3D ETHTOOL_MSG_LOOPBACK_SET, + .flags =3D GENL_UNS_ADMIN_PERM, + .doit =3D ethnl_default_set_doit, + .policy =3D ethnl_loopback_set_policy, + .maxattr =3D ARRAY_SIZE(ethnl_loopback_set_policy) - 1, + }, }; =20 static const struct genl_multicast_group ethtool_nl_mcgrps[] =3D { diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h index aa8d51903ecc..2320760f931e 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -470,6 +470,7 @@ extern const struct ethnl_request_ops ethnl_mm_request_= ops; extern const struct ethnl_request_ops ethnl_phy_request_ops; extern const struct ethnl_request_ops ethnl_tsconfig_request_ops; extern const struct ethnl_request_ops ethnl_mse_request_ops; +extern const struct ethnl_request_ops ethnl_loopback_request_ops; =20 extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS = + 1]; extern const struct nla_policy ethnl_header_policy_stats[ETHTOOL_A_HEADER_= FLAGS + 1]; @@ -526,6 +527,8 @@ extern const struct nla_policy ethnl_phy_get_policy[ETH= TOOL_A_PHY_HEADER + 1]; extern const struct nla_policy ethnl_tsconfig_get_policy[ETHTOOL_A_TSCONFI= G_HEADER + 1]; extern const struct nla_policy ethnl_tsconfig_set_policy[ETHTOOL_A_TSCONFI= G_MAX + 1]; extern const struct nla_policy ethnl_mse_get_policy[ETHTOOL_A_MSE_HEADER += 1]; +extern const struct nla_policy ethnl_loopback_get_policy[ETHTOOL_A_LOOPBAC= K_MAX + 1]; +extern const struct nla_policy ethnl_loopback_set_policy[ETHTOOL_A_LOOPBAC= K_MAX + 1]; =20 int ethnl_set_features(struct sk_buff *skb, struct genl_info *info); int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info); @@ -541,6 +544,9 @@ int ethnl_tsinfo_dumpit(struct sk_buff *skb, struct net= link_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 ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev, + const struct ethnl_dump_ctx *ctx, + const struct genl_info *info); int ethnl_perphy_dump_one_dev(struct sk_buff *skb, struct ethnl_dump_ctx *ctx, unsigned long *pos_sub, --=20 2.53.0 From nobody Thu Apr 9 06:36:02 2026 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 AB0F83314D2; Tue, 10 Mar 2026 10:48:17 +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=1773139697; cv=none; b=CuRbKHINa0/nPR6c1k3j22nIA6WXrHQ5F+ACh9bG04Uxg104q0TQ2uzG7bUaxrHq5F/copEamYARhPRjdwOgTKKo5S/yjzWdk8Q6xW46qNlDQG3LnSOQE49yjMGXoYDYkEDhLCTeBij7mpo03VFPdtGHiYLA+vzWkCb3wm2qBZM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139697; c=relaxed/simple; bh=xMZ42D5aNtwhnLwzvCzxcszWZhmxnCde5ccwKeK/940=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=EeqfYeZi0SK0rt/SMjMr2SP9xvi88Dr9shSF8+QIRFevgmV5tLUxpk3u34Wm65kQ6O6TxCUI1aoQLpfua9qhzxKvoNhfghv5WisFZaTt2dUwvDDrRyzvEYkTP+XgHAYv/y1nVt0nKqGJs+a9FdAxoeyeNTrTQNI0x+IAVx9HLfg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=G//q3ZAq; 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="G//q3ZAq" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E9605C2BC86; Tue, 10 Mar 2026 10:48:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139697; bh=xMZ42D5aNtwhnLwzvCzxcszWZhmxnCde5ccwKeK/940=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=G//q3ZAqdqO3pjHL2qPlfMxAVdS+wI6Fsp0qFtfjTK/2+3gFduHdM2CholyaemEjf eZh8cBc7h9fGxMfPdqlnpAoJPUa1Sly8WZlc7BixCIV4S/H7KncXcYYJhQZZv8q9co PnM4z7f76nk9GA0cGr6l5k+kbaVbFL7K5qTK6TrJUDXEg2xJY8GGZ1yHWM1fB+6dE6 HDXgW6Id5VRhlwdKtoChqH0AwZAHGFf0Iii+2DjjA/tlrXuYEyDeNe4Bg1OgV33jlb 7aglOUyOdpB2howrADDqLxO6kFRJOVO3jFnM6kgt2hMH4ucMYcdaP9e/YmPIvndYkP gwjPiQe474FTg== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 04/11] ethtool: Add CMIS loopback helpers for module loopback control Date: Tue, 10 Mar 2026 11:47:34 +0100 Message-ID: <20260310104743.907818-5-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable 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 =3D all lanes on, 0x00 =3D 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=C3=B6rn T=C3=B6pel --- 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 :=3D netlink.o bitset.o strset.o linkinfo.o li= nkmodes.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..0d419c369d64 --- /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", 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 =3D dev->ethtool_ops; + struct ethtool_module_eeprom page =3D {}; + 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 =3D &val; + ret =3D 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 =3D &val; + ret =3D 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 =3D &val; + ret =3D 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 =3D dev->ethtool_ops; + struct ethtool_module_eeprom page =3D {}; + u8 ctrl[CMIS_LB_CTRL_LEN]; + int caps, ret; + + *has_host =3D false; + *has_media =3D false; + + if (dev->ethtool->module_fw_flash_in_progress) + return -EBUSY; + + caps =3D cmis_loopback_caps(dev); + if (caps <=3D 0) + return caps ? caps : -EOPNOTSUPP; + + ethtool_cmis_page_init(&page, CMIS_LB_CTRL_PAGE, + CMIS_LB_CTRL_OFFSET, sizeof(ctrl)); + page.data =3D ctrl; + ret =3D ops->get_module_eeprom_by_page(dev, &page, NULL); + if (ret < 0) + return ret; + + memset(host, 0, sizeof(*host)); + host->component =3D ETHTOOL_LOOPBACK_COMPONENT_MODULE; + strscpy(host->name, CMIS_LB_NAME_HOST, sizeof(host->name)); + + memset(media, 0, sizeof(*media)); + media->component =3D ETHTOOL_LOOPBACK_COMPONENT_MODULE; + strscpy(media->name, CMIS_LB_NAME_MEDIA, sizeof(media->name)); + + if (caps & CMIS_LB_CAP_HOST_INPUT) { + *has_host =3D true; + host->supported |=3D ETHTOOL_LOOPBACK_DIRECTION_NEAR_END; + if (ctrl[CMIS_LB_CTRL_IDX_HOST_INPUT]) + host->direction |=3D ETHTOOL_LOOPBACK_DIRECTION_NEAR_END; + } + if (caps & CMIS_LB_CAP_HOST_OUTPUT) { + *has_host =3D true; + host->supported |=3D ETHTOOL_LOOPBACK_DIRECTION_FAR_END; + if (ctrl[CMIS_LB_CTRL_IDX_HOST_OUTPUT]) + host->direction |=3D ETHTOOL_LOOPBACK_DIRECTION_FAR_END; + } + if (caps & CMIS_LB_CAP_MEDIA_INPUT) { + *has_media =3D true; + media->supported |=3D ETHTOOL_LOOPBACK_DIRECTION_NEAR_END; + if (ctrl[CMIS_LB_CTRL_IDX_MEDIA_INPUT]) + media->direction |=3D ETHTOOL_LOOPBACK_DIRECTION_NEAR_END; + } + if (caps & CMIS_LB_CAP_MEDIA_OUTPUT) { + *has_media =3D true; + media->supported |=3D ETHTOOL_LOOPBACK_DIRECTION_FAR_END; + if (ctrl[CMIS_LB_CTRL_IDX_MEDIA_OUTPUT]) + media->direction |=3D ETHTOOL_LOOPBACK_DIRECTION_FAR_END; + } + + return 0; +} + +/** + * ethtool_cmis_get_loopback_by_index - Enumerate CMIS loopback entry by i= ndex + * @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 =3D 0; + int ret; + + ret =3D cmis_loopback_read(dev, &host, &media, &has_host, &has_media); + if (ret) + return ret; + + if (has_host) { + if (cur =3D=3D index) { + memcpy(entry, &host, sizeof(*entry)); + return 0; + } + cur++; + } + + if (has_media) { + if (cur =3D=3D 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 =3D 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 =3D=3D 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 =3D {}; + u8 ctrl[CMIS_LB_CTRL_LEN]; + int near_idx, far_idx; + u8 near_cap, far_cap; + bool mod =3D 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)) { + near_idx =3D CMIS_LB_CTRL_IDX_HOST_INPUT; + far_idx =3D CMIS_LB_CTRL_IDX_HOST_OUTPUT; + near_cap =3D CMIS_LB_CAP_HOST_INPUT; + far_cap =3D CMIS_LB_CAP_HOST_OUTPUT; + } else if (!strcmp(entry->name, CMIS_LB_NAME_MEDIA)) { + near_idx =3D CMIS_LB_CTRL_IDX_MEDIA_INPUT; + far_idx =3D CMIS_LB_CTRL_IDX_MEDIA_OUTPUT; + near_cap =3D CMIS_LB_CAP_MEDIA_INPUT; + far_cap =3D CMIS_LB_CAP_MEDIA_OUTPUT; + } else { + NL_SET_ERR_MSG(extack, "Unknown CMIS loopback name"); + return -EINVAL; + } + + caps =3D 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 =3D ctrl; + ret =3D 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] =3D 0x00; + mod =3D true; + } + if (ctrl[far_idx]) { + ctrl[far_idx] =3D 0x00; + mod =3D true; + } + } else { + int enable_idx, disable_idx; + u8 enable_cap; + + if (entry->direction & ETHTOOL_LOOPBACK_DIRECTION_NEAR_END) { + enable_idx =3D near_idx; + enable_cap =3D near_cap; + disable_idx =3D far_idx; + } else { + enable_idx =3D far_idx; + enable_cap =3D far_cap; + disable_idx =3D 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] =3D 0x00; + ret =3D dev->ethtool_ops->set_module_eeprom_by_page(dev, + &page, extack); + if (ret < 0) + return ret; + mod =3D true; + } + + if (ctrl[enable_idx] !=3D 0xff) { + ctrl[enable_idx] =3D 0xff; + mod =3D true; + } + } + + if (!mod) + return 0; + + ret =3D 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 8907dd147404..ed184f45322e 100644 --- a/net/ethtool/loopback.c +++ b/net/ethtool/loopback.c @@ -86,6 +86,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; } @@ -94,7 +96,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); } =20 static int loopback_prepare_data(const struct ethnl_req_info *req_base, @@ -223,6 +225,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 2320760f931e..5aec1b73da7a 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -551,6 +551,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); =20 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_G= STRING_LEN]; --=20 2.53.0 From nobody Thu Apr 9 06:36:02 2026 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 7C2563314D2; Tue, 10 Mar 2026 10:48:23 +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=1773139703; cv=none; b=uOgSQgLKyvVkyXT2k8rRJKRCnUlgFH+IZHBlWorEP9Infmzrm02BfulRpUq1pJaOc+/O4at9kidvIZAG0BZ0BxSikFTmUt7Wv86OAb+fIAnDH484UpSU96w08KJTOjDDABsPi2VCvkqxDg+LQb4bIMBwhZpd4Hmv55E0bSlkqJE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139703; c=relaxed/simple; bh=wUmXm68rgXvP51p2cWDEQwObxflbVjVTx5cn/rY7UKQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Oo50qIl90ORdTyQMaZFCVt+LfakvrAcRRec/Vqk+zO8t6cbgX2FB1thlo5VuRwrI4fQnOna3qDFyDAPjLv7Zoyjbds0J+EmCdBIIQSNvWYUCvbfRDVc13EwEoHIPDbuXbisGRD9dloMFtWK+1qE4L+mj+NNf86Q1kRu2p5BhcgQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=GpTOLp1Q; 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="GpTOLp1Q" Received: by smtp.kernel.org (Postfix) with ESMTPSA id DAF84C19423; Tue, 10 Mar 2026 10:48:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139703; bh=wUmXm68rgXvP51p2cWDEQwObxflbVjVTx5cn/rY7UKQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=GpTOLp1Q+TZtXw0MzpmV3ME/AypFYUOQ0PkgmWloZDv3xpD77Kf5g0H4s0GHLKBxQ Byl3P8hKpobK3us/CjYSBwlBwngfnShGS3+c+yhUyVvUugjvwyHlNgFUgeFazC2iIe hNYLdk4BGpLACh88UtNe9CJh9evCcASBJja3DuI3qyVEC7FK+f06vhIutsC7pf3PtC XQA5Oj3rr4eCWUj2njpaCXNl6WBM3fiOypNO5MWhzxDsxQJ04qxms7IpQapk5I1WUv zws1pgD5Gi5wAknJszdDS5g60SZ3P8JN+K7rjxL/YeDPSqFqjtd1lhZYuyjp3vM8xP aWJwhkwdYIrMQ== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 05/11] selftests: drv-net: Add loopback driver test Date: Tue, 10 Mar 2026 11:47:35 +0100 Message-ID: <20260310104743.907818-6-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add a selftest for the ethtool loopback UAPI exercising module loopback via the loopback GET/SET netlink commands. Works on any device that reports module loopback entries. Tests cover enable near-end and far-end, disable, direction switching (mutual exclusivity), idempotent enable, and rejection while interface is up. Devices without module loopback support are skipped. Signed-off-by: Bj=C3=B6rn T=C3=B6pel --- .../testing/selftests/drivers/net/hw/Makefile | 1 + .../selftests/drivers/net/hw/loopback_drv.py | 226 ++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_drv.py diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testin= g/selftests/drivers/net/hw/Makefile index 91df028abfc0..1c341aaa88c6 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -31,6 +31,7 @@ TEST_PROGS =3D \ iou-zcrx.py \ irq.py \ loopback.sh \ + loopback_drv.py \ nic_timestamp.py \ nk_netns.py \ pp_alloc_fail.py \ diff --git a/tools/testing/selftests/drivers/net/hw/loopback_drv.py b/tools= /testing/selftests/drivers/net/hw/loopback_drv.py new file mode 100755 index 000000000000..05374db42ae9 --- /dev/null +++ b/tools/testing/selftests/drivers/net/hw/loopback_drv.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +"""Tests for ethtool loopback GET/SET with CMIS modules. + +Works on any device that reports module loopback entries. On devices +without CMIS loopback support, tests are skipped. +""" + +import errno + +from lib.py import ksft_run, ksft_exit, ksft_eq +from lib.py import KsftSkipEx, KsftFailEx, ksft_disruptive +from lib.py import EthtoolFamily, NlError +from lib.py import NetDrvEnv, ip, defer + +# Direction flags as YNL returns them (sets of flag name strings) +DIR_NONE =3D set() +DIR_NEAR_END =3D {'near-end'} +DIR_FAR_END =3D {'far-end'} + + +def _get_loopback(cfg): + """GET loopback and return the list of entries (via DUMP).""" + results =3D cfg.ethnl.loopback_get({ + 'header': {'dev-index': cfg.ifindex} + }, dump=3DTrue) + entries =3D [] + for msg in results: + if 'entry' in msg: + entries.extend(msg['entry']) + return entries + + +def _set_loopback(cfg, component, name, direction): + """SET loopback for a single entry.""" + cfg.ethnl.loopback_set({ + 'header': {'dev-index': cfg.ifindex}, + 'entry': [{ + 'component': component, + 'name': name, + 'direction': direction, + }] + }) + + +def _require_module_entries(cfg): + """Return module loopback entries, skip if none available.""" + entries =3D _get_loopback(cfg) + mod_entries =3D [e for e in entries if e['component'] =3D=3D 'module'] + if not mod_entries: + raise KsftSkipEx("No module loopback entries") + return mod_entries + + +@ksft_disruptive +def test_set_near_end(cfg): + """SET a module entry to near-end and verify via GET.""" + mod_entries =3D _require_module_entries(cfg) + + near =3D [e for e in mod_entries + if 'near-end' in e['supported']] + if not near: + raise KsftSkipEx("No near-end capable module entry") + + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + target =3D near[0] + _set_loopback(cfg, 'module', target['name'], 'near-end') + defer(_set_loopback, cfg, 'module', target['name'], 0) + + entries =3D _get_loopback(cfg) + updated =3D [e for e in entries + if e['name'] =3D=3D target['name'] + and 'near-end' in e['supported']] + ksft_eq(len(updated), 1) + ksft_eq(updated[0]['direction'], DIR_NEAR_END) + + +@ksft_disruptive +def test_set_far_end(cfg): + """SET a module entry to far-end and verify via GET.""" + mod_entries =3D _require_module_entries(cfg) + + far =3D [e for e in mod_entries + if 'far-end' in e['supported']] + if not far: + raise KsftSkipEx("No far-end capable module entry") + + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + target =3D far[0] + _set_loopback(cfg, 'module', target['name'], 'far-end') + defer(_set_loopback, cfg, 'module', target['name'], 0) + + entries =3D _get_loopback(cfg) + updated =3D [e for e in entries + if e['name'] =3D=3D target['name'] + and 'far-end' in e['supported']] + ksft_eq(len(updated), 1) + ksft_eq(updated[0]['direction'], DIR_FAR_END) + + +@ksft_disruptive +def test_set_disable(cfg): + """Enable then disable loopback and verify.""" + mod_entries =3D _require_module_entries(cfg) + + near =3D [e for e in mod_entries + if 'near-end' in e['supported']] + if not near: + raise KsftSkipEx("No near-end capable module entry") + + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + target =3D near[0] + _set_loopback(cfg, 'module', target['name'], 'near-end') + defer(_set_loopback, cfg, 'module', target['name'], 0) + + # Disable + _set_loopback(cfg, 'module', target['name'], 0) + + entries =3D _get_loopback(cfg) + updated =3D [e for e in entries if e['name'] =3D=3D target['name']] + ksft_eq(updated[0]['direction'], DIR_NONE, + "Direction should be off after disable") + + +@ksft_disruptive +def test_set_direction_switch(cfg): + """Enable near-end, then switch to far-end. The kernel must disable + near-end before enabling far-end (mutual exclusivity). + """ + mod_entries =3D _require_module_entries(cfg) + + both =3D [e for e in mod_entries + if 'near-end' in e['supported'] and 'far-end' in e['supported'= ]] + if not both: + raise KsftSkipEx("No entry with both near-end and far-end support") + + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + target =3D both[0] + _set_loopback(cfg, 'module', target['name'], 'near-end') + defer(_set_loopback, cfg, 'module', target['name'], 0) + + entries =3D _get_loopback(cfg) + updated =3D [e for e in entries if e['name'] =3D=3D target['name']] + ksft_eq(updated[0]['direction'], DIR_NEAR_END) + + # Switch to far-end + _set_loopback(cfg, 'module', target['name'], 'far-end') + + entries =3D _get_loopback(cfg) + updated =3D [e for e in entries if e['name'] =3D=3D target['name']] + ksft_eq(updated[0]['direction'], DIR_FAR_END, + "Should have switched to far-end") + + +@ksft_disruptive +def test_set_idempotent(cfg): + """Enable the same direction twice. Second call should not fail.""" + mod_entries =3D _require_module_entries(cfg) + + near =3D [e for e in mod_entries + if 'near-end' in e['supported']] + if not near: + raise KsftSkipEx("No near-end capable module entry") + + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + target =3D near[0] + _set_loopback(cfg, 'module', target['name'], 'near-end') + defer(_set_loopback, cfg, 'module', target['name'], 0) + + # Second enable of the same direction should succeed + _set_loopback(cfg, 'module', target['name'], 'near-end') + + entries =3D _get_loopback(cfg) + updated =3D [e for e in entries + if e['name'] =3D=3D target['name'] + and 'near-end' in e['supported']] + ksft_eq(updated[0]['direction'], DIR_NEAR_END, + "Direction should still be near-end") + + +@ksft_disruptive +def test_set_while_up(cfg): + """SET while interface is UP should fail.""" + mod_entries =3D _require_module_entries(cfg) + + target =3D mod_entries[0] + direction =3D 'near-end' + if direction not in target['supported']: + direction =3D 'far-end' + + try: + _set_loopback(cfg, 'module', target['name'], direction) + raise KsftFailEx("Should have rejected SET while interface is up") + except NlError as e: + ksft_eq(e.error, errno.EBUSY, + "Expected EBUSY when interface is up") + + +def main() -> None: + with NetDrvEnv(__file__, nsim_test=3DFalse) as cfg: + cfg.ethnl =3D EthtoolFamily() + + ksft_run([ + test_set_near_end, + test_set_far_end, + test_set_disable, + test_set_direction_switch, + test_set_idempotent, + test_set_while_up, + ], args=3D(cfg, )) + ksft_exit() + + +if __name__ =3D=3D "__main__": + main() --=20 2.53.0 From nobody Thu Apr 9 06:36:02 2026 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 97D3C3806D2; Tue, 10 Mar 2026 10:48:29 +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=1773139709; cv=none; b=sWoQ2OLKTgHWyixp1wCQhu7si+A4efiuYR/a/20L5aLlgpxhBqLIZ9z+o1LPK2S2HZ6cRU2ppMISKWIzHAEOh7ucWcaCw8CionZS1dSukLQpREVy/V4HWKg+Ztxm4V5bk+acdaWVQq0kz3jRz2buaU8FDnihIEpeeU+6QOHLsrw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139709; c=relaxed/simple; bh=tZUWY8Nw1MN0n4NXR1N69crYXRhZXdao1EJvTD7KZiA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=gVJCjTWxZDMze2ooyoQYMZM/eEuaM2NvvUA3iKISF7T0qnTWruyoL6a21ZWg6/MOhBiHatabJfpg+SgcFMkcywNbT0iCUk5ZoUP6y8x5eUu/fvny/dJ9kHcE6hFAJtWbQdIMPN/q6a8WwVYVlDR4AP15jrUqDdbRccmzKGLfIZU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Pi6yjRxl; 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="Pi6yjRxl" Received: by smtp.kernel.org (Postfix) with ESMTPSA id CB383C19423; Tue, 10 Mar 2026 10:48:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139709; bh=tZUWY8Nw1MN0n4NXR1N69crYXRhZXdao1EJvTD7KZiA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Pi6yjRxlJtTYiSQo71zUlZ8gpz35vIuBVZiz1hMKG4YnW6A5qI5No/2ydIGbk9Qjn Fx9eEXLR6GUxU7sRDaysQPaNpZP74vAJGX8JL0K32TVV02yFzJ1+ExRlH6h/oeAdFo V0/FZhlkyKOF7+hvQiMUUd8sXQcv8wxoMEuDvBgbfVnvwyon6BpHLFfC8IJG6TmcR3 IDRP6U/+nAJjAWF+Y4qzpm3yirRbbUp3NAEKlfzEfxjh9WWdT3iRy2+q9hr4lUQv57 RpeWh3OEgAyKTDiqFVUpCLRHvDl2Lwll2n/LxqXWRCXbfP2m5vjEM0OZR6vE95v2Kb guiJb4IRTxjWw== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 06/11] ethtool: Add MAC loopback support via ethtool_ops Date: Tue, 10 Mar 2026 11:47:36 +0100 Message-ID: <20260310104743.907818-7-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Extend struct ethtool_ops with three loopback callbacks that drivers can implement for MAC-level loopback control: - get_loopback(dev, name, id, entry): exact lookup by name and instance id, used by doit (single-entry GET) requests. - get_loopback_by_index(dev, index, entry): flat enumeration by index, used by dumpit (multi-entry GET) requests to iterate all loopback points on a device. - set_loopback(dev, entry, extack): apply a loopback configuration change. Returns 1 if hardware state changed, 0 if no-op. Wire the MAC component into loopback.c's dispatch functions. For dump enumeration, MAC entries are tried first via the driver's get_loopback_by_index() op, then MODULE/CMIS entries follow at the next index offset. Signed-off-by: Bj=C3=B6rn T=C3=B6pel Reviewed-by: Naveen Mamindlapalli --- include/linux/ethtool.h | 7 ++++++ net/ethtool/loopback.c | 56 ++++++++++++++++++++++++++++++++--------- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index c9beca11fc40..8893e732f930 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -1321,6 +1321,13 @@ struct ethtool_ops { int (*set_mm)(struct net_device *dev, struct ethtool_mm_cfg *cfg, struct netlink_ext_ack *extack); void (*get_mm_stats)(struct net_device *dev, struct ethtool_mm_stats *sta= ts); + int (*get_loopback)(struct net_device *dev, const char *name, + u32 id, struct ethtool_loopback_entry *entry); + int (*get_loopback_by_index)(struct net_device *dev, u32 index, + struct ethtool_loopback_entry *entry); + int (*set_loopback)(struct net_device *dev, + const struct ethtool_loopback_entry *entry, + struct netlink_ext_ack *extack); }; =20 int ethtool_check_ops(const struct ethtool_ops *ops); diff --git a/net/ethtool/loopback.c b/net/ethtool/loopback.c index ed184f45322e..9c5834be743f 100644 --- a/net/ethtool/loopback.c +++ b/net/ethtool/loopback.c @@ -86,6 +86,10 @@ static int loopback_get(struct net_device *dev, struct ethtool_loopback_entry *entry) { switch (component) { + case ETHTOOL_LOOPBACK_COMPONENT_MAC: + if (!dev->ethtool_ops->get_loopback) + return -EOPNOTSUPP; + return dev->ethtool_ops->get_loopback(dev, name, id, entry); case ETHTOOL_LOOPBACK_COMPONENT_MODULE: return ethtool_cmis_get_loopback(dev, name, entry); default: @@ -93,10 +97,22 @@ static int loopback_get(struct net_device *dev, } } =20 -static int loopback_get_by_index(struct net_device *dev, u32 index, +static int loopback_get_by_index(struct net_device *dev, + enum ethtool_loopback_component component, + u32 index, struct ethtool_loopback_entry *entry) { - return ethtool_cmis_get_loopback_by_index(dev, index, entry); + switch (component) { + case ETHTOOL_LOOPBACK_COMPONENT_MAC: + if (!dev->ethtool_ops->get_loopback_by_index) + return -EOPNOTSUPP; + return dev->ethtool_ops->get_loopback_by_index(dev, index, + entry); + case ETHTOOL_LOOPBACK_COMPONENT_MODULE: + return ethtool_cmis_get_loopback_by_index(dev, index, entry); + default: + return -EOPNOTSUPP; + } } =20 static int loopback_prepare_data(const struct ethnl_req_info *req_base, @@ -116,7 +132,8 @@ static int loopback_prepare_data(const struct ethnl_req= _info *req_base, ret =3D loopback_get(dev, req_info->component, req_info->id, req_info->name, &data->entry); else - ret =3D loopback_get_by_index(dev, req_info->index, &data->entry); + ret =3D loopback_get_by_index(dev, req_info->component, + req_info->index, &data->entry); =20 ethnl_ops_complete(dev); =20 @@ -225,6 +242,10 @@ static int __loopback_set(struct net_device *dev, struct netlink_ext_ack *extack) { switch (entry->component) { + case ETHTOOL_LOOPBACK_COMPONENT_MAC: + if (!dev->ethtool_ops->set_loopback) + return -EOPNOTSUPP; + return dev->ethtool_ops->set_loopback(dev, entry, extack); case ETHTOOL_LOOPBACK_COMPONENT_MODULE: return ethtool_cmis_set_loopback(dev, entry, extack); default: @@ -274,20 +295,31 @@ static int loopback_dump_one_dev(struct sk_buff *skb, { struct loopback_req_info *req_info =3D container_of(ctx->req_info, struct loopback_req_info, base); + /* pos_sub encodes: upper 16 bits =3D component phase, lower 16 =3D index + * within that component. dump_one_dev is called repeatedly with + * increasing pos_sub until all components are exhausted. + */ + enum ethtool_loopback_component phase =3D *pos_sub >> 16; + u32 idx =3D *pos_sub & 0xffff; int ret; =20 - for (;; (*pos_sub)++) { - req_info->index =3D *pos_sub; - ret =3D ethnl_default_dump_one(skb, ctx->req_info->dev, ctx, - info); - if (ret =3D=3D -EOPNOTSUPP) - break; - if (ret) - return ret; + for (; phase <=3D ETHTOOL_LOOPBACK_COMPONENT_MODULE; phase++) { + for (;; idx++) { + req_info->component =3D phase; + req_info->index =3D idx; + ret =3D ethnl_default_dump_one(skb, ctx->req_info->dev, + ctx, info); + if (ret =3D=3D -EOPNOTSUPP) + break; + if (ret) { + *pos_sub =3D ((unsigned long)phase << 16) | idx; + return ret; + } + } + idx =3D 0; } =20 *pos_sub =3D 0; - return 0; } =20 --=20 2.53.0 From nobody Thu Apr 9 06:36:02 2026 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 950803803CE; Tue, 10 Mar 2026 10:48:35 +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=1773139715; cv=none; b=Z+bX+EfaemcumUAMe6dmsIfCSkim5fRv1AZw+56aFLfyjPGhGR2UnpN7XeLOMpcc457uITs3HVP1TYgQfGuBVSmn0itFP+YxDdqzgJ/XPmpRbhAbuAANoqlGWmlvFxe82A/7g6Y9P8zqQe/JsykxtdlmLLqkYg/Yxzu0jn7lDkM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139715; c=relaxed/simple; bh=Oy21scQSlKA5g3E3cwQ9SFAIKbw2IE+Ea/f3mxcVhwQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=uFvfWibFnO4R4FOBvRm3nSMTO3zHqR2UWaHmfXglo2ysWsGzjt3XToPH8xVIEOyofX9NOugGk1DnNGRsU1Wm6iv4ZBex92MJl2o8dUOoCwp4q3NL5huO56NInjW86s9H58KFkSATvbEaR9cT2e5/Mt7IF7RjNPVVtSIqrINVK0Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=IS7gVAJi; 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="IS7gVAJi" Received: by smtp.kernel.org (Postfix) with ESMTPSA id BF3A2C19423; Tue, 10 Mar 2026 10:48:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139715; bh=Oy21scQSlKA5g3E3cwQ9SFAIKbw2IE+Ea/f3mxcVhwQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IS7gVAJi1S2ss9vfAIC60dOBGHi2QvM2N1QF1hZmSFoXbajLRHBxBauCOz+CR57U7 QlGzC6aCRWdfcS9QIZeW6XWVQvTzFiUEYz4uHO4UBAlxIwYpp2SNyqNU6jnAX2LaPh w+Eng9huCVc6dzvpkzP47127ovTVNHewYBENyEbioiDTfp4x4O07Z9JYd2/9oxmZ5f 2GSKd0XZWm7Lby6pLRk0UW2e+hgKDqXbGQ3IC24edmuusEhNtrRVBD36aDADiC5S5M r08sNmdLO60OXYM/rCl3eNFh5rkNYtjDCTEBQ95uXppVWokvNm7cGmUNYldVyThbQI 0DZpWKUlfg4NA== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 07/11] netdevsim: Add MAC loopback simulation Date: Tue, 10 Mar 2026 11:47:37 +0100 Message-ID: <20260310104743.907818-8-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Implement the three ethtool loopback ops for MAC-level loopback simulation: - get_loopback(): exact lookup by name ("mac") - get_loopback_by()_index: enumerate (single entry at index 0) - set_loopback(): update direction, return 1 if changed The MAC loopback entry announces support for both near-end and far-end support by default. State is stored in nsim_ethtool.mac_lb and exposed via debugfs under ethtool/mac_lb/{supported,direction} for test inspection and control. Signed-off-by: Bj=C3=B6rn T=C3=B6pel --- drivers/net/netdevsim/ethtool.c | 64 +++++++++++++++++++++++++++++++ drivers/net/netdevsim/netdevsim.h | 4 ++ 2 files changed, 68 insertions(+) diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtoo= l.c index 36a201533aae..84bc025885f7 100644 --- a/drivers/net/netdevsim/ethtool.c +++ b/drivers/net/netdevsim/ethtool.c @@ -195,6 +195,58 @@ nsim_get_fec_stats(struct net_device *dev, struct etht= ool_fec_stats *fec_stats, values[2].per_lane[3] =3D 0; } =20 +static void nsim_fill_mac_lb_entry(struct netdevsim *ns, + struct ethtool_loopback_entry *entry) +{ + memset(entry, 0, sizeof(*entry)); + entry->component =3D ETHTOOL_LOOPBACK_COMPONENT_MAC; + strscpy(entry->name, "mac", sizeof(entry->name)); + entry->supported =3D ns->ethtool.mac_lb.supported; + entry->direction =3D ns->ethtool.mac_lb.direction; +} + +static int nsim_get_loopback(struct net_device *dev, const char *name, + u32 id, struct ethtool_loopback_entry *entry) +{ + struct netdevsim *ns =3D netdev_priv(dev); + + if (strcmp(name, "mac")) + return -EOPNOTSUPP; + + nsim_fill_mac_lb_entry(ns, entry); + return 0; +} + +static int nsim_get_loopback_by_index(struct net_device *dev, u32 index, + struct ethtool_loopback_entry *entry) +{ + struct netdevsim *ns =3D netdev_priv(dev); + + if (index > 0) + return -EOPNOTSUPP; + + nsim_fill_mac_lb_entry(ns, entry); + return 0; +} + +static int nsim_set_loopback(struct net_device *dev, + const struct ethtool_loopback_entry *entry, + struct netlink_ext_ack *extack) +{ + struct netdevsim *ns =3D netdev_priv(dev); + + if (strcmp(entry->name, "mac")) { + NL_SET_ERR_MSG(extack, "Unknown MAC loopback name"); + return -EOPNOTSUPP; + } + + if (ns->ethtool.mac_lb.direction =3D=3D entry->direction) + return 0; + + ns->ethtool.mac_lb.direction =3D entry->direction; + return 1; +} + static int nsim_get_ts_info(struct net_device *dev, struct kernel_ethtool_ts_info *info) { @@ -222,6 +274,9 @@ static const struct ethtool_ops nsim_ethtool_ops =3D { .set_fecparam =3D nsim_set_fecparam, .get_fec_stats =3D nsim_get_fec_stats, .get_ts_info =3D nsim_get_ts_info, + .get_loopback =3D nsim_get_loopback, + .get_loopback_by_index =3D nsim_get_loopback_by_index, + .set_loopback =3D nsim_set_loopback, }; =20 static void nsim_ethtool_ring_init(struct netdevsim *ns) @@ -270,4 +325,13 @@ void nsim_ethtool_init(struct netdevsim *ns) &ns->ethtool.ring.rx_mini_max_pending); debugfs_create_u32("tx_max_pending", 0600, dir, &ns->ethtool.ring.tx_max_pending); + + ns->ethtool.mac_lb.supported =3D ETHTOOL_LOOPBACK_DIRECTION_NEAR_END | + ETHTOOL_LOOPBACK_DIRECTION_FAR_END; + + dir =3D debugfs_create_dir("mac_lb", ethtool); + debugfs_create_u32("supported", 0600, dir, + &ns->ethtool.mac_lb.supported); + debugfs_create_u32("direction", 0600, dir, + &ns->ethtool.mac_lb.direction); } diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netd= evsim.h index f767fc8a7505..2e322b9410d2 100644 --- a/drivers/net/netdevsim/netdevsim.h +++ b/drivers/net/netdevsim/netdevsim.h @@ -90,6 +90,10 @@ struct nsim_ethtool { struct ethtool_coalesce coalesce; struct ethtool_ringparam ring; struct ethtool_fecparam fec; + struct { + u32 supported; + u32 direction; + } mac_lb; }; =20 struct nsim_rq { --=20 2.53.0 From nobody Thu Apr 9 06:36:02 2026 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 9D55F30FC23; Tue, 10 Mar 2026 10:48:41 +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=1773139721; cv=none; b=MHAQMCFZ4qjeElktpUjJdkwMQ3BndZApugGEdOZblRy1oC33VUiuCIKavkKvnmpiHm+FjR4UgyMLWctaS46eMN5gurt6EQmgouk3ExxstBTCjxZngJTDTOHVb3Zb2oMHOdvkuCViGPV0crKc5aE2jRvtMn5az8NbjZzE6+Nksfk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139721; c=relaxed/simple; bh=YvF2cb6VNBd6TdihjC7PxhVeN8lbvy5kF/3/KYsHq3U=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=nHw1g9OO+2kSX3svnWjkcvJuA6Rc8MnJEg6r9Te/hjHph4bHSxX25kKYkr/tKNJoDIslddrji7icEqiHsFtVG18jA1aqueEW0w4iiktDwGqQtm8ABI2jAv2BUzqXJjzFVQ5fSB+iiEKZKlUgGXlaZF7L6Rbzvo69pmTI155nRM8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=nXSOXl/A; 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="nXSOXl/A" Received: by smtp.kernel.org (Postfix) with ESMTPSA id C6686C4AF0C; Tue, 10 Mar 2026 10:48:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139721; bh=YvF2cb6VNBd6TdihjC7PxhVeN8lbvy5kF/3/KYsHq3U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nXSOXl/AGDAGgXnyJ/o/+E6anugaScJt1Bbb7cAs7mbwy/vPA272zH+YdZs/es3YC fYGuRpDDxJhseJ+cI/rCmmI07hRYYGY9OuJ3i6yB39dalO+Rbd5xrrjSDFmG2/02z8 aDHclPeaehP7Tr/ipDgms1xapj3gzloukojzKQRwwXBYsDQIatoY+61NpBDEca5bsZ Bg7iQAmhcTktOEYKR/p5LeMnRMX4jonezRkud49CJG+tLejvcFUr6FiyjKXgc7Sr7z pLftBar8mbMbpAAIQEMMoyJiZaMtAnt5/TFXZr4veMcmu+lLh/dWM6Bg/IZnhCkEPw 16cDs6Z2/38aw== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 08/11] selftests: drv-net: Add MAC loopback netdevsim test Date: Tue, 10 Mar 2026 11:47:38 +0100 Message-ID: <20260310104743.907818-9-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add loopback_nsim.py with netdevsim-specific tests for MAC loopback via the new ethtool_ops loopback callbacks: - test_get_mac_entry: verify MAC entry appears in dump with correct component, name, and supported directions - test_set_mac_near_end: SET near-end, verify via GET and debugfs - test_set_mac_disable: enable then disable - test_set_mac_unknown_name: SET with wrong name, expect EOPNOTSUPP Signed-off-by: Bj=C3=B6rn T=C3=B6pel --- .../testing/selftests/drivers/net/hw/Makefile | 1 + .../selftests/drivers/net/hw/loopback_nsim.py | 135 ++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100755 tools/testing/selftests/drivers/net/hw/loopback_nsim.py diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testin= g/selftests/drivers/net/hw/Makefile index 1c341aaa88c6..d7041b9d7461 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -32,6 +32,7 @@ TEST_PROGS =3D \ irq.py \ loopback.sh \ loopback_drv.py \ + loopback_nsim.py \ nic_timestamp.py \ nk_netns.py \ pp_alloc_fail.py \ diff --git a/tools/testing/selftests/drivers/net/hw/loopback_nsim.py b/tool= s/testing/selftests/drivers/net/hw/loopback_nsim.py new file mode 100755 index 000000000000..5edc999d920b --- /dev/null +++ b/tools/testing/selftests/drivers/net/hw/loopback_nsim.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +"""Netdevsim-specific tests for MAC loopback via ethtool_ops. + +Verifies that MAC loopback entries appear in dumps, that SET +operations update state correctly (both via GET and debugfs). +""" + +import errno +import os + +from lib.py import ksft_run, ksft_exit, ksft_eq +from lib.py import KsftSkipEx, KsftFailEx, ksft_disruptive +from lib.py import EthtoolFamily, NlError +from lib.py import NetDrvEnv, ip, defer + +# Direction flags as YNL returns them +DIR_NONE =3D set() +DIR_NEAR_END =3D {'near-end'} +DIR_FAR_END =3D {'far-end'} + + +def _nsim_dfs_path(cfg): + return cfg._ns.nsims[0].dfs_dir + + +def _dfs_read_u32(cfg, path): + with open(os.path.join(_nsim_dfs_path(cfg), path)) as f: + return int(f.read().strip()) + + +def _dfs_write_u32(cfg, path, val): + with open(os.path.join(_nsim_dfs_path(cfg), path), "w") as f: + f.write(str(val)) + + +def _get_loopback(cfg): + results =3D cfg.ethnl.loopback_get({ + 'header': {'dev-index': cfg.ifindex} + }, dump=3DTrue) + entries =3D [] + for msg in results: + if 'entry' in msg: + entries.extend(msg['entry']) + return entries + + +def _set_loopback(cfg, component, name, direction): + cfg.ethnl.loopback_set({ + 'header': {'dev-index': cfg.ifindex}, + 'entry': [{ + 'component': component, + 'name': name, + 'direction': direction, + }] + }) + + +def test_get_mac_entry(cfg): + """GET should return the MAC loopback entry with correct attributes.""" + entries =3D _get_loopback(cfg) + mac_entries =3D [e for e in entries if e['component'] =3D=3D 'mac'] + + ksft_eq(len(mac_entries), 1, "Expected 1 MAC loopback entry") + ksft_eq(mac_entries[0]['name'], 'mac') + ksft_eq(mac_entries[0]['supported'], DIR_NEAR_END | DIR_FAR_END) + ksft_eq(mac_entries[0]['direction'], DIR_NONE) + + +@ksft_disruptive +def test_set_mac_near_end(cfg): + """SET MAC near-end and verify via GET and debugfs.""" + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + _set_loopback(cfg, 'mac', 'mac', 'near-end') + defer(_set_loopback, cfg, 'mac', 'mac', 0) + + entries =3D _get_loopback(cfg) + mac =3D [e for e in entries if e['component'] =3D=3D 'mac'] + ksft_eq(mac[0]['direction'], DIR_NEAR_END) + + dfs_dir =3D _dfs_read_u32(cfg, "ethtool/mac_lb/direction") + ksft_eq(dfs_dir, 1, "debugfs direction should be 1 (NEAR_END)") + + +@ksft_disruptive +def test_set_mac_disable(cfg): + """Enable then disable MAC loopback.""" + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + _set_loopback(cfg, 'mac', 'mac', 'near-end') + defer(_set_loopback, cfg, 'mac', 'mac', 0) + + _set_loopback(cfg, 'mac', 'mac', 0) + + entries =3D _get_loopback(cfg) + mac =3D [e for e in entries if e['component'] =3D=3D 'mac'] + ksft_eq(mac[0]['direction'], DIR_NONE, "Direction should be off") + + dfs_dir =3D _dfs_read_u32(cfg, "ethtool/mac_lb/direction") + ksft_eq(dfs_dir, 0, "debugfs direction should be 0") + + +@ksft_disruptive +def test_set_mac_unknown_name(cfg): + """SET with unknown name should fail with EOPNOTSUPP.""" + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + try: + _set_loopback(cfg, 'mac', 'bogus', 'near-end') + raise KsftFailEx("Should have rejected unknown name") + except NlError as e: + ksft_eq(e.error, errno.EOPNOTSUPP, + "Expected EOPNOTSUPP for unknown name") + + +def main() -> None: + with NetDrvEnv(__file__) as cfg: + cfg.ethnl =3D EthtoolFamily() + + ksft_run([ + test_get_mac_entry, + test_set_mac_near_end, + test_set_mac_disable, + test_set_mac_unknown_name, + ], args=3D(cfg, )) + ksft_exit() + + +if __name__ =3D=3D "__main__": + main() --=20 2.53.0 From nobody Thu Apr 9 06:36:02 2026 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 582863859DE; Tue, 10 Mar 2026 10:48:47 +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=1773139727; cv=none; b=WmFSbcK67kxyRmloRFKc824T1OGztQCOnOU+eA1IgHhLyHcbrY9iUwFcaWykVoa6ifxCvDdUnV0sxvwOBu3YU6AGQVEJEPKFHxZDHt4Faj2pnMPttVBDgV4JDBT7hhMsTyjUWgTow9THsRTA4FyTZrq5EKuWZjaM+Dqa4GdDlSQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139727; c=relaxed/simple; bh=gQD72fiJ8PyerPFoC9XHgV0ULHgkxGnVvcLiv8h3rIs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=eBdkj2SQNokvI5jAEe8T1x8KsFJzWiNIxl9Ui3Ug5bFChkyEwAhBOG99kuc8CiWmDXT9Dp2lX+4kfxrSZF/oRPaCZokw1tjYREHs2jTW9m+0QE0Lu7kZ413tMyT1vOHSMf78fZAqy7PflLH4o8COJ1j0GIynesrtAj6zRJPSqI8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=gsO3MvX5; 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="gsO3MvX5" Received: by smtp.kernel.org (Postfix) with ESMTPSA id BA6B2C19423; Tue, 10 Mar 2026 10:48:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139727; bh=gQD72fiJ8PyerPFoC9XHgV0ULHgkxGnVvcLiv8h3rIs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gsO3MvX5y6wfvDvWCv2pbgT/3hsKUKYKXyy1M8F0//zlZIhZxzMpVrirZ/s8TNz0d rBq4P9/cfS2IvLUOrdxXr/y76H7BcbXkrZw0S/0b7AGJPl7CK4RDfQB14C9t0P8CIe kzDe5Vdlo64sx/ZFvHnUo/XAunisyHx30E3pDxLQR2b79rUoYlbYBKvguO2GJzoVih aMzMqoIZTM1gw/24y0W/BjF1ktohSViHV1yjHhqTYioe/SR/qXfHT72jdBJuEG7cx7 SKcCg0Z7mm0czTU/TuJVqtvZ8C4q6RipQgDmfz1D5ZQvxTxQx0+uWHv8jJL9l611oq JW6aqdmlcwy9Q== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 09/11] MAINTAINERS: Add entry for ethtool loopback Date: Tue, 10 Mar 2026 11:47:39 +0100 Message-ID: <20260310104743.907818-10-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add a MAINTAINERS entry for the ethtool loopback subsystem covering the core loopback and CMIS loopback netlink implementation, and the associated selftests. Signed-off-by: Bj=C3=B6rn T=C3=B6pel --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index a38fe0ed7144..37b134feffe9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18392,6 +18392,12 @@ F: net/ethtool/cabletest.c F: tools/testing/selftests/drivers/net/*/ethtool* K: cable_test =20 +NETWORKING [ETHTOOL LOOPBACK] +M: Bj=C3=B6rn T=C3=B6pel +F: net/ethtool/cmis_loopback.c +F: net/ethtool/loopback.c +F: tools/testing/selftests/drivers/net/hw/loopback* + NETWORKING [ETHTOOL MAC MERGE] M: Vladimir Oltean F: net/ethtool/mm.c --=20 2.53.0 From nobody Thu Apr 9 06:36:02 2026 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 4A9471F3D56; Tue, 10 Mar 2026 10:48:53 +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=1773139733; cv=none; b=W8jnYokUujd6WrumZIVJm1SQ50JdUj0X1SdDLoOgCJissHEwfm7TvqzPtl4OjZnOYK8eoAz4fuNBrUc4miL0JSXmfXV7ZNTwp2fkVX1f4pAzdCtnuYUbY0Z2gastDZK2SATufrWHWbRcUsBSxmQChUrrph77kmVCLaKepKVxYhI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139733; c=relaxed/simple; bh=L/ZpnxVlgnhKdpLTTMiA+jz9N1d0Xq77j1IS3Dua7Mc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ZoqsogGSI9JM88xnlpAwNb86Daii9SPGkPDmOIvlkPCTOxP7nna+y3f+FYBFM1ru34VEltRi8l4usaFqujT1eojWh7Jg24bw7tDla7s7M+LkzpfpwZrPwn6l/9hcKUEMjL9BAIBULCoaFtcbAbH+yBuGy32DcKV2B1Utk26xPLs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=XLgchWrO; 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="XLgchWrO" Received: by smtp.kernel.org (Postfix) with ESMTPSA id AD3D4C19423; Tue, 10 Mar 2026 10:48:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139733; bh=L/ZpnxVlgnhKdpLTTMiA+jz9N1d0Xq77j1IS3Dua7Mc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XLgchWrO4XczooXM/1mjQKe4xzeuv4K09I2W+VhoHBO9Wv8vKacAUahAnNQofIPA5 VcGpmNR5ekE4E3HcW2CIKfaUaEiGBWK17Gpj5LWOnYxh6piLWRspb4ucaurER+31wY 58SUYbbYWrAOPYxH6vd1kiUMB1TOBpiFaDTWcgCZFkCLjmbrRPSdPMuGjzmiWfCdeI 5Ut+Ie/Z5Kffvq89Si7EC1sJ7onBEupNmOVxzJNOOI08LPKnrOb0vibDm3riVm2tiX qozwZEjAFQqiuW1uEGi54X6+43nAv+mreZ+IPZ5Oy4McRMMCy0WoKaifFD2xLtoFDB Z0+IJJgqETDXg== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 10/11] netdevsim: Add module EEPROM simulation via debugfs Date: Tue, 10 Mar 2026 11:47:40 +0100 Message-ID: <20260310104743.907818-11-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add get/set_module_eeprom_by_page ethtool ops to netdevsim, enabling testing of kernel features that depend on module EEPROM access (e.g. CMIS loopback) without real hardware. The EEPROM is backed by a 256-page x 128-byte array exposed as binary debugfs files under ports//ethtool/module/pages/{0..255}. Offsets 0-127 map to page 0 (lower memory), 128-255 to the requested page's upper memory, following the CMIS layout. Error injection via get_err and set_err follows the existing netdevsim pattern. Signed-off-by: Bj=C3=B6rn T=C3=B6pel --- drivers/net/netdevsim/ethtool.c | 83 +++++++++++++++++++++++++++++++ drivers/net/netdevsim/netdevsim.h | 11 ++++ 2 files changed, 94 insertions(+) diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtoo= l.c index 84bc025885f7..7ef96a747643 100644 --- a/drivers/net/netdevsim/ethtool.c +++ b/drivers/net/netdevsim/ethtool.c @@ -247,6 +247,68 @@ static int nsim_set_loopback(struct net_device *dev, return 1; } =20 +static u8 *nsim_module_eeprom_ptr(struct netdevsim *ns, + const struct ethtool_module_eeprom *page_data, + u32 *len) +{ + u32 offset; + u8 page; + + if (page_data->offset < NSIM_MODULE_EEPROM_PAGE_LEN) { + page =3D 0; + offset =3D page_data->offset; + } else { + page =3D page_data->page; + offset =3D page_data->offset - NSIM_MODULE_EEPROM_PAGE_LEN; + } + + *len =3D min_t(u32, page_data->length, + NSIM_MODULE_EEPROM_PAGE_LEN - offset); + return ns->ethtool.module.pages[page] + offset; +} + +static int +nsim_get_module_eeprom_by_page(struct net_device *dev, + const struct ethtool_module_eeprom *page_data, + struct netlink_ext_ack *extack) +{ + struct netdevsim *ns =3D netdev_priv(dev); + u32 len; + u8 *ptr; + + if (ns->ethtool.module.get_err) + return -ns->ethtool.module.get_err; + + ptr =3D nsim_module_eeprom_ptr(ns, page_data, &len); + if (!ptr) + return -EINVAL; + + memcpy(page_data->data, ptr, len); + + return len; +} + +static int +nsim_set_module_eeprom_by_page(struct net_device *dev, + const struct ethtool_module_eeprom *page_data, + struct netlink_ext_ack *extack) +{ + struct netdevsim *ns =3D netdev_priv(dev); + u32 len; + u8 *ptr; + + if (ns->ethtool.module.set_err) + return -ns->ethtool.module.set_err; + + ptr =3D nsim_module_eeprom_ptr(ns, page_data, &len); + if (!ptr) + return -EINVAL; + + memcpy(ptr, page_data->data, len); + + return 0; +} + static int nsim_get_ts_info(struct net_device *dev, struct kernel_ethtool_ts_info *info) { @@ -274,6 +336,8 @@ static const struct ethtool_ops nsim_ethtool_ops =3D { .set_fecparam =3D nsim_set_fecparam, .get_fec_stats =3D nsim_get_fec_stats, .get_ts_info =3D nsim_get_ts_info, + .get_module_eeprom_by_page =3D nsim_get_module_eeprom_by_page, + .set_module_eeprom_by_page =3D nsim_set_module_eeprom_by_page, .get_loopback =3D nsim_get_loopback, .get_loopback_by_index =3D nsim_get_loopback_by_index, .set_loopback =3D nsim_set_loopback, @@ -292,6 +356,7 @@ static void nsim_ethtool_ring_init(struct netdevsim *ns) void nsim_ethtool_init(struct netdevsim *ns) { struct dentry *ethtool, *dir; + int i; =20 ns->netdev->ethtool_ops =3D &nsim_ethtool_ops; =20 @@ -326,6 +391,24 @@ void nsim_ethtool_init(struct netdevsim *ns) debugfs_create_u32("tx_max_pending", 0600, dir, &ns->ethtool.ring.tx_max_pending); =20 + dir =3D debugfs_create_dir("module", ethtool); + debugfs_create_u32("get_err", 0600, dir, &ns->ethtool.module.get_err); + debugfs_create_u32("set_err", 0600, dir, &ns->ethtool.module.set_err); + + dir =3D debugfs_create_dir("pages", dir); + for (i =3D 0; i < NSIM_MODULE_EEPROM_PAGES; i++) { + char name[8]; + + ns->ethtool.module.page_blobs[i].data =3D + ns->ethtool.module.pages[i]; + ns->ethtool.module.page_blobs[i].size =3D + NSIM_MODULE_EEPROM_PAGE_LEN; + + snprintf(name, sizeof(name), "%u", i); + debugfs_create_blob(name, 0600, dir, + &ns->ethtool.module.page_blobs[i]); + } + ns->ethtool.mac_lb.supported =3D ETHTOOL_LOOPBACK_DIRECTION_NEAR_END | ETHTOOL_LOOPBACK_DIRECTION_FAR_END; =20 diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netd= evsim.h index 2e322b9410d2..f6b2063d41c9 100644 --- a/drivers/net/netdevsim/netdevsim.h +++ b/drivers/net/netdevsim/netdevsim.h @@ -82,6 +82,16 @@ struct nsim_ethtool_pauseparam { bool report_stats_tx; }; =20 +#define NSIM_MODULE_EEPROM_PAGES 256 +#define NSIM_MODULE_EEPROM_PAGE_LEN 128 + +struct nsim_ethtool_module { + u32 get_err; + u32 set_err; + u8 pages[NSIM_MODULE_EEPROM_PAGES][NSIM_MODULE_EEPROM_PAGE_LEN]; + struct debugfs_blob_wrapper page_blobs[NSIM_MODULE_EEPROM_PAGES]; +}; + struct nsim_ethtool { u32 get_err; u32 set_err; @@ -94,6 +104,7 @@ struct nsim_ethtool { u32 supported; u32 direction; } mac_lb; + struct nsim_ethtool_module module; }; =20 struct nsim_rq { --=20 2.53.0 From nobody Thu Apr 9 06:36:02 2026 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 8021C3822BD; Tue, 10 Mar 2026 10:48:59 +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=1773139739; cv=none; b=DoG8IMMIuIWqYvirWmuEbqcOWjzdKIVh/nxTCQubtAyUrXoDIDTVZWn9QdPDzNf9ye9cchp5R+KZLdFEokkHwEINAJ2zQ3x7poP1RYckHmjIYpjwP7cCMYF/d4cUmrliUf0bYMhnIz5b5Xjm9l82/xDrtLRV7LyqMfy6Zw3pmgo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773139739; c=relaxed/simple; bh=QjfuMRNUKR1O0Ord6LYD+ihG6XFTihVz0FFS1WwUfO8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=g15P+nRfwzuS43z/JHfO5fXYXqjWzOGZkDpTyFsEAaSnHtPgLGtsDGsr5AKwbTHiesbp/M7J8/bhCeIwVMo9qutQEYEn5yWHqSeIUsOLJEuSwTvvvq3s4QVu8E/tQe5+mS8jytun2iz4zuKi5lDRfXpJh1uNBUytH9rCxVoCHAA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=kclfX8XI; 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="kclfX8XI" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 9E6C9C2BCB5; Tue, 10 Mar 2026 10:48:53 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773139739; bh=QjfuMRNUKR1O0Ord6LYD+ihG6XFTihVz0FFS1WwUfO8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kclfX8XIN7KO3wWbJ9CL0MxrA0z15wEkNAh/YD5sbd4gVcIAN8njIjeYVNK7XYc9G wft7Mt35ZKf8sYN67xqVRXU0178C0MYdErb+undUdZwHCqRbWQ+RamT7pPN2RaSFQq GYesZEqcPmdNFR3bXkSHj2LNt6kDY72Vmj91AsITkAxYU2/J2/+rQ7NLZrLqZyLcqE pB4IQti6QEVXpMEcq8vZMl/3JMYSAk+RBvSpB4zLJ26wMjh+A6qMngYGOql7uGlJPI 8Qnd1azCIGpCxkndZtSiLIpbfVXZE2d0mYHpQqt5OdwKp+8753MKfARUE/v901ob3w R/zC7FgbU6o2Q== 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 , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org Subject: [PATCH net-next 11/11] selftests: drv-net: Add CMIS loopback netdevsim test Date: Tue, 10 Mar 2026 11:47:41 +0100 Message-ID: <20260310104743.907818-12-bjorn@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310104743.907818-1-bjorn@kernel.org> References: <20260310104743.907818-1-bjorn@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Extend loopback_nsim.py with netdevsim-specific tests for CMIS module loopback. These tests seed the EEPROM via debugfs and verify register-level behavior. Tests cover: no-module GET, all/partial capability reporting, EEPROM byte verification for enable/disable and direction switching, rejection of unsupported directions, rejection without CMIS support, and combined MAC + MODULE dump. Signed-off-by: Bj=C3=B6rn T=C3=B6pel --- .../selftests/drivers/net/hw/loopback_nsim.py | 207 +++++++++++++++++- 1 file changed, 206 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/hw/loopback_nsim.py b/tool= s/testing/selftests/drivers/net/hw/loopback_nsim.py index 5edc999d920b..26e74718098a 100755 --- a/tools/testing/selftests/drivers/net/hw/loopback_nsim.py +++ b/tools/testing/selftests/drivers/net/hw/loopback_nsim.py @@ -1,10 +1,13 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 =20 -"""Netdevsim-specific tests for MAC loopback via ethtool_ops. +"""Netdevsim-specific tests for ethtool loopback. =20 Verifies that MAC loopback entries appear in dumps, that SET operations update state correctly (both via GET and debugfs). + +Seeds the CMIS EEPROM via debugfs and verifies register-level +behavior that can only be checked with controlled EEPROM contents. """ =20 import errno @@ -15,6 +18,20 @@ from lib.py import KsftSkipEx, KsftFailEx, ksft_disrupti= ve from lib.py import EthtoolFamily, NlError from lib.py import NetDrvEnv, ip, defer =20 +# CMIS register constants matching net/ethtool/cmis_loopback.c +SFF8024_ID_QSFP_DD =3D 0x18 + +# Page 01h, Byte 142: bit 5 =3D Page 13h supported +CMIS_DIAG_PAGE13_BIT =3D 1 << 5 + +# Page 13h, Byte 128: loopback capability bits +CMIS_LB_CAP_MEDIA_OUTPUT =3D 1 << 0 +CMIS_LB_CAP_MEDIA_INPUT =3D 1 << 1 +CMIS_LB_CAP_HOST_OUTPUT =3D 1 << 2 +CMIS_LB_CAP_HOST_INPUT =3D 1 << 3 +CMIS_LB_CAP_ALL =3D (CMIS_LB_CAP_MEDIA_OUTPUT | CMIS_LB_CAP_MEDIA_INPUT | + CMIS_LB_CAP_HOST_OUTPUT | CMIS_LB_CAP_HOST_INPUT) + # Direction flags as YNL returns them DIR_NONE =3D set() DIR_NEAR_END =3D {'near-end'} @@ -35,6 +52,54 @@ def _dfs_write_u32(cfg, path, val): f.write(str(val)) =20 =20 +def _nsim_write_page_byte(cfg, page, offset, value): + """Write a single byte to a netdevsim EEPROM page via debugfs.""" + if offset < 128: + page_file =3D os.path.join(_nsim_dfs_path(cfg), + "ethtool/module/pages/0") + file_offset =3D offset + else: + page_file =3D os.path.join(_nsim_dfs_path(cfg), + f"ethtool/module/pages/{page}") + file_offset =3D offset - 128 + + with open(page_file, "r+b") as f: + f.seek(file_offset) + f.write(bytes([value])) + + +def _nsim_read_page_byte(cfg, page, offset): + """Read a single byte from a netdevsim EEPROM page via debugfs.""" + if offset < 128: + page_file =3D os.path.join(_nsim_dfs_path(cfg), + "ethtool/module/pages/0") + file_offset =3D offset + else: + page_file =3D os.path.join(_nsim_dfs_path(cfg), + f"ethtool/module/pages/{page}") + file_offset =3D offset - 128 + + with open(page_file, "rb") as f: + f.seek(file_offset) + return f.read(1)[0] + + +def _nsim_seed_cmis(cfg, caps=3DCMIS_LB_CAP_ALL): + """Seed the netdevsim EEPROM with CMIS module identity and + loopback capabilities. + """ + _nsim_write_page_byte(cfg, 0x00, 0, SFF8024_ID_QSFP_DD) + _nsim_write_page_byte(cfg, 0x01, 0x8e, CMIS_DIAG_PAGE13_BIT) + _nsim_write_page_byte(cfg, 0x13, 0x80, caps) + + +def _nsim_clear_cmis(cfg): + """Clear CMIS identity bytes left by previous tests.""" + _nsim_write_page_byte(cfg, 0x00, 0, 0) + _nsim_write_page_byte(cfg, 0x01, 0x8e, 0) + _nsim_write_page_byte(cfg, 0x13, 0x80, 0) + + def _get_loopback(cfg): results =3D cfg.ethnl.loopback_get({ 'header': {'dev-index': cfg.ifindex} @@ -118,6 +183,138 @@ def test_set_mac_unknown_name(cfg): "Expected EOPNOTSUPP for unknown name") =20 =20 +def test_get_no_module(cfg): + """GET on a device with no CMIS module returns no module entries.""" + _nsim_clear_cmis(cfg) + + entries =3D _get_loopback(cfg) + mod_entries =3D [e for e in entries if e['component'] =3D=3D 'module'] + ksft_eq(len(mod_entries), 0, "Expected no module entries without CMIS = module") + + +def test_get_all_caps(cfg): + """GET with all four CMIS loopback capabilities seeded.""" + _nsim_seed_cmis(cfg, CMIS_LB_CAP_ALL) + + entries =3D _get_loopback(cfg) + mod_entries =3D [e for e in entries if e['component'] =3D=3D 'module'] + + # Expect 2 entries (one per name), each with both directions + ksft_eq(len(mod_entries), 2, "Expected 2 module loopback entries") + + host =3D [e for e in mod_entries if e['name'] =3D=3D 'cmis-host'] + media =3D [e for e in mod_entries if e['name'] =3D=3D 'cmis-media'] + ksft_eq(len(host), 1, "Expected 1 cmis-host entry") + ksft_eq(len(media), 1, "Expected 1 cmis-media entry") + + ksft_eq(host[0]['supported'], DIR_NEAR_END | DIR_FAR_END) + ksft_eq(media[0]['supported'], DIR_NEAR_END | DIR_FAR_END) + + for e in mod_entries: + ksft_eq(e['direction'], DIR_NONE, + f"Expected direction=3Doff for {e['name']}") + + +def test_get_partial_caps(cfg): + """GET with only host-input capability advertised.""" + _nsim_seed_cmis(cfg, CMIS_LB_CAP_HOST_INPUT) + + entries =3D _get_loopback(cfg) + mod_entries =3D [e for e in entries if e['component'] =3D=3D 'module'] + ksft_eq(len(mod_entries), 1, "Expected 1 module loopback entry") + ksft_eq(mod_entries[0]['name'], 'cmis-host') + ksft_eq(mod_entries[0]['supported'], DIR_NEAR_END) + + +@ksft_disruptive +def test_set_verify_eeprom(cfg): + """SET near-end and verify the EEPROM control byte directly.""" + _nsim_seed_cmis(cfg, CMIS_LB_CAP_ALL) + + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + _set_loopback(cfg, 'module', 'cmis-host', 'near-end') + defer(_set_loopback, cfg, 'module', 'cmis-host', 0) + + # Host Side Input =3D Page 13h, Byte 183 + val =3D _nsim_read_page_byte(cfg, 0x13, 183) + ksft_eq(val, 0xff, "Host Side Input control byte should be 0xff") + + # Disable and verify + _set_loopback(cfg, 'module', 'cmis-host', 0) + val =3D _nsim_read_page_byte(cfg, 0x13, 183) + ksft_eq(val, 0x00, "Host Side Input should be 0x00 after disable") + + +@ksft_disruptive +def test_set_direction_switch_eeprom(cfg): + """Switch directions and verify both EEPROM bytes.""" + _nsim_seed_cmis(cfg, CMIS_LB_CAP_ALL) + + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + _set_loopback(cfg, 'module', 'cmis-host', 'near-end') + defer(_set_loopback, cfg, 'module', 'cmis-host', 0) + + # Switch to far-end + _set_loopback(cfg, 'module', 'cmis-host', 'far-end') + + # Near-end (Host Input, Byte 183) should be disabled + val =3D _nsim_read_page_byte(cfg, 0x13, 183) + ksft_eq(val, 0x00, "Near-end should be disabled after switch") + # Far-end (Host Output, Byte 182) should be enabled + val =3D _nsim_read_page_byte(cfg, 0x13, 182) + ksft_eq(val, 0xff, "Far-end should be enabled after switch") + + +@ksft_disruptive +def test_set_unsupported_direction(cfg): + """SET with unsupported direction should fail.""" + _nsim_seed_cmis(cfg, CMIS_LB_CAP_HOST_INPUT) # only near-end + + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + try: + _set_loopback(cfg, 'module', 'cmis-host', 'far-end') + raise KsftFailEx("Should have rejected unsupported direction") + except NlError as e: + ksft_eq(e.error, errno.EOPNOTSUPP, + "Expected EOPNOTSUPP for unsupported direction") + + +@ksft_disruptive +def test_set_no_cmis(cfg): + """SET on a device without CMIS loopback support should fail.""" + _nsim_clear_cmis(cfg) + + ip(f"link set dev {cfg.ifname} down") + defer(ip, f"link set dev {cfg.ifname} up") + + try: + _set_loopback(cfg, 'module', 'cmis-host', 'near-end') + raise KsftFailEx("Should have rejected SET without CMIS support") + except NlError as e: + ksft_eq(e.error, errno.EOPNOTSUPP, + "Expected EOPNOTSUPP without CMIS support") + + +def test_combined_dump(cfg): + """Dump should return both MAC and MODULE entries.""" + _nsim_seed_cmis(cfg, CMIS_LB_CAP_ALL) + defer(_nsim_clear_cmis, cfg) + + entries =3D _get_loopback(cfg) + mac_entries =3D [e for e in entries if e['component'] =3D=3D 'mac'] + mod_entries =3D [e for e in entries if e['component'] =3D=3D 'module'] + + ksft_eq(len(mac_entries), 1, "Expected 1 MAC entry") + ksft_eq(len(mod_entries), 2, "Expected 2 MODULE entries") + ksft_eq(mac_entries[0]['name'], 'mac') + + def main() -> None: with NetDrvEnv(__file__) as cfg: cfg.ethnl =3D EthtoolFamily() @@ -127,6 +324,14 @@ def main() -> None: test_set_mac_near_end, test_set_mac_disable, test_set_mac_unknown_name, + test_get_no_module, + test_get_all_caps, + test_get_partial_caps, + test_set_verify_eeprom, + test_set_direction_switch_eeprom, + test_set_unsupported_direction, + test_set_no_cmis, + test_combined_dump, ], args=3D(cfg, )) ksft_exit() =20 --=20 2.53.0