![Greg Kroah-Hartman](/assets/img/avatar_default.png)
This resolves a lot of the more obvious sparse warnings in the code. There still are some major problems in the ioctl handlers dealing with user and kernel pointers that this patch does not resolve, that needs to be addressed still. Also, the locking seems to be a bit strange in places, which sparse points out, that too need to be resolved. Cc: Anas Nashif <anas.nashif@intel.com> Cc: Marcin Obara <marcin.obara@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
848 lines
23 KiB
C
848 lines
23 KiB
C
/*
|
|
* Part of Intel(R) Manageability Engine Interface Linux driver
|
|
*
|
|
* Copyright (c) 2003 - 2008 Intel Corp.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions, and the following disclaimer,
|
|
* without modification.
|
|
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
|
|
* substantially similar to the "NO WARRANTY" disclaimer below
|
|
* ("Disclaimer") and any redistribution must be conditioned upon
|
|
* including a substantially similar Disclaimer requirement for further
|
|
* binary redistribution.
|
|
* 3. Neither the names of the above-listed copyright holders nor the names
|
|
* of any contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* Alternatively, this software may be distributed under the terms of the
|
|
* GNU General Public License ("GPL") version 2 as published by the Free
|
|
* Software Foundation.
|
|
*
|
|
* NO WARRANTY
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGES.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/types.h>
|
|
#include <linux/fcntl.h>
|
|
#include <linux/aio.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kdev_t.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/list.h>
|
|
#include <linux/unistd.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include "heci_data_structures.h"
|
|
#include "heci.h"
|
|
#include "heci_interface.h"
|
|
#include "heci_version.h"
|
|
|
|
|
|
/**
|
|
* heci_ioctl_get_version - the get driver version IOCTL function
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @if_num: minor number
|
|
* @*u_msg: pointer to user data struct in user space
|
|
* @k_msg: data in kernel on the stack
|
|
* @file_ext: private data of the file object
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int heci_ioctl_get_version(struct iamt_heci_device *dev, int if_num,
|
|
struct heci_message_data __user *u_msg,
|
|
struct heci_message_data k_msg,
|
|
struct heci_file_private *file_ext)
|
|
{
|
|
int rets = 0;
|
|
struct heci_driver_version *version;
|
|
struct heci_message_data res_msg;
|
|
|
|
if ((if_num != HECI_MINOR_NUMBER) || (!dev)
|
|
|| (!file_ext))
|
|
return -ENODEV;
|
|
|
|
if (k_msg.size < (sizeof(struct heci_driver_version) - 2)) {
|
|
DBG("user buffer less than heci_driver_version.\n");
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
res_msg.data = kmalloc(sizeof(struct heci_driver_version), GFP_KERNEL);
|
|
if (!res_msg.data) {
|
|
DBG("failed allocation response buffer size = %d.\n",
|
|
(int) sizeof(struct heci_driver_version));
|
|
return -ENOMEM;
|
|
}
|
|
|
|
version = (struct heci_driver_version *) res_msg.data;
|
|
version->major = MAJOR_VERSION;
|
|
version->minor = MINOR_VERSION;
|
|
version->hotfix = QUICK_FIX_NUMBER;
|
|
version->build = VER_BUILD;
|
|
res_msg.size = sizeof(struct heci_driver_version);
|
|
if (k_msg.size < sizeof(struct heci_driver_version))
|
|
res_msg.size -= 2;
|
|
|
|
rets = file_ext->status;
|
|
/* now copy the data to user space */
|
|
if (copy_to_user(k_msg.data, res_msg.data, res_msg.size)) {
|
|
rets = -EFAULT;
|
|
goto end;
|
|
}
|
|
if (put_user(res_msg.size, &u_msg->size)) {
|
|
rets = -EFAULT;
|
|
goto end;
|
|
}
|
|
end:
|
|
kfree(res_msg.data);
|
|
return rets;
|
|
}
|
|
|
|
/**
|
|
* heci_ioctl_connect_client - the connect to fw client IOCTL function
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @if_num: minor number
|
|
* @*u_msg: pointer to user data struct in user space
|
|
* @k_msg: data in kernel on the stack
|
|
* @file_ext: private data of the file object
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int heci_ioctl_connect_client(struct iamt_heci_device *dev, int if_num,
|
|
struct heci_message_data __user *u_msg,
|
|
struct heci_message_data k_msg,
|
|
struct file *file)
|
|
{
|
|
int rets = 0;
|
|
struct heci_message_data req_msg, res_msg;
|
|
struct heci_cb_private *priv_cb = NULL;
|
|
struct heci_client *client;
|
|
struct heci_file_private *file_ext;
|
|
struct heci_file_private *file_pos = NULL;
|
|
struct heci_file_private *file_next = NULL;
|
|
long timeout = 15; /*15 second */
|
|
__u8 i;
|
|
int err = 0;
|
|
|
|
if ((if_num != HECI_MINOR_NUMBER) || (!dev) || (!file))
|
|
return -ENODEV;
|
|
|
|
file_ext = file->private_data;
|
|
if (!file_ext)
|
|
return -ENODEV;
|
|
|
|
if (k_msg.size != sizeof(struct guid)) {
|
|
DBG("user buffer size is not equal to size of struct "
|
|
"guid(16).\n");
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
if (!k_msg.data)
|
|
return -EIO;
|
|
|
|
req_msg.data = kmalloc(sizeof(struct guid), GFP_KERNEL);
|
|
res_msg.data = kmalloc(sizeof(struct heci_client), GFP_KERNEL);
|
|
|
|
if (!res_msg.data) {
|
|
DBG("failed allocation response buffer size = %d.\n",
|
|
(int) sizeof(struct heci_client));
|
|
kfree(req_msg.data);
|
|
return -ENOMEM;
|
|
}
|
|
if (!req_msg.data) {
|
|
DBG("failed allocation request buffer size = %d.\n",
|
|
(int) sizeof(struct guid));
|
|
kfree(res_msg.data);
|
|
return -ENOMEM;
|
|
}
|
|
req_msg.size = sizeof(struct guid);
|
|
res_msg.size = sizeof(struct heci_client);
|
|
|
|
/* copy the message to kernel space -
|
|
* use a pointer already copied into kernel space
|
|
*/
|
|
if (copy_from_user(req_msg.data, k_msg.data, k_msg.size)) {
|
|
rets = -EFAULT;
|
|
goto end;
|
|
}
|
|
/* buffered ioctl cb */
|
|
priv_cb = kzalloc(sizeof(struct heci_cb_private), GFP_KERNEL);
|
|
if (!priv_cb) {
|
|
rets = -ENOMEM;
|
|
goto end;
|
|
}
|
|
INIT_LIST_HEAD(&priv_cb->cb_list);
|
|
priv_cb->response_buffer.data = res_msg.data;
|
|
priv_cb->response_buffer.size = res_msg.size;
|
|
priv_cb->request_buffer.data = req_msg.data;
|
|
priv_cb->request_buffer.size = req_msg.size;
|
|
priv_cb->major_file_operations = HECI_IOCTL;
|
|
spin_lock_bh(&dev->device_lock);
|
|
if (dev->heci_state != HECI_ENABLED) {
|
|
rets = -ENODEV;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
goto end;
|
|
}
|
|
if ((file_ext->state != HECI_FILE_INITIALIZING) &&
|
|
(file_ext->state != HECI_FILE_DISCONNECTED)) {
|
|
rets = -EBUSY;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
goto end;
|
|
}
|
|
|
|
/* find ME client we're trying to connect to */
|
|
for (i = 0; i < dev->num_heci_me_clients; i++) {
|
|
if (memcmp((struct guid *)req_msg.data,
|
|
&dev->me_clients[i].props.protocol_name,
|
|
sizeof(struct guid)) == 0) {
|
|
if (dev->me_clients[i].props.fixed_address == 0) {
|
|
file_ext->me_client_id =
|
|
dev->me_clients[i].client_id;
|
|
file_ext->state = HECI_FILE_CONNECTING;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/* if we're connecting to PTHI client so we will use the exist
|
|
* connection
|
|
*/
|
|
if (memcmp((struct guid *)req_msg.data, &heci_pthi_guid,
|
|
sizeof(struct guid)) == 0) {
|
|
if (dev->iamthif_file_ext.state != HECI_FILE_CONNECTED) {
|
|
rets = -ENODEV;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
goto end;
|
|
}
|
|
dev->heci_host_clients[file_ext->host_client_id / 8] &=
|
|
~(1 << (file_ext->host_client_id % 8));
|
|
list_for_each_entry_safe(file_pos,
|
|
file_next, &dev->file_list, link) {
|
|
if (heci_fe_same_id(file_ext, file_pos)) {
|
|
DBG("remove file private data node host"
|
|
" client = %d, ME client = %d.\n",
|
|
file_pos->host_client_id,
|
|
file_pos->me_client_id);
|
|
list_del(&file_pos->link);
|
|
}
|
|
|
|
}
|
|
DBG("free file private data memory.\n");
|
|
kfree(file_ext);
|
|
file_ext = NULL;
|
|
file->private_data = &dev->iamthif_file_ext;
|
|
client = (struct heci_client *) res_msg.data;
|
|
client->max_msg_length =
|
|
dev->me_clients[i].props.max_msg_length;
|
|
client->protocol_version =
|
|
dev->me_clients[i].props.protocol_version;
|
|
rets = dev->iamthif_file_ext.status;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
|
|
/* now copy the data to user space */
|
|
if (copy_to_user(k_msg.data, res_msg.data, res_msg.size)) {
|
|
rets = -EFAULT;
|
|
goto end;
|
|
}
|
|
if (put_user(res_msg.size, &u_msg->size)) {
|
|
rets = -EFAULT;
|
|
goto end;
|
|
}
|
|
goto end;
|
|
}
|
|
spin_lock(&file_ext->file_lock);
|
|
if (file_ext->state != HECI_FILE_CONNECTING) {
|
|
rets = -ENODEV;
|
|
spin_unlock(&file_ext->file_lock);
|
|
spin_unlock_bh(&dev->device_lock);
|
|
goto end;
|
|
}
|
|
spin_unlock(&file_ext->file_lock);
|
|
/* prepare the output buffer */
|
|
client = (struct heci_client *) res_msg.data;
|
|
client->max_msg_length = dev->me_clients[i].props.max_msg_length;
|
|
client->protocol_version = dev->me_clients[i].props.protocol_version;
|
|
if (dev->host_buffer_is_empty
|
|
&& !other_client_is_connecting(dev, file_ext)) {
|
|
dev->host_buffer_is_empty = 0;
|
|
if (!heci_connect(dev, file_ext)) {
|
|
rets = -ENODEV;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
goto end;
|
|
} else {
|
|
file_ext->timer_count = HECI_CONNECT_TIMEOUT;
|
|
priv_cb->file_private = file_ext;
|
|
list_add_tail(&priv_cb->cb_list,
|
|
&dev->ctrl_rd_list.heci_cb.
|
|
cb_list);
|
|
}
|
|
|
|
|
|
} else {
|
|
priv_cb->file_private = file_ext;
|
|
DBG("add connect cb to control write list.\n");
|
|
list_add_tail(&priv_cb->cb_list,
|
|
&dev->ctrl_wr_list.heci_cb.cb_list);
|
|
}
|
|
spin_unlock_bh(&dev->device_lock);
|
|
err = wait_event_timeout(dev->wait_recvd_msg,
|
|
(HECI_FILE_CONNECTED == file_ext->state
|
|
|| HECI_FILE_DISCONNECTED == file_ext->state),
|
|
timeout * HZ);
|
|
|
|
if (HECI_FILE_CONNECTED == file_ext->state) {
|
|
DBG("successfully connected to FW client.\n");
|
|
rets = file_ext->status;
|
|
/* now copy the data to user space */
|
|
if (copy_to_user(k_msg.data, res_msg.data, res_msg.size)) {
|
|
rets = -EFAULT;
|
|
goto end;
|
|
}
|
|
if (put_user(res_msg.size, &u_msg->size)) {
|
|
rets = -EFAULT;
|
|
goto end;
|
|
}
|
|
goto end;
|
|
} else {
|
|
DBG("failed to connect to FW client.file_ext->state = %d.\n",
|
|
file_ext->state);
|
|
if (!err) {
|
|
DBG("wait_event_interruptible_timeout failed on client"
|
|
" connect message fw response message.\n");
|
|
}
|
|
rets = -EFAULT;
|
|
goto remove_list;
|
|
}
|
|
|
|
remove_list:
|
|
if (priv_cb) {
|
|
spin_lock_bh(&dev->device_lock);
|
|
heci_flush_list(&dev->ctrl_rd_list, file_ext);
|
|
heci_flush_list(&dev->ctrl_wr_list, file_ext);
|
|
spin_unlock_bh(&dev->device_lock);
|
|
}
|
|
end:
|
|
DBG("free connect cb memory.");
|
|
kfree(req_msg.data);
|
|
kfree(res_msg.data);
|
|
kfree(priv_cb);
|
|
return rets;
|
|
}
|
|
|
|
/**
|
|
* heci_ioctl_wd - the wd IOCTL function
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @if_num: minor number
|
|
* @k_msg: data in kernel on the stack
|
|
* @file_ext: private data of the file object
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int heci_ioctl_wd(struct iamt_heci_device *dev, int if_num,
|
|
struct heci_message_data k_msg,
|
|
struct heci_file_private *file_ext)
|
|
{
|
|
int rets = 0;
|
|
struct heci_message_data req_msg; /*in kernel on the stack */
|
|
|
|
if (if_num != HECI_MINOR_NUMBER)
|
|
return -ENODEV;
|
|
|
|
spin_lock(&file_ext->file_lock);
|
|
if (k_msg.size != HECI_WATCHDOG_DATA_SIZE) {
|
|
DBG("user buffer has invalid size.\n");
|
|
spin_unlock(&file_ext->file_lock);
|
|
return -EMSGSIZE;
|
|
}
|
|
spin_unlock(&file_ext->file_lock);
|
|
|
|
req_msg.data = kmalloc(HECI_WATCHDOG_DATA_SIZE, GFP_KERNEL);
|
|
if (!req_msg.data) {
|
|
DBG("failed allocation request buffer size = %d.\n",
|
|
HECI_WATCHDOG_DATA_SIZE);
|
|
return -ENOMEM;
|
|
}
|
|
req_msg.size = HECI_WATCHDOG_DATA_SIZE;
|
|
|
|
/* copy the message to kernel space - use a pointer already
|
|
* copied into kernel space
|
|
*/
|
|
if (copy_from_user(req_msg.data, k_msg.data, req_msg.size)) {
|
|
rets = -EFAULT;
|
|
goto end;
|
|
}
|
|
spin_lock_bh(&dev->device_lock);
|
|
if (dev->heci_state != HECI_ENABLED) {
|
|
rets = -ENODEV;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
goto end;
|
|
}
|
|
|
|
if (dev->wd_file_ext.state != HECI_FILE_CONNECTED) {
|
|
rets = -ENODEV;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
goto end;
|
|
}
|
|
if (!dev->asf_mode) {
|
|
rets = -EIO;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
goto end;
|
|
}
|
|
|
|
memcpy(&dev->wd_data[HECI_WD_PARAMS_SIZE], req_msg.data,
|
|
HECI_WATCHDOG_DATA_SIZE);
|
|
|
|
dev->wd_timeout = (req_msg.data[1] << 8) + req_msg.data[0];
|
|
dev->wd_pending = 0;
|
|
dev->wd_due_counter = 1; /* next timer */
|
|
if (dev->wd_timeout == 0) {
|
|
memcpy(dev->wd_data, heci_stop_wd_params,
|
|
HECI_WD_PARAMS_SIZE);
|
|
} else {
|
|
memcpy(dev->wd_data, heci_start_wd_params,
|
|
HECI_WD_PARAMS_SIZE);
|
|
mod_timer(&dev->wd_timer, jiffies);
|
|
}
|
|
spin_unlock_bh(&dev->device_lock);
|
|
end:
|
|
kfree(req_msg.data);
|
|
return rets;
|
|
}
|
|
|
|
|
|
/**
|
|
* heci_ioctl_bypass_wd - the bypass_wd IOCTL function
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @if_num: minor number
|
|
* @k_msg: data in kernel on the stack
|
|
* @file_ext: private data of the file object
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int heci_ioctl_bypass_wd(struct iamt_heci_device *dev, int if_num,
|
|
struct heci_message_data k_msg,
|
|
struct heci_file_private *file_ext)
|
|
{
|
|
__u8 flag = 0;
|
|
int rets = 0;
|
|
|
|
if (if_num != HECI_MINOR_NUMBER)
|
|
return -ENODEV;
|
|
|
|
spin_lock(&file_ext->file_lock);
|
|
if (k_msg.size < 1) {
|
|
DBG("user buffer less than HECI_WATCHDOG_DATA_SIZE.\n");
|
|
spin_unlock(&file_ext->file_lock);
|
|
return -EMSGSIZE;
|
|
}
|
|
spin_unlock(&file_ext->file_lock);
|
|
if (copy_from_user(&flag, k_msg.data, 1)) {
|
|
rets = -EFAULT;
|
|
goto end;
|
|
}
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
flag = flag ? (1) : (0);
|
|
dev->wd_bypass = flag;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
end:
|
|
return rets;
|
|
}
|
|
|
|
/**
|
|
* find_pthi_read_list_entry - finds a PTHIlist entry for current file
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @file: pointer to file object
|
|
*
|
|
* returns returned a list entry on success, NULL on failure.
|
|
*/
|
|
struct heci_cb_private *find_pthi_read_list_entry(
|
|
struct iamt_heci_device *dev,
|
|
struct file *file)
|
|
{
|
|
struct heci_file_private *file_ext_temp;
|
|
struct heci_cb_private *priv_cb_pos = NULL;
|
|
struct heci_cb_private *priv_cb_next = NULL;
|
|
|
|
if ((dev->pthi_read_complete_list.status == 0) &&
|
|
!list_empty(&dev->pthi_read_complete_list.heci_cb.cb_list)) {
|
|
list_for_each_entry_safe(priv_cb_pos, priv_cb_next,
|
|
&dev->pthi_read_complete_list.heci_cb.cb_list, cb_list) {
|
|
file_ext_temp = (struct heci_file_private *)
|
|
priv_cb_pos->file_private;
|
|
if ((file_ext_temp != NULL) &&
|
|
(file_ext_temp == &dev->iamthif_file_ext) &&
|
|
(priv_cb_pos->file_object == file))
|
|
return priv_cb_pos;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* pthi_read - read data from pthi client
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @if_num: minor number
|
|
* @file: pointer to file object
|
|
* @*ubuf: pointer to user data in user space
|
|
* @length: data length to read
|
|
* @offset: data read offset
|
|
*
|
|
* returns
|
|
* returned data length on success,
|
|
* zero if no data to read,
|
|
* negative on failure.
|
|
*/
|
|
int pthi_read(struct iamt_heci_device *dev, int if_num, struct file *file,
|
|
char __user *ubuf, size_t length, loff_t *offset)
|
|
{
|
|
int rets = 0;
|
|
struct heci_cb_private *priv_cb = NULL;
|
|
struct heci_file_private *file_ext = file->private_data;
|
|
__u8 i;
|
|
unsigned long currtime = get_seconds();
|
|
|
|
if ((if_num != HECI_MINOR_NUMBER) || (!dev))
|
|
return -ENODEV;
|
|
|
|
if ((file_ext == NULL) || (file_ext != &dev->iamthif_file_ext))
|
|
return -ENODEV;
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
for (i = 0; i < dev->num_heci_me_clients; i++) {
|
|
if (dev->me_clients[i].client_id ==
|
|
dev->iamthif_file_ext.me_client_id)
|
|
break;
|
|
}
|
|
BUG_ON(dev->me_clients[i].client_id != file_ext->me_client_id);
|
|
if ((i == dev->num_heci_me_clients)
|
|
|| (dev->me_clients[i].client_id !=
|
|
dev->iamthif_file_ext.me_client_id)) {
|
|
DBG("PTHI client not found.\n");
|
|
spin_unlock_bh(&dev->device_lock);
|
|
return -ENODEV;
|
|
}
|
|
priv_cb = find_pthi_read_list_entry(dev, file);
|
|
if (!priv_cb) {
|
|
spin_unlock_bh(&dev->device_lock);
|
|
return 0; /* No more data to read */
|
|
} else {
|
|
if (priv_cb &&
|
|
(currtime - priv_cb->read_time > IAMTHIF_READ_TIMER)) {
|
|
/* 15 sec for the message has expired */
|
|
list_del(&priv_cb->cb_list);
|
|
spin_unlock_bh(&dev->device_lock);
|
|
rets = -ETIMEDOUT;
|
|
goto free;
|
|
}
|
|
/* if the whole message will fit remove it from the list */
|
|
if ((priv_cb->information >= *offset) &&
|
|
(length >= (priv_cb->information - *offset)))
|
|
list_del(&priv_cb->cb_list);
|
|
else if ((priv_cb->information > 0) &&
|
|
(priv_cb->information <= *offset)) {
|
|
/* end of the message has been reached */
|
|
list_del(&priv_cb->cb_list);
|
|
rets = 0;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
goto free;
|
|
}
|
|
/* else means that not full buffer will be read and do not
|
|
* remove message from deletion list
|
|
*/
|
|
}
|
|
DBG("pthi priv_cb->response_buffer size - %d\n",
|
|
priv_cb->response_buffer.size);
|
|
DBG("pthi priv_cb->information - %lu\n",
|
|
priv_cb->information);
|
|
spin_unlock_bh(&dev->device_lock);
|
|
|
|
/* length is being turncated to PAGE_SIZE, however,
|
|
* the information may be longer */
|
|
length = length < (priv_cb->information - *offset) ?
|
|
length : (priv_cb->information - *offset);
|
|
|
|
if (copy_to_user(ubuf,
|
|
priv_cb->response_buffer.data + *offset,
|
|
length))
|
|
rets = -EFAULT;
|
|
else {
|
|
rets = length;
|
|
if ((*offset + length) < priv_cb->information) {
|
|
*offset += length;
|
|
goto out;
|
|
}
|
|
}
|
|
free:
|
|
DBG("free pthi cb memory.\n");
|
|
*offset = 0;
|
|
heci_free_cb_private(priv_cb);
|
|
out:
|
|
return rets;
|
|
}
|
|
|
|
/**
|
|
* heci_start_read - the start read client message function.
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @if_num: minor number
|
|
* @file_ext: private data of the file object
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int heci_start_read(struct iamt_heci_device *dev, int if_num,
|
|
struct heci_file_private *file_ext)
|
|
{
|
|
int rets = 0;
|
|
__u8 i;
|
|
struct heci_cb_private *priv_cb = NULL;
|
|
|
|
if ((if_num != HECI_MINOR_NUMBER) || (!dev) || (!file_ext)) {
|
|
DBG("received wrong function input param.\n");
|
|
return -ENODEV;
|
|
}
|
|
if (file_ext->state != HECI_FILE_CONNECTED)
|
|
return -ENODEV;
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
if (dev->heci_state != HECI_ENABLED) {
|
|
spin_unlock_bh(&dev->device_lock);
|
|
return -ENODEV;
|
|
}
|
|
spin_unlock_bh(&dev->device_lock);
|
|
DBG("check if read is pending.\n");
|
|
if ((file_ext->read_pending) || (file_ext->read_cb != NULL)) {
|
|
DBG("read is pending.\n");
|
|
return -EBUSY;
|
|
}
|
|
priv_cb = kzalloc(sizeof(struct heci_cb_private), GFP_KERNEL);
|
|
if (!priv_cb)
|
|
return -ENOMEM;
|
|
|
|
DBG("allocation call back success\n"
|
|
"host client = %d, ME client = %d\n",
|
|
file_ext->host_client_id, file_ext->me_client_id);
|
|
spin_lock_bh(&dev->device_lock);
|
|
for (i = 0; i < dev->num_heci_me_clients; i++) {
|
|
if (dev->me_clients[i].client_id == file_ext->me_client_id)
|
|
break;
|
|
|
|
}
|
|
|
|
BUG_ON(dev->me_clients[i].client_id != file_ext->me_client_id);
|
|
if (i == dev->num_heci_me_clients) {
|
|
rets = -ENODEV;
|
|
goto unlock;
|
|
}
|
|
|
|
priv_cb->response_buffer.size = dev->me_clients[i].props.max_msg_length;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
priv_cb->response_buffer.data =
|
|
kmalloc(priv_cb->response_buffer.size, GFP_KERNEL);
|
|
if (!priv_cb->response_buffer.data) {
|
|
rets = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
DBG("allocation call back data success.\n");
|
|
priv_cb->major_file_operations = HECI_READ;
|
|
/* make sure information is zero before we start */
|
|
priv_cb->information = 0;
|
|
priv_cb->file_private = (void *) file_ext;
|
|
file_ext->read_cb = priv_cb;
|
|
spin_lock_bh(&dev->device_lock);
|
|
if (dev->host_buffer_is_empty) {
|
|
dev->host_buffer_is_empty = 0;
|
|
if (!heci_send_flow_control(dev, file_ext)) {
|
|
rets = -ENODEV;
|
|
goto unlock;
|
|
} else {
|
|
list_add_tail(&priv_cb->cb_list,
|
|
&dev->read_list.heci_cb.cb_list);
|
|
}
|
|
} else {
|
|
list_add_tail(&priv_cb->cb_list,
|
|
&dev->ctrl_wr_list.heci_cb.cb_list);
|
|
}
|
|
spin_unlock_bh(&dev->device_lock);
|
|
return rets;
|
|
unlock:
|
|
spin_unlock_bh(&dev->device_lock);
|
|
fail:
|
|
heci_free_cb_private(priv_cb);
|
|
return rets;
|
|
}
|
|
|
|
/**
|
|
* pthi_write - write iamthif data to pthi client
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @priv_cb: heci call back struct
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int pthi_write(struct iamt_heci_device *dev,
|
|
struct heci_cb_private *priv_cb)
|
|
{
|
|
int rets = 0;
|
|
struct heci_msg_hdr heci_hdr;
|
|
|
|
if ((!dev) || (!priv_cb))
|
|
return -ENODEV;
|
|
|
|
DBG("write data to pthi client.\n");
|
|
|
|
dev->iamthif_state = HECI_IAMTHIF_WRITING;
|
|
dev->iamthif_current_cb = priv_cb;
|
|
dev->iamthif_file_object = priv_cb->file_object;
|
|
dev->iamthif_canceled = 0;
|
|
dev->iamthif_ioctl = 1;
|
|
dev->iamthif_msg_buf_size = priv_cb->request_buffer.size;
|
|
memcpy(dev->iamthif_msg_buf, priv_cb->request_buffer.data,
|
|
priv_cb->request_buffer.size);
|
|
|
|
if (flow_ctrl_creds(dev, &dev->iamthif_file_ext) &&
|
|
dev->host_buffer_is_empty) {
|
|
dev->host_buffer_is_empty = 0;
|
|
if (priv_cb->request_buffer.size >
|
|
(((dev->host_hw_state & H_CBD) >> 24) *
|
|
sizeof(__u32)) - sizeof(struct heci_msg_hdr)) {
|
|
heci_hdr.length =
|
|
(((dev->host_hw_state & H_CBD) >> 24) *
|
|
sizeof(__u32)) - sizeof(struct heci_msg_hdr);
|
|
heci_hdr.msg_complete = 0;
|
|
} else {
|
|
heci_hdr.length = priv_cb->request_buffer.size;
|
|
heci_hdr.msg_complete = 1;
|
|
}
|
|
|
|
heci_hdr.host_addr = dev->iamthif_file_ext.host_client_id;
|
|
heci_hdr.me_addr = dev->iamthif_file_ext.me_client_id;
|
|
heci_hdr.reserved = 0;
|
|
dev->iamthif_msg_buf_index += heci_hdr.length;
|
|
if (!heci_write_message(dev, &heci_hdr,
|
|
(unsigned char *)(dev->iamthif_msg_buf),
|
|
heci_hdr.length))
|
|
return -ENODEV;
|
|
|
|
if (heci_hdr.msg_complete) {
|
|
flow_ctrl_reduce(dev, &dev->iamthif_file_ext);
|
|
dev->iamthif_flow_control_pending = 1;
|
|
dev->iamthif_state = HECI_IAMTHIF_FLOW_CONTROL;
|
|
DBG("add pthi cb to write waiting list\n");
|
|
dev->iamthif_current_cb = priv_cb;
|
|
dev->iamthif_file_object = priv_cb->file_object;
|
|
list_add_tail(&priv_cb->cb_list,
|
|
&dev->write_waiting_list.heci_cb.cb_list);
|
|
} else {
|
|
DBG("message does not complete, "
|
|
"so add pthi cb to write list.\n");
|
|
list_add_tail(&priv_cb->cb_list,
|
|
&dev->write_list.heci_cb.cb_list);
|
|
}
|
|
} else {
|
|
if (!(dev->host_buffer_is_empty))
|
|
DBG("host buffer is not empty");
|
|
|
|
DBG("No flow control credentials, "
|
|
"so add iamthif cb to write list.\n");
|
|
list_add_tail(&priv_cb->cb_list,
|
|
&dev->write_list.heci_cb.cb_list);
|
|
}
|
|
return rets;
|
|
}
|
|
|
|
/**
|
|
* iamthif_ioctl_send_msg - send cmd data to pthi client
|
|
*
|
|
* @dev: Device object for our driver
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
void run_next_iamthif_cmd(struct iamt_heci_device *dev)
|
|
{
|
|
struct heci_file_private *file_ext_tmp;
|
|
struct heci_cb_private *priv_cb_pos = NULL;
|
|
struct heci_cb_private *priv_cb_next = NULL;
|
|
int status = 0;
|
|
|
|
if (!dev)
|
|
return;
|
|
|
|
dev->iamthif_msg_buf_size = 0;
|
|
dev->iamthif_msg_buf_index = 0;
|
|
dev->iamthif_canceled = 0;
|
|
dev->iamthif_ioctl = 1;
|
|
dev->iamthif_state = HECI_IAMTHIF_IDLE;
|
|
dev->iamthif_timer = 0;
|
|
dev->iamthif_file_object = NULL;
|
|
|
|
if (dev->pthi_cmd_list.status == 0 &&
|
|
!list_empty(&dev->pthi_cmd_list.heci_cb.cb_list)) {
|
|
DBG("complete pthi cmd_list cb.\n");
|
|
|
|
list_for_each_entry_safe(priv_cb_pos, priv_cb_next,
|
|
&dev->pthi_cmd_list.heci_cb.cb_list, cb_list) {
|
|
list_del(&priv_cb_pos->cb_list);
|
|
file_ext_tmp = (struct heci_file_private *)
|
|
priv_cb_pos->file_private;
|
|
|
|
if ((file_ext_tmp != NULL) &&
|
|
(file_ext_tmp == &dev->iamthif_file_ext)) {
|
|
status = pthi_write(dev, priv_cb_pos);
|
|
if (status != 0) {
|
|
DBG("pthi write failed status = %d\n",
|
|
status);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* heci_free_cb_private - free heci_cb_private related memory
|
|
*
|
|
* @priv_cb: heci callback struct
|
|
*/
|
|
void heci_free_cb_private(struct heci_cb_private *priv_cb)
|
|
{
|
|
if (priv_cb == NULL)
|
|
return;
|
|
|
|
kfree(priv_cb->request_buffer.data);
|
|
kfree(priv_cb->response_buffer.data);
|
|
kfree(priv_cb);
|
|
}
|
|
|