/* * PCIDEV: PCI host device mapping * Copyright (C) 2003, 2004 Frank Cornelis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * Create the PCIDEV using: * mknod /dev/pcidev c 240 0 */ #include #include #include #include #include #include #include #include #include #include #include "kernel_pcidev.h" MODULE_LICENSE("GPL"); MODULE_AUTHOR("Frank Cornelis "); MODULE_DESCRIPTION("PCI Host Device Mapper Driver"); MODULE_SUPPORTED_DEVICE("pcidev"); //EXPORT_NO_SYMBOLS; static char *pcidev_name = PCIDEV_NAME; static int pcidev_open(struct inode *inode, struct file *file) { struct pcidev_struct *pcidev = kmalloc(sizeof(struct pcidev_struct), GFP_KERNEL); int idx; if (!pcidev) goto out; pcidev->dev = NULL; pcidev->pid = 0; for (idx = 0; idx < PCIDEV_COUNT_RESOURCES; idx++) pcidev->mapped_mem[idx] = NULL; init_timer(&pcidev->irq_timer); pcidev->irq_timer.function = NULL; // no test irq signaling out: file->private_data = pcidev; return 0; } static int pcidev_release(struct inode *inode, struct file *file) { struct pcidev_struct *pcidev = (struct pcidev_struct *)file->private_data; int idx; if (!pcidev) return 0; if (!pcidev->dev) goto out; if (pcidev->irq_timer.function) del_timer_sync(&pcidev->irq_timer); if (pcidev->pid) { u8 irq; pci_read_config_byte(pcidev->dev, PCI_INTERRUPT_PIN, &irq); pci_read_config_byte(pcidev->dev, PCI_INTERRUPT_LINE, &irq); free_irq(irq, (void *)pcidev->pid); } for (idx = 0; idx < PCIDEV_COUNT_RESOURCES; idx++) if (pcidev->mapped_mem[idx]) iounmap(pcidev->mapped_mem[idx]); pci_release_regions(pcidev->dev); out: kfree(file->private_data); return 0; } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)) // linux kernel 2.4 typedef void irqreturn_t; #define IRQ_NONE #endif static irqreturn_t pcidev_irqhandler(int irq, void *dev_id, struct pt_regs *regs) { int pid = (int)dev_id; struct task_struct *task; read_lock(&tasklist_lock); task = find_task_by_pid(pid); if (task) send_sig_info(SIGUSR1, (struct siginfo *)1, task); // XXX: should be converted to real-time signals read_unlock(&tasklist_lock); return IRQ_NONE; /* * we cannot possible say IRQ_HANDLED because we simply * don't know yet... only bochs could tell */ } static void irq_test_timer(unsigned long data) { struct task_struct *task; struct pcidev_struct *pcidev = (struct pcidev_struct *)data; read_lock(&tasklist_lock); task = find_task_by_pid(pcidev->pid); if (task) send_sig_info(SIGUSR1, (struct siginfo *)1, task); read_unlock(&tasklist_lock); //printk(KERN_INFO "irq_test_timer\n"); init_timer(&pcidev->irq_timer); pcidev->irq_timer.data = data; pcidev->irq_timer.function = irq_test_timer; pcidev->irq_timer.expires = jiffies + HZ; add_timer(&pcidev->irq_timer); } static int pcidev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; struct pcidev_struct *pcidev = (struct pcidev_struct *)file->private_data; if (!pcidev) return -EIO; switch(cmd) { case PCIDEV_IOCTL_FIND: { struct pcidev_find_struct *find; struct pci_dev *dev; unsigned long vendorID, deviceID; int idx; if (pcidev->dev) return -EIO; // only alloc once for now if (!access_ok(VERIFY_WRITE, (void *)arg, sizeof(struct pcidev_find_struct))) return -EFAULT; find = (struct pcidev_find_struct *)arg; __get_user(vendorID, &find->vendorID); __get_user(deviceID, &find->deviceID); __put_user(-1, &find->bus); __put_user(-1, &find->device); __put_user(-1, &find->func); dev = pci_find_device(vendorID, deviceID, NULL); if (!dev) return -ENOENT; if (pci_enable_device(dev)) { printk(KERN_WARNING "pcidev: Could not enable the PCI device.\n"); return -EIO; } if (pci_set_dma_mask(dev, 0xffffffff)) printk(KERN_WARNING "pcidev: only limited PCI busmaster DMA support.\n"); pci_set_master(dev); printk(KERN_INFO "pcidev: device found at %x:%x.%d\n", dev->bus->number, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn)); ret = pci_request_regions(dev, pcidev_name); if (ret < 0) break; for (idx = 0; idx < PCIDEV_COUNT_RESOURCES; idx++) { if (pci_resource_flags(dev, idx) & IORESOURCE_MEM) { long len = pci_resource_len(dev, idx); unsigned long mapped_start = (unsigned long)ioremap(pci_resource_start(dev, idx), len); __put_user(mapped_start, &find->resources[idx].start); __put_user(mapped_start + len - 1, &find->resources[idx].end); pcidev->mapped_mem[idx] = (void *)mapped_start; } else { pcidev->mapped_mem[idx] = NULL; __put_user(pci_resource_start(dev, idx), &find->resources[idx].start); __put_user(pci_resource_end(dev, idx), &find->resources[idx].end); } __put_user(pci_resource_flags(dev, idx), &find->resources[idx].flags); } pcidev->dev = dev; __put_user(dev->bus->number, &find->bus); __put_user(PCI_SLOT(dev->devfn), &find->device); __put_user(PCI_FUNC(dev->devfn), &find->func); ret = 0; break; } case PCIDEV_IOCTL_READ_CONFIG_BYTE: case PCIDEV_IOCTL_READ_CONFIG_WORD: case PCIDEV_IOCTL_READ_CONFIG_DWORD: { struct pcidev_io_struct *io; unsigned long address, value; if (!pcidev->dev) return -EIO; if (!access_ok(VERIFY_WRITE, (void *)arg, sizeof(struct pcidev_io_struct))) return -EFAULT; io = (struct pcidev_io_struct *)arg; __get_user(address, &io->address); __put_user(-1, &io->value); printk(KERN_DEBUG "pcidev: reading config address %#x\n", (int)address); switch(cmd) { case PCIDEV_IOCTL_READ_CONFIG_BYTE: ret = pci_read_config_byte(pcidev->dev, address, (u8 *)&value); break; case PCIDEV_IOCTL_READ_CONFIG_WORD: ret = pci_read_config_word(pcidev->dev, address, (u16 *)&value); break; case PCIDEV_IOCTL_READ_CONFIG_DWORD: ret = pci_read_config_dword(pcidev->dev, address, (u32 *)&value); break; } if (ret < 0) return ret; __put_user(value, &io->value); break; } case PCIDEV_IOCTL_WRITE_CONFIG_BYTE: case PCIDEV_IOCTL_WRITE_CONFIG_WORD: case PCIDEV_IOCTL_WRITE_CONFIG_DWORD: { struct pcidev_io_struct *io; unsigned long address, value; if (!pcidev->dev) return -EIO; if (!access_ok(VERIFY_READ, (void *)arg, sizeof(struct pcidev_io_struct))) return -EFAULT; io = (struct pcidev_io_struct *)arg; __get_user(address, &io->address); __get_user(value, &io->value); /* * Next tests prevent the pcidev user from remapping * the PCI host device since this could cause great * trouble because we don't own those I/O resources. * If the pcidev wants to remap a device he needs to * emulate the mapping himself and not bother the host * kernel about it. */ if (address == PCI_INTERRUPT_PIN) { printk(KERN_WARNING "pcidev: not allowed to set irq pin!\n"); return -EIO; } if (address == PCI_INTERRUPT_LINE) { printk(KERN_WARNING "pcidev: not allowed to set irq line!\n"); return -EIO; } if (PCI_BASE_ADDRESS_0 <= address && (address & ~3UL) <= PCI_BASE_ADDRESS_5) { printk(KERN_WARNING "pcidev: now allowed to change base address %d\n", (int)((address & ~3UL) - PCI_BASE_ADDRESS_0) / 4); return -EIO; } printk(KERN_DEBUG "pcidev: writing config address %#x\n", (int)address); switch(cmd) { case PCIDEV_IOCTL_WRITE_CONFIG_BYTE: ret = pci_write_config_byte(pcidev->dev, address, (u8)value); break; case PCIDEV_IOCTL_WRITE_CONFIG_WORD: ret = pci_write_config_word(pcidev->dev, address, (u16)value); break; case PCIDEV_IOCTL_WRITE_CONFIG_DWORD: ret = pci_write_config_dword(pcidev->dev, address, (u32)value); break; } break; } case PCIDEV_IOCTL_INTERRUPT: { u8 irq; if (!pcidev->dev) return -EIO; ret = pci_read_config_byte(pcidev->dev, PCI_INTERRUPT_PIN, &irq); if (ret < 0) break; if (!irq) return -EIO; ret = pci_read_config_byte(pcidev->dev, PCI_INTERRUPT_LINE, &irq); if (ret < 0) break; if (arg & 1) { pcidev->pid = current->pid; // our dev_id printk(KERN_INFO "pcidev: enabling IRQ %d\n", irq); ret = request_irq(irq, pcidev_irqhandler, SA_SHIRQ, pcidev_name, (void *)current->pid); } else { if (!pcidev->pid) return -EIO; printk(KERN_INFO "pcidev: disabling IRQ %d\n", irq); free_irq(irq, (void *)pcidev->pid); pcidev->pid = 0; ret = 0; } break; } /* * Next ioctl is only for testing purposes. */ case PCIDEV_IOCTL_INTERRUPT_TEST: { ret = -EIO; if (!pcidev->dev) break; if (!pcidev->pid) break; if (pcidev->irq_timer.function) del_timer_sync(&pcidev->irq_timer); pcidev->irq_timer.function = NULL; if (arg & 1) { init_timer(&pcidev->irq_timer); pcidev->irq_timer.function = irq_test_timer; pcidev->irq_timer.data = (unsigned long)pcidev; pcidev->irq_timer.expires = jiffies + HZ; add_timer(&pcidev->irq_timer); } ret = 0; break; } case PCIDEV_IOCTL_READ_IO_BYTE: case PCIDEV_IOCTL_READ_IO_WORD: case PCIDEV_IOCTL_READ_IO_DWORD: { /* * We should probably check access rights against * the PCI resource list... but who cares for a * security hole more or less :) */ struct pcidev_io_struct *io; unsigned long address, value = -1; if (!access_ok(VERIFY_WRITE, (void *)arg, sizeof(struct pcidev_io_struct))) return -EFAULT; io = (struct pcidev_io_struct *)arg; __get_user(address, &io->address); printk(KERN_DEBUG "pcidev: reading I/O port %#x\n", (int)address); switch(cmd) { case PCIDEV_IOCTL_READ_IO_BYTE: value = inb(address); break; case PCIDEV_IOCTL_READ_IO_WORD: value = inw(address); break; case PCIDEV_IOCTL_READ_IO_DWORD: value = inl(address); break; } __put_user(value, &io->value); ret = 0; break; } case PCIDEV_IOCTL_WRITE_IO_BYTE: case PCIDEV_IOCTL_WRITE_IO_WORD: case PCIDEV_IOCTL_WRITE_IO_DWORD: { struct pcidev_io_struct *io; unsigned long address, value; if (!access_ok(VERIFY_READ, (void *)arg, sizeof(struct pcidev_io_struct))) return -EFAULT; io = (struct pcidev_io_struct *)arg; __get_user(address, &io->address); __get_user(value, &io->value); printk(KERN_DEBUG "pcidev: writing I/O port %#x\n", (int)address); switch(cmd) { case PCIDEV_IOCTL_WRITE_IO_BYTE: outb(value, address); break; case PCIDEV_IOCTL_WRITE_IO_WORD: outw(value, address); break; case PCIDEV_IOCTL_WRITE_IO_DWORD: outl(value, address); break; } ret = 0; break; } case PCIDEV_IOCTL_READ_MEM_BYTE: case PCIDEV_IOCTL_READ_MEM_WORD: case PCIDEV_IOCTL_READ_MEM_DWORD: { struct pcidev_io_struct *io; unsigned long address, value = -1; if (!access_ok(VERIFY_WRITE, (void *)arg, sizeof(struct pcidev_io_struct))) return -EFAULT; io = (struct pcidev_io_struct *)arg; __get_user(address, &io->address); printk(KERN_DEBUG "pcidev: reading memory %#x\n", (int)address); switch(cmd) { case PCIDEV_IOCTL_READ_MEM_BYTE: value = readb((unsigned char *)address); break; case PCIDEV_IOCTL_READ_MEM_WORD: value = readw((unsigned short *)address); break; case PCIDEV_IOCTL_READ_MEM_DWORD: value = readl((unsigned int *)address); break; } __put_user(value, &io->value); ret = 0; break; } case PCIDEV_IOCTL_WRITE_MEM_BYTE: case PCIDEV_IOCTL_WRITE_MEM_WORD: case PCIDEV_IOCTL_WRITE_MEM_DWORD: { struct pcidev_io_struct *io; unsigned long address, value; if (!access_ok(VERIFY_READ, (void *)arg, sizeof(struct pcidev_io_struct))) return -EFAULT; io = (struct pcidev_io_struct *)arg; __get_user(address, &io->address); __get_user(value, &io->value); printk(KERN_DEBUG "pcidev: writing memory %#x\n", (int)address); switch(cmd) { case PCIDEV_IOCTL_WRITE_MEM_BYTE: writeb(value, (unsigned char *)address); break; case PCIDEV_IOCTL_WRITE_MEM_WORD: writew(value, (unsigned short *)address); break; case PCIDEV_IOCTL_WRITE_MEM_DWORD: writel(value, (unsigned int *)address); break; } ret = 0; break; } case PCIDEV_IOCTL_PROBE_CONFIG_DWORD: { /* * This ioctl allows for probing a config space value. * This can be used for base address size probing */ struct pcidev_io_struct *io; unsigned long address, value, orig_value; if (!pcidev->dev) return -EIO; if (!access_ok(VERIFY_WRITE, (void *)arg, sizeof(struct pcidev_io_struct))) return -EFAULT; io = (struct pcidev_io_struct *)arg; __get_user(address, &io->address); __get_user(value, &io->value); __put_user(-1, &io->value); printk(KERN_INFO "pcidev: probing config space address: %#x\n", (int)address); ret = pci_read_config_dword(pcidev->dev, address, (u32 *)&orig_value); if (ret < 0) break; pci_write_config_dword(pcidev->dev, address, (u32)value); pci_read_config_dword(pcidev->dev, address, (u32 *)&value); ret = pci_write_config_dword(pcidev->dev, address, (u32)orig_value); if (ret < 0) break; __put_user(value, &io->value); break; } default: ret = -ENOTTY; } return ret; } static struct file_operations pcidev_fops = { open: pcidev_open, release: pcidev_release, ioctl: pcidev_ioctl, owner: THIS_MODULE }; int __init init_module(void) { int result; printk(KERN_INFO "pcidev init\n"); if ((result = register_chrdev(PCIDEV_MAJOR, pcidev_name, &pcidev_fops)) < 0) printk(KERN_WARNING "Could not register pcidev.\n"); return result; } void __exit cleanup_module(void) { if (unregister_chrdev(PCIDEV_MAJOR, pcidev_name) < 0) printk(KERN_WARNING "Could not unregister pcidev.\n"); printk(KERN_INFO "pcidev cleanup\n"); }