Merge remote-tracking branch 'regmap/for-5.7' into regmap-linus
[platform/kernel/linux-rpi.git] / net / ethtool / linkinfo.c
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_dev_get(&req_info,
125                                          tb[ETHTOOL_A_LINKINFO_HEADER],
126                                          genl_info_net(info), info->extack,
127                                          true);
128         if (ret < 0)
129                 return ret;
130         dev = req_info.dev;
131         ret = -EOPNOTSUPP;
132         if (!dev->ethtool_ops->get_link_ksettings ||
133             !dev->ethtool_ops->set_link_ksettings)
134                 goto out_dev;
135
136         rtnl_lock();
137         ret = ethnl_ops_begin(dev);
138         if (ret < 0)
139                 goto out_rtnl;
140
141         ret = __ethtool_get_link_ksettings(dev, &ksettings);
142         if (ret < 0) {
143                 if (info)
144                         GENL_SET_ERR_MSG(info, "failed to retrieve link settings");
145                 goto out_ops;
146         }
147         lsettings = &ksettings.base;
148
149         ethnl_update_u8(&lsettings->port, tb[ETHTOOL_A_LINKINFO_PORT], &mod);
150         ethnl_update_u8(&lsettings->phy_address, tb[ETHTOOL_A_LINKINFO_PHYADDR],
151                         &mod);
152         ethnl_update_u8(&lsettings->eth_tp_mdix_ctrl,
153                         tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL], &mod);
154         ret = 0;
155         if (!mod)
156                 goto out_ops;
157
158         ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings);
159         if (ret < 0)
160                 GENL_SET_ERR_MSG(info, "link settings update failed");
161         else
162                 ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL);
163
164 out_ops:
165         ethnl_ops_complete(dev);
166 out_rtnl:
167         rtnl_unlock();
168 out_dev:
169         dev_put(dev);
170         return ret;
171 }