]>
Commit | Line | Data |
---|---|---|
59fcd7c6 SO |
1 | /* |
2 | * | |
3 | * Intel Management Engine Interface (Intel MEI) Linux driver | |
4 | * Copyright (c) 2003-2013, Intel Corporation. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms and conditions of the GNU General Public License, | |
8 | * version 2, as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | * | |
15 | */ | |
16 | ||
17 | #include <linux/kernel.h> | |
36eda94f | 18 | #include <linux/sched.h> |
59fcd7c6 SO |
19 | #include <linux/module.h> |
20 | #include <linux/moduleparam.h> | |
21 | #include <linux/device.h> | |
22 | #include <linux/pci.h> | |
23 | #include <linux/mei_cl_bus.h> | |
24 | ||
25 | #include "mei_dev.h" | |
26 | #include "client.h" | |
27 | ||
28 | struct mei_nfc_cmd { | |
29 | u8 command; | |
30 | u8 status; | |
31 | u16 req_id; | |
32 | u32 reserved; | |
33 | u16 data_size; | |
34 | u8 sub_command; | |
35 | u8 data[]; | |
36 | } __packed; | |
37 | ||
38 | struct mei_nfc_reply { | |
39 | u8 command; | |
40 | u8 status; | |
41 | u16 req_id; | |
42 | u32 reserved; | |
43 | u16 data_size; | |
44 | u8 sub_command; | |
45 | u8 reply_status; | |
46 | u8 data[]; | |
47 | } __packed; | |
48 | ||
49 | struct mei_nfc_if_version { | |
50 | u8 radio_version_sw[3]; | |
51 | u8 reserved[3]; | |
52 | u8 radio_version_hw[3]; | |
53 | u8 i2c_addr; | |
54 | u8 fw_ivn; | |
55 | u8 vendor_id; | |
56 | u8 radio_type; | |
57 | } __packed; | |
58 | ||
59 | struct mei_nfc_connect { | |
60 | u8 fw_ivn; | |
61 | u8 vendor_id; | |
62 | } __packed; | |
63 | ||
64 | struct mei_nfc_connect_resp { | |
65 | u8 fw_ivn; | |
66 | u8 vendor_id; | |
67 | u16 me_major; | |
68 | u16 me_minor; | |
69 | u16 me_hotfix; | |
70 | u16 me_build; | |
71 | } __packed; | |
72 | ||
73 | struct mei_nfc_hci_hdr { | |
74 | u8 cmd; | |
75 | u8 status; | |
76 | u16 req_id; | |
77 | u32 reserved; | |
78 | u16 data_size; | |
79 | } __packed; | |
80 | ||
81 | #define MEI_NFC_CMD_MAINTENANCE 0x00 | |
82 | #define MEI_NFC_CMD_HCI_SEND 0x01 | |
83 | #define MEI_NFC_CMD_HCI_RECV 0x02 | |
84 | ||
85 | #define MEI_NFC_SUBCMD_CONNECT 0x00 | |
86 | #define MEI_NFC_SUBCMD_IF_VERSION 0x01 | |
87 | ||
88 | #define MEI_NFC_HEADER_SIZE 10 | |
89 | ||
90 | /** mei_nfc_dev - NFC mei device | |
91 | * | |
92 | * @cl: NFC host client | |
93 | * @cl_info: NFC info host client | |
94 | * @init_work: perform connection to the info client | |
95 | * @fw_ivn: NFC Intervace Version Number | |
96 | * @vendor_id: NFC manufacturer ID | |
97 | * @radio_type: NFC radio type | |
98 | */ | |
99 | struct mei_nfc_dev { | |
100 | struct mei_cl *cl; | |
101 | struct mei_cl *cl_info; | |
102 | struct work_struct init_work; | |
36eda94f | 103 | wait_queue_head_t send_wq; |
59fcd7c6 SO |
104 | u8 fw_ivn; |
105 | u8 vendor_id; | |
106 | u8 radio_type; | |
91a6b95f | 107 | char *bus_name; |
36eda94f SO |
108 | |
109 | u16 req_id; | |
110 | u16 recv_req_id; | |
59fcd7c6 SO |
111 | }; |
112 | ||
113 | static struct mei_nfc_dev nfc_dev; | |
114 | ||
115 | /* UUIDs for NFC F/W clients */ | |
116 | const uuid_le mei_nfc_guid = UUID_LE(0x0bb17a78, 0x2a8e, 0x4c50, | |
117 | 0x94, 0xd4, 0x50, 0x26, | |
118 | 0x67, 0x23, 0x77, 0x5c); | |
119 | ||
120 | static const uuid_le mei_nfc_info_guid = UUID_LE(0xd2de1625, 0x382d, 0x417d, | |
121 | 0x48, 0xa4, 0xef, 0xab, | |
122 | 0xba, 0x8a, 0x12, 0x06); | |
123 | ||
91a6b95f SO |
124 | /* Vendors */ |
125 | #define MEI_NFC_VENDOR_INSIDE 0x00 | |
126 | #define MEI_NFC_VENDOR_NXP 0x01 | |
127 | ||
128 | /* Radio types */ | |
129 | #define MEI_NFC_VENDOR_INSIDE_UREAD 0x00 | |
130 | #define MEI_NFC_VENDOR_NXP_PN544 0x01 | |
131 | ||
59fcd7c6 SO |
132 | static void mei_nfc_free(struct mei_nfc_dev *ndev) |
133 | { | |
134 | if (ndev->cl) { | |
135 | list_del(&ndev->cl->device_link); | |
136 | mei_cl_unlink(ndev->cl); | |
137 | kfree(ndev->cl); | |
138 | } | |
139 | ||
140 | if (ndev->cl_info) { | |
141 | list_del(&ndev->cl_info->device_link); | |
142 | mei_cl_unlink(ndev->cl_info); | |
143 | kfree(ndev->cl_info); | |
144 | } | |
2753ff53 TW |
145 | |
146 | memset(ndev, 0, sizeof(struct mei_nfc_dev)); | |
59fcd7c6 SO |
147 | } |
148 | ||
91a6b95f SO |
149 | static int mei_nfc_build_bus_name(struct mei_nfc_dev *ndev) |
150 | { | |
151 | struct mei_device *dev; | |
152 | ||
153 | if (!ndev->cl) | |
154 | return -ENODEV; | |
155 | ||
156 | dev = ndev->cl->dev; | |
157 | ||
158 | switch (ndev->vendor_id) { | |
159 | case MEI_NFC_VENDOR_INSIDE: | |
160 | switch (ndev->radio_type) { | |
161 | case MEI_NFC_VENDOR_INSIDE_UREAD: | |
162 | ndev->bus_name = "microread"; | |
163 | return 0; | |
164 | ||
165 | default: | |
166 | dev_err(&dev->pdev->dev, "Unknow radio type 0x%x\n", | |
167 | ndev->radio_type); | |
168 | ||
169 | return -EINVAL; | |
170 | } | |
171 | ||
172 | case MEI_NFC_VENDOR_NXP: | |
173 | switch (ndev->radio_type) { | |
174 | case MEI_NFC_VENDOR_NXP_PN544: | |
175 | ndev->bus_name = "pn544"; | |
176 | return 0; | |
177 | default: | |
178 | dev_err(&dev->pdev->dev, "Unknow radio type 0x%x\n", | |
179 | ndev->radio_type); | |
180 | ||
181 | return -EINVAL; | |
182 | } | |
183 | ||
184 | default: | |
185 | dev_err(&dev->pdev->dev, "Unknow vendor ID 0x%x\n", | |
186 | ndev->vendor_id); | |
187 | ||
188 | return -EINVAL; | |
189 | } | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
36eda94f SO |
194 | static int mei_nfc_connect(struct mei_nfc_dev *ndev) |
195 | { | |
196 | struct mei_device *dev; | |
197 | struct mei_cl *cl; | |
198 | struct mei_nfc_cmd *cmd, *reply; | |
199 | struct mei_nfc_connect *connect; | |
200 | struct mei_nfc_connect_resp *connect_resp; | |
201 | size_t connect_length, connect_resp_length; | |
202 | int bytes_recv, ret; | |
203 | ||
204 | cl = ndev->cl; | |
205 | dev = cl->dev; | |
206 | ||
207 | connect_length = sizeof(struct mei_nfc_cmd) + | |
208 | sizeof(struct mei_nfc_connect); | |
209 | ||
210 | connect_resp_length = sizeof(struct mei_nfc_cmd) + | |
211 | sizeof(struct mei_nfc_connect_resp); | |
212 | ||
213 | cmd = kzalloc(connect_length, GFP_KERNEL); | |
214 | if (!cmd) | |
215 | return -ENOMEM; | |
216 | connect = (struct mei_nfc_connect *)cmd->data; | |
217 | ||
218 | reply = kzalloc(connect_resp_length, GFP_KERNEL); | |
219 | if (!reply) { | |
220 | kfree(cmd); | |
221 | return -ENOMEM; | |
222 | } | |
223 | ||
224 | connect_resp = (struct mei_nfc_connect_resp *)reply->data; | |
225 | ||
226 | cmd->command = MEI_NFC_CMD_MAINTENANCE; | |
227 | cmd->data_size = 3; | |
228 | cmd->sub_command = MEI_NFC_SUBCMD_CONNECT; | |
229 | connect->fw_ivn = ndev->fw_ivn; | |
230 | connect->vendor_id = ndev->vendor_id; | |
231 | ||
232 | ret = __mei_cl_send(cl, (u8 *)cmd, connect_length); | |
233 | if (ret < 0) { | |
234 | dev_err(&dev->pdev->dev, "Could not send connect cmd\n"); | |
235 | goto err; | |
236 | } | |
237 | ||
238 | bytes_recv = __mei_cl_recv(cl, (u8 *)reply, connect_resp_length); | |
239 | if (bytes_recv < 0) { | |
240 | dev_err(&dev->pdev->dev, "Could not read connect response\n"); | |
241 | ret = bytes_recv; | |
242 | goto err; | |
243 | } | |
244 | ||
245 | dev_info(&dev->pdev->dev, "IVN 0x%x Vendor ID 0x%x\n", | |
246 | connect_resp->fw_ivn, connect_resp->vendor_id); | |
247 | ||
248 | dev_info(&dev->pdev->dev, "ME FW %d.%d.%d.%d\n", | |
249 | connect_resp->me_major, connect_resp->me_minor, | |
250 | connect_resp->me_hotfix, connect_resp->me_build); | |
251 | ||
252 | ret = 0; | |
253 | ||
254 | err: | |
255 | kfree(reply); | |
256 | kfree(cmd); | |
257 | ||
258 | return ret; | |
259 | } | |
260 | ||
59fcd7c6 SO |
261 | static int mei_nfc_if_version(struct mei_nfc_dev *ndev) |
262 | { | |
263 | struct mei_device *dev; | |
264 | struct mei_cl *cl; | |
265 | ||
266 | struct mei_nfc_cmd cmd; | |
267 | struct mei_nfc_reply *reply = NULL; | |
268 | struct mei_nfc_if_version *version; | |
269 | size_t if_version_length; | |
270 | int bytes_recv, ret; | |
271 | ||
272 | cl = ndev->cl_info; | |
273 | dev = cl->dev; | |
274 | ||
275 | memset(&cmd, 0, sizeof(struct mei_nfc_cmd)); | |
276 | cmd.command = MEI_NFC_CMD_MAINTENANCE; | |
277 | cmd.data_size = 1; | |
278 | cmd.sub_command = MEI_NFC_SUBCMD_IF_VERSION; | |
279 | ||
280 | ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(struct mei_nfc_cmd)); | |
281 | if (ret < 0) { | |
282 | dev_err(&dev->pdev->dev, "Could not send IF version cmd\n"); | |
283 | return ret; | |
284 | } | |
285 | ||
286 | /* to be sure on the stack we alloc memory */ | |
287 | if_version_length = sizeof(struct mei_nfc_reply) + | |
288 | sizeof(struct mei_nfc_if_version); | |
289 | ||
290 | reply = kzalloc(if_version_length, GFP_KERNEL); | |
291 | if (!reply) | |
292 | return -ENOMEM; | |
293 | ||
294 | bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length); | |
295 | if (bytes_recv < 0 || bytes_recv < sizeof(struct mei_nfc_reply)) { | |
296 | dev_err(&dev->pdev->dev, "Could not read IF version\n"); | |
297 | ret = -EIO; | |
298 | goto err; | |
299 | } | |
300 | ||
301 | version = (struct mei_nfc_if_version *)reply->data; | |
302 | ||
303 | ndev->fw_ivn = version->fw_ivn; | |
304 | ndev->vendor_id = version->vendor_id; | |
305 | ndev->radio_type = version->radio_type; | |
306 | ||
307 | err: | |
308 | kfree(reply); | |
309 | return ret; | |
310 | } | |
311 | ||
36eda94f SO |
312 | static int mei_nfc_enable(struct mei_cl_device *cldev) |
313 | { | |
314 | struct mei_device *dev; | |
315 | struct mei_nfc_dev *ndev = &nfc_dev; | |
316 | int ret; | |
317 | ||
318 | dev = ndev->cl->dev; | |
319 | ||
320 | ret = mei_nfc_connect(ndev); | |
321 | if (ret < 0) { | |
322 | dev_err(&dev->pdev->dev, "Could not connect to NFC"); | |
323 | return ret; | |
324 | } | |
325 | ||
326 | return 0; | |
327 | } | |
328 | ||
329 | static int mei_nfc_disable(struct mei_cl_device *cldev) | |
330 | { | |
331 | return 0; | |
332 | } | |
333 | ||
334 | static int mei_nfc_send(struct mei_cl_device *cldev, u8 *buf, size_t length) | |
335 | { | |
336 | struct mei_device *dev; | |
337 | struct mei_nfc_dev *ndev; | |
338 | struct mei_nfc_hci_hdr *hdr; | |
339 | u8 *mei_buf; | |
340 | int err; | |
341 | ||
342 | ndev = (struct mei_nfc_dev *) cldev->priv_data; | |
343 | dev = ndev->cl->dev; | |
344 | ||
345 | mei_buf = kzalloc(length + MEI_NFC_HEADER_SIZE, GFP_KERNEL); | |
346 | if (!mei_buf) | |
347 | return -ENOMEM; | |
348 | ||
349 | hdr = (struct mei_nfc_hci_hdr *) mei_buf; | |
350 | hdr->cmd = MEI_NFC_CMD_HCI_SEND; | |
351 | hdr->status = 0; | |
352 | hdr->req_id = ndev->req_id; | |
353 | hdr->reserved = 0; | |
354 | hdr->data_size = length; | |
355 | ||
356 | memcpy(mei_buf + MEI_NFC_HEADER_SIZE, buf, length); | |
357 | ||
358 | err = __mei_cl_send(ndev->cl, mei_buf, length + MEI_NFC_HEADER_SIZE); | |
359 | if (err < 0) | |
360 | return err; | |
361 | ||
362 | kfree(mei_buf); | |
363 | ||
364 | if (!wait_event_interruptible_timeout(ndev->send_wq, | |
365 | ndev->recv_req_id == ndev->req_id, HZ)) { | |
366 | dev_err(&dev->pdev->dev, "NFC MEI command timeout\n"); | |
367 | err = -ETIMEDOUT; | |
368 | } else { | |
369 | ndev->req_id++; | |
370 | } | |
371 | ||
372 | return err; | |
373 | } | |
374 | ||
375 | static int mei_nfc_recv(struct mei_cl_device *cldev, u8 *buf, size_t length) | |
376 | { | |
377 | struct mei_nfc_dev *ndev; | |
378 | struct mei_nfc_hci_hdr *hci_hdr; | |
379 | int received_length; | |
380 | ||
381 | ndev = (struct mei_nfc_dev *)cldev->priv_data; | |
382 | ||
383 | received_length = __mei_cl_recv(ndev->cl, buf, length); | |
384 | if (received_length < 0) | |
385 | return received_length; | |
386 | ||
387 | hci_hdr = (struct mei_nfc_hci_hdr *) buf; | |
388 | ||
389 | if (hci_hdr->cmd == MEI_NFC_CMD_HCI_SEND) { | |
390 | ndev->recv_req_id = hci_hdr->req_id; | |
391 | wake_up(&ndev->send_wq); | |
392 | ||
393 | return 0; | |
394 | } | |
395 | ||
396 | return received_length; | |
397 | } | |
398 | ||
399 | static struct mei_cl_ops nfc_ops = { | |
400 | .enable = mei_nfc_enable, | |
401 | .disable = mei_nfc_disable, | |
402 | .send = mei_nfc_send, | |
403 | .recv = mei_nfc_recv, | |
404 | }; | |
405 | ||
59fcd7c6 SO |
406 | static void mei_nfc_init(struct work_struct *work) |
407 | { | |
408 | struct mei_device *dev; | |
91a6b95f | 409 | struct mei_cl_device *cldev; |
59fcd7c6 SO |
410 | struct mei_nfc_dev *ndev; |
411 | struct mei_cl *cl_info; | |
412 | ||
413 | ndev = container_of(work, struct mei_nfc_dev, init_work); | |
414 | ||
415 | cl_info = ndev->cl_info; | |
416 | dev = cl_info->dev; | |
417 | ||
418 | mutex_lock(&dev->device_lock); | |
419 | ||
420 | if (mei_cl_connect(cl_info, NULL) < 0) { | |
421 | mutex_unlock(&dev->device_lock); | |
422 | dev_err(&dev->pdev->dev, | |
423 | "Could not connect to the NFC INFO ME client"); | |
424 | ||
425 | goto err; | |
426 | } | |
427 | ||
428 | mutex_unlock(&dev->device_lock); | |
429 | ||
430 | if (mei_nfc_if_version(ndev) < 0) { | |
431 | dev_err(&dev->pdev->dev, "Could not get the NFC interfave version"); | |
432 | ||
433 | goto err; | |
434 | } | |
435 | ||
436 | dev_info(&dev->pdev->dev, | |
437 | "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", | |
438 | ndev->fw_ivn, ndev->vendor_id, ndev->radio_type); | |
439 | ||
440 | mutex_lock(&dev->device_lock); | |
441 | ||
442 | if (mei_cl_disconnect(cl_info) < 0) { | |
443 | mutex_unlock(&dev->device_lock); | |
444 | dev_err(&dev->pdev->dev, | |
445 | "Could not disconnect the NFC INFO ME client"); | |
446 | ||
447 | goto err; | |
448 | } | |
449 | ||
450 | mutex_unlock(&dev->device_lock); | |
451 | ||
91a6b95f SO |
452 | if (mei_nfc_build_bus_name(ndev) < 0) { |
453 | dev_err(&dev->pdev->dev, | |
454 | "Could not build the bus ID name\n"); | |
455 | return; | |
456 | } | |
457 | ||
36eda94f | 458 | cldev = mei_cl_add_device(dev, mei_nfc_guid, ndev->bus_name, &nfc_ops); |
91a6b95f SO |
459 | if (!cldev) { |
460 | dev_err(&dev->pdev->dev, | |
461 | "Could not add the NFC device to the MEI bus\n"); | |
462 | ||
463 | goto err; | |
464 | } | |
465 | ||
466 | cldev->priv_data = ndev; | |
467 | ||
468 | ||
59fcd7c6 SO |
469 | return; |
470 | ||
471 | err: | |
472 | mei_nfc_free(ndev); | |
473 | ||
474 | return; | |
475 | } | |
476 | ||
477 | ||
478 | int mei_nfc_host_init(struct mei_device *dev) | |
479 | { | |
480 | struct mei_nfc_dev *ndev = &nfc_dev; | |
481 | struct mei_cl *cl_info, *cl = NULL; | |
482 | int i, ret; | |
483 | ||
484 | /* already initialzed */ | |
485 | if (ndev->cl_info) | |
486 | return 0; | |
487 | ||
4bff7208 TW |
488 | ndev->cl_info = mei_cl_allocate(dev); |
489 | ndev->cl = mei_cl_allocate(dev); | |
490 | ||
491 | cl = ndev->cl; | |
492 | cl_info = ndev->cl_info; | |
59fcd7c6 SO |
493 | |
494 | if (!cl || !cl_info) { | |
495 | ret = -ENOMEM; | |
496 | goto err; | |
497 | } | |
498 | ||
499 | /* check for valid client id */ | |
500 | i = mei_me_cl_by_uuid(dev, &mei_nfc_info_guid); | |
501 | if (i < 0) { | |
502 | dev_info(&dev->pdev->dev, "nfc: failed to find the client\n"); | |
503 | ret = -ENOENT; | |
504 | goto err; | |
505 | } | |
506 | ||
507 | cl_info->me_client_id = dev->me_clients[i].client_id; | |
508 | ||
509 | ret = mei_cl_link(cl_info, MEI_HOST_CLIENT_ID_ANY); | |
510 | if (ret) | |
511 | goto err; | |
512 | ||
513 | cl_info->device_uuid = mei_nfc_info_guid; | |
514 | ||
515 | list_add_tail(&cl_info->device_link, &dev->device_list); | |
516 | ||
517 | /* check for valid client id */ | |
518 | i = mei_me_cl_by_uuid(dev, &mei_nfc_guid); | |
519 | if (i < 0) { | |
520 | dev_info(&dev->pdev->dev, "nfc: failed to find the client\n"); | |
521 | ret = -ENOENT; | |
522 | goto err; | |
523 | } | |
524 | ||
525 | cl->me_client_id = dev->me_clients[i].client_id; | |
526 | ||
527 | ret = mei_cl_link(cl, MEI_HOST_CLIENT_ID_ANY); | |
528 | if (ret) | |
529 | goto err; | |
530 | ||
531 | cl->device_uuid = mei_nfc_guid; | |
532 | ||
4bff7208 | 533 | |
59fcd7c6 SO |
534 | list_add_tail(&cl->device_link, &dev->device_list); |
535 | ||
36eda94f | 536 | ndev->req_id = 1; |
59fcd7c6 SO |
537 | |
538 | INIT_WORK(&ndev->init_work, mei_nfc_init); | |
36eda94f | 539 | init_waitqueue_head(&ndev->send_wq); |
59fcd7c6 SO |
540 | schedule_work(&ndev->init_work); |
541 | ||
542 | return 0; | |
543 | ||
544 | err: | |
545 | mei_nfc_free(ndev); | |
546 | ||
547 | return ret; | |
548 | } | |
549 | ||
550 | void mei_nfc_host_exit(void) | |
551 | { | |
552 | struct mei_nfc_dev *ndev = &nfc_dev; | |
553 | ||
91a6b95f SO |
554 | if (ndev->cl && ndev->cl->device) |
555 | mei_cl_remove_device(ndev->cl->device); | |
556 | ||
59fcd7c6 SO |
557 | mei_nfc_free(ndev); |
558 | } |