Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
6389eaa7 GS |
2 | /* |
3 | * Copyright Gavin Shan, IBM Corporation 2016. | |
6389eaa7 GS |
4 | */ |
5 | ||
6 | #include <linux/module.h> | |
7 | #include <linux/kernel.h> | |
8 | #include <linux/init.h> | |
9 | #include <linux/etherdevice.h> | |
10 | #include <linux/netdevice.h> | |
11 | #include <linux/skbuff.h> | |
12 | ||
13 | #include <net/ncsi.h> | |
14 | #include <net/net_namespace.h> | |
15 | #include <net/sock.h> | |
9771b8cc | 16 | #include <net/genetlink.h> |
6389eaa7 GS |
17 | |
18 | #include "internal.h" | |
19 | #include "ncsi-pkt.h" | |
20 | ||
21 | u32 ncsi_calculate_checksum(unsigned char *data, int len) | |
22 | { | |
23 | u32 checksum = 0; | |
24 | int i; | |
25 | ||
26 | for (i = 0; i < len; i += 2) | |
27 | checksum += (((u32)data[i] << 8) | data[i + 1]); | |
28 | ||
29 | checksum = (~checksum + 1); | |
30 | return checksum; | |
31 | } | |
32 | ||
33 | /* This function should be called after the data area has been | |
34 | * populated completely. | |
35 | */ | |
36 | static void ncsi_cmd_build_header(struct ncsi_pkt_hdr *h, | |
37 | struct ncsi_cmd_arg *nca) | |
38 | { | |
39 | u32 checksum; | |
40 | __be32 *pchecksum; | |
41 | ||
42 | h->mc_id = 0; | |
43 | h->revision = NCSI_PKT_REVISION; | |
44 | h->reserved = 0; | |
45 | h->id = nca->id; | |
46 | h->type = nca->type; | |
47 | h->channel = NCSI_TO_CHANNEL(nca->package, | |
48 | nca->channel); | |
49 | h->length = htons(nca->payload); | |
50 | h->reserved1[0] = 0; | |
51 | h->reserved1[1] = 0; | |
52 | ||
53 | /* Fill with calculated checksum */ | |
54 | checksum = ncsi_calculate_checksum((unsigned char *)h, | |
55 | sizeof(*h) + nca->payload); | |
56 | pchecksum = (__be32 *)((void *)h + sizeof(struct ncsi_pkt_hdr) + | |
96a1b033 | 57 | ALIGN(nca->payload, 4)); |
6389eaa7 GS |
58 | *pchecksum = htonl(checksum); |
59 | } | |
60 | ||
61 | static int ncsi_cmd_handler_default(struct sk_buff *skb, | |
62 | struct ncsi_cmd_arg *nca) | |
63 | { | |
64 | struct ncsi_cmd_pkt *cmd; | |
65 | ||
b080db58 | 66 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
6389eaa7 GS |
67 | ncsi_cmd_build_header(&cmd->cmd.common, nca); |
68 | ||
69 | return 0; | |
70 | } | |
71 | ||
72 | static int ncsi_cmd_handler_sp(struct sk_buff *skb, | |
73 | struct ncsi_cmd_arg *nca) | |
74 | { | |
75 | struct ncsi_cmd_sp_pkt *cmd; | |
76 | ||
b080db58 | 77 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
6389eaa7 GS |
78 | cmd->hw_arbitration = nca->bytes[0]; |
79 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
80 | ||
81 | return 0; | |
82 | } | |
83 | ||
84 | static int ncsi_cmd_handler_dc(struct sk_buff *skb, | |
85 | struct ncsi_cmd_arg *nca) | |
86 | { | |
87 | struct ncsi_cmd_dc_pkt *cmd; | |
88 | ||
b080db58 | 89 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
6389eaa7 GS |
90 | cmd->ald = nca->bytes[0]; |
91 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
92 | ||
93 | return 0; | |
94 | } | |
95 | ||
96 | static int ncsi_cmd_handler_rc(struct sk_buff *skb, | |
97 | struct ncsi_cmd_arg *nca) | |
98 | { | |
99 | struct ncsi_cmd_rc_pkt *cmd; | |
100 | ||
b080db58 | 101 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
6389eaa7 GS |
102 | ncsi_cmd_build_header(&cmd->cmd.common, nca); |
103 | ||
104 | return 0; | |
105 | } | |
106 | ||
107 | static int ncsi_cmd_handler_ae(struct sk_buff *skb, | |
108 | struct ncsi_cmd_arg *nca) | |
109 | { | |
110 | struct ncsi_cmd_ae_pkt *cmd; | |
111 | ||
b080db58 | 112 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
6389eaa7 GS |
113 | cmd->mc_id = nca->bytes[0]; |
114 | cmd->mode = htonl(nca->dwords[1]); | |
115 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
116 | ||
117 | return 0; | |
118 | } | |
119 | ||
120 | static int ncsi_cmd_handler_sl(struct sk_buff *skb, | |
121 | struct ncsi_cmd_arg *nca) | |
122 | { | |
123 | struct ncsi_cmd_sl_pkt *cmd; | |
124 | ||
b080db58 | 125 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
6389eaa7 GS |
126 | cmd->mode = htonl(nca->dwords[0]); |
127 | cmd->oem_mode = htonl(nca->dwords[1]); | |
128 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
129 | ||
130 | return 0; | |
131 | } | |
132 | ||
133 | static int ncsi_cmd_handler_svf(struct sk_buff *skb, | |
134 | struct ncsi_cmd_arg *nca) | |
135 | { | |
136 | struct ncsi_cmd_svf_pkt *cmd; | |
137 | ||
b080db58 | 138 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
8579a67e SMJ |
139 | cmd->vlan = htons(nca->words[1]); |
140 | cmd->index = nca->bytes[6]; | |
141 | cmd->enable = nca->bytes[7]; | |
6389eaa7 GS |
142 | ncsi_cmd_build_header(&cmd->cmd.common, nca); |
143 | ||
144 | return 0; | |
145 | } | |
146 | ||
147 | static int ncsi_cmd_handler_ev(struct sk_buff *skb, | |
148 | struct ncsi_cmd_arg *nca) | |
149 | { | |
150 | struct ncsi_cmd_ev_pkt *cmd; | |
151 | ||
b080db58 | 152 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
8579a67e | 153 | cmd->mode = nca->bytes[3]; |
6389eaa7 GS |
154 | ncsi_cmd_build_header(&cmd->cmd.common, nca); |
155 | ||
156 | return 0; | |
157 | } | |
158 | ||
159 | static int ncsi_cmd_handler_sma(struct sk_buff *skb, | |
160 | struct ncsi_cmd_arg *nca) | |
161 | { | |
162 | struct ncsi_cmd_sma_pkt *cmd; | |
163 | int i; | |
164 | ||
b080db58 | 165 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
6389eaa7 GS |
166 | for (i = 0; i < 6; i++) |
167 | cmd->mac[i] = nca->bytes[i]; | |
168 | cmd->index = nca->bytes[6]; | |
169 | cmd->at_e = nca->bytes[7]; | |
170 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | static int ncsi_cmd_handler_ebf(struct sk_buff *skb, | |
176 | struct ncsi_cmd_arg *nca) | |
177 | { | |
178 | struct ncsi_cmd_ebf_pkt *cmd; | |
179 | ||
b080db58 | 180 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
6389eaa7 GS |
181 | cmd->mode = htonl(nca->dwords[0]); |
182 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | static int ncsi_cmd_handler_egmf(struct sk_buff *skb, | |
188 | struct ncsi_cmd_arg *nca) | |
189 | { | |
190 | struct ncsi_cmd_egmf_pkt *cmd; | |
191 | ||
b080db58 | 192 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
6389eaa7 GS |
193 | cmd->mode = htonl(nca->dwords[0]); |
194 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
195 | ||
196 | return 0; | |
197 | } | |
198 | ||
199 | static int ncsi_cmd_handler_snfc(struct sk_buff *skb, | |
200 | struct ncsi_cmd_arg *nca) | |
201 | { | |
202 | struct ncsi_cmd_snfc_pkt *cmd; | |
203 | ||
b080db58 | 204 | cmd = skb_put_zero(skb, sizeof(*cmd)); |
6389eaa7 GS |
205 | cmd->mode = nca->bytes[0]; |
206 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
207 | ||
208 | return 0; | |
209 | } | |
210 | ||
fb4ee675 VK |
211 | static int ncsi_cmd_handler_oem(struct sk_buff *skb, |
212 | struct ncsi_cmd_arg *nca) | |
213 | { | |
214 | struct ncsi_cmd_oem_pkt *cmd; | |
215 | unsigned int len; | |
216 | ||
217 | len = sizeof(struct ncsi_cmd_pkt_hdr) + 4; | |
218 | if (nca->payload < 26) | |
219 | len += 26; | |
220 | else | |
221 | len += nca->payload; | |
222 | ||
223 | cmd = skb_put_zero(skb, len); | |
224 | memcpy(&cmd->mfr_id, nca->data, nca->payload); | |
225 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
6389eaa7 GS |
230 | static struct ncsi_cmd_handler { |
231 | unsigned char type; | |
232 | int payload; | |
233 | int (*handler)(struct sk_buff *skb, | |
234 | struct ncsi_cmd_arg *nca); | |
235 | } ncsi_cmd_handlers[] = { | |
236 | { NCSI_PKT_CMD_CIS, 0, ncsi_cmd_handler_default }, | |
237 | { NCSI_PKT_CMD_SP, 4, ncsi_cmd_handler_sp }, | |
238 | { NCSI_PKT_CMD_DP, 0, ncsi_cmd_handler_default }, | |
239 | { NCSI_PKT_CMD_EC, 0, ncsi_cmd_handler_default }, | |
240 | { NCSI_PKT_CMD_DC, 4, ncsi_cmd_handler_dc }, | |
241 | { NCSI_PKT_CMD_RC, 4, ncsi_cmd_handler_rc }, | |
242 | { NCSI_PKT_CMD_ECNT, 0, ncsi_cmd_handler_default }, | |
243 | { NCSI_PKT_CMD_DCNT, 0, ncsi_cmd_handler_default }, | |
244 | { NCSI_PKT_CMD_AE, 8, ncsi_cmd_handler_ae }, | |
245 | { NCSI_PKT_CMD_SL, 8, ncsi_cmd_handler_sl }, | |
246 | { NCSI_PKT_CMD_GLS, 0, ncsi_cmd_handler_default }, | |
8579a67e | 247 | { NCSI_PKT_CMD_SVF, 8, ncsi_cmd_handler_svf }, |
6389eaa7 GS |
248 | { NCSI_PKT_CMD_EV, 4, ncsi_cmd_handler_ev }, |
249 | { NCSI_PKT_CMD_DV, 0, ncsi_cmd_handler_default }, | |
250 | { NCSI_PKT_CMD_SMA, 8, ncsi_cmd_handler_sma }, | |
251 | { NCSI_PKT_CMD_EBF, 4, ncsi_cmd_handler_ebf }, | |
252 | { NCSI_PKT_CMD_DBF, 0, ncsi_cmd_handler_default }, | |
253 | { NCSI_PKT_CMD_EGMF, 4, ncsi_cmd_handler_egmf }, | |
254 | { NCSI_PKT_CMD_DGMF, 0, ncsi_cmd_handler_default }, | |
255 | { NCSI_PKT_CMD_SNFC, 4, ncsi_cmd_handler_snfc }, | |
256 | { NCSI_PKT_CMD_GVI, 0, ncsi_cmd_handler_default }, | |
257 | { NCSI_PKT_CMD_GC, 0, ncsi_cmd_handler_default }, | |
258 | { NCSI_PKT_CMD_GP, 0, ncsi_cmd_handler_default }, | |
259 | { NCSI_PKT_CMD_GCPS, 0, ncsi_cmd_handler_default }, | |
260 | { NCSI_PKT_CMD_GNS, 0, ncsi_cmd_handler_default }, | |
261 | { NCSI_PKT_CMD_GNPTS, 0, ncsi_cmd_handler_default }, | |
262 | { NCSI_PKT_CMD_GPS, 0, ncsi_cmd_handler_default }, | |
fb4ee675 | 263 | { NCSI_PKT_CMD_OEM, -1, ncsi_cmd_handler_oem }, |
6389eaa7 GS |
264 | { NCSI_PKT_CMD_PLDM, 0, NULL }, |
265 | { NCSI_PKT_CMD_GPUUID, 0, ncsi_cmd_handler_default } | |
266 | }; | |
267 | ||
268 | static struct ncsi_request *ncsi_alloc_command(struct ncsi_cmd_arg *nca) | |
269 | { | |
270 | struct ncsi_dev_priv *ndp = nca->ndp; | |
271 | struct ncsi_dev *nd = &ndp->ndev; | |
272 | struct net_device *dev = nd->dev; | |
273 | int hlen = LL_RESERVED_SPACE(dev); | |
274 | int tlen = dev->needed_tailroom; | |
275 | int len = hlen + tlen; | |
276 | struct sk_buff *skb; | |
277 | struct ncsi_request *nr; | |
278 | ||
a0509cbe | 279 | nr = ncsi_alloc_request(ndp, nca->req_flags); |
6389eaa7 GS |
280 | if (!nr) |
281 | return NULL; | |
282 | ||
283 | /* NCSI command packet has 16-bytes header, payload, 4 bytes checksum. | |
284 | * The packet needs padding if its payload is less than 26 bytes to | |
285 | * meet 64 bytes minimal ethernet frame length. | |
286 | */ | |
287 | len += sizeof(struct ncsi_cmd_pkt_hdr) + 4; | |
288 | if (nca->payload < 26) | |
289 | len += 26; | |
290 | else | |
291 | len += nca->payload; | |
292 | ||
293 | /* Allocate skb */ | |
294 | skb = alloc_skb(len, GFP_ATOMIC); | |
295 | if (!skb) { | |
296 | ncsi_free_request(nr); | |
297 | return NULL; | |
298 | } | |
299 | ||
300 | nr->cmd = skb; | |
301 | skb_reserve(skb, hlen); | |
302 | skb_reset_network_header(skb); | |
303 | ||
304 | skb->dev = dev; | |
305 | skb->protocol = htons(ETH_P_NCSI); | |
306 | ||
307 | return nr; | |
308 | } | |
309 | ||
310 | int ncsi_xmit_cmd(struct ncsi_cmd_arg *nca) | |
311 | { | |
f6edbf2d | 312 | struct ncsi_cmd_handler *nch = NULL; |
6389eaa7 | 313 | struct ncsi_request *nr; |
f6edbf2d | 314 | unsigned char type; |
6389eaa7 | 315 | struct ethhdr *eh; |
6389eaa7 GS |
316 | int i, ret; |
317 | ||
f6edbf2d JLD |
318 | /* Use OEM generic handler for Netlink request */ |
319 | if (nca->req_flags == NCSI_REQ_FLAG_NETLINK_DRIVEN) | |
320 | type = NCSI_PKT_CMD_OEM; | |
321 | else | |
322 | type = nca->type; | |
323 | ||
6389eaa7 GS |
324 | /* Search for the handler */ |
325 | for (i = 0; i < ARRAY_SIZE(ncsi_cmd_handlers); i++) { | |
f6edbf2d | 326 | if (ncsi_cmd_handlers[i].type == type) { |
6389eaa7 GS |
327 | if (ncsi_cmd_handlers[i].handler) |
328 | nch = &ncsi_cmd_handlers[i]; | |
329 | else | |
330 | nch = NULL; | |
331 | ||
332 | break; | |
333 | } | |
334 | } | |
335 | ||
336 | if (!nch) { | |
337 | netdev_err(nca->ndp->ndev.dev, | |
338 | "Cannot send packet with type 0x%02x\n", nca->type); | |
339 | return -ENOENT; | |
340 | } | |
341 | ||
fb4ee675 VK |
342 | /* Get packet payload length and allocate the request |
343 | * It is expected that if length set as negative in | |
344 | * handler structure means caller is initializing it | |
345 | * and setting length in nca before calling xmit function | |
346 | */ | |
347 | if (nch->payload >= 0) | |
348 | nca->payload = nch->payload; | |
6389eaa7 GS |
349 | nr = ncsi_alloc_command(nca); |
350 | if (!nr) | |
351 | return -ENOMEM; | |
352 | ||
9771b8cc JLD |
353 | /* track netlink information */ |
354 | if (nca->req_flags == NCSI_REQ_FLAG_NETLINK_DRIVEN) { | |
355 | nr->snd_seq = nca->info->snd_seq; | |
356 | nr->snd_portid = nca->info->snd_portid; | |
357 | nr->nlhdr = *nca->info->nlhdr; | |
358 | } | |
359 | ||
6389eaa7 GS |
360 | /* Prepare the packet */ |
361 | nca->id = nr->id; | |
362 | ret = nch->handler(nr->cmd, nca); | |
363 | if (ret) { | |
364 | ncsi_free_request(nr); | |
365 | return ret; | |
366 | } | |
367 | ||
368 | /* Fill the ethernet header */ | |
d58ff351 | 369 | eh = skb_push(nr->cmd, sizeof(*eh)); |
6389eaa7 GS |
370 | eh->h_proto = htons(ETH_P_NCSI); |
371 | eth_broadcast_addr(eh->h_dest); | |
372 | eth_broadcast_addr(eh->h_source); | |
373 | ||
374 | /* Start the timer for the request that might not have | |
375 | * corresponding response. Given NCSI is an internal | |
376 | * connection a 1 second delay should be sufficient. | |
377 | */ | |
378 | nr->enabled = true; | |
379 | mod_timer(&nr->timer, jiffies + 1 * HZ); | |
380 | ||
381 | /* Send NCSI packet */ | |
382 | skb_get(nr->cmd); | |
383 | ret = dev_queue_xmit(nr->cmd); | |
384 | if (ret < 0) { | |
385 | ncsi_free_request(nr); | |
386 | return ret; | |
387 | } | |
388 | ||
389 | return 0; | |
390 | } |