diff options
Diffstat (limited to 'contrib/libdha/kernelhelper/dhahelper.c')
-rw-r--r-- | contrib/libdha/kernelhelper/dhahelper.c | 1239 |
1 files changed, 1239 insertions, 0 deletions
diff --git a/contrib/libdha/kernelhelper/dhahelper.c b/contrib/libdha/kernelhelper/dhahelper.c new file mode 100644 index 000000000..0e755064a --- /dev/null +++ b/contrib/libdha/kernelhelper/dhahelper.c @@ -0,0 +1,1239 @@ +/* + Direct Hardware Access kernel helper + + (C) 2002 Alex Beregszaszi <alex@naxine.org> + (C) 2002-2003 Nick Kurshev <nickols_k@mail.ru> + (C) 2002-2004 Måns Rullgård <mru@users.sourceforge.net> + + Accessing hardware from userspace as USER (no root needed!) + + Tested on 2.2.x (2.2.19), 2.4.x (2.4.3,2.4.17) and 2.6.1. + + License: GPL + + WARNING! THIS MODULE VIOLATES SEVERAL SECURITY LINES! DON'T USE IT + ON PRODUCTION SYSTEMS, ONLY AT HOME, ON A "SINGLE-USER" SYSTEM. + NO WARRANTY! + + IF YOU WANT TO USE IT ON PRODUCTION SYSTEMS THEN PLEASE READ 'README' + FILE TO KNOW HOW TO PREVENT ANONYMOUS ACCESS TO THIS MODULE. + + Tech: + Communication between userspace and kernelspace goes over character + device using ioctl. + + Usage: + mknod -m 600 /dev/dhahelper c 252 0 + + Also you can change the major number, setting the "dhahelper_major" + module parameter, the default is 252, specified in dhahelper.h. + + Note: do not use other than minor==0, the module forbids it. + + TODO: + * select (request?) a "valid" major number (from Linux project? ;) + * make security + * is pci handling needed? (libdha does this with lowlevel port funcs) + * is mttr handling needed? + * test on older kernels (2.0.x (?)) +*/ + +#ifndef MODULE +#define MODULE +#endif + +#ifndef __KERNEL__ +#define __KERNEL__ +#endif + +#include <linux/config.h> + +#ifdef CONFIG_MODVERSION +#define MODVERSION +#include <linux/modversions.h> +#endif + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/vmalloc.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/unistd.h> +#include <asm/uaccess.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0) +#include <linux/malloc.h> +#else +#include <linux/slab.h> +#endif + +#include <linux/pci.h> +#include <linux/ioport.h> +#include <linux/init.h> + +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/io.h> + +#include <linux/mman.h> + +#include <linux/fs.h> +#include <linux/unistd.h> + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif +#ifdef CONFIG_DEVFS_FS +#include <linux/devfs_fs_kernel.h> +#endif + +#include "dhahelper.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) +#define pte_offset(p,a) pte_offset_kernel(p,a) +#define LockPage(p) SetPageLocked(p) +#define UnlockPage(p) ClearPageLocked(p) +#define irqreturn(n) return(n) +#else +#define irqreturn_t void +#define irqreturn(n) return +#endif + +MODULE_AUTHOR("Alex Beregszaszi <alex@naxine.org>, Nick Kurshev <nickols_k@mail.ru>, Måns Rullgård <mru@users.sf.net>"); +MODULE_DESCRIPTION("Provides userspace access to hardware"); +#ifdef MODULE_LICENSE +MODULE_LICENSE("GPL"); +#endif + +static int dhahelper_major = DEFAULT_MAJOR; +MODULE_PARM(dhahelper_major, "i"); +MODULE_PARM_DESC(dhahelper_major, "Major number of dhahelper characterdevice"); + +/* 0 = silent */ +/* 1 = report errors (default) */ +/* 2 = debug */ +static int dhahelper_verbosity = 1; +MODULE_PARM(dhahelper_verbosity, "i"); +MODULE_PARM_DESC(dhahelper_verbosity, "Level of verbosity (0 = silent, 1 = only errors, 2 = debug)"); + +static int dhahelper_open(struct inode *inode, struct file *file) +{ + if (dhahelper_verbosity > 1) + printk(KERN_DEBUG "dhahelper: device opened\n"); + + if (MINOR(inode->i_rdev) != 0) + return -ENXIO; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0) + MOD_INC_USE_COUNT; +#endif + + return 0; +} + +static int dhahelper_release(struct inode *inode, struct file *file) +{ + if (dhahelper_verbosity > 1) + printk(KERN_DEBUG "dhahelper: device released\n"); + + if (MINOR(inode->i_rdev) != 0) + return -ENXIO; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0) + MOD_DEC_USE_COUNT; +#endif + + return 0; +} + +static int dhahelper_get_version(int * arg) +{ + int version = API_VERSION; + + if (copy_to_user(arg, &version, sizeof(int))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy to userspace\n"); + return -EFAULT; + } + return 0; +} + +static int dhahelper_port(dhahelper_port_t * arg) +{ + dhahelper_port_t port; + if (copy_from_user(&port, arg, sizeof(dhahelper_port_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + switch(port.operation) + { + case PORT_OP_READ: + { + switch(port.size) + { + case 1: + port.value = inb(port.addr); + break; + case 2: + port.value = inw(port.addr); + break; + case 4: + port.value = inl(port.addr); + break; + default: + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: invalid port read size (%d)\n", + port.size); + return -EINVAL; + } + break; + } + case PORT_OP_WRITE: + { + switch(port.size) + { + case 1: + outb(port.value, port.addr); + break; + case 2: + outw(port.value, port.addr); + break; + case 4: + outl(port.value, port.addr); + break; + default: + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: invalid port write size (%d)\n", + port.size); + return -EINVAL; + } + break; + } + default: + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: invalid port operation (%d)\n", + port.operation); + return -EINVAL; + } + /* copy back only if read was performed */ + if (port.operation == PORT_OP_READ) + if (copy_to_user(arg, &port, sizeof(dhahelper_port_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy to userspace\n"); + return -EFAULT; + } + return 0; +} + +/*******************************/ +/* Memory management functions */ +/* from kernel:/drivers/media/video/bttv-driver.c */ +/*******************************/ + +#define MDEBUG(x) do { } while(0) /* Debug memory management */ + +/* [DaveM] I've recoded most of this so that: + * 1) It's easier to tell what is happening + * 2) It's more portable, especially for translating things + * out of vmalloc mapped areas in the kernel. + * 3) Less unnecessary translations happen. + * + * The code used to assume that the kernel vmalloc mappings + * existed in the page tables of every process, this is simply + * not guarenteed. We now use pgd_offset_k which is the + * defined way to get at the kernel page tables. + */ + +/* Given PGD from the address space's page table, return the kernel + * virtual mapping of the physical memory mapped at ADR. + */ +static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr) +{ + unsigned long ret = 0UL; + pmd_t *pmd; + pte_t *ptep, pte; + + if (!pgd_none(*pgd)) { + pmd = pmd_offset(pgd, adr); + if (!pmd_none(*pmd)) { + ptep = pte_offset(pmd, adr); + pte = *ptep; + if(pte_present(pte)) { + ret = (unsigned long) page_address(pte_page(pte)); + ret |= (adr & (PAGE_SIZE - 1)); + + } + } + } + MDEBUG(printk("uv2kva(%lx-->%lx)", adr, ret)); + return ret; +} + +static inline unsigned long uvirt_to_bus(unsigned long adr) +{ + unsigned long kva, ret; + + kva = uvirt_to_kva(pgd_offset(current->mm, adr), adr); + ret = virt_to_bus((void *)kva); + MDEBUG(printk("uv2b(%lx-->%lx)", adr, ret)); + return ret; +} + +static inline unsigned long uvirt_to_pa(unsigned long adr) +{ + unsigned long kva, ret; + + kva = uvirt_to_kva(pgd_offset(current->mm, adr), adr); + ret = virt_to_phys((void *)kva); + MDEBUG(printk("uv2b(%lx-->%lx)", adr, ret)); + return ret; +} + +static inline unsigned long kvirt_to_bus(unsigned long va) +{ + unsigned long kva, ret; + + kva = uvirt_to_kva(pgd_offset_k(va), va); + ret = virt_to_bus((void *)kva); + MDEBUG(printk("kv2b(%lx-->%lx)", adr, ret)); + return ret; +} + +/* Here we want the physical address of the memory. + * This is used when initializing the contents of the + * area and marking the pages as reserved. + */ +static inline unsigned long kvirt_to_pa(unsigned long va) +{ + unsigned long kva, ret; + + kva = uvirt_to_kva(pgd_offset_k(va), va); + ret = __pa(kva); + MDEBUG(printk("kv2pa(%lx-->%lx)", adr, ret)); + return ret; +} + +static void * rvmalloc(signed long size) +{ + void * mem; + unsigned long adr, page; + + mem=vmalloc_32(size); + if (mem) + { + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr=(unsigned long) mem; + while (size > 0) + { + page = kvirt_to_pa(adr); + SetPageReserved(virt_to_page(__va(page))); + adr+=PAGE_SIZE; + size-=PAGE_SIZE; + } + } + return mem; +} + +static int pag_lock(unsigned long addr) +{ + unsigned long page; + unsigned long kva; + + kva = uvirt_to_kva(pgd_offset(current->mm, addr), addr); + if(kva) + { + lock_it: + page = uvirt_to_pa((unsigned long)addr); + LockPage(virt_to_page(__va(page))); + SetPageReserved(virt_to_page(__va(page))); + } + else + { + copy_from_user(&page,(char *)addr,1); /* try access it */ + kva = uvirt_to_kva(pgd_offset(current->mm, addr), addr); + if(kva) goto lock_it; + else return EPERM; + } + return 0; +} + +static int pag_unlock(unsigned long addr) +{ + unsigned long page; + unsigned long kva; + + kva = uvirt_to_kva(pgd_offset(current->mm, addr), addr); + if(kva) + { + page = uvirt_to_pa((unsigned long)addr); + UnlockPage(virt_to_page(__va(page))); + ClearPageReserved(virt_to_page(__va(page))); + return 0; + } + return EPERM; +} + + +static void rvfree(void * mem, signed long size) +{ + unsigned long adr, page; + + if (mem) + { + adr=(unsigned long) mem; + while (size > 0) + { + page = kvirt_to_pa(adr); + ClearPageReserved(virt_to_page(__va(page))); + adr+=PAGE_SIZE; + size-=PAGE_SIZE; + } + vfree(mem); + } +} + + +static int dhahelper_virt_to_phys(dhahelper_vmi_t *arg) +{ + dhahelper_vmi_t mem; + unsigned long i,nitems; + char *addr; + if (copy_from_user(&mem, arg, sizeof(dhahelper_vmi_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + nitems = mem.length / PAGE_SIZE; + if(mem.length % PAGE_SIZE) nitems++; + addr = mem.virtaddr; + for(i=0;i<nitems;i++) + { + unsigned long result; + result = uvirt_to_pa((unsigned long)addr); + if (copy_to_user(&mem.realaddr[i], &result, sizeof(unsigned long))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy to userspace\n"); + return -EFAULT; + } + addr += PAGE_SIZE; + } + return 0; +} + +static int dhahelper_virt_to_bus(dhahelper_vmi_t *arg) +{ + dhahelper_vmi_t mem; + unsigned long i,nitems; + char *addr; + if (copy_from_user(&mem, arg, sizeof(dhahelper_vmi_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + nitems = mem.length / PAGE_SIZE; + if(mem.length % PAGE_SIZE) nitems++; + addr = mem.virtaddr; + for(i=0;i<nitems;i++) + { + unsigned long result; + result = uvirt_to_bus((unsigned long)addr); + if (copy_to_user(&mem.realaddr[i], &result, sizeof(unsigned long))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy to userspace\n"); + return -EFAULT; + } + addr += PAGE_SIZE; + } + return 0; +} + + +static int dhahelper_alloc_pa(dhahelper_mem_t *arg) +{ + dhahelper_mem_t mem; + if (copy_from_user(&mem, arg, sizeof(dhahelper_mem_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + mem.addr = rvmalloc(mem.length); + if (copy_to_user(arg, &mem, sizeof(dhahelper_mem_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy to userspace\n"); + return -EFAULT; + } + return 0; +} + +static int dhahelper_free_pa(dhahelper_mem_t *arg) +{ + dhahelper_mem_t mem; + if (copy_from_user(&mem, arg, sizeof(dhahelper_mem_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + rvfree(mem.addr,mem.length); + return 0; +} + +static int dhahelper_lock_mem(dhahelper_mem_t *arg) +{ + dhahelper_mem_t mem; + int retval; + unsigned long i,nitems,addr; + if (copy_from_user(&mem, arg, sizeof(dhahelper_mem_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + nitems = mem.length / PAGE_SIZE; + if(mem.length % PAGE_SIZE) nitems++; + addr = (unsigned long)mem.addr; + for(i=0;i<nitems;i++) + { + retval = pag_lock((unsigned long)addr); + if(retval) + { + unsigned long j; + addr = (unsigned long)mem.addr; + for(j=0;j<i;j++) + { + pag_unlock(addr); + addr += PAGE_SIZE; + } + return retval; + } + addr += PAGE_SIZE; + } + return 0; +} + +static int dhahelper_unlock_mem(dhahelper_mem_t *arg) +{ + dhahelper_mem_t mem; + int retval; + unsigned long i,nitems,addr; + if (copy_from_user(&mem, arg, sizeof(dhahelper_mem_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + nitems = mem.length / PAGE_SIZE; + if(mem.length % PAGE_SIZE) nitems++; + addr = (unsigned long)mem.addr; + for(i=0;i<nitems;i++) + { + retval = pag_unlock((unsigned long)addr); + if(retval) return retval; + addr += PAGE_SIZE; + } + return 0; +} + +static struct dha_irq { + spinlock_t lock; + unsigned long flags; + int handled; + int rcvd; + volatile u32 *ack_addr; + u32 ack_data; + struct pci_dev *dev; + wait_queue_head_t wait; + unsigned long count; +} dha_irqs[256]; + +static irqreturn_t dhahelper_irq_handler(int irq, void *dev_id, + struct pt_regs *regs) +{ + spin_lock_irqsave(&dha_irqs[irq].lock, dha_irqs[irq].flags); + if(dha_irqs[irq].handled){ + dha_irqs[irq].rcvd = 1; + dha_irqs[irq].count++; + if(dha_irqs[irq].ack_addr){ + *dha_irqs[irq].ack_addr = dha_irqs[irq].ack_data; + mb(); + } + wake_up_interruptible(&dha_irqs[irq].wait); + } + spin_unlock_irqrestore(&dha_irqs[irq].lock, dha_irqs[irq].flags); + irqreturn(0); +} + +static int dhahelper_install_irq(dhahelper_irq_t *arg) +{ + dhahelper_irq_t my_irq; + struct pci_dev *pci; + long rlen; + int retval; + long ack_addr; + int irqn; + + if (copy_from_user(&my_irq, arg, sizeof(dhahelper_irq_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + + if(!(pci = pci_find_slot(my_irq.bus, PCI_DEVFN(my_irq.dev, my_irq.func)))) + return -EINVAL; + + rlen = pci_resource_len(pci, my_irq.ack_region); + if(my_irq.ack_offset > rlen - 4) + return -EINVAL; + + irqn = pci->irq; + + spin_lock_irqsave(&dha_irqs[irqn].lock, + dha_irqs[irqn].flags); + + if(dha_irqs[irqn].handled){ + retval = -EBUSY; + goto fail; + } + + if(my_irq.ack_region >= 0){ + ack_addr = pci_resource_start(pci, my_irq.ack_region); + ack_addr += my_irq.ack_offset; +#ifdef CONFIG_ALPHA + ack_addr += ((struct pci_controller *) pci->sysdata)->dense_mem_base; +#endif + /* FIXME: Other architectures */ + + dha_irqs[irqn].ack_addr = phys_to_virt(ack_addr); + dha_irqs[irqn].ack_data = my_irq.ack_data; + } else { + dha_irqs[irqn].ack_addr = 0; + } + + dha_irqs[irqn].lock = SPIN_LOCK_UNLOCKED; + dha_irqs[irqn].flags = 0; + dha_irqs[irqn].rcvd = 0; + dha_irqs[irqn].dev = pci; + init_waitqueue_head(&dha_irqs[irqn].wait); + dha_irqs[irqn].count = 0; + + retval = request_irq(irqn, dhahelper_irq_handler, + SA_SHIRQ, "dhahelper", pci); + + if(retval < 0) + goto fail; + + copy_to_user(&arg->num, &irqn, sizeof(irqn)); + + dha_irqs[irqn].handled = 1; + +out: + spin_unlock_irqrestore(&dha_irqs[irqn].lock, + dha_irqs[irqn].flags); + return retval; + +fail: + if(retval == -EINVAL){ + printk("dhahelper: bad irq number or handler\n"); + } else if(retval == -EBUSY){ + printk("dhahelper: IRQ %u busy\n", irqn); + } else { + printk("dhahelper: Could not install irq handler...\n"); + } + printk("dhahelper: Perhaps you need to let your BIOS assign an IRQ to your video card\n"); + goto out; +} + +static int dhahelper_free_irq(dhahelper_irq_t *arg) +{ + dhahelper_irq_t irq; + struct pci_dev *pci; + int irqn; + + if (copy_from_user(&irq, arg, sizeof(dhahelper_irq_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + + pci = pci_find_slot(irq.bus, PCI_DEVFN(irq.dev, irq.func)); + if(!pci) + return -EINVAL; + + irqn = pci->irq; + + spin_lock_irqsave(&dha_irqs[irqn].lock, dha_irqs[irqn].flags); + if(dha_irqs[irqn].handled) { + free_irq(irqn, pci); + dha_irqs[irqn].handled = 0; + printk("IRQ %i: %li\n", irqn, dha_irqs[irqn].count); + } + spin_unlock_irqrestore(&dha_irqs[irqn].lock, dha_irqs[irqn].flags); + return 0; +} + +static int dhahelper_ack_irq(dhahelper_irq_t *arg) +{ + dhahelper_irq_t irq; + int retval = 0; + DECLARE_WAITQUEUE(wait, current); + if (copy_from_user(&irq, arg, sizeof(dhahelper_irq_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + if(irq.num > 255) return -EINVAL; + if(!dha_irqs[irq.num].handled) return -ESRCH; + add_wait_queue(&dha_irqs[irq.num].wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + for(;;){ + int r; + spin_lock_irqsave(&dha_irqs[irq.num].lock, + dha_irqs[irq.num].flags); + r = dha_irqs[irq.num].rcvd; + spin_unlock_irqrestore(&dha_irqs[irq.num].lock, + dha_irqs[irq.num].flags); + + if(r){ + dha_irqs[irq.num].rcvd = 0; + break; + } + + if(signal_pending(current)){ + retval = -ERESTARTSYS; + break; + } + + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&dha_irqs[irq.num].wait, &wait); + return retval; +} + +static int dhahelper_cpu_flush(dhahelper_cpu_flush_t *arg) +{ + dhahelper_cpu_flush_t my_l2; + if (copy_from_user(&my_l2, arg, sizeof(dhahelper_cpu_flush_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } +#if defined(__i386__) + /* WBINVD writes all modified cache lines back to main memory */ + if(boot_cpu_data.x86 > 3) { __asm __volatile("wbinvd":::"memory"); } +#else + /* FIXME!!!*/ + mb(); /* declared in "asm/system.h" */ +#endif + return 0; +} + +static struct pci_dev *pdev = NULL; +static int dhahelper_pci_find(dhahelper_pci_device_t *arg) +{ + dhahelper_pci_device_t this_dev; + pdev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev); + if(pdev) + { + this_dev.bus = pdev->bus->number; + this_dev.card = PCI_SLOT(pdev->devfn); + this_dev.func = PCI_FUNC(pdev->devfn); + this_dev.vendor = pdev->vendor; + this_dev.device = pdev->device; + this_dev.base0 = pci_resource_start (pdev, 0); + this_dev.base1 = pci_resource_start (pdev, 1); + this_dev.base2 = pci_resource_start (pdev, 2); + pci_read_config_dword(pdev, pdev->rom_base_reg, (u32*)&this_dev.baserom); + this_dev.base3 = pci_resource_start (pdev, 3); + this_dev.base4 = pci_resource_start (pdev, 4); + this_dev.base5 = pci_resource_start (pdev, 5); + pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &this_dev.irq); + pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &this_dev.ipin); + pci_read_config_byte(pdev, PCI_MIN_GNT, &this_dev.gnt); + pci_read_config_byte(pdev, PCI_MAX_LAT, &this_dev.lat); + } + else memset(&this_dev,0,sizeof(dhahelper_pci_device_t)); + if (copy_to_user(arg, &this_dev, sizeof(dhahelper_pci_device_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy to userspace\n"); + return -EFAULT; + } + return pdev?0:-ENODATA; +} + +static int dhahelper_pci_config(dhahelper_pci_config_t *arg) +{ + dhahelper_pci_config_t op; + struct pci_dev *pdev; + if (copy_from_user(&op, arg, sizeof(dhahelper_pci_config_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + pdev = pci_find_slot(op.bus,PCI_DEVFN(op.dev,op.func)); + if(!pdev) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: can't identify device\n"); + return -EFAULT; + } + switch(op.operation) + { + case PCI_OP_READ: + switch(op.size) + { + case 1: + pci_read_config_byte(pdev,op.cmd,(u8*)&op.ret); + break; + case 2: + pci_read_config_word(pdev,op.cmd,(u16*)&op.ret); + break; + case 4: + pci_read_config_dword(pdev,op.cmd,(u32*)&op.ret); + break; + default: + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: wrong size of pci operation: %u \n",op.size); + return -EFAULT; + } + case PCI_OP_WRITE: + switch(op.size) + { + case 1: + pci_write_config_byte(pdev,op.cmd,op.ret); + break; + case 2: + pci_write_config_word(pdev,op.cmd,op.ret); + break; + case 4: + pci_write_config_dword(pdev,op.cmd,op.ret); + break; + default: + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: wrong size of pci operation: %u \n",op.size); + return -EFAULT; + } + default: + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: unknown pci operation %i\n",op.operation); + return -EFAULT; + } + if (copy_to_user(arg, &op, sizeof(dhahelper_pci_device_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy to userspace\n"); + return -EFAULT; + } + return 0; +} + +static int dhahelper_mtrr(dhahelper_mtrr_t *arg) +{ +#ifdef CONFIG_MTRR + dhahelper_mtrr_t op; + if (copy_from_user(&op, arg, sizeof(dhahelper_pci_config_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy from userspace\n"); + return -EFAULT; + } + switch(op.operation) + { + case MTRR_OP_ADD: + op.privat = mtrr_add (op.start,op.size,op.type,1); + break; + case MTRR_OP_DEL: + mtrr_del(op.privat, op.start, op.size); + break; + default: + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: unknown mtrr operation %i\n",op.operation); + return -EFAULT; + } + if (copy_to_user(arg, &op, sizeof(dhahelper_mtrr_t))) + { + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: failed copy to userspace\n"); + return -EFAULT; + } +#endif + return 0; +} + +static int dhahelper_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + if (dhahelper_verbosity > 1) + printk(KERN_DEBUG "dhahelper: ioctl(cmd=%x, arg=%lx)\n", + cmd, arg); + + if (MINOR(inode->i_rdev) != 0) + return -ENXIO; + + switch(cmd) + { + case DHAHELPER_GET_VERSION: return dhahelper_get_version((int *)arg); + case DHAHELPER_PORT: return dhahelper_port((dhahelper_port_t *)arg); + case DHAHELPER_MTRR: return dhahelper_mtrr((dhahelper_mtrr_t *)arg); + case DHAHELPER_PCI_CONFIG: return dhahelper_pci_config((dhahelper_pci_config_t *)arg); + case DHAHELPER_VIRT_TO_PHYS:return dhahelper_virt_to_phys((dhahelper_vmi_t *)arg); + case DHAHELPER_VIRT_TO_BUS: return dhahelper_virt_to_bus((dhahelper_vmi_t *)arg); + case DHAHELPER_ALLOC_PA:return dhahelper_alloc_pa((dhahelper_mem_t *)arg); + case DHAHELPER_FREE_PA: return dhahelper_free_pa((dhahelper_mem_t *)arg); + case DHAHELPER_LOCK_MEM: return dhahelper_lock_mem((dhahelper_mem_t *)arg); + case DHAHELPER_UNLOCK_MEM: return dhahelper_unlock_mem((dhahelper_mem_t *)arg); + case DHAHELPER_INSTALL_IRQ: return dhahelper_install_irq((dhahelper_irq_t *)arg); + case DHAHELPER_ACK_IRQ: return dhahelper_ack_irq((dhahelper_irq_t *)arg); + case DHAHELPER_FREE_IRQ: return dhahelper_free_irq((dhahelper_irq_t *)arg); + case DHAHELPER_CPU_FLUSH: return dhahelper_cpu_flush((dhahelper_cpu_flush_t *)arg); + case DHAHELPER_PCI_FIND: return dhahelper_pci_find((dhahelper_pci_device_t *)arg); + default: + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: invalid ioctl (%x)\n", cmd); + return -EINVAL; + } + return 0; +} + +/* + fops functions were shamelessly stolen from linux-kernel project ;) +*/ + +static loff_t dhahelper_lseek(struct file * file, loff_t offset, int orig) +{ + switch (orig) { + case 0: + file->f_pos = offset; + return file->f_pos; + case 1: + file->f_pos += offset; + return file->f_pos; + default: + return -EINVAL; + } +} + +/* + * This funcion reads the *physical* memory. The f_pos points directly to the + * memory location. + */ +static ssize_t dhahelper_read(struct file * file, char * buf, + size_t count, loff_t *ppos) +{ + unsigned long p = *ppos; + unsigned long end_mem; + ssize_t read; + + end_mem = __pa(high_memory); + if (p >= end_mem) + return 0; + if (count > end_mem - p) + count = end_mem - p; + read = 0; +#if defined(__sparc__) || defined(__mc68000__) + /* we don't have page 0 mapped on sparc and m68k.. */ + if (p < PAGE_SIZE) { + unsigned long sz = PAGE_SIZE-p; + if (sz > count) + sz = count; + if (sz > 0) { + if (clear_user(buf, sz)) + return -EFAULT; + buf += sz; + p += sz; + count -= sz; + read += sz; + } + } +#endif + if (copy_to_user(buf, __va(p), count)) + return -EFAULT; + read += count; + *ppos += read; + return read; +} + +static ssize_t do_write_mem(struct file * file, void *p, unsigned long realp, + const char * buf, size_t count, loff_t *ppos) +{ + ssize_t written; + + written = 0; +#if defined(__sparc__) || defined(__mc68000__) + /* we don't have page 0 mapped on sparc and m68k.. */ + if (realp < PAGE_SIZE) { + unsigned long sz = PAGE_SIZE-realp; + if (sz > count) sz = count; + /* Hmm. Do something? */ + buf+=sz; + p+=sz; + count-=sz; + written+=sz; + } +#endif + if (copy_from_user(p, buf, count)) + return -EFAULT; + written += count; + *ppos += written; + return written; +} + +static ssize_t dhahelper_write(struct file * file, const char * buf, + size_t count, loff_t *ppos) +{ + unsigned long p = *ppos; + unsigned long end_mem; + + end_mem = __pa(high_memory); + if (p >= end_mem) + return 0; + if (count > end_mem - p) + count = end_mem - p; + return do_write_mem(file, __va(p), p, buf, count, ppos); +} + +#ifndef pgprot_noncached + +/* + * This should probably be per-architecture in <asm/pgtable.h> + */ +static inline pgprot_t pgprot_noncached(pgprot_t _prot) +{ + unsigned long prot = pgprot_val(_prot); + +#if defined(__i386__) || defined(__x86_64__) + /* On PPro and successors, PCD alone doesn't always mean + uncached because of interactions with the MTRRs. PCD | PWT + means definitely uncached. */ + if (boot_cpu_data.x86 > 3) + prot |= _PAGE_PCD | _PAGE_PWT; +#elif defined(__powerpc__) + prot |= _PAGE_NO_CACHE | _PAGE_GUARDED; +#elif defined(__mc68000__) +#ifdef SUN3_PAGE_NOCACHE + if (MMU_IS_SUN3) + prot |= SUN3_PAGE_NOCACHE; + else +#endif + if (MMU_IS_851 || MMU_IS_030) + prot |= _PAGE_NOCACHE030; + /* Use no-cache mode, serialized */ + else if (MMU_IS_040 || MMU_IS_060) + prot = (prot & _CACHEMASK040) | _PAGE_NOCACHE_S; +#endif + + return __pgprot(prot); +} + +#endif /* !pgprot_noncached */ + +/* + * Architectures vary in how they handle caching for addresses + * outside of main memory. + */ +static inline int noncached_address(unsigned long addr) +{ +#if defined(__i386__) + /* + * On the PPro and successors, the MTRRs are used to set + * memory types for physical addresses outside main memory, + * so blindly setting PCD or PWT on those pages is wrong. + * For Pentiums and earlier, the surround logic should disable + * caching for the high addresses through the KEN pin, but + * we maintain the tradition of paranoia in this code. + */ + return !( test_bit(X86_FEATURE_MTRR, boot_cpu_data.x86_capability) || + test_bit(X86_FEATURE_K6_MTRR, boot_cpu_data.x86_capability) || + test_bit(X86_FEATURE_CYRIX_ARR, boot_cpu_data.x86_capability) || + test_bit(X86_FEATURE_CENTAUR_MCR, boot_cpu_data.x86_capability) ) + && addr >= __pa(high_memory); +#else + return addr >= __pa(high_memory); +#endif +} + +static int dhahelper_mmap(struct file * file, struct vm_area_struct * vma) +{ + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + int err; + + /* + * Accessing memory above the top the kernel knows about or + * through a file pointer that was marked O_SYNC will be + * done non-cached. + */ + if (noncached_address(offset) || (file->f_flags & O_SYNC)) + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Don't try to swap out physical pages.. */ + vma->vm_flags |= VM_RESERVED; + + /* + * Don't dump addresses that are not real memory to a core file. + */ + if (offset >= __pa(high_memory) || (file->f_flags & O_SYNC)) + vma->vm_flags |= VM_IO; + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + err = remap_page_range(vma, vma->vm_start, offset, + vma->vm_end-vma->vm_start, vma->vm_page_prot); +#else + err = remap_page_range(vma->vm_start, offset, + vma->vm_end-vma->vm_start, vma->vm_page_prot); +#endif + if(err) + return -EAGAIN; + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0) +static struct file_operations dhahelper_fops = +{ + /*llseek*/ dhahelper_lseek, + /*read*/ dhahelper_read, + /*write*/ dhahelper_write, + /*readdir*/ NULL, + /*poll*/ NULL, + /*ioctl*/ dhahelper_ioctl, + /*mmap*/ dhahelper_mmap, + /*open*/ dhahelper_open, + /*flush*/ NULL, + /*release*/ dhahelper_release, + /* zero out the last 5 entries too ? */ +}; +#else +static struct file_operations dhahelper_fops = +{ + owner: THIS_MODULE, + ioctl: dhahelper_ioctl, + open: dhahelper_open, + release: dhahelper_release, + llseek: dhahelper_lseek, + read: dhahelper_read, + write: dhahelper_write, + mmap: dhahelper_mmap, +}; +#endif + +#ifdef CONFIG_DEVFS_FS +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +devfs_handle_t dha_devfsh; + +static int +register_dev(void) +{ + dha_devfsh = devfs_register(NULL, "dhahelper", DEVFS_FL_NONE, + dhahelper_major, 0, + S_IFCHR | S_IRUSR | S_IWUSR, + &dhahelper_fops, NULL); + if(!dha_devfsh) + return -EIO; + return 0; +} + +static void +unregister_dev(void) +{ + devfs_unregister(dha_devfsh); +} +#else /* VERSION < 2.6.0 */ +static int +register_dev(void) +{ + devfs_mk_cdev(MKDEV(dhahelper_major, 0), S_IFCHR | S_IRUSR | S_IWUSR, + "dhahelper"); + if(register_chrdev(dhahelper_major, "dhahelper", &dhahelper_fops)) + return -EIO; + return 0; +} + +static void +unregister_dev(void) +{ + devfs_remove("dhahelper"); + unregister_chrdev(dhahelper_major, "dhahelper"); +} +#endif /* VERSION < 2.6.0 */ +#else +static int +register_dev(void) +{ + return register_chrdev(dhahelper_major, "dhahelper", &dhahelper_fops); +} + +static void +unregister_dev(void) +{ + unregister_chrdev(dhahelper_major, "dhahelper"); +} +#endif /* defined CONFIG_DEVFS_FS */ + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0) +int init_module(void) +#else +static int __init init_dhahelper(void) +#endif +{ + int err = 0; + printk(KERN_INFO "Direct Hardware Access kernel helper (C) Alex Beregszaszi\n"); + + err = register_dev(); + if(err){ + if (dhahelper_verbosity > 0) + printk(KERN_ERR "dhahelper: unable to register character device (major: %d)\n", + dhahelper_major); + return err; + } + memset(dha_irqs, 0, sizeof(dha_irqs)); + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0) +void cleanup_module(void) +#else +static void __exit exit_dhahelper(void) +#endif +{ + unsigned i; + for(i=0;i<256;i++) + if(dha_irqs[i].handled) + free_irq(i, dha_irqs[i].dev); + + unregister_dev(); +} + +#ifdef EXPORT_NO_SYMBOLS +EXPORT_NO_SYMBOLS; +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) +module_init(init_dhahelper); +module_exit(exit_dhahelper); +#endif |