Commit | Line | Data |
---|---|---|
955dc68c SMJ |
1 | /* |
2 | * Copyright Samuel Mendoza-Jonas, IBM Corporation 2018. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | */ | |
9 | ||
10 | #include <linux/module.h> | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/if_arp.h> | |
13 | #include <linux/rtnetlink.h> | |
14 | #include <linux/etherdevice.h> | |
15 | #include <linux/module.h> | |
16 | #include <net/genetlink.h> | |
17 | #include <net/ncsi.h> | |
18 | #include <linux/skbuff.h> | |
19 | #include <net/sock.h> | |
20 | #include <uapi/linux/ncsi.h> | |
21 | ||
22 | #include "internal.h" | |
23 | #include "ncsi-netlink.h" | |
24 | ||
25 | static struct genl_family ncsi_genl_family; | |
26 | ||
27 | static const struct nla_policy ncsi_genl_policy[NCSI_ATTR_MAX + 1] = { | |
28 | [NCSI_ATTR_IFINDEX] = { .type = NLA_U32 }, | |
29 | [NCSI_ATTR_PACKAGE_LIST] = { .type = NLA_NESTED }, | |
30 | [NCSI_ATTR_PACKAGE_ID] = { .type = NLA_U32 }, | |
31 | [NCSI_ATTR_CHANNEL_ID] = { .type = NLA_U32 }, | |
32 | }; | |
33 | ||
34 | static struct ncsi_dev_priv *ndp_from_ifindex(struct net *net, u32 ifindex) | |
35 | { | |
36 | struct ncsi_dev_priv *ndp; | |
37 | struct net_device *dev; | |
38 | struct ncsi_dev *nd; | |
39 | struct ncsi_dev; | |
40 | ||
41 | if (!net) | |
42 | return NULL; | |
43 | ||
44 | dev = dev_get_by_index(net, ifindex); | |
45 | if (!dev) { | |
46 | pr_err("NCSI netlink: No device for ifindex %u\n", ifindex); | |
47 | return NULL; | |
48 | } | |
49 | ||
50 | nd = ncsi_find_dev(dev); | |
51 | ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; | |
52 | ||
53 | dev_put(dev); | |
54 | return ndp; | |
55 | } | |
56 | ||
57 | static int ncsi_write_channel_info(struct sk_buff *skb, | |
58 | struct ncsi_dev_priv *ndp, | |
59 | struct ncsi_channel *nc) | |
60 | { | |
61 | struct nlattr *vid_nest; | |
62 | struct ncsi_channel_filter *ncf; | |
63 | struct ncsi_channel_mode *m; | |
64 | u32 *data; | |
65 | int i; | |
66 | ||
67 | nla_put_u32(skb, NCSI_CHANNEL_ATTR_ID, nc->id); | |
68 | m = &nc->modes[NCSI_MODE_LINK]; | |
69 | nla_put_u32(skb, NCSI_CHANNEL_ATTR_LINK_STATE, m->data[2]); | |
70 | if (nc->state == NCSI_CHANNEL_ACTIVE) | |
71 | nla_put_flag(skb, NCSI_CHANNEL_ATTR_ACTIVE); | |
72 | if (ndp->force_channel == nc) | |
73 | nla_put_flag(skb, NCSI_CHANNEL_ATTR_FORCED); | |
74 | ||
75 | nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MAJOR, nc->version.version); | |
76 | nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MINOR, nc->version.alpha2); | |
77 | nla_put_string(skb, NCSI_CHANNEL_ATTR_VERSION_STR, nc->version.fw_name); | |
78 | ||
79 | vid_nest = nla_nest_start(skb, NCSI_CHANNEL_ATTR_VLAN_LIST); | |
80 | if (!vid_nest) | |
81 | return -ENOMEM; | |
82 | ncf = nc->filters[NCSI_FILTER_VLAN]; | |
83 | i = -1; | |
84 | if (ncf) { | |
85 | while ((i = find_next_bit((void *)&ncf->bitmap, ncf->total, | |
86 | i + 1)) < ncf->total) { | |
87 | data = ncsi_get_filter(nc, NCSI_FILTER_VLAN, i); | |
88 | /* Uninitialised channels will have 'zero' vlan ids */ | |
89 | if (!data || !*data) | |
90 | continue; | |
91 | nla_put_u16(skb, NCSI_CHANNEL_ATTR_VLAN_ID, | |
92 | *(u16 *)data); | |
93 | } | |
94 | } | |
95 | nla_nest_end(skb, vid_nest); | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
100 | static int ncsi_write_package_info(struct sk_buff *skb, | |
101 | struct ncsi_dev_priv *ndp, unsigned int id) | |
102 | { | |
103 | struct nlattr *pnest, *cnest, *nest; | |
104 | struct ncsi_package *np; | |
105 | struct ncsi_channel *nc; | |
106 | bool found; | |
107 | int rc; | |
108 | ||
109 | if (id > ndp->package_num) { | |
110 | netdev_info(ndp->ndev.dev, "NCSI: No package with id %u\n", id); | |
111 | return -ENODEV; | |
112 | } | |
113 | ||
114 | found = false; | |
115 | NCSI_FOR_EACH_PACKAGE(ndp, np) { | |
116 | if (np->id != id) | |
117 | continue; | |
118 | pnest = nla_nest_start(skb, NCSI_PKG_ATTR); | |
119 | if (!pnest) | |
120 | return -ENOMEM; | |
121 | nla_put_u32(skb, NCSI_PKG_ATTR_ID, np->id); | |
122 | if (ndp->force_package == np) | |
123 | nla_put_flag(skb, NCSI_PKG_ATTR_FORCED); | |
124 | cnest = nla_nest_start(skb, NCSI_PKG_ATTR_CHANNEL_LIST); | |
125 | if (!cnest) { | |
126 | nla_nest_cancel(skb, pnest); | |
127 | return -ENOMEM; | |
128 | } | |
129 | NCSI_FOR_EACH_CHANNEL(np, nc) { | |
130 | nest = nla_nest_start(skb, NCSI_CHANNEL_ATTR); | |
131 | if (!nest) { | |
132 | nla_nest_cancel(skb, cnest); | |
133 | nla_nest_cancel(skb, pnest); | |
134 | return -ENOMEM; | |
135 | } | |
136 | rc = ncsi_write_channel_info(skb, ndp, nc); | |
137 | if (rc) { | |
138 | nla_nest_cancel(skb, nest); | |
139 | nla_nest_cancel(skb, cnest); | |
140 | nla_nest_cancel(skb, pnest); | |
141 | return rc; | |
142 | } | |
143 | nla_nest_end(skb, nest); | |
144 | } | |
145 | nla_nest_end(skb, cnest); | |
146 | nla_nest_end(skb, pnest); | |
147 | found = true; | |
148 | } | |
149 | ||
150 | if (!found) | |
151 | return -ENODEV; | |
152 | ||
153 | return 0; | |
154 | } | |
155 | ||
156 | static int ncsi_pkg_info_nl(struct sk_buff *msg, struct genl_info *info) | |
157 | { | |
158 | struct ncsi_dev_priv *ndp; | |
159 | unsigned int package_id; | |
160 | struct sk_buff *skb; | |
161 | struct nlattr *attr; | |
162 | void *hdr; | |
163 | int rc; | |
164 | ||
165 | if (!info || !info->attrs) | |
166 | return -EINVAL; | |
167 | ||
168 | if (!info->attrs[NCSI_ATTR_IFINDEX]) | |
169 | return -EINVAL; | |
170 | ||
171 | if (!info->attrs[NCSI_ATTR_PACKAGE_ID]) | |
172 | return -EINVAL; | |
173 | ||
174 | ndp = ndp_from_ifindex(genl_info_net(info), | |
175 | nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); | |
176 | if (!ndp) | |
177 | return -ENODEV; | |
178 | ||
179 | skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); | |
180 | if (!skb) | |
181 | return -ENOMEM; | |
182 | ||
183 | hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, | |
184 | &ncsi_genl_family, 0, NCSI_CMD_PKG_INFO); | |
185 | if (!hdr) { | |
186 | kfree(skb); | |
187 | return -EMSGSIZE; | |
188 | } | |
189 | ||
190 | package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); | |
191 | ||
192 | attr = nla_nest_start(skb, NCSI_ATTR_PACKAGE_LIST); | |
193 | rc = ncsi_write_package_info(skb, ndp, package_id); | |
194 | ||
195 | if (rc) { | |
196 | nla_nest_cancel(skb, attr); | |
197 | goto err; | |
198 | } | |
199 | ||
200 | nla_nest_end(skb, attr); | |
201 | ||
202 | genlmsg_end(skb, hdr); | |
203 | return genlmsg_reply(skb, info); | |
204 | ||
205 | err: | |
206 | genlmsg_cancel(skb, hdr); | |
207 | kfree(skb); | |
208 | return rc; | |
209 | } | |
210 | ||
211 | static int ncsi_pkg_info_all_nl(struct sk_buff *skb, | |
212 | struct netlink_callback *cb) | |
213 | { | |
214 | struct nlattr *attrs[NCSI_ATTR_MAX]; | |
215 | struct ncsi_package *np, *package; | |
216 | struct ncsi_dev_priv *ndp; | |
217 | unsigned int package_id; | |
218 | struct nlattr *attr; | |
219 | void *hdr; | |
220 | int rc; | |
221 | ||
222 | rc = genlmsg_parse(cb->nlh, &ncsi_genl_family, attrs, NCSI_ATTR_MAX, | |
223 | ncsi_genl_policy, NULL); | |
224 | if (rc) | |
225 | return rc; | |
226 | ||
227 | if (!attrs[NCSI_ATTR_IFINDEX]) | |
228 | return -EINVAL; | |
229 | ||
230 | ndp = ndp_from_ifindex(get_net(sock_net(skb->sk)), | |
231 | nla_get_u32(attrs[NCSI_ATTR_IFINDEX])); | |
232 | ||
233 | if (!ndp) | |
234 | return -ENODEV; | |
235 | ||
236 | package_id = cb->args[0]; | |
237 | package = NULL; | |
238 | NCSI_FOR_EACH_PACKAGE(ndp, np) | |
239 | if (np->id == package_id) | |
240 | package = np; | |
241 | ||
242 | if (!package) | |
243 | return 0; /* done */ | |
244 | ||
245 | hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, | |
246 | &ncsi_genl_family, 0, NCSI_CMD_PKG_INFO); | |
247 | if (!hdr) { | |
248 | rc = -EMSGSIZE; | |
249 | goto err; | |
250 | } | |
251 | ||
252 | attr = nla_nest_start(skb, NCSI_ATTR_PACKAGE_LIST); | |
253 | rc = ncsi_write_package_info(skb, ndp, package->id); | |
254 | if (rc) { | |
255 | nla_nest_cancel(skb, attr); | |
256 | goto err; | |
257 | } | |
258 | ||
259 | nla_nest_end(skb, attr); | |
260 | genlmsg_end(skb, hdr); | |
261 | ||
262 | cb->args[0] = package_id + 1; | |
263 | ||
264 | return skb->len; | |
265 | err: | |
266 | genlmsg_cancel(skb, hdr); | |
267 | return rc; | |
268 | } | |
269 | ||
270 | static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) | |
271 | { | |
272 | struct ncsi_package *np, *package; | |
273 | struct ncsi_channel *nc, *channel; | |
274 | u32 package_id, channel_id; | |
275 | struct ncsi_dev_priv *ndp; | |
276 | unsigned long flags; | |
277 | ||
278 | if (!info || !info->attrs) | |
279 | return -EINVAL; | |
280 | ||
281 | if (!info->attrs[NCSI_ATTR_IFINDEX]) | |
282 | return -EINVAL; | |
283 | ||
284 | if (!info->attrs[NCSI_ATTR_PACKAGE_ID]) | |
285 | return -EINVAL; | |
286 | ||
287 | ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), | |
288 | nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); | |
289 | if (!ndp) | |
290 | return -ENODEV; | |
291 | ||
292 | package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); | |
293 | package = NULL; | |
294 | ||
295 | spin_lock_irqsave(&ndp->lock, flags); | |
296 | ||
297 | NCSI_FOR_EACH_PACKAGE(ndp, np) | |
298 | if (np->id == package_id) | |
299 | package = np; | |
300 | if (!package) { | |
301 | /* The user has set a package that does not exist */ | |
302 | return -ERANGE; | |
303 | } | |
304 | ||
305 | channel = NULL; | |
306 | if (!info->attrs[NCSI_ATTR_CHANNEL_ID]) { | |
307 | /* Allow any channel */ | |
308 | channel_id = NCSI_RESERVED_CHANNEL; | |
309 | } else { | |
310 | channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]); | |
311 | NCSI_FOR_EACH_CHANNEL(package, nc) | |
312 | if (nc->id == channel_id) | |
313 | channel = nc; | |
314 | } | |
315 | ||
316 | if (channel_id != NCSI_RESERVED_CHANNEL && !channel) { | |
317 | /* The user has set a channel that does not exist on this | |
318 | * package | |
319 | */ | |
320 | netdev_info(ndp->ndev.dev, "NCSI: Channel %u does not exist!\n", | |
321 | channel_id); | |
322 | return -ERANGE; | |
323 | } | |
324 | ||
325 | ndp->force_package = package; | |
326 | ndp->force_channel = channel; | |
327 | spin_unlock_irqrestore(&ndp->lock, flags); | |
328 | ||
329 | netdev_info(ndp->ndev.dev, "Set package 0x%x, channel 0x%x%s as preferred\n", | |
330 | package_id, channel_id, | |
331 | channel_id == NCSI_RESERVED_CHANNEL ? " (any)" : ""); | |
332 | ||
333 | /* Bounce the NCSI channel to set changes */ | |
334 | ncsi_stop_dev(&ndp->ndev); | |
335 | ncsi_start_dev(&ndp->ndev); | |
336 | ||
337 | return 0; | |
338 | } | |
339 | ||
340 | static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info) | |
341 | { | |
342 | struct ncsi_dev_priv *ndp; | |
343 | unsigned long flags; | |
344 | ||
345 | if (!info || !info->attrs) | |
346 | return -EINVAL; | |
347 | ||
348 | if (!info->attrs[NCSI_ATTR_IFINDEX]) | |
349 | return -EINVAL; | |
350 | ||
351 | ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), | |
352 | nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); | |
353 | if (!ndp) | |
354 | return -ENODEV; | |
355 | ||
356 | /* Clear any override */ | |
357 | spin_lock_irqsave(&ndp->lock, flags); | |
358 | ndp->force_package = NULL; | |
359 | ndp->force_channel = NULL; | |
360 | spin_unlock_irqrestore(&ndp->lock, flags); | |
361 | netdev_info(ndp->ndev.dev, "NCSI: Cleared preferred package/channel\n"); | |
362 | ||
363 | /* Bounce the NCSI channel to set changes */ | |
364 | ncsi_stop_dev(&ndp->ndev); | |
365 | ncsi_start_dev(&ndp->ndev); | |
366 | ||
367 | return 0; | |
368 | } | |
369 | ||
370 | static const struct genl_ops ncsi_ops[] = { | |
371 | { | |
372 | .cmd = NCSI_CMD_PKG_INFO, | |
373 | .policy = ncsi_genl_policy, | |
374 | .doit = ncsi_pkg_info_nl, | |
375 | .dumpit = ncsi_pkg_info_all_nl, | |
376 | .flags = 0, | |
377 | }, | |
378 | { | |
379 | .cmd = NCSI_CMD_SET_INTERFACE, | |
380 | .policy = ncsi_genl_policy, | |
381 | .doit = ncsi_set_interface_nl, | |
382 | .flags = GENL_ADMIN_PERM, | |
383 | }, | |
384 | { | |
385 | .cmd = NCSI_CMD_CLEAR_INTERFACE, | |
386 | .policy = ncsi_genl_policy, | |
387 | .doit = ncsi_clear_interface_nl, | |
388 | .flags = GENL_ADMIN_PERM, | |
389 | }, | |
390 | }; | |
391 | ||
392 | static struct genl_family ncsi_genl_family __ro_after_init = { | |
393 | .name = "NCSI", | |
394 | .version = 0, | |
395 | .maxattr = NCSI_ATTR_MAX, | |
396 | .module = THIS_MODULE, | |
397 | .ops = ncsi_ops, | |
398 | .n_ops = ARRAY_SIZE(ncsi_ops), | |
399 | }; | |
400 | ||
401 | int ncsi_init_netlink(struct net_device *dev) | |
402 | { | |
403 | int rc; | |
404 | ||
405 | rc = genl_register_family(&ncsi_genl_family); | |
406 | if (rc) | |
407 | netdev_err(dev, "ncsi: failed to register netlink family\n"); | |
408 | ||
409 | return rc; | |
410 | } | |
411 | ||
412 | int ncsi_unregister_netlink(struct net_device *dev) | |
413 | { | |
414 | int rc; | |
415 | ||
416 | rc = genl_unregister_family(&ncsi_genl_family); | |
417 | if (rc) | |
418 | netdev_err(dev, "ncsi: failed to unregister netlink family\n"); | |
419 | ||
420 | return rc; | |
421 | } |