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 | const 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, link_ksettings: &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(payload: sizeof(u8)) /* LINKINFO_PORT */ |
49 | + nla_total_size(payload: sizeof(u8)) /* LINKINFO_PHYADDR */ |
50 | + nla_total_size(payload: sizeof(u8)) /* LINKINFO_TP_MDIX */ |
51 | + nla_total_size(payload: sizeof(u8)) /* LINKINFO_TP_MDIX_CTRL */ |
52 | + nla_total_size(payload: 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, attrtype: ETHTOOL_A_LINKINFO_PORT, value: data->lsettings->port) || |
63 | nla_put_u8(skb, attrtype: ETHTOOL_A_LINKINFO_PHYADDR, |
64 | value: data->lsettings->phy_address) || |
65 | nla_put_u8(skb, attrtype: ETHTOOL_A_LINKINFO_TP_MDIX, |
66 | value: data->lsettings->eth_tp_mdix) || |
67 | nla_put_u8(skb, attrtype: ETHTOOL_A_LINKINFO_TP_MDIX_CTRL, |
68 | value: data->lsettings->eth_tp_mdix_ctrl) || |
69 | nla_put_u8(skb, attrtype: ETHTOOL_A_LINKINFO_TRANSCEIVER, |
70 | value: data->lsettings->transceiver)) |
71 | return -EMSGSIZE; |
72 | |
73 | return 0; |
74 | } |
75 | |
76 | /* LINKINFO_SET */ |
77 | |
78 | const struct nla_policy ethnl_linkinfo_set_policy[] = { |
79 | [ETHTOOL_A_LINKINFO_HEADER] = |
80 | NLA_POLICY_NESTED(ethnl_header_policy), |
81 | [ETHTOOL_A_LINKINFO_PORT] = { .type = NLA_U8 }, |
82 | [ETHTOOL_A_LINKINFO_PHYADDR] = { .type = NLA_U8 }, |
83 | [ETHTOOL_A_LINKINFO_TP_MDIX_CTRL] = { .type = NLA_U8 }, |
84 | }; |
85 | |
86 | static int |
87 | ethnl_set_linkinfo_validate(struct ethnl_req_info *req_info, |
88 | struct genl_info *info) |
89 | { |
90 | const struct ethtool_ops *ops = req_info->dev->ethtool_ops; |
91 | |
92 | if (!ops->get_link_ksettings || !ops->set_link_ksettings) |
93 | return -EOPNOTSUPP; |
94 | return 1; |
95 | } |
96 | |
97 | static int |
98 | ethnl_set_linkinfo(struct ethnl_req_info *req_info, struct genl_info *info) |
99 | { |
100 | struct ethtool_link_ksettings ksettings = {}; |
101 | struct ethtool_link_settings *lsettings; |
102 | struct net_device *dev = req_info->dev; |
103 | struct nlattr **tb = info->attrs; |
104 | bool mod = false; |
105 | int ret; |
106 | |
107 | ret = __ethtool_get_link_ksettings(dev, link_ksettings: &ksettings); |
108 | if (ret < 0) { |
109 | GENL_SET_ERR_MSG(info, "failed to retrieve link settings" ); |
110 | return ret; |
111 | } |
112 | lsettings = &ksettings.base; |
113 | |
114 | ethnl_update_u8(dst: &lsettings->port, attr: tb[ETHTOOL_A_LINKINFO_PORT], mod: &mod); |
115 | ethnl_update_u8(dst: &lsettings->phy_address, attr: tb[ETHTOOL_A_LINKINFO_PHYADDR], |
116 | mod: &mod); |
117 | ethnl_update_u8(dst: &lsettings->eth_tp_mdix_ctrl, |
118 | attr: tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL], mod: &mod); |
119 | if (!mod) |
120 | return 0; |
121 | |
122 | ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings); |
123 | if (ret < 0) { |
124 | GENL_SET_ERR_MSG(info, "link settings update failed" ); |
125 | return ret; |
126 | } |
127 | |
128 | return 1; |
129 | } |
130 | |
131 | const struct ethnl_request_ops ethnl_linkinfo_request_ops = { |
132 | .request_cmd = ETHTOOL_MSG_LINKINFO_GET, |
133 | .reply_cmd = ETHTOOL_MSG_LINKINFO_GET_REPLY, |
134 | .hdr_attr = ETHTOOL_A_LINKINFO_HEADER, |
135 | .req_info_size = sizeof(struct linkinfo_req_info), |
136 | .reply_data_size = sizeof(struct linkinfo_reply_data), |
137 | |
138 | .prepare_data = linkinfo_prepare_data, |
139 | .reply_size = linkinfo_reply_size, |
140 | .fill_reply = linkinfo_fill_reply, |
141 | |
142 | .set_validate = ethnl_set_linkinfo_validate, |
143 | .set = ethnl_set_linkinfo, |
144 | .set_ntf_cmd = ETHTOOL_MSG_LINKINFO_NTF, |
145 | }; |
146 | |