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