xref: /openbmc/linux/net/ethtool/wol.c (revision 62975d27)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #include "netlink.h"
4 #include "common.h"
5 #include "bitset.h"
6 
7 struct wol_req_info {
8 	struct ethnl_req_info		base;
9 };
10 
11 struct wol_reply_data {
12 	struct ethnl_reply_data		base;
13 	struct ethtool_wolinfo		wol;
14 	bool				show_sopass;
15 };
16 
17 #define WOL_REPDATA(__reply_base) \
18 	container_of(__reply_base, struct wol_reply_data, base)
19 
20 static const struct nla_policy
21 wol_get_policy[ETHTOOL_A_WOL_MAX + 1] = {
22 	[ETHTOOL_A_WOL_UNSPEC]		= { .type = NLA_REJECT },
23 	[ETHTOOL_A_WOL_HEADER]		= { .type = NLA_NESTED },
24 	[ETHTOOL_A_WOL_MODES]		= { .type = NLA_REJECT },
25 	[ETHTOOL_A_WOL_SOPASS]		= { .type = NLA_REJECT },
26 };
27 
28 static int wol_prepare_data(const struct ethnl_req_info *req_base,
29 			    struct ethnl_reply_data *reply_base,
30 			    struct genl_info *info)
31 {
32 	struct wol_reply_data *data = WOL_REPDATA(reply_base);
33 	struct net_device *dev = reply_base->dev;
34 	int ret;
35 
36 	if (!dev->ethtool_ops->get_wol)
37 		return -EOPNOTSUPP;
38 
39 	ret = ethnl_ops_begin(dev);
40 	if (ret < 0)
41 		return ret;
42 	dev->ethtool_ops->get_wol(dev, &data->wol);
43 	ethnl_ops_complete(dev);
44 	/* do not include password in notifications */
45 	data->show_sopass = info && (data->wol.supported & WAKE_MAGICSECURE);
46 
47 	return 0;
48 }
49 
50 static int wol_reply_size(const struct ethnl_req_info *req_base,
51 			  const struct ethnl_reply_data *reply_base)
52 {
53 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
54 	const struct wol_reply_data *data = WOL_REPDATA(reply_base);
55 	int len;
56 
57 	len = ethnl_bitset32_size(&data->wol.wolopts, &data->wol.supported,
58 				  WOL_MODE_COUNT, wol_mode_names, compact);
59 	if (len < 0)
60 		return len;
61 	if (data->show_sopass)
62 		len += nla_total_size(sizeof(data->wol.sopass));
63 
64 	return len;
65 }
66 
67 static int wol_fill_reply(struct sk_buff *skb,
68 			  const struct ethnl_req_info *req_base,
69 			  const struct ethnl_reply_data *reply_base)
70 {
71 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
72 	const struct wol_reply_data *data = WOL_REPDATA(reply_base);
73 	int ret;
74 
75 	ret = ethnl_put_bitset32(skb, ETHTOOL_A_WOL_MODES, &data->wol.wolopts,
76 				 &data->wol.supported, WOL_MODE_COUNT,
77 				 wol_mode_names, compact);
78 	if (ret < 0)
79 		return ret;
80 	if (data->show_sopass &&
81 	    nla_put(skb, ETHTOOL_A_WOL_SOPASS, sizeof(data->wol.sopass),
82 		    data->wol.sopass))
83 		return -EMSGSIZE;
84 
85 	return 0;
86 }
87 
88 const struct ethnl_request_ops ethnl_wol_request_ops = {
89 	.request_cmd		= ETHTOOL_MSG_WOL_GET,
90 	.reply_cmd		= ETHTOOL_MSG_WOL_GET_REPLY,
91 	.hdr_attr		= ETHTOOL_A_WOL_HEADER,
92 	.max_attr		= ETHTOOL_A_WOL_MAX,
93 	.req_info_size		= sizeof(struct wol_req_info),
94 	.reply_data_size	= sizeof(struct wol_reply_data),
95 	.request_policy		= wol_get_policy,
96 
97 	.prepare_data		= wol_prepare_data,
98 	.reply_size		= wol_reply_size,
99 	.fill_reply		= wol_fill_reply,
100 };
101 
102 /* WOL_SET */
103 
104 static const struct nla_policy
105 wol_set_policy[ETHTOOL_A_WOL_MAX + 1] = {
106 	[ETHTOOL_A_WOL_UNSPEC]		= { .type = NLA_REJECT },
107 	[ETHTOOL_A_WOL_HEADER]		= { .type = NLA_NESTED },
108 	[ETHTOOL_A_WOL_MODES]		= { .type = NLA_NESTED },
109 	[ETHTOOL_A_WOL_SOPASS]		= { .type = NLA_BINARY,
110 					    .len = SOPASS_MAX },
111 };
112 
113 int ethnl_set_wol(struct sk_buff *skb, struct genl_info *info)
114 {
115 	struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL };
116 	struct nlattr *tb[ETHTOOL_A_WOL_MAX + 1];
117 	struct ethnl_req_info req_info = {};
118 	struct net_device *dev;
119 	bool mod = false;
120 	int ret;
121 
122 	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb, ETHTOOL_A_WOL_MAX,
123 			  wol_set_policy, info->extack);
124 	if (ret < 0)
125 		return ret;
126 	ret = ethnl_parse_header_dev_get(&req_info, tb[ETHTOOL_A_WOL_HEADER],
127 					 genl_info_net(info), info->extack,
128 					 true);
129 	if (ret < 0)
130 		return ret;
131 	dev = req_info.dev;
132 	ret = -EOPNOTSUPP;
133 	if (!dev->ethtool_ops->get_wol || !dev->ethtool_ops->set_wol)
134 		goto out_dev;
135 
136 	rtnl_lock();
137 	ret = ethnl_ops_begin(dev);
138 	if (ret < 0)
139 		goto out_rtnl;
140 
141 	dev->ethtool_ops->get_wol(dev, &wol);
142 	ret = ethnl_update_bitset32(&wol.wolopts, WOL_MODE_COUNT,
143 				    tb[ETHTOOL_A_WOL_MODES], wol_mode_names,
144 				    info->extack, &mod);
145 	if (ret < 0)
146 		goto out_ops;
147 	if (wol.wolopts & ~wol.supported) {
148 		NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_WOL_MODES],
149 				    "cannot enable unsupported WoL mode");
150 		ret = -EINVAL;
151 		goto out_ops;
152 	}
153 	if (tb[ETHTOOL_A_WOL_SOPASS]) {
154 		if (!(wol.supported & WAKE_MAGICSECURE)) {
155 			NL_SET_ERR_MSG_ATTR(info->extack,
156 					    tb[ETHTOOL_A_WOL_SOPASS],
157 					    "magicsecure not supported, cannot set password");
158 			ret = -EINVAL;
159 			goto out_ops;
160 		}
161 		ethnl_update_binary(wol.sopass, sizeof(wol.sopass),
162 				    tb[ETHTOOL_A_WOL_SOPASS], &mod);
163 	}
164 
165 	if (!mod)
166 		goto out_ops;
167 	ret = dev->ethtool_ops->set_wol(dev, &wol);
168 	if (ret)
169 		goto out_ops;
170 	dev->wol_enabled = !!wol.wolopts;
171 	ethtool_notify(dev, ETHTOOL_MSG_WOL_NTF, NULL);
172 
173 out_ops:
174 	ethnl_ops_complete(dev);
175 out_rtnl:
176 	rtnl_unlock();
177 out_dev:
178 	dev_put(dev);
179 	return ret;
180 }
181