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