]>
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 | ||
69 | cmd = (struct ncsi_cmd_pkt *)skb_put(skb, sizeof(*cmd)); | |
70 | memset(cmd, 0, sizeof(*cmd)); | |
71 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
72 | ||
73 | return 0; | |
74 | } | |
75 | ||
76 | static int ncsi_cmd_handler_sp(struct sk_buff *skb, | |
77 | struct ncsi_cmd_arg *nca) | |
78 | { | |
79 | struct ncsi_cmd_sp_pkt *cmd; | |
80 | ||
81 | cmd = (struct ncsi_cmd_sp_pkt *)skb_put(skb, sizeof(*cmd)); | |
82 | memset(cmd, 0, sizeof(*cmd)); | |
83 | cmd->hw_arbitration = nca->bytes[0]; | |
84 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
85 | ||
86 | return 0; | |
87 | } | |
88 | ||
89 | static int ncsi_cmd_handler_dc(struct sk_buff *skb, | |
90 | struct ncsi_cmd_arg *nca) | |
91 | { | |
92 | struct ncsi_cmd_dc_pkt *cmd; | |
93 | ||
94 | cmd = (struct ncsi_cmd_dc_pkt *)skb_put(skb, sizeof(*cmd)); | |
95 | memset(cmd, 0, sizeof(*cmd)); | |
96 | cmd->ald = nca->bytes[0]; | |
97 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
98 | ||
99 | return 0; | |
100 | } | |
101 | ||
102 | static int ncsi_cmd_handler_rc(struct sk_buff *skb, | |
103 | struct ncsi_cmd_arg *nca) | |
104 | { | |
105 | struct ncsi_cmd_rc_pkt *cmd; | |
106 | ||
107 | cmd = (struct ncsi_cmd_rc_pkt *)skb_put(skb, sizeof(*cmd)); | |
108 | memset(cmd, 0, sizeof(*cmd)); | |
109 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
110 | ||
111 | return 0; | |
112 | } | |
113 | ||
114 | static int ncsi_cmd_handler_ae(struct sk_buff *skb, | |
115 | struct ncsi_cmd_arg *nca) | |
116 | { | |
117 | struct ncsi_cmd_ae_pkt *cmd; | |
118 | ||
119 | cmd = (struct ncsi_cmd_ae_pkt *)skb_put(skb, sizeof(*cmd)); | |
120 | memset(cmd, 0, sizeof(*cmd)); | |
121 | cmd->mc_id = nca->bytes[0]; | |
122 | cmd->mode = htonl(nca->dwords[1]); | |
123 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
124 | ||
125 | return 0; | |
126 | } | |
127 | ||
128 | static int ncsi_cmd_handler_sl(struct sk_buff *skb, | |
129 | struct ncsi_cmd_arg *nca) | |
130 | { | |
131 | struct ncsi_cmd_sl_pkt *cmd; | |
132 | ||
133 | cmd = (struct ncsi_cmd_sl_pkt *)skb_put(skb, sizeof(*cmd)); | |
134 | memset(cmd, 0, sizeof(*cmd)); | |
135 | cmd->mode = htonl(nca->dwords[0]); | |
136 | cmd->oem_mode = htonl(nca->dwords[1]); | |
137 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
138 | ||
139 | return 0; | |
140 | } | |
141 | ||
142 | static int ncsi_cmd_handler_svf(struct sk_buff *skb, | |
143 | struct ncsi_cmd_arg *nca) | |
144 | { | |
145 | struct ncsi_cmd_svf_pkt *cmd; | |
146 | ||
147 | cmd = (struct ncsi_cmd_svf_pkt *)skb_put(skb, sizeof(*cmd)); | |
148 | memset(cmd, 0, sizeof(*cmd)); | |
149 | cmd->vlan = htons(nca->words[0]); | |
150 | cmd->index = nca->bytes[2]; | |
151 | cmd->enable = nca->bytes[3]; | |
152 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
153 | ||
154 | return 0; | |
155 | } | |
156 | ||
157 | static int ncsi_cmd_handler_ev(struct sk_buff *skb, | |
158 | struct ncsi_cmd_arg *nca) | |
159 | { | |
160 | struct ncsi_cmd_ev_pkt *cmd; | |
161 | ||
162 | cmd = (struct ncsi_cmd_ev_pkt *)skb_put(skb, sizeof(*cmd)); | |
163 | memset(cmd, 0, sizeof(*cmd)); | |
164 | cmd->mode = nca->bytes[0]; | |
165 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
166 | ||
167 | return 0; | |
168 | } | |
169 | ||
170 | static int ncsi_cmd_handler_sma(struct sk_buff *skb, | |
171 | struct ncsi_cmd_arg *nca) | |
172 | { | |
173 | struct ncsi_cmd_sma_pkt *cmd; | |
174 | int i; | |
175 | ||
176 | cmd = (struct ncsi_cmd_sma_pkt *)skb_put(skb, sizeof(*cmd)); | |
177 | memset(cmd, 0, sizeof(*cmd)); | |
178 | for (i = 0; i < 6; i++) | |
179 | cmd->mac[i] = nca->bytes[i]; | |
180 | cmd->index = nca->bytes[6]; | |
181 | cmd->at_e = nca->bytes[7]; | |
182 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | static int ncsi_cmd_handler_ebf(struct sk_buff *skb, | |
188 | struct ncsi_cmd_arg *nca) | |
189 | { | |
190 | struct ncsi_cmd_ebf_pkt *cmd; | |
191 | ||
192 | cmd = (struct ncsi_cmd_ebf_pkt *)skb_put(skb, sizeof(*cmd)); | |
193 | memset(cmd, 0, sizeof(*cmd)); | |
194 | cmd->mode = htonl(nca->dwords[0]); | |
195 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | static int ncsi_cmd_handler_egmf(struct sk_buff *skb, | |
201 | struct ncsi_cmd_arg *nca) | |
202 | { | |
203 | struct ncsi_cmd_egmf_pkt *cmd; | |
204 | ||
205 | cmd = (struct ncsi_cmd_egmf_pkt *)skb_put(skb, sizeof(*cmd)); | |
206 | memset(cmd, 0, sizeof(*cmd)); | |
207 | cmd->mode = htonl(nca->dwords[0]); | |
208 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
213 | static int ncsi_cmd_handler_snfc(struct sk_buff *skb, | |
214 | struct ncsi_cmd_arg *nca) | |
215 | { | |
216 | struct ncsi_cmd_snfc_pkt *cmd; | |
217 | ||
218 | cmd = (struct ncsi_cmd_snfc_pkt *)skb_put(skb, sizeof(*cmd)); | |
219 | memset(cmd, 0, sizeof(*cmd)); | |
220 | cmd->mode = nca->bytes[0]; | |
221 | ncsi_cmd_build_header(&cmd->cmd.common, nca); | |
222 | ||
223 | return 0; | |
224 | } | |
225 | ||
226 | static struct ncsi_cmd_handler { | |
227 | unsigned char type; | |
228 | int payload; | |
229 | int (*handler)(struct sk_buff *skb, | |
230 | struct ncsi_cmd_arg *nca); | |
231 | } ncsi_cmd_handlers[] = { | |
232 | { NCSI_PKT_CMD_CIS, 0, ncsi_cmd_handler_default }, | |
233 | { NCSI_PKT_CMD_SP, 4, ncsi_cmd_handler_sp }, | |
234 | { NCSI_PKT_CMD_DP, 0, ncsi_cmd_handler_default }, | |
235 | { NCSI_PKT_CMD_EC, 0, ncsi_cmd_handler_default }, | |
236 | { NCSI_PKT_CMD_DC, 4, ncsi_cmd_handler_dc }, | |
237 | { NCSI_PKT_CMD_RC, 4, ncsi_cmd_handler_rc }, | |
238 | { NCSI_PKT_CMD_ECNT, 0, ncsi_cmd_handler_default }, | |
239 | { NCSI_PKT_CMD_DCNT, 0, ncsi_cmd_handler_default }, | |
240 | { NCSI_PKT_CMD_AE, 8, ncsi_cmd_handler_ae }, | |
241 | { NCSI_PKT_CMD_SL, 8, ncsi_cmd_handler_sl }, | |
242 | { NCSI_PKT_CMD_GLS, 0, ncsi_cmd_handler_default }, | |
243 | { NCSI_PKT_CMD_SVF, 4, ncsi_cmd_handler_svf }, | |
244 | { NCSI_PKT_CMD_EV, 4, ncsi_cmd_handler_ev }, | |
245 | { NCSI_PKT_CMD_DV, 0, ncsi_cmd_handler_default }, | |
246 | { NCSI_PKT_CMD_SMA, 8, ncsi_cmd_handler_sma }, | |
247 | { NCSI_PKT_CMD_EBF, 4, ncsi_cmd_handler_ebf }, | |
248 | { NCSI_PKT_CMD_DBF, 0, ncsi_cmd_handler_default }, | |
249 | { NCSI_PKT_CMD_EGMF, 4, ncsi_cmd_handler_egmf }, | |
250 | { NCSI_PKT_CMD_DGMF, 0, ncsi_cmd_handler_default }, | |
251 | { NCSI_PKT_CMD_SNFC, 4, ncsi_cmd_handler_snfc }, | |
252 | { NCSI_PKT_CMD_GVI, 0, ncsi_cmd_handler_default }, | |
253 | { NCSI_PKT_CMD_GC, 0, ncsi_cmd_handler_default }, | |
254 | { NCSI_PKT_CMD_GP, 0, ncsi_cmd_handler_default }, | |
255 | { NCSI_PKT_CMD_GCPS, 0, ncsi_cmd_handler_default }, | |
256 | { NCSI_PKT_CMD_GNS, 0, ncsi_cmd_handler_default }, | |
257 | { NCSI_PKT_CMD_GNPTS, 0, ncsi_cmd_handler_default }, | |
258 | { NCSI_PKT_CMD_GPS, 0, ncsi_cmd_handler_default }, | |
259 | { NCSI_PKT_CMD_OEM, 0, NULL }, | |
260 | { NCSI_PKT_CMD_PLDM, 0, NULL }, | |
261 | { NCSI_PKT_CMD_GPUUID, 0, ncsi_cmd_handler_default } | |
262 | }; | |
263 | ||
264 | static struct ncsi_request *ncsi_alloc_command(struct ncsi_cmd_arg *nca) | |
265 | { | |
266 | struct ncsi_dev_priv *ndp = nca->ndp; | |
267 | struct ncsi_dev *nd = &ndp->ndev; | |
268 | struct net_device *dev = nd->dev; | |
269 | int hlen = LL_RESERVED_SPACE(dev); | |
270 | int tlen = dev->needed_tailroom; | |
271 | int len = hlen + tlen; | |
272 | struct sk_buff *skb; | |
273 | struct ncsi_request *nr; | |
274 | ||
275 | nr = ncsi_alloc_request(ndp, nca->driven); | |
276 | if (!nr) | |
277 | return NULL; | |
278 | ||
279 | /* NCSI command packet has 16-bytes header, payload, 4 bytes checksum. | |
280 | * The packet needs padding if its payload is less than 26 bytes to | |
281 | * meet 64 bytes minimal ethernet frame length. | |
282 | */ | |
283 | len += sizeof(struct ncsi_cmd_pkt_hdr) + 4; | |
284 | if (nca->payload < 26) | |
285 | len += 26; | |
286 | else | |
287 | len += nca->payload; | |
288 | ||
289 | /* Allocate skb */ | |
290 | skb = alloc_skb(len, GFP_ATOMIC); | |
291 | if (!skb) { | |
292 | ncsi_free_request(nr); | |
293 | return NULL; | |
294 | } | |
295 | ||
296 | nr->cmd = skb; | |
297 | skb_reserve(skb, hlen); | |
298 | skb_reset_network_header(skb); | |
299 | ||
300 | skb->dev = dev; | |
301 | skb->protocol = htons(ETH_P_NCSI); | |
302 | ||
303 | return nr; | |
304 | } | |
305 | ||
306 | int ncsi_xmit_cmd(struct ncsi_cmd_arg *nca) | |
307 | { | |
308 | struct ncsi_request *nr; | |
309 | struct ethhdr *eh; | |
310 | struct ncsi_cmd_handler *nch = NULL; | |
311 | int i, ret; | |
312 | ||
313 | /* Search for the handler */ | |
314 | for (i = 0; i < ARRAY_SIZE(ncsi_cmd_handlers); i++) { | |
315 | if (ncsi_cmd_handlers[i].type == nca->type) { | |
316 | if (ncsi_cmd_handlers[i].handler) | |
317 | nch = &ncsi_cmd_handlers[i]; | |
318 | else | |
319 | nch = NULL; | |
320 | ||
321 | break; | |
322 | } | |
323 | } | |
324 | ||
325 | if (!nch) { | |
326 | netdev_err(nca->ndp->ndev.dev, | |
327 | "Cannot send packet with type 0x%02x\n", nca->type); | |
328 | return -ENOENT; | |
329 | } | |
330 | ||
331 | /* Get packet payload length and allocate the request */ | |
332 | nca->payload = nch->payload; | |
333 | nr = ncsi_alloc_command(nca); | |
334 | if (!nr) | |
335 | return -ENOMEM; | |
336 | ||
337 | /* Prepare the packet */ | |
338 | nca->id = nr->id; | |
339 | ret = nch->handler(nr->cmd, nca); | |
340 | if (ret) { | |
341 | ncsi_free_request(nr); | |
342 | return ret; | |
343 | } | |
344 | ||
345 | /* Fill the ethernet header */ | |
346 | eh = (struct ethhdr *)skb_push(nr->cmd, sizeof(*eh)); | |
347 | eh->h_proto = htons(ETH_P_NCSI); | |
348 | eth_broadcast_addr(eh->h_dest); | |
349 | eth_broadcast_addr(eh->h_source); | |
350 | ||
351 | /* Start the timer for the request that might not have | |
352 | * corresponding response. Given NCSI is an internal | |
353 | * connection a 1 second delay should be sufficient. | |
354 | */ | |
355 | nr->enabled = true; | |
356 | mod_timer(&nr->timer, jiffies + 1 * HZ); | |
357 | ||
358 | /* Send NCSI packet */ | |
359 | skb_get(nr->cmd); | |
360 | ret = dev_queue_xmit(nr->cmd); | |
361 | if (ret < 0) { | |
362 | ncsi_free_request(nr); | |
363 | return ret; | |
364 | } | |
365 | ||
366 | return 0; | |
367 | } |