330 lines
8.1 KiB
C
330 lines
8.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include <linux/ethtool.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "netlink.h"
|
|
#include "common.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 = kcalloc(PHY_MSE_CHANNEL_COUNT,
|
|
sizeof(*data->snapshots), GFP_KERNEL);
|
|
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,
|
|
};
|