diff options
Diffstat (limited to 'drivers/staging/heci/heci_main.c')
-rw-r--r-- | drivers/staging/heci/heci_main.c | 1564 |
1 files changed, 1564 insertions, 0 deletions
diff --git a/drivers/staging/heci/heci_main.c b/drivers/staging/heci/heci_main.c new file mode 100644 index 000000000000..00e44c781428 --- /dev/null +++ b/drivers/staging/heci/heci_main.c @@ -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); |