]>
Commit | Line | Data |
---|---|---|
ab841160 OW |
1 | /* |
2 | * | |
3 | * Intel Management Engine Interface (Intel MEI) Linux driver | |
733ba91c | 4 | * Copyright (c) 2003-2012, Intel Corporation. |
ab841160 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 | ||
ab841160 | 17 | #include <linux/pci.h> |
ab841160 | 18 | #include <linux/sched.h> |
9ca9050b TW |
19 | #include <linux/wait.h> |
20 | #include <linux/delay.h> | |
04bb139a | 21 | #include <linux/pm_runtime.h> |
ab841160 | 22 | |
4f3afe1d | 23 | #include <linux/mei.h> |
47a73801 TW |
24 | |
25 | #include "mei_dev.h" | |
0edb23fc | 26 | #include "hbm.h" |
90e0b5f1 TW |
27 | #include "client.h" |
28 | ||
29 | /** | |
30 | * mei_me_cl_by_uuid - locate index of me client | |
31 | * | |
32 | * @dev: mei device | |
a27a76d3 AU |
33 | * |
34 | * Locking: called under "dev->device_lock" lock | |
35 | * | |
90e0b5f1 TW |
36 | * returns me client index or -ENOENT if not found |
37 | */ | |
38 | int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *uuid) | |
39 | { | |
a27a76d3 | 40 | int i; |
90e0b5f1 TW |
41 | |
42 | for (i = 0; i < dev->me_clients_num; ++i) | |
43 | if (uuid_le_cmp(*uuid, | |
a27a76d3 AU |
44 | dev->me_clients[i].props.protocol_name) == 0) |
45 | return i; | |
90e0b5f1 | 46 | |
a27a76d3 | 47 | return -ENOENT; |
90e0b5f1 TW |
48 | } |
49 | ||
50 | ||
51 | /** | |
52 | * mei_me_cl_by_id return index to me_clients for client_id | |
53 | * | |
54 | * @dev: the device structure | |
55 | * @client_id: me client id | |
56 | * | |
57 | * Locking: called under "dev->device_lock" lock | |
58 | * | |
59 | * returns index on success, -ENOENT on failure. | |
60 | */ | |
61 | ||
62 | int mei_me_cl_by_id(struct mei_device *dev, u8 client_id) | |
63 | { | |
64 | int i; | |
a27a76d3 | 65 | |
90e0b5f1 TW |
66 | for (i = 0; i < dev->me_clients_num; i++) |
67 | if (dev->me_clients[i].client_id == client_id) | |
a27a76d3 | 68 | return i; |
90e0b5f1 | 69 | |
a27a76d3 | 70 | return -ENOENT; |
90e0b5f1 | 71 | } |
ab841160 | 72 | |
9ca9050b TW |
73 | |
74 | /** | |
cc99ecfd | 75 | * mei_cl_cmp_id - tells if the clients are the same |
9ca9050b | 76 | * |
cc99ecfd TW |
77 | * @cl1: host client 1 |
78 | * @cl2: host client 2 | |
79 | * | |
80 | * returns true - if the clients has same host and me ids | |
81 | * false - otherwise | |
82 | */ | |
83 | static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, | |
84 | const struct mei_cl *cl2) | |
85 | { | |
86 | return cl1 && cl2 && | |
87 | (cl1->host_client_id == cl2->host_client_id) && | |
88 | (cl1->me_client_id == cl2->me_client_id); | |
89 | } | |
90 | ||
91 | /** | |
92 | * mei_io_list_flush - removes cbs belonging to cl. | |
93 | * | |
94 | * @list: an instance of our list structure | |
95 | * @cl: host client, can be NULL for flushing the whole list | |
96 | * @free: whether to free the cbs | |
9ca9050b | 97 | */ |
cc99ecfd TW |
98 | static void __mei_io_list_flush(struct mei_cl_cb *list, |
99 | struct mei_cl *cl, bool free) | |
9ca9050b TW |
100 | { |
101 | struct mei_cl_cb *cb; | |
102 | struct mei_cl_cb *next; | |
103 | ||
cc99ecfd | 104 | /* enable removing everything if no cl is specified */ |
9ca9050b | 105 | list_for_each_entry_safe(cb, next, &list->list, list) { |
cc99ecfd | 106 | if (!cl || (cb->cl && mei_cl_cmp_id(cl, cb->cl))) { |
9ca9050b | 107 | list_del(&cb->list); |
cc99ecfd TW |
108 | if (free) |
109 | mei_io_cb_free(cb); | |
110 | } | |
9ca9050b TW |
111 | } |
112 | } | |
113 | ||
cc99ecfd TW |
114 | /** |
115 | * mei_io_list_flush - removes list entry belonging to cl. | |
116 | * | |
117 | * @list: An instance of our list structure | |
118 | * @cl: host client | |
119 | */ | |
120 | static inline void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl) | |
121 | { | |
122 | __mei_io_list_flush(list, cl, false); | |
123 | } | |
124 | ||
125 | ||
126 | /** | |
127 | * mei_io_list_free - removes cb belonging to cl and free them | |
128 | * | |
129 | * @list: An instance of our list structure | |
130 | * @cl: host client | |
131 | */ | |
132 | static inline void mei_io_list_free(struct mei_cl_cb *list, struct mei_cl *cl) | |
133 | { | |
134 | __mei_io_list_flush(list, cl, true); | |
135 | } | |
136 | ||
601a1efa TW |
137 | /** |
138 | * mei_io_cb_free - free mei_cb_private related memory | |
139 | * | |
140 | * @cb: mei callback struct | |
141 | */ | |
142 | void mei_io_cb_free(struct mei_cl_cb *cb) | |
143 | { | |
144 | if (cb == NULL) | |
145 | return; | |
146 | ||
147 | kfree(cb->request_buffer.data); | |
148 | kfree(cb->response_buffer.data); | |
149 | kfree(cb); | |
150 | } | |
9ca9050b | 151 | |
664df38b TW |
152 | /** |
153 | * mei_io_cb_init - allocate and initialize io callback | |
154 | * | |
155 | * @cl - mei client | |
393b148f | 156 | * @fp: pointer to file structure |
664df38b TW |
157 | * |
158 | * returns mei_cl_cb pointer or NULL; | |
159 | */ | |
160 | struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) | |
161 | { | |
162 | struct mei_cl_cb *cb; | |
163 | ||
164 | cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); | |
165 | if (!cb) | |
166 | return NULL; | |
167 | ||
168 | mei_io_list_init(cb); | |
169 | ||
170 | cb->file_object = fp; | |
db3ed431 | 171 | cb->cl = cl; |
664df38b TW |
172 | cb->buf_idx = 0; |
173 | return cb; | |
174 | } | |
175 | ||
664df38b TW |
176 | /** |
177 | * mei_io_cb_alloc_req_buf - allocate request buffer | |
178 | * | |
393b148f MI |
179 | * @cb: io callback structure |
180 | * @length: size of the buffer | |
664df38b TW |
181 | * |
182 | * returns 0 on success | |
183 | * -EINVAL if cb is NULL | |
184 | * -ENOMEM if allocation failed | |
185 | */ | |
186 | int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length) | |
187 | { | |
188 | if (!cb) | |
189 | return -EINVAL; | |
190 | ||
191 | if (length == 0) | |
192 | return 0; | |
193 | ||
194 | cb->request_buffer.data = kmalloc(length, GFP_KERNEL); | |
195 | if (!cb->request_buffer.data) | |
196 | return -ENOMEM; | |
197 | cb->request_buffer.size = length; | |
198 | return 0; | |
199 | } | |
200 | /** | |
83ce0741 | 201 | * mei_io_cb_alloc_resp_buf - allocate response buffer |
664df38b | 202 | * |
393b148f MI |
203 | * @cb: io callback structure |
204 | * @length: size of the buffer | |
664df38b TW |
205 | * |
206 | * returns 0 on success | |
207 | * -EINVAL if cb is NULL | |
208 | * -ENOMEM if allocation failed | |
209 | */ | |
210 | int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length) | |
211 | { | |
212 | if (!cb) | |
213 | return -EINVAL; | |
214 | ||
215 | if (length == 0) | |
216 | return 0; | |
217 | ||
218 | cb->response_buffer.data = kmalloc(length, GFP_KERNEL); | |
219 | if (!cb->response_buffer.data) | |
220 | return -ENOMEM; | |
221 | cb->response_buffer.size = length; | |
222 | return 0; | |
223 | } | |
224 | ||
601a1efa | 225 | |
9ca9050b TW |
226 | |
227 | /** | |
228 | * mei_cl_flush_queues - flushes queue lists belonging to cl. | |
229 | * | |
9ca9050b TW |
230 | * @cl: host client |
231 | */ | |
232 | int mei_cl_flush_queues(struct mei_cl *cl) | |
233 | { | |
c0abffbd AU |
234 | struct mei_device *dev; |
235 | ||
90e0b5f1 | 236 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
237 | return -EINVAL; |
238 | ||
c0abffbd AU |
239 | dev = cl->dev; |
240 | ||
241 | cl_dbg(dev, cl, "remove list entry belonging to cl\n"); | |
9ca9050b | 242 | mei_io_list_flush(&cl->dev->read_list, cl); |
cc99ecfd TW |
243 | mei_io_list_free(&cl->dev->write_list, cl); |
244 | mei_io_list_free(&cl->dev->write_waiting_list, cl); | |
9ca9050b TW |
245 | mei_io_list_flush(&cl->dev->ctrl_wr_list, cl); |
246 | mei_io_list_flush(&cl->dev->ctrl_rd_list, cl); | |
247 | mei_io_list_flush(&cl->dev->amthif_cmd_list, cl); | |
248 | mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl); | |
249 | return 0; | |
250 | } | |
251 | ||
ab841160 | 252 | |
9ca9050b | 253 | /** |
83ce0741 | 254 | * mei_cl_init - initializes cl. |
9ca9050b TW |
255 | * |
256 | * @cl: host client to be initialized | |
257 | * @dev: mei device | |
258 | */ | |
259 | void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) | |
260 | { | |
261 | memset(cl, 0, sizeof(struct mei_cl)); | |
262 | init_waitqueue_head(&cl->wait); | |
263 | init_waitqueue_head(&cl->rx_wait); | |
264 | init_waitqueue_head(&cl->tx_wait); | |
265 | INIT_LIST_HEAD(&cl->link); | |
a7b71bc0 | 266 | INIT_LIST_HEAD(&cl->device_link); |
9ca9050b TW |
267 | cl->reading_state = MEI_IDLE; |
268 | cl->writing_state = MEI_IDLE; | |
269 | cl->dev = dev; | |
270 | } | |
271 | ||
272 | /** | |
273 | * mei_cl_allocate - allocates cl structure and sets it up. | |
274 | * | |
275 | * @dev: mei device | |
276 | * returns The allocated file or NULL on failure | |
277 | */ | |
278 | struct mei_cl *mei_cl_allocate(struct mei_device *dev) | |
279 | { | |
280 | struct mei_cl *cl; | |
281 | ||
282 | cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL); | |
283 | if (!cl) | |
284 | return NULL; | |
285 | ||
286 | mei_cl_init(cl, dev); | |
287 | ||
288 | return cl; | |
289 | } | |
290 | ||
90e0b5f1 TW |
291 | /** |
292 | * mei_cl_find_read_cb - find this cl's callback in the read list | |
293 | * | |
393b148f MI |
294 | * @cl: host client |
295 | * | |
90e0b5f1 TW |
296 | * returns cb on success, NULL on error |
297 | */ | |
298 | struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) | |
299 | { | |
300 | struct mei_device *dev = cl->dev; | |
31f88f57 | 301 | struct mei_cl_cb *cb; |
90e0b5f1 | 302 | |
31f88f57 | 303 | list_for_each_entry(cb, &dev->read_list.list, list) |
90e0b5f1 TW |
304 | if (mei_cl_cmp_id(cl, cb->cl)) |
305 | return cb; | |
306 | return NULL; | |
307 | } | |
308 | ||
83ce0741 | 309 | /** mei_cl_link: allocate host id in the host map |
9ca9050b | 310 | * |
781d0d89 | 311 | * @cl - host client |
83ce0741 | 312 | * @id - fixed host id or -1 for generic one |
393b148f | 313 | * |
781d0d89 | 314 | * returns 0 on success |
9ca9050b TW |
315 | * -EINVAL on incorrect values |
316 | * -ENONET if client not found | |
317 | */ | |
781d0d89 | 318 | int mei_cl_link(struct mei_cl *cl, int id) |
9ca9050b | 319 | { |
90e0b5f1 | 320 | struct mei_device *dev; |
22f96a0e | 321 | long open_handle_count; |
9ca9050b | 322 | |
781d0d89 | 323 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
324 | return -EINVAL; |
325 | ||
90e0b5f1 TW |
326 | dev = cl->dev; |
327 | ||
83ce0741 | 328 | /* If Id is not assigned get one*/ |
781d0d89 TW |
329 | if (id == MEI_HOST_CLIENT_ID_ANY) |
330 | id = find_first_zero_bit(dev->host_clients_map, | |
331 | MEI_CLIENTS_MAX); | |
9ca9050b | 332 | |
781d0d89 | 333 | if (id >= MEI_CLIENTS_MAX) { |
83ce0741 | 334 | dev_err(&dev->pdev->dev, "id exceeded %d", MEI_CLIENTS_MAX); |
e036cc57 TW |
335 | return -EMFILE; |
336 | } | |
337 | ||
22f96a0e TW |
338 | open_handle_count = dev->open_handle_count + dev->iamthif_open_count; |
339 | if (open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { | |
83ce0741 | 340 | dev_err(&dev->pdev->dev, "open_handle_count exceeded %d", |
e036cc57 TW |
341 | MEI_MAX_OPEN_HANDLE_COUNT); |
342 | return -EMFILE; | |
9ca9050b TW |
343 | } |
344 | ||
781d0d89 TW |
345 | dev->open_handle_count++; |
346 | ||
347 | cl->host_client_id = id; | |
348 | list_add_tail(&cl->link, &dev->file_list); | |
349 | ||
350 | set_bit(id, dev->host_clients_map); | |
351 | ||
352 | cl->state = MEI_FILE_INITIALIZING; | |
353 | ||
c0abffbd | 354 | cl_dbg(dev, cl, "link cl\n"); |
781d0d89 | 355 | return 0; |
9ca9050b | 356 | } |
781d0d89 | 357 | |
9ca9050b | 358 | /** |
90e0b5f1 | 359 | * mei_cl_unlink - remove me_cl from the list |
9ca9050b | 360 | * |
393b148f | 361 | * @cl: host client |
9ca9050b | 362 | */ |
90e0b5f1 | 363 | int mei_cl_unlink(struct mei_cl *cl) |
9ca9050b | 364 | { |
90e0b5f1 | 365 | struct mei_device *dev; |
90e0b5f1 | 366 | |
781d0d89 TW |
367 | /* don't shout on error exit path */ |
368 | if (!cl) | |
369 | return 0; | |
370 | ||
8e9a4a9a TW |
371 | /* wd and amthif might not be initialized */ |
372 | if (!cl->dev) | |
373 | return 0; | |
90e0b5f1 TW |
374 | |
375 | dev = cl->dev; | |
376 | ||
a14c44d8 TW |
377 | cl_dbg(dev, cl, "unlink client"); |
378 | ||
22f96a0e TW |
379 | if (dev->open_handle_count > 0) |
380 | dev->open_handle_count--; | |
381 | ||
382 | /* never clear the 0 bit */ | |
383 | if (cl->host_client_id) | |
384 | clear_bit(cl->host_client_id, dev->host_clients_map); | |
385 | ||
386 | list_del_init(&cl->link); | |
387 | ||
388 | cl->state = MEI_FILE_INITIALIZING; | |
389 | ||
90e0b5f1 | 390 | return 0; |
9ca9050b TW |
391 | } |
392 | ||
393 | ||
394 | void mei_host_client_init(struct work_struct *work) | |
395 | { | |
396 | struct mei_device *dev = container_of(work, | |
397 | struct mei_device, init_work); | |
398 | struct mei_client_properties *client_props; | |
399 | int i; | |
400 | ||
401 | mutex_lock(&dev->device_lock); | |
402 | ||
9ca9050b TW |
403 | for (i = 0; i < dev->me_clients_num; i++) { |
404 | client_props = &dev->me_clients[i].props; | |
405 | ||
1a1aca42 | 406 | if (!uuid_le_cmp(client_props->protocol_name, mei_amthif_guid)) |
9ca9050b TW |
407 | mei_amthif_host_init(dev); |
408 | else if (!uuid_le_cmp(client_props->protocol_name, mei_wd_guid)) | |
409 | mei_wd_host_init(dev); | |
59fcd7c6 SO |
410 | else if (!uuid_le_cmp(client_props->protocol_name, mei_nfc_guid)) |
411 | mei_nfc_host_init(dev); | |
412 | ||
9ca9050b TW |
413 | } |
414 | ||
415 | dev->dev_state = MEI_DEV_ENABLED; | |
6adb8efb | 416 | dev->reset_count = 0; |
9ca9050b TW |
417 | |
418 | mutex_unlock(&dev->device_lock); | |
04bb139a TW |
419 | |
420 | pm_runtime_mark_last_busy(&dev->pdev->dev); | |
421 | dev_dbg(&dev->pdev->dev, "rpm: autosuspend\n"); | |
422 | pm_runtime_autosuspend(&dev->pdev->dev); | |
9ca9050b TW |
423 | } |
424 | ||
6aae48ff TW |
425 | /** |
426 | * mei_hbuf_acquire: try to acquire host buffer | |
427 | * | |
428 | * @dev: the device structure | |
429 | * returns true if host buffer was acquired | |
430 | */ | |
431 | bool mei_hbuf_acquire(struct mei_device *dev) | |
432 | { | |
04bb139a TW |
433 | if (mei_pg_state(dev) == MEI_PG_ON || |
434 | dev->pg_event == MEI_PG_EVENT_WAIT) { | |
435 | dev_dbg(&dev->pdev->dev, "device is in pg\n"); | |
436 | return false; | |
437 | } | |
438 | ||
6aae48ff TW |
439 | if (!dev->hbuf_is_ready) { |
440 | dev_dbg(&dev->pdev->dev, "hbuf is not ready\n"); | |
441 | return false; | |
442 | } | |
443 | ||
444 | dev->hbuf_is_ready = false; | |
445 | ||
446 | return true; | |
447 | } | |
9ca9050b TW |
448 | |
449 | /** | |
83ce0741 | 450 | * mei_cl_disconnect - disconnect host client from the me one |
9ca9050b | 451 | * |
90e0b5f1 | 452 | * @cl: host client |
9ca9050b TW |
453 | * |
454 | * Locking: called under "dev->device_lock" lock | |
455 | * | |
456 | * returns 0 on success, <0 on failure. | |
457 | */ | |
90e0b5f1 | 458 | int mei_cl_disconnect(struct mei_cl *cl) |
9ca9050b | 459 | { |
90e0b5f1 | 460 | struct mei_device *dev; |
9ca9050b TW |
461 | struct mei_cl_cb *cb; |
462 | int rets, err; | |
463 | ||
90e0b5f1 | 464 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
465 | return -ENODEV; |
466 | ||
90e0b5f1 TW |
467 | dev = cl->dev; |
468 | ||
c0abffbd AU |
469 | cl_dbg(dev, cl, "disconnecting"); |
470 | ||
9ca9050b TW |
471 | if (cl->state != MEI_FILE_DISCONNECTING) |
472 | return 0; | |
473 | ||
04bb139a TW |
474 | rets = pm_runtime_get(&dev->pdev->dev); |
475 | if (rets < 0 && rets != -EINPROGRESS) { | |
476 | pm_runtime_put_noidle(&dev->pdev->dev); | |
477 | cl_err(dev, cl, "rpm: get failed %d\n", rets); | |
478 | return rets; | |
479 | } | |
480 | ||
9ca9050b | 481 | cb = mei_io_cb_init(cl, NULL); |
04bb139a TW |
482 | if (!cb) { |
483 | rets = -ENOMEM; | |
484 | goto free; | |
485 | } | |
9ca9050b TW |
486 | |
487 | cb->fop_type = MEI_FOP_CLOSE; | |
6aae48ff | 488 | if (mei_hbuf_acquire(dev)) { |
9ca9050b TW |
489 | if (mei_hbm_cl_disconnect_req(dev, cl)) { |
490 | rets = -ENODEV; | |
c0abffbd | 491 | cl_err(dev, cl, "failed to disconnect.\n"); |
9ca9050b TW |
492 | goto free; |
493 | } | |
494 | mdelay(10); /* Wait for hardware disconnection ready */ | |
495 | list_add_tail(&cb->list, &dev->ctrl_rd_list.list); | |
496 | } else { | |
c0abffbd | 497 | cl_dbg(dev, cl, "add disconnect cb to control write list\n"); |
9ca9050b TW |
498 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); |
499 | ||
500 | } | |
501 | mutex_unlock(&dev->device_lock); | |
502 | ||
503 | err = wait_event_timeout(dev->wait_recvd_msg, | |
504 | MEI_FILE_DISCONNECTED == cl->state, | |
505 | mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); | |
506 | ||
507 | mutex_lock(&dev->device_lock); | |
508 | if (MEI_FILE_DISCONNECTED == cl->state) { | |
509 | rets = 0; | |
c0abffbd | 510 | cl_dbg(dev, cl, "successfully disconnected from FW client.\n"); |
9ca9050b TW |
511 | } else { |
512 | rets = -ENODEV; | |
513 | if (MEI_FILE_DISCONNECTED != cl->state) | |
c0abffbd | 514 | cl_err(dev, cl, "wrong status client disconnect.\n"); |
9ca9050b TW |
515 | |
516 | if (err) | |
04bb139a | 517 | cl_dbg(dev, cl, "wait failed disconnect err=%d\n", err); |
9ca9050b | 518 | |
c0abffbd | 519 | cl_err(dev, cl, "failed to disconnect from FW client.\n"); |
9ca9050b TW |
520 | } |
521 | ||
522 | mei_io_list_flush(&dev->ctrl_rd_list, cl); | |
523 | mei_io_list_flush(&dev->ctrl_wr_list, cl); | |
524 | free: | |
04bb139a TW |
525 | cl_dbg(dev, cl, "rpm: autosuspend\n"); |
526 | pm_runtime_mark_last_busy(&dev->pdev->dev); | |
527 | pm_runtime_put_autosuspend(&dev->pdev->dev); | |
528 | ||
9ca9050b TW |
529 | mei_io_cb_free(cb); |
530 | return rets; | |
531 | } | |
532 | ||
533 | ||
534 | /** | |
90e0b5f1 TW |
535 | * mei_cl_is_other_connecting - checks if other |
536 | * client with the same me client id is connecting | |
9ca9050b | 537 | * |
9ca9050b TW |
538 | * @cl: private data of the file object |
539 | * | |
83ce0741 | 540 | * returns true if other client is connected, false - otherwise. |
9ca9050b | 541 | */ |
90e0b5f1 | 542 | bool mei_cl_is_other_connecting(struct mei_cl *cl) |
9ca9050b | 543 | { |
90e0b5f1 | 544 | struct mei_device *dev; |
31f88f57 | 545 | struct mei_cl *ocl; /* the other client */ |
9ca9050b | 546 | |
90e0b5f1 TW |
547 | if (WARN_ON(!cl || !cl->dev)) |
548 | return false; | |
549 | ||
550 | dev = cl->dev; | |
551 | ||
31f88f57 TW |
552 | list_for_each_entry(ocl, &dev->file_list, link) { |
553 | if (ocl->state == MEI_FILE_CONNECTING && | |
554 | ocl != cl && | |
555 | cl->me_client_id == ocl->me_client_id) | |
90e0b5f1 | 556 | return true; |
9ca9050b TW |
557 | |
558 | } | |
90e0b5f1 TW |
559 | |
560 | return false; | |
9ca9050b TW |
561 | } |
562 | ||
9f81abda | 563 | /** |
83ce0741 | 564 | * mei_cl_connect - connect host client to the me one |
9f81abda TW |
565 | * |
566 | * @cl: host client | |
567 | * | |
568 | * Locking: called under "dev->device_lock" lock | |
569 | * | |
570 | * returns 0 on success, <0 on failure. | |
571 | */ | |
572 | int mei_cl_connect(struct mei_cl *cl, struct file *file) | |
573 | { | |
574 | struct mei_device *dev; | |
575 | struct mei_cl_cb *cb; | |
9f81abda TW |
576 | int rets; |
577 | ||
578 | if (WARN_ON(!cl || !cl->dev)) | |
579 | return -ENODEV; | |
580 | ||
581 | dev = cl->dev; | |
582 | ||
04bb139a TW |
583 | rets = pm_runtime_get(&dev->pdev->dev); |
584 | if (rets < 0 && rets != -EINPROGRESS) { | |
585 | pm_runtime_put_noidle(&dev->pdev->dev); | |
586 | cl_err(dev, cl, "rpm: get failed %d\n", rets); | |
587 | return rets; | |
588 | } | |
589 | ||
9f81abda TW |
590 | cb = mei_io_cb_init(cl, file); |
591 | if (!cb) { | |
592 | rets = -ENOMEM; | |
593 | goto out; | |
594 | } | |
595 | ||
02a7eecc | 596 | cb->fop_type = MEI_FOP_CONNECT; |
9f81abda | 597 | |
6aae48ff TW |
598 | /* run hbuf acquire last so we don't have to undo */ |
599 | if (!mei_cl_is_other_connecting(cl) && mei_hbuf_acquire(dev)) { | |
e4d8270e | 600 | cl->state = MEI_FILE_CONNECTING; |
9f81abda TW |
601 | if (mei_hbm_cl_connect_req(dev, cl)) { |
602 | rets = -ENODEV; | |
603 | goto out; | |
604 | } | |
605 | cl->timer_count = MEI_CONNECT_TIMEOUT; | |
606 | list_add_tail(&cb->list, &dev->ctrl_rd_list.list); | |
607 | } else { | |
608 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); | |
609 | } | |
610 | ||
611 | mutex_unlock(&dev->device_lock); | |
285e2996 AU |
612 | wait_event_timeout(dev->wait_recvd_msg, |
613 | (cl->state == MEI_FILE_CONNECTED || | |
614 | cl->state == MEI_FILE_DISCONNECTED), | |
615 | mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); | |
9f81abda TW |
616 | mutex_lock(&dev->device_lock); |
617 | ||
618 | if (cl->state != MEI_FILE_CONNECTED) { | |
285e2996 AU |
619 | /* something went really wrong */ |
620 | if (!cl->status) | |
621 | cl->status = -EFAULT; | |
9f81abda TW |
622 | |
623 | mei_io_list_flush(&dev->ctrl_rd_list, cl); | |
624 | mei_io_list_flush(&dev->ctrl_wr_list, cl); | |
9f81abda TW |
625 | } |
626 | ||
627 | rets = cl->status; | |
628 | ||
629 | out: | |
04bb139a TW |
630 | cl_dbg(dev, cl, "rpm: autosuspend\n"); |
631 | pm_runtime_mark_last_busy(&dev->pdev->dev); | |
632 | pm_runtime_put_autosuspend(&dev->pdev->dev); | |
633 | ||
9f81abda TW |
634 | mei_io_cb_free(cb); |
635 | return rets; | |
636 | } | |
637 | ||
9ca9050b | 638 | /** |
90e0b5f1 | 639 | * mei_cl_flow_ctrl_creds - checks flow_control credits for cl. |
9ca9050b | 640 | * |
9ca9050b TW |
641 | * @cl: private data of the file object |
642 | * | |
643 | * returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise. | |
644 | * -ENOENT if mei_cl is not present | |
645 | * -EINVAL if single_recv_buf == 0 | |
646 | */ | |
90e0b5f1 | 647 | int mei_cl_flow_ctrl_creds(struct mei_cl *cl) |
9ca9050b | 648 | { |
90e0b5f1 | 649 | struct mei_device *dev; |
12d00665 AU |
650 | struct mei_me_client *me_cl; |
651 | int id; | |
9ca9050b | 652 | |
90e0b5f1 TW |
653 | if (WARN_ON(!cl || !cl->dev)) |
654 | return -EINVAL; | |
655 | ||
656 | dev = cl->dev; | |
657 | ||
9ca9050b TW |
658 | if (!dev->me_clients_num) |
659 | return 0; | |
660 | ||
661 | if (cl->mei_flow_ctrl_creds > 0) | |
662 | return 1; | |
663 | ||
12d00665 AU |
664 | id = mei_me_cl_by_id(dev, cl->me_client_id); |
665 | if (id < 0) { | |
666 | cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); | |
667 | return id; | |
9ca9050b | 668 | } |
12d00665 AU |
669 | |
670 | me_cl = &dev->me_clients[id]; | |
671 | if (me_cl->mei_flow_ctrl_creds) { | |
672 | if (WARN_ON(me_cl->props.single_recv_buf == 0)) | |
673 | return -EINVAL; | |
674 | return 1; | |
675 | } | |
676 | return 0; | |
9ca9050b TW |
677 | } |
678 | ||
679 | /** | |
90e0b5f1 | 680 | * mei_cl_flow_ctrl_reduce - reduces flow_control. |
9ca9050b | 681 | * |
9ca9050b | 682 | * @cl: private data of the file object |
393b148f | 683 | * |
9ca9050b TW |
684 | * @returns |
685 | * 0 on success | |
686 | * -ENOENT when me client is not found | |
687 | * -EINVAL when ctrl credits are <= 0 | |
688 | */ | |
90e0b5f1 | 689 | int mei_cl_flow_ctrl_reduce(struct mei_cl *cl) |
9ca9050b | 690 | { |
90e0b5f1 | 691 | struct mei_device *dev; |
12d00665 AU |
692 | struct mei_me_client *me_cl; |
693 | int id; | |
9ca9050b | 694 | |
90e0b5f1 TW |
695 | if (WARN_ON(!cl || !cl->dev)) |
696 | return -EINVAL; | |
697 | ||
698 | dev = cl->dev; | |
699 | ||
12d00665 AU |
700 | id = mei_me_cl_by_id(dev, cl->me_client_id); |
701 | if (id < 0) { | |
702 | cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); | |
703 | return id; | |
704 | } | |
9ca9050b | 705 | |
12d00665 AU |
706 | me_cl = &dev->me_clients[id]; |
707 | if (me_cl->props.single_recv_buf != 0) { | |
708 | if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) | |
709 | return -EINVAL; | |
710 | me_cl->mei_flow_ctrl_creds--; | |
711 | } else { | |
712 | if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) | |
713 | return -EINVAL; | |
714 | cl->mei_flow_ctrl_creds--; | |
9ca9050b | 715 | } |
12d00665 | 716 | return 0; |
9ca9050b TW |
717 | } |
718 | ||
ab841160 | 719 | /** |
393b148f | 720 | * mei_cl_read_start - the start read client message function. |
ab841160 | 721 | * |
90e0b5f1 | 722 | * @cl: host client |
ab841160 OW |
723 | * |
724 | * returns 0 on success, <0 on failure. | |
725 | */ | |
fcb136e1 | 726 | int mei_cl_read_start(struct mei_cl *cl, size_t length) |
ab841160 | 727 | { |
90e0b5f1 | 728 | struct mei_device *dev; |
ab841160 | 729 | struct mei_cl_cb *cb; |
664df38b | 730 | int rets; |
ab841160 OW |
731 | int i; |
732 | ||
90e0b5f1 TW |
733 | if (WARN_ON(!cl || !cl->dev)) |
734 | return -ENODEV; | |
735 | ||
736 | dev = cl->dev; | |
737 | ||
b950ac1d | 738 | if (!mei_cl_is_connected(cl)) |
ab841160 OW |
739 | return -ENODEV; |
740 | ||
d91aaed3 | 741 | if (cl->read_cb) { |
c0abffbd | 742 | cl_dbg(dev, cl, "read is pending.\n"); |
ab841160 OW |
743 | return -EBUSY; |
744 | } | |
664df38b TW |
745 | i = mei_me_cl_by_id(dev, cl->me_client_id); |
746 | if (i < 0) { | |
c0abffbd | 747 | cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); |
7ca96aa2 | 748 | return -ENOTTY; |
664df38b | 749 | } |
ab841160 | 750 | |
04bb139a TW |
751 | rets = pm_runtime_get(&dev->pdev->dev); |
752 | if (rets < 0 && rets != -EINPROGRESS) { | |
753 | pm_runtime_put_noidle(&dev->pdev->dev); | |
754 | cl_err(dev, cl, "rpm: get failed %d\n", rets); | |
755 | return rets; | |
756 | } | |
757 | ||
664df38b | 758 | cb = mei_io_cb_init(cl, NULL); |
04bb139a TW |
759 | if (!cb) { |
760 | rets = -ENOMEM; | |
761 | goto out; | |
762 | } | |
ab841160 | 763 | |
fcb136e1 TW |
764 | /* always allocate at least client max message */ |
765 | length = max_t(size_t, length, dev->me_clients[i].props.max_msg_length); | |
766 | rets = mei_io_cb_alloc_resp_buf(cb, length); | |
664df38b | 767 | if (rets) |
04bb139a | 768 | goto out; |
ab841160 | 769 | |
4b8960b4 | 770 | cb->fop_type = MEI_FOP_READ; |
6aae48ff | 771 | if (mei_hbuf_acquire(dev)) { |
86113500 AU |
772 | rets = mei_hbm_cl_flow_control_req(dev, cl); |
773 | if (rets < 0) | |
04bb139a | 774 | goto out; |
04bb139a | 775 | |
fb601adb | 776 | list_add_tail(&cb->list, &dev->read_list.list); |
ab841160 | 777 | } else { |
fb601adb | 778 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); |
ab841160 | 779 | } |
accb884b CB |
780 | |
781 | cl->read_cb = cb; | |
782 | ||
04bb139a TW |
783 | out: |
784 | cl_dbg(dev, cl, "rpm: autosuspend\n"); | |
785 | pm_runtime_mark_last_busy(&dev->pdev->dev); | |
786 | pm_runtime_put_autosuspend(&dev->pdev->dev); | |
787 | ||
788 | if (rets) | |
789 | mei_io_cb_free(cb); | |
790 | ||
ab841160 OW |
791 | return rets; |
792 | } | |
793 | ||
21767546 | 794 | /** |
9d098192 | 795 | * mei_cl_irq_write - write a message to device |
21767546 TW |
796 | * from the interrupt thread context |
797 | * | |
798 | * @cl: client | |
799 | * @cb: callback block. | |
21767546 TW |
800 | * @cmpl_list: complete list. |
801 | * | |
802 | * returns 0, OK; otherwise error. | |
803 | */ | |
9d098192 TW |
804 | int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, |
805 | struct mei_cl_cb *cmpl_list) | |
21767546 | 806 | { |
136698e5 TW |
807 | struct mei_device *dev; |
808 | struct mei_msg_data *buf; | |
21767546 | 809 | struct mei_msg_hdr mei_hdr; |
136698e5 TW |
810 | size_t len; |
811 | u32 msg_slots; | |
9d098192 | 812 | int slots; |
2ebf8c94 | 813 | int rets; |
21767546 | 814 | |
136698e5 TW |
815 | if (WARN_ON(!cl || !cl->dev)) |
816 | return -ENODEV; | |
817 | ||
818 | dev = cl->dev; | |
819 | ||
820 | buf = &cb->request_buffer; | |
821 | ||
822 | rets = mei_cl_flow_ctrl_creds(cl); | |
823 | if (rets < 0) | |
824 | return rets; | |
825 | ||
826 | if (rets == 0) { | |
04bb139a | 827 | cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); |
136698e5 TW |
828 | return 0; |
829 | } | |
830 | ||
9d098192 | 831 | slots = mei_hbuf_empty_slots(dev); |
136698e5 TW |
832 | len = buf->size - cb->buf_idx; |
833 | msg_slots = mei_data2slots(len); | |
834 | ||
21767546 TW |
835 | mei_hdr.host_addr = cl->host_client_id; |
836 | mei_hdr.me_addr = cl->me_client_id; | |
837 | mei_hdr.reserved = 0; | |
479327fc | 838 | mei_hdr.internal = cb->internal; |
21767546 | 839 | |
9d098192 | 840 | if (slots >= msg_slots) { |
21767546 TW |
841 | mei_hdr.length = len; |
842 | mei_hdr.msg_complete = 1; | |
843 | /* Split the message only if we can write the whole host buffer */ | |
9d098192 TW |
844 | } else if (slots == dev->hbuf_depth) { |
845 | msg_slots = slots; | |
846 | len = (slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); | |
21767546 TW |
847 | mei_hdr.length = len; |
848 | mei_hdr.msg_complete = 0; | |
849 | } else { | |
850 | /* wait for next time the host buffer is empty */ | |
851 | return 0; | |
852 | } | |
853 | ||
c0abffbd | 854 | cl_dbg(dev, cl, "buf: size = %d idx = %lu\n", |
21767546 | 855 | cb->request_buffer.size, cb->buf_idx); |
21767546 | 856 | |
136698e5 | 857 | rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx); |
2ebf8c94 TW |
858 | if (rets) { |
859 | cl->status = rets; | |
21767546 | 860 | list_move_tail(&cb->list, &cmpl_list->list); |
2ebf8c94 | 861 | return rets; |
21767546 TW |
862 | } |
863 | ||
864 | cl->status = 0; | |
4dfaa9f7 | 865 | cl->writing_state = MEI_WRITING; |
21767546 | 866 | cb->buf_idx += mei_hdr.length; |
4dfaa9f7 | 867 | |
21767546 TW |
868 | if (mei_hdr.msg_complete) { |
869 | if (mei_cl_flow_ctrl_reduce(cl)) | |
2ebf8c94 | 870 | return -EIO; |
21767546 TW |
871 | list_move_tail(&cb->list, &dev->write_waiting_list.list); |
872 | } | |
873 | ||
874 | return 0; | |
875 | } | |
876 | ||
4234a6de TW |
877 | /** |
878 | * mei_cl_write - submit a write cb to mei device | |
879 | assumes device_lock is locked | |
880 | * | |
881 | * @cl: host client | |
882 | * @cl: write callback with filled data | |
883 | * | |
83ce0741 | 884 | * returns number of bytes sent on success, <0 on failure. |
4234a6de TW |
885 | */ |
886 | int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) | |
887 | { | |
888 | struct mei_device *dev; | |
889 | struct mei_msg_data *buf; | |
890 | struct mei_msg_hdr mei_hdr; | |
891 | int rets; | |
892 | ||
893 | ||
894 | if (WARN_ON(!cl || !cl->dev)) | |
895 | return -ENODEV; | |
896 | ||
897 | if (WARN_ON(!cb)) | |
898 | return -EINVAL; | |
899 | ||
900 | dev = cl->dev; | |
901 | ||
902 | ||
903 | buf = &cb->request_buffer; | |
904 | ||
c0abffbd | 905 | cl_dbg(dev, cl, "mei_cl_write %d\n", buf->size); |
4234a6de | 906 | |
04bb139a TW |
907 | rets = pm_runtime_get(&dev->pdev->dev); |
908 | if (rets < 0 && rets != -EINPROGRESS) { | |
909 | pm_runtime_put_noidle(&dev->pdev->dev); | |
910 | cl_err(dev, cl, "rpm: get failed %d\n", rets); | |
911 | return rets; | |
912 | } | |
4234a6de TW |
913 | |
914 | cb->fop_type = MEI_FOP_WRITE; | |
6aae48ff TW |
915 | cb->buf_idx = 0; |
916 | cl->writing_state = MEI_IDLE; | |
917 | ||
918 | mei_hdr.host_addr = cl->host_client_id; | |
919 | mei_hdr.me_addr = cl->me_client_id; | |
920 | mei_hdr.reserved = 0; | |
921 | mei_hdr.msg_complete = 0; | |
922 | mei_hdr.internal = cb->internal; | |
4234a6de TW |
923 | |
924 | rets = mei_cl_flow_ctrl_creds(cl); | |
925 | if (rets < 0) | |
926 | goto err; | |
927 | ||
6aae48ff TW |
928 | if (rets == 0) { |
929 | cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); | |
930 | rets = buf->size; | |
931 | goto out; | |
932 | } | |
933 | if (!mei_hbuf_acquire(dev)) { | |
934 | cl_dbg(dev, cl, "Cannot acquire the host buffer: not sending.\n"); | |
4234a6de TW |
935 | rets = buf->size; |
936 | goto out; | |
937 | } | |
4234a6de TW |
938 | |
939 | /* Check for a maximum length */ | |
940 | if (buf->size > mei_hbuf_max_len(dev)) { | |
941 | mei_hdr.length = mei_hbuf_max_len(dev); | |
942 | mei_hdr.msg_complete = 0; | |
943 | } else { | |
944 | mei_hdr.length = buf->size; | |
945 | mei_hdr.msg_complete = 1; | |
946 | } | |
947 | ||
2ebf8c94 TW |
948 | rets = mei_write_message(dev, &mei_hdr, buf->data); |
949 | if (rets) | |
4234a6de | 950 | goto err; |
4234a6de TW |
951 | |
952 | cl->writing_state = MEI_WRITING; | |
953 | cb->buf_idx = mei_hdr.length; | |
954 | ||
4234a6de TW |
955 | out: |
956 | if (mei_hdr.msg_complete) { | |
7ca96aa2 AU |
957 | rets = mei_cl_flow_ctrl_reduce(cl); |
958 | if (rets < 0) | |
4234a6de | 959 | goto err; |
7ca96aa2 | 960 | |
4234a6de TW |
961 | list_add_tail(&cb->list, &dev->write_waiting_list.list); |
962 | } else { | |
963 | list_add_tail(&cb->list, &dev->write_list.list); | |
964 | } | |
965 | ||
966 | ||
967 | if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) { | |
968 | ||
969 | mutex_unlock(&dev->device_lock); | |
7ca96aa2 AU |
970 | rets = wait_event_interruptible(cl->tx_wait, |
971 | cl->writing_state == MEI_WRITE_COMPLETE); | |
4234a6de | 972 | mutex_lock(&dev->device_lock); |
7ca96aa2 AU |
973 | /* wait_event_interruptible returns -ERESTARTSYS */ |
974 | if (rets) { | |
975 | if (signal_pending(current)) | |
976 | rets = -EINTR; | |
977 | goto err; | |
978 | } | |
4234a6de | 979 | } |
7ca96aa2 AU |
980 | |
981 | rets = buf->size; | |
4234a6de | 982 | err: |
04bb139a TW |
983 | cl_dbg(dev, cl, "rpm: autosuspend\n"); |
984 | pm_runtime_mark_last_busy(&dev->pdev->dev); | |
985 | pm_runtime_put_autosuspend(&dev->pdev->dev); | |
986 | ||
4234a6de TW |
987 | return rets; |
988 | } | |
989 | ||
990 | ||
db086fa9 TW |
991 | /** |
992 | * mei_cl_complete - processes completed operation for a client | |
993 | * | |
994 | * @cl: private data of the file object. | |
995 | * @cb: callback block. | |
996 | */ | |
997 | void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) | |
998 | { | |
999 | if (cb->fop_type == MEI_FOP_WRITE) { | |
1000 | mei_io_cb_free(cb); | |
1001 | cb = NULL; | |
1002 | cl->writing_state = MEI_WRITE_COMPLETE; | |
1003 | if (waitqueue_active(&cl->tx_wait)) | |
1004 | wake_up_interruptible(&cl->tx_wait); | |
1005 | ||
1006 | } else if (cb->fop_type == MEI_FOP_READ && | |
1007 | MEI_READING == cl->reading_state) { | |
1008 | cl->reading_state = MEI_READ_COMPLETE; | |
1009 | if (waitqueue_active(&cl->rx_wait)) | |
1010 | wake_up_interruptible(&cl->rx_wait); | |
1011 | else | |
1012 | mei_cl_bus_rx_event(cl); | |
1013 | ||
1014 | } | |
1015 | } | |
1016 | ||
4234a6de | 1017 | |
074b4c01 TW |
1018 | /** |
1019 | * mei_cl_all_disconnect - disconnect forcefully all connected clients | |
1020 | * | |
1021 | * @dev - mei device | |
1022 | */ | |
1023 | ||
1024 | void mei_cl_all_disconnect(struct mei_device *dev) | |
1025 | { | |
31f88f57 | 1026 | struct mei_cl *cl; |
074b4c01 | 1027 | |
31f88f57 | 1028 | list_for_each_entry(cl, &dev->file_list, link) { |
074b4c01 TW |
1029 | cl->state = MEI_FILE_DISCONNECTED; |
1030 | cl->mei_flow_ctrl_creds = 0; | |
074b4c01 TW |
1031 | cl->timer_count = 0; |
1032 | } | |
1033 | } | |
1034 | ||
1035 | ||
1036 | /** | |
5290801c | 1037 | * mei_cl_all_wakeup - wake up all readers and writers they can be interrupted |
074b4c01 TW |
1038 | * |
1039 | * @dev - mei device | |
1040 | */ | |
5290801c | 1041 | void mei_cl_all_wakeup(struct mei_device *dev) |
074b4c01 | 1042 | { |
31f88f57 TW |
1043 | struct mei_cl *cl; |
1044 | list_for_each_entry(cl, &dev->file_list, link) { | |
074b4c01 | 1045 | if (waitqueue_active(&cl->rx_wait)) { |
c0abffbd | 1046 | cl_dbg(dev, cl, "Waking up reading client!\n"); |
074b4c01 TW |
1047 | wake_up_interruptible(&cl->rx_wait); |
1048 | } | |
5290801c | 1049 | if (waitqueue_active(&cl->tx_wait)) { |
c0abffbd | 1050 | cl_dbg(dev, cl, "Waking up writing client!\n"); |
5290801c TW |
1051 | wake_up_interruptible(&cl->tx_wait); |
1052 | } | |
074b4c01 TW |
1053 | } |
1054 | } | |
1055 | ||
1056 | /** | |
1057 | * mei_cl_all_write_clear - clear all pending writes | |
1058 | ||
1059 | * @dev - mei device | |
1060 | */ | |
1061 | void mei_cl_all_write_clear(struct mei_device *dev) | |
1062 | { | |
cc99ecfd TW |
1063 | mei_io_list_free(&dev->write_list, NULL); |
1064 | mei_io_list_free(&dev->write_waiting_list, NULL); | |
074b4c01 TW |
1065 | } |
1066 | ||
1067 |