/* * This software is a driver for ACCES I/O Products, Inc. PCI cards. * Copyright (C) 2007 ACCES I/O Products, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation. * This software is released under version 2 of the GPL. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * In addition ACCES provides other licenses with its software at customer * request. For more information please contact the ACCES software department * at (800)-326-1649 or visit www.accesio.com */ #include #include #include #include #include #include #include #include #include #include #include #include "apci.h" MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Jay Dolan "); MODULE_DESCRIPTION("Driver for ACCES PCI cards"); MODULE_VERSION("1.3"); /* global variables */ static int major_num; /* our major number */ struct cdev apci_cdev; typedef struct { __u32 start; __u32 end; __u32 length; unsigned long flags; void *mapped_address; } io_region; struct device_info_structure { __u32 dev_id; io_region regions[6], plx_region; int irq; int irq_capable; /* is the card even able to generate irqs? */ int waiting_for_irq; /* boolean for if the user has requested an IRQ */ int irq_cancelled; /* boolean for if the user has cancelled the wait */ wait_queue_head_t wait_queue; spinlock_t irq_lock; struct device_info_structure *next; }; typedef struct device_info_structure dev_info; enum ADDRESS_TYPE {INVALID = 0, IO, MEM}; typedef enum ADDRESS_TYPE address_type; dev_info *head = NULL; /* fops function declarations */ int apci_open(struct inode *inode, struct file *filp); int apci_release(struct inode *inode, struct file *filp); ssize_t apci_read(struct file *filp, char *buf, size_t count, loff_t *f_pos); ssize_t apci_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos); int apci_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); /* fops structure */ struct file_operations apci_fops = { read : apci_read, write : apci_write, open : apci_open, release : apci_release, ioctl : apci_ioctl }; /* other useful functions */ address_type is_valid_addr (dev_info *driver_data, int bar, int addr); static struct pci_device_id ids[] = { { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_24H), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_24D), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_24H_C), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_24D_C), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_24S), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_48), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_48S), }, { PCI_DEVICE(A_VENDOR_ID, P104_DIO_48S), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_48H), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_48HS), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_72), }, { PCI_DEVICE(A_VENDOR_ID, P104_DIO_96), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_96), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_96CT), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_96C3), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DIO_120), }, { PCI_DEVICE(A_VENDOR_ID, PCI_AI12_16), }, { PCI_DEVICE(A_VENDOR_ID, PCI_AI12_16A), }, { PCI_DEVICE(A_VENDOR_ID, PCI_AIO12_16), }, { PCI_DEVICE(A_VENDOR_ID, PCI_A12_16A), }, { PCI_DEVICE(A_VENDOR_ID, LPCI_A16_16A), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DA12_16), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DA12_8), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DA12_6), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DA12_4), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DA12_2), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DA12_16V), }, { PCI_DEVICE(A_VENDOR_ID, PCI_DA12_8V), }, { PCI_DEVICE(A_VENDOR_ID, LPCI_IIRO_8), }, { PCI_DEVICE(A_VENDOR_ID, PCI_IIRO_8), }, { PCI_DEVICE(A_VENDOR_ID, PCI_IIRO_16), }, { PCI_DEVICE(A_VENDOR_ID, PCI_IDI_48), }, { PCI_DEVICE(A_VENDOR_ID, PCI_IDO_48), }, { PCI_DEVICE(A_VENDOR_ID, PCI_IDIO_16), }, { PCI_DEVICE(A_VENDOR_ID, PCI_WDG_2S), }, { PCI_DEVICE(A_VENDOR_ID, PCI_WDG_CSM), }, { PCI_DEVICE(A_VENDOR_ID, PCI_WDG_IMPAC), }, { 0, } }; MODULE_DEVICE_TABLE(pci, ids); address_type is_valid_addr(dev_info *driver_data, int bar, int addr) { /* if it is a valid bar */ if (driver_data->regions[bar].start != 0) { /* if it is within the valid range */ if (driver_data->regions[bar].length >= addr) { /* if it is an I/O region */ if (driver_data->regions[bar].flags & IORESOURCE_IO) { #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: Valid I/O range.\n"); #endif return IO; } else { #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: Valid Mem range.\n"); #endif return MEM; } } } #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: Invalid addr.\n"); #endif return INVALID; } irqreturn_t apci_interrupt(int irq, void *dev_id) { dev_info *driver_data; __u8 byte; driver_data = (dev_info *) dev_id; /* The first thing we do is check to see if the card is causing an IRQ. * If it is then we can proceed to clear the IRQ. Otherwise let * Linux know that it wasn't us. */ byte = inb(driver_data->plx_region.start + 0x4C); if ((byte & 4) == 0) { return IRQ_NONE; /* not me */ } /* Now we know it was us that caused the IRQ so clear it from * the card/board. */ #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: Entering ISR\n"); #endif /* Handle interrupt based on the device ID for the board. */ switch (driver_data->dev_id) { case PCI_DIO_24H: case PCI_DIO_24D: case PCI_DIO_24H_C: case PCI_DIO_24D_C: case PCI_DIO_24S: case PCI_DIO_48: case PCI_DIO_48S: outb(0, driver_data->regions[2].start + 0x0f); break; /* These cards don't have the IRQ simply "Cleared", * it must be disabled then re-enabled. */ case PCI_DIO_72: case PCI_DIO_96: case PCI_DIO_96CT: case PCI_DIO_96C3: case PCI_DIO_120: outb(0, driver_data->regions[2].start + 0x1e); outb(0, driver_data->regions[2].start + 0x1f); break; case PCI_DA12_16: case PCI_DA12_8: case PCI_DA12_6: case PCI_DA12_4: case PCI_DA12_2: case PCI_DA12_16V: case PCI_DA12_8V: byte = inb(driver_data->regions[2].start + 0xC); break; case PCI_WDG_CSM: outb(0, driver_data->regions[2].start + 0x9); outb(0, driver_data->regions[2].start + 0x4); break; case PCI_IIRO_8: case PCI_IIRO_16: case PCI_IDIO_16: case LPCI_IIRO_8: outb(0, driver_data->regions[2].start + 0x1); break; case PCI_IDI_48: byte = inb(driver_data->regions[2].start + 0x7); break; case PCI_AI12_16: case PCI_AI12_16A: case PCI_AIO12_16: case PCI_A12_16A: /* Clear the FIFO interrupt enable bits, but leave * the counter enabled. Otherwise the IRQ will not * go away and user code will never run as the machine * will hang in a never-ending IRQ loop. The userland * irq routine must re-enable the interrupts if desired. */ outb( 0x01, driver_data->regions[2].start + 0x4); byte = inb(driver_data->regions[2].start + 0x4); break; case LPCI_A16_16A: outb(0, driver_data->regions[2].start + 0xc); outb(0x10, driver_data->regions[2].start + 0xc); break; case P104_DIO_96: /* unknown at this time 6-FEB-2007 */ case P104_DIO_48S: /* unknown at this time 6-FEB-2007 */ break; }; /* Check to see if we were actually waiting for an IRQ. If we were * then we need to wake the queue associated with this device. * Right now it is not possible for any other code sections that access * the critical data to interrupt us so we won't disable other IRQs. */ spin_lock(&(driver_data->irq_lock)); if (driver_data->waiting_for_irq) { driver_data->waiting_for_irq = 0; spin_unlock(&(driver_data->irq_lock)); wake_up_interruptible(&(driver_data->wait_queue)); } else { spin_unlock(&(driver_data->irq_lock)); } return IRQ_HANDLED; } /* Do probing type stuff here. * Like calling request_region(); */ static int probe(struct pci_dev *dev, const struct pci_device_id *id) { int count, status, plx_bar; struct resource *presource; dev_info *driver_data, *current_dev; #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: entering probe.\n"); #endif if (pci_enable_device(dev)) { return -ENODEV; } driver_data = kmalloc(sizeof(dev_info), GFP_KERNEL); if (driver_data == NULL) return -ENOMEM; /* Fill up the dev_info structure for this card with starting data. */ driver_data->irq = 0; driver_data->irq_capable = 0; for (count = 0; count < 6; count++) { driver_data->regions[count].start = 0; driver_data->regions[count].end = 0; driver_data->regions[count].flags = 0; driver_data->regions[count].mapped_address = NULL; driver_data->regions[count].length = 0; } driver_data->plx_region.start = 0; driver_data->plx_region.end = 0; driver_data->plx_region.length = 0; driver_data->plx_region.mapped_address = NULL; driver_data->dev_id = id->device; spin_lock_init(&(driver_data->irq_lock)); driver_data->next = NULL; /* All of our cards use either BAR[0] or BAR[1] as an I/O mapped * address. Reading the byte at this address + 0x4C and checking the * value of bit 2 will tell us if the card currently is signalling an * IRQ. */ if (pci_resource_flags(dev, 0) & IORESOURCE_IO) { plx_bar = 0; } else { plx_bar = 1; } driver_data->plx_region.start = pci_resource_start(dev, plx_bar); driver_data->plx_region.end = pci_resource_end(dev, plx_bar); driver_data->plx_region.length = driver_data->plx_region.end - driver_data->plx_region.start + 1; #ifdef A_PCI_DEBUG printk( KERN_INFO "apci: plx_region.start = %08x\n", driver_data->plx_region.start ); printk( KERN_INFO "apci: plx_region.end = %08x\n", driver_data->plx_region.end ); printk( KERN_INFO "apci: plx_region.length= %08x\n", driver_data->plx_region.length ); #endif /* TODO: request and remap the region for plx */ presource = request_region(driver_data->plx_region.start, driver_data->plx_region.length, "apci"); if (presource == NULL) { /* We couldn't get the region. We have only allocated * driver_data so release it and return an error. */ kfree(driver_data); return -EACCES; /* could not access io region */ } /* This switch statement is divided into groups based on which BARS * each card uses and if they have an IRQ. * group1 : uses BAR[2] for its base address and can generate IRQS * group2 : uses BAR[2] for its base address and can't generate IRQS * group3 : uses BAR[2] and BAR[3] For base address. Can generate IRQ * group4 : uses BAR[2] and BAR[3] for base address. Can't generate IRQ */ switch(driver_data->dev_id) { case PCI_DIO_24H:/* group1 */ case PCI_DIO_24D: case PCI_DIO_24H_C: case PCI_DIO_24D_C: case PCI_DIO_24S: case PCI_DIO_48: case PCI_DIO_48S: case P104_DIO_48S: // case PCI_DIO_48H: identical to another // case PCI_DIO_48HS: identical to another case PCI_DIO_72: case PCI_DIO_96: case PCI_DIO_96C3: case PCI_DIO_120: case PCI_AI12_16: case PCI_AI12_16A: case PCI_A12_16A: case LPCI_IIRO_8: case PCI_IIRO_8: case PCI_IIRO_16: case PCI_IDI_48: case PCI_IDIO_16: driver_data->regions[2].start = pci_resource_start(dev, 2); driver_data->regions[2].end = pci_resource_end(dev, 2); driver_data->regions[2].flags = pci_resource_flags(dev, 2); driver_data->regions[2].length = driver_data->regions[2].end - driver_data->regions[2].start + 1; driver_data->irq = dev->irq; driver_data->irq_capable = 1; #ifdef A_PCI_DEBUG printk( KERN_INFO "apci: regions[2].start = %08x\n", driver_data->regions[2].start ); printk( KERN_INFO "apci: regions[2].end = %08x\n", driver_data->regions[2].end ); printk( KERN_INFO "apci: regions[2].length= %08x\n", driver_data->regions[2].length ); printk( KERN_INFO "apci: regions[2].flags = %lx\n", driver_data->regions[2].flags ); printk( KERN_INFO "apci: irq = %d\n", driver_data->irq ); #endif break; case PCI_IDO_48: /* group2 */ driver_data->regions[2].start = pci_resource_start(dev, 2); driver_data->regions[2].end = pci_resource_end(dev, 2); driver_data->regions[2].flags = pci_resource_flags(dev, 2); driver_data->regions[2].length = driver_data->regions[2].end - driver_data->regions[2].start + 1; break; case LPCI_A16_16A: case PCI_DA12_16: /* group3 */ case PCI_DA12_8: driver_data->regions[2].start = pci_resource_start(dev, 2); driver_data->regions[2].end = pci_resource_end(dev, 2); driver_data->regions[2].flags = pci_resource_flags(dev, 2); driver_data->regions[2].length = driver_data->regions[2].end - driver_data->regions[2].start + 1; driver_data->regions[3].start = pci_resource_start(dev, 3); driver_data->regions[3].end = pci_resource_end(dev, 3); driver_data->regions[3].flags = pci_resource_flags(dev, 3); driver_data->regions[3].length = driver_data->regions[3].end - driver_data->regions[3].start + 1; driver_data->irq = dev->irq; driver_data->irq_capable = 1; break; case PCI_DA12_6: /* group4 */ case PCI_DA12_4: case PCI_DA12_2: driver_data->regions[2].start = pci_resource_start(dev, 2); driver_data->regions[2].end = pci_resource_end(dev, 2); driver_data->regions[2].flags = pci_resource_flags(dev, 2); driver_data->regions[2].length = driver_data->regions[2].end - driver_data->regions[2].start + 1; driver_data->regions[3].start = pci_resource_start(dev, 3); driver_data->regions[3].end = pci_resource_end(dev, 3); driver_data->regions[3].flags = pci_resource_flags(dev, 3); driver_data->regions[3].length = driver_data->regions[3].end - driver_data->regions[3].start + 1; break; }; /* Now request the regions. */ for (count = 0; count < 6; count++) { if (driver_data->regions[count].start == 0) { continue; /* invalid region */ } if (driver_data->regions[count].flags & IORESOURCE_IO) { presource = request_region(driver_data->regions[count].start, driver_data->regions[count].length, "apci"); } else { presource = request_mem_region(driver_data->regions[count].start, driver_data->regions[count].length, "apci"); if (presource != NULL) { driver_data->regions[count].mapped_address = ioremap(driver_data->regions[count].start, driver_data->regions[count].length); } } if (presource == NULL) { /* If we failed to allocate the region. */ count--; while (count >= 0) { if (driver_data->regions[count].start != 0) { if (driver_data->regions[count].flags & IORESOURCE_IO) { /* if it is a valid region */ release_region(driver_data->regions[count].start, driver_data->regions[count].length); } else { iounmap(driver_data->regions[count].mapped_address); release_region(driver_data->regions[count].start, driver_data->regions[count].length); } } } kfree(driver_data); return -EACCES; /* couldn't access the region */ } } if (driver_data->irq_capable) { status = request_irq((unsigned int) driver_data->irq, apci_interrupt, IRQF_SHARED, "apci", driver_data); if (status) { #ifdef A_PCI_DEBUG printk(KERN_ERR "apci: Could not allocate irq."); #endif release_region(driver_data->plx_region.start, driver_data->plx_region.length); /* Unmap the regions, free up the kmalloced memory * and return an error value. */ for (count = 0; count < 6; count ++) { if (driver_data->regions[count].start == 0) { continue; /* invalid region */ } if (driver_data->regions[count].flags & IORESOURCE_IO) { release_region(driver_data->regions[count].start, driver_data->regions[count].length); } else { iounmap(driver_data->regions[count].mapped_address); release_mem_region(driver_data->regions[count].start, driver_data->regions[count].length); } } kfree(driver_data); return status; } else { /* We have been granted the irq so init the wait * queue and set its next to NULL */ init_waitqueue_head(&(driver_data->wait_queue)); } } if (head == NULL) { #ifdef A_PCI_DEBUG printk( KERN_INFO "apci: head = %08x\n", (unsigned int)driver_data ); #endif head = driver_data; pci_set_drvdata(dev, head); } else { current_dev = head; while (current_dev->next != NULL) { current_dev = current_dev->next; } current_dev->next = driver_data; } #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: probe finished.\n"); #endif return 0; } static void remove(struct pci_dev *dev) { dev_info *driver_data, *current_dev; int count; driver_data = (dev_info *) pci_get_drvdata(dev); /* None of our cards support any sort of removal with the machine * powered on. We may get called multiple times when the driver is * being unloaded. The first time we will remove all our memory * regions and unregister IRQs. After that we will just return. */ if (driver_data == NULL) { return; } do { release_region(driver_data->plx_region.start, driver_data->plx_region.length); for (count = 0; count < 6; count ++) { if (driver_data->regions[count].start == 0) { continue; /* invalid region */ } if (driver_data->regions[count].flags & IORESOURCE_IO) { release_region(driver_data->regions[count].start, driver_data->regions[count].length); } else { iounmap(driver_data->regions[count].mapped_address); release_mem_region(driver_data->regions[count].start, driver_data->regions[count].length); } } /* Free any claimed irq. */ if (driver_data->irq_capable) { free_irq((unsigned int) driver_data->irq, driver_data); } current_dev = driver_data->next; kfree(driver_data); driver_data = current_dev; } while (driver_data != NULL); pci_set_drvdata(dev, NULL); } static struct pci_driver pci_driver = { .name = "apci", .id_table = ids, .probe = probe, .remove = remove, }; int apci_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { int count; int status; dev_info *driver_data; info_struct info; iopack io_pack; unsigned long device_index, flags; #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: entering ioctl\n"); #endif switch (cmd) { case apci_get_devices_ioctl: #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: entering get_devices\n"); #endif if (filp->private_data == NULL) return 0; #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: private_data was not null\n"); #endif driver_data = filp->private_data; count = 0; do { count++; driver_data = driver_data->next; } while (driver_data != NULL); #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: get_devices returning %d\n", count); #endif return count; break; case apci_get_device_info_ioctl: status = access_ok(VERIFY_WRITE, arg, sizeof(info_struct)); if (status == 0) return -EACCES; status = copy_from_user(&info, (info_struct *) arg, sizeof(info_struct)); driver_data = head; count = 0; while (count < info.device_index) { if (driver_data != NULL) { driver_data = driver_data->next; } count++; } if (driver_data == NULL) return -ENXIO; /* invalid device index */ info.dev_id = driver_data->dev_id; for (count = 0; count < 6; count++) { info.base_addresses[count] = driver_data->regions[count].start; } status = copy_to_user((info_struct *) arg, &info, sizeof(info_struct)); break; case apci_write_ioctl: status = access_ok (VERIFY_WRITE, arg, sizeof(iopack)); if (status == 0) { printk(KERN_ERR "apci: access_ok failed\n"); return -EACCES; /* TODO: FIND appropriate return value */ } status = copy_from_user(&io_pack, (iopack *) arg, sizeof(iopack)); driver_data = filp->private_data; count = 0; while (count < io_pack.device_index) { if (driver_data != NULL) { driver_data = driver_data->next; } count++; } if (driver_data == NULL) return -ENXIO; /* invalid device index */ switch(is_valid_addr(driver_data, io_pack.bar, io_pack.offset)) { case IO: #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: performing I/O write to %X\n", driver_data->regions[io_pack.bar].start + io_pack.offset); #endif switch(io_pack.size) { case BYTE: outb(io_pack.data, driver_data->regions[io_pack.bar].start + io_pack.offset); break; case WORD: outw(io_pack.data, driver_data->regions[io_pack.bar].start + io_pack.offset); break; case DWORD: outl(io_pack.data, driver_data->regions[io_pack.bar].start + io_pack.offset); break; }; break; case MEM: switch(io_pack.size) { case BYTE: iowrite8(io_pack.data, driver_data->regions[io_pack.bar].mapped_address + io_pack.offset); break; case WORD: iowrite16(io_pack.data, driver_data->regions[io_pack.bar].mapped_address + io_pack.offset); break; case DWORD: iowrite32(io_pack.data, driver_data->regions[io_pack.bar].mapped_address + io_pack.offset); break; }; break; case INVALID: return -EFAULT; break; }; break; case apci_read_ioctl: status = access_ok(VERIFY_WRITE, arg, sizeof(iopack)); if (status == 0) return -EACCES; /* TODO: Find a better return code */ status = copy_from_user(&io_pack, (iopack *) arg, sizeof(iopack)); driver_data = head; count = 0; while (count < io_pack.device_index) { if (driver_data != NULL) { driver_data = driver_data->next; } count++; } if (driver_data == NULL) return -ENXIO; /* invalid device index */ switch (is_valid_addr(driver_data, io_pack.bar, io_pack.offset)) { case IO: switch(io_pack.size) { case BYTE: io_pack.data = inb(driver_data->regions[io_pack.bar].start + io_pack.offset); break; case WORD: io_pack.data = inw(driver_data->regions[io_pack.bar].start + io_pack.offset); break; case DWORD: io_pack.data = inl(driver_data->regions[io_pack.bar].start + io_pack.offset); break; }; break; case MEM: switch(io_pack.size) { case BYTE: io_pack.data = ioread8(driver_data->regions[io_pack.bar].mapped_address + io_pack.offset); break; case WORD: io_pack.data = ioread16(driver_data->regions[io_pack.bar].mapped_address + io_pack.offset); break; case DWORD: io_pack.data = ioread32(driver_data->regions[io_pack.bar].mapped_address + io_pack.offset); break; }; break; case INVALID: return -EFAULT; break; }; #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: performed read from %X\n", driver_data->regions[io_pack.bar].start + io_pack.offset); #endif status = copy_to_user((iopack *)arg, &io_pack, sizeof(iopack)); break; case apci_wait_for_irq_ioctl: #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: enter wait_for_IRQ.\n"); #endif device_index = arg; driver_data = head; for (count = 0; count < device_index; count++) { if (driver_data != NULL) { driver_data = driver_data->next; } } if (driver_data == NULL) return -ENXIO; /* invalid device index */ /* From this point on we want exclusive access to stuff * and an interrupt could dead lock the system so we * will get the spin lock and disable IRQs. */ #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: Acquiring spin lock\n"); #endif spin_lock_irqsave(&(driver_data->irq_lock), flags); if (driver_data->waiting_for_irq == 1) { /* if we are already waiting for an IRQ on * this device. */ spin_unlock_irqrestore (&(driver_data->irq_lock), flags); return -EALREADY; /* TODO: find a better return code */ } else { driver_data->waiting_for_irq = 1; driver_data->irq_cancelled = 0; } spin_unlock_irqrestore (&(driver_data->irq_lock), flags); #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: Released spin lock\n"); #endif wait_event_interruptible(driver_data->wait_queue, driver_data->waiting_for_irq == 0); if (driver_data->irq_cancelled == 1) return -ECANCELED; break; case apci_cancel_wait_ioctl: #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: Cancel wait_for_irq.\n"); #endif device_index = arg; driver_data = head; for ( count = 0; count < device_index; count++) { if (driver_data != NULL) { driver_data = driver_data->next; } } if (driver_data == NULL) return -ENXIO; /* invalid device index */ spin_lock_irqsave(&(driver_data->irq_lock), flags); if (driver_data->waiting_for_irq == 0) { spin_unlock_irqrestore(&(driver_data->irq_lock), flags); return -EALREADY; } driver_data->irq_cancelled = 1; driver_data->waiting_for_irq = 0; spin_unlock_irqrestore(&(driver_data->irq_lock), flags); wake_up_interruptible(&(driver_data->wait_queue)); break; }; return 0; } ssize_t apci_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos) { /* For now we will pretend we wrote the data so it will kind of be * like writing to /dev/NULL I _think_. Can't just return 0 as the * kernel will just keeping retrying the operation. */ return count; } ssize_t apci_read (struct file *filp, char *buf, size_t count, loff_t *f_pos) { /* I think this will just make any read from the device look like * End Of File which is not such a bad thing */ return 0; } int apci_open (struct inode *inode, struct file *filp) { /* Most of the stuff that happened in the open function for the ISA * driver takes place in probe for the PCI driver. */ #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: entering apci_open\n"); #endif if (head == NULL) printk (KERN_WARNING "apci: opened with NULL"); filp->private_data = head; return 0; } int apci_release (struct inode *inode, struct file *filp) { /* Most of the functionality that went into release for the ISA driver * is now removed for the PCI driver so just return 0. */ return 0; } static int apci_init(void) { /* Get our major device number, for now only request 1 minor number. */ int result; dev_t dev = MKDEV(0, 0); /* set it to 0 for error check after alloc */ #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: entering apci_init\n"); #endif result = alloc_chrdev_region(&dev, 0, 1, "apci"); major_num = MAJOR(dev); if (!major_num) { return result; /* return error code if we didn't get major # */ } else { #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: major num granted %d\n", major_num); #endif } cdev_init(&apci_cdev, &apci_fops); apci_cdev.owner = THIS_MODULE; apci_cdev.ops = &apci_fops; result = cdev_add(&apci_cdev, dev, 1); if (result < 0) { #ifdef A_PCI_DEBUG printk(KERN_INFO "apci: cdev_add failed in apci_init\n"); #endif return result; } return pci_register_driver(&pci_driver); } static void apci_exit(void) { #ifdef A_PCI_DEBUG printk(KERN_INFO"apci: entering apci_EXIT\n"); #endif /* Need to release our major number. */ unregister_chrdev_region(MKDEV(major_num, 0), 1); cdev_del(&apci_cdev); pci_unregister_driver(&pci_driver); /* This function can't fail and no return value is expected. */ } module_init(apci_init); module_exit(apci_exit); /* end of file */