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