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örn Töpel <[email protected]> --- include/linux/ethtool.h | 10 ++++++++ net/ethtool/loopback.c | 56 ++++++++++++++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 81a9c186564d..971be759a915 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -1168,6 +1168,9 @@ struct kernel_ethtool_ts_info { * @get_mm: Query the 802.3 MAC Merge layer state. * @set_mm: Set the 802.3 MAC Merge layer parameters. * @get_mm_stats: Query the 802.3 MAC Merge layer statistics. + * @get_loopback: Get the state of a loopback entry identified by name and id. + * @get_loopback_by_index: Get the state of a loopback entry by its index. + * @set_loopback: Set the loopback mode for a given entry. * * All operations are optional (i.e. the function pointer may be set * to %NULL) and callers must take this into account. Callers must @@ -1337,6 +1340,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 *stats); + 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); }; int ethtool_check_ops(const struct ethtool_ops *ops); diff --git a/net/ethtool/loopback.c b/net/ethtool/loopback.c index 2d0ad62ce42f..60eb7b94a716 100644 --- a/net/ethtool/loopback.c +++ b/net/ethtool/loopback.c @@ -88,6 +88,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: @@ -95,10 +99,22 @@ static int loopback_get(struct net_device *dev, } } -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; + } } static int loopback_prepare_data(const struct ethnl_req_info *req_base, @@ -118,7 +134,8 @@ static int loopback_prepare_data(const struct ethnl_req_info *req_base, ret = loopback_get(dev, req_info->component, req_info->id, req_info->name, &data->entry); else - ret = loopback_get_by_index(dev, req_info->index, &data->entry); + ret = loopback_get_by_index(dev, req_info->component, + req_info->index, &data->entry); ethnl_ops_complete(dev); @@ -235,6 +252,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: @@ -284,20 +305,31 @@ static int loopback_dump_one_dev(struct sk_buff *skb, { struct loopback_req_info *req_info = container_of(ctx->req_info, struct loopback_req_info, base); + /* pos_sub encodes: upper 16 bits = component phase, lower 16 = index + * within that component. dump_one_dev is called repeatedly with + * increasing pos_sub until all components are exhausted. + */ + enum ethtool_loopback_component phase = *pos_sub >> 16; + u32 idx = *pos_sub & 0xffff; int ret; - for (;; (*pos_sub)++) { - req_info->index = *pos_sub; - ret = ethnl_default_dump_one(skb, ctx->req_info->dev, ctx, - info); - if (ret == -EOPNOTSUPP) - break; - if (ret) - return ret; + for (; phase <= ETHTOOL_LOOPBACK_COMPONENT_MODULE; phase++) { + for (;; idx++) { + req_info->component = phase; + req_info->index = idx; + ret = ethnl_default_dump_one(skb, ctx->req_info->dev, + ctx, info); + if (ret == -EOPNOTSUPP) + break; + if (ret) { + *pos_sub = ((unsigned long)phase << 16) | idx; + return ret; + } + } + idx = 0; } *pos_sub = 0; - return 0; } -- 2.53.0

