// SPDX-License-Identifier: GPL-2.0-only #include <linux/ethtool.h>#include <linux/phy.h>#include <linux/slab.h> #include "common.h"#include "netlink.h" /* Channels A-D only; WORST and LINK are exclusive alternatives */#define PHY_MSE_CHANNEL_COUNT 4 struct mse_req_info { struct ethnl_req_info base;}; struct mse_snapshot_entry { struct phy_mse_snapshot snapshot; int channel;}; struct mse_reply_data { struct ethnl_reply_data base; struct phy_mse_capability capability; struct mse_snapshot_entry *snapshots; unsigned int num_snapshots;}; static struct mse_reply_data *mse_repdata(const struct ethnl_reply_data *reply_base){ return container_of(reply_base, struct mse_reply_data, base);} const struct nla_policy ethnl_mse_get_policy[] = { [ETHTOOL_A_MSE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy_phy),}; static int get_snapshot_if_supported(struct phy_device *phydev, struct mse_reply_data *data, unsigned int *idx, u32 cap_bit, enum phy_mse_channel channel){ int ret; if (data->capability.supported_caps & cap_bit) { ret = phydev->drv->get_mse_snapshot(phydev, channel, &data->snapshots[*idx].snapshot); if (ret) return ret; data->snapshots[*idx].channel = channel; (*idx)++; } return 0;} static int mse_get_channels(struct phy_device *phydev, struct mse_reply_data *data){ unsigned int i = 0; int ret; if (!data->capability.supported_caps) return 0; data->snapshots = kzalloc_objs(*data->snapshots, PHY_MSE_CHANNEL_COUNT); if (!data->snapshots) return -ENOMEM; /* Priority 1: Individual channels */ ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_A, PHY_MSE_CHANNEL_A); if (ret) return ret; ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_B, PHY_MSE_CHANNEL_B); if (ret) return ret; ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_C, PHY_MSE_CHANNEL_C); if (ret) return ret; ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_D, PHY_MSE_CHANNEL_D); if (ret) return ret; /* If any individual channels were found, we are done. */ if (i > 0) { data->num_snapshots = i; return 0; } /* Priority 2: Worst channel, if no individual channels supported. */ ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_WORST_CHANNEL, PHY_MSE_CHANNEL_WORST); if (ret) return ret; /* If worst channel was found, we are done. */ if (i > 0) { data->num_snapshots = i; return 0; } /* Priority 3: Link-wide, if nothing else is supported. */ ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_LINK, PHY_MSE_CHANNEL_LINK); if (ret) return ret; data->num_snapshots = i; return 0;} static int mse_prepare_data(const struct ethnl_req_info *req_base, struct ethnl_reply_data *reply_base, const struct genl_info *info){ struct mse_reply_data *data = mse_repdata(reply_base); struct net_device *dev = reply_base->dev; struct phy_device *phydev; int ret; phydev = ethnl_req_get_phydev(req_base, info->attrs, ETHTOOL_A_MSE_HEADER, info->extack); if (IS_ERR(phydev)) return PTR_ERR(phydev); if (!phydev) return -EOPNOTSUPP; ret = ethnl_ops_begin(dev); if (ret) return ret; mutex_lock(&phydev->lock); if (!phydev->drv || !phydev->drv->get_mse_capability || !phydev->drv->get_mse_snapshot) { ret = -EOPNOTSUPP; goto out_unlock; } if (!phydev->link) { ret = -ENETDOWN; goto out_unlock; } ret = phydev->drv->get_mse_capability(phydev, &data->capability); if (ret) goto out_unlock; ret = mse_get_channels(phydev, data); out_unlock: mutex_unlock(&phydev->lock); ethnl_ops_complete(dev); if (ret) kfree(data->snapshots); return ret;} static void mse_cleanup_data(struct ethnl_reply_data *reply_base){ struct mse_reply_data *data = mse_repdata(reply_base); kfree(data->snapshots);} static int mse_reply_size(const struct ethnl_req_info *req_base, const struct ethnl_reply_data *reply_base){ const struct mse_reply_data *data = mse_repdata(reply_base); size_t len = 0; unsigned int i; /* ETHTOOL_A_MSE_CAPABILITIES */ len += nla_total_size(0); if (data->capability.supported_caps & PHY_MSE_CAP_AVG) /* ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE */ len += nla_total_size(sizeof(u64)); if (data->capability.supported_caps & (PHY_MSE_CAP_PEAK | PHY_MSE_CAP_WORST_PEAK)) /* ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE */ len += nla_total_size(sizeof(u64)); /* ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS */ len += nla_total_size(sizeof(u64)); /* ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS */ len += nla_total_size(sizeof(u64)); for (i = 0; i < data->num_snapshots; i++) { size_t snapshot_len = 0; /* Per-channel nest (e.g., ETHTOOL_A_MSE_CHANNEL_A / _B / _C / * _D / _WORST_CHANNEL / _LINK) */ snapshot_len += nla_total_size(0); if (data->capability.supported_caps & PHY_MSE_CAP_AVG) snapshot_len += nla_total_size(sizeof(u64)); if (data->capability.supported_caps & PHY_MSE_CAP_PEAK) snapshot_len += nla_total_size(sizeof(u64)); if (data->capability.supported_caps & PHY_MSE_CAP_WORST_PEAK) snapshot_len += nla_total_size(sizeof(u64)); len += snapshot_len; } return len;} static int mse_channel_to_attr(int ch){ switch (ch) { case PHY_MSE_CHANNEL_A: return ETHTOOL_A_MSE_CHANNEL_A; case PHY_MSE_CHANNEL_B: return ETHTOOL_A_MSE_CHANNEL_B; case PHY_MSE_CHANNEL_C: return ETHTOOL_A_MSE_CHANNEL_C; case PHY_MSE_CHANNEL_D: return ETHTOOL_A_MSE_CHANNEL_D; case PHY_MSE_CHANNEL_WORST: return ETHTOOL_A_MSE_WORST_CHANNEL; case PHY_MSE_CHANNEL_LINK: return ETHTOOL_A_MSE_LINK; default: return -EINVAL; }} static int mse_fill_reply(struct sk_buff *skb, const struct ethnl_req_info *req_base, const struct ethnl_reply_data *reply_base){ const struct mse_reply_data *data = mse_repdata(reply_base); struct nlattr *nest; unsigned int i; int ret; nest = nla_nest_start(skb, ETHTOOL_A_MSE_CAPABILITIES); if (!nest) return -EMSGSIZE; if (data->capability.supported_caps & PHY_MSE_CAP_AVG) { ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE, data->capability.max_average_mse); if (ret < 0) goto nla_put_nest_failure; } if (data->capability.supported_caps & (PHY_MSE_CAP_PEAK | PHY_MSE_CAP_WORST_PEAK)) { ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE, data->capability.max_peak_mse); if (ret < 0) goto nla_put_nest_failure; } ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS, data->capability.refresh_rate_ps); if (ret < 0) goto nla_put_nest_failure; ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS, data->capability.num_symbols); if (ret < 0) goto nla_put_nest_failure; nla_nest_end(skb, nest); for (i = 0; i < data->num_snapshots; i++) { const struct mse_snapshot_entry *s = &data->snapshots[i]; int chan_attr; chan_attr = mse_channel_to_attr(s->channel); if (chan_attr < 0) return chan_attr; nest = nla_nest_start(skb, chan_attr); if (!nest) return -EMSGSIZE; if (data->capability.supported_caps & PHY_MSE_CAP_AVG) { ret = nla_put_uint(skb, ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE, s->snapshot.average_mse); if (ret) goto nla_put_nest_failure; } if (data->capability.supported_caps & PHY_MSE_CAP_PEAK) { ret = nla_put_uint(skb, ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE, s->snapshot.peak_mse); if (ret) goto nla_put_nest_failure; } if (data->capability.supported_caps & PHY_MSE_CAP_WORST_PEAK) { ret = nla_put_uint(skb, ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE, s->snapshot.worst_peak_mse); if (ret) goto nla_put_nest_failure; } nla_nest_end(skb, nest); } return 0; nla_put_nest_failure: nla_nest_cancel(skb, nest); return ret;} const struct ethnl_request_ops ethnl_mse_request_ops = { .request_cmd = ETHTOOL_MSG_MSE_GET, .reply_cmd = ETHTOOL_MSG_MSE_GET_REPLY, .hdr_attr = ETHTOOL_A_MSE_HEADER, .req_info_size = sizeof(struct mse_req_info), .reply_data_size = sizeof(struct mse_reply_data), .prepare_data = mse_prepare_data, .cleanup_data = mse_cleanup_data, .reply_size = mse_reply_size, .fill_reply = mse_fill_reply,};
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/ethtool.h>
#include <linux/phy.h>
#include <linux/slab.h>
#include "common.h"
#include "netlink.h"
/* Channels A-D only; WORST and LINK are exclusive alternatives */
#define PHY_MSE_CHANNEL_COUNT 4
struct mse_req_info {
struct ethnl_req_info base;
};
struct mse_snapshot_entry {
struct phy_mse_snapshot snapshot;
int channel;
};
struct mse_reply_data {
struct ethnl_reply_data base;
struct phy_mse_capability capability;
struct mse_snapshot_entry *snapshots;
unsigned int num_snapshots;
};
static struct mse_reply_data *
mse_repdata(const struct ethnl_reply_data *reply_base)
{
return container_of(reply_base, struct mse_reply_data, base);
}
const struct nla_policy ethnl_mse_get_policy[] = {
[ETHTOOL_A_MSE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy_phy),
};
static int get_snapshot_if_supported(struct phy_device *phydev,
struct mse_reply_data *data,
unsigned int *idx, u32 cap_bit,
enum phy_mse_channel channel)
{
int ret;
if (data->capability.supported_caps & cap_bit) {
ret = phydev->drv->get_mse_snapshot(phydev, channel,
&data->snapshots[*idx].snapshot);
if (ret)
return ret;
data->snapshots[*idx].channel = channel;
(*idx)++;
}
return 0;
}
static int mse_get_channels(struct phy_device *phydev,
struct mse_reply_data *data)
{
unsigned int i = 0;
int ret;
if (!data->capability.supported_caps)
return 0;
data->snapshots = kzalloc_objs(*data->snapshots, PHY_MSE_CHANNEL_COUNT);
if (!data->snapshots)
return -ENOMEM;
/* Priority 1: Individual channels */
ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_A,
PHY_MSE_CHANNEL_A);
if (ret)
return ret;
ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_B,
PHY_MSE_CHANNEL_B);
if (ret)
return ret;
ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_C,
PHY_MSE_CHANNEL_C);
if (ret)
return ret;
ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_D,
PHY_MSE_CHANNEL_D);
if (ret)
return ret;
/* If any individual channels were found, we are done. */
if (i > 0) {
data->num_snapshots = i;
return 0;
}
/* Priority 2: Worst channel, if no individual channels supported. */
ret = get_snapshot_if_supported(phydev, data, &i,
PHY_MSE_CAP_WORST_CHANNEL,
PHY_MSE_CHANNEL_WORST);
if (ret)
return ret;
/* If worst channel was found, we are done. */
if (i > 0) {
data->num_snapshots = i;
return 0;
}
/* Priority 3: Link-wide, if nothing else is supported. */
ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_LINK,
PHY_MSE_CHANNEL_LINK);
if (ret)
return ret;
data->num_snapshots = i;
return 0;
}
static int mse_prepare_data(const struct ethnl_req_info *req_base,
struct ethnl_reply_data *reply_base,
const struct genl_info *info)
{
struct mse_reply_data *data = mse_repdata(reply_base);
struct net_device *dev = reply_base->dev;
struct phy_device *phydev;
int ret;
phydev = ethnl_req_get_phydev(req_base, info->attrs,
ETHTOOL_A_MSE_HEADER, info->extack);
if (IS_ERR(phydev))
return PTR_ERR(phydev);
if (!phydev)
return -EOPNOTSUPP;
ret = ethnl_ops_begin(dev);
if (ret)
return ret;
mutex_lock(&phydev->lock);
if (!phydev->drv || !phydev->drv->get_mse_capability ||
!phydev->drv->get_mse_snapshot) {
ret = -EOPNOTSUPP;
goto out_unlock;
}
if (!phydev->link) {
ret = -ENETDOWN;
goto out_unlock;
}
ret = phydev->drv->get_mse_capability(phydev, &data->capability);
if (ret)
goto out_unlock;
ret = mse_get_channels(phydev, data);
out_unlock:
mutex_unlock(&phydev->lock);
ethnl_ops_complete(dev);
if (ret)
kfree(data->snapshots);
return ret;
}
static void mse_cleanup_data(struct ethnl_reply_data *reply_base)
{
struct mse_reply_data *data = mse_repdata(reply_base);
kfree(data->snapshots);
}
static int mse_reply_size(const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct mse_reply_data *data = mse_repdata(reply_base);
size_t len = 0;
unsigned int i;
/* ETHTOOL_A_MSE_CAPABILITIES */
len += nla_total_size(0);
if (data->capability.supported_caps & PHY_MSE_CAP_AVG)
/* ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE */
len += nla_total_size(sizeof(u64));
if (data->capability.supported_caps & (PHY_MSE_CAP_PEAK |
PHY_MSE_CAP_WORST_PEAK))
/* ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE */
len += nla_total_size(sizeof(u64));
/* ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS */
len += nla_total_size(sizeof(u64));
/* ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS */
len += nla_total_size(sizeof(u64));
for (i = 0; i < data->num_snapshots; i++) {
size_t snapshot_len = 0;
/* Per-channel nest (e.g., ETHTOOL_A_MSE_CHANNEL_A / _B / _C /
* _D / _WORST_CHANNEL / _LINK)
*/
snapshot_len += nla_total_size(0);
if (data->capability.supported_caps & PHY_MSE_CAP_AVG)
snapshot_len += nla_total_size(sizeof(u64));
if (data->capability.supported_caps & PHY_MSE_CAP_PEAK)
snapshot_len += nla_total_size(sizeof(u64));
if (data->capability.supported_caps & PHY_MSE_CAP_WORST_PEAK)
snapshot_len += nla_total_size(sizeof(u64));
len += snapshot_len;
}
return len;
}
static int mse_channel_to_attr(int ch)
{
switch (ch) {
case PHY_MSE_CHANNEL_A:
return ETHTOOL_A_MSE_CHANNEL_A;
case PHY_MSE_CHANNEL_B:
return ETHTOOL_A_MSE_CHANNEL_B;
case PHY_MSE_CHANNEL_C:
return ETHTOOL_A_MSE_CHANNEL_C;
case PHY_MSE_CHANNEL_D:
return ETHTOOL_A_MSE_CHANNEL_D;
case PHY_MSE_CHANNEL_WORST:
return ETHTOOL_A_MSE_WORST_CHANNEL;
case PHY_MSE_CHANNEL_LINK:
return ETHTOOL_A_MSE_LINK;
default:
return -EINVAL;
}
}
static int mse_fill_reply(struct sk_buff *skb,
const struct ethnl_req_info *req_base,
const struct ethnl_reply_data *reply_base)
{
const struct mse_reply_data *data = mse_repdata(reply_base);
struct nlattr *nest;
unsigned int i;
int ret;
nest = nla_nest_start(skb, ETHTOOL_A_MSE_CAPABILITIES);
if (!nest)
return -EMSGSIZE;
if (data->capability.supported_caps & PHY_MSE_CAP_AVG) {
ret = nla_put_uint(skb,
ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE,
data->capability.max_average_mse);
if (ret < 0)
goto nla_put_nest_failure;
}
if (data->capability.supported_caps & (PHY_MSE_CAP_PEAK |
PHY_MSE_CAP_WORST_PEAK)) {
ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE,
data->capability.max_peak_mse);
if (ret < 0)
goto nla_put_nest_failure;
}
ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS,
data->capability.refresh_rate_ps);
if (ret < 0)
goto nla_put_nest_failure;
ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS,
data->capability.num_symbols);
if (ret < 0)
goto nla_put_nest_failure;
nla_nest_end(skb, nest);
for (i = 0; i < data->num_snapshots; i++) {
const struct mse_snapshot_entry *s = &data->snapshots[i];
int chan_attr;
chan_attr = mse_channel_to_attr(s->channel);
if (chan_attr < 0)
return chan_attr;
nest = nla_nest_start(skb, chan_attr);
if (!nest)
return -EMSGSIZE;
if (data->capability.supported_caps & PHY_MSE_CAP_AVG) {
ret = nla_put_uint(skb,
ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE,
s->snapshot.average_mse);
if (ret)
goto nla_put_nest_failure;
}
if (data->capability.supported_caps & PHY_MSE_CAP_PEAK) {
ret = nla_put_uint(skb, ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE,
s->snapshot.peak_mse);
if (ret)
goto nla_put_nest_failure;
}
if (data->capability.supported_caps & PHY_MSE_CAP_WORST_PEAK) {
ret = nla_put_uint(skb,
ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE,
s->snapshot.worst_peak_mse);
if (ret)
goto nla_put_nest_failure;
}
nla_nest_end(skb, nest);
}
return 0;
nla_put_nest_failure:
nla_nest_cancel(skb, nest);
return ret;
}
const struct ethnl_request_ops ethnl_mse_request_ops = {
.request_cmd = ETHTOOL_MSG_MSE_GET,
.reply_cmd = ETHTOOL_MSG_MSE_GET_REPLY,
.hdr_attr = ETHTOOL_A_MSE_HEADER,
.req_info_size = sizeof(struct mse_req_info),
.reply_data_size = sizeof(struct mse_reply_data),
.prepare_data = mse_prepare_data,
.cleanup_data = mse_cleanup_data,
.reply_size = mse_reply_size,
.fill_reply = mse_fill_reply,
};