blob: 40b0233a30e5e864a14f22e653cc95059bfd336d [file] [log] [blame]
/*
* 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/system.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)
{
if (!ia64_platform_is("sn2"))
return -ENODEV;
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 <= 0)
return node;
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 <= 0)
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 <= 0)
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 <= 0)
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 ia64_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);
}