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