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 | |
393b148f | 112 | * @fp: pointer to file structure |
664df38b TW |
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 | * | |
393b148f MI |
135 | * @cb: io callback structure |
136 | * @length: size of the buffer | |
664df38b TW |
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 | /** | |
393b148f | 157 | * mei_io_cb_alloc_resp_buf - allocate respose buffer |
664df38b | 158 | * |
393b148f MI |
159 | * @cb: io callback structure |
160 | * @length: size of the buffer | |
664df38b TW |
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 | * | |
9ca9050b TW |
186 | * @cl: host client |
187 | */ | |
188 | int mei_cl_flush_queues(struct mei_cl *cl) | |
189 | { | |
c0abffbd AU |
190 | struct mei_device *dev; |
191 | ||
90e0b5f1 | 192 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
193 | return -EINVAL; |
194 | ||
c0abffbd AU |
195 | dev = cl->dev; |
196 | ||
197 | cl_dbg(dev, cl, "remove list entry belonging to cl\n"); | |
9ca9050b TW |
198 | mei_io_list_flush(&cl->dev->read_list, cl); |
199 | mei_io_list_flush(&cl->dev->write_list, cl); | |
200 | mei_io_list_flush(&cl->dev->write_waiting_list, cl); | |
201 | mei_io_list_flush(&cl->dev->ctrl_wr_list, cl); | |
202 | mei_io_list_flush(&cl->dev->ctrl_rd_list, cl); | |
203 | mei_io_list_flush(&cl->dev->amthif_cmd_list, cl); | |
204 | mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl); | |
205 | return 0; | |
206 | } | |
207 | ||
ab841160 | 208 | |
9ca9050b TW |
209 | /** |
210 | * mei_cl_init - initializes intialize cl. | |
211 | * | |
212 | * @cl: host client to be initialized | |
213 | * @dev: mei device | |
214 | */ | |
215 | void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) | |
216 | { | |
217 | memset(cl, 0, sizeof(struct mei_cl)); | |
218 | init_waitqueue_head(&cl->wait); | |
219 | init_waitqueue_head(&cl->rx_wait); | |
220 | init_waitqueue_head(&cl->tx_wait); | |
221 | INIT_LIST_HEAD(&cl->link); | |
a7b71bc0 | 222 | INIT_LIST_HEAD(&cl->device_link); |
9ca9050b TW |
223 | cl->reading_state = MEI_IDLE; |
224 | cl->writing_state = MEI_IDLE; | |
225 | cl->dev = dev; | |
226 | } | |
227 | ||
228 | /** | |
229 | * mei_cl_allocate - allocates cl structure and sets it up. | |
230 | * | |
231 | * @dev: mei device | |
232 | * returns The allocated file or NULL on failure | |
233 | */ | |
234 | struct mei_cl *mei_cl_allocate(struct mei_device *dev) | |
235 | { | |
236 | struct mei_cl *cl; | |
237 | ||
238 | cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL); | |
239 | if (!cl) | |
240 | return NULL; | |
241 | ||
242 | mei_cl_init(cl, dev); | |
243 | ||
244 | return cl; | |
245 | } | |
246 | ||
90e0b5f1 TW |
247 | /** |
248 | * mei_cl_find_read_cb - find this cl's callback in the read list | |
249 | * | |
393b148f MI |
250 | * @cl: host client |
251 | * | |
90e0b5f1 TW |
252 | * returns cb on success, NULL on error |
253 | */ | |
254 | struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) | |
255 | { | |
256 | struct mei_device *dev = cl->dev; | |
257 | struct mei_cl_cb *cb = NULL; | |
258 | struct mei_cl_cb *next = NULL; | |
259 | ||
260 | list_for_each_entry_safe(cb, next, &dev->read_list.list, list) | |
261 | if (mei_cl_cmp_id(cl, cb->cl)) | |
262 | return cb; | |
263 | return NULL; | |
264 | } | |
265 | ||
781d0d89 | 266 | /** mei_cl_link: allocte host id in the host map |
9ca9050b | 267 | * |
781d0d89 TW |
268 | * @cl - host client |
269 | * @id - fixed host id or -1 for genereting one | |
393b148f | 270 | * |
781d0d89 | 271 | * returns 0 on success |
9ca9050b TW |
272 | * -EINVAL on incorrect values |
273 | * -ENONET if client not found | |
274 | */ | |
781d0d89 | 275 | int mei_cl_link(struct mei_cl *cl, int id) |
9ca9050b | 276 | { |
90e0b5f1 | 277 | struct mei_device *dev; |
9ca9050b | 278 | |
781d0d89 | 279 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
280 | return -EINVAL; |
281 | ||
90e0b5f1 TW |
282 | dev = cl->dev; |
283 | ||
781d0d89 TW |
284 | /* If Id is not asigned get one*/ |
285 | if (id == MEI_HOST_CLIENT_ID_ANY) | |
286 | id = find_first_zero_bit(dev->host_clients_map, | |
287 | MEI_CLIENTS_MAX); | |
9ca9050b | 288 | |
781d0d89 TW |
289 | if (id >= MEI_CLIENTS_MAX) { |
290 | dev_err(&dev->pdev->dev, "id exceded %d", MEI_CLIENTS_MAX) ; | |
e036cc57 TW |
291 | return -EMFILE; |
292 | } | |
293 | ||
294 | if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { | |
295 | dev_err(&dev->pdev->dev, "open_handle_count exceded %d", | |
296 | MEI_MAX_OPEN_HANDLE_COUNT); | |
297 | return -EMFILE; | |
9ca9050b TW |
298 | } |
299 | ||
caaeb09a TW |
300 | if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { |
301 | dev_err(&dev->pdev->dev, "open_handle_count exceded %d", | |
302 | MEI_MAX_OPEN_HANDLE_COUNT); | |
303 | return -ENOENT; | |
304 | } | |
305 | ||
781d0d89 TW |
306 | dev->open_handle_count++; |
307 | ||
308 | cl->host_client_id = id; | |
309 | list_add_tail(&cl->link, &dev->file_list); | |
310 | ||
311 | set_bit(id, dev->host_clients_map); | |
312 | ||
313 | cl->state = MEI_FILE_INITIALIZING; | |
314 | ||
c0abffbd | 315 | cl_dbg(dev, cl, "link cl\n"); |
781d0d89 | 316 | return 0; |
9ca9050b | 317 | } |
781d0d89 | 318 | |
9ca9050b | 319 | /** |
90e0b5f1 | 320 | * mei_cl_unlink - remove me_cl from the list |
9ca9050b | 321 | * |
393b148f | 322 | * @cl: host client |
9ca9050b | 323 | */ |
90e0b5f1 | 324 | int mei_cl_unlink(struct mei_cl *cl) |
9ca9050b | 325 | { |
90e0b5f1 | 326 | struct mei_device *dev; |
90e0b5f1 | 327 | |
781d0d89 TW |
328 | /* don't shout on error exit path */ |
329 | if (!cl) | |
330 | return 0; | |
331 | ||
8e9a4a9a TW |
332 | /* wd and amthif might not be initialized */ |
333 | if (!cl->dev) | |
334 | return 0; | |
90e0b5f1 TW |
335 | |
336 | dev = cl->dev; | |
337 | ||
a14c44d8 TW |
338 | cl_dbg(dev, cl, "unlink client"); |
339 | ||
340 | list_del_init(&cl->link); | |
341 | ||
90e0b5f1 | 342 | return 0; |
9ca9050b TW |
343 | } |
344 | ||
345 | ||
346 | void mei_host_client_init(struct work_struct *work) | |
347 | { | |
348 | struct mei_device *dev = container_of(work, | |
349 | struct mei_device, init_work); | |
350 | struct mei_client_properties *client_props; | |
351 | int i; | |
352 | ||
353 | mutex_lock(&dev->device_lock); | |
354 | ||
355 | bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); | |
356 | dev->open_handle_count = 0; | |
357 | ||
358 | /* | |
359 | * Reserving the first three client IDs | |
360 | * 0: Reserved for MEI Bus Message communications | |
361 | * 1: Reserved for Watchdog | |
362 | * 2: Reserved for AMTHI | |
363 | */ | |
364 | bitmap_set(dev->host_clients_map, 0, 3); | |
365 | ||
366 | for (i = 0; i < dev->me_clients_num; i++) { | |
367 | client_props = &dev->me_clients[i].props; | |
368 | ||
1a1aca42 | 369 | if (!uuid_le_cmp(client_props->protocol_name, mei_amthif_guid)) |
9ca9050b TW |
370 | mei_amthif_host_init(dev); |
371 | else if (!uuid_le_cmp(client_props->protocol_name, mei_wd_guid)) | |
372 | mei_wd_host_init(dev); | |
59fcd7c6 SO |
373 | else if (!uuid_le_cmp(client_props->protocol_name, mei_nfc_guid)) |
374 | mei_nfc_host_init(dev); | |
375 | ||
9ca9050b TW |
376 | } |
377 | ||
378 | dev->dev_state = MEI_DEV_ENABLED; | |
379 | ||
380 | mutex_unlock(&dev->device_lock); | |
381 | } | |
382 | ||
383 | ||
384 | /** | |
90e0b5f1 | 385 | * mei_cl_disconnect - disconnect host clinet form the me one |
9ca9050b | 386 | * |
90e0b5f1 | 387 | * @cl: host client |
9ca9050b TW |
388 | * |
389 | * Locking: called under "dev->device_lock" lock | |
390 | * | |
391 | * returns 0 on success, <0 on failure. | |
392 | */ | |
90e0b5f1 | 393 | int mei_cl_disconnect(struct mei_cl *cl) |
9ca9050b | 394 | { |
90e0b5f1 | 395 | struct mei_device *dev; |
9ca9050b TW |
396 | struct mei_cl_cb *cb; |
397 | int rets, err; | |
398 | ||
90e0b5f1 | 399 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
400 | return -ENODEV; |
401 | ||
90e0b5f1 TW |
402 | dev = cl->dev; |
403 | ||
c0abffbd AU |
404 | cl_dbg(dev, cl, "disconnecting"); |
405 | ||
9ca9050b TW |
406 | if (cl->state != MEI_FILE_DISCONNECTING) |
407 | return 0; | |
408 | ||
409 | cb = mei_io_cb_init(cl, NULL); | |
410 | if (!cb) | |
411 | return -ENOMEM; | |
412 | ||
413 | cb->fop_type = MEI_FOP_CLOSE; | |
330dd7da TW |
414 | if (dev->hbuf_is_ready) { |
415 | dev->hbuf_is_ready = false; | |
9ca9050b TW |
416 | if (mei_hbm_cl_disconnect_req(dev, cl)) { |
417 | rets = -ENODEV; | |
c0abffbd | 418 | cl_err(dev, cl, "failed to disconnect.\n"); |
9ca9050b TW |
419 | goto free; |
420 | } | |
421 | mdelay(10); /* Wait for hardware disconnection ready */ | |
422 | list_add_tail(&cb->list, &dev->ctrl_rd_list.list); | |
423 | } else { | |
c0abffbd | 424 | cl_dbg(dev, cl, "add disconnect cb to control write list\n"); |
9ca9050b TW |
425 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); |
426 | ||
427 | } | |
428 | mutex_unlock(&dev->device_lock); | |
429 | ||
430 | err = wait_event_timeout(dev->wait_recvd_msg, | |
431 | MEI_FILE_DISCONNECTED == cl->state, | |
432 | mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); | |
433 | ||
434 | mutex_lock(&dev->device_lock); | |
435 | if (MEI_FILE_DISCONNECTED == cl->state) { | |
436 | rets = 0; | |
c0abffbd | 437 | cl_dbg(dev, cl, "successfully disconnected from FW client.\n"); |
9ca9050b TW |
438 | } else { |
439 | rets = -ENODEV; | |
440 | if (MEI_FILE_DISCONNECTED != cl->state) | |
c0abffbd | 441 | cl_err(dev, cl, "wrong status client disconnect.\n"); |
9ca9050b TW |
442 | |
443 | if (err) | |
c0abffbd | 444 | cl_dbg(dev, cl, "wait failed disconnect err=%08x\n", |
9ca9050b TW |
445 | err); |
446 | ||
c0abffbd | 447 | cl_err(dev, cl, "failed to disconnect from FW client.\n"); |
9ca9050b TW |
448 | } |
449 | ||
450 | mei_io_list_flush(&dev->ctrl_rd_list, cl); | |
451 | mei_io_list_flush(&dev->ctrl_wr_list, cl); | |
452 | free: | |
453 | mei_io_cb_free(cb); | |
454 | return rets; | |
455 | } | |
456 | ||
457 | ||
458 | /** | |
90e0b5f1 TW |
459 | * mei_cl_is_other_connecting - checks if other |
460 | * client with the same me client id is connecting | |
9ca9050b | 461 | * |
9ca9050b TW |
462 | * @cl: private data of the file object |
463 | * | |
90e0b5f1 | 464 | * returns ture if other client is connected, 0 - otherwise. |
9ca9050b | 465 | */ |
90e0b5f1 | 466 | bool mei_cl_is_other_connecting(struct mei_cl *cl) |
9ca9050b | 467 | { |
90e0b5f1 TW |
468 | struct mei_device *dev; |
469 | struct mei_cl *pos; | |
470 | struct mei_cl *next; | |
9ca9050b | 471 | |
90e0b5f1 TW |
472 | if (WARN_ON(!cl || !cl->dev)) |
473 | return false; | |
474 | ||
475 | dev = cl->dev; | |
476 | ||
477 | list_for_each_entry_safe(pos, next, &dev->file_list, link) { | |
478 | if ((pos->state == MEI_FILE_CONNECTING) && | |
479 | (pos != cl) && cl->me_client_id == pos->me_client_id) | |
480 | return true; | |
9ca9050b TW |
481 | |
482 | } | |
90e0b5f1 TW |
483 | |
484 | return false; | |
9ca9050b TW |
485 | } |
486 | ||
9f81abda TW |
487 | /** |
488 | * mei_cl_connect - connect host clinet to the me one | |
489 | * | |
490 | * @cl: host client | |
491 | * | |
492 | * Locking: called under "dev->device_lock" lock | |
493 | * | |
494 | * returns 0 on success, <0 on failure. | |
495 | */ | |
496 | int mei_cl_connect(struct mei_cl *cl, struct file *file) | |
497 | { | |
498 | struct mei_device *dev; | |
499 | struct mei_cl_cb *cb; | |
9f81abda TW |
500 | int rets; |
501 | ||
502 | if (WARN_ON(!cl || !cl->dev)) | |
503 | return -ENODEV; | |
504 | ||
505 | dev = cl->dev; | |
506 | ||
507 | cb = mei_io_cb_init(cl, file); | |
508 | if (!cb) { | |
509 | rets = -ENOMEM; | |
510 | goto out; | |
511 | } | |
512 | ||
513 | cb->fop_type = MEI_FOP_IOCTL; | |
514 | ||
330dd7da TW |
515 | if (dev->hbuf_is_ready && !mei_cl_is_other_connecting(cl)) { |
516 | dev->hbuf_is_ready = false; | |
9f81abda TW |
517 | |
518 | if (mei_hbm_cl_connect_req(dev, cl)) { | |
519 | rets = -ENODEV; | |
520 | goto out; | |
521 | } | |
522 | cl->timer_count = MEI_CONNECT_TIMEOUT; | |
523 | list_add_tail(&cb->list, &dev->ctrl_rd_list.list); | |
524 | } else { | |
525 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); | |
526 | } | |
527 | ||
528 | mutex_unlock(&dev->device_lock); | |
529 | rets = wait_event_timeout(dev->wait_recvd_msg, | |
530 | (cl->state == MEI_FILE_CONNECTED || | |
531 | cl->state == MEI_FILE_DISCONNECTED), | |
206ecfc2 | 532 | mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); |
9f81abda TW |
533 | mutex_lock(&dev->device_lock); |
534 | ||
535 | if (cl->state != MEI_FILE_CONNECTED) { | |
536 | rets = -EFAULT; | |
537 | ||
538 | mei_io_list_flush(&dev->ctrl_rd_list, cl); | |
539 | mei_io_list_flush(&dev->ctrl_wr_list, cl); | |
540 | goto out; | |
541 | } | |
542 | ||
543 | rets = cl->status; | |
544 | ||
545 | out: | |
546 | mei_io_cb_free(cb); | |
547 | return rets; | |
548 | } | |
549 | ||
9ca9050b | 550 | /** |
90e0b5f1 | 551 | * mei_cl_flow_ctrl_creds - checks flow_control credits for cl. |
9ca9050b | 552 | * |
9ca9050b TW |
553 | * @cl: private data of the file object |
554 | * | |
555 | * returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise. | |
556 | * -ENOENT if mei_cl is not present | |
557 | * -EINVAL if single_recv_buf == 0 | |
558 | */ | |
90e0b5f1 | 559 | int mei_cl_flow_ctrl_creds(struct mei_cl *cl) |
9ca9050b | 560 | { |
90e0b5f1 | 561 | struct mei_device *dev; |
9ca9050b TW |
562 | int i; |
563 | ||
90e0b5f1 TW |
564 | if (WARN_ON(!cl || !cl->dev)) |
565 | return -EINVAL; | |
566 | ||
567 | dev = cl->dev; | |
568 | ||
9ca9050b TW |
569 | if (!dev->me_clients_num) |
570 | return 0; | |
571 | ||
572 | if (cl->mei_flow_ctrl_creds > 0) | |
573 | return 1; | |
574 | ||
575 | for (i = 0; i < dev->me_clients_num; i++) { | |
576 | struct mei_me_client *me_cl = &dev->me_clients[i]; | |
577 | if (me_cl->client_id == cl->me_client_id) { | |
578 | if (me_cl->mei_flow_ctrl_creds) { | |
579 | if (WARN_ON(me_cl->props.single_recv_buf == 0)) | |
580 | return -EINVAL; | |
581 | return 1; | |
582 | } else { | |
583 | return 0; | |
584 | } | |
585 | } | |
586 | } | |
587 | return -ENOENT; | |
588 | } | |
589 | ||
590 | /** | |
90e0b5f1 | 591 | * mei_cl_flow_ctrl_reduce - reduces flow_control. |
9ca9050b | 592 | * |
9ca9050b | 593 | * @cl: private data of the file object |
393b148f | 594 | * |
9ca9050b TW |
595 | * @returns |
596 | * 0 on success | |
597 | * -ENOENT when me client is not found | |
598 | * -EINVAL when ctrl credits are <= 0 | |
599 | */ | |
90e0b5f1 | 600 | int mei_cl_flow_ctrl_reduce(struct mei_cl *cl) |
9ca9050b | 601 | { |
90e0b5f1 | 602 | struct mei_device *dev; |
9ca9050b TW |
603 | int i; |
604 | ||
90e0b5f1 TW |
605 | if (WARN_ON(!cl || !cl->dev)) |
606 | return -EINVAL; | |
607 | ||
608 | dev = cl->dev; | |
609 | ||
9ca9050b TW |
610 | if (!dev->me_clients_num) |
611 | return -ENOENT; | |
612 | ||
613 | for (i = 0; i < dev->me_clients_num; i++) { | |
614 | struct mei_me_client *me_cl = &dev->me_clients[i]; | |
615 | if (me_cl->client_id == cl->me_client_id) { | |
616 | if (me_cl->props.single_recv_buf != 0) { | |
617 | if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) | |
618 | return -EINVAL; | |
619 | dev->me_clients[i].mei_flow_ctrl_creds--; | |
620 | } else { | |
621 | if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) | |
622 | return -EINVAL; | |
623 | cl->mei_flow_ctrl_creds--; | |
624 | } | |
625 | return 0; | |
626 | } | |
627 | } | |
628 | return -ENOENT; | |
629 | } | |
630 | ||
ab841160 | 631 | /** |
393b148f | 632 | * mei_cl_read_start - the start read client message function. |
ab841160 | 633 | * |
90e0b5f1 | 634 | * @cl: host client |
ab841160 OW |
635 | * |
636 | * returns 0 on success, <0 on failure. | |
637 | */ | |
fcb136e1 | 638 | int mei_cl_read_start(struct mei_cl *cl, size_t length) |
ab841160 | 639 | { |
90e0b5f1 | 640 | struct mei_device *dev; |
ab841160 | 641 | struct mei_cl_cb *cb; |
664df38b | 642 | int rets; |
ab841160 OW |
643 | int i; |
644 | ||
90e0b5f1 TW |
645 | if (WARN_ON(!cl || !cl->dev)) |
646 | return -ENODEV; | |
647 | ||
648 | dev = cl->dev; | |
649 | ||
b950ac1d | 650 | if (!mei_cl_is_connected(cl)) |
ab841160 OW |
651 | return -ENODEV; |
652 | ||
d91aaed3 | 653 | if (cl->read_cb) { |
c0abffbd | 654 | cl_dbg(dev, cl, "read is pending.\n"); |
ab841160 OW |
655 | return -EBUSY; |
656 | } | |
664df38b TW |
657 | i = mei_me_cl_by_id(dev, cl->me_client_id); |
658 | if (i < 0) { | |
c0abffbd | 659 | cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); |
664df38b TW |
660 | return -ENODEV; |
661 | } | |
ab841160 | 662 | |
664df38b | 663 | cb = mei_io_cb_init(cl, NULL); |
ab841160 OW |
664 | if (!cb) |
665 | return -ENOMEM; | |
666 | ||
fcb136e1 TW |
667 | /* always allocate at least client max message */ |
668 | length = max_t(size_t, length, dev->me_clients[i].props.max_msg_length); | |
669 | rets = mei_io_cb_alloc_resp_buf(cb, length); | |
664df38b TW |
670 | if (rets) |
671 | goto err; | |
ab841160 | 672 | |
4b8960b4 | 673 | cb->fop_type = MEI_FOP_READ; |
ab841160 | 674 | cl->read_cb = cb; |
330dd7da TW |
675 | if (dev->hbuf_is_ready) { |
676 | dev->hbuf_is_ready = false; | |
8120e720 | 677 | if (mei_hbm_cl_flow_control_req(dev, cl)) { |
c0abffbd | 678 | cl_err(dev, cl, "flow control send failed\n"); |
ab841160 | 679 | rets = -ENODEV; |
664df38b | 680 | goto err; |
ab841160 | 681 | } |
fb601adb | 682 | list_add_tail(&cb->list, &dev->read_list.list); |
ab841160 | 683 | } else { |
fb601adb | 684 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); |
ab841160 OW |
685 | } |
686 | return rets; | |
664df38b | 687 | err: |
601a1efa | 688 | mei_io_cb_free(cb); |
ab841160 OW |
689 | return rets; |
690 | } | |
691 | ||
21767546 TW |
692 | /** |
693 | * mei_cl_irq_write_complete - write a message to device | |
694 | * from the interrupt thread context | |
695 | * | |
696 | * @cl: client | |
697 | * @cb: callback block. | |
698 | * @slots: free slots. | |
699 | * @cmpl_list: complete list. | |
700 | * | |
701 | * returns 0, OK; otherwise error. | |
702 | */ | |
703 | int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb, | |
704 | s32 *slots, struct mei_cl_cb *cmpl_list) | |
705 | { | |
136698e5 TW |
706 | struct mei_device *dev; |
707 | struct mei_msg_data *buf; | |
21767546 | 708 | struct mei_msg_hdr mei_hdr; |
136698e5 TW |
709 | size_t len; |
710 | u32 msg_slots; | |
2ebf8c94 | 711 | int rets; |
21767546 | 712 | |
136698e5 TW |
713 | |
714 | if (WARN_ON(!cl || !cl->dev)) | |
715 | return -ENODEV; | |
716 | ||
717 | dev = cl->dev; | |
718 | ||
719 | buf = &cb->request_buffer; | |
720 | ||
721 | rets = mei_cl_flow_ctrl_creds(cl); | |
722 | if (rets < 0) | |
723 | return rets; | |
724 | ||
725 | if (rets == 0) { | |
726 | cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); | |
727 | return 0; | |
728 | } | |
729 | ||
730 | len = buf->size - cb->buf_idx; | |
731 | msg_slots = mei_data2slots(len); | |
732 | ||
21767546 TW |
733 | mei_hdr.host_addr = cl->host_client_id; |
734 | mei_hdr.me_addr = cl->me_client_id; | |
735 | mei_hdr.reserved = 0; | |
736 | ||
737 | if (*slots >= msg_slots) { | |
738 | mei_hdr.length = len; | |
739 | mei_hdr.msg_complete = 1; | |
740 | /* Split the message only if we can write the whole host buffer */ | |
741 | } else if (*slots == dev->hbuf_depth) { | |
742 | msg_slots = *slots; | |
743 | len = (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); | |
744 | mei_hdr.length = len; | |
745 | mei_hdr.msg_complete = 0; | |
746 | } else { | |
747 | /* wait for next time the host buffer is empty */ | |
748 | return 0; | |
749 | } | |
750 | ||
c0abffbd | 751 | cl_dbg(dev, cl, "buf: size = %d idx = %lu\n", |
21767546 | 752 | cb->request_buffer.size, cb->buf_idx); |
21767546 TW |
753 | |
754 | *slots -= msg_slots; | |
136698e5 | 755 | rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx); |
2ebf8c94 TW |
756 | if (rets) { |
757 | cl->status = rets; | |
21767546 | 758 | list_move_tail(&cb->list, &cmpl_list->list); |
2ebf8c94 | 759 | return rets; |
21767546 TW |
760 | } |
761 | ||
762 | cl->status = 0; | |
4dfaa9f7 | 763 | cl->writing_state = MEI_WRITING; |
21767546 | 764 | cb->buf_idx += mei_hdr.length; |
4dfaa9f7 | 765 | |
21767546 TW |
766 | if (mei_hdr.msg_complete) { |
767 | if (mei_cl_flow_ctrl_reduce(cl)) | |
2ebf8c94 | 768 | return -EIO; |
21767546 TW |
769 | list_move_tail(&cb->list, &dev->write_waiting_list.list); |
770 | } | |
771 | ||
772 | return 0; | |
773 | } | |
774 | ||
4234a6de TW |
775 | /** |
776 | * mei_cl_write - submit a write cb to mei device | |
777 | assumes device_lock is locked | |
778 | * | |
779 | * @cl: host client | |
780 | * @cl: write callback with filled data | |
781 | * | |
782 | * returns numbe of bytes sent on success, <0 on failure. | |
783 | */ | |
784 | int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) | |
785 | { | |
786 | struct mei_device *dev; | |
787 | struct mei_msg_data *buf; | |
788 | struct mei_msg_hdr mei_hdr; | |
789 | int rets; | |
790 | ||
791 | ||
792 | if (WARN_ON(!cl || !cl->dev)) | |
793 | return -ENODEV; | |
794 | ||
795 | if (WARN_ON(!cb)) | |
796 | return -EINVAL; | |
797 | ||
798 | dev = cl->dev; | |
799 | ||
800 | ||
801 | buf = &cb->request_buffer; | |
802 | ||
c0abffbd | 803 | cl_dbg(dev, cl, "mei_cl_write %d\n", buf->size); |
4234a6de TW |
804 | |
805 | ||
806 | cb->fop_type = MEI_FOP_WRITE; | |
807 | ||
808 | rets = mei_cl_flow_ctrl_creds(cl); | |
809 | if (rets < 0) | |
810 | goto err; | |
811 | ||
812 | /* Host buffer is not ready, we queue the request */ | |
813 | if (rets == 0 || !dev->hbuf_is_ready) { | |
814 | cb->buf_idx = 0; | |
815 | /* unseting complete will enqueue the cb for write */ | |
816 | mei_hdr.msg_complete = 0; | |
4234a6de TW |
817 | rets = buf->size; |
818 | goto out; | |
819 | } | |
820 | ||
821 | dev->hbuf_is_ready = false; | |
822 | ||
823 | /* Check for a maximum length */ | |
824 | if (buf->size > mei_hbuf_max_len(dev)) { | |
825 | mei_hdr.length = mei_hbuf_max_len(dev); | |
826 | mei_hdr.msg_complete = 0; | |
827 | } else { | |
828 | mei_hdr.length = buf->size; | |
829 | mei_hdr.msg_complete = 1; | |
830 | } | |
831 | ||
832 | mei_hdr.host_addr = cl->host_client_id; | |
833 | mei_hdr.me_addr = cl->me_client_id; | |
834 | mei_hdr.reserved = 0; | |
835 | ||
4234a6de | 836 | |
2ebf8c94 TW |
837 | rets = mei_write_message(dev, &mei_hdr, buf->data); |
838 | if (rets) | |
4234a6de | 839 | goto err; |
4234a6de TW |
840 | |
841 | cl->writing_state = MEI_WRITING; | |
842 | cb->buf_idx = mei_hdr.length; | |
843 | ||
844 | rets = buf->size; | |
845 | out: | |
846 | if (mei_hdr.msg_complete) { | |
847 | if (mei_cl_flow_ctrl_reduce(cl)) { | |
848 | rets = -ENODEV; | |
849 | goto err; | |
850 | } | |
851 | list_add_tail(&cb->list, &dev->write_waiting_list.list); | |
852 | } else { | |
853 | list_add_tail(&cb->list, &dev->write_list.list); | |
854 | } | |
855 | ||
856 | ||
857 | if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) { | |
858 | ||
859 | mutex_unlock(&dev->device_lock); | |
860 | if (wait_event_interruptible(cl->tx_wait, | |
861 | cl->writing_state == MEI_WRITE_COMPLETE)) { | |
862 | if (signal_pending(current)) | |
863 | rets = -EINTR; | |
864 | else | |
865 | rets = -ERESTARTSYS; | |
866 | } | |
867 | mutex_lock(&dev->device_lock); | |
868 | } | |
869 | err: | |
870 | return rets; | |
871 | } | |
872 | ||
873 | ||
db086fa9 TW |
874 | /** |
875 | * mei_cl_complete - processes completed operation for a client | |
876 | * | |
877 | * @cl: private data of the file object. | |
878 | * @cb: callback block. | |
879 | */ | |
880 | void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) | |
881 | { | |
882 | if (cb->fop_type == MEI_FOP_WRITE) { | |
883 | mei_io_cb_free(cb); | |
884 | cb = NULL; | |
885 | cl->writing_state = MEI_WRITE_COMPLETE; | |
886 | if (waitqueue_active(&cl->tx_wait)) | |
887 | wake_up_interruptible(&cl->tx_wait); | |
888 | ||
889 | } else if (cb->fop_type == MEI_FOP_READ && | |
890 | MEI_READING == cl->reading_state) { | |
891 | cl->reading_state = MEI_READ_COMPLETE; | |
892 | if (waitqueue_active(&cl->rx_wait)) | |
893 | wake_up_interruptible(&cl->rx_wait); | |
894 | else | |
895 | mei_cl_bus_rx_event(cl); | |
896 | ||
897 | } | |
898 | } | |
899 | ||
4234a6de | 900 | |
074b4c01 TW |
901 | /** |
902 | * mei_cl_all_disconnect - disconnect forcefully all connected clients | |
903 | * | |
904 | * @dev - mei device | |
905 | */ | |
906 | ||
907 | void mei_cl_all_disconnect(struct mei_device *dev) | |
908 | { | |
909 | struct mei_cl *cl, *next; | |
910 | ||
911 | list_for_each_entry_safe(cl, next, &dev->file_list, link) { | |
912 | cl->state = MEI_FILE_DISCONNECTED; | |
913 | cl->mei_flow_ctrl_creds = 0; | |
914 | cl->read_cb = NULL; | |
915 | cl->timer_count = 0; | |
916 | } | |
917 | } | |
918 | ||
919 | ||
920 | /** | |
5290801c | 921 | * mei_cl_all_wakeup - wake up all readers and writers they can be interrupted |
074b4c01 TW |
922 | * |
923 | * @dev - mei device | |
924 | */ | |
5290801c | 925 | void mei_cl_all_wakeup(struct mei_device *dev) |
074b4c01 TW |
926 | { |
927 | struct mei_cl *cl, *next; | |
928 | list_for_each_entry_safe(cl, next, &dev->file_list, link) { | |
929 | if (waitqueue_active(&cl->rx_wait)) { | |
c0abffbd | 930 | cl_dbg(dev, cl, "Waking up reading client!\n"); |
074b4c01 TW |
931 | wake_up_interruptible(&cl->rx_wait); |
932 | } | |
5290801c | 933 | if (waitqueue_active(&cl->tx_wait)) { |
c0abffbd | 934 | cl_dbg(dev, cl, "Waking up writing client!\n"); |
5290801c TW |
935 | wake_up_interruptible(&cl->tx_wait); |
936 | } | |
074b4c01 TW |
937 | } |
938 | } | |
939 | ||
940 | /** | |
941 | * mei_cl_all_write_clear - clear all pending writes | |
942 | ||
943 | * @dev - mei device | |
944 | */ | |
945 | void mei_cl_all_write_clear(struct mei_device *dev) | |
946 | { | |
947 | struct mei_cl_cb *cb, *next; | |
948 | ||
949 | list_for_each_entry_safe(cb, next, &dev->write_list.list, list) { | |
950 | list_del(&cb->list); | |
951 | mei_io_cb_free(cb); | |
952 | } | |
953 | } | |
954 | ||
955 |