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