]>
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 | ||
214 | static struct ncsi_cmd_handler { | |
215 | unsigned char type; | |
216 | int payload; | |
217 | int (*handler)(struct sk_buff *skb, | |
218 | struct ncsi_cmd_arg *nca); | |
219 | } ncsi_cmd_handlers[] = { | |
220 | { NCSI_PKT_CMD_CIS, 0, ncsi_cmd_handler_default }, | |
221 | { NCSI_PKT_CMD_SP, 4, ncsi_cmd_handler_sp }, | |
222 | { NCSI_PKT_CMD_DP, 0, ncsi_cmd_handler_default }, | |
223 | { NCSI_PKT_CMD_EC, 0, ncsi_cmd_handler_default }, | |
224 | { NCSI_PKT_CMD_DC, 4, ncsi_cmd_handler_dc }, | |
225 | { NCSI_PKT_CMD_RC, 4, ncsi_cmd_handler_rc }, | |
226 | { NCSI_PKT_CMD_ECNT, 0, ncsi_cmd_handler_default }, | |
227 | { NCSI_PKT_CMD_DCNT, 0, ncsi_cmd_handler_default }, | |
228 | { NCSI_PKT_CMD_AE, 8, ncsi_cmd_handler_ae }, | |
229 | { NCSI_PKT_CMD_SL, 8, ncsi_cmd_handler_sl }, | |
230 | { NCSI_PKT_CMD_GLS, 0, ncsi_cmd_handler_default }, | |
8579a67e | 231 | { NCSI_PKT_CMD_SVF, 8, ncsi_cmd_handler_svf }, |
6389eaa7 GS |
232 | { NCSI_PKT_CMD_EV, 4, ncsi_cmd_handler_ev }, |
233 | { NCSI_PKT_CMD_DV, 0, ncsi_cmd_handler_default }, | |
234 | { NCSI_PKT_CMD_SMA, 8, ncsi_cmd_handler_sma }, | |
235 | { NCSI_PKT_CMD_EBF, 4, ncsi_cmd_handler_ebf }, | |
236 | { NCSI_PKT_CMD_DBF, 0, ncsi_cmd_handler_default }, | |
237 | { NCSI_PKT_CMD_EGMF, 4, ncsi_cmd_handler_egmf }, | |
238 | { NCSI_PKT_CMD_DGMF, 0, ncsi_cmd_handler_default }, | |
239 | { NCSI_PKT_CMD_SNFC, 4, ncsi_cmd_handler_snfc }, | |
240 | { NCSI_PKT_CMD_GVI, 0, ncsi_cmd_handler_default }, | |
241 | { NCSI_PKT_CMD_GC, 0, ncsi_cmd_handler_default }, | |
242 | { NCSI_PKT_CMD_GP, 0, ncsi_cmd_handler_default }, | |
243 | { NCSI_PKT_CMD_GCPS, 0, ncsi_cmd_handler_default }, | |
244 | { NCSI_PKT_CMD_GNS, 0, ncsi_cmd_handler_default }, | |
245 | { NCSI_PKT_CMD_GNPTS, 0, ncsi_cmd_handler_default }, | |
246 | { NCSI_PKT_CMD_GPS, 0, ncsi_cmd_handler_default }, | |
247 | { NCSI_PKT_CMD_OEM, 0, NULL }, | |
248 | { NCSI_PKT_CMD_PLDM, 0, NULL }, | |
249 | { NCSI_PKT_CMD_GPUUID, 0, ncsi_cmd_handler_default } | |
250 | }; | |
251 | ||
252 | static struct ncsi_request *ncsi_alloc_command(struct ncsi_cmd_arg *nca) | |
253 | { | |
254 | struct ncsi_dev_priv *ndp = nca->ndp; | |
255 | struct ncsi_dev *nd = &ndp->ndev; | |
256 | struct net_device *dev = nd->dev; | |
257 | int hlen = LL_RESERVED_SPACE(dev); | |
258 | int tlen = dev->needed_tailroom; | |
259 | int len = hlen + tlen; | |
260 | struct sk_buff *skb; | |
261 | struct ncsi_request *nr; | |
262 | ||
a0509cbe | 263 | nr = ncsi_alloc_request(ndp, nca->req_flags); |
6389eaa7 GS |
264 | if (!nr) |
265 | return NULL; | |
266 | ||
267 | /* NCSI command packet has 16-bytes header, payload, 4 bytes checksum. | |
268 | * The packet needs padding if its payload is less than 26 bytes to | |
269 | * meet 64 bytes minimal ethernet frame length. | |
270 | */ | |
271 | len += sizeof(struct ncsi_cmd_pkt_hdr) + 4; | |
272 | if (nca->payload < 26) | |
273 | len += 26; | |
274 | else | |
275 | len += nca->payload; | |
276 | ||
277 | /* Allocate skb */ | |
278 | skb = alloc_skb(len, GFP_ATOMIC); | |
279 | if (!skb) { | |
280 | ncsi_free_request(nr); | |
281 | return NULL; | |
282 | } | |
283 | ||
284 | nr->cmd = skb; | |
285 | skb_reserve(skb, hlen); | |
286 | skb_reset_network_header(skb); | |
287 | ||
288 | skb->dev = dev; | |
289 | skb->protocol = htons(ETH_P_NCSI); | |
290 | ||
291 | return nr; | |
292 | } | |
293 | ||
294 | int ncsi_xmit_cmd(struct ncsi_cmd_arg *nca) | |
295 | { | |
296 | struct ncsi_request *nr; | |
297 | struct ethhdr *eh; | |
298 | struct ncsi_cmd_handler *nch = NULL; | |
299 | int i, ret; | |
300 | ||
301 | /* Search for the handler */ | |
302 | for (i = 0; i < ARRAY_SIZE(ncsi_cmd_handlers); i++) { | |
303 | if (ncsi_cmd_handlers[i].type == nca->type) { | |
304 | if (ncsi_cmd_handlers[i].handler) | |
305 | nch = &ncsi_cmd_handlers[i]; | |
306 | else | |
307 | nch = NULL; | |
308 | ||
309 | break; | |
310 | } | |
311 | } | |
312 | ||
313 | if (!nch) { | |
314 | netdev_err(nca->ndp->ndev.dev, | |
315 | "Cannot send packet with type 0x%02x\n", nca->type); | |
316 | return -ENOENT; | |
317 | } | |
318 | ||
319 | /* Get packet payload length and allocate the request */ | |
320 | nca->payload = nch->payload; | |
321 | nr = ncsi_alloc_command(nca); | |
322 | if (!nr) | |
323 | return -ENOMEM; | |
324 | ||
325 | /* Prepare the packet */ | |
326 | nca->id = nr->id; | |
327 | ret = nch->handler(nr->cmd, nca); | |
328 | if (ret) { | |
329 | ncsi_free_request(nr); | |
330 | return ret; | |
331 | } | |
332 | ||
333 | /* Fill the ethernet header */ | |
d58ff351 | 334 | eh = skb_push(nr->cmd, sizeof(*eh)); |
6389eaa7 GS |
335 | eh->h_proto = htons(ETH_P_NCSI); |
336 | eth_broadcast_addr(eh->h_dest); | |
337 | eth_broadcast_addr(eh->h_source); | |
338 | ||
339 | /* Start the timer for the request that might not have | |
340 | * corresponding response. Given NCSI is an internal | |
341 | * connection a 1 second delay should be sufficient. | |
342 | */ | |
343 | nr->enabled = true; | |
344 | mod_timer(&nr->timer, jiffies + 1 * HZ); | |
345 | ||
346 | /* Send NCSI packet */ | |
347 | skb_get(nr->cmd); | |
348 | ret = dev_queue_xmit(nr->cmd); | |
349 | if (ret < 0) { | |
350 | ncsi_free_request(nr); | |
351 | return ret; | |
352 | } | |
353 | ||
354 | return 0; | |
355 | } |