mirror of
https://github.com/torvalds/linux.git
synced 2024-12-18 09:02:17 +00:00
22f96a0eb6
Make open counter to be incremented and decremented from mei_cl_link and mei_cl_unlik function respectively Nfc was assuming symmetric linking API and thus open handler count was never decreased. This patch fixes that. We need to add separate open hander count for amthif which is handled out of link/unlink functions and doesn't break the symmetric API. Last we do not waste clients slots if amthif or wd are not present in the device. we don't need to allocates slots ahead it is all covered by link/unlink before the devices is responding to user space connection and thus not racing on allocation Signed-off-by: Tomas Winkler <tomas.winkler@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
967 lines
20 KiB
C
967 lines
20 KiB
C
/*
|
|
*
|
|
* Intel Management Engine Interface (Intel MEI) Linux driver
|
|
* Copyright (c) 2003-2012, Intel Corporation.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/pci.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/mei.h>
|
|
|
|
#include "mei_dev.h"
|
|
#include "hbm.h"
|
|
#include "client.h"
|
|
|
|
/**
|
|
* mei_me_cl_by_uuid - locate index of me client
|
|
*
|
|
* @dev: mei device
|
|
* returns me client index or -ENOENT if not found
|
|
*/
|
|
int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *uuid)
|
|
{
|
|
int i, res = -ENOENT;
|
|
|
|
for (i = 0; i < dev->me_clients_num; ++i)
|
|
if (uuid_le_cmp(*uuid,
|
|
dev->me_clients[i].props.protocol_name) == 0) {
|
|
res = i;
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* mei_me_cl_by_id return index to me_clients for client_id
|
|
*
|
|
* @dev: the device structure
|
|
* @client_id: me client id
|
|
*
|
|
* Locking: called under "dev->device_lock" lock
|
|
*
|
|
* returns index on success, -ENOENT on failure.
|
|
*/
|
|
|
|
int mei_me_cl_by_id(struct mei_device *dev, u8 client_id)
|
|
{
|
|
int i;
|
|
for (i = 0; i < dev->me_clients_num; i++)
|
|
if (dev->me_clients[i].client_id == client_id)
|
|
break;
|
|
if (WARN_ON(dev->me_clients[i].client_id != client_id))
|
|
return -ENOENT;
|
|
|
|
if (i == dev->me_clients_num)
|
|
return -ENOENT;
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
/**
|
|
* mei_io_list_flush - removes list entry belonging to cl.
|
|
*
|
|
* @list: An instance of our list structure
|
|
* @cl: host client
|
|
*/
|
|
void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl)
|
|
{
|
|
struct mei_cl_cb *cb;
|
|
struct mei_cl_cb *next;
|
|
|
|
list_for_each_entry_safe(cb, next, &list->list, list) {
|
|
if (cb->cl && mei_cl_cmp_id(cl, cb->cl))
|
|
list_del(&cb->list);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* mei_io_cb_free - free mei_cb_private related memory
|
|
*
|
|
* @cb: mei callback struct
|
|
*/
|
|
void mei_io_cb_free(struct mei_cl_cb *cb)
|
|
{
|
|
if (cb == NULL)
|
|
return;
|
|
|
|
kfree(cb->request_buffer.data);
|
|
kfree(cb->response_buffer.data);
|
|
kfree(cb);
|
|
}
|
|
|
|
/**
|
|
* mei_io_cb_init - allocate and initialize io callback
|
|
*
|
|
* @cl - mei client
|
|
* @fp: pointer to file structure
|
|
*
|
|
* returns mei_cl_cb pointer or NULL;
|
|
*/
|
|
struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp)
|
|
{
|
|
struct mei_cl_cb *cb;
|
|
|
|
cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL);
|
|
if (!cb)
|
|
return NULL;
|
|
|
|
mei_io_list_init(cb);
|
|
|
|
cb->file_object = fp;
|
|
cb->cl = cl;
|
|
cb->buf_idx = 0;
|
|
return cb;
|
|
}
|
|
|
|
/**
|
|
* mei_io_cb_alloc_req_buf - allocate request buffer
|
|
*
|
|
* @cb: io callback structure
|
|
* @length: size of the buffer
|
|
*
|
|
* returns 0 on success
|
|
* -EINVAL if cb is NULL
|
|
* -ENOMEM if allocation failed
|
|
*/
|
|
int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length)
|
|
{
|
|
if (!cb)
|
|
return -EINVAL;
|
|
|
|
if (length == 0)
|
|
return 0;
|
|
|
|
cb->request_buffer.data = kmalloc(length, GFP_KERNEL);
|
|
if (!cb->request_buffer.data)
|
|
return -ENOMEM;
|
|
cb->request_buffer.size = length;
|
|
return 0;
|
|
}
|
|
/**
|
|
* mei_io_cb_alloc_resp_buf - allocate respose buffer
|
|
*
|
|
* @cb: io callback structure
|
|
* @length: size of the buffer
|
|
*
|
|
* returns 0 on success
|
|
* -EINVAL if cb is NULL
|
|
* -ENOMEM if allocation failed
|
|
*/
|
|
int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length)
|
|
{
|
|
if (!cb)
|
|
return -EINVAL;
|
|
|
|
if (length == 0)
|
|
return 0;
|
|
|
|
cb->response_buffer.data = kmalloc(length, GFP_KERNEL);
|
|
if (!cb->response_buffer.data)
|
|
return -ENOMEM;
|
|
cb->response_buffer.size = length;
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* mei_cl_flush_queues - flushes queue lists belonging to cl.
|
|
*
|
|
* @cl: host client
|
|
*/
|
|
int mei_cl_flush_queues(struct mei_cl *cl)
|
|
{
|
|
struct mei_device *dev;
|
|
|
|
if (WARN_ON(!cl || !cl->dev))
|
|
return -EINVAL;
|
|
|
|
dev = cl->dev;
|
|
|
|
cl_dbg(dev, cl, "remove list entry belonging to cl\n");
|
|
mei_io_list_flush(&cl->dev->read_list, cl);
|
|
mei_io_list_flush(&cl->dev->write_list, cl);
|
|
mei_io_list_flush(&cl->dev->write_waiting_list, cl);
|
|
mei_io_list_flush(&cl->dev->ctrl_wr_list, cl);
|
|
mei_io_list_flush(&cl->dev->ctrl_rd_list, cl);
|
|
mei_io_list_flush(&cl->dev->amthif_cmd_list, cl);
|
|
mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* mei_cl_init - initializes intialize cl.
|
|
*
|
|
* @cl: host client to be initialized
|
|
* @dev: mei device
|
|
*/
|
|
void mei_cl_init(struct mei_cl *cl, struct mei_device *dev)
|
|
{
|
|
memset(cl, 0, sizeof(struct mei_cl));
|
|
init_waitqueue_head(&cl->wait);
|
|
init_waitqueue_head(&cl->rx_wait);
|
|
init_waitqueue_head(&cl->tx_wait);
|
|
INIT_LIST_HEAD(&cl->link);
|
|
INIT_LIST_HEAD(&cl->device_link);
|
|
cl->reading_state = MEI_IDLE;
|
|
cl->writing_state = MEI_IDLE;
|
|
cl->dev = dev;
|
|
}
|
|
|
|
/**
|
|
* mei_cl_allocate - allocates cl structure and sets it up.
|
|
*
|
|
* @dev: mei device
|
|
* returns The allocated file or NULL on failure
|
|
*/
|
|
struct mei_cl *mei_cl_allocate(struct mei_device *dev)
|
|
{
|
|
struct mei_cl *cl;
|
|
|
|
cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL);
|
|
if (!cl)
|
|
return NULL;
|
|
|
|
mei_cl_init(cl, dev);
|
|
|
|
return cl;
|
|
}
|
|
|
|
/**
|
|
* mei_cl_find_read_cb - find this cl's callback in the read list
|
|
*
|
|
* @cl: host client
|
|
*
|
|
* returns cb on success, NULL on error
|
|
*/
|
|
struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl)
|
|
{
|
|
struct mei_device *dev = cl->dev;
|
|
struct mei_cl_cb *cb = NULL;
|
|
struct mei_cl_cb *next = NULL;
|
|
|
|
list_for_each_entry_safe(cb, next, &dev->read_list.list, list)
|
|
if (mei_cl_cmp_id(cl, cb->cl))
|
|
return cb;
|
|
return NULL;
|
|
}
|
|
|
|
/** mei_cl_link: allocte host id in the host map
|
|
*
|
|
* @cl - host client
|
|
* @id - fixed host id or -1 for genereting one
|
|
*
|
|
* returns 0 on success
|
|
* -EINVAL on incorrect values
|
|
* -ENONET if client not found
|
|
*/
|
|
int mei_cl_link(struct mei_cl *cl, int id)
|
|
{
|
|
struct mei_device *dev;
|
|
long open_handle_count;
|
|
|
|
if (WARN_ON(!cl || !cl->dev))
|
|
return -EINVAL;
|
|
|
|
dev = cl->dev;
|
|
|
|
/* If Id is not asigned get one*/
|
|
if (id == MEI_HOST_CLIENT_ID_ANY)
|
|
id = find_first_zero_bit(dev->host_clients_map,
|
|
MEI_CLIENTS_MAX);
|
|
|
|
if (id >= MEI_CLIENTS_MAX) {
|
|
dev_err(&dev->pdev->dev, "id exceded %d", MEI_CLIENTS_MAX) ;
|
|
return -EMFILE;
|
|
}
|
|
|
|
open_handle_count = dev->open_handle_count + dev->iamthif_open_count;
|
|
if (open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) {
|
|
dev_err(&dev->pdev->dev, "open_handle_count exceded %d",
|
|
MEI_MAX_OPEN_HANDLE_COUNT);
|
|
return -EMFILE;
|
|
}
|
|
|
|
if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) {
|
|
dev_err(&dev->pdev->dev, "open_handle_count exceded %d",
|
|
MEI_MAX_OPEN_HANDLE_COUNT);
|
|
return -ENOENT;
|
|
}
|
|
|
|
dev->open_handle_count++;
|
|
|
|
cl->host_client_id = id;
|
|
list_add_tail(&cl->link, &dev->file_list);
|
|
|
|
set_bit(id, dev->host_clients_map);
|
|
|
|
cl->state = MEI_FILE_INITIALIZING;
|
|
|
|
cl_dbg(dev, cl, "link cl\n");
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* mei_cl_unlink - remove me_cl from the list
|
|
*
|
|
* @cl: host client
|
|
*/
|
|
int mei_cl_unlink(struct mei_cl *cl)
|
|
{
|
|
struct mei_device *dev;
|
|
|
|
/* don't shout on error exit path */
|
|
if (!cl)
|
|
return 0;
|
|
|
|
/* wd and amthif might not be initialized */
|
|
if (!cl->dev)
|
|
return 0;
|
|
|
|
dev = cl->dev;
|
|
|
|
cl_dbg(dev, cl, "unlink client");
|
|
|
|
if (dev->open_handle_count > 0)
|
|
dev->open_handle_count--;
|
|
|
|
/* never clear the 0 bit */
|
|
if (cl->host_client_id)
|
|
clear_bit(cl->host_client_id, dev->host_clients_map);
|
|
|
|
list_del_init(&cl->link);
|
|
|
|
cl->state = MEI_FILE_INITIALIZING;
|
|
|
|
list_del_init(&cl->link);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void mei_host_client_init(struct work_struct *work)
|
|
{
|
|
struct mei_device *dev = container_of(work,
|
|
struct mei_device, init_work);
|
|
struct mei_client_properties *client_props;
|
|
int i;
|
|
|
|
mutex_lock(&dev->device_lock);
|
|
|
|
bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX);
|
|
dev->open_handle_count = 0;
|
|
|
|
/*
|
|
* Reserving the first three client IDs
|
|
* 0: Reserved for MEI Bus Message communications
|
|
*/
|
|
bitmap_set(dev->host_clients_map, 0, 1);
|
|
|
|
for (i = 0; i < dev->me_clients_num; i++) {
|
|
client_props = &dev->me_clients[i].props;
|
|
|
|
if (!uuid_le_cmp(client_props->protocol_name, mei_amthif_guid))
|
|
mei_amthif_host_init(dev);
|
|
else if (!uuid_le_cmp(client_props->protocol_name, mei_wd_guid))
|
|
mei_wd_host_init(dev);
|
|
else if (!uuid_le_cmp(client_props->protocol_name, mei_nfc_guid))
|
|
mei_nfc_host_init(dev);
|
|
|
|
}
|
|
|
|
dev->dev_state = MEI_DEV_ENABLED;
|
|
|
|
mutex_unlock(&dev->device_lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* mei_cl_disconnect - disconnect host clinet form the me one
|
|
*
|
|
* @cl: host client
|
|
*
|
|
* Locking: called under "dev->device_lock" lock
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int mei_cl_disconnect(struct mei_cl *cl)
|
|
{
|
|
struct mei_device *dev;
|
|
struct mei_cl_cb *cb;
|
|
int rets, err;
|
|
|
|
if (WARN_ON(!cl || !cl->dev))
|
|
return -ENODEV;
|
|
|
|
dev = cl->dev;
|
|
|
|
cl_dbg(dev, cl, "disconnecting");
|
|
|
|
if (cl->state != MEI_FILE_DISCONNECTING)
|
|
return 0;
|
|
|
|
cb = mei_io_cb_init(cl, NULL);
|
|
if (!cb)
|
|
return -ENOMEM;
|
|
|
|
cb->fop_type = MEI_FOP_CLOSE;
|
|
if (dev->hbuf_is_ready) {
|
|
dev->hbuf_is_ready = false;
|
|
if (mei_hbm_cl_disconnect_req(dev, cl)) {
|
|
rets = -ENODEV;
|
|
cl_err(dev, cl, "failed to disconnect.\n");
|
|
goto free;
|
|
}
|
|
mdelay(10); /* Wait for hardware disconnection ready */
|
|
list_add_tail(&cb->list, &dev->ctrl_rd_list.list);
|
|
} else {
|
|
cl_dbg(dev, cl, "add disconnect cb to control write list\n");
|
|
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
|
|
|
|
}
|
|
mutex_unlock(&dev->device_lock);
|
|
|
|
err = wait_event_timeout(dev->wait_recvd_msg,
|
|
MEI_FILE_DISCONNECTED == cl->state,
|
|
mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
|
|
|
|
mutex_lock(&dev->device_lock);
|
|
if (MEI_FILE_DISCONNECTED == cl->state) {
|
|
rets = 0;
|
|
cl_dbg(dev, cl, "successfully disconnected from FW client.\n");
|
|
} else {
|
|
rets = -ENODEV;
|
|
if (MEI_FILE_DISCONNECTED != cl->state)
|
|
cl_err(dev, cl, "wrong status client disconnect.\n");
|
|
|
|
if (err)
|
|
cl_dbg(dev, cl, "wait failed disconnect err=%08x\n",
|
|
err);
|
|
|
|
cl_err(dev, cl, "failed to disconnect from FW client.\n");
|
|
}
|
|
|
|
mei_io_list_flush(&dev->ctrl_rd_list, cl);
|
|
mei_io_list_flush(&dev->ctrl_wr_list, cl);
|
|
free:
|
|
mei_io_cb_free(cb);
|
|
return rets;
|
|
}
|
|
|
|
|
|
/**
|
|
* mei_cl_is_other_connecting - checks if other
|
|
* client with the same me client id is connecting
|
|
*
|
|
* @cl: private data of the file object
|
|
*
|
|
* returns ture if other client is connected, 0 - otherwise.
|
|
*/
|
|
bool mei_cl_is_other_connecting(struct mei_cl *cl)
|
|
{
|
|
struct mei_device *dev;
|
|
struct mei_cl *pos;
|
|
struct mei_cl *next;
|
|
|
|
if (WARN_ON(!cl || !cl->dev))
|
|
return false;
|
|
|
|
dev = cl->dev;
|
|
|
|
list_for_each_entry_safe(pos, next, &dev->file_list, link) {
|
|
if ((pos->state == MEI_FILE_CONNECTING) &&
|
|
(pos != cl) && cl->me_client_id == pos->me_client_id)
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* mei_cl_connect - connect host clinet to the me one
|
|
*
|
|
* @cl: host client
|
|
*
|
|
* Locking: called under "dev->device_lock" lock
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int mei_cl_connect(struct mei_cl *cl, struct file *file)
|
|
{
|
|
struct mei_device *dev;
|
|
struct mei_cl_cb *cb;
|
|
int rets;
|
|
|
|
if (WARN_ON(!cl || !cl->dev))
|
|
return -ENODEV;
|
|
|
|
dev = cl->dev;
|
|
|
|
cb = mei_io_cb_init(cl, file);
|
|
if (!cb) {
|
|
rets = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
cb->fop_type = MEI_FOP_IOCTL;
|
|
|
|
if (dev->hbuf_is_ready && !mei_cl_is_other_connecting(cl)) {
|
|
dev->hbuf_is_ready = false;
|
|
|
|
if (mei_hbm_cl_connect_req(dev, cl)) {
|
|
rets = -ENODEV;
|
|
goto out;
|
|
}
|
|
cl->timer_count = MEI_CONNECT_TIMEOUT;
|
|
list_add_tail(&cb->list, &dev->ctrl_rd_list.list);
|
|
} else {
|
|
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
|
|
}
|
|
|
|
mutex_unlock(&dev->device_lock);
|
|
rets = wait_event_timeout(dev->wait_recvd_msg,
|
|
(cl->state == MEI_FILE_CONNECTED ||
|
|
cl->state == MEI_FILE_DISCONNECTED),
|
|
mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
|
|
mutex_lock(&dev->device_lock);
|
|
|
|
if (cl->state != MEI_FILE_CONNECTED) {
|
|
rets = -EFAULT;
|
|
|
|
mei_io_list_flush(&dev->ctrl_rd_list, cl);
|
|
mei_io_list_flush(&dev->ctrl_wr_list, cl);
|
|
goto out;
|
|
}
|
|
|
|
rets = cl->status;
|
|
|
|
out:
|
|
mei_io_cb_free(cb);
|
|
return rets;
|
|
}
|
|
|
|
/**
|
|
* mei_cl_flow_ctrl_creds - checks flow_control credits for cl.
|
|
*
|
|
* @cl: private data of the file object
|
|
*
|
|
* returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise.
|
|
* -ENOENT if mei_cl is not present
|
|
* -EINVAL if single_recv_buf == 0
|
|
*/
|
|
int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
|
|
{
|
|
struct mei_device *dev;
|
|
int i;
|
|
|
|
if (WARN_ON(!cl || !cl->dev))
|
|
return -EINVAL;
|
|
|
|
dev = cl->dev;
|
|
|
|
if (!dev->me_clients_num)
|
|
return 0;
|
|
|
|
if (cl->mei_flow_ctrl_creds > 0)
|
|
return 1;
|
|
|
|
for (i = 0; i < dev->me_clients_num; i++) {
|
|
struct mei_me_client *me_cl = &dev->me_clients[i];
|
|
if (me_cl->client_id == cl->me_client_id) {
|
|
if (me_cl->mei_flow_ctrl_creds) {
|
|
if (WARN_ON(me_cl->props.single_recv_buf == 0))
|
|
return -EINVAL;
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return -ENOENT;
|
|
}
|
|
|
|
/**
|
|
* mei_cl_flow_ctrl_reduce - reduces flow_control.
|
|
*
|
|
* @cl: private data of the file object
|
|
*
|
|
* @returns
|
|
* 0 on success
|
|
* -ENOENT when me client is not found
|
|
* -EINVAL when ctrl credits are <= 0
|
|
*/
|
|
int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
|
|
{
|
|
struct mei_device *dev;
|
|
int i;
|
|
|
|
if (WARN_ON(!cl || !cl->dev))
|
|
return -EINVAL;
|
|
|
|
dev = cl->dev;
|
|
|
|
if (!dev->me_clients_num)
|
|
return -ENOENT;
|
|
|
|
for (i = 0; i < dev->me_clients_num; i++) {
|
|
struct mei_me_client *me_cl = &dev->me_clients[i];
|
|
if (me_cl->client_id == cl->me_client_id) {
|
|
if (me_cl->props.single_recv_buf != 0) {
|
|
if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0))
|
|
return -EINVAL;
|
|
dev->me_clients[i].mei_flow_ctrl_creds--;
|
|
} else {
|
|
if (WARN_ON(cl->mei_flow_ctrl_creds <= 0))
|
|
return -EINVAL;
|
|
cl->mei_flow_ctrl_creds--;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
return -ENOENT;
|
|
}
|
|
|
|
/**
|
|
* mei_cl_read_start - the start read client message function.
|
|
*
|
|
* @cl: host client
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int mei_cl_read_start(struct mei_cl *cl, size_t length)
|
|
{
|
|
struct mei_device *dev;
|
|
struct mei_cl_cb *cb;
|
|
int rets;
|
|
int i;
|
|
|
|
if (WARN_ON(!cl || !cl->dev))
|
|
return -ENODEV;
|
|
|
|
dev = cl->dev;
|
|
|
|
if (!mei_cl_is_connected(cl))
|
|
return -ENODEV;
|
|
|
|
if (cl->read_cb) {
|
|
cl_dbg(dev, cl, "read is pending.\n");
|
|
return -EBUSY;
|
|
}
|
|
i = mei_me_cl_by_id(dev, cl->me_client_id);
|
|
if (i < 0) {
|
|
cl_err(dev, cl, "no such me client %d\n", cl->me_client_id);
|
|
return -ENODEV;
|
|
}
|
|
|
|
cb = mei_io_cb_init(cl, NULL);
|
|
if (!cb)
|
|
return -ENOMEM;
|
|
|
|
/* always allocate at least client max message */
|
|
length = max_t(size_t, length, dev->me_clients[i].props.max_msg_length);
|
|
rets = mei_io_cb_alloc_resp_buf(cb, length);
|
|
if (rets)
|
|
goto err;
|
|
|
|
cb->fop_type = MEI_FOP_READ;
|
|
cl->read_cb = cb;
|
|
if (dev->hbuf_is_ready) {
|
|
dev->hbuf_is_ready = false;
|
|
if (mei_hbm_cl_flow_control_req(dev, cl)) {
|
|
cl_err(dev, cl, "flow control send failed\n");
|
|
rets = -ENODEV;
|
|
goto err;
|
|
}
|
|
list_add_tail(&cb->list, &dev->read_list.list);
|
|
} else {
|
|
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
|
|
}
|
|
return rets;
|
|
err:
|
|
mei_io_cb_free(cb);
|
|
return rets;
|
|
}
|
|
|
|
/**
|
|
* mei_cl_irq_write_complete - write a message to device
|
|
* from the interrupt thread context
|
|
*
|
|
* @cl: client
|
|
* @cb: callback block.
|
|
* @slots: free slots.
|
|
* @cmpl_list: complete list.
|
|
*
|
|
* returns 0, OK; otherwise error.
|
|
*/
|
|
int mei_cl_irq_write_complete(struct mei_cl *cl, struct mei_cl_cb *cb,
|
|
s32 *slots, struct mei_cl_cb *cmpl_list)
|
|
{
|
|
struct mei_device *dev;
|
|
struct mei_msg_data *buf;
|
|
struct mei_msg_hdr mei_hdr;
|
|
size_t len;
|
|
u32 msg_slots;
|
|
int rets;
|
|
|
|
|
|
if (WARN_ON(!cl || !cl->dev))
|
|
return -ENODEV;
|
|
|
|
dev = cl->dev;
|
|
|
|
buf = &cb->request_buffer;
|
|
|
|
rets = mei_cl_flow_ctrl_creds(cl);
|
|
if (rets < 0)
|
|
return rets;
|
|
|
|
if (rets == 0) {
|
|
cl_dbg(dev, cl, "No flow control credentials: not sending.\n");
|
|
return 0;
|
|
}
|
|
|
|
len = buf->size - cb->buf_idx;
|
|
msg_slots = mei_data2slots(len);
|
|
|
|
mei_hdr.host_addr = cl->host_client_id;
|
|
mei_hdr.me_addr = cl->me_client_id;
|
|
mei_hdr.reserved = 0;
|
|
|
|
if (*slots >= msg_slots) {
|
|
mei_hdr.length = len;
|
|
mei_hdr.msg_complete = 1;
|
|
/* Split the message only if we can write the whole host buffer */
|
|
} else if (*slots == dev->hbuf_depth) {
|
|
msg_slots = *slots;
|
|
len = (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr);
|
|
mei_hdr.length = len;
|
|
mei_hdr.msg_complete = 0;
|
|
} else {
|
|
/* wait for next time the host buffer is empty */
|
|
return 0;
|
|
}
|
|
|
|
cl_dbg(dev, cl, "buf: size = %d idx = %lu\n",
|
|
cb->request_buffer.size, cb->buf_idx);
|
|
|
|
*slots -= msg_slots;
|
|
rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx);
|
|
if (rets) {
|
|
cl->status = rets;
|
|
list_move_tail(&cb->list, &cmpl_list->list);
|
|
return rets;
|
|
}
|
|
|
|
cl->status = 0;
|
|
cl->writing_state = MEI_WRITING;
|
|
cb->buf_idx += mei_hdr.length;
|
|
|
|
if (mei_hdr.msg_complete) {
|
|
if (mei_cl_flow_ctrl_reduce(cl))
|
|
return -EIO;
|
|
list_move_tail(&cb->list, &dev->write_waiting_list.list);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* mei_cl_write - submit a write cb to mei device
|
|
assumes device_lock is locked
|
|
*
|
|
* @cl: host client
|
|
* @cl: write callback with filled data
|
|
*
|
|
* returns numbe of bytes sent on success, <0 on failure.
|
|
*/
|
|
int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
|
|
{
|
|
struct mei_device *dev;
|
|
struct mei_msg_data *buf;
|
|
struct mei_msg_hdr mei_hdr;
|
|
int rets;
|
|
|
|
|
|
if (WARN_ON(!cl || !cl->dev))
|
|
return -ENODEV;
|
|
|
|
if (WARN_ON(!cb))
|
|
return -EINVAL;
|
|
|
|
dev = cl->dev;
|
|
|
|
|
|
buf = &cb->request_buffer;
|
|
|
|
cl_dbg(dev, cl, "mei_cl_write %d\n", buf->size);
|
|
|
|
|
|
cb->fop_type = MEI_FOP_WRITE;
|
|
|
|
rets = mei_cl_flow_ctrl_creds(cl);
|
|
if (rets < 0)
|
|
goto err;
|
|
|
|
/* Host buffer is not ready, we queue the request */
|
|
if (rets == 0 || !dev->hbuf_is_ready) {
|
|
cb->buf_idx = 0;
|
|
/* unseting complete will enqueue the cb for write */
|
|
mei_hdr.msg_complete = 0;
|
|
rets = buf->size;
|
|
goto out;
|
|
}
|
|
|
|
dev->hbuf_is_ready = false;
|
|
|
|
/* Check for a maximum length */
|
|
if (buf->size > mei_hbuf_max_len(dev)) {
|
|
mei_hdr.length = mei_hbuf_max_len(dev);
|
|
mei_hdr.msg_complete = 0;
|
|
} else {
|
|
mei_hdr.length = buf->size;
|
|
mei_hdr.msg_complete = 1;
|
|
}
|
|
|
|
mei_hdr.host_addr = cl->host_client_id;
|
|
mei_hdr.me_addr = cl->me_client_id;
|
|
mei_hdr.reserved = 0;
|
|
|
|
|
|
rets = mei_write_message(dev, &mei_hdr, buf->data);
|
|
if (rets)
|
|
goto err;
|
|
|
|
cl->writing_state = MEI_WRITING;
|
|
cb->buf_idx = mei_hdr.length;
|
|
|
|
rets = buf->size;
|
|
out:
|
|
if (mei_hdr.msg_complete) {
|
|
if (mei_cl_flow_ctrl_reduce(cl)) {
|
|
rets = -ENODEV;
|
|
goto err;
|
|
}
|
|
list_add_tail(&cb->list, &dev->write_waiting_list.list);
|
|
} else {
|
|
list_add_tail(&cb->list, &dev->write_list.list);
|
|
}
|
|
|
|
|
|
if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) {
|
|
|
|
mutex_unlock(&dev->device_lock);
|
|
if (wait_event_interruptible(cl->tx_wait,
|
|
cl->writing_state == MEI_WRITE_COMPLETE)) {
|
|
if (signal_pending(current))
|
|
rets = -EINTR;
|
|
else
|
|
rets = -ERESTARTSYS;
|
|
}
|
|
mutex_lock(&dev->device_lock);
|
|
}
|
|
err:
|
|
return rets;
|
|
}
|
|
|
|
|
|
/**
|
|
* mei_cl_complete - processes completed operation for a client
|
|
*
|
|
* @cl: private data of the file object.
|
|
* @cb: callback block.
|
|
*/
|
|
void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
|
|
{
|
|
if (cb->fop_type == MEI_FOP_WRITE) {
|
|
mei_io_cb_free(cb);
|
|
cb = NULL;
|
|
cl->writing_state = MEI_WRITE_COMPLETE;
|
|
if (waitqueue_active(&cl->tx_wait))
|
|
wake_up_interruptible(&cl->tx_wait);
|
|
|
|
} else if (cb->fop_type == MEI_FOP_READ &&
|
|
MEI_READING == cl->reading_state) {
|
|
cl->reading_state = MEI_READ_COMPLETE;
|
|
if (waitqueue_active(&cl->rx_wait))
|
|
wake_up_interruptible(&cl->rx_wait);
|
|
else
|
|
mei_cl_bus_rx_event(cl);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* mei_cl_all_disconnect - disconnect forcefully all connected clients
|
|
*
|
|
* @dev - mei device
|
|
*/
|
|
|
|
void mei_cl_all_disconnect(struct mei_device *dev)
|
|
{
|
|
struct mei_cl *cl, *next;
|
|
|
|
list_for_each_entry_safe(cl, next, &dev->file_list, link) {
|
|
cl->state = MEI_FILE_DISCONNECTED;
|
|
cl->mei_flow_ctrl_creds = 0;
|
|
cl->read_cb = NULL;
|
|
cl->timer_count = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* mei_cl_all_wakeup - wake up all readers and writers they can be interrupted
|
|
*
|
|
* @dev - mei device
|
|
*/
|
|
void mei_cl_all_wakeup(struct mei_device *dev)
|
|
{
|
|
struct mei_cl *cl, *next;
|
|
list_for_each_entry_safe(cl, next, &dev->file_list, link) {
|
|
if (waitqueue_active(&cl->rx_wait)) {
|
|
cl_dbg(dev, cl, "Waking up reading client!\n");
|
|
wake_up_interruptible(&cl->rx_wait);
|
|
}
|
|
if (waitqueue_active(&cl->tx_wait)) {
|
|
cl_dbg(dev, cl, "Waking up writing client!\n");
|
|
wake_up_interruptible(&cl->tx_wait);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* mei_cl_all_write_clear - clear all pending writes
|
|
|
|
* @dev - mei device
|
|
*/
|
|
void mei_cl_all_write_clear(struct mei_device *dev)
|
|
{
|
|
struct mei_cl_cb *cb, *next;
|
|
|
|
list_for_each_entry_safe(cb, next, &dev->write_list.list, list) {
|
|
list_del(&cb->list);
|
|
mei_io_cb_free(cb);
|
|
}
|
|
}
|
|
|
|
|