18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci 38c2ecf20Sopenharmony_ci#include "netlink.h" 48c2ecf20Sopenharmony_ci#include "common.h" 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_cistruct pause_req_info { 78c2ecf20Sopenharmony_ci struct ethnl_req_info base; 88c2ecf20Sopenharmony_ci}; 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_cistruct pause_reply_data { 118c2ecf20Sopenharmony_ci struct ethnl_reply_data base; 128c2ecf20Sopenharmony_ci struct ethtool_pauseparam pauseparam; 138c2ecf20Sopenharmony_ci struct ethtool_pause_stats pausestat; 148c2ecf20Sopenharmony_ci}; 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define PAUSE_REPDATA(__reply_base) \ 178c2ecf20Sopenharmony_ci container_of(__reply_base, struct pause_reply_data, base) 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ciconst struct nla_policy ethnl_pause_get_policy[] = { 208c2ecf20Sopenharmony_ci [ETHTOOL_A_PAUSE_HEADER] = 218c2ecf20Sopenharmony_ci NLA_POLICY_NESTED(ethnl_header_policy_stats), 228c2ecf20Sopenharmony_ci}; 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistatic void ethtool_stats_init(u64 *stats, unsigned int n) 258c2ecf20Sopenharmony_ci{ 268c2ecf20Sopenharmony_ci while (n--) 278c2ecf20Sopenharmony_ci stats[n] = ETHTOOL_STAT_NOT_SET; 288c2ecf20Sopenharmony_ci} 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistatic int pause_prepare_data(const struct ethnl_req_info *req_base, 318c2ecf20Sopenharmony_ci struct ethnl_reply_data *reply_base, 328c2ecf20Sopenharmony_ci struct genl_info *info) 338c2ecf20Sopenharmony_ci{ 348c2ecf20Sopenharmony_ci struct pause_reply_data *data = PAUSE_REPDATA(reply_base); 358c2ecf20Sopenharmony_ci struct net_device *dev = reply_base->dev; 368c2ecf20Sopenharmony_ci int ret; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci if (!dev->ethtool_ops->get_pauseparam) 398c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci ethtool_stats_init((u64 *)&data->pausestat, 428c2ecf20Sopenharmony_ci sizeof(data->pausestat) / 8); 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci ret = ethnl_ops_begin(dev); 458c2ecf20Sopenharmony_ci if (ret < 0) 468c2ecf20Sopenharmony_ci return ret; 478c2ecf20Sopenharmony_ci dev->ethtool_ops->get_pauseparam(dev, &data->pauseparam); 488c2ecf20Sopenharmony_ci if (req_base->flags & ETHTOOL_FLAG_STATS && 498c2ecf20Sopenharmony_ci dev->ethtool_ops->get_pause_stats) 508c2ecf20Sopenharmony_ci dev->ethtool_ops->get_pause_stats(dev, &data->pausestat); 518c2ecf20Sopenharmony_ci ethnl_ops_complete(dev); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci return 0; 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic int pause_reply_size(const struct ethnl_req_info *req_base, 578c2ecf20Sopenharmony_ci const struct ethnl_reply_data *reply_base) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci int n = nla_total_size(sizeof(u8)) + /* _PAUSE_AUTONEG */ 608c2ecf20Sopenharmony_ci nla_total_size(sizeof(u8)) + /* _PAUSE_RX */ 618c2ecf20Sopenharmony_ci nla_total_size(sizeof(u8)); /* _PAUSE_TX */ 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci if (req_base->flags & ETHTOOL_FLAG_STATS) 648c2ecf20Sopenharmony_ci n += nla_total_size(0) + /* _PAUSE_STATS */ 658c2ecf20Sopenharmony_ci nla_total_size_64bit(sizeof(u64)) * ETHTOOL_PAUSE_STAT_CNT; 668c2ecf20Sopenharmony_ci return n; 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int ethtool_put_stat(struct sk_buff *skb, u64 val, u16 attrtype, 708c2ecf20Sopenharmony_ci u16 padtype) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci if (val == ETHTOOL_STAT_NOT_SET) 738c2ecf20Sopenharmony_ci return 0; 748c2ecf20Sopenharmony_ci if (nla_put_u64_64bit(skb, attrtype, val, padtype)) 758c2ecf20Sopenharmony_ci return -EMSGSIZE; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci return 0; 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic int pause_put_stats(struct sk_buff *skb, 818c2ecf20Sopenharmony_ci const struct ethtool_pause_stats *pause_stats) 828c2ecf20Sopenharmony_ci{ 838c2ecf20Sopenharmony_ci const u16 pad = ETHTOOL_A_PAUSE_STAT_PAD; 848c2ecf20Sopenharmony_ci struct nlattr *nest; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci nest = nla_nest_start(skb, ETHTOOL_A_PAUSE_STATS); 878c2ecf20Sopenharmony_ci if (!nest) 888c2ecf20Sopenharmony_ci return -EMSGSIZE; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci if (ethtool_put_stat(skb, pause_stats->tx_pause_frames, 918c2ecf20Sopenharmony_ci ETHTOOL_A_PAUSE_STAT_TX_FRAMES, pad) || 928c2ecf20Sopenharmony_ci ethtool_put_stat(skb, pause_stats->rx_pause_frames, 938c2ecf20Sopenharmony_ci ETHTOOL_A_PAUSE_STAT_RX_FRAMES, pad)) 948c2ecf20Sopenharmony_ci goto err_cancel; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci nla_nest_end(skb, nest); 978c2ecf20Sopenharmony_ci return 0; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cierr_cancel: 1008c2ecf20Sopenharmony_ci nla_nest_cancel(skb, nest); 1018c2ecf20Sopenharmony_ci return -EMSGSIZE; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic int pause_fill_reply(struct sk_buff *skb, 1058c2ecf20Sopenharmony_ci const struct ethnl_req_info *req_base, 1068c2ecf20Sopenharmony_ci const struct ethnl_reply_data *reply_base) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci const struct pause_reply_data *data = PAUSE_REPDATA(reply_base); 1098c2ecf20Sopenharmony_ci const struct ethtool_pauseparam *pauseparam = &data->pauseparam; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (nla_put_u8(skb, ETHTOOL_A_PAUSE_AUTONEG, !!pauseparam->autoneg) || 1128c2ecf20Sopenharmony_ci nla_put_u8(skb, ETHTOOL_A_PAUSE_RX, !!pauseparam->rx_pause) || 1138c2ecf20Sopenharmony_ci nla_put_u8(skb, ETHTOOL_A_PAUSE_TX, !!pauseparam->tx_pause)) 1148c2ecf20Sopenharmony_ci return -EMSGSIZE; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci if (req_base->flags & ETHTOOL_FLAG_STATS && 1178c2ecf20Sopenharmony_ci pause_put_stats(skb, &data->pausestat)) 1188c2ecf20Sopenharmony_ci return -EMSGSIZE; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci return 0; 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ciconst struct ethnl_request_ops ethnl_pause_request_ops = { 1248c2ecf20Sopenharmony_ci .request_cmd = ETHTOOL_MSG_PAUSE_GET, 1258c2ecf20Sopenharmony_ci .reply_cmd = ETHTOOL_MSG_PAUSE_GET_REPLY, 1268c2ecf20Sopenharmony_ci .hdr_attr = ETHTOOL_A_PAUSE_HEADER, 1278c2ecf20Sopenharmony_ci .req_info_size = sizeof(struct pause_req_info), 1288c2ecf20Sopenharmony_ci .reply_data_size = sizeof(struct pause_reply_data), 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci .prepare_data = pause_prepare_data, 1318c2ecf20Sopenharmony_ci .reply_size = pause_reply_size, 1328c2ecf20Sopenharmony_ci .fill_reply = pause_fill_reply, 1338c2ecf20Sopenharmony_ci}; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci/* PAUSE_SET */ 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ciconst struct nla_policy ethnl_pause_set_policy[] = { 1388c2ecf20Sopenharmony_ci [ETHTOOL_A_PAUSE_HEADER] = 1398c2ecf20Sopenharmony_ci NLA_POLICY_NESTED(ethnl_header_policy), 1408c2ecf20Sopenharmony_ci [ETHTOOL_A_PAUSE_AUTONEG] = { .type = NLA_U8 }, 1418c2ecf20Sopenharmony_ci [ETHTOOL_A_PAUSE_RX] = { .type = NLA_U8 }, 1428c2ecf20Sopenharmony_ci [ETHTOOL_A_PAUSE_TX] = { .type = NLA_U8 }, 1438c2ecf20Sopenharmony_ci}; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ciint ethnl_set_pause(struct sk_buff *skb, struct genl_info *info) 1468c2ecf20Sopenharmony_ci{ 1478c2ecf20Sopenharmony_ci struct ethtool_pauseparam params = {}; 1488c2ecf20Sopenharmony_ci struct ethnl_req_info req_info = {}; 1498c2ecf20Sopenharmony_ci struct nlattr **tb = info->attrs; 1508c2ecf20Sopenharmony_ci const struct ethtool_ops *ops; 1518c2ecf20Sopenharmony_ci struct net_device *dev; 1528c2ecf20Sopenharmony_ci bool mod = false; 1538c2ecf20Sopenharmony_ci int ret; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci ret = ethnl_parse_header_dev_get(&req_info, 1568c2ecf20Sopenharmony_ci tb[ETHTOOL_A_PAUSE_HEADER], 1578c2ecf20Sopenharmony_ci genl_info_net(info), info->extack, 1588c2ecf20Sopenharmony_ci true); 1598c2ecf20Sopenharmony_ci if (ret < 0) 1608c2ecf20Sopenharmony_ci return ret; 1618c2ecf20Sopenharmony_ci dev = req_info.dev; 1628c2ecf20Sopenharmony_ci ops = dev->ethtool_ops; 1638c2ecf20Sopenharmony_ci ret = -EOPNOTSUPP; 1648c2ecf20Sopenharmony_ci if (!ops->get_pauseparam || !ops->set_pauseparam) 1658c2ecf20Sopenharmony_ci goto out_dev; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci rtnl_lock(); 1688c2ecf20Sopenharmony_ci ret = ethnl_ops_begin(dev); 1698c2ecf20Sopenharmony_ci if (ret < 0) 1708c2ecf20Sopenharmony_ci goto out_rtnl; 1718c2ecf20Sopenharmony_ci ops->get_pauseparam(dev, ¶ms); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci ethnl_update_bool32(¶ms.autoneg, tb[ETHTOOL_A_PAUSE_AUTONEG], &mod); 1748c2ecf20Sopenharmony_ci ethnl_update_bool32(¶ms.rx_pause, tb[ETHTOOL_A_PAUSE_RX], &mod); 1758c2ecf20Sopenharmony_ci ethnl_update_bool32(¶ms.tx_pause, tb[ETHTOOL_A_PAUSE_TX], &mod); 1768c2ecf20Sopenharmony_ci ret = 0; 1778c2ecf20Sopenharmony_ci if (!mod) 1788c2ecf20Sopenharmony_ci goto out_ops; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci ret = dev->ethtool_ops->set_pauseparam(dev, ¶ms); 1818c2ecf20Sopenharmony_ci if (ret < 0) 1828c2ecf20Sopenharmony_ci goto out_ops; 1838c2ecf20Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_PAUSE_NTF, NULL); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ciout_ops: 1868c2ecf20Sopenharmony_ci ethnl_ops_complete(dev); 1878c2ecf20Sopenharmony_ciout_rtnl: 1888c2ecf20Sopenharmony_ci rtnl_unlock(); 1898c2ecf20Sopenharmony_ciout_dev: 1908c2ecf20Sopenharmony_ci dev_put(dev); 1918c2ecf20Sopenharmony_ci return ret; 1928c2ecf20Sopenharmony_ci} 193