linux/net/ethtool/strset.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2
   3#include <linux/ethtool.h>
   4#include <linux/phy.h>
   5#include "netlink.h"
   6#include "common.h"
   7
   8struct strset_info {
   9        bool per_dev;
  10        bool free_strings;
  11        unsigned int count;
  12        const char (*strings)[ETH_GSTRING_LEN];
  13};
  14
  15static const struct strset_info info_template[] = {
  16        [ETH_SS_TEST] = {
  17                .per_dev        = true,
  18        },
  19        [ETH_SS_STATS] = {
  20                .per_dev        = true,
  21        },
  22        [ETH_SS_PRIV_FLAGS] = {
  23                .per_dev        = true,
  24        },
  25        [ETH_SS_FEATURES] = {
  26                .per_dev        = false,
  27                .count          = ARRAY_SIZE(netdev_features_strings),
  28                .strings        = netdev_features_strings,
  29        },
  30        [ETH_SS_RSS_HASH_FUNCS] = {
  31                .per_dev        = false,
  32                .count          = ARRAY_SIZE(rss_hash_func_strings),
  33                .strings        = rss_hash_func_strings,
  34        },
  35        [ETH_SS_TUNABLES] = {
  36                .per_dev        = false,
  37                .count          = ARRAY_SIZE(tunable_strings),
  38                .strings        = tunable_strings,
  39        },
  40        [ETH_SS_PHY_STATS] = {
  41                .per_dev        = true,
  42        },
  43        [ETH_SS_PHY_TUNABLES] = {
  44                .per_dev        = false,
  45                .count          = ARRAY_SIZE(phy_tunable_strings),
  46                .strings        = phy_tunable_strings,
  47        },
  48        [ETH_SS_LINK_MODES] = {
  49                .per_dev        = false,
  50                .count          = __ETHTOOL_LINK_MODE_MASK_NBITS,
  51                .strings        = link_mode_names,
  52        },
  53        [ETH_SS_MSG_CLASSES] = {
  54                .per_dev        = false,
  55                .count          = NETIF_MSG_CLASS_COUNT,
  56                .strings        = netif_msg_class_names,
  57        },
  58        [ETH_SS_WOL_MODES] = {
  59                .per_dev        = false,
  60                .count          = WOL_MODE_COUNT,
  61                .strings        = wol_mode_names,
  62        },
  63        [ETH_SS_SOF_TIMESTAMPING] = {
  64                .per_dev        = false,
  65                .count          = __SOF_TIMESTAMPING_CNT,
  66                .strings        = sof_timestamping_names,
  67        },
  68        [ETH_SS_TS_TX_TYPES] = {
  69                .per_dev        = false,
  70                .count          = __HWTSTAMP_TX_CNT,
  71                .strings        = ts_tx_type_names,
  72        },
  73        [ETH_SS_TS_RX_FILTERS] = {
  74                .per_dev        = false,
  75                .count          = __HWTSTAMP_FILTER_CNT,
  76                .strings        = ts_rx_filter_names,
  77        },
  78        [ETH_SS_UDP_TUNNEL_TYPES] = {
  79                .per_dev        = false,
  80                .count          = __ETHTOOL_UDP_TUNNEL_TYPE_CNT,
  81                .strings        = udp_tunnel_type_names,
  82        },
  83        [ETH_SS_STATS_STD] = {
  84                .per_dev        = false,
  85                .count          = __ETHTOOL_STATS_CNT,
  86                .strings        = stats_std_names,
  87        },
  88        [ETH_SS_STATS_ETH_PHY] = {
  89                .per_dev        = false,
  90                .count          = __ETHTOOL_A_STATS_ETH_PHY_CNT,
  91                .strings        = stats_eth_phy_names,
  92        },
  93        [ETH_SS_STATS_ETH_MAC] = {
  94                .per_dev        = false,
  95                .count          = __ETHTOOL_A_STATS_ETH_MAC_CNT,
  96                .strings        = stats_eth_mac_names,
  97        },
  98        [ETH_SS_STATS_ETH_CTRL] = {
  99                .per_dev        = false,
 100                .count          = __ETHTOOL_A_STATS_ETH_CTRL_CNT,
 101                .strings        = stats_eth_ctrl_names,
 102        },
 103        [ETH_SS_STATS_RMON] = {
 104                .per_dev        = false,
 105                .count          = __ETHTOOL_A_STATS_RMON_CNT,
 106                .strings        = stats_rmon_names,
 107        },
 108};
 109
 110struct strset_req_info {
 111        struct ethnl_req_info           base;
 112        u32                             req_ids;
 113        bool                            counts_only;
 114};
 115
 116#define STRSET_REQINFO(__req_base) \
 117        container_of(__req_base, struct strset_req_info, base)
 118
 119struct strset_reply_data {
 120        struct ethnl_reply_data         base;
 121        struct strset_info              sets[ETH_SS_COUNT];
 122};
 123
 124#define STRSET_REPDATA(__reply_base) \
 125        container_of(__reply_base, struct strset_reply_data, base)
 126
 127const struct nla_policy ethnl_strset_get_policy[] = {
 128        [ETHTOOL_A_STRSET_HEADER]       =
 129                NLA_POLICY_NESTED(ethnl_header_policy),
 130        [ETHTOOL_A_STRSET_STRINGSETS]   = { .type = NLA_NESTED },
 131        [ETHTOOL_A_STRSET_COUNTS_ONLY]  = { .type = NLA_FLAG },
 132};
 133
 134static const struct nla_policy get_stringset_policy[] = {
 135        [ETHTOOL_A_STRINGSET_ID]        = { .type = NLA_U32 },
 136};
 137
 138/**
 139 * strset_include() - test if a string set should be included in reply
 140 * @info: parsed client request
 141 * @data: pointer to request data structure
 142 * @id:   id of string set to check (ETH_SS_* constants)
 143 */
 144static bool strset_include(const struct strset_req_info *info,
 145                           const struct strset_reply_data *data, u32 id)
 146{
 147        bool per_dev;
 148
 149        BUILD_BUG_ON(ETH_SS_COUNT >= BITS_PER_BYTE * sizeof(info->req_ids));
 150
 151        if (info->req_ids)
 152                return info->req_ids & (1U << id);
 153        per_dev = data->sets[id].per_dev;
 154        if (!per_dev && !data->sets[id].strings)
 155                return false;
 156
 157        return data->base.dev ? per_dev : !per_dev;
 158}
 159
 160static int strset_get_id(const struct nlattr *nest, u32 *val,
 161                         struct netlink_ext_ack *extack)
 162{
 163        struct nlattr *tb[ARRAY_SIZE(get_stringset_policy)];
 164        int ret;
 165
 166        ret = nla_parse_nested(tb, ARRAY_SIZE(get_stringset_policy) - 1, nest,
 167                               get_stringset_policy, extack);
 168        if (ret < 0)
 169                return ret;
 170        if (!tb[ETHTOOL_A_STRINGSET_ID])
 171                return -EINVAL;
 172
 173        *val = nla_get_u32(tb[ETHTOOL_A_STRINGSET_ID]);
 174        return 0;
 175}
 176
 177static const struct nla_policy strset_stringsets_policy[] = {
 178        [ETHTOOL_A_STRINGSETS_STRINGSET]        = { .type = NLA_NESTED },
 179};
 180
 181static int strset_parse_request(struct ethnl_req_info *req_base,
 182                                struct nlattr **tb,
 183                                struct netlink_ext_ack *extack)
 184{
 185        struct strset_req_info *req_info = STRSET_REQINFO(req_base);
 186        struct nlattr *nest = tb[ETHTOOL_A_STRSET_STRINGSETS];
 187        struct nlattr *attr;
 188        int rem, ret;
 189
 190        if (!nest)
 191                return 0;
 192        ret = nla_validate_nested(nest,
 193                                  ARRAY_SIZE(strset_stringsets_policy) - 1,
 194                                  strset_stringsets_policy, extack);
 195        if (ret < 0)
 196                return ret;
 197
 198        req_info->counts_only = tb[ETHTOOL_A_STRSET_COUNTS_ONLY];
 199        nla_for_each_nested(attr, nest, rem) {
 200                u32 id;
 201
 202                if (WARN_ONCE(nla_type(attr) != ETHTOOL_A_STRINGSETS_STRINGSET,
 203                              "unexpected attrtype %u in ETHTOOL_A_STRSET_STRINGSETS\n",
 204                              nla_type(attr)))
 205                        return -EINVAL;
 206
 207                ret = strset_get_id(attr, &id, extack);
 208                if (ret < 0)
 209                        return ret;
 210                if (id >= ETH_SS_COUNT) {
 211                        NL_SET_ERR_MSG_ATTR(extack, attr,
 212                                            "unknown string set id");
 213                        return -EOPNOTSUPP;
 214                }
 215
 216                req_info->req_ids |= (1U << id);
 217        }
 218
 219        return 0;
 220}
 221
 222static void strset_cleanup_data(struct ethnl_reply_data *reply_base)
 223{
 224        struct strset_reply_data *data = STRSET_REPDATA(reply_base);
 225        unsigned int i;
 226
 227        for (i = 0; i < ETH_SS_COUNT; i++)
 228                if (data->sets[i].free_strings) {
 229                        kfree(data->sets[i].strings);
 230                        data->sets[i].strings = NULL;
 231                        data->sets[i].free_strings = false;
 232                }
 233}
 234
 235static int strset_prepare_set(struct strset_info *info, struct net_device *dev,
 236                              unsigned int id, bool counts_only)
 237{
 238        const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops;
 239        const struct ethtool_ops *ops = dev->ethtool_ops;
 240        void *strings;
 241        int count, ret;
 242
 243        if (id == ETH_SS_PHY_STATS && dev->phydev &&
 244            !ops->get_ethtool_phy_stats && phy_ops &&
 245            phy_ops->get_sset_count)
 246                ret = phy_ops->get_sset_count(dev->phydev);
 247        else if (ops->get_sset_count && ops->get_strings)
 248                ret = ops->get_sset_count(dev, id);
 249        else
 250                ret = -EOPNOTSUPP;
 251        if (ret <= 0) {
 252                info->count = 0;
 253                return 0;
 254        }
 255
 256        count = ret;
 257        if (!counts_only) {
 258                strings = kcalloc(count, ETH_GSTRING_LEN, GFP_KERNEL);
 259                if (!strings)
 260                        return -ENOMEM;
 261                if (id == ETH_SS_PHY_STATS && dev->phydev &&
 262                    !ops->get_ethtool_phy_stats && phy_ops &&
 263                    phy_ops->get_strings)
 264                        phy_ops->get_strings(dev->phydev, strings);
 265                else
 266                        ops->get_strings(dev, id, strings);
 267                info->strings = strings;
 268                info->free_strings = true;
 269        }
 270        info->count = count;
 271
 272        return 0;
 273}
 274
 275static int strset_prepare_data(const struct ethnl_req_info *req_base,
 276                               struct ethnl_reply_data *reply_base,
 277                               struct genl_info *info)
 278{
 279        const struct strset_req_info *req_info = STRSET_REQINFO(req_base);
 280        struct strset_reply_data *data = STRSET_REPDATA(reply_base);
 281        struct net_device *dev = reply_base->dev;
 282        unsigned int i;
 283        int ret;
 284
 285        BUILD_BUG_ON(ARRAY_SIZE(info_template) != ETH_SS_COUNT);
 286        memcpy(&data->sets, &info_template, sizeof(data->sets));
 287
 288        if (!dev) {
 289                for (i = 0; i < ETH_SS_COUNT; i++) {
 290                        if ((req_info->req_ids & (1U << i)) &&
 291                            data->sets[i].per_dev) {
 292                                if (info)
 293                                        GENL_SET_ERR_MSG(info, "requested per device strings without dev");
 294                                return -EINVAL;
 295                        }
 296                }
 297                return 0;
 298        }
 299
 300        ret = ethnl_ops_begin(dev);
 301        if (ret < 0)
 302                goto err_strset;
 303        for (i = 0; i < ETH_SS_COUNT; i++) {
 304                if (!strset_include(req_info, data, i) ||
 305                    !data->sets[i].per_dev)
 306                        continue;
 307
 308                ret = strset_prepare_set(&data->sets[i], dev, i,
 309                                         req_info->counts_only);
 310                if (ret < 0)
 311                        goto err_ops;
 312        }
 313        ethnl_ops_complete(dev);
 314
 315        return 0;
 316err_ops:
 317        ethnl_ops_complete(dev);
 318err_strset:
 319        strset_cleanup_data(reply_base);
 320        return ret;
 321}
 322
 323/* calculate size of ETHTOOL_A_STRSET_STRINGSET nest for one string set */
 324static int strset_set_size(const struct strset_info *info, bool counts_only)
 325{
 326        unsigned int len = 0;
 327        unsigned int i;
 328
 329        if (info->count == 0)
 330                return 0;
 331        if (counts_only)
 332                return nla_total_size(2 * nla_total_size(sizeof(u32)));
 333
 334        for (i = 0; i < info->count; i++) {
 335                const char *str = info->strings[i];
 336
 337                /* ETHTOOL_A_STRING_INDEX, ETHTOOL_A_STRING_VALUE, nest */
 338                len += nla_total_size(nla_total_size(sizeof(u32)) +
 339                                      ethnl_strz_size(str));
 340        }
 341        /* ETHTOOL_A_STRINGSET_ID, ETHTOOL_A_STRINGSET_COUNT */
 342        len = 2 * nla_total_size(sizeof(u32)) + nla_total_size(len);
 343
 344        return nla_total_size(len);
 345}
 346
 347static int strset_reply_size(const struct ethnl_req_info *req_base,
 348                             const struct ethnl_reply_data *reply_base)
 349{
 350        const struct strset_req_info *req_info = STRSET_REQINFO(req_base);
 351        const struct strset_reply_data *data = STRSET_REPDATA(reply_base);
 352        unsigned int i;
 353        int len = 0;
 354        int ret;
 355
 356        len += nla_total_size(0); /* ETHTOOL_A_STRSET_STRINGSETS */
 357
 358        for (i = 0; i < ETH_SS_COUNT; i++) {
 359                const struct strset_info *set_info = &data->sets[i];
 360
 361                if (!strset_include(req_info, data, i))
 362                        continue;
 363
 364                ret = strset_set_size(set_info, req_info->counts_only);
 365                if (ret < 0)
 366                        return ret;
 367                len += ret;
 368        }
 369
 370        return len;
 371}
 372
 373/* fill one string into reply */
 374static int strset_fill_string(struct sk_buff *skb,
 375                              const struct strset_info *set_info, u32 idx)
 376{
 377        struct nlattr *string_attr;
 378        const char *value;
 379
 380        value = set_info->strings[idx];
 381
 382        string_attr = nla_nest_start(skb, ETHTOOL_A_STRINGS_STRING);
 383        if (!string_attr)
 384                return -EMSGSIZE;
 385        if (nla_put_u32(skb, ETHTOOL_A_STRING_INDEX, idx) ||
 386            ethnl_put_strz(skb, ETHTOOL_A_STRING_VALUE, value))
 387                goto nla_put_failure;
 388        nla_nest_end(skb, string_attr);
 389
 390        return 0;
 391nla_put_failure:
 392        nla_nest_cancel(skb, string_attr);
 393        return -EMSGSIZE;
 394}
 395
 396/* fill one string set into reply */
 397static int strset_fill_set(struct sk_buff *skb,
 398                           const struct strset_info *set_info, u32 id,
 399                           bool counts_only)
 400{
 401        struct nlattr *stringset_attr;
 402        struct nlattr *strings_attr;
 403        unsigned int i;
 404
 405        if (!set_info->per_dev && !set_info->strings)
 406                return -EOPNOTSUPP;
 407        if (set_info->count == 0)
 408                return 0;
 409        stringset_attr = nla_nest_start(skb, ETHTOOL_A_STRINGSETS_STRINGSET);
 410        if (!stringset_attr)
 411                return -EMSGSIZE;
 412
 413        if (nla_put_u32(skb, ETHTOOL_A_STRINGSET_ID, id) ||
 414            nla_put_u32(skb, ETHTOOL_A_STRINGSET_COUNT, set_info->count))
 415                goto nla_put_failure;
 416
 417        if (!counts_only) {
 418                strings_attr = nla_nest_start(skb, ETHTOOL_A_STRINGSET_STRINGS);
 419                if (!strings_attr)
 420                        goto nla_put_failure;
 421                for (i = 0; i < set_info->count; i++) {
 422                        if (strset_fill_string(skb, set_info, i) < 0)
 423                                goto nla_put_failure;
 424                }
 425                nla_nest_end(skb, strings_attr);
 426        }
 427
 428        nla_nest_end(skb, stringset_attr);
 429        return 0;
 430
 431nla_put_failure:
 432        nla_nest_cancel(skb, stringset_attr);
 433        return -EMSGSIZE;
 434}
 435
 436static int strset_fill_reply(struct sk_buff *skb,
 437                             const struct ethnl_req_info *req_base,
 438                             const struct ethnl_reply_data *reply_base)
 439{
 440        const struct strset_req_info *req_info = STRSET_REQINFO(req_base);
 441        const struct strset_reply_data *data = STRSET_REPDATA(reply_base);
 442        struct nlattr *nest;
 443        unsigned int i;
 444        int ret;
 445
 446        nest = nla_nest_start(skb, ETHTOOL_A_STRSET_STRINGSETS);
 447        if (!nest)
 448                return -EMSGSIZE;
 449
 450        for (i = 0; i < ETH_SS_COUNT; i++) {
 451                if (strset_include(req_info, data, i)) {
 452                        ret = strset_fill_set(skb, &data->sets[i], i,
 453                                              req_info->counts_only);
 454                        if (ret < 0)
 455                                goto nla_put_failure;
 456                }
 457        }
 458
 459        nla_nest_end(skb, nest);
 460        return 0;
 461
 462nla_put_failure:
 463        nla_nest_cancel(skb, nest);
 464        return ret;
 465}
 466
 467const struct ethnl_request_ops ethnl_strset_request_ops = {
 468        .request_cmd            = ETHTOOL_MSG_STRSET_GET,
 469        .reply_cmd              = ETHTOOL_MSG_STRSET_GET_REPLY,
 470        .hdr_attr               = ETHTOOL_A_STRSET_HEADER,
 471        .req_info_size          = sizeof(struct strset_req_info),
 472        .reply_data_size        = sizeof(struct strset_reply_data),
 473        .allow_nodev_do         = true,
 474
 475        .parse_request          = strset_parse_request,
 476        .prepare_data           = strset_prepare_data,
 477        .reply_size             = strset_reply_size,
 478        .fill_reply             = strset_fill_reply,
 479        .cleanup_data           = strset_cleanup_data,
 480};
 481