1 // SPDX-License-Identifier: GPL-2.0-only 2 3 #include "netlink.h" 4 #include "common.h" 5 6 struct linkinfo_req_info { 7 struct ethnl_req_info base; 8 }; 9 10 struct linkinfo_reply_data { 11 struct ethnl_reply_data base; 12 struct ethtool_link_ksettings ksettings; 13 struct ethtool_link_settings *lsettings; 14 }; 15 16 #define LINKINFO_REPDATA(__reply_base) \ 17 container_of(__reply_base, struct linkinfo_reply_data, base) 18 19 const struct nla_policy ethnl_linkinfo_get_policy[] = { 20 [ETHTOOL_A_LINKINFO_HEADER] = 21 NLA_POLICY_NESTED(ethnl_header_policy), 22 }; 23 24 static int linkinfo_prepare_data(const struct ethnl_req_info *req_base, 25 struct ethnl_reply_data *reply_base, 26 struct genl_info *info) 27 { 28 struct linkinfo_reply_data *data = LINKINFO_REPDATA(reply_base); 29 struct net_device *dev = reply_base->dev; 30 int ret; 31 32 data->lsettings = &data->ksettings.base; 33 34 ret = ethnl_ops_begin(dev); 35 if (ret < 0) 36 return ret; 37 ret = __ethtool_get_link_ksettings(dev, &data->ksettings); 38 if (ret < 0 && info) 39 GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); 40 ethnl_ops_complete(dev); 41 42 return ret; 43 } 44 45 static int linkinfo_reply_size(const struct ethnl_req_info *req_base, 46 const struct ethnl_reply_data *reply_base) 47 { 48 return nla_total_size(sizeof(u8)) /* LINKINFO_PORT */ 49 + nla_total_size(sizeof(u8)) /* LINKINFO_PHYADDR */ 50 + nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX */ 51 + nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX_CTRL */ 52 + nla_total_size(sizeof(u8)) /* LINKINFO_TRANSCEIVER */ 53 + 0; 54 } 55 56 static int linkinfo_fill_reply(struct sk_buff *skb, 57 const struct ethnl_req_info *req_base, 58 const struct ethnl_reply_data *reply_base) 59 { 60 const struct linkinfo_reply_data *data = LINKINFO_REPDATA(reply_base); 61 62 if (nla_put_u8(skb, ETHTOOL_A_LINKINFO_PORT, data->lsettings->port) || 63 nla_put_u8(skb, ETHTOOL_A_LINKINFO_PHYADDR, 64 data->lsettings->phy_address) || 65 nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX, 66 data->lsettings->eth_tp_mdix) || 67 nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX_CTRL, 68 data->lsettings->eth_tp_mdix_ctrl) || 69 nla_put_u8(skb, ETHTOOL_A_LINKINFO_TRANSCEIVER, 70 data->lsettings->transceiver)) 71 return -EMSGSIZE; 72 73 return 0; 74 } 75 76 const struct ethnl_request_ops ethnl_linkinfo_request_ops = { 77 .request_cmd = ETHTOOL_MSG_LINKINFO_GET, 78 .reply_cmd = ETHTOOL_MSG_LINKINFO_GET_REPLY, 79 .hdr_attr = ETHTOOL_A_LINKINFO_HEADER, 80 .req_info_size = sizeof(struct linkinfo_req_info), 81 .reply_data_size = sizeof(struct linkinfo_reply_data), 82 83 .prepare_data = linkinfo_prepare_data, 84 .reply_size = linkinfo_reply_size, 85 .fill_reply = linkinfo_fill_reply, 86 }; 87 88 /* LINKINFO_SET */ 89 90 const struct nla_policy ethnl_linkinfo_set_policy[] = { 91 [ETHTOOL_A_LINKINFO_HEADER] = 92 NLA_POLICY_NESTED(ethnl_header_policy), 93 [ETHTOOL_A_LINKINFO_PORT] = { .type = NLA_U8 }, 94 [ETHTOOL_A_LINKINFO_PHYADDR] = { .type = NLA_U8 }, 95 [ETHTOOL_A_LINKINFO_TP_MDIX_CTRL] = { .type = NLA_U8 }, 96 }; 97 98 int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info) 99 { 100 struct ethtool_link_ksettings ksettings = {}; 101 struct ethtool_link_settings *lsettings; 102 struct ethnl_req_info req_info = {}; 103 struct nlattr **tb = info->attrs; 104 struct net_device *dev; 105 bool mod = false; 106 int ret; 107 108 ret = ethnl_parse_header_dev_get(&req_info, 109 tb[ETHTOOL_A_LINKINFO_HEADER], 110 genl_info_net(info), info->extack, 111 true); 112 if (ret < 0) 113 return ret; 114 dev = req_info.dev; 115 ret = -EOPNOTSUPP; 116 if (!dev->ethtool_ops->get_link_ksettings || 117 !dev->ethtool_ops->set_link_ksettings) 118 goto out_dev; 119 120 rtnl_lock(); 121 ret = ethnl_ops_begin(dev); 122 if (ret < 0) 123 goto out_rtnl; 124 125 ret = __ethtool_get_link_ksettings(dev, &ksettings); 126 if (ret < 0) { 127 GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); 128 goto out_ops; 129 } 130 lsettings = &ksettings.base; 131 132 ethnl_update_u8(&lsettings->port, tb[ETHTOOL_A_LINKINFO_PORT], &mod); 133 ethnl_update_u8(&lsettings->phy_address, tb[ETHTOOL_A_LINKINFO_PHYADDR], 134 &mod); 135 ethnl_update_u8(&lsettings->eth_tp_mdix_ctrl, 136 tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL], &mod); 137 ret = 0; 138 if (!mod) 139 goto out_ops; 140 141 ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings); 142 if (ret < 0) 143 GENL_SET_ERR_MSG(info, "link settings update failed"); 144 else 145 ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL); 146 147 out_ops: 148 ethnl_ops_complete(dev); 149 out_rtnl: 150 rtnl_unlock(); 151 out_dev: 152 dev_put(dev); 153 return ret; 154 } 155