diff options
Diffstat (limited to 'drivers/staging/mei/init.c')
-rw-r--r-- | drivers/staging/mei/init.c | 765 |
1 files changed, 765 insertions, 0 deletions
diff --git a/drivers/staging/mei/init.c b/drivers/staging/mei/init.c new file mode 100644 index 00000000000..0fa8216fd0e --- /dev/null +++ b/drivers/staging/mei/init.c | |||
@@ -0,0 +1,765 @@ | |||
1 | /* | ||
2 | * | ||
3 | * Intel Management Engine Interface (Intel MEI) Linux driver | ||
4 | * Copyright (c) 2003-2011, 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/pci.h> | ||
18 | #include <linux/sched.h> | ||
19 | #include <linux/wait.h> | ||
20 | #include <linux/delay.h> | ||
21 | |||
22 | #include "mei_dev.h" | ||
23 | #include "hw.h" | ||
24 | #include "interface.h" | ||
25 | #include "mei.h" | ||
26 | |||
27 | const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, | ||
28 | 0xa8, 0x46, 0xe0, 0xff, 0x65, | ||
29 | 0x81, 0x4c); | ||
30 | |||
31 | /** | ||
32 | * mei_io_list_init - Sets up a queue list. | ||
33 | * | ||
34 | * @list: An instance io list structure | ||
35 | * @dev: the device structure | ||
36 | */ | ||
37 | void mei_io_list_init(struct mei_io_list *list) | ||
38 | { | ||
39 | /* initialize our queue list */ | ||
40 | INIT_LIST_HEAD(&list->mei_cb.cb_list); | ||
41 | list->status = 0; | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * mei_io_list_flush - removes list entry belonging to cl. | ||
46 | * | ||
47 | * @list: An instance of our list structure | ||
48 | * @cl: private data of the file object | ||
49 | */ | ||
50 | void mei_io_list_flush(struct mei_io_list *list, struct mei_cl *cl) | ||
51 | { | ||
52 | struct mei_cl_cb *cb_pos = NULL; | ||
53 | struct mei_cl_cb *cb_next = NULL; | ||
54 | |||
55 | if (list->status != 0) | ||
56 | return; | ||
57 | |||
58 | if (list_empty(&list->mei_cb.cb_list)) | ||
59 | return; | ||
60 | |||
61 | list_for_each_entry_safe(cb_pos, cb_next, | ||
62 | &list->mei_cb.cb_list, cb_list) { | ||
63 | if (cb_pos) { | ||
64 | struct mei_cl *cl_tmp; | ||
65 | cl_tmp = (struct mei_cl *)cb_pos->file_private; | ||
66 | if (mei_cl_cmp_id(cl, cl_tmp)) | ||
67 | list_del(&cb_pos->cb_list); | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | /** | ||
72 | * mei_cl_flush_queues - flushes queue lists belonging to cl. | ||
73 | * | ||
74 | * @dev: the device structure | ||
75 | * @cl: private data of the file object | ||
76 | */ | ||
77 | int mei_cl_flush_queues(struct mei_cl *cl) | ||
78 | { | ||
79 | if (!cl || !cl->dev) | ||
80 | return -EINVAL; | ||
81 | |||
82 | dev_dbg(&cl->dev->pdev->dev, "remove list entry belonging to cl\n"); | ||
83 | mei_io_list_flush(&cl->dev->read_list, cl); | ||
84 | mei_io_list_flush(&cl->dev->write_list, cl); | ||
85 | mei_io_list_flush(&cl->dev->write_waiting_list, cl); | ||
86 | mei_io_list_flush(&cl->dev->ctrl_wr_list, cl); | ||
87 | mei_io_list_flush(&cl->dev->ctrl_rd_list, cl); | ||
88 | mei_io_list_flush(&cl->dev->amthi_cmd_list, cl); | ||
89 | mei_io_list_flush(&cl->dev->amthi_read_complete_list, cl); | ||
90 | return 0; | ||
91 | } | ||
92 | |||
93 | |||
94 | |||
95 | /** | ||
96 | * mei_reset_iamthif_params - initializes mei device iamthif | ||
97 | * | ||
98 | * @dev: the device structure | ||
99 | */ | ||
100 | static void mei_reset_iamthif_params(struct mei_device *dev) | ||
101 | { | ||
102 | /* reset iamthif parameters. */ | ||
103 | dev->iamthif_current_cb = NULL; | ||
104 | dev->iamthif_msg_buf_size = 0; | ||
105 | dev->iamthif_msg_buf_index = 0; | ||
106 | dev->iamthif_canceled = false; | ||
107 | dev->iamthif_ioctl = false; | ||
108 | dev->iamthif_state = MEI_IAMTHIF_IDLE; | ||
109 | dev->iamthif_timer = 0; | ||
110 | } | ||
111 | |||
112 | /** | ||
113 | * init_mei_device - allocates and initializes the mei device structure | ||
114 | * | ||
115 | * @pdev: The pci device structure | ||
116 | * | ||
117 | * returns The mei_device_device pointer on success, NULL on failure. | ||
118 | */ | ||
119 | struct mei_device *mei_device_init(struct pci_dev *pdev) | ||
120 | { | ||
121 | struct mei_device *dev; | ||
122 | |||
123 | dev = kzalloc(sizeof(struct mei_device), GFP_KERNEL); | ||
124 | if (!dev) | ||
125 | return NULL; | ||
126 | |||
127 | /* setup our list array */ | ||
128 | INIT_LIST_HEAD(&dev->file_list); | ||
129 | INIT_LIST_HEAD(&dev->wd_cl.link); | ||
130 | INIT_LIST_HEAD(&dev->iamthif_cl.link); | ||
131 | mutex_init(&dev->device_lock); | ||
132 | init_waitqueue_head(&dev->wait_recvd_msg); | ||
133 | init_waitqueue_head(&dev->wait_stop_wd); | ||
134 | dev->mei_state = MEI_INITIALIZING; | ||
135 | dev->iamthif_state = MEI_IAMTHIF_IDLE; | ||
136 | |||
137 | |||
138 | mei_io_list_init(&dev->read_list); | ||
139 | mei_io_list_init(&dev->write_list); | ||
140 | mei_io_list_init(&dev->write_waiting_list); | ||
141 | mei_io_list_init(&dev->ctrl_wr_list); | ||
142 | mei_io_list_init(&dev->ctrl_rd_list); | ||
143 | mei_io_list_init(&dev->amthi_cmd_list); | ||
144 | mei_io_list_init(&dev->amthi_read_complete_list); | ||
145 | dev->pdev = pdev; | ||
146 | return dev; | ||
147 | } | ||
148 | |||
149 | /** | ||
150 | * mei_hw_init - initializes host and fw to start work. | ||
151 | * | ||
152 | * @dev: the device structure | ||
153 | * | ||
154 | * returns 0 on success, <0 on failure. | ||
155 | */ | ||
156 | int mei_hw_init(struct mei_device *dev) | ||
157 | { | ||
158 | int err = 0; | ||
159 | int ret; | ||
160 | |||
161 | mutex_lock(&dev->device_lock); | ||
162 | |||
163 | dev->host_hw_state = mei_hcsr_read(dev); | ||
164 | dev->me_hw_state = mei_mecsr_read(dev); | ||
165 | dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, mestate = 0x%08x.\n", | ||
166 | dev->host_hw_state, dev->me_hw_state); | ||
167 | |||
168 | /* acknowledge interrupt and stop interupts */ | ||
169 | if ((dev->host_hw_state & H_IS) == H_IS) | ||
170 | mei_reg_write(dev, H_CSR, dev->host_hw_state); | ||
171 | |||
172 | dev->recvd_msg = false; | ||
173 | dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n"); | ||
174 | |||
175 | mei_reset(dev, 1); | ||
176 | |||
177 | dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", | ||
178 | dev->host_hw_state, dev->me_hw_state); | ||
179 | |||
180 | /* wait for ME to turn on ME_RDY */ | ||
181 | if (!dev->recvd_msg) { | ||
182 | mutex_unlock(&dev->device_lock); | ||
183 | err = wait_event_interruptible_timeout(dev->wait_recvd_msg, | ||
184 | dev->recvd_msg, MEI_INTEROP_TIMEOUT); | ||
185 | mutex_lock(&dev->device_lock); | ||
186 | } | ||
187 | |||
188 | if (err <= 0 && !dev->recvd_msg) { | ||
189 | dev->mei_state = MEI_DISABLED; | ||
190 | dev_dbg(&dev->pdev->dev, | ||
191 | "wait_event_interruptible_timeout failed" | ||
192 | "on wait for ME to turn on ME_RDY.\n"); | ||
193 | ret = -ENODEV; | ||
194 | goto out; | ||
195 | } | ||
196 | |||
197 | if (!(((dev->host_hw_state & H_RDY) == H_RDY) && | ||
198 | ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA))) { | ||
199 | dev->mei_state = MEI_DISABLED; | ||
200 | dev_dbg(&dev->pdev->dev, | ||
201 | "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", | ||
202 | dev->host_hw_state, dev->me_hw_state); | ||
203 | |||
204 | if (!(dev->host_hw_state & H_RDY)) | ||
205 | dev_dbg(&dev->pdev->dev, "host turn off H_RDY.\n"); | ||
206 | |||
207 | if (!(dev->me_hw_state & ME_RDY_HRA)) | ||
208 | dev_dbg(&dev->pdev->dev, "ME turn off ME_RDY.\n"); | ||
209 | |||
210 | printk(KERN_ERR "mei: link layer initialization failed.\n"); | ||
211 | ret = -ENODEV; | ||
212 | goto out; | ||
213 | } | ||
214 | |||
215 | if (dev->version.major_version != HBM_MAJOR_VERSION || | ||
216 | dev->version.minor_version != HBM_MINOR_VERSION) { | ||
217 | dev_dbg(&dev->pdev->dev, "MEI start failed.\n"); | ||
218 | ret = -ENODEV; | ||
219 | goto out; | ||
220 | } | ||
221 | |||
222 | dev->recvd_msg = false; | ||
223 | dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", | ||
224 | dev->host_hw_state, dev->me_hw_state); | ||
225 | dev_dbg(&dev->pdev->dev, "ME turn on ME_RDY and host turn on H_RDY.\n"); | ||
226 | dev_dbg(&dev->pdev->dev, "link layer has been established.\n"); | ||
227 | dev_dbg(&dev->pdev->dev, "MEI start success.\n"); | ||
228 | ret = 0; | ||
229 | |||
230 | out: | ||
231 | mutex_unlock(&dev->device_lock); | ||
232 | return ret; | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * mei_hw_reset - resets fw via mei csr register. | ||
237 | * | ||
238 | * @dev: the device structure | ||
239 | * @interrupts_enabled: if interrupt should be enabled after reset. | ||
240 | */ | ||
241 | static void mei_hw_reset(struct mei_device *dev, int interrupts_enabled) | ||
242 | { | ||
243 | dev->host_hw_state |= (H_RST | H_IG); | ||
244 | |||
245 | if (interrupts_enabled) | ||
246 | mei_enable_interrupts(dev); | ||
247 | else | ||
248 | mei_disable_interrupts(dev); | ||
249 | } | ||
250 | |||
251 | /** | ||
252 | * mei_reset - resets host and fw. | ||
253 | * | ||
254 | * @dev: the device structure | ||
255 | * @interrupts_enabled: if interrupt should be enabled after reset. | ||
256 | */ | ||
257 | void mei_reset(struct mei_device *dev, int interrupts_enabled) | ||
258 | { | ||
259 | struct mei_cl *cl_pos = NULL; | ||
260 | struct mei_cl *cl_next = NULL; | ||
261 | struct mei_cl_cb *cb_pos = NULL; | ||
262 | struct mei_cl_cb *cb_next = NULL; | ||
263 | bool unexpected; | ||
264 | |||
265 | if (dev->mei_state == MEI_RECOVERING_FROM_RESET) { | ||
266 | dev->need_reset = true; | ||
267 | return; | ||
268 | } | ||
269 | |||
270 | unexpected = (dev->mei_state != MEI_INITIALIZING && | ||
271 | dev->mei_state != MEI_DISABLED && | ||
272 | dev->mei_state != MEI_POWER_DOWN && | ||
273 | dev->mei_state != MEI_POWER_UP); | ||
274 | |||
275 | dev->host_hw_state = mei_hcsr_read(dev); | ||
276 | |||
277 | dev_dbg(&dev->pdev->dev, "before reset host_hw_state = 0x%08x.\n", | ||
278 | dev->host_hw_state); | ||
279 | |||
280 | mei_hw_reset(dev, interrupts_enabled); | ||
281 | |||
282 | dev->host_hw_state &= ~H_RST; | ||
283 | dev->host_hw_state |= H_IG; | ||
284 | |||
285 | mei_hcsr_set(dev); | ||
286 | |||
287 | dev_dbg(&dev->pdev->dev, "currently saved host_hw_state = 0x%08x.\n", | ||
288 | dev->host_hw_state); | ||
289 | |||
290 | dev->need_reset = false; | ||
291 | |||
292 | if (dev->mei_state != MEI_INITIALIZING) { | ||
293 | if (dev->mei_state != MEI_DISABLED && | ||
294 | dev->mei_state != MEI_POWER_DOWN) | ||
295 | dev->mei_state = MEI_RESETING; | ||
296 | |||
297 | list_for_each_entry_safe(cl_pos, | ||
298 | cl_next, &dev->file_list, link) { | ||
299 | cl_pos->state = MEI_FILE_DISCONNECTED; | ||
300 | cl_pos->mei_flow_ctrl_creds = 0; | ||
301 | cl_pos->read_cb = NULL; | ||
302 | cl_pos->timer_count = 0; | ||
303 | } | ||
304 | /* remove entry if already in list */ | ||
305 | dev_dbg(&dev->pdev->dev, "list del iamthif and wd file list.\n"); | ||
306 | mei_remove_client_from_file_list(dev, | ||
307 | dev->wd_cl.host_client_id); | ||
308 | |||
309 | mei_remove_client_from_file_list(dev, | ||
310 | dev->iamthif_cl.host_client_id); | ||
311 | |||
312 | mei_reset_iamthif_params(dev); | ||
313 | dev->wd_due_counter = 0; | ||
314 | dev->extra_write_index = 0; | ||
315 | } | ||
316 | |||
317 | dev->me_clients_num = 0; | ||
318 | dev->rd_msg_hdr = 0; | ||
319 | dev->stop = false; | ||
320 | dev->wd_pending = false; | ||
321 | |||
322 | /* update the state of the registers after reset */ | ||
323 | dev->host_hw_state = mei_hcsr_read(dev); | ||
324 | dev->me_hw_state = mei_mecsr_read(dev); | ||
325 | |||
326 | dev_dbg(&dev->pdev->dev, "after reset host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", | ||
327 | dev->host_hw_state, dev->me_hw_state); | ||
328 | |||
329 | if (unexpected) | ||
330 | dev_warn(&dev->pdev->dev, "unexpected reset.\n"); | ||
331 | |||
332 | /* Wake up all readings so they can be interrupted */ | ||
333 | list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { | ||
334 | if (waitqueue_active(&cl_pos->rx_wait)) { | ||
335 | dev_dbg(&dev->pdev->dev, "Waking up client!\n"); | ||
336 | wake_up_interruptible(&cl_pos->rx_wait); | ||
337 | } | ||
338 | } | ||
339 | /* remove all waiting requests */ | ||
340 | if (dev->write_list.status == 0 && | ||
341 | !list_empty(&dev->write_list.mei_cb.cb_list)) { | ||
342 | list_for_each_entry_safe(cb_pos, cb_next, | ||
343 | &dev->write_list.mei_cb.cb_list, cb_list) { | ||
344 | if (cb_pos) { | ||
345 | list_del(&cb_pos->cb_list); | ||
346 | mei_free_cb_private(cb_pos); | ||
347 | cb_pos = NULL; | ||
348 | } | ||
349 | } | ||
350 | } | ||
351 | } | ||
352 | |||
353 | |||
354 | |||
355 | /** | ||
356 | * host_start_message - mei host sends start message. | ||
357 | * | ||
358 | * @dev: the device structure | ||
359 | * | ||
360 | * returns none. | ||
361 | */ | ||
362 | void mei_host_start_message(struct mei_device *dev) | ||
363 | { | ||
364 | struct mei_msg_hdr *mei_hdr; | ||
365 | struct hbm_host_version_request *host_start_req; | ||
366 | |||
367 | /* host start message */ | ||
368 | mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; | ||
369 | mei_hdr->host_addr = 0; | ||
370 | mei_hdr->me_addr = 0; | ||
371 | mei_hdr->length = sizeof(struct hbm_host_version_request); | ||
372 | mei_hdr->msg_complete = 1; | ||
373 | mei_hdr->reserved = 0; | ||
374 | |||
375 | host_start_req = | ||
376 | (struct hbm_host_version_request *) &dev->wr_msg_buf[1]; | ||
377 | memset(host_start_req, 0, sizeof(struct hbm_host_version_request)); | ||
378 | host_start_req->cmd.cmd = HOST_START_REQ_CMD; | ||
379 | host_start_req->host_version.major_version = HBM_MAJOR_VERSION; | ||
380 | host_start_req->host_version.minor_version = HBM_MINOR_VERSION; | ||
381 | dev->recvd_msg = false; | ||
382 | if (!mei_write_message(dev, mei_hdr, | ||
383 | (unsigned char *) (host_start_req), | ||
384 | mei_hdr->length)) { | ||
385 | dev_dbg(&dev->pdev->dev, "write send version message to FW fail.\n"); | ||
386 | dev->mei_state = MEI_RESETING; | ||
387 | mei_reset(dev, 1); | ||
388 | } | ||
389 | dev->init_clients_state = MEI_START_MESSAGE; | ||
390 | dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; | ||
391 | return ; | ||
392 | } | ||
393 | |||
394 | /** | ||
395 | * host_enum_clients_message - host sends enumeration client request message. | ||
396 | * | ||
397 | * @dev: the device structure | ||
398 | * | ||
399 | * returns none. | ||
400 | */ | ||
401 | void mei_host_enum_clients_message(struct mei_device *dev) | ||
402 | { | ||
403 | struct mei_msg_hdr *mei_hdr; | ||
404 | struct hbm_host_enum_request *host_enum_req; | ||
405 | mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; | ||
406 | /* enumerate clients */ | ||
407 | mei_hdr->host_addr = 0; | ||
408 | mei_hdr->me_addr = 0; | ||
409 | mei_hdr->length = sizeof(struct hbm_host_enum_request); | ||
410 | mei_hdr->msg_complete = 1; | ||
411 | mei_hdr->reserved = 0; | ||
412 | |||
413 | host_enum_req = (struct hbm_host_enum_request *) &dev->wr_msg_buf[1]; | ||
414 | memset(host_enum_req, 0, sizeof(struct hbm_host_enum_request)); | ||
415 | host_enum_req->cmd.cmd = HOST_ENUM_REQ_CMD; | ||
416 | if (!mei_write_message(dev, mei_hdr, | ||
417 | (unsigned char *) (host_enum_req), | ||
418 | mei_hdr->length)) { | ||
419 | dev->mei_state = MEI_RESETING; | ||
420 | dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); | ||
421 | mei_reset(dev, 1); | ||
422 | } | ||
423 | dev->init_clients_state = MEI_ENUM_CLIENTS_MESSAGE; | ||
424 | dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; | ||
425 | return ; | ||
426 | } | ||
427 | |||
428 | |||
429 | /** | ||
430 | * allocate_me_clients_storage - allocates storage for me clients | ||
431 | * | ||
432 | * @dev: the device structure | ||
433 | * | ||
434 | * returns none. | ||
435 | */ | ||
436 | void mei_allocate_me_clients_storage(struct mei_device *dev) | ||
437 | { | ||
438 | struct mei_me_client *clients; | ||
439 | int b; | ||
440 | |||
441 | /* count how many ME clients we have */ | ||
442 | for_each_set_bit(b, dev->me_clients_map, MEI_CLIENTS_MAX) | ||
443 | dev->me_clients_num++; | ||
444 | |||
445 | if (dev->me_clients_num <= 0) | ||
446 | return ; | ||
447 | |||
448 | |||
449 | if (dev->me_clients != NULL) { | ||
450 | kfree(dev->me_clients); | ||
451 | dev->me_clients = NULL; | ||
452 | } | ||
453 | dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%zd.\n", | ||
454 | dev->me_clients_num * sizeof(struct mei_me_client)); | ||
455 | /* allocate storage for ME clients representation */ | ||
456 | clients = kcalloc(dev->me_clients_num, | ||
457 | sizeof(struct mei_me_client), GFP_KERNEL); | ||
458 | if (!clients) { | ||
459 | dev_dbg(&dev->pdev->dev, "memory allocation for ME clients failed.\n"); | ||
460 | dev->mei_state = MEI_RESETING; | ||
461 | mei_reset(dev, 1); | ||
462 | return ; | ||
463 | } | ||
464 | dev->me_clients = clients; | ||
465 | return ; | ||
466 | } | ||
467 | /** | ||
468 | * host_client_properties - reads properties for client | ||
469 | * | ||
470 | * @dev: the device structure | ||
471 | * | ||
472 | * returns none. | ||
473 | */ | ||
474 | void mei_host_client_properties(struct mei_device *dev) | ||
475 | { | ||
476 | struct mei_msg_hdr *mei_header; | ||
477 | struct hbm_props_request *host_cli_req; | ||
478 | int b; | ||
479 | u8 client_num = dev->me_client_presentation_num; | ||
480 | |||
481 | b = dev->me_client_index; | ||
482 | b = find_next_bit(dev->me_clients_map, MEI_CLIENTS_MAX, b); | ||
483 | if (b < MEI_CLIENTS_MAX) { | ||
484 | dev->me_clients[client_num].client_id = b; | ||
485 | dev->me_clients[client_num].mei_flow_ctrl_creds = 0; | ||
486 | mei_header = (struct mei_msg_hdr *)&dev->wr_msg_buf[0]; | ||
487 | mei_header->host_addr = 0; | ||
488 | mei_header->me_addr = 0; | ||
489 | mei_header->length = sizeof(struct hbm_props_request); | ||
490 | mei_header->msg_complete = 1; | ||
491 | mei_header->reserved = 0; | ||
492 | |||
493 | host_cli_req = (struct hbm_props_request *)&dev->wr_msg_buf[1]; | ||
494 | |||
495 | memset(host_cli_req, 0, sizeof(struct hbm_props_request)); | ||
496 | |||
497 | host_cli_req->cmd.cmd = HOST_CLIENT_PROPERTIES_REQ_CMD; | ||
498 | host_cli_req->address = b; | ||
499 | |||
500 | if (!mei_write_message(dev, mei_header, | ||
501 | (unsigned char *)host_cli_req, | ||
502 | mei_header->length)) { | ||
503 | dev->mei_state = MEI_RESETING; | ||
504 | dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); | ||
505 | mei_reset(dev, 1); | ||
506 | return; | ||
507 | } | ||
508 | |||
509 | dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; | ||
510 | dev->me_client_index = b; | ||
511 | return; | ||
512 | } | ||
513 | |||
514 | |||
515 | /* | ||
516 | * Clear Map for indicating now ME clients | ||
517 | * with associated host client | ||
518 | */ | ||
519 | bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); | ||
520 | dev->open_handle_count = 0; | ||
521 | bitmap_set(dev->host_clients_map, 0, 3); | ||
522 | dev->mei_state = MEI_ENABLED; | ||
523 | |||
524 | mei_wd_host_init(dev); | ||
525 | return; | ||
526 | } | ||
527 | |||
528 | /** | ||
529 | * mei_init_file_private - initializes private file structure. | ||
530 | * | ||
531 | * @priv: private file structure to be initialized | ||
532 | * @file: the file structure | ||
533 | */ | ||
534 | void mei_cl_init(struct mei_cl *priv, struct mei_device *dev) | ||
535 | { | ||
536 | memset(priv, 0, sizeof(struct mei_cl)); | ||
537 | init_waitqueue_head(&priv->wait); | ||
538 | init_waitqueue_head(&priv->rx_wait); | ||
539 | init_waitqueue_head(&priv->tx_wait); | ||
540 | INIT_LIST_HEAD(&priv->link); | ||
541 | priv->reading_state = MEI_IDLE; | ||
542 | priv->writing_state = MEI_IDLE; | ||
543 | priv->dev = dev; | ||
544 | } | ||
545 | |||
546 | int mei_find_me_client_index(const struct mei_device *dev, uuid_le cuuid) | ||
547 | { | ||
548 | int i, res = -1; | ||
549 | |||
550 | for (i = 0; i < dev->me_clients_num; ++i) | ||
551 | if (uuid_le_cmp(cuuid, | ||
552 | dev->me_clients[i].props.protocol_name) == 0) { | ||
553 | res = i; | ||
554 | break; | ||
555 | } | ||
556 | |||
557 | return res; | ||
558 | } | ||
559 | |||
560 | |||
561 | /** | ||
562 | * mei_find_me_client_update_filext - searches for ME client guid | ||
563 | * sets client_id in mei_file_private if found | ||
564 | * @dev: the device structure | ||
565 | * @priv: private file structure to set client_id in | ||
566 | * @cguid: searched guid of ME client | ||
567 | * @client_id: id of host client to be set in file private structure | ||
568 | * | ||
569 | * returns ME client index | ||
570 | */ | ||
571 | u8 mei_find_me_client_update_filext(struct mei_device *dev, struct mei_cl *priv, | ||
572 | const uuid_le *cguid, u8 client_id) | ||
573 | { | ||
574 | int i; | ||
575 | |||
576 | if (!dev || !priv || !cguid) | ||
577 | return 0; | ||
578 | |||
579 | /* check for valid client id */ | ||
580 | i = mei_find_me_client_index(dev, *cguid); | ||
581 | if (i >= 0) { | ||
582 | priv->me_client_id = dev->me_clients[i].client_id; | ||
583 | priv->state = MEI_FILE_CONNECTING; | ||
584 | priv->host_client_id = client_id; | ||
585 | |||
586 | list_add_tail(&priv->link, &dev->file_list); | ||
587 | return (u8)i; | ||
588 | } | ||
589 | |||
590 | return 0; | ||
591 | } | ||
592 | |||
593 | /** | ||
594 | * host_init_iamthif - mei initialization iamthif client. | ||
595 | * | ||
596 | * @dev: the device structure | ||
597 | * | ||
598 | */ | ||
599 | void mei_host_init_iamthif(struct mei_device *dev) | ||
600 | { | ||
601 | u8 i; | ||
602 | unsigned char *msg_buf; | ||
603 | |||
604 | mei_cl_init(&dev->iamthif_cl, dev); | ||
605 | dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; | ||
606 | |||
607 | /* find ME amthi client */ | ||
608 | i = mei_find_me_client_update_filext(dev, &dev->iamthif_cl, | ||
609 | &mei_amthi_guid, MEI_IAMTHIF_HOST_CLIENT_ID); | ||
610 | if (dev->iamthif_cl.state != MEI_FILE_CONNECTING) { | ||
611 | dev_dbg(&dev->pdev->dev, "failed to find iamthif client.\n"); | ||
612 | return; | ||
613 | } | ||
614 | |||
615 | /* Do not render the system unusable when iamthif_mtu is not equal to | ||
616 | the value received from ME. | ||
617 | Assign iamthif_mtu to the value received from ME in order to solve the | ||
618 | hardware macro incompatibility. */ | ||
619 | |||
620 | dev_dbg(&dev->pdev->dev, "[DEFAULT] IAMTHIF = %d\n", dev->iamthif_mtu); | ||
621 | dev->iamthif_mtu = dev->me_clients[i].props.max_msg_length; | ||
622 | dev_dbg(&dev->pdev->dev, | ||
623 | "IAMTHIF = %d\n", | ||
624 | dev->me_clients[i].props.max_msg_length); | ||
625 | |||
626 | kfree(dev->iamthif_msg_buf); | ||
627 | dev->iamthif_msg_buf = NULL; | ||
628 | |||
629 | /* allocate storage for ME message buffer */ | ||
630 | msg_buf = kcalloc(dev->iamthif_mtu, | ||
631 | sizeof(unsigned char), GFP_KERNEL); | ||
632 | if (!msg_buf) { | ||
633 | dev_dbg(&dev->pdev->dev, "memory allocation for ME message buffer failed.\n"); | ||
634 | return; | ||
635 | } | ||
636 | |||
637 | dev->iamthif_msg_buf = msg_buf; | ||
638 | |||
639 | if (!mei_connect(dev, &dev->iamthif_cl)) { | ||
640 | dev_dbg(&dev->pdev->dev, "Failed to connect to AMTHI client\n"); | ||
641 | dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; | ||
642 | dev->iamthif_cl.host_client_id = 0; | ||
643 | } else { | ||
644 | dev->iamthif_cl.timer_count = CONNECT_TIMEOUT; | ||
645 | } | ||
646 | } | ||
647 | |||
648 | /** | ||
649 | * mei_alloc_file_private - allocates a private file structure and sets it up. | ||
650 | * @file: the file structure | ||
651 | * | ||
652 | * returns The allocated file or NULL on failure | ||
653 | */ | ||
654 | struct mei_cl *mei_cl_allocate(struct mei_device *dev) | ||
655 | { | ||
656 | struct mei_cl *cl; | ||
657 | |||
658 | cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL); | ||
659 | if (!cl) | ||
660 | return NULL; | ||
661 | |||
662 | mei_cl_init(cl, dev); | ||
663 | |||
664 | return cl; | ||
665 | } | ||
666 | |||
667 | |||
668 | |||
669 | /** | ||
670 | * mei_disconnect_host_client - sends disconnect message to fw from host client. | ||
671 | * | ||
672 | * @dev: the device structure | ||
673 | * @cl: private data of the file object | ||
674 | * | ||
675 | * Locking: called under "dev->device_lock" lock | ||
676 | * | ||
677 | * returns 0 on success, <0 on failure. | ||
678 | */ | ||
679 | int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) | ||
680 | { | ||
681 | int rets, err; | ||
682 | long timeout = 15; /* 15 seconds */ | ||
683 | struct mei_cl_cb *cb; | ||
684 | |||
685 | if (!dev || !cl) | ||
686 | return -ENODEV; | ||
687 | |||
688 | if (cl->state != MEI_FILE_DISCONNECTING) | ||
689 | return 0; | ||
690 | |||
691 | cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); | ||
692 | if (!cb) | ||
693 | return -ENOMEM; | ||
694 | |||
695 | INIT_LIST_HEAD(&cb->cb_list); | ||
696 | cb->file_private = cl; | ||
697 | cb->major_file_operations = MEI_CLOSE; | ||
698 | if (dev->mei_host_buffer_is_empty) { | ||
699 | dev->mei_host_buffer_is_empty = false; | ||
700 | if (mei_disconnect(dev, cl)) { | ||
701 | mdelay(10); /* Wait for hardware disconnection ready */ | ||
702 | list_add_tail(&cb->cb_list, | ||
703 | &dev->ctrl_rd_list.mei_cb.cb_list); | ||
704 | } else { | ||
705 | rets = -ENODEV; | ||
706 | dev_dbg(&dev->pdev->dev, "failed to call mei_disconnect.\n"); | ||
707 | goto free; | ||
708 | } | ||
709 | } else { | ||
710 | dev_dbg(&dev->pdev->dev, "add disconnect cb to control write list\n"); | ||
711 | list_add_tail(&cb->cb_list, | ||
712 | &dev->ctrl_wr_list.mei_cb.cb_list); | ||
713 | } | ||
714 | mutex_unlock(&dev->device_lock); | ||
715 | |||
716 | err = wait_event_timeout(dev->wait_recvd_msg, | ||
717 | (MEI_FILE_DISCONNECTED == cl->state), | ||
718 | timeout * HZ); | ||
719 | |||
720 | mutex_lock(&dev->device_lock); | ||
721 | if (MEI_FILE_DISCONNECTED == cl->state) { | ||
722 | rets = 0; | ||
723 | dev_dbg(&dev->pdev->dev, "successfully disconnected from FW client.\n"); | ||
724 | } else { | ||
725 | rets = -ENODEV; | ||
726 | if (MEI_FILE_DISCONNECTED != cl->state) | ||
727 | dev_dbg(&dev->pdev->dev, "wrong status client disconnect.\n"); | ||
728 | |||
729 | if (err) | ||
730 | dev_dbg(&dev->pdev->dev, | ||
731 | "wait failed disconnect err=%08x\n", | ||
732 | err); | ||
733 | |||
734 | dev_dbg(&dev->pdev->dev, "failed to disconnect from FW client.\n"); | ||
735 | } | ||
736 | |||
737 | mei_io_list_flush(&dev->ctrl_rd_list, cl); | ||
738 | mei_io_list_flush(&dev->ctrl_wr_list, cl); | ||
739 | free: | ||
740 | mei_free_cb_private(cb); | ||
741 | return rets; | ||
742 | } | ||
743 | |||
744 | /** | ||
745 | * mei_remove_client_from_file_list - | ||
746 | * removes file private data from device file list | ||
747 | * | ||
748 | * @dev: the device structure | ||
749 | * @host_client_id: host client id to be removed | ||
750 | */ | ||
751 | void mei_remove_client_from_file_list(struct mei_device *dev, | ||
752 | u8 host_client_id) | ||
753 | { | ||
754 | struct mei_cl *cl_pos = NULL; | ||
755 | struct mei_cl *cl_next = NULL; | ||
756 | list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { | ||
757 | if (host_client_id == cl_pos->host_client_id) { | ||
758 | dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n", | ||
759 | cl_pos->host_client_id, | ||
760 | cl_pos->me_client_id); | ||
761 | list_del_init(&cl_pos->link); | ||
762 | break; | ||
763 | } | ||
764 | } | ||
765 | } | ||