summaryrefslogtreecommitdiff
path: root/src/video_out/libdha/kernelhelper/dhahelper.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_out/libdha/kernelhelper/dhahelper.c')
-rw-r--r--src/video_out/libdha/kernelhelper/dhahelper.c765
1 files changed, 696 insertions, 69 deletions
diff --git a/src/video_out/libdha/kernelhelper/dhahelper.c b/src/video_out/libdha/kernelhelper/dhahelper.c
index f85b4efd1..b438364d0 100644
--- a/src/video_out/libdha/kernelhelper/dhahelper.c
+++ b/src/video_out/libdha/kernelhelper/dhahelper.c
@@ -2,7 +2,8 @@
Direct Hardware Access kernel helper
(C) 2002 Alex Beregszaszi <alex@naxine.org>
-
+ (C) 2002 Nick Kurshev <nickols_k@mail.ru>
+
Accessing hardware from userspace as USER (no root needed!)
Tested on 2.2.x (2.2.19) and 2.4.x (2.4.3,2.4.17).
@@ -12,6 +13,9 @@
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
@@ -26,8 +30,6 @@
Note: do not use other than minor==0, the module forbids it.
TODO:
- * do memory mapping without fops:mmap
- * implement unmap memory
* select (request?) a "valid" major number (from Linux project? ;)
* make security
* is pci handling needed? (libdha does this with lowlevel port funcs)
@@ -56,8 +58,18 @@
#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/wrapper.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>
@@ -78,10 +90,14 @@
#include <linux/fs.h>
#include <linux/unistd.h>
+#ifdef CONFIG_DEVFS_FS
+#include <linux/devfs_fs_kernel.h>
+#endif
+
#include "dhahelper.h"
-MODULE_AUTHOR("Alex Beregszaszi <alex@naxine.org>");
-MODULE_DESCRIPTION("Provides userspace access to hardware (security violation!)");
+MODULE_AUTHOR("Alex Beregszaszi <alex@naxine.org> and Nick Kurshev <nickols_k@mail.ru>");
+MODULE_DESCRIPTION("Provides userspace access to hardware");
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif
@@ -97,9 +113,6 @@ 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 dhahelper_memory_t last_mem_request;
-
-
static int dhahelper_open(struct inode *inode, struct file *file)
{
if (dhahelper_verbosity > 1)
@@ -209,44 +222,176 @@ static int dhahelper_port(dhahelper_port_t * arg)
return 0;
}
-static int dhahelper_memory(dhahelper_memory_t * arg)
+/*******************************/
+/* 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)
{
- dhahelper_memory_t mem;
- if (copy_from_user(&mem, arg, sizeof(dhahelper_memory_t)))
+ 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 adr)
+{
+ unsigned long va, kva, ret;
+
+ va = VMALLOC_VMADDR(adr);
+ 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 adr)
+{
+ unsigned long va, kva, ret;
+
+ va = VMALLOC_VMADDR(adr);
+ 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)
{
- if (dhahelper_verbosity > 0)
- printk(KERN_ERR "dhahelper: failed copy from userspace\n");
- return -EFAULT;
+ 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);
+ mem_map_reserve(virt_to_page(__va(page)));
+ adr+=PAGE_SIZE;
+ size-=PAGE_SIZE;
+ }
}
- switch(mem.operation)
+ 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)
{
- case MEMORY_OP_MAP:
- {
-#if 1
- memcpy(&last_mem_request, &mem, sizeof(dhahelper_memory_t));
-#else
- mem.ret = do_mmap(file, mem.start, mem.size, PROT_READ|PROT_WRITE,
- MAP_SHARED, mem.offset);
-#endif
- break;
- }
- case MEMORY_OP_UNMAP:
- break;
- default:
- if (dhahelper_verbosity > 0)
- printk(KERN_ERR "dhahelper: invalid memory operation (%d)\n",
- mem.operation);
- return -EINVAL;
+ lock_it:
+ page = uvirt_to_pa((unsigned long)addr);
+ LockPage(virt_to_page(__va(page)));
+ SetPageReserved(virt_to_page(__va(page)));
}
- if (copy_to_user(arg, &mem, sizeof(dhahelper_memory_t)))
+ else
{
- if (dhahelper_verbosity > 0)
- printk(KERN_ERR "dhahelper: failed copy to userspace\n");
- return -EFAULT;
+ 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);
+ mem_map_unreserve(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;
@@ -264,7 +409,7 @@ static int dhahelper_virt_to_phys(dhahelper_vmi_t *arg)
for(i=0;i<nitems;i++)
{
unsigned long result;
- result = virt_to_phys(addr);
+ result = uvirt_to_pa((unsigned long)addr);
if (copy_to_user(&mem.realaddr[i], &result, sizeof(unsigned long)))
{
if (dhahelper_verbosity > 0)
@@ -293,7 +438,7 @@ static int dhahelper_virt_to_bus(dhahelper_vmi_t *arg)
for(i=0;i<nitems;i++)
{
unsigned long result;
- result = virt_to_bus(addr);
+ result = uvirt_to_bus((unsigned long)addr);
if (copy_to_user(&mem.realaddr[i], &result, sizeof(unsigned long)))
{
if (dhahelper_verbosity > 0)
@@ -305,6 +450,291 @@ static int dhahelper_virt_to_bus(dhahelper_vmi_t *arg)
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;
+ 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 void 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);
+}
+
+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 int dhahelper_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
@@ -319,9 +749,16 @@ static int dhahelper_ioctl(struct inode *inode, struct file *file,
{
case DHAHELPER_GET_VERSION: return dhahelper_get_version((int *)arg);
case DHAHELPER_PORT: return dhahelper_port((dhahelper_port_t *)arg);
- case DHAHELPER_MEMORY: return dhahelper_memory((dhahelper_memory_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);
default:
if (dhahelper_verbosity > 0)
printk(KERN_ERR "dhahelper: invalid ioctl (%x)\n", cmd);
@@ -330,36 +767,196 @@ static int dhahelper_ioctl(struct inode *inode, struct file *file,
return 0;
}
-static int dhahelper_mmap(struct file *file, struct vm_area_struct *vma)
+/*
+ fops functions were shamelessly stolen from linux-kernel project ;)
+*/
+
+static loff_t dhahelper_lseek(struct file * file, loff_t offset, int orig)
{
- if (last_mem_request.operation != MEMORY_OP_MAP)
- {
- if (dhahelper_verbosity > 0)
- printk(KERN_ERR "dhahelper: mapping not requested before mmap\n");
- return -EFAULT;
- }
-
- if (dhahelper_verbosity > 1)
- printk(KERN_INFO "dhahelper: mapping %x (size: %x)\n",
- last_mem_request.start+last_mem_request.offset, last_mem_request.size);
-
- if (remap_page_range(0, last_mem_request.start + last_mem_request.offset,
- last_mem_request.size, vma->vm_page_prot))
- {
- if (dhahelper_verbosity > 0)
- printk(KERN_ERR "dhahelper: error mapping memory\n");
- return -EFAULT;
- }
+ 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;
+ }
+}
- return 0;
+/*
+ * 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;
+
+ /*
+ * 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 (remap_page_range(vma->vm_start, offset, vma->vm_end-vma->vm_start,
+ vma->vm_page_prot))
+ return -EAGAIN;
+ return 0;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
static struct file_operations dhahelper_fops =
{
- /*llseek*/ NULL,
- /*read*/ NULL,
- /*write*/ NULL,
+ /*llseek*/ dhahelper_lseek,
+ /*read*/ dhahelper_read,
+ /*write*/ dhahelper_write,
/*readdir*/ NULL,
/*poll*/ NULL,
/*ioctl*/ dhahelper_ioctl,
@@ -374,43 +971,73 @@ static struct file_operations dhahelper_fops =
{
owner: THIS_MODULE,
ioctl: dhahelper_ioctl,
- mmap: dhahelper_mmap,
open: dhahelper_open,
- release: dhahelper_release
+ release: dhahelper_release,
+ llseek: dhahelper_lseek,
+ read: dhahelper_read,
+ write: dhahelper_write,
+ mmap: dhahelper_mmap,
};
#endif
-#if KERNEL_VERSION < KERNEL_VERSION(2,4,0)
+#ifdef CONFIG_DEVFS_FS
+devfs_handle_t dha_devfsh;
+#endif
+
+#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");
+#ifdef CONFIG_DEVFS_FS
+ dha_devfsh = devfs_register(NULL, "dhahelper", DEVFS_FL_NONE,
+ dhahelper_major, 0,
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ &dhahelper_fops, NULL);
+ if(!dha_devfsh){
+ err = -EIO;
+ }
+#else
if(register_chrdev(dhahelper_major, "dhahelper", &dhahelper_fops))
{
+ err = -EIO;
+ }
+#endif
+ if(err){
if (dhahelper_verbosity > 0)
printk(KERN_ERR "dhahelper: unable to register character device (major: %d)\n",
dhahelper_major);
- return -EIO;
+ return err;
}
-
+ memset(dha_irqs, 0, sizeof(dha_irqs));
return 0;
}
-#if KERNEL_VERSION < KERNEL_VERSION(2,4,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);
+
+#ifdef CONFIG_DEVFS_FS
+ devfs_unregister(dha_devfsh);
+#else
unregister_chrdev(dhahelper_major, "dhahelper");
+#endif
}
EXPORT_NO_SYMBOLS;
-#if KERNEL_VERSION >= KERNEL_VERSION(2,4,0)
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
module_init(init_dhahelper);
module_exit(exit_dhahelper);
#endif