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> | |
ab841160 | 21 | |
4f3afe1d | 22 | #include <linux/mei.h> |
47a73801 TW |
23 | |
24 | #include "mei_dev.h" | |
0edb23fc | 25 | #include "hbm.h" |
90e0b5f1 TW |
26 | #include "client.h" |
27 | ||
28 | /** | |
29 | * mei_me_cl_by_uuid - locate index of me client | |
30 | * | |
31 | * @dev: mei device | |
32 | * returns me client index or -ENOENT if not found | |
33 | */ | |
34 | int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *uuid) | |
35 | { | |
36 | int i, res = -ENOENT; | |
37 | ||
38 | for (i = 0; i < dev->me_clients_num; ++i) | |
39 | if (uuid_le_cmp(*uuid, | |
40 | dev->me_clients[i].props.protocol_name) == 0) { | |
41 | res = i; | |
42 | break; | |
43 | } | |
44 | ||
45 | return res; | |
46 | } | |
47 | ||
48 | ||
49 | /** | |
50 | * mei_me_cl_by_id return index to me_clients for client_id | |
51 | * | |
52 | * @dev: the device structure | |
53 | * @client_id: me client id | |
54 | * | |
55 | * Locking: called under "dev->device_lock" lock | |
56 | * | |
57 | * returns index on success, -ENOENT on failure. | |
58 | */ | |
59 | ||
60 | int mei_me_cl_by_id(struct mei_device *dev, u8 client_id) | |
61 | { | |
62 | int i; | |
63 | for (i = 0; i < dev->me_clients_num; i++) | |
64 | if (dev->me_clients[i].client_id == client_id) | |
65 | break; | |
66 | if (WARN_ON(dev->me_clients[i].client_id != client_id)) | |
67 | return -ENOENT; | |
68 | ||
69 | if (i == dev->me_clients_num) | |
70 | return -ENOENT; | |
71 | ||
72 | return i; | |
73 | } | |
ab841160 | 74 | |
9ca9050b TW |
75 | |
76 | /** | |
77 | * mei_io_list_flush - removes list entry belonging to cl. | |
78 | * | |
79 | * @list: An instance of our list structure | |
80 | * @cl: host client | |
81 | */ | |
82 | void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl) | |
83 | { | |
84 | struct mei_cl_cb *cb; | |
85 | struct mei_cl_cb *next; | |
86 | ||
87 | list_for_each_entry_safe(cb, next, &list->list, list) { | |
88 | if (cb->cl && mei_cl_cmp_id(cl, cb->cl)) | |
89 | list_del(&cb->list); | |
90 | } | |
91 | } | |
92 | ||
601a1efa TW |
93 | /** |
94 | * mei_io_cb_free - free mei_cb_private related memory | |
95 | * | |
96 | * @cb: mei callback struct | |
97 | */ | |
98 | void mei_io_cb_free(struct mei_cl_cb *cb) | |
99 | { | |
100 | if (cb == NULL) | |
101 | return; | |
102 | ||
103 | kfree(cb->request_buffer.data); | |
104 | kfree(cb->response_buffer.data); | |
105 | kfree(cb); | |
106 | } | |
9ca9050b | 107 | |
664df38b TW |
108 | /** |
109 | * mei_io_cb_init - allocate and initialize io callback | |
110 | * | |
111 | * @cl - mei client | |
112 | * @file: pointer to file structure | |
113 | * | |
114 | * returns mei_cl_cb pointer or NULL; | |
115 | */ | |
116 | struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) | |
117 | { | |
118 | struct mei_cl_cb *cb; | |
119 | ||
120 | cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); | |
121 | if (!cb) | |
122 | return NULL; | |
123 | ||
124 | mei_io_list_init(cb); | |
125 | ||
126 | cb->file_object = fp; | |
db3ed431 | 127 | cb->cl = cl; |
664df38b TW |
128 | cb->buf_idx = 0; |
129 | return cb; | |
130 | } | |
131 | ||
664df38b TW |
132 | /** |
133 | * mei_io_cb_alloc_req_buf - allocate request buffer | |
134 | * | |
135 | * @cb - io callback structure | |
136 | * @size: size of the buffer | |
137 | * | |
138 | * returns 0 on success | |
139 | * -EINVAL if cb is NULL | |
140 | * -ENOMEM if allocation failed | |
141 | */ | |
142 | int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length) | |
143 | { | |
144 | if (!cb) | |
145 | return -EINVAL; | |
146 | ||
147 | if (length == 0) | |
148 | return 0; | |
149 | ||
150 | cb->request_buffer.data = kmalloc(length, GFP_KERNEL); | |
151 | if (!cb->request_buffer.data) | |
152 | return -ENOMEM; | |
153 | cb->request_buffer.size = length; | |
154 | return 0; | |
155 | } | |
156 | /** | |
157 | * mei_io_cb_alloc_req_buf - allocate respose buffer | |
158 | * | |
159 | * @cb - io callback structure | |
160 | * @size: size of the buffer | |
161 | * | |
162 | * returns 0 on success | |
163 | * -EINVAL if cb is NULL | |
164 | * -ENOMEM if allocation failed | |
165 | */ | |
166 | int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length) | |
167 | { | |
168 | if (!cb) | |
169 | return -EINVAL; | |
170 | ||
171 | if (length == 0) | |
172 | return 0; | |
173 | ||
174 | cb->response_buffer.data = kmalloc(length, GFP_KERNEL); | |
175 | if (!cb->response_buffer.data) | |
176 | return -ENOMEM; | |
177 | cb->response_buffer.size = length; | |
178 | return 0; | |
179 | } | |
180 | ||
601a1efa | 181 | |
9ca9050b TW |
182 | |
183 | /** | |
184 | * mei_cl_flush_queues - flushes queue lists belonging to cl. | |
185 | * | |
186 | * @dev: the device structure | |
187 | * @cl: host client | |
188 | */ | |
189 | int mei_cl_flush_queues(struct mei_cl *cl) | |
190 | { | |
90e0b5f1 | 191 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
192 | return -EINVAL; |
193 | ||
194 | dev_dbg(&cl->dev->pdev->dev, "remove list entry belonging to cl\n"); | |
195 | mei_io_list_flush(&cl->dev->read_list, cl); | |
196 | mei_io_list_flush(&cl->dev->write_list, cl); | |
197 | mei_io_list_flush(&cl->dev->write_waiting_list, cl); | |
198 | mei_io_list_flush(&cl->dev->ctrl_wr_list, cl); | |
199 | mei_io_list_flush(&cl->dev->ctrl_rd_list, cl); | |
200 | mei_io_list_flush(&cl->dev->amthif_cmd_list, cl); | |
201 | mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl); | |
202 | return 0; | |
203 | } | |
204 | ||
ab841160 | 205 | |
9ca9050b TW |
206 | /** |
207 | * mei_cl_init - initializes intialize cl. | |
208 | * | |
209 | * @cl: host client to be initialized | |
210 | * @dev: mei device | |
211 | */ | |
212 | void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) | |
213 | { | |
214 | memset(cl, 0, sizeof(struct mei_cl)); | |
215 | init_waitqueue_head(&cl->wait); | |
216 | init_waitqueue_head(&cl->rx_wait); | |
217 | init_waitqueue_head(&cl->tx_wait); | |
218 | INIT_LIST_HEAD(&cl->link); | |
219 | cl->reading_state = MEI_IDLE; | |
220 | cl->writing_state = MEI_IDLE; | |
221 | cl->dev = dev; | |
222 | } | |
223 | ||
224 | /** | |
225 | * mei_cl_allocate - allocates cl structure and sets it up. | |
226 | * | |
227 | * @dev: mei device | |
228 | * returns The allocated file or NULL on failure | |
229 | */ | |
230 | struct mei_cl *mei_cl_allocate(struct mei_device *dev) | |
231 | { | |
232 | struct mei_cl *cl; | |
233 | ||
234 | cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL); | |
235 | if (!cl) | |
236 | return NULL; | |
237 | ||
238 | mei_cl_init(cl, dev); | |
239 | ||
240 | return cl; | |
241 | } | |
242 | ||
90e0b5f1 TW |
243 | /** |
244 | * mei_cl_find_read_cb - find this cl's callback in the read list | |
245 | * | |
246 | * @dev: device structure | |
247 | * returns cb on success, NULL on error | |
248 | */ | |
249 | struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) | |
250 | { | |
251 | struct mei_device *dev = cl->dev; | |
252 | struct mei_cl_cb *cb = NULL; | |
253 | struct mei_cl_cb *next = NULL; | |
254 | ||
255 | list_for_each_entry_safe(cb, next, &dev->read_list.list, list) | |
256 | if (mei_cl_cmp_id(cl, cb->cl)) | |
257 | return cb; | |
258 | return NULL; | |
259 | } | |
260 | ||
9ca9050b TW |
261 | |
262 | /** | |
263 | * mei_me_cl_link - create link between host and me clinet and add | |
264 | * me_cl to the list | |
265 | * | |
9ca9050b TW |
266 | * @cl: link between me and host client assocated with opened file descriptor |
267 | * @uuid: uuid of ME client | |
268 | * @client_id: id of the host client | |
269 | * | |
270 | * returns ME client index if ME client | |
271 | * -EINVAL on incorrect values | |
272 | * -ENONET if client not found | |
273 | */ | |
90e0b5f1 | 274 | int mei_cl_link_me(struct mei_cl *cl, const uuid_le *uuid, u8 host_cl_id) |
9ca9050b | 275 | { |
90e0b5f1 | 276 | struct mei_device *dev; |
9ca9050b TW |
277 | int i; |
278 | ||
90e0b5f1 | 279 | if (WARN_ON(!cl || !cl->dev || !uuid)) |
9ca9050b TW |
280 | return -EINVAL; |
281 | ||
90e0b5f1 TW |
282 | dev = cl->dev; |
283 | ||
9ca9050b TW |
284 | /* check for valid client id */ |
285 | i = mei_me_cl_by_uuid(dev, uuid); | |
286 | if (i >= 0) { | |
287 | cl->me_client_id = dev->me_clients[i].client_id; | |
288 | cl->state = MEI_FILE_CONNECTING; | |
289 | cl->host_client_id = host_cl_id; | |
290 | ||
291 | list_add_tail(&cl->link, &dev->file_list); | |
292 | return (u8)i; | |
293 | } | |
294 | ||
295 | return -ENOENT; | |
296 | } | |
297 | /** | |
90e0b5f1 | 298 | * mei_cl_unlink - remove me_cl from the list |
9ca9050b TW |
299 | * |
300 | * @dev: the device structure | |
301 | * @host_client_id: host client id to be removed | |
302 | */ | |
90e0b5f1 | 303 | int mei_cl_unlink(struct mei_cl *cl) |
9ca9050b | 304 | { |
90e0b5f1 | 305 | struct mei_device *dev; |
9ca9050b | 306 | struct mei_cl *pos, *next; |
90e0b5f1 TW |
307 | |
308 | if (WARN_ON(!cl || !cl->dev)) | |
309 | return -EINVAL; | |
310 | ||
311 | dev = cl->dev; | |
312 | ||
9ca9050b TW |
313 | list_for_each_entry_safe(pos, next, &dev->file_list, link) { |
314 | if (cl->host_client_id == pos->host_client_id) { | |
315 | dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n", | |
90e0b5f1 | 316 | pos->host_client_id, pos->me_client_id); |
9ca9050b TW |
317 | list_del_init(&pos->link); |
318 | break; | |
319 | } | |
320 | } | |
90e0b5f1 | 321 | return 0; |
9ca9050b TW |
322 | } |
323 | ||
324 | ||
325 | void mei_host_client_init(struct work_struct *work) | |
326 | { | |
327 | struct mei_device *dev = container_of(work, | |
328 | struct mei_device, init_work); | |
329 | struct mei_client_properties *client_props; | |
330 | int i; | |
331 | ||
332 | mutex_lock(&dev->device_lock); | |
333 | ||
334 | bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); | |
335 | dev->open_handle_count = 0; | |
336 | ||
337 | /* | |
338 | * Reserving the first three client IDs | |
339 | * 0: Reserved for MEI Bus Message communications | |
340 | * 1: Reserved for Watchdog | |
341 | * 2: Reserved for AMTHI | |
342 | */ | |
343 | bitmap_set(dev->host_clients_map, 0, 3); | |
344 | ||
345 | for (i = 0; i < dev->me_clients_num; i++) { | |
346 | client_props = &dev->me_clients[i].props; | |
347 | ||
1a1aca42 | 348 | if (!uuid_le_cmp(client_props->protocol_name, mei_amthif_guid)) |
9ca9050b TW |
349 | mei_amthif_host_init(dev); |
350 | else if (!uuid_le_cmp(client_props->protocol_name, mei_wd_guid)) | |
351 | mei_wd_host_init(dev); | |
352 | } | |
353 | ||
354 | dev->dev_state = MEI_DEV_ENABLED; | |
355 | ||
356 | mutex_unlock(&dev->device_lock); | |
357 | } | |
358 | ||
359 | ||
360 | /** | |
90e0b5f1 | 361 | * mei_cl_disconnect - disconnect host clinet form the me one |
9ca9050b | 362 | * |
90e0b5f1 | 363 | * @cl: host client |
9ca9050b TW |
364 | * |
365 | * Locking: called under "dev->device_lock" lock | |
366 | * | |
367 | * returns 0 on success, <0 on failure. | |
368 | */ | |
90e0b5f1 | 369 | int mei_cl_disconnect(struct mei_cl *cl) |
9ca9050b | 370 | { |
90e0b5f1 | 371 | struct mei_device *dev; |
9ca9050b TW |
372 | struct mei_cl_cb *cb; |
373 | int rets, err; | |
374 | ||
90e0b5f1 | 375 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
376 | return -ENODEV; |
377 | ||
90e0b5f1 TW |
378 | dev = cl->dev; |
379 | ||
9ca9050b TW |
380 | if (cl->state != MEI_FILE_DISCONNECTING) |
381 | return 0; | |
382 | ||
383 | cb = mei_io_cb_init(cl, NULL); | |
384 | if (!cb) | |
385 | return -ENOMEM; | |
386 | ||
387 | cb->fop_type = MEI_FOP_CLOSE; | |
388 | if (dev->mei_host_buffer_is_empty) { | |
389 | dev->mei_host_buffer_is_empty = false; | |
390 | if (mei_hbm_cl_disconnect_req(dev, cl)) { | |
391 | rets = -ENODEV; | |
392 | dev_err(&dev->pdev->dev, "failed to disconnect.\n"); | |
393 | goto free; | |
394 | } | |
395 | mdelay(10); /* Wait for hardware disconnection ready */ | |
396 | list_add_tail(&cb->list, &dev->ctrl_rd_list.list); | |
397 | } else { | |
398 | dev_dbg(&dev->pdev->dev, "add disconnect cb to control write list\n"); | |
399 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); | |
400 | ||
401 | } | |
402 | mutex_unlock(&dev->device_lock); | |
403 | ||
404 | err = wait_event_timeout(dev->wait_recvd_msg, | |
405 | MEI_FILE_DISCONNECTED == cl->state, | |
406 | mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); | |
407 | ||
408 | mutex_lock(&dev->device_lock); | |
409 | if (MEI_FILE_DISCONNECTED == cl->state) { | |
410 | rets = 0; | |
411 | dev_dbg(&dev->pdev->dev, "successfully disconnected from FW client.\n"); | |
412 | } else { | |
413 | rets = -ENODEV; | |
414 | if (MEI_FILE_DISCONNECTED != cl->state) | |
415 | dev_dbg(&dev->pdev->dev, "wrong status client disconnect.\n"); | |
416 | ||
417 | if (err) | |
418 | dev_dbg(&dev->pdev->dev, | |
419 | "wait failed disconnect err=%08x\n", | |
420 | err); | |
421 | ||
422 | dev_dbg(&dev->pdev->dev, "failed to disconnect from FW client.\n"); | |
423 | } | |
424 | ||
425 | mei_io_list_flush(&dev->ctrl_rd_list, cl); | |
426 | mei_io_list_flush(&dev->ctrl_wr_list, cl); | |
427 | free: | |
428 | mei_io_cb_free(cb); | |
429 | return rets; | |
430 | } | |
431 | ||
432 | ||
433 | /** | |
90e0b5f1 TW |
434 | * mei_cl_is_other_connecting - checks if other |
435 | * client with the same me client id is connecting | |
9ca9050b | 436 | * |
9ca9050b TW |
437 | * @cl: private data of the file object |
438 | * | |
90e0b5f1 | 439 | * returns ture if other client is connected, 0 - otherwise. |
9ca9050b | 440 | */ |
90e0b5f1 | 441 | bool mei_cl_is_other_connecting(struct mei_cl *cl) |
9ca9050b | 442 | { |
90e0b5f1 TW |
443 | struct mei_device *dev; |
444 | struct mei_cl *pos; | |
445 | struct mei_cl *next; | |
9ca9050b | 446 | |
90e0b5f1 TW |
447 | if (WARN_ON(!cl || !cl->dev)) |
448 | return false; | |
449 | ||
450 | dev = cl->dev; | |
451 | ||
452 | list_for_each_entry_safe(pos, next, &dev->file_list, link) { | |
453 | if ((pos->state == MEI_FILE_CONNECTING) && | |
454 | (pos != cl) && cl->me_client_id == pos->me_client_id) | |
455 | return true; | |
9ca9050b TW |
456 | |
457 | } | |
90e0b5f1 TW |
458 | |
459 | return false; | |
9ca9050b TW |
460 | } |
461 | ||
9f81abda TW |
462 | /** |
463 | * mei_cl_connect - connect host clinet to the me one | |
464 | * | |
465 | * @cl: host client | |
466 | * | |
467 | * Locking: called under "dev->device_lock" lock | |
468 | * | |
469 | * returns 0 on success, <0 on failure. | |
470 | */ | |
471 | int mei_cl_connect(struct mei_cl *cl, struct file *file) | |
472 | { | |
473 | struct mei_device *dev; | |
474 | struct mei_cl_cb *cb; | |
475 | long timeout = mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT); | |
476 | int rets; | |
477 | ||
478 | if (WARN_ON(!cl || !cl->dev)) | |
479 | return -ENODEV; | |
480 | ||
481 | dev = cl->dev; | |
482 | ||
483 | cb = mei_io_cb_init(cl, file); | |
484 | if (!cb) { | |
485 | rets = -ENOMEM; | |
486 | goto out; | |
487 | } | |
488 | ||
489 | cb->fop_type = MEI_FOP_IOCTL; | |
490 | ||
491 | if (dev->mei_host_buffer_is_empty && | |
492 | !mei_cl_is_other_connecting(cl)) { | |
493 | dev->mei_host_buffer_is_empty = false; | |
494 | ||
495 | if (mei_hbm_cl_connect_req(dev, cl)) { | |
496 | rets = -ENODEV; | |
497 | goto out; | |
498 | } | |
499 | cl->timer_count = MEI_CONNECT_TIMEOUT; | |
500 | list_add_tail(&cb->list, &dev->ctrl_rd_list.list); | |
501 | } else { | |
502 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); | |
503 | } | |
504 | ||
505 | mutex_unlock(&dev->device_lock); | |
506 | rets = wait_event_timeout(dev->wait_recvd_msg, | |
507 | (cl->state == MEI_FILE_CONNECTED || | |
508 | cl->state == MEI_FILE_DISCONNECTED), | |
509 | timeout * HZ); | |
510 | mutex_lock(&dev->device_lock); | |
511 | ||
512 | if (cl->state != MEI_FILE_CONNECTED) { | |
513 | rets = -EFAULT; | |
514 | ||
515 | mei_io_list_flush(&dev->ctrl_rd_list, cl); | |
516 | mei_io_list_flush(&dev->ctrl_wr_list, cl); | |
517 | goto out; | |
518 | } | |
519 | ||
520 | rets = cl->status; | |
521 | ||
522 | out: | |
523 | mei_io_cb_free(cb); | |
524 | return rets; | |
525 | } | |
526 | ||
9ca9050b | 527 | /** |
90e0b5f1 | 528 | * mei_cl_flow_ctrl_creds - checks flow_control credits for cl. |
9ca9050b TW |
529 | * |
530 | * @dev: the device structure | |
531 | * @cl: private data of the file object | |
532 | * | |
533 | * returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise. | |
534 | * -ENOENT if mei_cl is not present | |
535 | * -EINVAL if single_recv_buf == 0 | |
536 | */ | |
90e0b5f1 | 537 | int mei_cl_flow_ctrl_creds(struct mei_cl *cl) |
9ca9050b | 538 | { |
90e0b5f1 | 539 | struct mei_device *dev; |
9ca9050b TW |
540 | int i; |
541 | ||
90e0b5f1 TW |
542 | if (WARN_ON(!cl || !cl->dev)) |
543 | return -EINVAL; | |
544 | ||
545 | dev = cl->dev; | |
546 | ||
9ca9050b TW |
547 | if (!dev->me_clients_num) |
548 | return 0; | |
549 | ||
550 | if (cl->mei_flow_ctrl_creds > 0) | |
551 | return 1; | |
552 | ||
553 | for (i = 0; i < dev->me_clients_num; i++) { | |
554 | struct mei_me_client *me_cl = &dev->me_clients[i]; | |
555 | if (me_cl->client_id == cl->me_client_id) { | |
556 | if (me_cl->mei_flow_ctrl_creds) { | |
557 | if (WARN_ON(me_cl->props.single_recv_buf == 0)) | |
558 | return -EINVAL; | |
559 | return 1; | |
560 | } else { | |
561 | return 0; | |
562 | } | |
563 | } | |
564 | } | |
565 | return -ENOENT; | |
566 | } | |
567 | ||
568 | /** | |
90e0b5f1 | 569 | * mei_cl_flow_ctrl_reduce - reduces flow_control. |
9ca9050b TW |
570 | * |
571 | * @dev: the device structure | |
572 | * @cl: private data of the file object | |
573 | * @returns | |
574 | * 0 on success | |
575 | * -ENOENT when me client is not found | |
576 | * -EINVAL when ctrl credits are <= 0 | |
577 | */ | |
90e0b5f1 | 578 | int mei_cl_flow_ctrl_reduce(struct mei_cl *cl) |
9ca9050b | 579 | { |
90e0b5f1 | 580 | struct mei_device *dev; |
9ca9050b TW |
581 | int i; |
582 | ||
90e0b5f1 TW |
583 | if (WARN_ON(!cl || !cl->dev)) |
584 | return -EINVAL; | |
585 | ||
586 | dev = cl->dev; | |
587 | ||
9ca9050b TW |
588 | if (!dev->me_clients_num) |
589 | return -ENOENT; | |
590 | ||
591 | for (i = 0; i < dev->me_clients_num; i++) { | |
592 | struct mei_me_client *me_cl = &dev->me_clients[i]; | |
593 | if (me_cl->client_id == cl->me_client_id) { | |
594 | if (me_cl->props.single_recv_buf != 0) { | |
595 | if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) | |
596 | return -EINVAL; | |
597 | dev->me_clients[i].mei_flow_ctrl_creds--; | |
598 | } else { | |
599 | if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) | |
600 | return -EINVAL; | |
601 | cl->mei_flow_ctrl_creds--; | |
602 | } | |
603 | return 0; | |
604 | } | |
605 | } | |
606 | return -ENOENT; | |
607 | } | |
608 | ||
ab841160 | 609 | /** |
90e0b5f1 | 610 | * mei_cl_start_read - the start read client message function. |
ab841160 | 611 | * |
90e0b5f1 | 612 | * @cl: host client |
ab841160 OW |
613 | * |
614 | * returns 0 on success, <0 on failure. | |
615 | */ | |
90e0b5f1 | 616 | int mei_cl_read_start(struct mei_cl *cl) |
ab841160 | 617 | { |
90e0b5f1 | 618 | struct mei_device *dev; |
ab841160 | 619 | struct mei_cl_cb *cb; |
664df38b | 620 | int rets; |
ab841160 OW |
621 | int i; |
622 | ||
90e0b5f1 TW |
623 | if (WARN_ON(!cl || !cl->dev)) |
624 | return -ENODEV; | |
625 | ||
626 | dev = cl->dev; | |
627 | ||
ab841160 OW |
628 | if (cl->state != MEI_FILE_CONNECTED) |
629 | return -ENODEV; | |
630 | ||
b210d750 | 631 | if (dev->dev_state != MEI_DEV_ENABLED) |
ab841160 OW |
632 | return -ENODEV; |
633 | ||
d91aaed3 | 634 | if (cl->read_cb) { |
ab841160 OW |
635 | dev_dbg(&dev->pdev->dev, "read is pending.\n"); |
636 | return -EBUSY; | |
637 | } | |
664df38b TW |
638 | i = mei_me_cl_by_id(dev, cl->me_client_id); |
639 | if (i < 0) { | |
640 | dev_err(&dev->pdev->dev, "no such me client %d\n", | |
641 | cl->me_client_id); | |
642 | return -ENODEV; | |
643 | } | |
ab841160 | 644 | |
664df38b | 645 | cb = mei_io_cb_init(cl, NULL); |
ab841160 OW |
646 | if (!cb) |
647 | return -ENOMEM; | |
648 | ||
664df38b TW |
649 | rets = mei_io_cb_alloc_resp_buf(cb, |
650 | dev->me_clients[i].props.max_msg_length); | |
651 | if (rets) | |
652 | goto err; | |
ab841160 | 653 | |
4b8960b4 | 654 | cb->fop_type = MEI_FOP_READ; |
ab841160 OW |
655 | cl->read_cb = cb; |
656 | if (dev->mei_host_buffer_is_empty) { | |
eb9af0ac | 657 | dev->mei_host_buffer_is_empty = false; |
8120e720 | 658 | if (mei_hbm_cl_flow_control_req(dev, cl)) { |
ab841160 | 659 | rets = -ENODEV; |
664df38b | 660 | goto err; |
ab841160 | 661 | } |
fb601adb | 662 | list_add_tail(&cb->list, &dev->read_list.list); |
ab841160 | 663 | } else { |
fb601adb | 664 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); |
ab841160 OW |
665 | } |
666 | return rets; | |
664df38b | 667 | err: |
601a1efa | 668 | mei_io_cb_free(cb); |
ab841160 OW |
669 | return rets; |
670 | } | |
671 |