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