162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci#include "netlink.h"
462306a36Sopenharmony_ci#include "common.h"
562306a36Sopenharmony_ci
662306a36Sopenharmony_cistruct linkinfo_req_info {
762306a36Sopenharmony_ci	struct ethnl_req_info		base;
862306a36Sopenharmony_ci};
962306a36Sopenharmony_ci
1062306a36Sopenharmony_cistruct linkinfo_reply_data {
1162306a36Sopenharmony_ci	struct ethnl_reply_data		base;
1262306a36Sopenharmony_ci	struct ethtool_link_ksettings	ksettings;
1362306a36Sopenharmony_ci	struct ethtool_link_settings	*lsettings;
1462306a36Sopenharmony_ci};
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define LINKINFO_REPDATA(__reply_base) \
1762306a36Sopenharmony_ci	container_of(__reply_base, struct linkinfo_reply_data, base)
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ciconst struct nla_policy ethnl_linkinfo_get_policy[] = {
2062306a36Sopenharmony_ci	[ETHTOOL_A_LINKINFO_HEADER]		=
2162306a36Sopenharmony_ci		NLA_POLICY_NESTED(ethnl_header_policy),
2262306a36Sopenharmony_ci};
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic int linkinfo_prepare_data(const struct ethnl_req_info *req_base,
2562306a36Sopenharmony_ci				 struct ethnl_reply_data *reply_base,
2662306a36Sopenharmony_ci				 const struct genl_info *info)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	struct linkinfo_reply_data *data = LINKINFO_REPDATA(reply_base);
2962306a36Sopenharmony_ci	struct net_device *dev = reply_base->dev;
3062306a36Sopenharmony_ci	int ret;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	data->lsettings = &data->ksettings.base;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	ret = ethnl_ops_begin(dev);
3562306a36Sopenharmony_ci	if (ret < 0)
3662306a36Sopenharmony_ci		return ret;
3762306a36Sopenharmony_ci	ret = __ethtool_get_link_ksettings(dev, &data->ksettings);
3862306a36Sopenharmony_ci	if (ret < 0 && info)
3962306a36Sopenharmony_ci		GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
4062306a36Sopenharmony_ci	ethnl_ops_complete(dev);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	return ret;
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic int linkinfo_reply_size(const struct ethnl_req_info *req_base,
4662306a36Sopenharmony_ci			       const struct ethnl_reply_data *reply_base)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	return nla_total_size(sizeof(u8)) /* LINKINFO_PORT */
4962306a36Sopenharmony_ci		+ nla_total_size(sizeof(u8)) /* LINKINFO_PHYADDR */
5062306a36Sopenharmony_ci		+ nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX */
5162306a36Sopenharmony_ci		+ nla_total_size(sizeof(u8)) /* LINKINFO_TP_MDIX_CTRL */
5262306a36Sopenharmony_ci		+ nla_total_size(sizeof(u8)) /* LINKINFO_TRANSCEIVER */
5362306a36Sopenharmony_ci		+ 0;
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic int linkinfo_fill_reply(struct sk_buff *skb,
5762306a36Sopenharmony_ci			       const struct ethnl_req_info *req_base,
5862306a36Sopenharmony_ci			       const struct ethnl_reply_data *reply_base)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	const struct linkinfo_reply_data *data = LINKINFO_REPDATA(reply_base);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (nla_put_u8(skb, ETHTOOL_A_LINKINFO_PORT, data->lsettings->port) ||
6362306a36Sopenharmony_ci	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_PHYADDR,
6462306a36Sopenharmony_ci		       data->lsettings->phy_address) ||
6562306a36Sopenharmony_ci	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX,
6662306a36Sopenharmony_ci		       data->lsettings->eth_tp_mdix) ||
6762306a36Sopenharmony_ci	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_TP_MDIX_CTRL,
6862306a36Sopenharmony_ci		       data->lsettings->eth_tp_mdix_ctrl) ||
6962306a36Sopenharmony_ci	    nla_put_u8(skb, ETHTOOL_A_LINKINFO_TRANSCEIVER,
7062306a36Sopenharmony_ci		       data->lsettings->transceiver))
7162306a36Sopenharmony_ci		return -EMSGSIZE;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	return 0;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/* LINKINFO_SET */
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ciconst struct nla_policy ethnl_linkinfo_set_policy[] = {
7962306a36Sopenharmony_ci	[ETHTOOL_A_LINKINFO_HEADER]		=
8062306a36Sopenharmony_ci		NLA_POLICY_NESTED(ethnl_header_policy),
8162306a36Sopenharmony_ci	[ETHTOOL_A_LINKINFO_PORT]		= { .type = NLA_U8 },
8262306a36Sopenharmony_ci	[ETHTOOL_A_LINKINFO_PHYADDR]		= { .type = NLA_U8 },
8362306a36Sopenharmony_ci	[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL]	= { .type = NLA_U8 },
8462306a36Sopenharmony_ci};
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic int
8762306a36Sopenharmony_ciethnl_set_linkinfo_validate(struct ethnl_req_info *req_info,
8862306a36Sopenharmony_ci			    struct genl_info *info)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	if (!ops->get_link_ksettings || !ops->set_link_ksettings)
9362306a36Sopenharmony_ci		return -EOPNOTSUPP;
9462306a36Sopenharmony_ci	return 1;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic int
9862306a36Sopenharmony_ciethnl_set_linkinfo(struct ethnl_req_info *req_info, struct genl_info *info)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	struct ethtool_link_ksettings ksettings = {};
10162306a36Sopenharmony_ci	struct ethtool_link_settings *lsettings;
10262306a36Sopenharmony_ci	struct net_device *dev = req_info->dev;
10362306a36Sopenharmony_ci	struct nlattr **tb = info->attrs;
10462306a36Sopenharmony_ci	bool mod = false;
10562306a36Sopenharmony_ci	int ret;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	ret = __ethtool_get_link_ksettings(dev, &ksettings);
10862306a36Sopenharmony_ci	if (ret < 0) {
10962306a36Sopenharmony_ci		GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
11062306a36Sopenharmony_ci		return ret;
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci	lsettings = &ksettings.base;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	ethnl_update_u8(&lsettings->port, tb[ETHTOOL_A_LINKINFO_PORT], &mod);
11562306a36Sopenharmony_ci	ethnl_update_u8(&lsettings->phy_address, tb[ETHTOOL_A_LINKINFO_PHYADDR],
11662306a36Sopenharmony_ci			&mod);
11762306a36Sopenharmony_ci	ethnl_update_u8(&lsettings->eth_tp_mdix_ctrl,
11862306a36Sopenharmony_ci			tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL], &mod);
11962306a36Sopenharmony_ci	if (!mod)
12062306a36Sopenharmony_ci		return 0;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings);
12362306a36Sopenharmony_ci	if (ret < 0) {
12462306a36Sopenharmony_ci		GENL_SET_ERR_MSG(info, "link settings update failed");
12562306a36Sopenharmony_ci		return ret;
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	return 1;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ciconst struct ethnl_request_ops ethnl_linkinfo_request_ops = {
13262306a36Sopenharmony_ci	.request_cmd		= ETHTOOL_MSG_LINKINFO_GET,
13362306a36Sopenharmony_ci	.reply_cmd		= ETHTOOL_MSG_LINKINFO_GET_REPLY,
13462306a36Sopenharmony_ci	.hdr_attr		= ETHTOOL_A_LINKINFO_HEADER,
13562306a36Sopenharmony_ci	.req_info_size		= sizeof(struct linkinfo_req_info),
13662306a36Sopenharmony_ci	.reply_data_size	= sizeof(struct linkinfo_reply_data),
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	.prepare_data		= linkinfo_prepare_data,
13962306a36Sopenharmony_ci	.reply_size		= linkinfo_reply_size,
14062306a36Sopenharmony_ci	.fill_reply		= linkinfo_fill_reply,
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	.set_validate		= ethnl_set_linkinfo_validate,
14362306a36Sopenharmony_ci	.set			= ethnl_set_linkinfo,
14462306a36Sopenharmony_ci	.set_ntf_cmd		= ETHTOOL_MSG_LINKINFO_NTF,
14562306a36Sopenharmony_ci};
146