xref: /openbmc/linux/net/ethtool/linkinfo.c (revision 76ce0265)
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 static const struct nla_policy
20 linkinfo_get_policy[ETHTOOL_A_LINKINFO_MAX + 1] = {
21 	[ETHTOOL_A_LINKINFO_UNSPEC]		= { .type = NLA_REJECT },
22 	[ETHTOOL_A_LINKINFO_HEADER]		= { .type = NLA_NESTED },
23 	[ETHTOOL_A_LINKINFO_PORT]		= { .type = NLA_REJECT },
24 	[ETHTOOL_A_LINKINFO_PHYADDR]		= { .type = NLA_REJECT },
25 	[ETHTOOL_A_LINKINFO_TP_MDIX]		= { .type = NLA_REJECT },
26 	[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL]	= { .type = NLA_REJECT },
27 	[ETHTOOL_A_LINKINFO_TRANSCEIVER]	= { .type = NLA_REJECT },
28 };
29 
30 static int linkinfo_prepare_data(const struct ethnl_req_info *req_base,
31 				 struct ethnl_reply_data *reply_base,
32 				 struct genl_info *info)
33 {
34 	struct linkinfo_reply_data *data = LINKINFO_REPDATA(reply_base);
35 	struct net_device *dev = reply_base->dev;
36 	int ret;
37 
38 	data->lsettings = &data->ksettings.base;
39 
40 	ret = ethnl_ops_begin(dev);
41 	if (ret < 0)
42 		return ret;
43 	ret = __ethtool_get_link_ksettings(dev, &data->ksettings);
44 	if (ret < 0 && info)
45 		GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
46 	ethnl_ops_complete(dev);
47 
48 	return ret;
49 }
50 
51 static int linkinfo_reply_size(const struct ethnl_req_info *req_base,
52 			       const struct ethnl_reply_data *reply_base)
53 {
54 	return nla_total_size(sizeof(u8)) /* LINKINFO_PORT */
55 		+ nla_total_size(sizeof(u8)) /* LINKINFO_PHYADDR */
56 		+ nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX */
57 		+ nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX_CTRL */
58 		+ nla_total_size(sizeof(u8)) /* LINKINFO_TRANSCEIVER */
59 		+ 0;
60 }
61 
62 static int linkinfo_fill_reply(struct sk_buff *skb,
63 			       const struct ethnl_req_info *req_base,
64 			       const struct ethnl_reply_data *reply_base)
65 {
66 	const struct linkinfo_reply_data *data = LINKINFO_REPDATA(reply_base);
67 
68 	if (nla_put_u8(skb, ETHTOOL_A_LINKINFO_PORT, data->lsettings->port) ||
69 	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_PHYADDR,
70 		       data->lsettings->phy_address) ||
71 	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX,
72 		       data->lsettings->eth_tp_mdix) ||
73 	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX_CTRL,
74 		       data->lsettings->eth_tp_mdix_ctrl) ||
75 	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_TRANSCEIVER,
76 		       data->lsettings->transceiver))
77 		return -EMSGSIZE;
78 
79 	return 0;
80 }
81 
82 const struct ethnl_request_ops ethnl_linkinfo_request_ops = {
83 	.request_cmd		= ETHTOOL_MSG_LINKINFO_GET,
84 	.reply_cmd		= ETHTOOL_MSG_LINKINFO_GET_REPLY,
85 	.hdr_attr		= ETHTOOL_A_LINKINFO_HEADER,
86 	.max_attr		= ETHTOOL_A_LINKINFO_MAX,
87 	.req_info_size		= sizeof(struct linkinfo_req_info),
88 	.reply_data_size	= sizeof(struct linkinfo_reply_data),
89 	.request_policy		= linkinfo_get_policy,
90 
91 	.prepare_data		= linkinfo_prepare_data,
92 	.reply_size		= linkinfo_reply_size,
93 	.fill_reply		= linkinfo_fill_reply,
94 };
95 
96 /* LINKINFO_SET */
97 
98 static const struct nla_policy
99 linkinfo_set_policy[ETHTOOL_A_LINKINFO_MAX + 1] = {
100 	[ETHTOOL_A_LINKINFO_UNSPEC]		= { .type = NLA_REJECT },
101 	[ETHTOOL_A_LINKINFO_HEADER]		= { .type = NLA_NESTED },
102 	[ETHTOOL_A_LINKINFO_PORT]		= { .type = NLA_U8 },
103 	[ETHTOOL_A_LINKINFO_PHYADDR]		= { .type = NLA_U8 },
104 	[ETHTOOL_A_LINKINFO_TP_MDIX]		= { .type = NLA_REJECT },
105 	[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL]	= { .type = NLA_U8 },
106 	[ETHTOOL_A_LINKINFO_TRANSCEIVER]	= { .type = NLA_REJECT },
107 };
108 
109 int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info)
110 {
111 	struct nlattr *tb[ETHTOOL_A_LINKINFO_MAX + 1];
112 	struct ethtool_link_ksettings ksettings = {};
113 	struct ethtool_link_settings *lsettings;
114 	struct ethnl_req_info req_info = {};
115 	struct net_device *dev;
116 	bool mod = false;
117 	int ret;
118 
119 	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
120 			  ETHTOOL_A_LINKINFO_MAX, linkinfo_set_policy,
121 			  info->extack);
122 	if (ret < 0)
123 		return ret;
124 	ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKINFO_HEADER],
125 				 genl_info_net(info), info->extack, true);
126 	if (ret < 0)
127 		return ret;
128 	dev = req_info.dev;
129 	if (!dev->ethtool_ops->get_link_ksettings ||
130 	    !dev->ethtool_ops->set_link_ksettings)
131 		return -EOPNOTSUPP;
132 
133 	rtnl_lock();
134 	ret = ethnl_ops_begin(dev);
135 	if (ret < 0)
136 		goto out_rtnl;
137 
138 	ret = __ethtool_get_link_ksettings(dev, &ksettings);
139 	if (ret < 0) {
140 		if (info)
141 			GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
142 		goto out_ops;
143 	}
144 	lsettings = &ksettings.base;
145 
146 	ethnl_update_u8(&lsettings->port, tb[ETHTOOL_A_LINKINFO_PORT], &mod);
147 	ethnl_update_u8(&lsettings->phy_address, tb[ETHTOOL_A_LINKINFO_PHYADDR],
148 			&mod);
149 	ethnl_update_u8(&lsettings->eth_tp_mdix_ctrl,
150 			tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL], &mod);
151 	ret = 0;
152 	if (!mod)
153 		goto out_ops;
154 
155 	ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings);
156 	if (ret < 0)
157 		GENL_SET_ERR_MSG(info, "link settings update failed");
158 	else
159 		ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
160 
161 out_ops:
162 	ethnl_ops_complete(dev);
163 out_rtnl:
164 	rtnl_unlock();
165 	dev_put(dev);
166 	return ret;
167 }
168