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 linkinfo_req_info { 78c2ecf20Sopenharmony_ci struct ethnl_req_info base; 88c2ecf20Sopenharmony_ci}; 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_cistruct linkinfo_reply_data { 118c2ecf20Sopenharmony_ci struct ethnl_reply_data base; 128c2ecf20Sopenharmony_ci struct ethtool_link_ksettings ksettings; 138c2ecf20Sopenharmony_ci struct ethtool_link_settings *lsettings; 148c2ecf20Sopenharmony_ci}; 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define LINKINFO_REPDATA(__reply_base) \ 178c2ecf20Sopenharmony_ci container_of(__reply_base, struct linkinfo_reply_data, base) 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ciconst struct nla_policy ethnl_linkinfo_get_policy[] = { 208c2ecf20Sopenharmony_ci [ETHTOOL_A_LINKINFO_HEADER] = 218c2ecf20Sopenharmony_ci NLA_POLICY_NESTED(ethnl_header_policy), 228c2ecf20Sopenharmony_ci}; 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistatic int linkinfo_prepare_data(const struct ethnl_req_info *req_base, 258c2ecf20Sopenharmony_ci struct ethnl_reply_data *reply_base, 268c2ecf20Sopenharmony_ci struct genl_info *info) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci struct linkinfo_reply_data *data = LINKINFO_REPDATA(reply_base); 298c2ecf20Sopenharmony_ci struct net_device *dev = reply_base->dev; 308c2ecf20Sopenharmony_ci int ret; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci data->lsettings = &data->ksettings.base; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci ret = ethnl_ops_begin(dev); 358c2ecf20Sopenharmony_ci if (ret < 0) 368c2ecf20Sopenharmony_ci return ret; 378c2ecf20Sopenharmony_ci ret = __ethtool_get_link_ksettings(dev, &data->ksettings); 388c2ecf20Sopenharmony_ci if (ret < 0 && info) 398c2ecf20Sopenharmony_ci GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); 408c2ecf20Sopenharmony_ci ethnl_ops_complete(dev); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci return ret; 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic int linkinfo_reply_size(const struct ethnl_req_info *req_base, 468c2ecf20Sopenharmony_ci const struct ethnl_reply_data *reply_base) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci return nla_total_size(sizeof(u8)) /* LINKINFO_PORT */ 498c2ecf20Sopenharmony_ci + nla_total_size(sizeof(u8)) /* LINKINFO_PHYADDR */ 508c2ecf20Sopenharmony_ci + nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX */ 518c2ecf20Sopenharmony_ci + nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX_CTRL */ 528c2ecf20Sopenharmony_ci + nla_total_size(sizeof(u8)) /* LINKINFO_TRANSCEIVER */ 538c2ecf20Sopenharmony_ci + 0; 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic int linkinfo_fill_reply(struct sk_buff *skb, 578c2ecf20Sopenharmony_ci const struct ethnl_req_info *req_base, 588c2ecf20Sopenharmony_ci const struct ethnl_reply_data *reply_base) 598c2ecf20Sopenharmony_ci{ 608c2ecf20Sopenharmony_ci const struct linkinfo_reply_data *data = LINKINFO_REPDATA(reply_base); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci if (nla_put_u8(skb, ETHTOOL_A_LINKINFO_PORT, data->lsettings->port) || 638c2ecf20Sopenharmony_ci nla_put_u8(skb, ETHTOOL_A_LINKINFO_PHYADDR, 648c2ecf20Sopenharmony_ci data->lsettings->phy_address) || 658c2ecf20Sopenharmony_ci nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX, 668c2ecf20Sopenharmony_ci data->lsettings->eth_tp_mdix) || 678c2ecf20Sopenharmony_ci nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX_CTRL, 688c2ecf20Sopenharmony_ci data->lsettings->eth_tp_mdix_ctrl) || 698c2ecf20Sopenharmony_ci nla_put_u8(skb, ETHTOOL_A_LINKINFO_TRANSCEIVER, 708c2ecf20Sopenharmony_ci data->lsettings->transceiver)) 718c2ecf20Sopenharmony_ci return -EMSGSIZE; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci return 0; 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ciconst struct ethnl_request_ops ethnl_linkinfo_request_ops = { 778c2ecf20Sopenharmony_ci .request_cmd = ETHTOOL_MSG_LINKINFO_GET, 788c2ecf20Sopenharmony_ci .reply_cmd = ETHTOOL_MSG_LINKINFO_GET_REPLY, 798c2ecf20Sopenharmony_ci .hdr_attr = ETHTOOL_A_LINKINFO_HEADER, 808c2ecf20Sopenharmony_ci .req_info_size = sizeof(struct linkinfo_req_info), 818c2ecf20Sopenharmony_ci .reply_data_size = sizeof(struct linkinfo_reply_data), 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci .prepare_data = linkinfo_prepare_data, 848c2ecf20Sopenharmony_ci .reply_size = linkinfo_reply_size, 858c2ecf20Sopenharmony_ci .fill_reply = linkinfo_fill_reply, 868c2ecf20Sopenharmony_ci}; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci/* LINKINFO_SET */ 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ciconst struct nla_policy ethnl_linkinfo_set_policy[] = { 918c2ecf20Sopenharmony_ci [ETHTOOL_A_LINKINFO_HEADER] = 928c2ecf20Sopenharmony_ci NLA_POLICY_NESTED(ethnl_header_policy), 938c2ecf20Sopenharmony_ci [ETHTOOL_A_LINKINFO_PORT] = { .type = NLA_U8 }, 948c2ecf20Sopenharmony_ci [ETHTOOL_A_LINKINFO_PHYADDR] = { .type = NLA_U8 }, 958c2ecf20Sopenharmony_ci [ETHTOOL_A_LINKINFO_TP_MDIX_CTRL] = { .type = NLA_U8 }, 968c2ecf20Sopenharmony_ci}; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ciint ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci struct ethtool_link_ksettings ksettings = {}; 1018c2ecf20Sopenharmony_ci struct ethtool_link_settings *lsettings; 1028c2ecf20Sopenharmony_ci struct ethnl_req_info req_info = {}; 1038c2ecf20Sopenharmony_ci struct nlattr **tb = info->attrs; 1048c2ecf20Sopenharmony_ci struct net_device *dev; 1058c2ecf20Sopenharmony_ci bool mod = false; 1068c2ecf20Sopenharmony_ci int ret; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci ret = ethnl_parse_header_dev_get(&req_info, 1098c2ecf20Sopenharmony_ci tb[ETHTOOL_A_LINKINFO_HEADER], 1108c2ecf20Sopenharmony_ci genl_info_net(info), info->extack, 1118c2ecf20Sopenharmony_ci true); 1128c2ecf20Sopenharmony_ci if (ret < 0) 1138c2ecf20Sopenharmony_ci return ret; 1148c2ecf20Sopenharmony_ci dev = req_info.dev; 1158c2ecf20Sopenharmony_ci ret = -EOPNOTSUPP; 1168c2ecf20Sopenharmony_ci if (!dev->ethtool_ops->get_link_ksettings || 1178c2ecf20Sopenharmony_ci !dev->ethtool_ops->set_link_ksettings) 1188c2ecf20Sopenharmony_ci goto out_dev; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci rtnl_lock(); 1218c2ecf20Sopenharmony_ci ret = ethnl_ops_begin(dev); 1228c2ecf20Sopenharmony_ci if (ret < 0) 1238c2ecf20Sopenharmony_ci goto out_rtnl; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci ret = __ethtool_get_link_ksettings(dev, &ksettings); 1268c2ecf20Sopenharmony_ci if (ret < 0) { 1278c2ecf20Sopenharmony_ci GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); 1288c2ecf20Sopenharmony_ci goto out_ops; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci lsettings = &ksettings.base; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci ethnl_update_u8(&lsettings->port, tb[ETHTOOL_A_LINKINFO_PORT], &mod); 1338c2ecf20Sopenharmony_ci ethnl_update_u8(&lsettings->phy_address, tb[ETHTOOL_A_LINKINFO_PHYADDR], 1348c2ecf20Sopenharmony_ci &mod); 1358c2ecf20Sopenharmony_ci ethnl_update_u8(&lsettings->eth_tp_mdix_ctrl, 1368c2ecf20Sopenharmony_ci tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL], &mod); 1378c2ecf20Sopenharmony_ci ret = 0; 1388c2ecf20Sopenharmony_ci if (!mod) 1398c2ecf20Sopenharmony_ci goto out_ops; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings); 1428c2ecf20Sopenharmony_ci if (ret < 0) 1438c2ecf20Sopenharmony_ci GENL_SET_ERR_MSG(info, "link settings update failed"); 1448c2ecf20Sopenharmony_ci else 1458c2ecf20Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ciout_ops: 1488c2ecf20Sopenharmony_ci ethnl_ops_complete(dev); 1498c2ecf20Sopenharmony_ciout_rtnl: 1508c2ecf20Sopenharmony_ci rtnl_unlock(); 1518c2ecf20Sopenharmony_ciout_dev: 1528c2ecf20Sopenharmony_ci dev_put(dev); 1538c2ecf20Sopenharmony_ci return ret; 1548c2ecf20Sopenharmony_ci} 155