![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>
1078 lines
29 KiB
C
1078 lines
29 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/pci.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kdev_t.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kthread.h>
|
|
|
|
#include "heci_data_structures.h"
|
|
#include "heci_interface.h"
|
|
#include "heci.h"
|
|
|
|
|
|
const __u8 heci_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 };
|
|
const __u8 heci_stop_wd_params[] = { 0x02, 0x02, 0x14, 0x10 };
|
|
|
|
const __u8 heci_wd_state_independence_msg[3][4] = {
|
|
{0x05, 0x02, 0x51, 0x10},
|
|
{0x05, 0x02, 0x52, 0x10},
|
|
{0x07, 0x02, 0x01, 0x10}
|
|
};
|
|
|
|
static const struct guid heci_asf_guid = {
|
|
0x75B30CD6, 0xA29E, 0x4AF7,
|
|
{0xA7, 0x12, 0xE6, 0x17, 0x43, 0x93, 0xC8, 0xA6}
|
|
};
|
|
const struct guid heci_wd_guid = {
|
|
0x05B79A6F, 0x4628, 0x4D7F,
|
|
{0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB}
|
|
};
|
|
const struct guid heci_pthi_guid = {
|
|
0x12f80028, 0xb4b7, 0x4b2d,
|
|
{0xac, 0xa8, 0x46, 0xe0, 0xff, 0x65, 0x81, 0x4c}
|
|
};
|
|
|
|
|
|
/*
|
|
* heci init function prototypes
|
|
*/
|
|
static void heci_check_asf_mode(struct iamt_heci_device *dev);
|
|
static int host_start_message(struct iamt_heci_device *dev);
|
|
static int host_enum_clients_message(struct iamt_heci_device *dev);
|
|
static int allocate_me_clients_storage(struct iamt_heci_device *dev);
|
|
static void host_init_wd(struct iamt_heci_device *dev);
|
|
static void host_init_iamthif(struct iamt_heci_device *dev);
|
|
static int heci_wait_event_int_timeout(struct iamt_heci_device *dev,
|
|
long timeout);
|
|
|
|
|
|
/**
|
|
* heci_initialize_list - Sets up a queue list.
|
|
*
|
|
* @list: An instance of our list structure
|
|
* @dev: Device object for our driver
|
|
*/
|
|
void heci_initialize_list(struct io_heci_list *list,
|
|
struct iamt_heci_device *dev)
|
|
{
|
|
/* initialize our queue list */
|
|
INIT_LIST_HEAD(&list->heci_cb.cb_list);
|
|
list->status = 0;
|
|
list->device_extension = dev;
|
|
}
|
|
|
|
/**
|
|
* heci_flush_queues - flush our queues list belong to file_ext.
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @file_ext: private data of the file object
|
|
*/
|
|
void heci_flush_queues(struct iamt_heci_device *dev,
|
|
struct heci_file_private *file_ext)
|
|
{
|
|
int i;
|
|
|
|
if (!dev || !file_ext)
|
|
return;
|
|
|
|
/* flush our queue list belong to file_ext */
|
|
for (i = 0; i < HECI_IO_LISTS_NUMBER; i++) {
|
|
DBG("remove list entry belong to file_ext\n");
|
|
heci_flush_list(dev->io_list_array[i], file_ext);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* heci_flush_list - remove list entry belong to file_ext.
|
|
*
|
|
* @list: An instance of our list structure
|
|
* @file_ext: private data of the file object
|
|
*/
|
|
void heci_flush_list(struct io_heci_list *list,
|
|
struct heci_file_private *file_ext)
|
|
{
|
|
struct heci_file_private *file_ext_tmp;
|
|
struct heci_cb_private *priv_cb_pos = NULL;
|
|
struct heci_cb_private *priv_cb_next = NULL;
|
|
|
|
if (!list || !file_ext)
|
|
return;
|
|
|
|
if (list->status != 0)
|
|
return;
|
|
|
|
if (list_empty(&list->heci_cb.cb_list))
|
|
return;
|
|
|
|
list_for_each_entry_safe(priv_cb_pos, priv_cb_next,
|
|
&list->heci_cb.cb_list, cb_list) {
|
|
if (priv_cb_pos) {
|
|
file_ext_tmp = (struct heci_file_private *)
|
|
priv_cb_pos->file_private;
|
|
if (file_ext_tmp) {
|
|
if (heci_fe_same_id(file_ext, file_ext_tmp))
|
|
list_del(&priv_cb_pos->cb_list);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* heci_reset_iamthif_params - initializes heci device iamthif
|
|
*
|
|
* @dev: The heci device structure
|
|
*/
|
|
static void heci_reset_iamthif_params(struct iamt_heci_device *dev)
|
|
{
|
|
/* reset iamthif parameters. */
|
|
dev->iamthif_current_cb = NULL;
|
|
dev->iamthif_msg_buf_size = 0;
|
|
dev->iamthif_msg_buf_index = 0;
|
|
dev->iamthif_canceled = 0;
|
|
dev->iamthif_file_ext.file = NULL;
|
|
dev->iamthif_ioctl = 0;
|
|
dev->iamthif_state = HECI_IAMTHIF_IDLE;
|
|
dev->iamthif_timer = 0;
|
|
}
|
|
|
|
/**
|
|
* init_heci_device - allocates and initializes the heci device structure
|
|
*
|
|
* @pdev: The pci device structure
|
|
*
|
|
* returns The heci_device_device pointer on success, NULL on failure.
|
|
*/
|
|
struct iamt_heci_device *init_heci_device(struct pci_dev *pdev)
|
|
{
|
|
int i;
|
|
struct iamt_heci_device *dev;
|
|
|
|
dev = kzalloc(sizeof(struct iamt_heci_device), GFP_KERNEL);
|
|
if (!dev)
|
|
return NULL;
|
|
|
|
/* setup our list array */
|
|
dev->io_list_array[0] = &dev->read_list;
|
|
dev->io_list_array[1] = &dev->write_list;
|
|
dev->io_list_array[2] = &dev->write_waiting_list;
|
|
dev->io_list_array[3] = &dev->ctrl_wr_list;
|
|
dev->io_list_array[4] = &dev->ctrl_rd_list;
|
|
dev->io_list_array[5] = &dev->pthi_cmd_list;
|
|
dev->io_list_array[6] = &dev->pthi_read_complete_list;
|
|
INIT_LIST_HEAD(&dev->file_list);
|
|
INIT_LIST_HEAD(&dev->wd_file_ext.link);
|
|
INIT_LIST_HEAD(&dev->iamthif_file_ext.link);
|
|
spin_lock_init(&dev->device_lock);
|
|
init_waitqueue_head(&dev->wait_recvd_msg);
|
|
init_waitqueue_head(&dev->wait_stop_wd);
|
|
dev->heci_state = HECI_INITIALIZING;
|
|
dev->iamthif_state = HECI_IAMTHIF_IDLE;
|
|
|
|
/* init work for schedule work */
|
|
INIT_WORK(&dev->work, NULL);
|
|
for (i = 0; i < HECI_IO_LISTS_NUMBER; i++)
|
|
heci_initialize_list(dev->io_list_array[i], dev);
|
|
dev->pdev = pdev;
|
|
return dev;
|
|
}
|
|
|
|
|
|
|
|
|
|
static int heci_wait_event_int_timeout(struct iamt_heci_device *dev,
|
|
long timeout)
|
|
{
|
|
return wait_event_interruptible_timeout(dev->wait_recvd_msg,
|
|
(dev->recvd_msg), timeout);
|
|
}
|
|
|
|
/**
|
|
* heci_hw_init - init host and fw to start work.
|
|
*
|
|
* @dev: Device object for our driver
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int heci_hw_init(struct iamt_heci_device *dev)
|
|
{
|
|
int err = 0;
|
|
|
|
dev->host_hw_state = read_heci_register(dev, H_CSR);
|
|
dev->me_hw_state = read_heci_register(dev, ME_CSR_HA);
|
|
DBG("host_hw_state = 0x%08x, mestate = 0x%08x.\n",
|
|
dev->host_hw_state, dev->me_hw_state);
|
|
|
|
if ((dev->host_hw_state & H_IS) == H_IS) {
|
|
/* acknowledge interrupt and stop interupts */
|
|
heci_set_csr_register(dev);
|
|
}
|
|
dev->recvd_msg = 0;
|
|
DBG("reset in start the heci device.\n");
|
|
|
|
heci_reset(dev, 1);
|
|
|
|
DBG("host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n",
|
|
dev->host_hw_state, dev->me_hw_state);
|
|
|
|
/* wait for ME to turn on ME_RDY */
|
|
if (!dev->recvd_msg)
|
|
err = heci_wait_event_int_timeout(dev, HECI_INTEROP_TIMEOUT);
|
|
|
|
if (!err && !dev->recvd_msg) {
|
|
dev->heci_state = HECI_DISABLED;
|
|
DBG("wait_event_interruptible_timeout failed"
|
|
"on wait for ME to turn on ME_RDY.\n");
|
|
return -ENODEV;
|
|
} else {
|
|
if (!(((dev->host_hw_state & H_RDY) == H_RDY)
|
|
&& ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA))) {
|
|
dev->heci_state = HECI_DISABLED;
|
|
DBG("host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n",
|
|
dev->host_hw_state,
|
|
dev->me_hw_state);
|
|
|
|
if (!(dev->host_hw_state & H_RDY) != H_RDY)
|
|
DBG("host turn off H_RDY.\n");
|
|
|
|
if (!(dev->me_hw_state & ME_RDY_HRA) != ME_RDY_HRA)
|
|
DBG("ME turn off ME_RDY.\n");
|
|
|
|
printk(KERN_ERR
|
|
"heci: link layer initialization failed.\n");
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
dev->recvd_msg = 0;
|
|
DBG("host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n",
|
|
dev->host_hw_state, dev->me_hw_state);
|
|
DBG("ME turn on ME_RDY and host turn on H_RDY.\n");
|
|
printk(KERN_INFO "heci: link layer has been established.\n");
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* heci_hw_reset - reset fw via heci csr register.
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @interrupts: if interrupt should be enable after reset.
|
|
*/
|
|
static void heci_hw_reset(struct iamt_heci_device *dev, int interrupts)
|
|
{
|
|
dev->host_hw_state |= (H_RST | H_IG);
|
|
|
|
if (interrupts)
|
|
heci_csr_enable_interrupts(dev);
|
|
else
|
|
heci_csr_disable_interrupts(dev);
|
|
|
|
BUG_ON((dev->host_hw_state & H_RST) != H_RST);
|
|
BUG_ON((dev->host_hw_state & H_RDY) != 0);
|
|
}
|
|
|
|
/**
|
|
* heci_reset - reset host and fw.
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @interrupts: if interrupt should be enable after reset.
|
|
*/
|
|
void heci_reset(struct iamt_heci_device *dev, int interrupts)
|
|
{
|
|
struct heci_file_private *file_pos = NULL;
|
|
struct heci_file_private *file_next = NULL;
|
|
struct heci_cb_private *priv_cb_pos = NULL;
|
|
struct heci_cb_private *priv_cb_next = NULL;
|
|
int unexpected = 0;
|
|
|
|
if (dev->heci_state == HECI_RECOVERING_FROM_RESET) {
|
|
dev->need_reset = 1;
|
|
return;
|
|
}
|
|
|
|
if (dev->heci_state != HECI_INITIALIZING &&
|
|
dev->heci_state != HECI_DISABLED &&
|
|
dev->heci_state != HECI_POWER_DOWN &&
|
|
dev->heci_state != HECI_POWER_UP)
|
|
unexpected = 1;
|
|
|
|
if (dev->reinit_tsk != NULL) {
|
|
kthread_stop(dev->reinit_tsk);
|
|
dev->reinit_tsk = NULL;
|
|
}
|
|
|
|
dev->host_hw_state = read_heci_register(dev, H_CSR);
|
|
|
|
DBG("before reset host_hw_state = 0x%08x.\n",
|
|
dev->host_hw_state);
|
|
|
|
heci_hw_reset(dev, interrupts);
|
|
|
|
dev->host_hw_state &= ~H_RST;
|
|
dev->host_hw_state |= H_IG;
|
|
|
|
write_heci_register(dev, H_CSR, dev->host_hw_state);
|
|
|
|
DBG("currently saved host_hw_state = 0x%08x.\n",
|
|
dev->host_hw_state);
|
|
|
|
dev->need_reset = 0;
|
|
|
|
if (dev->heci_state != HECI_INITIALIZING) {
|
|
if ((dev->heci_state != HECI_DISABLED) &&
|
|
(dev->heci_state != HECI_POWER_DOWN))
|
|
dev->heci_state = HECI_RESETING;
|
|
|
|
list_for_each_entry_safe(file_pos,
|
|
file_next, &dev->file_list, link) {
|
|
file_pos->state = HECI_FILE_DISCONNECTED;
|
|
file_pos->flow_ctrl_creds = 0;
|
|
file_pos->read_cb = NULL;
|
|
file_pos->timer_count = 0;
|
|
}
|
|
/* remove entry if already in list */
|
|
DBG("list del iamthif and wd file list.\n");
|
|
heci_remove_client_from_file_list(dev,
|
|
dev->wd_file_ext.host_client_id);
|
|
|
|
heci_remove_client_from_file_list(dev,
|
|
dev->iamthif_file_ext.host_client_id);
|
|
|
|
heci_reset_iamthif_params(dev);
|
|
dev->wd_due_counter = 0;
|
|
dev->extra_write_index = 0;
|
|
}
|
|
|
|
dev->num_heci_me_clients = 0;
|
|
dev->rd_msg_hdr = 0;
|
|
dev->stop = 0;
|
|
dev->wd_pending = 0;
|
|
|
|
/* update the state of the registers after reset */
|
|
dev->host_hw_state = read_heci_register(dev, H_CSR);
|
|
dev->me_hw_state = read_heci_register(dev, ME_CSR_HA);
|
|
|
|
DBG("after reset host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n",
|
|
dev->host_hw_state, dev->me_hw_state);
|
|
|
|
if (unexpected)
|
|
printk(KERN_WARNING "heci: unexpected reset.\n");
|
|
|
|
/* Wake up all readings so they can be interrupted */
|
|
list_for_each_entry_safe(file_pos, file_next, &dev->file_list, link) {
|
|
if (&file_pos->rx_wait &&
|
|
waitqueue_active(&file_pos->rx_wait)) {
|
|
printk(KERN_INFO "heci: Waking up client!\n");
|
|
wake_up_interruptible(&file_pos->rx_wait);
|
|
}
|
|
}
|
|
/* remove all waiting requests */
|
|
if (dev->write_list.status == 0 &&
|
|
!list_empty(&dev->write_list.heci_cb.cb_list)) {
|
|
list_for_each_entry_safe(priv_cb_pos, priv_cb_next,
|
|
&dev->write_list.heci_cb.cb_list, cb_list) {
|
|
if (priv_cb_pos) {
|
|
list_del(&priv_cb_pos->cb_list);
|
|
heci_free_cb_private(priv_cb_pos);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* heci_initialize_clients - heci communication initialization.
|
|
*
|
|
* @dev: Device object for our driver
|
|
*/
|
|
int heci_initialize_clients(struct iamt_heci_device *dev)
|
|
{
|
|
int status;
|
|
|
|
msleep(100); /* FW needs time to be ready to talk with us */
|
|
DBG("link is established start sending messages.\n");
|
|
/* link is established start sending messages. */
|
|
status = host_start_message(dev);
|
|
if (status != 0) {
|
|
spin_lock_bh(&dev->device_lock);
|
|
dev->heci_state = HECI_DISABLED;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
DBG("start sending messages failed.\n");
|
|
return status;
|
|
}
|
|
|
|
/* enumerate clients */
|
|
status = host_enum_clients_message(dev);
|
|
if (status != 0) {
|
|
spin_lock_bh(&dev->device_lock);
|
|
dev->heci_state = HECI_DISABLED;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
DBG("enum clients failed.\n");
|
|
return status;
|
|
}
|
|
/* allocate storage for ME clients representation */
|
|
status = allocate_me_clients_storage(dev);
|
|
if (status != 0) {
|
|
spin_lock_bh(&dev->device_lock);
|
|
dev->num_heci_me_clients = 0;
|
|
dev->heci_state = HECI_DISABLED;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
DBG("allocate clients failed.\n");
|
|
return status;
|
|
}
|
|
|
|
heci_check_asf_mode(dev);
|
|
/*heci initialization wd */
|
|
host_init_wd(dev);
|
|
/*heci initialization iamthif client */
|
|
host_init_iamthif(dev);
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
if (dev->need_reset) {
|
|
dev->need_reset = 0;
|
|
dev->heci_state = HECI_DISABLED;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
memset(dev->heci_host_clients, 0, sizeof(dev->heci_host_clients));
|
|
dev->open_handle_count = 0;
|
|
dev->heci_host_clients[0] |= 7;
|
|
dev->current_host_client_id = 3;
|
|
dev->heci_state = HECI_ENABLED;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
DBG("initialization heci clients successful.\n");
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* heci_task_initialize_clients - heci reinitialization task
|
|
*
|
|
* @data: Device object for our driver
|
|
*/
|
|
int heci_task_initialize_clients(void *data)
|
|
{
|
|
int ret;
|
|
struct iamt_heci_device *dev = (struct iamt_heci_device *) data;
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
if (dev->reinit_tsk != NULL) {
|
|
spin_unlock_bh(&dev->device_lock);
|
|
DBG("reinit task already started.\n");
|
|
return 0;
|
|
}
|
|
dev->reinit_tsk = current;
|
|
current->flags |= PF_NOFREEZE;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
|
|
ret = heci_initialize_clients(dev);
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
dev->reinit_tsk = NULL;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* host_start_message - heci host send start message.
|
|
*
|
|
* @dev: Device object for our driver
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
static int host_start_message(struct iamt_heci_device *dev)
|
|
{
|
|
long timeout = 60; /* 60 second */
|
|
|
|
struct heci_msg_hdr *heci_hdr;
|
|
struct hbm_host_version_request *host_start_req;
|
|
struct hbm_host_stop_request *host_stop_req;
|
|
int err = 0;
|
|
|
|
/* host start message */
|
|
heci_hdr = (struct heci_msg_hdr *) &dev->wr_msg_buf[0];
|
|
heci_hdr->host_addr = 0;
|
|
heci_hdr->me_addr = 0;
|
|
heci_hdr->length = sizeof(struct hbm_host_version_request);
|
|
heci_hdr->msg_complete = 1;
|
|
heci_hdr->reserved = 0;
|
|
|
|
host_start_req =
|
|
(struct hbm_host_version_request *) &dev->wr_msg_buf[1];
|
|
memset(host_start_req, 0, sizeof(struct hbm_host_version_request));
|
|
host_start_req->cmd.cmd = HOST_START_REQ_CMD;
|
|
host_start_req->host_version.major_version = HBM_MAJOR_VERSION;
|
|
host_start_req->host_version.minor_version = HBM_MINOR_VERSION;
|
|
dev->recvd_msg = 0;
|
|
if (!heci_write_message(dev, heci_hdr,
|
|
(unsigned char *) (host_start_req),
|
|
heci_hdr->length)) {
|
|
DBG("send version to fw fail.\n");
|
|
return -ENODEV;
|
|
}
|
|
DBG("call wait_event_interruptible_timeout for response message.\n");
|
|
/* wait for response */
|
|
err = heci_wait_event_int_timeout(dev, timeout * HZ);
|
|
if (!err && !dev->recvd_msg) {
|
|
DBG("wait_timeout failed on host start response message.\n");
|
|
return -ENODEV;
|
|
}
|
|
dev->recvd_msg = 0;
|
|
DBG("wait_timeout successful on host start response message.\n");
|
|
if ((dev->version.major_version != HBM_MAJOR_VERSION) ||
|
|
(dev->version.minor_version != HBM_MINOR_VERSION)) {
|
|
/* send stop message */
|
|
heci_hdr->host_addr = 0;
|
|
heci_hdr->me_addr = 0;
|
|
heci_hdr->length = sizeof(struct hbm_host_stop_request);
|
|
heci_hdr->msg_complete = 1;
|
|
heci_hdr->reserved = 0;
|
|
|
|
host_stop_req =
|
|
(struct hbm_host_stop_request *) &dev->wr_msg_buf[1];
|
|
|
|
memset(host_stop_req, 0, sizeof(struct hbm_host_stop_request));
|
|
host_stop_req->cmd.cmd = HOST_STOP_REQ_CMD;
|
|
host_stop_req->reason = DRIVER_STOP_REQUEST;
|
|
heci_write_message(dev, heci_hdr,
|
|
(unsigned char *) (host_stop_req),
|
|
heci_hdr->length);
|
|
DBG("version mismatch.\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* host_enum_clients_message - host send enumeration client request message.
|
|
*
|
|
* @dev: Device object for our driver
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
static int host_enum_clients_message(struct iamt_heci_device *dev)
|
|
{
|
|
long timeout = 5; /*5 second */
|
|
struct heci_msg_hdr *heci_hdr;
|
|
struct hbm_host_enum_request *host_enum_req;
|
|
int err = 0;
|
|
int i, j;
|
|
|
|
heci_hdr = (struct heci_msg_hdr *) &dev->wr_msg_buf[0];
|
|
/* enumerate clients */
|
|
heci_hdr->host_addr = 0;
|
|
heci_hdr->me_addr = 0;
|
|
heci_hdr->length = sizeof(struct hbm_host_enum_request);
|
|
heci_hdr->msg_complete = 1;
|
|
heci_hdr->reserved = 0;
|
|
|
|
host_enum_req = (struct hbm_host_enum_request *) &dev->wr_msg_buf[1];
|
|
memset(host_enum_req, 0, sizeof(struct hbm_host_enum_request));
|
|
host_enum_req->cmd.cmd = HOST_ENUM_REQ_CMD;
|
|
if (!heci_write_message(dev, heci_hdr,
|
|
(unsigned char *) (host_enum_req),
|
|
heci_hdr->length)) {
|
|
DBG("send enumeration request failed.\n");
|
|
return -ENODEV;
|
|
}
|
|
/* wait for response */
|
|
dev->recvd_msg = 0;
|
|
err = heci_wait_event_int_timeout(dev, timeout * HZ);
|
|
if (!err && !dev->recvd_msg) {
|
|
DBG("wait_event_interruptible_timeout failed "
|
|
"on enumeration clients response message.\n");
|
|
return -ENODEV;
|
|
}
|
|
dev->recvd_msg = 0;
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
/* count how many ME clients we have */
|
|
for (i = 0; i < sizeof(dev->heci_me_clients); i++) {
|
|
for (j = 0; j < 8; j++) {
|
|
if ((dev->heci_me_clients[i] & (1 << j)) != 0)
|
|
dev->num_heci_me_clients++;
|
|
|
|
}
|
|
}
|
|
spin_unlock_bh(&dev->device_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* host_client_properties - reads properties for client
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @idx: client index in me client array
|
|
* @client_id: id of the client
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
static int host_client_properties(struct iamt_heci_device *dev,
|
|
struct heci_me_client *client)
|
|
{
|
|
struct heci_msg_hdr *heci_hdr;
|
|
struct hbm_props_request *host_cli_req;
|
|
int err;
|
|
|
|
heci_hdr = (struct heci_msg_hdr *) &dev->wr_msg_buf[0];
|
|
heci_hdr->host_addr = 0;
|
|
heci_hdr->me_addr = 0;
|
|
heci_hdr->length = sizeof(struct hbm_props_request);
|
|
heci_hdr->msg_complete = 1;
|
|
heci_hdr->reserved = 0;
|
|
|
|
host_cli_req = (struct hbm_props_request *) &dev->wr_msg_buf[1];
|
|
memset(host_cli_req, 0, sizeof(struct hbm_props_request));
|
|
host_cli_req->cmd.cmd = HOST_CLIENT_PROPERTEIS_REQ_CMD;
|
|
host_cli_req->address = client->client_id;
|
|
if (!heci_write_message(dev, heci_hdr,
|
|
(unsigned char *) (host_cli_req),
|
|
heci_hdr->length)) {
|
|
DBG("send props request failed.\n");
|
|
return -ENODEV;
|
|
}
|
|
/* wait for response */
|
|
dev->recvd_msg = 0;
|
|
err = heci_wait_event_int_timeout(dev, 10 * HZ);
|
|
if (!err && !dev->recvd_msg) {
|
|
DBG("wait failed on props resp msg.\n");
|
|
return -ENODEV;
|
|
}
|
|
dev->recvd_msg = 0;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* allocate_me_clients_storage - allocate storage for me clients
|
|
*
|
|
* @dev: Device object for our driver
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
static int allocate_me_clients_storage(struct iamt_heci_device *dev)
|
|
{
|
|
struct heci_me_client *clients;
|
|
struct heci_me_client *client;
|
|
__u8 num, i, j;
|
|
int err;
|
|
|
|
if (dev->num_heci_me_clients <= 0)
|
|
return 0;
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
kfree(dev->me_clients);
|
|
dev->me_clients = NULL;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
|
|
/* allocate storage for ME clients representation */
|
|
clients = kcalloc(dev->num_heci_me_clients,
|
|
sizeof(struct heci_me_client), GFP_KERNEL);
|
|
if (!clients) {
|
|
DBG("memory allocation for ME clients failed.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
dev->me_clients = clients;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
|
|
num = 0;
|
|
for (i = 0; i < sizeof(dev->heci_me_clients); i++) {
|
|
for (j = 0; j < 8; j++) {
|
|
if ((dev->heci_me_clients[i] & (1 << j)) != 0) {
|
|
client = &dev->me_clients[num];
|
|
client->client_id = (i * 8) + j;
|
|
client->flow_ctrl_creds = 0;
|
|
err = host_client_properties(dev, client);
|
|
if (err != 0) {
|
|
spin_lock_bh(&dev->device_lock);
|
|
kfree(dev->me_clients);
|
|
dev->me_clients = NULL;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
return err;
|
|
}
|
|
num++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* heci_init_file_private - initializes private file structure.
|
|
*
|
|
* @priv: private file structure to be initialized
|
|
* @file: the file structure
|
|
*/
|
|
static void heci_init_file_private(struct heci_file_private *priv,
|
|
struct file *file)
|
|
{
|
|
memset(priv, 0, sizeof(struct heci_file_private));
|
|
spin_lock_init(&priv->file_lock);
|
|
spin_lock_init(&priv->read_io_lock);
|
|
spin_lock_init(&priv->write_io_lock);
|
|
init_waitqueue_head(&priv->wait);
|
|
init_waitqueue_head(&priv->rx_wait);
|
|
DBG("priv->rx_wait =%p\n", &priv->rx_wait);
|
|
init_waitqueue_head(&priv->tx_wait);
|
|
INIT_LIST_HEAD(&priv->link);
|
|
priv->reading_state = HECI_IDLE;
|
|
priv->writing_state = HECI_IDLE;
|
|
}
|
|
|
|
/**
|
|
* heci_find_me_client - search for ME client guid
|
|
* sets client_id in heci_file_private if found
|
|
* @dev: Device object for our driver
|
|
* @priv: private file structure to set client_id in
|
|
* @cguid: searched guid of ME client
|
|
* @client_id: id of host client to be set in file private structure
|
|
*
|
|
* returns ME client index
|
|
*/
|
|
static __u8 heci_find_me_client(struct iamt_heci_device *dev,
|
|
struct heci_file_private *priv,
|
|
const struct guid *cguid, __u8 client_id)
|
|
{
|
|
__u8 i;
|
|
|
|
if ((dev == NULL) || (priv == NULL) || (cguid == NULL))
|
|
return 0;
|
|
|
|
for (i = 0; i < dev->num_heci_me_clients; i++) {
|
|
if (memcmp(cguid,
|
|
&dev->me_clients[i].props.protocol_name,
|
|
sizeof(struct guid)) == 0) {
|
|
priv->me_client_id = dev->me_clients[i].client_id;
|
|
priv->state = HECI_FILE_CONNECTING;
|
|
priv->host_client_id = client_id;
|
|
|
|
list_add_tail(&priv->link, &dev->file_list);
|
|
return i;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* heci_check_asf_mode - check for ASF client
|
|
*
|
|
* @dev: Device object for our driver
|
|
*/
|
|
static void heci_check_asf_mode(struct iamt_heci_device *dev)
|
|
{
|
|
__u8 i;
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
dev->asf_mode = 0;
|
|
/* find ME ASF client - otherwise assume AMT mode */
|
|
DBG("find ME ASF client - otherwise assume AMT mode.\n");
|
|
for (i = 0; i < dev->num_heci_me_clients; i++) {
|
|
if (memcmp(&heci_asf_guid,
|
|
&dev->me_clients[i].props.protocol_name,
|
|
sizeof(struct guid)) == 0) {
|
|
dev->asf_mode = 1;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
DBG("found ME ASF client.\n");
|
|
return;
|
|
}
|
|
}
|
|
spin_unlock_bh(&dev->device_lock);
|
|
DBG("assume AMT mode.\n");
|
|
}
|
|
|
|
/**
|
|
* heci_connect_me_client - connect ME client
|
|
* @dev: Device object for our driver
|
|
* @priv: private file structure
|
|
* @timeout: connect timeout in seconds
|
|
*
|
|
* returns 1 - if connected, 0 - if not
|
|
*/
|
|
static __u8 heci_connect_me_client(struct iamt_heci_device *dev,
|
|
struct heci_file_private *priv,
|
|
long timeout)
|
|
{
|
|
int err = 0;
|
|
|
|
if ((dev == NULL) || (priv == NULL))
|
|
return 0;
|
|
|
|
if (!heci_connect(dev, priv)) {
|
|
DBG("failed to call heci_connect for client_id=%d.\n",
|
|
priv->host_client_id);
|
|
spin_lock_bh(&dev->device_lock);
|
|
heci_remove_client_from_file_list(dev, priv->host_client_id);
|
|
priv->state = HECI_FILE_DISCONNECTED;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
return 0;
|
|
}
|
|
|
|
err = wait_event_timeout(dev->wait_recvd_msg,
|
|
(HECI_FILE_CONNECTED == priv->state ||
|
|
HECI_FILE_DISCONNECTED == priv->state),
|
|
timeout * HZ);
|
|
if (HECI_FILE_CONNECTED != priv->state) {
|
|
spin_lock_bh(&dev->device_lock);
|
|
heci_remove_client_from_file_list(dev, priv->host_client_id);
|
|
DBG("failed to connect client_id=%d state=%d.\n",
|
|
priv->host_client_id, priv->state);
|
|
if (err)
|
|
DBG("failed connect err=%08x\n", err);
|
|
priv->state = HECI_FILE_DISCONNECTED;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
return 0;
|
|
}
|
|
DBG("successfully connected client_id=%d.\n",
|
|
priv->host_client_id);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* host_init_wd - heci initialization wd.
|
|
*
|
|
* @dev: Device object for our driver
|
|
*/
|
|
static void host_init_wd(struct iamt_heci_device *dev)
|
|
{
|
|
spin_lock_bh(&dev->device_lock);
|
|
|
|
heci_init_file_private(&dev->wd_file_ext, NULL);
|
|
|
|
/* look for WD client and connect to it */
|
|
dev->wd_file_ext.state = HECI_FILE_DISCONNECTED;
|
|
dev->wd_timeout = 0;
|
|
|
|
if (dev->asf_mode) {
|
|
memcpy(dev->wd_data, heci_stop_wd_params, HECI_WD_PARAMS_SIZE);
|
|
} else {
|
|
/* AMT mode */
|
|
dev->wd_timeout = AMT_WD_VALUE;
|
|
DBG("dev->wd_timeout=%d.\n", dev->wd_timeout);
|
|
memcpy(dev->wd_data, heci_start_wd_params, HECI_WD_PARAMS_SIZE);
|
|
memcpy(dev->wd_data + HECI_WD_PARAMS_SIZE,
|
|
&dev->wd_timeout, sizeof(__u16));
|
|
}
|
|
|
|
/* find ME WD client */
|
|
heci_find_me_client(dev, &dev->wd_file_ext,
|
|
&heci_wd_guid, HECI_WD_HOST_CLIENT_ID);
|
|
spin_unlock_bh(&dev->device_lock);
|
|
|
|
DBG("check wd_file_ext\n");
|
|
if (HECI_FILE_CONNECTING == dev->wd_file_ext.state) {
|
|
if (heci_connect_me_client(dev, &dev->wd_file_ext, 15) == 1) {
|
|
DBG("dev->wd_timeout=%d.\n", dev->wd_timeout);
|
|
if (dev->wd_timeout != 0)
|
|
dev->wd_due_counter = 1;
|
|
else
|
|
dev->wd_due_counter = 0;
|
|
DBG("successfully connected to WD client.\n");
|
|
}
|
|
} else
|
|
DBG("failed to find WD client.\n");
|
|
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
dev->wd_timer.function = &heci_wd_timer;
|
|
dev->wd_timer.data = (unsigned long) dev;
|
|
spin_unlock_bh(&dev->device_lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* host_init_iamthif - heci initialization iamthif client.
|
|
*
|
|
* @dev: Device object for our driver
|
|
*
|
|
*/
|
|
static void host_init_iamthif(struct iamt_heci_device *dev)
|
|
{
|
|
__u8 i;
|
|
|
|
spin_lock_bh(&dev->device_lock);
|
|
|
|
heci_init_file_private(&dev->iamthif_file_ext, NULL);
|
|
dev->iamthif_file_ext.state = HECI_FILE_DISCONNECTED;
|
|
|
|
/* find ME PTHI client */
|
|
i = heci_find_me_client(dev, &dev->iamthif_file_ext,
|
|
&heci_pthi_guid, HECI_IAMTHIF_HOST_CLIENT_ID);
|
|
if (dev->iamthif_file_ext.state != HECI_FILE_CONNECTING) {
|
|
DBG("failed to find iamthif client.\n");
|
|
spin_unlock_bh(&dev->device_lock);
|
|
return;
|
|
}
|
|
|
|
BUG_ON(dev->me_clients[i].props.max_msg_length != IAMTHIF_MTU);
|
|
|
|
spin_unlock_bh(&dev->device_lock);
|
|
if (heci_connect_me_client(dev, &dev->iamthif_file_ext, 15) == 1) {
|
|
DBG("connected to iamthif client.\n");
|
|
dev->iamthif_state = HECI_IAMTHIF_IDLE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* heci_alloc_file_private - allocates a private file structure and set it up.
|
|
* @file: the file structure
|
|
*
|
|
* returns The allocated file or NULL on failure
|
|
*/
|
|
struct heci_file_private *heci_alloc_file_private(struct file *file)
|
|
{
|
|
struct heci_file_private *priv;
|
|
|
|
priv = kmalloc(sizeof(struct heci_file_private), GFP_KERNEL);
|
|
if (!priv)
|
|
return NULL;
|
|
|
|
heci_init_file_private(priv, file);
|
|
|
|
return priv;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* heci_disconnect_host_client - send disconnect message to fw from host client.
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @file_ext: private data of the file object
|
|
*
|
|
* returns 0 on success, <0 on failure.
|
|
*/
|
|
int heci_disconnect_host_client(struct iamt_heci_device *dev,
|
|
struct heci_file_private *file_ext)
|
|
{
|
|
int rets, err;
|
|
long timeout = 15; /* 15 seconds */
|
|
struct heci_cb_private *priv_cb;
|
|
|
|
if ((!dev) || (!file_ext))
|
|
return -ENODEV;
|
|
|
|
if (file_ext->state != HECI_FILE_DISCONNECTING)
|
|
return 0;
|
|
|
|
priv_cb = kzalloc(sizeof(struct heci_cb_private), GFP_KERNEL);
|
|
if (!priv_cb)
|
|
return -ENOMEM;
|
|
|
|
INIT_LIST_HEAD(&priv_cb->cb_list);
|
|
priv_cb->file_private = file_ext;
|
|
priv_cb->major_file_operations = HECI_CLOSE;
|
|
spin_lock_bh(&dev->device_lock);
|
|
if (dev->host_buffer_is_empty) {
|
|
dev->host_buffer_is_empty = 0;
|
|
if (heci_disconnect(dev, file_ext)) {
|
|
list_add_tail(&priv_cb->cb_list,
|
|
&dev->ctrl_rd_list.heci_cb.cb_list);
|
|
} else {
|
|
spin_unlock_bh(&dev->device_lock);
|
|
rets = -ENODEV;
|
|
DBG("failed to call heci_disconnect.\n");
|
|
goto free;
|
|
}
|
|
} else {
|
|
DBG("add disconnect 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_DISCONNECTED == file_ext->state),
|
|
timeout * HZ);
|
|
if (HECI_FILE_DISCONNECTED == file_ext->state) {
|
|
rets = 0;
|
|
DBG("successfully disconnected from fw client.\n");
|
|
} else {
|
|
rets = -ENODEV;
|
|
if (HECI_FILE_DISCONNECTED != file_ext->state)
|
|
DBG("wrong status client disconnect.\n");
|
|
|
|
if (err)
|
|
DBG("wait failed disconnect err=%08x\n", err);
|
|
|
|
DBG("failed to disconnect from fw client.\n");
|
|
}
|
|
|
|
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);
|
|
free:
|
|
heci_free_cb_private(priv_cb);
|
|
return rets;
|
|
}
|
|
|
|
/**
|
|
* heci_remove_client_from_file_list -
|
|
* remove file private data from device file list
|
|
*
|
|
* @dev: Device object for our driver
|
|
* @host_client_id: host client id to be removed
|
|
*/
|
|
void heci_remove_client_from_file_list(struct iamt_heci_device *dev,
|
|
__u8 host_client_id)
|
|
{
|
|
struct heci_file_private *file_pos = NULL;
|
|
struct heci_file_private *file_next = NULL;
|
|
list_for_each_entry_safe(file_pos, file_next, &dev->file_list, link) {
|
|
if (host_client_id == file_pos->host_client_id) {
|
|
DBG("remove host client = %d, ME client = %d\n",
|
|
file_pos->host_client_id,
|
|
file_pos->me_client_id);
|
|
list_del_init(&file_pos->link);
|
|
break;
|
|
}
|
|
}
|
|
}
|