| /* |
| * 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); |