]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/staging/heci/heci_main.c
Merge branch 'omap-pool'
[linux-2.6-omap-h63xx.git] / drivers / staging / heci / heci_main.c
diff --git a/drivers/staging/heci/heci_main.c b/drivers/staging/heci/heci_main.c
new file mode 100644 (file)
index 0000000..00e44c7
--- /dev/null
@@ -0,0 +1,1564 @@
+/*
+ * 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/device.h>
+#include <linux/unistd.h>
+#include <linux/kthread.h>
+
+#include "heci.h"
+#include "heci_interface.h"
+#include "heci_version.h"
+
+
+#define HECI_READ_TIMEOUT      45
+
+#define HECI_DRIVER_NAME       "heci"
+
+/*
+ *  heci driver strings
+ */
+static char heci_driver_name[] = HECI_DRIVER_NAME;
+static char heci_driver_string[] = "Intel(R) Management Engine Interface";
+static char heci_driver_version[] = HECI_DRIVER_VERSION;
+static char heci_copyright[] = "Copyright (c) 2003 - 2008 Intel Corporation.";
+
+
+#ifdef HECI_DEBUG
+int heci_debug = 1;
+#else
+int heci_debug;
+#endif
+MODULE_PARM_DESC(heci_debug,  "Debug enabled or not");
+module_param(heci_debug, int, 0644);
+
+
+#define HECI_DEV_NAME  "heci"
+
+/* heci char device for registration */
+static struct cdev heci_cdev;
+
+/* major number for device */
+static int heci_major;
+/* The device pointer */
+static struct pci_dev *heci_device;
+
+static struct class *heci_class;
+
+
+/* heci_pci_tbl - PCI Device ID Table */
+static struct pci_device_id heci_pci_tbl[] = {
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_82946GZ)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_82G35)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_82Q965)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_82G965)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_82GM965)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_82GME965)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9_82Q35)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9_82G33)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9_82Q33)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9_82X38)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9_3200)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9_6)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9_7)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9_8)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9_9)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9_10)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9M_1)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9M_2)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9M_3)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH9M_4)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH10_1)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH10_2)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH10_3)},
+       {PCI_DEVICE(PCI_VENDOR_ID_INTEL, HECI_DEV_ID_ICH10_4)},
+       /* required last entry */
+       {0, }
+};
+
+MODULE_DEVICE_TABLE(pci, heci_pci_tbl);
+
+/*
+ * Local Function Prototypes
+ */
+static int __init heci_init_module(void);
+static void __exit heci_exit_module(void);
+static int __devinit heci_probe(struct pci_dev *pdev,
+                               const struct pci_device_id *ent);
+static void __devexit heci_remove(struct pci_dev *pdev);
+static int heci_open(struct inode *inode, struct file *file);
+static int heci_release(struct inode *inode, struct file *file);
+static ssize_t heci_read(struct file *file, char __user *ubuf,
+                        size_t length, loff_t *offset);
+static int heci_ioctl(struct inode *inode, struct file *file,
+                     unsigned int cmd, unsigned long data);
+static ssize_t heci_write(struct file *file, const char __user *ubuf,
+                         size_t length, loff_t *offset);
+static unsigned int heci_poll(struct file *file, poll_table *wait);
+static struct heci_cb_private *find_read_list_entry(
+               struct iamt_heci_device *dev,
+               struct heci_file_private *file_ext);
+#ifdef CONFIG_PM
+static int heci_suspend(struct pci_dev *pdev, pm_message_t state);
+static int heci_resume(struct pci_dev *pdev);
+static __u16 g_sus_wd_timeout;
+#else
+#define heci_suspend NULL
+#define heci_resume NULL
+#endif
+/*
+ *  PCI driver structure
+ */
+static struct pci_driver heci_driver = {
+       .name = heci_driver_name,
+       .id_table = heci_pci_tbl,
+       .probe = heci_probe,
+       .remove = __devexit_p(heci_remove),
+       .shutdown = __devexit_p(heci_remove),
+       .suspend = heci_suspend,
+       .resume = heci_resume
+};
+
+/*
+ * file operations structure will be use heci char device.
+ */
+static const struct file_operations heci_fops = {
+       .owner = THIS_MODULE,
+       .read = heci_read,
+       .ioctl = heci_ioctl,
+       .open = heci_open,
+       .release = heci_release,
+       .write = heci_write,
+       .poll = heci_poll,
+};
+
+/**
+ * heci_registration_cdev - set up the cdev structure for heci device.
+ *
+ * @dev: char device struct
+ * @hminor: minor number for registration char device
+ * @fops: file operations structure
+ *
+ * returns 0 on success, <0 on failure.
+ */
+static int heci_registration_cdev(struct cdev *dev, int hminor,
+                                 const struct file_operations *fops)
+{
+       int ret, devno = MKDEV(heci_major, hminor);
+
+       cdev_init(dev, fops);
+       dev->owner = THIS_MODULE;
+       ret = cdev_add(dev, devno, 1);
+       /* Fail gracefully if need be */
+       if (ret) {
+               printk(KERN_ERR "heci: Error %d registering heci device %d\n",
+                      ret, hminor);
+       }
+       return ret;
+}
+
+/* Display the version of heci driver. */
+static ssize_t version_show(struct class *dev, char *buf)
+{
+       return sprintf(buf, "%s %s.\n",
+                      heci_driver_string, heci_driver_version);
+}
+
+static CLASS_ATTR(version, S_IRUGO, version_show, NULL);
+
+/**
+ * heci_register_cdev - registers heci char device
+ *
+ * returns 0 on success, <0 on failure.
+ */
+static int heci_register_cdev(void)
+{
+       int ret;
+       dev_t dev;
+
+       /* registration of char devices */
+       ret = alloc_chrdev_region(&dev, HECI_MINORS_BASE, HECI_MINORS_COUNT,
+                                 HECI_DRIVER_NAME);
+       if (ret) {
+               printk(KERN_ERR "heci: Error allocating char device region.\n");
+               return ret;
+       }
+
+       heci_major = MAJOR(dev);
+
+       ret = heci_registration_cdev(&heci_cdev, HECI_MINOR_NUMBER,
+                                    &heci_fops);
+       if (ret)
+               unregister_chrdev_region(MKDEV(heci_major, HECI_MINORS_BASE),
+                                        HECI_MINORS_COUNT);
+
+       return ret;
+}
+
+/**
+ * heci_unregister_cdev - unregisters heci char device
+ */
+static void heci_unregister_cdev(void)
+{
+       cdev_del(&heci_cdev);
+       unregister_chrdev_region(MKDEV(heci_major, HECI_MINORS_BASE),
+                                HECI_MINORS_COUNT);
+}
+
+#ifndef HECI_DEVICE_CREATE
+#define HECI_DEVICE_CREATE device_create
+#endif
+/**
+ * heci_sysfs_device_create - adds device entry to sysfs
+ *
+ * returns 0 on success, <0 on failure.
+ */
+static int heci_sysfs_device_create(void)
+{
+       struct class *class;
+       void *tmphdev;
+       int err = 0;
+
+       class = class_create(THIS_MODULE, HECI_DRIVER_NAME);
+       if (IS_ERR(class)) {
+               err = PTR_ERR(class);
+               printk(KERN_ERR "heci: Error creating heci class.\n");
+               goto err_out;
+       }
+
+       err = class_create_file(class, &class_attr_version);
+       if (err) {
+               class_destroy(class);
+               printk(KERN_ERR "heci: Error creating heci class file.\n");
+               goto err_out;
+       }
+
+       tmphdev = HECI_DEVICE_CREATE(class, NULL, heci_cdev.dev, NULL,
+                                       HECI_DEV_NAME);
+       if (IS_ERR(tmphdev)) {
+               err = PTR_ERR(tmphdev);
+               class_remove_file(class, &class_attr_version);
+               class_destroy(class);
+               goto err_out;
+       }
+
+       heci_class = class;
+err_out:
+       return err;
+}
+
+/**
+ * heci_sysfs_device_remove - unregisters the device entry on sysfs
+ */
+static void heci_sysfs_device_remove(void)
+{
+       if ((heci_class == NULL) || (IS_ERR(heci_class)))
+               return;
+
+       device_destroy(heci_class, heci_cdev.dev);
+       class_remove_file(heci_class, &class_attr_version);
+       class_destroy(heci_class);
+}
+
+/**
+ * heci_init_module - Driver Registration Routine
+ *
+ * heci_init_module is the first routine called when the driver is
+ * loaded. All it does is register with the PCI subsystem.
+ *
+ * returns 0 on success, <0 on failure.
+ */
+static int __init heci_init_module(void)
+{
+       int ret = 0;
+
+       printk(KERN_INFO "heci: %s - version %s\n", heci_driver_string,
+                       heci_driver_version);
+       printk(KERN_INFO "heci: %s\n", heci_copyright);
+
+       /* init pci module */
+       ret = pci_register_driver(&heci_driver);
+       if (ret < 0) {
+               printk(KERN_ERR "heci: Error registering driver.\n");
+               goto end;
+       }
+
+       ret = heci_register_cdev();
+       if (ret)
+               goto unregister_pci;
+
+       ret = heci_sysfs_device_create();
+       if (ret)
+               goto unregister_cdev;
+
+       return ret;
+
+unregister_cdev:
+       heci_unregister_cdev();
+unregister_pci:
+       pci_unregister_driver(&heci_driver);
+end:
+       return ret;
+}
+
+module_init(heci_init_module);
+
+
+/**
+ * heci_exit_module - Driver Exit Cleanup Routine
+ *
+ * heci_exit_module is called just before the driver is removed
+ * from memory.
+ */
+static void __exit heci_exit_module(void)
+{
+       pci_unregister_driver(&heci_driver);
+       heci_sysfs_device_remove();
+       heci_unregister_cdev();
+}
+
+module_exit(heci_exit_module);
+
+
+/**
+ * heci_probe - Device Initialization Routine
+ *
+ * @pdev: PCI device information struct
+ * @ent: entry in kcs_pci_tbl
+ *
+ * returns 0 on success, <0 on failure.
+ */
+static int __devinit heci_probe(struct pci_dev *pdev,
+                               const struct pci_device_id *ent)
+{
+       struct iamt_heci_device *dev = NULL;
+       int i, err = 0;
+
+       if (heci_device) {
+               err = -EEXIST;
+               goto end;
+       }
+       /* enable pci dev */
+       err = pci_enable_device(pdev);
+       if (err) {
+               printk(KERN_ERR "heci: Failed to enable pci device.\n");
+               goto end;
+       }
+       /* set PCI host mastering  */
+       pci_set_master(pdev);
+       /* pci request regions for heci driver */
+       err = pci_request_regions(pdev, heci_driver_name);
+       if (err) {
+               printk(KERN_ERR "heci: Failed to get pci regions.\n");
+               goto disable_device;
+       }
+       /* allocates and initializes the heci dev structure */
+       dev = init_heci_device(pdev);
+       if (!dev) {
+               err = -ENOMEM;
+               goto release_regions;
+       }
+       /* mapping  IO device memory */
+       for (i = 0; i <= 5; i++) {
+               if (pci_resource_len(pdev, i) == 0)
+                       continue;
+               if (pci_resource_flags(pdev, i) & IORESOURCE_IO) {
+                       printk(KERN_ERR "heci: heci has IO ports.\n");
+                       goto free_device;
+               } else if (pci_resource_flags(pdev, i) & IORESOURCE_MEM) {
+                       if (dev->mem_base) {
+                               printk(KERN_ERR
+                                       "heci: Too many mem addresses.\n");
+                               goto free_device;
+                       }
+                       dev->mem_base = pci_resource_start(pdev, i);
+                       dev->mem_length = pci_resource_len(pdev, i);
+               }
+       }
+       if (!dev->mem_base) {
+               printk(KERN_ERR "heci: No address to use.\n");
+               err = -ENODEV;
+               goto free_device;
+       }
+       dev->mem_addr = ioremap_nocache(dev->mem_base,
+                       dev->mem_length);
+       if (!dev->mem_addr) {
+               printk(KERN_ERR "heci: Remap IO device memory failure.\n");
+               err = -ENOMEM;
+               goto free_device;
+       }
+       /* request and enable interrupt   */
+       err = request_irq(pdev->irq, heci_isr_interrupt, IRQF_SHARED,
+                       heci_driver_name, dev);
+       if (err) {
+               printk(KERN_ERR "heci: Request_irq failure. irq = %d \n",
+                      pdev->irq);
+               goto unmap_memory;
+       }
+
+       if (heci_hw_init(dev)) {
+               printk(KERN_ERR "heci: Init hw failure.\n");
+               err = -ENODEV;
+               goto release_irq;
+       }
+       init_timer(&dev->wd_timer);
+
+       heci_initialize_clients(dev);
+       if (dev->heci_state != HECI_ENABLED) {
+               err = -ENODEV;
+               goto release_hw;
+       }
+
+       spin_lock_bh(&dev->device_lock);
+       heci_device = pdev;
+       pci_set_drvdata(pdev, dev);
+       spin_unlock_bh(&dev->device_lock);
+
+       if (dev->wd_timeout)
+               mod_timer(&dev->wd_timer, jiffies);
+
+#ifdef CONFIG_PM
+       g_sus_wd_timeout = 0;
+#endif
+       printk(KERN_INFO "heci driver initialization successful.\n");
+       return 0;
+
+release_hw:
+       /* disable interrupts */
+       dev->host_hw_state = read_heci_register(dev, H_CSR);
+       heci_csr_disable_interrupts(dev);
+
+       del_timer_sync(&dev->wd_timer);
+
+       flush_scheduled_work();
+
+release_irq:
+       free_irq(pdev->irq, dev);
+unmap_memory:
+       if (dev->mem_addr)
+               iounmap(dev->mem_addr);
+free_device:
+       kfree(dev);
+release_regions:
+       pci_release_regions(pdev);
+disable_device:
+       pci_disable_device(pdev);
+end:
+       printk(KERN_ERR "heci driver initialization failed.\n");
+       return err;
+}
+
+/**
+ * heci_remove - Device Removal Routine
+ *
+ * @pdev: PCI device information struct
+ *
+ * heci_remove is called by the PCI subsystem to alert the driver
+ * that it should release a PCI device.
+ */
+static void __devexit heci_remove(struct pci_dev *pdev)
+{
+       struct iamt_heci_device *dev = pci_get_drvdata(pdev);
+
+       if (heci_device != pdev)
+               return;
+
+       if (dev == NULL)
+               return;
+
+       spin_lock_bh(&dev->device_lock);
+       if (heci_device != pdev) {
+               spin_unlock_bh(&dev->device_lock);
+               return;
+       }
+
+       if (dev->reinit_tsk != NULL) {
+               kthread_stop(dev->reinit_tsk);
+               dev->reinit_tsk = NULL;
+       }
+
+       del_timer_sync(&dev->wd_timer);
+       if (dev->wd_file_ext.state == HECI_FILE_CONNECTED
+           && dev->wd_timeout) {
+               dev->wd_timeout = 0;
+               dev->wd_due_counter = 0;
+               memcpy(dev->wd_data, heci_stop_wd_params, HECI_WD_PARAMS_SIZE);
+               dev->stop = 1;
+               if (dev->host_buffer_is_empty &&
+                   flow_ctrl_creds(dev, &dev->wd_file_ext)) {
+                       dev->host_buffer_is_empty = 0;
+
+                       if (!heci_send_wd(dev))
+                               DBG("send stop WD failed\n");
+                       else
+                               flow_ctrl_reduce(dev, &dev->wd_file_ext);
+
+                       dev->wd_pending = 0;
+               } else {
+                       dev->wd_pending = 1;
+               }
+               dev->wd_stoped = 0;
+               spin_unlock_bh(&dev->device_lock);
+
+               wait_event_interruptible_timeout(dev->wait_stop_wd,
+                               (dev->wd_stoped), 10 * HZ);
+               spin_lock_bh(&dev->device_lock);
+               if (!dev->wd_stoped)
+                       DBG("stop wd failed to complete.\n");
+               else
+                       DBG("stop wd complete.\n");
+
+       }
+
+       heci_device = NULL;
+       spin_unlock_bh(&dev->device_lock);
+
+       if (dev->iamthif_file_ext.state == HECI_FILE_CONNECTED) {
+               dev->iamthif_file_ext.state = HECI_FILE_DISCONNECTING;
+               heci_disconnect_host_client(dev,
+                                           &dev->iamthif_file_ext);
+       }
+       if (dev->wd_file_ext.state == HECI_FILE_CONNECTED) {
+               dev->wd_file_ext.state = HECI_FILE_DISCONNECTING;
+               heci_disconnect_host_client(dev,
+                                           &dev->wd_file_ext);
+       }
+
+       spin_lock_bh(&dev->device_lock);
+
+       /* 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);
+
+       dev->iamthif_current_cb = NULL;
+       dev->iamthif_file_ext.file = NULL;
+       dev->num_heci_me_clients = 0;
+
+       spin_unlock_bh(&dev->device_lock);
+
+       flush_scheduled_work();
+
+       /* disable interrupts */
+       heci_csr_disable_interrupts(dev);
+
+       free_irq(pdev->irq, dev);
+       pci_set_drvdata(pdev, NULL);
+
+       if (dev->mem_addr)
+               iounmap(dev->mem_addr);
+
+       kfree(dev);
+
+       pci_release_regions(pdev);
+       pci_disable_device(pdev);
+}
+
+/**
+ * heci_clear_list - remove all callbacks associated with file
+ *             from heci_cb_list
+ *
+ * @file: file information struct
+ * @heci_cb_list: callbacks list
+ *
+ * heci_clear_list is called to clear resources associated with file
+ * when application calls close function or Ctrl-C was pressed
+ *
+ * returns 1 if callback removed from the list, 0 otherwise
+ */
+static int heci_clear_list(struct iamt_heci_device *dev,
+               struct file *file, struct list_head *heci_cb_list)
+{
+       struct heci_cb_private *priv_cb_pos = NULL;
+       struct heci_cb_private *priv_cb_next = NULL;
+       struct file *file_temp;
+       int rets = 0;
+
+       /* list all list member */
+       list_for_each_entry_safe(priv_cb_pos, priv_cb_next,
+                                heci_cb_list, cb_list) {
+               file_temp = (struct file *)priv_cb_pos->file_object;
+               /* check if list member associated with a file */
+               if (file_temp == file) {
+                       /* remove member from the list */
+                       list_del(&priv_cb_pos->cb_list);
+                       /* check if cb equal to current iamthif cb */
+                       if (dev->iamthif_current_cb == priv_cb_pos) {
+                               dev->iamthif_current_cb = NULL;
+                               /* send flow control to iamthif client */
+                               heci_send_flow_control(dev,
+                                                      &dev->iamthif_file_ext);
+                       }
+                       /* free all allocated buffers */
+                       heci_free_cb_private(priv_cb_pos);
+                       rets = 1;
+               }
+       }
+       return rets;
+}
+
+/**
+ * heci_clear_lists - remove all callbacks associated with file
+ *
+ * @dev: device information struct
+ * @file: file information struct
+ *
+ * heci_clear_lists is called to clear resources associated with file
+ * when application calls close function or Ctrl-C was pressed
+ *
+ * returns 1 if callback removed from the list, 0 otherwise
+ */
+static int heci_clear_lists(struct iamt_heci_device *dev, struct file *file)
+{
+       int rets = 0;
+
+       /* remove callbacks associated with a file */
+       heci_clear_list(dev, file, &dev->pthi_cmd_list.heci_cb.cb_list);
+       if (heci_clear_list(dev, file,
+                           &dev->pthi_read_complete_list.heci_cb.cb_list))
+               rets = 1;
+
+       heci_clear_list(dev, file, &dev->ctrl_rd_list.heci_cb.cb_list);
+
+       if (heci_clear_list(dev, file, &dev->ctrl_wr_list.heci_cb.cb_list))
+               rets = 1;
+
+       if (heci_clear_list(dev, file,
+                           &dev->write_waiting_list.heci_cb.cb_list))
+               rets = 1;
+
+       if (heci_clear_list(dev, file, &dev->write_list.heci_cb.cb_list))
+               rets = 1;
+
+       /* check if iamthif_current_cb not NULL */
+       if (dev->iamthif_current_cb && (!rets)) {
+               /* check file and iamthif current cb association */
+               if (dev->iamthif_current_cb->file_object == file) {
+                       /* remove cb */
+                       heci_free_cb_private(dev->iamthif_current_cb);
+                       dev->iamthif_current_cb = NULL;
+                       rets = 1;
+               }
+       }
+       return rets;
+}
+
+/**
+ * heci_open - the open function
+ *
+ * @inode: pointer to inode structure
+ * @file: pointer to file structure
+ *
+ * returns 0 on success, <0 on error
+ */
+static int heci_open(struct inode *inode, struct file *file)
+{
+       struct heci_file_private *file_ext;
+       int if_num = iminor(inode);
+       struct iamt_heci_device *dev;
+
+       if (!heci_device)
+               return -ENODEV;
+
+       dev = pci_get_drvdata(heci_device);
+       if ((if_num != HECI_MINOR_NUMBER) || (!dev))
+               return -ENODEV;
+
+       file_ext = heci_alloc_file_private(file);
+       if (file_ext == NULL)
+               return -ENOMEM;
+
+       spin_lock_bh(&dev->device_lock);
+       if (dev->heci_state != HECI_ENABLED) {
+               spin_unlock_bh(&dev->device_lock);
+               kfree(file_ext);
+               return -ENODEV;
+       }
+       if (dev->open_handle_count >= HECI_MAX_OPEN_HANDLE_COUNT) {
+               spin_unlock_bh(&dev->device_lock);
+               kfree(file_ext);
+               return -ENFILE;
+       };
+       dev->open_handle_count++;
+       list_add_tail(&file_ext->link, &dev->file_list);
+       while ((dev->heci_host_clients[dev->current_host_client_id / 8]
+               & (1 << (dev->current_host_client_id % 8))) != 0) {
+
+               dev->current_host_client_id++; /* allow overflow */
+               DBG("current_host_client_id = %d\n",
+                   dev->current_host_client_id);
+               DBG("dev->open_handle_count = %lu\n",
+                   dev->open_handle_count);
+       }
+       DBG("current_host_client_id = %d\n", dev->current_host_client_id);
+       file_ext->host_client_id = dev->current_host_client_id;
+       dev->heci_host_clients[file_ext->host_client_id / 8] |=
+               (1 << (file_ext->host_client_id % 8));
+       spin_unlock_bh(&dev->device_lock);
+       spin_lock(&file_ext->file_lock);
+       file_ext->state = HECI_FILE_INITIALIZING;
+       file_ext->sm_state = 0;
+
+       file->private_data = file_ext;
+       spin_unlock(&file_ext->file_lock);
+
+       return 0;
+}
+
+/**
+ * heci_release - the release function
+ *
+ * @inode: pointer to inode structure
+ * @file: pointer to file structure
+ *
+ * returns 0 on success, <0 on error
+ */
+static int heci_release(struct inode *inode, struct file *file)
+{
+       int rets = 0;
+       int if_num = iminor(inode);
+       struct heci_file_private *file_ext = file->private_data;
+       struct heci_cb_private *priv_cb = NULL;
+       struct iamt_heci_device *dev;
+
+       if (!heci_device)
+               return -ENODEV;
+
+       dev = pci_get_drvdata(heci_device);
+       if ((if_num != HECI_MINOR_NUMBER) || (!dev) || (!file_ext))
+               return -ENODEV;
+
+       if (file_ext != &dev->iamthif_file_ext) {
+               spin_lock(&file_ext->file_lock);
+               if (file_ext->state == HECI_FILE_CONNECTED) {
+                       file_ext->state = HECI_FILE_DISCONNECTING;
+                       spin_unlock(&file_ext->file_lock);
+                       DBG("disconnecting client host client = %d, "
+                           "ME client = %d\n",
+                           file_ext->host_client_id,
+                           file_ext->me_client_id);
+                       rets = heci_disconnect_host_client(dev, file_ext);
+                       spin_lock(&file_ext->file_lock);
+               }
+               spin_lock_bh(&dev->device_lock);
+               heci_flush_queues(dev, file_ext);
+               DBG("remove client host client = %d, ME client = %d\n",
+                   file_ext->host_client_id,
+                   file_ext->me_client_id);
+
+               if (dev->open_handle_count > 0) {
+                       dev->heci_host_clients[file_ext->host_client_id / 8] &=
+                       ~(1 << (file_ext->host_client_id % 8));
+                       dev->open_handle_count--;
+               }
+               heci_remove_client_from_file_list(dev,
+                               file_ext->host_client_id);
+
+               /* free read cb */
+               if (file_ext->read_cb != NULL) {
+                       priv_cb = find_read_list_entry(dev, file_ext);
+                       /* Remove entry from read list */
+                       if (priv_cb != NULL)
+                               list_del(&priv_cb->cb_list);
+
+                       priv_cb = file_ext->read_cb;
+                       file_ext->read_cb = NULL;
+               }
+
+               spin_unlock_bh(&dev->device_lock);
+               file->private_data = NULL;
+               spin_unlock(&file_ext->file_lock);
+
+               if (priv_cb != NULL)
+                       heci_free_cb_private(priv_cb);
+
+               kfree(file_ext);
+       } else {
+               spin_lock_bh(&dev->device_lock);
+
+               if (dev->open_handle_count > 0)
+                       dev->open_handle_count--;
+
+               if (dev->iamthif_file_object == file
+                   && dev->iamthif_state != HECI_IAMTHIF_IDLE) {
+                       DBG("pthi canceled iamthif state %d\n",
+                           dev->iamthif_state);
+                       dev->iamthif_canceled = 1;
+                       if (dev->iamthif_state == HECI_IAMTHIF_READ_COMPLETE) {
+                               DBG("run next pthi iamthif cb\n");
+                               run_next_iamthif_cmd(dev);
+                       }
+               }
+
+               if (heci_clear_lists(dev, file))
+                       dev->iamthif_state = HECI_IAMTHIF_IDLE;
+
+               spin_unlock_bh(&dev->device_lock);
+       }
+       return rets;
+}
+
+static struct heci_cb_private *find_read_list_entry(
+               struct iamt_heci_device *dev,
+               struct heci_file_private *file_ext)
+{
+       struct heci_cb_private *priv_cb_pos = NULL;
+       struct heci_cb_private *priv_cb_next = NULL;
+       struct heci_file_private *file_ext_list_temp;
+
+       if (dev->read_list.status == 0
+           && !list_empty(&dev->read_list.heci_cb.cb_list)) {
+               DBG("remove read_list CB \n");
+               list_for_each_entry_safe(priv_cb_pos,
+                               priv_cb_next,
+                               &dev->read_list.heci_cb.cb_list, cb_list) {
+
+                       file_ext_list_temp = (struct heci_file_private *)
+                               priv_cb_pos->file_private;
+
+                       if ((file_ext_list_temp != NULL) &&
+                           heci_fe_same_id(file_ext, file_ext_list_temp))
+                               return priv_cb_pos;
+
+               }
+       }
+       return NULL;
+}
+
+/**
+ * heci_read - the read client message function.
+ *
+ * @file: pointer to file structure
+ * @ubuf: pointer to user buffer
+ * @length: buffer length
+ * @offset: data offset in buffer
+ *
+ * returns >=0 data length on success , <0 on error
+ */
+static ssize_t heci_read(struct file *file, char __user *ubuf,
+                        size_t length, loff_t *offset)
+{
+       int i;
+       int rets = 0, err = 0;
+       int if_num = iminor(file->f_dentry->d_inode);
+       struct heci_file_private *file_ext = file->private_data;
+       struct heci_cb_private *priv_cb_pos = NULL;
+       struct heci_cb_private *priv_cb = NULL;
+       struct iamt_heci_device *dev;
+
+       if (!heci_device)
+               return -ENODEV;
+
+       dev = pci_get_drvdata(heci_device);
+       if ((if_num != HECI_MINOR_NUMBER) || (!dev) || (!file_ext))
+               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);
+
+       spin_lock(&file_ext->file_lock);
+       if ((file_ext->sm_state & HECI_WD_STATE_INDEPENDENCE_MSG_SENT) == 0) {
+               spin_unlock(&file_ext->file_lock);
+               /* Do not allow to read watchdog client */
+               for (i = 0; i < dev->num_heci_me_clients; i++) {
+                       if (memcmp(&heci_wd_guid,
+                                  &dev->me_clients[i].props.protocol_name,
+                                  sizeof(struct guid)) == 0) {
+                               if (file_ext->me_client_id ==
+                                   dev->me_clients[i].client_id)
+                                       return -EBADF;
+                       }
+               }
+       } else {
+               file_ext->sm_state &= ~HECI_WD_STATE_INDEPENDENCE_MSG_SENT;
+               spin_unlock(&file_ext->file_lock);
+       }
+
+       if (file_ext == &dev->iamthif_file_ext) {
+               rets = pthi_read(dev, if_num, file, ubuf, length, offset);
+               goto out;
+       }
+
+       if (file_ext->read_cb && file_ext->read_cb->information > *offset) {
+               priv_cb = file_ext->read_cb;
+               goto copy_buffer;
+       } else if (file_ext->read_cb && file_ext->read_cb->information > 0 &&
+                  file_ext->read_cb->information <= *offset) {
+               priv_cb = file_ext->read_cb;
+               rets = 0;
+               goto free;
+       } else if ((!file_ext->read_cb || file_ext->read_cb->information == 0)
+                   && *offset > 0) {
+               /*Offset needs to be cleaned for contingous reads*/
+               *offset = 0;
+               rets = 0;
+               goto out;
+       }
+
+       spin_lock(&file_ext->read_io_lock);
+       err = heci_start_read(dev, if_num, file_ext);
+       if (err != 0 && err != -EBUSY) {
+               DBG("heci start read failure with status = %d\n", err);
+               spin_unlock(&file_ext->read_io_lock);
+               rets = err;
+               goto out;
+       }
+       if (HECI_READ_COMPLETE != file_ext->reading_state
+                       && !waitqueue_active(&file_ext->rx_wait)) {
+               if (file->f_flags & O_NONBLOCK) {
+                       rets = -EAGAIN;
+                       spin_unlock(&file_ext->read_io_lock);
+                       goto out;
+               }
+               spin_unlock(&file_ext->read_io_lock);
+
+               if (wait_event_interruptible(file_ext->rx_wait,
+                       (HECI_READ_COMPLETE == file_ext->reading_state
+                        || HECI_FILE_INITIALIZING == file_ext->state
+                        || HECI_FILE_DISCONNECTED == file_ext->state
+                        || HECI_FILE_DISCONNECTING == file_ext->state))) {
+                       if (signal_pending(current)) {
+                               rets = -EINTR;
+                               goto out;
+                       }
+                       return -ERESTARTSYS;
+               }
+
+               if (HECI_FILE_INITIALIZING == file_ext->state ||
+                   HECI_FILE_DISCONNECTED == file_ext->state ||
+                   HECI_FILE_DISCONNECTING == file_ext->state) {
+                       rets = -EBUSY;
+                       goto out;
+               }
+               spin_lock(&file_ext->read_io_lock);
+       }
+
+       priv_cb = file_ext->read_cb;
+
+       if (!priv_cb) {
+               spin_unlock(&file_ext->read_io_lock);
+               return -ENODEV;
+       }
+       if (file_ext->reading_state != HECI_READ_COMPLETE) {
+               spin_unlock(&file_ext->read_io_lock);
+               return 0;
+       }
+       spin_unlock(&file_ext->read_io_lock);
+       /* now copy the data to user space */
+copy_buffer:
+       DBG("priv_cb->response_buffer size - %d\n",
+           priv_cb->response_buffer.size);
+       DBG("priv_cb->information - %lu\n",
+           priv_cb->information);
+       if (length == 0 || ubuf == NULL ||
+           *offset > priv_cb->information) {
+               rets = -EMSGSIZE;
+               goto free;
+       }
+
+       /* length is being turncated to PAGE_SIZE, however, */
+       /* information size 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;
+               goto free;
+       }
+
+       rets = length;
+       *offset += length;
+       if ((unsigned long)*offset < priv_cb->information)
+               goto out;
+
+free:
+       spin_lock_bh(&dev->device_lock);
+       priv_cb_pos = find_read_list_entry(dev, file_ext);
+       /* Remove entry from read list */
+       if (priv_cb_pos != NULL)
+               list_del(&priv_cb_pos->cb_list);
+       spin_unlock_bh(&dev->device_lock);
+       heci_free_cb_private(priv_cb);
+       spin_lock(&file_ext->read_io_lock);
+       file_ext->reading_state = HECI_IDLE;
+       file_ext->read_cb = NULL;
+       file_ext->read_pending = 0;
+       spin_unlock(&file_ext->read_io_lock);
+out:   DBG("end heci read rets= %d\n", rets);
+       return rets;
+}
+
+/**
+ * heci_write - the write function.
+ *
+ * @file: pointer to file structure
+ * @ubuf: pointer to user buffer
+ * @length: buffer length
+ * @offset: data offset in buffer
+ *
+ * returns >=0 data length on success , <0 on error
+ */
+static ssize_t heci_write(struct file *file, const char __user *ubuf,
+                         size_t length, loff_t *offset)
+{
+       int rets = 0;
+       __u8 i;
+       int if_num = iminor(file->f_dentry->d_inode);
+       struct heci_file_private *file_ext = file->private_data;
+       struct heci_cb_private *priv_write_cb = NULL;
+       struct heci_msg_hdr heci_hdr;
+       struct iamt_heci_device *dev;
+       unsigned long currtime = get_seconds();
+
+       if (!heci_device)
+               return -ENODEV;
+
+       dev = pci_get_drvdata(heci_device);
+
+       if ((if_num != HECI_MINOR_NUMBER) || (!dev) || (!file_ext))
+               return -ENODEV;
+
+       spin_lock_bh(&dev->device_lock);
+
+       if (dev->heci_state != HECI_ENABLED) {
+               spin_unlock_bh(&dev->device_lock);
+               return -ENODEV;
+       }
+       if (file_ext == &dev->iamthif_file_ext) {
+               priv_write_cb = find_pthi_read_list_entry(dev, file);
+               if ((priv_write_cb != NULL) &&
+                    (((currtime - priv_write_cb->read_time) >
+                           IAMTHIF_READ_TIMER) ||
+                     (file_ext->reading_state == HECI_READ_COMPLETE))) {
+                       (*offset) = 0;
+                       list_del(&priv_write_cb->cb_list);
+                       heci_free_cb_private(priv_write_cb);
+                       priv_write_cb = NULL;
+               }
+       }
+
+       /* free entry used in read */
+       if (file_ext->reading_state == HECI_READ_COMPLETE) {
+               *offset = 0;
+               priv_write_cb = find_read_list_entry(dev, file_ext);
+               if (priv_write_cb != NULL) {
+                       list_del(&priv_write_cb->cb_list);
+                       heci_free_cb_private(priv_write_cb);
+                       priv_write_cb = NULL;
+                       spin_lock(&file_ext->read_io_lock);
+                       file_ext->reading_state = HECI_IDLE;
+                       file_ext->read_cb = NULL;
+                       file_ext->read_pending = 0;
+                       spin_unlock(&file_ext->read_io_lock);
+               }
+       } else if (file_ext->reading_state == HECI_IDLE &&
+                       file_ext->read_pending == 0)
+               (*offset) = 0;
+
+       spin_unlock_bh(&dev->device_lock);
+
+       priv_write_cb = kzalloc(sizeof(struct heci_cb_private), GFP_KERNEL);
+       if (!priv_write_cb)
+               return -ENOMEM;
+
+       priv_write_cb->file_object = file;
+       priv_write_cb->file_private = file_ext;
+       priv_write_cb->request_buffer.data = kmalloc(length, GFP_KERNEL);
+       if (!priv_write_cb->request_buffer.data) {
+               kfree(priv_write_cb);
+               return -ENOMEM;
+       }
+       DBG("length =%d\n", (int) length);
+
+       if (copy_from_user(priv_write_cb->request_buffer.data,
+               ubuf, length)) {
+               rets = -EFAULT;
+               goto fail;
+       }
+
+       spin_lock(&file_ext->file_lock);
+       file_ext->sm_state = 0;
+       if ((length == 4) &&
+           ((memcmp(heci_wd_state_independence_msg[0], ubuf, 4) == 0) ||
+            (memcmp(heci_wd_state_independence_msg[1], ubuf, 4) == 0) ||
+            (memcmp(heci_wd_state_independence_msg[2], ubuf, 4) == 0)))
+               file_ext->sm_state |= HECI_WD_STATE_INDEPENDENCE_MSG_SENT;
+       spin_unlock(&file_ext->file_lock);
+
+       INIT_LIST_HEAD(&priv_write_cb->cb_list);
+       if (file_ext == &dev->iamthif_file_ext) {
+               priv_write_cb->response_buffer.data =
+                   kmalloc(IAMTHIF_MTU, GFP_KERNEL);
+               if (!priv_write_cb->response_buffer.data) {
+                       rets = -ENOMEM;
+                       goto fail;
+               }
+               spin_lock_bh(&dev->device_lock);
+               if (dev->heci_state != HECI_ENABLED) {
+                       spin_unlock_bh(&dev->device_lock);
+                       rets = -ENODEV;
+                       goto fail;
+               }
+               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)) {
+
+                       spin_unlock_bh(&dev->device_lock);
+                       rets = -ENODEV;
+                       goto fail;
+               } else if ((length > dev->me_clients[i].props.max_msg_length)
+                           || (length <= 0)) {
+                       spin_unlock_bh(&dev->device_lock);
+                       rets = -EMSGSIZE;
+                       goto fail;
+               }
+
+
+               priv_write_cb->response_buffer.size = IAMTHIF_MTU;
+               priv_write_cb->major_file_operations = HECI_IOCTL;
+               priv_write_cb->information = 0;
+               priv_write_cb->request_buffer.size = length;
+               if (dev->iamthif_file_ext.state != HECI_FILE_CONNECTED) {
+                       spin_unlock_bh(&dev->device_lock);
+                       rets = -ENODEV;
+                       goto fail;
+               }
+
+               if (!list_empty(&dev->pthi_cmd_list.heci_cb.cb_list)
+                               || dev->iamthif_state != HECI_IAMTHIF_IDLE) {
+                       DBG("pthi_state = %d\n", (int) dev->iamthif_state);
+                       DBG("add PTHI cb to pthi cmd waiting list\n");
+                       list_add_tail(&priv_write_cb->cb_list,
+                                       &dev->pthi_cmd_list.heci_cb.cb_list);
+                       rets = length;
+               } else {
+                       DBG("call pthi write\n");
+                       rets = pthi_write(dev, priv_write_cb);
+
+                       if (rets != 0) {
+                               DBG("pthi write failed with status = %d\n",
+                                   rets);
+                               spin_unlock_bh(&dev->device_lock);
+                               goto fail;
+                       }
+                       rets = length;
+               }
+               spin_unlock_bh(&dev->device_lock);
+               return rets;
+       }
+
+       priv_write_cb->major_file_operations = HECI_WRITE;
+       /* make sure information is zero before we start */
+
+       priv_write_cb->information = 0;
+       priv_write_cb->request_buffer.size = length;
+
+       spin_lock(&file_ext->write_io_lock);
+       DBG("host client = %d, ME client = %d\n",
+           file_ext->host_client_id, file_ext->me_client_id);
+       if (file_ext->state != HECI_FILE_CONNECTED) {
+               rets = -ENODEV;
+               DBG("host client = %d,  is not connected to ME client = %d",
+                   file_ext->host_client_id,
+                   file_ext->me_client_id);
+
+               goto unlock;
+       }
+       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;
+       }
+       if (length > dev->me_clients[i].props.max_msg_length || length <= 0) {
+               rets = -EINVAL;
+               goto unlock;
+       }
+       priv_write_cb->file_private = file_ext;
+
+       spin_lock_bh(&dev->device_lock);
+       if (flow_ctrl_creds(dev, file_ext) &&
+               dev->host_buffer_is_empty) {
+               spin_unlock_bh(&dev->device_lock);
+               dev->host_buffer_is_empty = 0;
+               if (length > ((((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 = length;
+                       heci_hdr.msg_complete = 1;
+               }
+               heci_hdr.host_addr = file_ext->host_client_id;
+               heci_hdr.me_addr = file_ext->me_client_id;
+               heci_hdr.reserved = 0;
+               DBG("call heci_write_message header=%08x.\n",
+                   *((__u32 *) &heci_hdr));
+               spin_unlock(&file_ext->write_io_lock);
+               /*  protect heci low level write */
+               spin_lock_bh(&dev->device_lock);
+               if (!heci_write_message(dev, &heci_hdr,
+                       (unsigned char *) (priv_write_cb->request_buffer.data),
+                       heci_hdr.length)) {
+
+                       spin_unlock_bh(&dev->device_lock);
+                       heci_free_cb_private(priv_write_cb);
+                       rets = -ENODEV;
+                       priv_write_cb->information = 0;
+                       return rets;
+               }
+               file_ext->writing_state = HECI_WRITING;
+               priv_write_cb->information = heci_hdr.length;
+               if (heci_hdr.msg_complete) {
+                       flow_ctrl_reduce(dev, file_ext);
+                       list_add_tail(&priv_write_cb->cb_list,
+                                     &dev->write_waiting_list.heci_cb.cb_list);
+               } else {
+                       list_add_tail(&priv_write_cb->cb_list,
+                                     &dev->write_list.heci_cb.cb_list);
+               }
+               spin_unlock_bh(&dev->device_lock);
+
+       } else {
+
+               spin_unlock_bh(&dev->device_lock);
+               priv_write_cb->information = 0;
+               file_ext->writing_state = HECI_WRITING;
+               spin_unlock(&file_ext->write_io_lock);
+               list_add_tail(&priv_write_cb->cb_list,
+                             &dev->write_list.heci_cb.cb_list);
+       }
+       return length;
+
+unlock:
+       spin_unlock(&file_ext->write_io_lock);
+fail:
+       heci_free_cb_private(priv_write_cb);
+       return rets;
+
+}
+
+/**
+ * heci_ioctl - the IOCTL function
+ *
+ * @inode: pointer to inode structure
+ * @file: pointer to file structure
+ * @cmd: ioctl command
+ * @data: pointer to heci message structure
+ *
+ * returns 0 on success , <0 on error
+ */
+static int heci_ioctl(struct inode *inode, struct file *file,
+                     unsigned int cmd, unsigned long data)
+{
+       int rets = 0;
+       int if_num = iminor(inode);
+       struct heci_file_private *file_ext = file->private_data;
+       /* in user space */
+       struct heci_message_data __user *u_msg;
+       struct heci_message_data k_msg; /* all in kernel on the stack */
+       struct iamt_heci_device *dev;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       if (!heci_device)
+               return -ENODEV;
+
+       dev = pci_get_drvdata(heci_device);
+       if ((if_num != HECI_MINOR_NUMBER) || (!dev) || (!file_ext))
+               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);
+
+       /* first copy from user all data needed */
+       u_msg = (struct heci_message_data __user *)data;
+       if (copy_from_user(&k_msg, u_msg, sizeof(k_msg))) {
+               DBG("first copy from user all data needed filled\n");
+               return -EFAULT;
+       }
+       DBG("user message size is %d\n", k_msg.size);
+
+       switch (cmd) {
+       case IOCTL_HECI_GET_VERSION:
+               DBG(": IOCTL_HECI_GET_VERSION\n");
+               rets = heci_ioctl_get_version(dev, if_num, u_msg, k_msg,
+                                             file_ext);
+               break;
+
+       case IOCTL_HECI_CONNECT_CLIENT:
+               DBG(": IOCTL_HECI_CONNECT_CLIENT.\n");
+               rets = heci_ioctl_connect_client(dev, if_num, u_msg, k_msg,
+                                                file);
+               break;
+
+       case IOCTL_HECI_WD:
+               DBG(": IOCTL_HECI_WD.\n");
+               rets = heci_ioctl_wd(dev, if_num, k_msg, file_ext);
+               break;
+
+       case IOCTL_HECI_BYPASS_WD:
+               DBG(": IOCTL_HECI_BYPASS_WD.\n");
+               rets = heci_ioctl_bypass_wd(dev, if_num, k_msg, file_ext);
+               break;
+
+       default:
+               rets = -EINVAL;
+               break;
+       }
+       return rets;
+}
+
+/**
+ * heci_poll - the poll function
+ *
+ * @file: pointer to file structure
+ * @wait: pointer to poll_table structure
+ *
+ * returns poll mask
+ */
+static unsigned int heci_poll(struct file *file, poll_table *wait)
+{
+       int if_num = iminor(file->f_dentry->d_inode);
+       unsigned int mask = 0;
+       struct heci_file_private *file_ext = file->private_data;
+       struct iamt_heci_device *dev;
+
+       if (!heci_device)
+               return mask;
+
+       dev = pci_get_drvdata(heci_device);
+
+       if ((if_num != HECI_MINOR_NUMBER) || (!dev) || (!file_ext))
+               return mask;
+
+       spin_lock_bh(&dev->device_lock);
+       if (dev->heci_state != HECI_ENABLED) {
+               spin_unlock_bh(&dev->device_lock);
+               return mask;
+       }
+       spin_unlock_bh(&dev->device_lock);
+
+       if (file_ext == &dev->iamthif_file_ext) {
+               poll_wait(file, &dev->iamthif_file_ext.wait, wait);
+               spin_lock(&dev->iamthif_file_ext.file_lock);
+               if (dev->iamthif_state == HECI_IAMTHIF_READ_COMPLETE
+                   && dev->iamthif_file_object == file) {
+                       mask |= (POLLIN | POLLRDNORM);
+                       spin_lock_bh(&dev->device_lock);
+                       DBG("run next pthi cb\n");
+                       run_next_iamthif_cmd(dev);
+                       spin_unlock_bh(&dev->device_lock);
+               }
+               spin_unlock(&dev->iamthif_file_ext.file_lock);
+
+       } else{
+               poll_wait(file, &file_ext->tx_wait, wait);
+               spin_lock(&file_ext->write_io_lock);
+               if (HECI_WRITE_COMPLETE == file_ext->writing_state)
+                       mask |= (POLLIN | POLLRDNORM);
+
+               spin_unlock(&file_ext->write_io_lock);
+       }
+
+       return mask;
+}
+
+#ifdef CONFIG_PM
+static int heci_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+       struct iamt_heci_device *dev = pci_get_drvdata(pdev);
+       int err = 0;
+
+       spin_lock_bh(&dev->device_lock);
+       if (dev->reinit_tsk != NULL) {
+               kthread_stop(dev->reinit_tsk);
+               dev->reinit_tsk = NULL;
+       }
+       spin_unlock_bh(&dev->device_lock);
+
+       /* Stop watchdog if exists */
+       del_timer_sync(&dev->wd_timer);
+       if (dev->wd_file_ext.state == HECI_FILE_CONNECTED
+           && dev->wd_timeout) {
+               spin_lock_bh(&dev->device_lock);
+               g_sus_wd_timeout = dev->wd_timeout;
+               dev->wd_timeout = 0;
+               dev->wd_due_counter = 0;
+               memcpy(dev->wd_data, heci_stop_wd_params,
+                                       HECI_WD_PARAMS_SIZE);
+               dev->stop = 1;
+               if (dev->host_buffer_is_empty &&
+                   flow_ctrl_creds(dev, &dev->wd_file_ext)) {
+                       dev->host_buffer_is_empty = 0;
+                       if (!heci_send_wd(dev))
+                               DBG("send stop WD failed\n");
+                       else
+                               flow_ctrl_reduce(dev, &dev->wd_file_ext);
+
+                       dev->wd_pending = 0;
+               } else {
+                       dev->wd_pending = 1;
+               }
+               spin_unlock_bh(&dev->device_lock);
+               dev->wd_stoped = 0;
+
+               err = wait_event_interruptible_timeout(dev->wait_stop_wd,
+                                                      (dev->wd_stoped),
+                                                      10 * HZ);
+               if (!dev->wd_stoped)
+                       DBG("stop wd failed to complete.\n");
+               else {
+                       DBG("stop wd complete %d.\n", err);
+                       err = 0;
+               }
+       }
+       /* Set new heci state */
+       spin_lock_bh(&dev->device_lock);
+       if (dev->heci_state == HECI_ENABLED ||
+           dev->heci_state == HECI_RECOVERING_FROM_RESET) {
+               dev->heci_state = HECI_POWER_DOWN;
+               heci_reset(dev, 0);
+       }
+       spin_unlock_bh(&dev->device_lock);
+
+       pci_save_state(pdev);
+
+       pci_disable_device(pdev);
+       free_irq(pdev->irq, dev);
+
+       pci_set_power_state(pdev, PCI_D3hot);
+
+       return err;
+}
+
+static int heci_resume(struct pci_dev *pdev)
+{
+       struct iamt_heci_device *dev;
+       int err = 0;
+
+       pci_set_power_state(pdev, PCI_D0);
+       pci_restore_state(pdev);
+
+       dev = pci_get_drvdata(pdev);
+       if (!dev)
+               return -ENODEV;
+
+       /* request and enable interrupt   */
+       err = request_irq(pdev->irq, heci_isr_interrupt, IRQF_SHARED,
+                       heci_driver_name, dev);
+       if (err) {
+               printk(KERN_ERR "heci: Request_irq failure. irq = %d \n",
+                      pdev->irq);
+               return err;
+       }
+
+       spin_lock_bh(&dev->device_lock);
+       dev->heci_state = HECI_POWER_UP;
+       heci_reset(dev, 1);
+       spin_unlock_bh(&dev->device_lock);
+
+       /* Start watchdog if stopped in suspend */
+       if (g_sus_wd_timeout != 0) {
+               dev->wd_timeout = g_sus_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));
+               dev->wd_due_counter = 1;
+
+               if (dev->wd_timeout)
+                       mod_timer(&dev->wd_timer, jiffies);
+
+               g_sus_wd_timeout = 0;
+       }
+       return err;
+}
+#endif
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_DESCRIPTION("Intel(R) Management Engine Interface");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_VERSION(HECI_DRIVER_VERSION);