diff options
Diffstat (limited to 'arch/ia64/sn/io/drivers/pciba.c')
-rw-r--r-- | arch/ia64/sn/io/drivers/pciba.c | 941 |
1 files changed, 941 insertions, 0 deletions
diff --git a/arch/ia64/sn/io/drivers/pciba.c b/arch/ia64/sn/io/drivers/pciba.c new file mode 100644 index 00000000000000..c3f01e28896f14 --- /dev/null +++ b/arch/ia64/sn/io/drivers/pciba.c @@ -0,0 +1,941 @@ +/* + * arch/ia64/sn/io/pciba.c + * + * IRIX PCIBA-inspired user mode PCI interface + * + * requires: devfs + * + * device nodes show up in /dev/pci/BB/SS.F (where BB is the bus the + * device is on, SS is the slot the device is in, and F is the + * device's function on a multi-function card). + * + * when compiled into the kernel, it will only be initialized by the + * sgi sn1 specific initialization code. in this case, device nodes + * are under /dev/hw/..../ + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of + * this archive for more details. + * + * Copyright (C) 2001-2003 Silicon Graphics, Inc. All rights reserved. + * + * 03262001 - Initial version by Chad Talbott + */ + + +/* jesse's beefs: + + register_pci_device should be documented + + grossness with do_swap should be documented + + big, gross union'ized node_data should be replaced with independent + structures + + replace global list of nodes with global lists of resources. could + use object oriented approach of allocating and cleaning up + resources. + +*/ + + +#include <linux/config.h> +#include <linux/module.h> +#include <asm/sn/sgi.h> +#include <asm/sn/iograph.h> +#include <asm/sn/invent.h> +#include <asm/sn/hcl.h> +#include <asm/sn/labelcl.h> +#include <linux/pci.h> +#include <linux/list.h> + +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/mman.h> +#include <linux/init.h> +#include <linux/raw.h> +#include <linux/capability.h> + +#include <asm/uaccess.h> +#include <asm/sn/sgi.h> +#include <asm/io.h> +#include <asm/pgalloc.h> +#include <asm/page.h> + +#include <asm/sn/pci/pciba.h> + + +MODULE_DESCRIPTION("User mode PCI interface"); +MODULE_AUTHOR("Chad Talbott"); + + +#undef DEBUG_PCIBA +/* #define DEBUG_PCIBA */ + +#undef TRACE_PCIBA +/* #define TRACE_PCIBA */ + +#if defined(DEBUG_PCIBA) +# define DPRINTF(x...) printk(KERN_DEBUG x) +#else +# define DPRINTF(x...) +#endif + +#if defined(TRACE_PCIBA) +# if defined(__GNUC__) +# define TRACE() printk(KERN_DEBUG "%s:%d:%s\n", \ + __FILE__, __LINE__, __FUNCTION__) +# else +# define TRACE() printk(KERN_DEBUG "%s:%d\n", __LINE__, __FILE__) +# endif +#else +# define TRACE() +#endif + + +typedef enum { failure, success } status; +typedef enum { false, true } boolean; + + +/* major data structures: + + struct node_data - + + one for each file registered with devfs. contains everything + that any file's fops would need to know about. + + struct dma_allocation - + + a single DMA allocation. only the 'dma' nodes care about + these. they are there primarily to allow the driver to look + up the kernel virtual address of dma buffers allocated by + pci_alloc_consistent, as the application is only given the + physical address (to program the device's dma, presumably) and + cannot supply the kernel virtual address when freeing the + buffer. + + it's also useful to maintain a list of buffers allocated + through a specific node to allow some sanity checking by this + driver. this prevents (for example) a broken application from + freeing buffers that it didn't allocate, or buffers allocated + on another node. + + global_node_list - + + a list of all nodes allocated. this allows the driver to free + all the memory it has 'kmalloc'd in case of an error, or on + module removal. + + global_dma_list - + + a list of all dma buffers allocated by this driver. this + allows the driver to 'pci_free_consistent' all buffers on + module removal or error. + +*/ + + +struct node_data { + /* flat list of all the device nodes. makes it easy to free + them all when we're unregistered */ + struct list_head global_node_list; + vertex_hdl_t devfs_handle; + + void (* cleanup)(struct node_data *); + + union { + struct { + struct pci_dev * dev; + struct list_head dma_allocs; + boolean mmapped; + } dma; + struct { + struct pci_dev * dev; + u32 saved_rom_base_reg; + boolean mmapped; + } rom; + struct { + struct resource * res; + } base; + struct { + struct pci_dev * dev; + } config; + } u; +}; + +struct dma_allocation { + struct list_head list; + + dma_addr_t handle; + void * va; + size_t size; +}; + + +static LIST_HEAD(global_node_list); +static LIST_HEAD(global_dma_list); + + +/* module entry points */ +int __init pciba_init(void); +void __exit pciba_exit(void); + +static status __init register_with_devfs(void); +static void __exit unregister_with_devfs(void); + +static status __init register_pci_device(vertex_hdl_t device_dir_handle, + struct pci_dev * dev); + +/* file operations */ +static int generic_open(struct inode * inode, struct file * file); +static int rom_mmap(struct file * file, struct vm_area_struct * vma); +static int rom_release(struct inode * inode, struct file * file); +static int base_mmap(struct file * file, struct vm_area_struct * vma); +static int config_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, + unsigned long arg); +static int dma_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, + unsigned long arg); +static int dma_mmap(struct file * file, struct vm_area_struct * vma); + +/* support routines */ +static int mmap_pci_address(struct vm_area_struct * vma, unsigned long pci_va); +static int mmap_kernel_address(struct vm_area_struct * vma, void * kernel_va); + +#ifdef DEBUG_PCIBA +static void dump_nodes(struct list_head * nodes); +static void dump_allocations(struct list_head * dalp); +#endif + +/* file operations for each type of node */ +static struct file_operations rom_fops = { + owner: THIS_MODULE, + mmap: rom_mmap, + open: generic_open, + release: rom_release +}; + + +static struct file_operations base_fops = { + owner: THIS_MODULE, + mmap: base_mmap, + open: generic_open +}; + + +static struct file_operations config_fops = { + owner: THIS_MODULE, + ioctl: config_ioctl, + open: generic_open +}; + +static struct file_operations dma_fops = { + owner: THIS_MODULE, + ioctl: dma_ioctl, + mmap: dma_mmap, + open: generic_open +}; + + +module_init(pciba_init); +module_exit(pciba_exit); + + +int __init +pciba_init(void) +{ + TRACE(); + + if (register_with_devfs() == failure) + return 1; /* failure */ + + printk("PCIBA (a user mode PCI interface) initialized.\n"); + + return 0; /* success */ +} + + +void __exit +pciba_exit(void) +{ + TRACE(); + + /* FIXME: should also free all that memory that we allocated + ;) */ + unregister_with_devfs(); +} + + +# if 0 +static void __exit +free_nodes(void) +{ + struct node_data * nd; + + TRACE(); + + list_for_each(nd, &node_list) { + kfree(list_entry(nd, struct nd, node_list)); + } +} +#endif + + +static vertex_hdl_t pciba_devfs_handle; + + +extern vertex_hdl_t +devfn_to_vertex(unsigned char busnum, unsigned int devfn); + +static status __init +register_with_devfs(void) +{ + struct pci_dev * dev; + vertex_hdl_t device_dir_handle; + + TRACE(); + + /* FIXME: don't forget /dev/.../pci/mem & /dev/.../pci/io */ + + pci_for_each_dev(dev) { + device_dir_handle = devfn_to_vertex(dev->bus->number, + dev->devfn); + if (device_dir_handle == NULL) + return failure; + + if (register_pci_device(device_dir_handle, dev) == failure) { + hwgraph_vertex_destroy(pciba_devfs_handle); + return failure; + } + } + + return success; +} + +static void __exit +unregister_with_devfs(void) +{ + struct list_head * lhp; + struct node_data * nd; + + TRACE(); + + list_for_each(lhp, &global_node_list) { + nd = list_entry(lhp, struct node_data, global_node_list); + hwgraph_vertex_destroy(nd->devfs_handle); + } + +} + + +struct node_data * new_node(void) +{ + struct node_data * node; + + TRACE(); + + node = kmalloc(sizeof(struct node_data), GFP_KERNEL); + if (node == NULL) + return NULL; + list_add(&node->global_node_list, &global_node_list); + return node; +} + + +void dma_cleanup(struct node_data * dma_node) +{ + TRACE(); + + /* FIXME: should free these allocations */ +#ifdef DEBUG_PCIBA + dump_allocations(&dma_node->u.dma.dma_allocs); +#endif + hwgraph_vertex_destroy(dma_node->devfs_handle); +} + + +void init_dma_node(struct node_data * node, + struct pci_dev * dev, vertex_hdl_t dh) +{ + TRACE(); + + node->devfs_handle = dh; + node->u.dma.dev = dev; + node->cleanup = dma_cleanup; + INIT_LIST_HEAD(&node->u.dma.dma_allocs); +} + + +void rom_cleanup(struct node_data * rom_node) +{ + TRACE(); + + if (rom_node->u.rom.mmapped) + pci_write_config_dword(rom_node->u.rom.dev, + PCI_ROM_ADDRESS, + rom_node->u.rom.saved_rom_base_reg); + hwgraph_vertex_destroy(rom_node->devfs_handle); +} + + +void init_rom_node(struct node_data * node, + struct pci_dev * dev, vertex_hdl_t dh) +{ + TRACE(); + + node->devfs_handle = dh; + node->u.rom.dev = dev; + node->cleanup = rom_cleanup; + node->u.rom.mmapped = false; +} + + +static status __init +register_pci_device(vertex_hdl_t device_dir_handle, struct pci_dev * dev) +{ + struct node_data * nd; + char devfs_path[20]; + vertex_hdl_t node_devfs_handle; + int ri; + + TRACE(); + + + /* register nodes for all the device's base address registers */ + for (ri = 0; ri < PCI_ROM_RESOURCE; ri++) { + if (pci_resource_len(dev, ri) != 0) { + sprintf(devfs_path, "base/%d", ri); + if (hwgraph_register(device_dir_handle, devfs_path, + 0, DEVFS_FL_NONE, + 0, 0, + S_IFCHR | S_IRUSR | S_IWUSR, 0, 0, + &base_fops, + &dev->resource[ri]) == NULL) + return failure; + } + } + + /* register a node corresponding to the first MEM resource on + the device */ + for (ri = 0; ri < PCI_ROM_RESOURCE; ri++) { + if (dev->resource[ri].flags & IORESOURCE_MEM && + pci_resource_len(dev, ri) != 0) { + if (hwgraph_register(device_dir_handle, "mem", + 0, DEVFS_FL_NONE, 0, 0, + S_IFCHR | S_IRUSR | S_IWUSR, 0, 0, + &base_fops, + &dev->resource[ri]) == NULL) + return failure; + break; + } + } + + /* also register a node corresponding to the first IO resource + on the device */ + for (ri = 0; ri < PCI_ROM_RESOURCE; ri++) { + if (dev->resource[ri].flags & IORESOURCE_IO && + pci_resource_len(dev, ri) != 0) { + if (hwgraph_register(device_dir_handle, "io", + 0, DEVFS_FL_NONE, 0, 0, + S_IFCHR | S_IRUSR | S_IWUSR, 0, 0, + &base_fops, + &dev->resource[ri]) == NULL) + return failure; + break; + } + } + + /* register a node corresponding to the device's ROM resource, + if present */ + if (pci_resource_len(dev, PCI_ROM_RESOURCE) != 0) { + nd = new_node(); + if (nd == NULL) + return failure; + node_devfs_handle = hwgraph_register(device_dir_handle, "rom", + 0, DEVFS_FL_NONE, 0, 0, + S_IFCHR | S_IRUSR, 0, 0, + &rom_fops, nd); + if (node_devfs_handle == NULL) + return failure; + init_rom_node(nd, dev, node_devfs_handle); + } + + /* register a node that allows ioctl's to read and write to + the device's config space */ + if (hwgraph_register(device_dir_handle, "config", 0, DEVFS_FL_NONE, + 0, 0, S_IFCHR | S_IRUSR | S_IWUSR, 0, 0, + &config_fops, dev) == NULL) + return failure; + + + /* finally, register a node that allows ioctl's to allocate + and free DMA buffers, as well as memory map those + buffers. */ + nd = new_node(); + if (nd == NULL) + return failure; + node_devfs_handle = + hwgraph_register(device_dir_handle, "dma", 0, DEVFS_FL_NONE, + 0, 0, S_IFCHR | S_IRUSR | S_IWUSR, 0, 0, + &dma_fops, nd); + if (node_devfs_handle == NULL) + return failure; + init_dma_node(nd, dev, node_devfs_handle); + +#ifdef DEBUG_PCIBA + dump_nodes(&global_node_list); +#endif + + return success; +} + + +static int +generic_open(struct inode * inode, struct file * file) +{ + TRACE(); + + /* FIXME: should check that they're not trying to open the ROM + writable */ + + return 0; /* success */ +} + + +static int +rom_mmap(struct file * file, struct vm_area_struct * vma) +{ + unsigned long pci_pa; + struct node_data * nd; + + TRACE(); + +#ifdef CONFIG_HWGFS_FS + nd = (struct node_data * )file->f_dentry->d_fsdata; +#else + nd = (struct node_data * )file->private_data; +#endif + + pci_pa = pci_resource_start(nd->u.rom.dev, PCI_ROM_RESOURCE); + + if (!nd->u.rom.mmapped) { + nd->u.rom.mmapped = true; + DPRINTF("Enabling ROM address decoder.\n"); + DPRINTF( +"rom_mmap: FIXME: some cards do not allow both ROM and memory addresses to\n" +"rom_mmap: FIXME: be enabled simultaneously, as they share a decoder.\n"); + pci_read_config_dword(nd->u.rom.dev, PCI_ROM_ADDRESS, + &nd->u.rom.saved_rom_base_reg); + DPRINTF("ROM base address contains %x\n", + nd->u.rom.saved_rom_base_reg); + pci_write_config_dword(nd->u.rom.dev, PCI_ROM_ADDRESS, + nd->u.rom.saved_rom_base_reg | + PCI_ROM_ADDRESS_ENABLE); + } + + return mmap_pci_address(vma, pci_pa); +} + + +static int +rom_release(struct inode * inode, struct file * file) +{ + struct node_data * nd; + + TRACE(); + +#ifdef CONFIG_HWGFS_FS + nd = (struct node_data * )file->f_dentry->d_fsdata; +#else + nd = (struct node_data * )file->private_data; +#endif + + if (nd->u.rom.mmapped) { + nd->u.rom.mmapped = false; + DPRINTF("Disabling ROM address decoder.\n"); + pci_write_config_dword(nd->u.rom.dev, PCI_ROM_ADDRESS, + nd->u.rom.saved_rom_base_reg); + } + return 0; /* indicate success */ +} + + +static int +base_mmap(struct file * file, struct vm_area_struct * vma) +{ + struct resource * resource; + + TRACE(); + +#ifdef CONFIG_HWGFS_FS + resource = (struct resource *)file->f_dentry->d_fsdata; +#else + resource = (struct resource *)file->private_data; +#endif + + return mmap_pci_address(vma, resource->start); +} + + +static int +config_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, + unsigned long arg) +{ + struct pci_dev * dev; + + union cfg_data { + uint8_t byte; + uint16_t word; + uint32_t dword; + } read_data, write_data; + + int dir, size, offset; + + TRACE(); + + DPRINTF("cmd = %x (DIR = %x, TYPE = %x, NR = %x, SIZE = %x)\n", + cmd, + _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd)); + DPRINTF("arg = %lx\n", arg); + +#ifdef CONFIG_HWGFS_FS + dev = (struct pci_dev *)file->f_dentry->d_fsdata; +#else + dev = (struct pci_dev *)file->private_data; +#endif + + /* PCIIOCCFG{RD,WR}: read and/or write PCI configuration + space. If both, the read happens first (this becomes a swap + operation, atomic with respect to other updates through + this path). */ + + dir = _IOC_DIR(cmd); + +#define do_swap(suffix, type) \ + do { \ + if (dir & _IOC_READ) { \ + pci_read_config_##suffix(dev, _IOC_NR(cmd), \ + &read_data.suffix); \ + } \ + if (dir & _IOC_WRITE) { \ + get_user(write_data.suffix, (type)arg); \ + pci_write_config_##suffix(dev, _IOC_NR(cmd), \ + write_data.suffix); \ + } \ + if (dir & _IOC_READ) { \ + put_user(read_data.suffix, (type)arg); \ + } \ + } while (0) + + size = _IOC_SIZE(cmd); + offset = _IOC_NR(cmd); + + DPRINTF("sanity check\n"); + if (((size > 0) || (size <= 4)) && + ((offset + size) <= 256) && + (dir & (_IOC_READ | _IOC_WRITE))) { + + switch (size) + { + case 1: + do_swap(byte, uint8_t *); + break; + case 2: + do_swap(word, uint16_t *); + break; + case 4: + do_swap(dword, uint32_t *); + break; + default: + DPRINTF("invalid ioctl\n"); + return -EINVAL; + } + } else + return -EINVAL; + + return 0; +} + + +#ifdef DEBUG_PCIBA +static void +dump_allocations(struct list_head * dalp) +{ + struct dma_allocation * dap; + struct list_head * p; + + printk("{\n"); + list_for_each(p, dalp) { + dap = list_entry(p, struct dma_allocation, + list); + printk(" handle = %lx, va = %p\n", + dap->handle, dap->va); + } + printk("}\n"); +} + +static void +dump_nodes(struct list_head * nodes) +{ + struct node_data * ndp; + struct list_head * p; + + printk("{\n"); + list_for_each(p, nodes) { + ndp = list_entry(p, struct node_data, + global_node_list); + printk(" %p\n", (void *)ndp); + } + printk("}\n"); +} + + +#if 0 +#define NEW(ptr) (ptr = kmalloc(sizeof (*(ptr)), GFP_KERNEL)) + +static void +test_list(void) +{ + u64 i; + LIST_HEAD(the_list); + + for (i = 0; i < 5; i++) { + struct dma_allocation * new_alloc; + NEW(new_alloc); + new_alloc->va = (void *)i; + new_alloc->handle = 5*i; + printk("%d - the_list->next = %lx\n", i, the_list.next); + list_add(&new_alloc->list, &the_list); + } + dump_allocations(&the_list); +} +#endif +#endif + + +static LIST_HEAD(dma_buffer_list); + + +static int +dma_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, + unsigned long arg) +{ + struct node_data * nd; + uint64_t argv; + int result; + struct dma_allocation * dma_alloc; + struct list_head * iterp; + + TRACE(); + + DPRINTF("cmd = %x\n", cmd); + DPRINTF("arg = %lx\n", arg); + +#ifdef CONFIG_HWGFS_FS + nd = (struct node_data *)file->f_dentry->d_fsdata; +#else + nd = (struct node_data *)file->private_data; +#endif + +#ifdef DEBUG_PCIBA + DPRINTF("at dma_ioctl entry\n"); + dump_allocations(&nd->u.dma.dma_allocs); +#endif + + switch (cmd) { + case PCIIOCDMAALLOC: + /* PCIIOCDMAALLOC: allocate a chunk of physical memory + and set it up for DMA. Return the PCI address that + gets to it. */ + DPRINTF("case PCIIOCDMAALLOC (%lx)\n", PCIIOCDMAALLOC); + + if ( (result = get_user(argv, (uint64_t *)arg)) ) + return result; + DPRINTF("argv (size of buffer) = %lx\n", argv); + + dma_alloc = (struct dma_allocation *) + kmalloc(sizeof(struct dma_allocation), GFP_KERNEL); + if (dma_alloc == NULL) + return -ENOMEM; + + dma_alloc->size = (size_t)argv; + dma_alloc->va = pci_alloc_consistent(nd->u.dma.dev, + dma_alloc->size, + &dma_alloc->handle); + DPRINTF("dma_alloc->va = %p, dma_alloc->handle = %lx\n", + dma_alloc->va, dma_alloc->handle); + if (dma_alloc->va == NULL) { + kfree(dma_alloc); + return -ENOMEM; + } + + list_add(&dma_alloc->list, &nd->u.dma.dma_allocs); + if ( (result = put_user((uint64_t)dma_alloc->handle, + (uint64_t *)arg)) ) { + DPRINTF("put_user failed\n"); + pci_free_consistent(nd->u.dma.dev, (size_t)argv, + dma_alloc->va, dma_alloc->handle); + kfree(dma_alloc); + return result; + } + +#ifdef DEBUG_PCIBA + DPRINTF("after insertion\n"); + dump_allocations(&nd->u.dma.dma_allocs); +#endif + break; + + case PCIIOCDMAFREE: + DPRINTF("case PCIIOCDMAFREE (%lx)\n", PCIIOCDMAFREE); + + if ( (result = get_user(argv, (uint64_t *)arg)) ) { + DPRINTF("get_user failed\n"); + return result; + } + + DPRINTF("argv (physical address of DMA buffer) = %lx\n", argv); + list_for_each(iterp, &nd->u.dma.dma_allocs) { + struct dma_allocation * da = + list_entry(iterp, struct dma_allocation, list); + if (da->handle == argv) { + pci_free_consistent(nd->u.dma.dev, da->size, + da->va, da->handle); + list_del(&da->list); + kfree(da); +#ifdef DEBUG_PCIBA + DPRINTF("after deletion\n"); + dump_allocations(&nd->u.dma.dma_allocs); +#endif + return 0; /* success */ + } + } + /* previously allocated dma buffer wasn't found */ + DPRINTF("attempt to free invalid dma handle\n"); + return -EINVAL; + + default: + DPRINTF("undefined ioctl\n"); + return -EINVAL; + } + + DPRINTF("success\n"); + return 0; +} + + +static int +dma_mmap(struct file * file, struct vm_area_struct * vma) +{ + struct node_data * nd; + struct list_head * iterp; + int result; + + TRACE(); + +#ifdef CONFIG_HWGFS_FS + nd = (struct node_data *)file->f_dentry->d_fsdata; +#else + nd = (struct node_data *)file->private_data; +#endif + + DPRINTF("vma->vm_start is %lx\n", vma->vm_start); + DPRINTF("vma->vm_end is %lx\n", vma->vm_end); + DPRINTF("offset = %lx\n", vma->vm_pgoff); + + /* get kernel virtual address for the dma buffer (necessary + * for the mmap). */ + list_for_each(iterp, &nd->u.dma.dma_allocs) { + struct dma_allocation * da = + list_entry(iterp, struct dma_allocation, list); + /* why does mmap shift its offset argument? */ + if (da->handle == vma->vm_pgoff << PAGE_SHIFT) { + DPRINTF("found dma handle\n"); + if ( (result = mmap_kernel_address(vma, + da->va)) ) { + return result; /* failure */ + } else { + /* it seems like at least one of these + should show up in user land.... + I'm missing something */ + *(char *)da->va = 0xaa; + strncpy(da->va, " Toastie!", da->size); + if (put_user(0x18badbeeful, + (u64 *)vma->vm_start)) + DPRINTF("put_user failed?!\n"); + return 0; /* success */ + } + + } + } + DPRINTF("attempt to mmap an invalid dma handle\n"); + return -EINVAL; +} + + +static int +mmap_pci_address(struct vm_area_struct * vma, unsigned long pci_va) +{ + unsigned long pci_pa; + + TRACE(); + + DPRINTF("vma->vm_start is %lx\n", vma->vm_start); + DPRINTF("vma->vm_end is %lx\n", vma->vm_end); + + /* the size of the vma doesn't necessarily correspond to the + size specified in the mmap call. So we can't really do any + kind of sanity check here. This is a dangerous driver, and + it's very easy for a user process to kill the machine. */ + + DPRINTF("PCI base at virtual address %lx\n", pci_va); + /* the __pa macro is intended for region 7 on IA64, so it + doesn't work for region 6 */ + /* pci_pa = __pa(pci_va); */ + /* should be replaced by __tpa or equivalent (preferably a + generic equivalent) */ + pci_pa = pci_va & ~0xe000000000000000ul; + DPRINTF("PCI base at physical address %lx\n", pci_pa); + + /* there are various arch-specific versions of this function + defined in linux/drivers/char/mem.c, but it would be nice + if all architectures put it in pgtable.h. it's defined + there for ia64.... */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + vma->vm_flags |= VM_NONCACHED | VM_RESERVED | VM_IO; + + return io_remap_page_range(vma->vm_start, pci_pa, + vma->vm_end-vma->vm_start, + vma->vm_page_prot); +} + + +static int +mmap_kernel_address(struct vm_area_struct * vma, void * kernel_va) +{ + unsigned long kernel_pa; + + TRACE(); + + DPRINTF("vma->vm_start is %lx\n", vma->vm_start); + DPRINTF("vma->vm_end is %lx\n", vma->vm_end); + + /* the size of the vma doesn't necessarily correspond to the + size specified in the mmap call. So we can't really do any + kind of sanity check here. This is a dangerous driver, and + it's very easy for a user process to kill the machine. */ + + DPRINTF("mapping virtual address %p\n", kernel_va); + kernel_pa = __pa(kernel_va); + DPRINTF("mapping physical address %lx\n", kernel_pa); + + vma->vm_flags |= VM_NONCACHED | VM_RESERVED | VM_IO; + + return remap_page_range(vma->vm_start, kernel_pa, + vma->vm_end-vma->vm_start, + vma->vm_page_prot); +} |