| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- // 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 = 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,
- };
|