/* _NVRM_COPYRIGHT_BEGIN_
 *
 * Copyright 1999-2001 by NVIDIA Corporation.  All rights reserved.  All
 * information contained herein is proprietary and confidential to NVIDIA
 * Corporation.  Any use, reproduction, or disclosure without the written
 * permission of NVIDIA Corporation is prohibited.
 *
 * _NVRM_COPYRIGHT_END_
 */


#include "nv-misc.h"
#include "os-interface.h"
#include "nv-linux.h"
#include "nv_compiler.h"
#include "os-agp.h"


/*
 * our global state; one per device
 */

nv_linux_state_t nv_linux_devices[NV_MAX_DEVICES] = { { { 0 } } };

#ifdef CONFIG_PM
/* XXX PM do we only need one, or one for each device? */
static struct pm_dev *pm_nv_dev;
#endif

/*
 * And one for the control device
 */

nv_linux_state_t nv_ctl_device = { { 0 } };

// keep track of opened clients and their process id so they
//   can be free'd up on abnormal close
nv_client_t       nv_clients[NV_MAX_CLIENTS];
struct tq_struct  nv_bottom_halves[NV_MAX_CLIENTS];


#ifdef CONFIG_DEVFS_FS
devfs_handle_t  nv_dev_handle[NV_MAX_DEVICES];
devfs_handle_t  nv_ctl_handle;
#endif

/*
 * pick apart our minor device number
 * low 3 bits is NV device
 * if 255, then its the control device
 */

#define NV_DEVICE_NUMBER(_minor) ((_minor) & 0x0f)
#define NV_DEVICE_IS_CONTROL_DEVICE(_minor) \
             (((_minor) & 0xFF) == 0xFF)

// #define NV_DBG_MEM 1
#undef NV_DBG_MEM

// allow an easy way to convert all debug printfs related to memory
// management back and forth between 'info' and 'errors'
#if defined(NV_DBG_MEM)
#define NV_DBG_MEMINFO NV_DBG_ERRORS
#else
#define NV_DBG_MEMINFO NV_DBG_INFO
#endif

/***
 *** STATIC functions, only in this file
 ***/

/* nvos_ functions.. do not take a state device parameter  */
static int      nvos_set_primary_card(nv_ioctl_primary_card_t *info);
static int      nvos_probe_devices(void);
static void     nvos_proc_create(void);
static void     nvos_proc_remove(void);
static int      nvos_malloc_pages(void **, unsigned long);
static void     nvos_unlock_pages(void **, unsigned long);
static void     nvos_free_pages(void **, unsigned long);

#define nvos_unlock_and_free_pages(at_count, page_list, page_count) \
    if (page_list) {                                                \
        if (at_count == 0)                                          \
            nvos_unlock_pages(page_list, page_count);               \
        nvos_free_pages(page_list, page_count);                     \
    }

static nv_alloc_t  *nvos_create_alloc(int);
static int          nvos_free_alloc(nv_alloc_t *);

/* nvl_ functions.. take a linux state device pointer */
static nv_alloc_t  *nvl_find_alloc(nv_linux_state_t *, unsigned long, unsigned long);
static int          nvl_add_alloc(nv_linux_state_t *, nv_alloc_t *);
static int          nvl_remove_alloc(nv_linux_state_t *, nv_alloc_t *);

/* lock-related functions that should only be called from this file */
static void nv_lock_init_locks(nv_state_t *nv);
static void nv_lock_ldata(nv_state_t *nv);
static void nv_unlock_ldata(nv_state_t *nv);
static void nv_lock_at(nv_state_t *nv);
static void nv_unlock_at(nv_state_t *nv);

/***
 *** EXPORTS to Linux Kernel
 ***/

/* linux module interface functions (called by linux kernel) */
int           init_module(void);
void          cleanup_module(void);

/* nv_kern_ functions, interfaces used by linux kernel */
void          nv_kern_vma_open(struct vm_area_struct *vma);
void          nv_kern_vma_release(struct vm_area_struct *vma);

int           nv_kern_open(struct inode *, struct file *);
int           nv_kern_close(struct inode *, struct file *);
int           nv_kern_mmap(struct file *, struct vm_area_struct *);
unsigned int  nv_kern_poll(struct file *, poll_table *);
int           nv_kern_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
void          nv_kern_bh(void *);
void          nv_kern_isr(int, void *, struct pt_regs *);
void          nv_kern_rc_timer(unsigned long);
#ifdef CONFIG_PM
int           nv_kern_pm(struct pm_dev *dev, pm_request_t rqst, void *data);
#endif

int           nv_kern_read_cardinfo(char *, char **, off_t off, int, int *, void *);
int           nv_kern_read_status(char *, char **, off_t off, int, int *, void *);
int           nv_kern_read_agpinfo(char *, char **, off_t off, int, int *, void *);
int           nv_kern_read_version(char *, char **, off_t off, int, int *, void *);
int           nv_kern_read_legacy(char *, char **, off_t off, int, int *, void *);

int           nv_kern_ctl_open(struct inode *, struct file *);
int           nv_kern_ctl_close(struct inode *, struct file *);
unsigned int  nv_kern_ctl_poll(struct file *, poll_table *);

/***
 *** see nv.h for functions exported to other parts of resman
 ***/


/* character driver entry points */

static struct file_operations nv_fops = {
    poll:           nv_kern_poll,
    ioctl:          nv_kern_ioctl,
    mmap:           nv_kern_mmap,
    open:           nv_kern_open,
    release:        nv_kern_close,
};

// Our reserved major device number.
int nv_major = NV_MAJOR_DEVICE_NUMBER;

// pull in the pointer to the NVID stamp from the binary module
extern const char *pNVRM_ID;



/***
 *** STATIC functions
 ***/

/* set which card is primary */
static int nvos_set_primary_card(nv_ioctl_primary_card_t *info)
{
    int i;

    for (i = 0; i < NV_MAX_DEVICES; i++)
    {
        if (NV_STATE_PTR(&nv_linux_devices[i])->bus == info->bus &&
            NV_STATE_PTR(&nv_linux_devices[i])->slot == info->slot)
        {
            NV_STATE_PTR(&nv_linux_devices[i])->flags |= NV_FLAG_POSTED;
        }
    }

    return 0;
}

/* get pci aperture information */
static void
nvos_get_pci_size(struct pci_dev *dev, nv_aperture_t *ap, u32 base)
{
    u32 base_reg, size;

    pci_read_config_dword(dev, base, &base_reg);
    pci_write_config_dword(dev, base, ~0);
    pci_read_config_dword(dev, base, &size);
    pci_write_config_dword(dev, base, base_reg);

    if (base == PCI_BASE_ADDRESS_0) {
        /* registers */
        ap->address = base_reg & PCI_BASE_ADDRESS_IO_MASK;
        size &= PCI_BASE_ADDRESS_IO_MASK;
    } else {
        /* framebuffer */
        ap->address = base_reg & PCI_BASE_ADDRESS_MEM_MASK;
        size &= PCI_BASE_ADDRESS_MEM_MASK;
    }

    /* translate the size */
    ap->size = ~(size - 1) & 0xffffffff;
}

/* find nvidia devices and set initial state */
static int
nvos_probe_devices(void)
{
    unsigned short count;
    unsigned short cmd;
    struct pci_dev *dev;

    /* for state tracking */
    nv_state_t *nv;
    nv_linux_state_t *nvl;

    count = 0;
    dev = (struct pci_dev *) 0;

    dev = pci_find_class(PCI_CLASS_DISPLAY_VGA << 8, dev);
    while (dev)
    {
        if ((dev->vendor != 0x10de) || (dev->device < 0x20))
            goto next;

        /* initialize bus-dependent config state */
        nvl = &nv_linux_devices[count];
        nv  = NV_STATE_PTR(nvl);

        nv->vendor_id     = dev->vendor;
        nv->device_id     = dev->device;
        nv->os_state      = (void *) nvl;
        nv->bus           = dev->bus->number;
        nv->slot          = PCI_SLOT(dev->devfn);

        nv_lock_init_locks(nv);
        pci_read_config_word(dev, PCI_COMMAND, &cmd);

        /* is the device currently enabled? */
        if (!(cmd & PCI_COMMAND_MEMORY) || !(cmd & PCI_COMMAND_MASTER)) {
            nv_printf(NV_DBG_INFO, "device not enabled; enabling");
            cmd |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
            pci_write_config_word(dev, PCI_COMMAND, cmd);
        }
    
        nvos_get_pci_size(dev, &nv->bar.regs, PCI_BASE_ADDRESS_0);
        nvos_get_pci_size(dev, &nv->bar.fb,   PCI_BASE_ADDRESS_1);
    
        nv->interrupt_line = dev->irq;

        /* check common error condition */
        if (nv->interrupt_line == 0) {
            nv_printf(NV_DBG_ERRORS, "nvidia: Can't find an IRQ for your NVIDIA card!  \n");
            nv_printf(NV_DBG_ERRORS, "nvidia: Please check your BIOS settings.         \n");
            nv_printf(NV_DBG_ERRORS, "nvidia: [Plug & Play OS   ] should be set to NO  \n");
            nv_printf(NV_DBG_ERRORS, "nvidia: [Assign IRQ to VGA] should be set to YES \n");
        }

        nv_printf(NV_DBG_INFO, "NVRM: %02x:%02x.%x %04x:%04x - 0x%08x [size=%dM]\n",
                nv->bus, nv->slot, PCI_FUNC(dev->devfn),
                nv->vendor_id, nv->device_id, nv->bar.regs.address,
                nv->bar.regs.size / (1024 * 1024));
        nv_printf(NV_DBG_INFO, "NVRM: %02x:%02x.%x %04x:%04x - 0x%08x [size=%dM]\n",
                nv->bus, nv->slot, PCI_FUNC(dev->devfn),
                nv->vendor_id, nv->device_id, nv->bar.fb.address,
                nv->bar.fb.size / (1024 * 1024));

        if (count++ == NV_MAX_DEVICES - 1) {
            nv_printf(NV_DBG_WARNINGS, "NVRM: maximum device number (%d) reached!\n", count);
            break;
        }

    next:
        dev = pci_find_class(PCI_CLASS_DISPLAY_VGA << 8, dev);
    }

    nv_printf(NV_DBG_INFO, "NVRM: found %d device%s\n", count, count ? "" : "s");

    return count;
}

#if defined(NV_DBG_MEM)
static void
nvos_list_page_count(void *page_list, unsigned long num_pages)
{
    unsigned long *page_ptr = (unsigned long *) page_list;

    if (page_ptr == NULL)
        return;

    nv_printf(NV_DBG_MEMINFO, "  page_table at 0x%x, %d pages\n", 
        page_ptr, num_pages);

    while (num_pages)
    {
        nv_printf(NV_DBG_MEMINFO, "  0x%x: count %d flags 0x%x\n", 
            *page_ptr, 
            (GET_MAP_NR(*page_ptr))->count,
            (GET_MAP_NR(*page_ptr))->flags);
        num_pages--;
        page_ptr++;
    }
}
#else
#define nvos_list_page_count(page_ptr, num_pages)
#endif

/*
 * The idea behind nvos_malloc is to manage physical memory for DMA buffers
 * directly (as opposed to using "vmalloc") to avoid exhausting the kernel's
 * virtual address space which can shrink down to 128MB on i386 systems with
 * large amounts of physical memory. This also minimizes fragmentation of
 * virtual memory.
 * While this isn't really necessary on ia64, it can't hurt either.
 */

/*
 * When called with __GFP_DMA, __get_free_pages should return physical pages
 * addressable by 32bit PCI hardware on the ia64. On ia32, this will always
 * be the case unless we explicitly tell __get_free_pages to give us "high"
 * memory on systems with high memory support enabled.
 */

/* note that there's a subtle kernel interaction with regards to bookkeeping
 * on these pages. So long as the pages are marked reserved, the kernel won't
 * touch them (alter the usage count on them). this leads to a subtle problem
 * with mmap. Normally, allocating the pages would set the count to 1, then 
 * mmaping them would bump the count up to 2. The order of unmapping and freeing
 * the pages wouldn't matter, as they wouldn't really be considered free by the
 * kernel until the count dropped back to 0. Since the kernel won't touch the
 * count when the page is reserved, we need to be careful about this order and
 * unreserving the pages. if we unreserve the pages while freeing them, and the
 * munmap comes later, the munmap code path will attempt a second free on the 
 * same pages. We also don't have a lot of control over which comes first, 
 * sometimes we'll get called to free the pages first, sometimes we'll get called
 * to munmap them first. Oh, and we'll get vma open/close calls every time the
 * process is cloned, then execv'd, and munmap == vma close.
 * sooo, we keep our own count of the allocation usage, and don't unreserve the
 * pages until our count drops to 0. this should currently happen in either
 * vma_release or nvos_free, both of which will be followed by a kernel attempt
 * to free the page. Since the page fill finally be unreserved, the kernel will
 * reduce the count to 0 and successfully free the page for us, only once.
 * sigh... you have to love s&%*^y interfaces that force you to *know* too much
 * about kernel internals. 
 */

static int nvos_malloc_pages(
    void         **page_list,
    unsigned long  num_pages
)
{
    /* point page_ptr at the start of the actual page list */
    unsigned long *page_ptr = (unsigned long *) page_list;
    unsigned long pages_needed = num_pages;

    nv_printf(NV_DBG_MEMINFO, "nvos_malloc_pages: %d pages\n", pages_needed);
    nv_printf(NV_DBG_MEMINFO, "   page_table: 0x%x\n", page_list);
    while (pages_needed) 
    {
        unsigned long virt_addr;
        unsigned long phys_addr;
        
        virt_addr = __get_free_pages(NV_GFP_HW, 0);
        if (virt_addr == 0) {
            goto failed;
        }
        phys_addr = virt_to_phys((void *) virt_addr);

        /* lock the page for dma purposes */
        mem_map_reserve(GET_MAP_NR(phys_addr));

        *page_ptr++ = phys_addr;
        pages_needed--;
    }
    nvos_list_page_count(page_list, num_pages);

    return 0;

failed:
    /* back up to last valid page */
    page_ptr -= 1;

    while (page_ptr != (unsigned long *) page_list)
    {
        mem_map_unreserve(GET_MAP_NR(*page_ptr));
        free_page((unsigned long) phys_to_virt(*page_ptr));
        page_ptr--;
    }

    return -1;
}

// unlock the pages we've locked down for dma purposes
static void nvos_unlock_pages(
    void          **page_list,
    unsigned long   pages_left
)
{
    if (page_list == NULL)
        return;

    nv_printf(NV_DBG_MEMINFO, "nvos_unlock_pages: %d pages\n", pages_left);

    while (pages_left)
    {
        mem_map_unreserve(GET_MAP_NR(*page_list));
        page_list++;
        pages_left--;
    }
}

static void nvos_free_pages(
    void         **page_list,
    unsigned long  pages_left
)
{
    if (page_list == NULL)
        return;

    nv_printf(NV_DBG_MEMINFO, "nvos_free: %d pages\n", pages_left);

    nvos_list_page_count(page_list, pages_left);
    while (pages_left)
    {
        free_page((unsigned long) phys_to_virt(*(unsigned long *) page_list));
        page_list++;
        pages_left--;
    }
}

static 
nv_alloc_t *nvos_create_alloc(
    int num_pages
)
{
    nv_alloc_t *at;
    int pt_size;

    NV_KMALLOC(at, sizeof(nv_alloc_t));
    if (at == NULL)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to allocate alloc_t\n");
        return NULL;
    }

    memset(at, 0, sizeof(nv_alloc_t));

    pt_size = num_pages *  sizeof(unsigned long);
    NV_VMALLOC(at->page_table, pt_size);
    if (at->page_table == NULL)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to allocate page table\n");
        NV_KFREE(at);
        return NULL;
    }
    memset(at->page_table, 0, pt_size);
    at->num_pages = num_pages;

    return at;
}

static 
int nvos_free_alloc(
    nv_alloc_t *at
)
{
    if (at == NULL)
        return -1;

    if (at->usage_count)
        return 1;

    // we keep the page_table around after freeing the pages
    // for bookkeeping reasons. Free the page_table and assume
    // the underlying pages are already unlocked and freed.
    if (at->page_table)
        NV_VFREE(at->page_table);

    NV_KFREE(at);

    return 0;
}

static u8 nvos_find_agp_capability(struct pci_dev *dev)
{
    u16 status;
    u8  cap_ptr, cap_id;

    pci_read_config_word(dev, PCI_STATUS, &status);
    status &= PCI_STATUS_CAP_LIST;
    if (!status)
        return 0;

    switch (dev->hdr_type) {
        case PCI_HEADER_TYPE_NORMAL:
        case PCI_HEADER_TYPE_BRIDGE:
            pci_read_config_byte(dev, PCI_CAPABILITY_LIST, &cap_ptr);
            break;
        default:
            return 0;
    }

    do {
        cap_ptr &= 0xfc;
        pci_read_config_byte(dev, cap_ptr + PCI_CAP_LIST_ID, &cap_id);
        if (cap_id == PCI_CAP_ID_AGP)
            return cap_ptr;
        pci_read_config_byte(dev, cap_ptr + PCI_CAP_LIST_NEXT, &cap_ptr);
    } while (cap_ptr && cap_id != 0xff);

    return 0;
}

static struct pci_dev* nvos_find_agp_by_class(unsigned int class)
{
    struct pci_dev *dev;
    u8     cap_ptr;

    dev = pci_find_class(class << 8, NULL);
    do {
        cap_ptr = nvos_find_agp_capability(dev);
        if (cap_ptr)
            return dev;
        dev = pci_find_class(class << 8, dev);
    } while (dev);

    return NULL;
}

static struct pci_dev* nv_find_pci_dev(nv_state_t *nv)
{
    struct pci_dev *dev;
    int    bus  = 0;
    int    slot = 0;
    
    dev = NULL;
    do {
        dev = pci_find_device(nv->vendor_id, nv->device_id, dev);
        if (dev) {
            bus = dev->bus->number;
            slot = PCI_SLOT(dev->devfn);
        }
    } while (dev && (bus != nv->bus || slot != nv->slot));

    return dev;
}

static void nvos_proc_create(void)
{
#ifdef CONFIG_PROC_FS
    struct pci_dev *dev;
    int i = 0;
    char name[6];

    struct proc_dir_entry *entry;
    struct proc_dir_entry *proc[5];

    /* world readable directory */
    int flags = S_IFDIR | S_IRUGO | S_IXUGO;

    enum { NVIDIA, DRIVER, CARDS, AGP, LEGACY };

    nv_state_t *nv;
    nv_linux_state_t *nvl;
    nv_linux_state_t *nv_max_devices;

#if defined (KERNEL_2_2)
    proc[DRIVER] = create_proc_entry("driver", flags, &proc_root);
#else
    proc[DRIVER] = proc_root_driver;
#endif

    proc[NVIDIA] = create_proc_entry("nvidia", flags, proc[DRIVER]);
    proc[AGP]    = create_proc_entry("agp",    flags, proc[NVIDIA]);
    proc[CARDS]  = create_proc_entry("cards",  flags, proc[NVIDIA]);
    proc[LEGACY] = create_proc_entry("nv",     flags, &proc_root);

    nv_max_devices = nv_linux_devices + NV_MAX_DEVICES;
    for (nvl = nv_linux_devices; nvl < nv_max_devices; nvl++) 
    {
        nv = NV_STATE_PTR(nvl);

        if (nv->device_id == 0)
            break;

        /* world readable file */
        flags = S_IFREG | S_IRUGO;

        dev = nv_find_pci_dev(nv);
        if (!dev)
            break;

        sprintf(name, "card%d", i);
        entry = create_proc_entry(name, flags, proc[LEGACY]);
        entry->read_proc = nv_kern_read_legacy;
        
        sprintf(name, "%d", i++);
        entry = create_proc_entry(name, flags, proc[CARDS]);
        entry->data = nv;
        entry->read_proc = nv_kern_read_cardinfo;

        if (nvos_find_agp_capability(dev)) {
            entry = create_proc_entry("status", flags, proc[AGP]);
            entry->data = nv;
            entry->read_proc = nv_kern_read_status;

            entry = create_proc_entry("card", flags, proc[AGP]);
            entry->data = nv;
            entry->read_proc = nv_kern_read_agpinfo;
        }
    }

    entry = create_proc_entry("version", flags, proc[NVIDIA]);
    entry->read_proc = nv_kern_read_version;

    entry = create_proc_entry("host-bridge", flags, proc[AGP]);
    entry->data = NULL;
    entry->read_proc = nv_kern_read_agpinfo;
#endif
}

static void nvos_proc_remove(void)
{
#ifdef CONFIG_PROC_FS
#if defined (KERNEL_2_2)
    remove_proc_entry("driver", &proc_root);
    remove_proc_entry("nv", &proc_root);
#else
    remove_proc_entry("nvidia", proc_root_driver);
    remove_proc_entry("nv", &proc_root);
#endif
#endif
}

/*
 * Given a virtual address, fid the 'at' that owns it
 * Uses the physical address as the key.
 */
static nv_alloc_t *nvl_find_alloc(
    nv_linux_state_t    *nvl,
    unsigned long  address,
    unsigned long  flags
)
{
    nv_alloc_t *at;

    for (at = nvl->alloc_queue; at; at = at->next)
    {
        // make sure this 'at' matches the flags the caller provided
        // ie, don't mistake a pci allocation with an agp allocation
        if (!(at->flags & flags))
            continue;

        // most mappings will be found based on the 'key'
        if (address == ((unsigned long) at->key_mapping))
            return at;

        if (at->page_table)
        {
            int i;
            for (i = 0; i < at->num_pages; i++)
            {
                unsigned long offset = (unsigned long) at->page_table[i];
                if ((address >= offset) &&
                    (address < (offset + PAGE_SIZE)))
                    return at;
            }
        }

    }

    /* failure is not necessarily an error if the caller
       was just probing an address */
    nv_printf(NV_DBG_INFO, "could not find map for vm 0x%lx\n", address);
    return NULL;
}

static int nvl_add_alloc(
    nv_linux_state_t *nvl, 
    nv_alloc_t *at
)
{
    nv_lock_at(NV_STATE_PTR(nvl));
    at->next = nvl->alloc_queue;
    nvl->alloc_queue = at;
    nv_unlock_at(NV_STATE_PTR(nvl));
    return 0;
}

static int nvl_remove_alloc(
    nv_linux_state_t *nvl, 
    nv_alloc_t *at
)
{
    nv_alloc_t *tmp, *prev;

    if (nvl->alloc_queue == at)
    {
        nvl->alloc_queue = nvl->alloc_queue->next;
        return 0;
    }

    for (tmp = prev = nvl->alloc_queue; tmp; prev = tmp, tmp = tmp->next)
    {
        if (tmp == at)
        {
            prev->next = tmp->next;
            return 0;
        }
    }

    return -1;
}


/***
 *** EXPORTS to Linux Kernel
 ***/

int init_module(void)
{
    nv_linux_state_t *nvl;
    int rc;
    int num_devices;

    memset(nv_linux_devices, 0, sizeof(nv_linux_devices));
    num_devices = nvos_probe_devices();

    if (num_devices == 0) {
        nv_printf(NV_DBG_ERRORS, "nvidia: no NVIDIA graphics adapter found\n");
        return -ENODEV;
    }

    nv_printf(NV_DBG_ERRORS, "nvidia: loading %s\n", pNVRM_ID);

#ifdef CONFIG_DEVFS_FS
    rc = devfs_register_chrdev(nv_major, "nvidia", &nv_fops);
#else
    rc = register_chrdev(nv_major, "nvidia", &nv_fops);
#endif

    if (rc < 0) {
        nv_printf(NV_DBG_ERRORS, "init_module: register failed\n");
        return rc;
    }

#ifdef CONFIG_DEVFS_FS
    memset(nv_dev_handle, 0, sizeof(devfs_handle_t) * NV_MAX_DEVICES);
    do {
        char name[10];
        int i;

        nv_ctl_handle = devfs_register(NULL, "nvidiactl",
                            DEVFS_FL_DEFAULT, nv_major, 255,
                            S_IFCHR | S_IRUGO | S_IWUGO,
                            &nv_fops, NULL);

        for (i = 0; i < num_devices; i++) {
            sprintf(name, "nvidia%d", i);
            nv_dev_handle[i] = devfs_register(NULL, name,
                                  DEVFS_FL_DEFAULT, nv_major, i,
                                  S_IFCHR | S_IRUGO | S_IWUGO,
                                  &nv_fops, NULL);
        }
    } while(0);
#endif

    nv_printf(NV_DBG_INFO, "init_module: major number %d\n", nv_major);

    // init all the bottom half structures
    for (nvl = nv_linux_devices; nvl < nv_linux_devices + NV_MAX_DEVICES; nvl++)
    {
        nvl->bh = &nv_bottom_halves[nvl - nv_linux_devices];
        nvl->bh->routine = nv_kern_bh;
        nvl->bh->data = (void *) nvl;
        nvl->bh->sync = 0;
    }

    // init the nvidia control device
    {
        nv_state_t *nv_ctl = NV_STATE_PTR(&nv_ctl_device);
        nv_ctl_device.event_queue = NULL;
        nv_ctl->os_state = (void *) &nv_ctl_device;
        nv_lock_init_locks(nv_ctl);
    }

#ifdef CONFIG_PM
    /* XXX PM egads, is this the right place to do this? */
    pm_nv_dev = pm_register(PM_PCI_DEV, PM_SYS_VGA, nv_kern_pm);
#endif

    // Init the resource manager
    if (!rm_init_rm())
    {
        nv_printf(NV_DBG_ERRORS, "rm_init_rm() failed\n");
        rc = -EIO;
        goto failed;
    }

    // load our local registry entries into the registry
    {
        extern nv_parm_t nv_parms[];
        rm_load_registry(nv_parms);
    }

    /* create /proc/driver/nvidia */
    nvos_proc_create();

    return 0;

 failed:
#ifdef CONFIG_DEVFS_FS
    devfs_unregister_chrdev(nv_major, "nvidia");
#else
    unregister_chrdev(nv_major, "nvidia");
#endif
    return rc;
}

void cleanup_module(void)
{
    int rc;
    nv_linux_state_t *nvl;
    nv_linux_state_t *max_devices;

    /* remove /proc/driver/nvidia */
    nvos_proc_remove();

    nv_printf(NV_DBG_INFO, "cleanup_module\n");

#ifdef CONFIG_PM
    /* XXX PM egads, is this the right place to do this? */
    pm_unregister(pm_nv_dev);
#endif

    // Shutdown the resource manager
    rm_shutdown_rm();

    /*
     * Make sure we freed up all the mappings. The kernel should
     * do this automatically before calling close.
     */
    max_devices = nv_linux_devices + NV_MAX_DEVICES;
    for (nvl = nv_linux_devices; nvl < max_devices; nvl++)
    {
        if (nvl->alloc_queue)
        {
            if (nvl->alloc_queue->vma == NULL)
                continue;

            nv_printf(NV_DBG_ERRORS,
                    "still have vm que at cleanup_module(): 0x%x to 0x%x\n",
                    nvl->alloc_queue->vma->vm_start,
                    nvl->alloc_queue->vma->vm_end);
        }
    }

#ifdef CONFIG_DEVFS_FS
    rc = devfs_unregister_chrdev(nv_major, "nvidia");
#else
    rc = unregister_chrdev(nv_major, "nvidia");
#endif

    if (rc < 0) {
        nv_printf(NV_DBG_ERRORS, "cleanup_module: unregister nv failed\n");
    }

#ifdef CONFIG_DEVFS_FS
    do {
        int i;
        for (i = 0; nv_dev_handle[i] != 0; i++) {
            devfs_unregister(nv_dev_handle[i]);
        }
    } while(0);
    devfs_unregister(nv_ctl_handle);
#endif
}


/* this is only called when the vmas are duplicated.
 * this appears to only happen when the process is cloned to create
 * a new process, and not when the process is threaded.
 *
 * increment the usage count for the physical pages, so when this
 * clone unmaps the mappings, the pages are not deallocated under
 * the original process.
 */
void
nv_kern_vma_open(struct vm_area_struct *vma)
{
    nv_printf(NV_DBG_MEMINFO, "vma_open for 0x%x - 0x%x, offset 0x%x\n",
        vma->vm_start, vma->vm_end, LINUX_VMA_OFFS(vma));

    if (VMA_PRIVATE(vma))
    {
        nv_alloc_t *at = (nv_alloc_t *) VMA_PRIVATE(vma);
        at->usage_count++;

        nv_printf(NV_DBG_MEMINFO, "  at 0x%x, usage count %d, page_table 0x%x\n",
            at, at->usage_count, at->page_table);

        nvos_list_page_count(at->page_table, at->num_pages);
    }

    MOD_INC_USE_COUNT;
}


void
nv_kern_vma_release(struct vm_area_struct *vma)
{
    nv_printf(NV_DBG_MEMINFO, "vma_release for 0x%x - 0x%x, offset 0x%x\n",
        vma->vm_start, vma->vm_end, LINUX_VMA_OFFS(vma));

    if (VMA_PRIVATE(vma))
    {
        nv_alloc_t *at = (nv_alloc_t *) VMA_PRIVATE(vma);

        at->usage_count--;

        nv_printf(NV_DBG_MEMINFO, "  at 0x%x, usage count %d, page_table 0x%x\n",
            at, at->usage_count, at->page_table);

        nvos_list_page_count(at->page_table, at->num_pages);

        // if usage_count is down to 0, the kernel virtual mapping was freed
        // but the underlying physical pages were not, due to the reserved bit
        // being set. We need to clear the reserved bit, then munmap will
        // zap the pages and free the physical pages.
        if (at->usage_count == 0)
        {
            if (at->page_table)
                nvos_unlock_pages(at->page_table, at->num_pages);
            nvos_free_alloc(at);
            VMA_PRIVATE(vma) = NULL;
        }
    }

    MOD_DEC_USE_COUNT;
}


/* at this point, this code just plain won't work with 2.2 kernels.
 * additionally, only ia64 & the 460GX need a nopage handler, and 2.2 doesn't
 * work on ia64 anyways. It's expected that at some point other agp chipsets
 * will work similar to the 460GX (AGP 3.0 spec), so pre-emptively make sure
 * this works on our standard ia32 driver.
 */
#if !defined(KERNEL_2_2)

/* AGP allocations under the 460GX are not mapped to the aperture
 * addresses by the CPU.  This nopage handler will fault on CPU
 * accesses to AGP memory and map the address to the correct page.
 */
struct page *nv_kern_vma_nopage(struct vm_area_struct *vma, unsigned long address, int write_access)
{
    nv_alloc_t *at, *tmp;
    nv_linux_state_t *nvl;
    nv_state_t *nv;
    struct page *page_ptr;
    int rm_status, index;

    at = VMA_PRIVATE(vma);
    if (at == NULL)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: nopage handler called without an at: "
                  "vm_start 0x%x, at 0x%x\n", vma->vm_start, at);
        return NOPAGE_SIGBUS;
    }

    // let's verify this 'at' is valid
    // I can imagine cases where something went wrong, the 'at' and underlying
    // pages were freed, but the virtual mapping still exists and this 'at'
    // pointer is potentially pointing to freed memory. Let's make sure we can
    // still find the 'at' in our alloc_queue.
    nvl = NV_GET_NVL_FROM_FILEP(vma->vm_file);
    if (nvl == NULL)
        return NOPAGE_SIGBUS;

    nv = (nv_state_t *) nvl;

    rm_status = RM_ERROR;
    tmp = nvl->alloc_queue;
    while (tmp)
    {
        if (tmp == at)
        {
            rm_status = RM_OK;
            break;
        }
        tmp = tmp->next;
    }

    if (rm_status != RM_OK)
    {
        // we didn't find the 'at' (and haven't dereferenced it yet).
        // let's bail before something bad happens, but first print an
        // error message and NULL the pointer out so we don't come this
        // far again
        nv_printf(NV_DBG_ERRORS, "NVRM: nopage handler called on a freed"
                  "address: vm_start 0x%x, at 0x%x\n", vma->vm_start, at);
        VMA_PRIVATE(vma) = NULL;
        return NOPAGE_SIGBUS;
    }

    rm_status = KernMapAGPNopage((void *)address, vma, at->priv_data, 
                                 (void **)&page_ptr);
    if (rm_status)
        return NOPAGE_SIGBUS;

    // get the index of this page into the allocation
    index = (address - vma->vm_start)>>PAGE_SHIFT;

    // save that index into our page list (make sure it doesn't already exist)
    if (at->page_table[index])
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: page slot already filled in nopage handler!\n");
        os_dbg_breakpoint();
    }
    at->page_table[index] = (void *) ((page_ptr - mem_map) << PAGE_SHIFT);

    return page_ptr;
}
#endif

struct vm_operations_struct nv_vm_ops = {
    nv_kern_vma_open,
    nv_kern_vma_release,  /* "close" */
#if !defined(KERNEL_2_2)
    nv_kern_vma_nopage,
#endif
};


/*
** nv_kern_open
**
** nv driver open entry point.  Sessions are created here.
*/
int nv_kern_open(
    struct inode *inode,
    struct file *file
)
{
    nv_state_t *nv = (nv_state_t *) 0;
    nv_linux_state_t *nvl = (nv_linux_state_t *) 0;
    nv_file_private_t *nvfp;
    int devnum;
    int rc = 0, status;

    nv_printf(NV_DBG_INFO, "nv_kern_open...\n");

    /* Grab a file private area and save 'nv' in file structure */
    NV_KMALLOC(nvfp, sizeof(nv_file_private_t));
    if ( ! nvfp)
        return -ENOMEM;
    memset(nvfp, 0, sizeof(*nvfp));
    file->private_data = nvfp;

    /* for control device, just jump to its open routine */
    /* after setting up the private data */
    if (NV_DEVICE_IS_CONTROL_DEVICE(inode->i_rdev))
        return nv_kern_ctl_open(inode, file);

    /* what device are we talking about? */
    devnum = NV_DEVICE_NUMBER(inode->i_rdev);
    if (devnum >= NV_MAX_DEVICES)
    {
        rc = -ENODEV;
        goto failed;
    }


    MOD_INC_USE_COUNT;

    nvl = &nv_linux_devices[devnum];
    nv = NV_STATE_PTR(nvl);

    nv_printf(NV_DBG_INFO, "nv_kern_open on device %d\n", devnum);
    nv_lock_ldata(nv);

    NV_HIDE_IN_FILEP(file, nvl);

    /*
     * map the memory and allocate isr on first open
     */

    if ( ! (nv->flags & NV_FLAG_OPEN))
    {
        if (nv->device_id == 0)
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: open of nonexistent device %d\n", devnum);
            rc = -ENXIO;
            goto failed;
        }

        if ( ! rm_init_adapter(nv))
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: rm_init_adapter failed\n");
            rc = -EIO;
            goto failed;
        }
        nv->flags |= NV_FLAG_POSTED;

        status = request_irq(nv->interrupt_line, nv_kern_isr,
                             SA_INTERRUPT | SA_SHIRQ, "nvidia",
                             (void *) nvl);
        if (status != 0)
        {
            if ( nv->interrupt_line && (status == -EBUSY) )
            {
                nv_printf(NV_DBG_ERRORS, "NV: Tried to get irq %d, but another driver",
                    (unsigned int) nv->interrupt_line);
                nv_printf(NV_DBG_ERRORS, " has it and is not sharing it.\n");
                nv_printf(NV_DBG_ERRORS, "NV: you may want to verify that an audio driver");
                nv_printf(NV_DBG_ERRORS, " isn't using the irq\n");
            }
            nv_printf(NV_DBG_ERRORS, "NVRM: isr request failed 0x%x\n", status);
            rc = -EIO;
            goto failed;
        }

#if !defined (KERNEL_2_2)
        NV_KMALLOC(nvl->event_queue, sizeof(struct __wait_queue_head));
        if (nvl->event_queue == NULL)
            goto failed;
        memset(nvl->event_queue, 0, sizeof(struct __wait_queue_head));

        init_waitqueue_head(GET_EVENT_QUEUE(nvl));
#else
        nvl->event_queue = NULL;
#endif

        nv->flags |= NV_FLAG_OPEN;
    }

    nv->usage_count++;
    nv_unlock_ldata(nv);

    return rc;

 failed:
    MOD_DEC_USE_COUNT;
    nv_unlock_ldata(nv);
    return rc;
}


/*
** nv_kern_close
**
** Master driver close entry point.
*/

int nv_kern_close(
    struct inode *inode,
    struct file *file
)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_FILEP(file);
    nv_state_t *nv = NV_STATE_PTR(nvl);

    /* for control device, just jump to its open routine */
    /* after setting up the private data */
    if (NV_DEVICE_IS_CONTROL_DEVICE(inode->i_rdev))
        return nv_kern_ctl_close(inode, file);

    nv_printf(NV_DBG_INFO, "nv_kern_close on device %d\n", NV_DEVICE_NUMBER(inode->i_rdev));

    rm_free_unused_clients(nv, current->pid, (void *) file);

    nv_lock_ldata(nv);
    if (--nv->usage_count == 0)
    {
        int counter = 0;

        /* turn off interrupts.
        ** be careful to make sure any pending bottom half gets run
        **  or disabled before calling rm_shutdown_adapter() since
        **  it will free up the pdev.  This is hard to see on single
        **  cpu systems, but easy on dual cpu :-)
        */
        rm_disable_adapter(nv);

        /* give it a moment to allow any bottom half to run */

#define MAX_BH_TASKS 10
        while (NV_ATOMIC_READ(nvl->bh_count) && (counter < MAX_BH_TASKS))
        {
            current->state = TASK_INTERRUPTIBLE;
            schedule_timeout(HZ/50);
            counter++;
        }

        /* free the irq, which may block until any pending interrupts */
        /* are done being processed. */
        free_irq(nv->interrupt_line, (void *) nv);

        rm_shutdown_adapter(nv);

        /*
         * Make sure we have freed up all the mappings. The kernel
         * should do this automagically before calling close
         */
        if (nvl->alloc_queue)
        {
            if (nvl->alloc_queue->vma)
            {
                nv_printf(NV_DBG_ERRORS,
                    "still have vm que at nv_close(): 0x%x to 0x%x",
                    nvl->alloc_queue->vma->vm_start,
                    nvl->alloc_queue->vma->vm_end);
            }
        }

#if !defined (KERNEL_2_2)
        /* this only needs to be freed on 2.4 and later kernels */
        NV_KFREE(nvl->event_queue);
        nvl->event_queue = NULL;
#endif

        /* leave INIT flag alone so we don't reinit every time */
        nv->flags &= ~(NV_FLAG_OPEN | NV_FLAG_WAITING);
    }
    nv_unlock_ldata(nv);

    /* free up our per file private data */
    if (file->private_data)
        NV_KFREE(file->private_data);
    file->private_data = (void *) 0;

    MOD_DEC_USE_COUNT;

    return 0;
}

int nv_kern_mmap(
    struct file  *file,
    struct vm_area_struct *vma
)
{
    int pages;
    nv_alloc_t *at;
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_FILEP(file);
    nv_state_t *nv = NV_STATE_PTR(nvl);

    nv_printf(NV_DBG_INFO, "mmap([0x%lx-0x%lx] off=0x%lx)\n",
        vma->vm_start,
        vma->vm_end,
        LINUX_VMA_OFFS(vma));

    // be a bit paranoid for now
    if ((NV_MASK_OFFSET(LINUX_VMA_OFFS(vma))) ||
        (NV_MASK_OFFSET(vma->vm_start)) ||
        (NV_MASK_OFFSET(vma->vm_end)))
    {
        return -ENXIO;
    }

    pages = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;

    // we have our own version to keep the module count right
    vma->vm_ops = &nv_vm_ops;

    /*
     * figure out the range and map it in
     */


    /* NV reg space */
    if (IS_REG_OFFSET(nv, LINUX_VMA_OFFS(vma), vma->vm_end - vma->vm_start))
    {
        /* truncate to size of registers */
        if (pages > nv->regs->size / PAGE_SIZE)
            pages = nv->regs->size / PAGE_SIZE;

        vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
        if (remap_page_range(vma->vm_start,
                             LINUX_VMA_OFFS(vma),
                             vma->vm_end - vma->vm_start,
                             vma->vm_page_prot))
            return -EAGAIN;

        /* mark it as IO so that we don't dump it on core dump */
        vma->vm_flags |= VM_IO;
    }

    /* NV fb space */
    else if (IS_FB_OFFSET(nv, LINUX_VMA_OFFS(vma), vma->vm_end - vma->vm_start))
    {

        /* truncate to size of framebuffer */
        if (pages > nv->fb->size / PAGE_SIZE)
            pages = nv->fb->size / PAGE_SIZE;

        vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
        if (remap_page_range(vma->vm_start,
                             LINUX_VMA_OFFS(vma),
                             vma->vm_end - vma->vm_start,
                             vma->vm_page_prot))
            return -EAGAIN;

        // mark it as IO so that we don't dump it on core dump
        vma->vm_flags |= VM_IO;
    }

    /* AGP allocator */
    else if (IS_AGP_OFFSET(nv, LINUX_VMA_OFFS(vma), vma->vm_end - vma->vm_start))
    {
        nv_lock_at(nv);
        at = nvl_find_alloc(nvl, LINUX_VMA_OFFS(vma), NV_ALLOC_TYPE_AGP);

        if (at == NULL)
        {
            nv_unlock_at(nv);
            nv_printf(NV_DBG_ERRORS, "NVRM: couldn't find pre-allocated agp memory!\n");
            return -EAGAIN;
        }

        if (at->num_pages != pages)
        {
            nv_unlock_at(nv);
            nv_printf(NV_DBG_ERRORS,
                "NVRM: pre-allocated agp memory has wrong number of pages!\n");
            return -EAGAIN;
        }

        at->vma = vma;
        VMA_PRIVATE(vma) = at;
        at->usage_count++;

        if (NV_OSAGP_ENABLED(nv))
        {
#if !defined(NVCPU_IA64) || (LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 9))
            KernMapAGPPages(vma, at->priv_data);
#else
            vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
#endif
        }
        else
        {
            rm_map_agp_pages(nv, (void **) &vma->vm_start,
                          at->class, at->priv_data);

        }
        nvos_list_page_count(at->page_table, at->num_pages);
        nv_unlock_at(nv);

        /* prevent the swapper from swapping it out */
        vma->vm_flags |= VM_LOCKED;
    }

    /* Magic allocator */
    else // if (LINUX_VMA_OFFS(vma) == NV_MMAP_ALLOCATION_OFFSET)
    {
        unsigned long page = 0, pos, start;
        int i = 0;

        nv_lock_at(nv);
        at = nvl_find_alloc(nvl, LINUX_VMA_OFFS(vma), NV_ALLOC_TYPE_PCI);

        if (at == NULL)
        {
            nv_unlock_at(nv);
            nv_printf(NV_DBG_ERRORS, "NVRM: couldn't find pre-allocated memory!\n");
            return -EAGAIN;
        }

        if (at->num_pages != pages)
        {
            nv_unlock_at(nv);
            nv_printf(NV_DBG_ERRORS,
                "NVRM: pre-allocated sys memory has wrong number of pages!\n");
            return -EAGAIN;
        }

        at->vma = vma;
        VMA_PRIVATE(vma) = at;
        at->usage_count++;

        nv_printf(NV_DBG_INFO, "remapping %d system pages for at 0x%x\n", pages, at);
        start = vma->vm_start;
        while (pages--)
        {
            page = (unsigned long) at->page_table[i++];
            if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED))
              	return -EAGAIN;
            start += PAGE_SIZE;
            pos += PAGE_SIZE;
       	}
        nvos_list_page_count(at->page_table, at->num_pages);
        nv_unlock_at(nv);

        /* prevent the swapper from swapping it out */
        vma->vm_flags |= VM_LOCKED;
    }

    vma->vm_file = file;

    /* just increment usage count, rather than calling vma_open */
    MOD_INC_USE_COUNT;

    return 0;
}


unsigned int nv_kern_poll(
    struct file *file,
    poll_table *wait
)
{
    unsigned int mask = 0;
    nv_linux_state_t *nvl;
    nv_state_t *nv;
    nv_file_private_t *nvfp;

    nvfp = file->private_data;
    nvl = NV_GET_NVL_FROM_FILEP(file);
    nv = NV_STATE_PTR(nvl);

    // nv_printf(NV_DBG_INFO, "poll()\n");

    if (nvl->nv_state.device_number == NV_CONTROL_DEVICE_NUMBER)
        return nv_kern_ctl_poll (file, wait);

    // add us to the list
    poll_wait(file, GET_EVENT_QUEUE(nvl), wait);

    nv_lock_ldata(nv);

    // wake the user on any file-specific event, or a general vblank
    if (nvfp->any_fired_notifiers || nvl->vblank_notifier)
    {
        if (nvl->vblank_notifier)
        {
            // don't clear vblank_notifier until everyone's been notified
            nvl->waiting_for_vblank--;
            if (nvl->waiting_for_vblank == 0)
            {
                nvl->vblank_notifier = 0;
                NV_STATE_PTR(nvl)->flags &= ~NV_FLAG_WAITING;
            }
        }

        // tell user some have been posted
        mask |= POLLPRI;
        nv_printf(NV_DBG_INFO, "poll() live on entry!\n");
    } else {
        // keep track of how many people are waiting for vblank
        nvl->waiting_for_vblank++;
        NV_STATE_PTR(nvl)->flags |= NV_FLAG_WAITING;
    }

    nv_unlock_ldata(nv);

    return mask;
}

//
// nv_kern_ioctl
//
// nv driver ioctl entry point.
//

#ifdef __KERNEL__
#define COPYIN(dst, src, bytes)     copy_from_user((dst), (src), (bytes))
#define COPYOUT(dst, src, bytes)   copy_to_user((dst), (src), (bytes))
#else
#define COPYIN(dst, src, bytes)     memcpy((dst), (src), (bytes)), 0
#define COPYOUT(dst, src, bytes)   memcpy((dst), (src), (bytes)), 0
#endif

#define DO_OR_DIE(n) { if (n) { status = -EFAULT; goto done; } }

/*
 * some ioctl's can only be done on actual device, others only on the control device
 */
#define CTL_DEVICE_ONLY(nv) { if ( ! ((nv)->flags & NV_FLAG_CONTROL)) { status = -EINVAL; goto done; } }

#define ACTUAL_DEVICE_ONLY(nv) { if ((nv)->flags & NV_FLAG_CONTROL) { status = -EINVAL; goto done; } }


/* todo:
   need ioctl to raise a thread priority that is not superuser
       set its priority to SCHED_FIFO which is simple
       priority scheduling w/ disabled timeslicing
*/

int nv_kern_ioctl(
    struct inode *inode,
    struct file *file,
    unsigned int cmd,
    unsigned long i_arg)
{
    int status = 0;
    nv_linux_state_t *nvl;
    nv_state_t *nv;
    void *arg = (void *) i_arg;

    nvl = NV_GET_NVL_FROM_FILEP(file);
    nv = NV_STATE_PTR(nvl);

    nv_printf(NV_DBG_INFO, "ioctl(0x%x, 0x%x)\n", _IOC_NR(cmd), (unsigned int) i_arg);

    switch (_IOC_NR(cmd))
    {
        /* debug tool; zap the module use count so we can unload driver */
        /*             even if it is confused */
        case _IOC_NR(NV_IOCTL_MODULE_RESET):
            atomic_set(&__this_module.uc.usecount, 1);
            break;

        /* pass out info about the card */
        case _IOC_NR(NV_IOCTL_CARD_INFO):
        {
            nv_ioctl_card_info_t ci;
            nv_ioctl_card_info_t *userbuf = arg;
            nv_linux_state_t *tnvl;
            nv_ioctl_rm_api_version_t rm_api;
            int i;

            CTL_DEVICE_ONLY(nv);

            /* the first element of card info passed from the client will have
             * the rm_api_version_magic value to show that the client is new
             * enough to support versioning. If the client is too old to 
             * support versioning, our mmap interfaces are probably different
             * enough to cause serious damage.
             * just copy in the one dword to check.
             */
	    DO_OR_DIE(COPYIN(&rm_api, userbuf, sizeof(rm_api)));
            if ((rm_api.magic   != NV_RM_API_VERSION_MAGIC_REQ) ||
                (rm_api.version != NV_RM_API_VERSION))
            {
                if (rm_api.magic != NV_RM_API_VERSION_MAGIC_REQ)
                {
                    nv_printf(NV_DBG_ERRORS, 
                        "NVRM: client does not support versioning!!\n");
                } else
                if (rm_api.version != NV_RM_API_VERSION)
                {
                    nv_printf(NV_DBG_ERRORS, 
                        "NVRM: client supports wrong rm api version!!\n");
                }
                nv_printf(NV_DBG_ERRORS,
                    "NVRM:    aborting to avoid catastrophe!\n");
                rm_api.magic   = NV_RM_API_VERSION_MAGIC_REP;
                rm_api.version = NV_RM_API_VERSION;
                rm_api.major   = NV_MAJOR_VERSION;
                rm_api.minor   = NV_MINOR_VERSION;
                rm_api.patch   = NV_PATCHLEVEL;
                DO_OR_DIE(COPYOUT(userbuf, &rm_api, sizeof(rm_api)));
                return -EINVAL;
            }

            for (i = 0, tnvl = nv_linux_devices; tnvl < nv_linux_devices + NV_MAX_DEVICES; tnvl++, i++)
            {
                nv_state_t *tnv = NV_STATE_PTR(tnvl);
                (void) memset(&ci, 0, sizeof(ci));
                if (tnv->device_id)
                {
                    ci.flags = NV_IOCTL_CARD_INFO_FLAG_PRESENT;
                    ci.bus = tnv->bus;
                    ci.slot = tnv->slot;
                    ci.vendor_id = tnv->vendor_id;
                    ci.device_id = tnv->device_id;
                    ci.interrupt_line = tnv->interrupt_line;
                    ci.reg_address = tnv->bar.regs.address;
                    ci.reg_size = tnv->bar.regs.size;
                    ci.fb_address = tnv->bar.fb.address;
                    ci.fb_size = tnv->bar.fb.size;
                }
                /* copy the card info out to user */
                /* just copying 0's for nonexistent cards */
                DO_OR_DIE(COPYOUT(userbuf + i, &ci, sizeof(ci)));
            }
            break;
        }

	/* set a card to be primary (not post it) */
	case _IOC_NR(NV_IOCTL_PRIMARY_CARD):
	{
	    nv_ioctl_primary_card_t params;
	    nv_ioctl_primary_card_t *userbuf = arg;

	    CTL_DEVICE_ONLY(nv);

	    /* copy in the user command buffer */
	    DO_OR_DIE(COPYIN(&params, userbuf, sizeof(params)));

	    status = nvos_set_primary_card(&params);

	    break;
	}

        /* get the sim environment info for this setup */
        case _IOC_NR(NV_IOCTL_SIM_ENV):
        {
            nv_ioctl_sim_env_t simenv;

	    CTL_DEVICE_ONLY(nv);

            simenv.sim_env = nv->sim_env;

            DO_OR_DIE(COPYOUT(arg, &simenv, sizeof(nv_ioctl_sim_env_t)));
	    break;
	}

        case _IOC_NR(NV_IOCTL_RM_API_VERSION):
        {
            nv_ioctl_rm_api_version_t rm_api;

            CTL_DEVICE_ONLY(nv);

            rm_api.version = NV_RM_API_VERSION;
            rm_api.major   = NV_MAJOR_VERSION;
            rm_api.minor   = NV_MINOR_VERSION;
            rm_api.patch   = NV_PATCHLEVEL;

            DO_OR_DIE(COPYOUT(arg, &rm_api, sizeof(nv_ioctl_rm_api_version_t)));
            break;
        }


        default:
            status = rm_ioctl(nv, file, _IOC_NR(cmd), arg) ? 0 : -EINVAL;
            break;
    }

 done:
    nv_printf(NV_DBG_INFO, "done with ioctl\n");
    return status;
}

/*
 * driver receives an interrupt
 *    if someone waiting, then hand it off.
 */
void nv_kern_isr(
    int   irq,
    void *arg,
    struct pt_regs *regs
)
{
    nv_linux_state_t *nvl = (void *) arg;
    nv_state_t *nv = NV_STATE_PTR(nvl);
    U032 need_to_run_bottom_half = 0;

    rm_isr(nv->device_number, &need_to_run_bottom_half);
    if (need_to_run_bottom_half)
    {
        NV_ATOMIC_INC(nvl->bh_count);
        queue_task(nvl->bh, &tq_immediate);
        mark_bh(IMMEDIATE_BH);
    }
}

void nv_kern_bh(
    void *data
)
{
    nv_linux_state_t *nvl = (nv_linux_state_t *) data;
    nv_state_t *nv = NV_STATE_PTR(nvl);

    NV_ATOMIC_DEC(nvl->bh_count);
    rm_isr_bh(nv->pdev);
}

void nv_kern_rc_timer(
    unsigned long data
)
{
    nv_linux_state_t *nvl = (nv_linux_state_t *) data;

    // nv_printf(NV_DBG_INFO, "NVRM: rc timer\n");

    rm_run_rc_callback((nv_state_t *) data);
    mod_timer(&nvl->rc_timer, jiffies + HZ);  /* set another timeout in 1 second */
}

#ifdef CONFIG_PM
/* kernel calls us with a power management event */
int
nv_kern_pm(
    struct pm_dev *dev,
    pm_request_t rqst,
    void *data
)
{
   /* nv_printf(NV_DBG_INFO, "nv_kern_pm event: rqst 0x%x data 0x%lx\n",
        (unsigned int) rqst,
        (unsigned long) data); */

    switch (rqst)
    {
#if 0
        /* XXX PM HACK!! for now, let's try this */
        nv_linux_state_t *lnv = &nv_linux_devices[0];

        // our video bios doesn't support APM, only ACPI
        // for now, return an error to try and keep the machine
        // from entering suspend/resume, so as to not lose any
        // user's data.
        case PM_RESUME:
            nv_printf(NV_DBG_INFO, "NVRM: received PM resume event\n");
            rm_power_management(NV_STATE_PTR(lnv), 0, rqst);
            break;

        case PM_SUSPEND:
            nv_printf(NV_DBG_INFO, "NVRM: received PM suspend event\n");
            rm_power_management(NV_STATE_PTR(lnv), 0, rqst);
            break;
#endif
        default:
            nv_printf(NV_DBG_INFO, "NVRM: received unknown PM event: 0x%x\n", rqst);
	    /* 3/13/03 DMK: changed from 1 to 0 to facilitate APM */
            return 0;
    }
    return 1;
}
#endif

/*
** nv_kern_ctl_open
**
** nv control driver open entry point.  Sessions are created here.
*/
int nv_kern_ctl_open(
    struct inode *inode,
    struct file *file
)
{
    nv_state_t *nv;
    nv_linux_state_t *nvl;

    nvl = &nv_ctl_device;
    nv = (nv_state_t *) nvl;

    nv_printf(NV_DBG_INFO, "nv_kern_ctl_open\n");

    nv_lock_ldata(nv);


    nv->device_number = NV_CONTROL_DEVICE_NUMBER;

    /* save the nv away in file->private_data */
    NV_HIDE_IN_FILEP(file, nv);

    MOD_INC_USE_COUNT;

    /* if this is the first time the control device has been opened,
     * allocate the wait queue
     */

    if (! nvl->event_queue) {

#if !defined (KERNEL_2_2)
        NV_KMALLOC(nvl->event_queue, sizeof(struct __wait_queue_head));
        if (nvl->event_queue == NULL)
            return -ENOMEM;
        memset(nvl->event_queue, 0, sizeof(struct __wait_queue_head));

        init_waitqueue_head(GET_EVENT_QUEUE(nvl));
#else
        nvl->event_queue = NULL;
#endif
    }

    nv->flags |= NV_FLAG_OPEN + NV_FLAG_CONTROL;

    /* turn off the hotkey occurred bit */

    nv->flags &= ~NV_FLAG_HOTKEY_OCCURRED;

    nv->usage_count++;
    nv_unlock_ldata(nv);

    return 0;
}


/*
** nv_kern_ctl_close
*/
int nv_kern_ctl_close(
    struct inode *inode,
    struct file *file
)
{
    nv_state_t *nv = NV_GET_NV_FROM_FILEP(file);

    nv_printf(NV_DBG_INFO, "nv_kern_ctl_close\n");

    nv_lock_ldata(nv);
    if (--nv->usage_count == 0)
    {
#if !defined (KERNEL_2_2)
        nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
        /* this only needs to be freed on 2.4 and later kernels */
        NV_KFREE(nvl->event_queue);
        nvl->event_queue = 0;
#endif
        nv->flags = 0;
    }
    nv_unlock_ldata(nv);

    rm_free_unused_clients(nv, current->pid, (void *) file);

    /* free up our per file private data */
    if (file->private_data)
        NV_KFREE(file->private_data);
    file->private_data = (void *) 0;

    MOD_DEC_USE_COUNT;

    return 0;
}



/*
 * nv_kern_ctl_poll() - add the process to the wait queue
 */

unsigned int nv_kern_ctl_poll(
    struct file *file,
    poll_table *wait
)
{
    nv_linux_state_t *nvl;
    nv_state_t *nv;
    unsigned int ret = 0;

    nvl = NV_GET_NVL_FROM_FILEP(file);
    nv = NV_STATE_PTR(nvl);

    if (file->f_flags & O_NONBLOCK)
        return -EAGAIN;

    poll_wait(file, GET_EVENT_QUEUE(nvl), wait);

    nv_lock_ldata(nv);

    if (nv->flags & NV_FLAG_HOTKEY_OCCURRED) {
        nv_printf(NV_DBG_INFO, "a hotkey event has occurred\n");
        nv->flags &= ~NV_FLAG_HOTKEY_OCCURRED;
        ret = POLLIN | POLLRDNORM;
    }
    nv_unlock_ldata(nv);

    return ret;
}




/*
 * nv_set_hotkey_occurred_flag() - set the hotkey flag and wake up anybody
 * waiting on the wait queue
 */

void nv_set_hotkey_occurred_flag(void)
{
    nv_printf(NV_DBG_INFO,"setting the hotkey occurred flag!\n");

    nv_lock_ldata(&(nv_ctl_device.nv_state));
    nv_ctl_device.nv_state.flags |= NV_FLAG_HOTKEY_OCCURRED;
    nv_unlock_ldata(&(nv_ctl_device.nv_state));

    wake_up_interruptible(GET_EVENT_QUEUE(&nv_ctl_device));
}

struct host_bridge_t {
    unsigned int vendor;
    unsigned int device;
    const char *name;
};

static struct host_bridge_t known_hosts[]  = {
    { PCI_VENDOR_ID_AL,     0x1541,  "ALi M1541            " },
    { PCI_VENDOR_ID_AL,     0x1621,  "ALi M1621            " },
    { PCI_VENDOR_ID_AL,     0x1631,  "ALi M1631            " },
    { PCI_VENDOR_ID_AL,     0x1632,  "ALi M1632            " },
    { PCI_VENDOR_ID_AL,     0x1641,  "ALi M1641            " },
    { PCI_VENDOR_ID_AL,     0x1647,  "ALi M1647            " },
    { PCI_VENDOR_ID_AL,     0x1651,  "ALi M1651            " },
    { PCI_VENDOR_ID_AL,     0x0000,  "Ali (unknown)        " },
    { PCI_VENDOR_ID_AMD,    0x7006,  "AMD Irongate         " },
    { PCI_VENDOR_ID_AMD,    0x700e,  "AMD 761              " },
    { PCI_VENDOR_ID_AMD,    0x700c,  "AMD 760MP            " },
    { PCI_VENDOR_ID_AMD,    0x0000,  "AMD (unknown)        " },
    { PCI_VENDOR_ID_INTEL,  0x7180,  "Intel 440LX          " },
    { PCI_VENDOR_ID_INTEL,  0x7190,  "Intel 440BX          " },
    { PCI_VENDOR_ID_INTEL,  0x71a0,  "Intel 440GX          " },
    { PCI_VENDOR_ID_INTEL,  0x84ea,  "Intel 460GX          " },
    { PCI_VENDOR_ID_INTEL,  0x7120,  "Intel i810           " },
    { PCI_VENDOR_ID_INTEL,  0x7124,  "Intel i810E          " },
    { PCI_VENDOR_ID_INTEL,  0x1130,  "Intel i815           " },
    { PCI_VENDOR_ID_INTEL,  0x2500,  "Intel i820           " },
    { PCI_VENDOR_ID_INTEL,  0x2501,  "Intel i820           " },
    { PCI_VENDOR_ID_INTEL,  0x3575,  "Intel i830M          " },
    { PCI_VENDOR_ID_INTEL,  0x1a20,  "Intel i840           " },
    { PCI_VENDOR_ID_INTEL,  0x1a21,  "Intel i840           " },
    { PCI_VENDOR_ID_INTEL,  0x1a22,  "Intel i840           " },
    { PCI_VENDOR_ID_INTEL,  0x1a30,  "Intel i845           " },
    { PCI_VENDOR_ID_INTEL,  0x2530,  "Intel i850           " },
    { PCI_VENDOR_ID_INTEL,  0x2531,  "Intel i850           " },
    { PCI_VENDOR_ID_INTEL,  0x2532,  "Intel i860           " },
    { PCI_VENDOR_ID_INTEL,  0x0000,  "Intel (unknown)      " },
    { PCI_VENDOR_ID_NVIDIA, 0x01a4,  "nVidia nForce        " },
    { PCI_VENDOR_ID_NVIDIA, 0x01a5,  "nVidia nForce        " },
    { PCI_VENDOR_ID_NVIDIA, 0x01a6,  "nVidia nForce        " },
    { PCI_VENDOR_ID_NVIDIA, 0x0000,  "nVidia (unknown)     " },
    { PCI_VENDOR_ID_SI,     0x0530,  "SiS 530              " },
    { PCI_VENDOR_ID_SI,     0x0540,  "SiS 540              " },
    { PCI_VENDOR_ID_SI,     0x0550,  "SiS 550              " },
    { PCI_VENDOR_ID_SI,     0x0620,  "SiS 620              " },
    { PCI_VENDOR_ID_SI,     0x0630,  "SiS 630              " },
    { PCI_VENDOR_ID_SI,     0x0645,  "SiS 645              " },
    { PCI_VENDOR_ID_SI,     0x0650,  "SiS 650              " },
    { PCI_VENDOR_ID_SI,     0x0730,  "SiS 730              " },
    { PCI_VENDOR_ID_SI,     0x0735,  "SiS 735              " },
    { PCI_VENDOR_ID_SI,     0x0740,  "SiS 740              " },
    { PCI_VENDOR_ID_SI,     0x0000,  "SiS (unknown)        " },
    { PCI_VENDOR_ID_VIA,    0x0391,  "Via Apollo Pro KX133 " },
    { PCI_VENDOR_ID_VIA,    0x0305,  "Via Apollo Pro KT133 " },
    { PCI_VENDOR_ID_VIA,    0x3099,  "Via Apollo Pro KT266 " },
    { PCI_VENDOR_ID_VIA,    0x0691,  "Via Apollo Pro       " },
    { PCI_VENDOR_ID_VIA,    0x0501,  "Via MVP4             " },
    { PCI_VENDOR_ID_VIA,    0x0597,  "Via VP3              " },
    { PCI_VENDOR_ID_VIA,    0x0598,  "Via MVP3             " },
    { PCI_VENDOR_ID_VIA,    0x0000,  "Via (unknown)        " },
    { 0x0000,               0x0000,  "Unkown Host Bridge   " },
};

int nv_kern_read_cardinfo(char *page, char **start, off_t off,
        int count, int *eof, void *data)
{
    struct pci_dev *dev;
    char *type, *fmt, tmpstr[NV_DEVICE_NAME_LENGTH];
    int len = 0, status;
    U032 vbios_rev1, vbios_rev2, vbios_rev3, vbios_rev4, vbios_rev5;

    nv_state_t *nv;
    nv = (nv_state_t *) data;

    dev = nv_find_pci_dev(nv);
    if (!dev)
        return 0;
    
    if (rm_get_device_name(dev->device, NV_DEVICE_NAME_LENGTH,
                           tmpstr) != RM_OK) {
        strcpy (tmpstr, "Unknown");
    }
    
    len += sprintf(page+len, "Model: \t\t %s\n", tmpstr);
    len += sprintf(page+len, "IRQ:   \t\t %d\n", nv->interrupt_line);

    status = rm_get_vbios_version(nv, &vbios_rev1, &vbios_rev2,
                                  &vbios_rev3, &vbios_rev4, &vbios_rev5);

    if (status < 0) {
        /* before rm_init_adapter */
        len += sprintf(page+len, "Video BIOS: \t ??.??.??.??.??\n");
    } else {
        fmt = "Video BIOS: \t %02x.%02x.%02x.%02x.%02x\n";
        len += sprintf(page+len, fmt, vbios_rev1, vbios_rev2, vbios_rev3,
                                                  vbios_rev4, vbios_rev5);
    }

    type = nvos_find_agp_capability(dev) ? "AGP" : "PCI";
    len += sprintf(page+len, "Card Type: \t %s\n", type);

    return len;
}

int nv_kern_read_version(char *page, char **start, off_t off,
        int count, int *eof, void *data)
{
    int len = 0;
    
    len += sprintf(page+len, "NVRM version: %s\n", pNVRM_ID);
    len += sprintf(page+len, "GCC version:  %s\n", NV_COMPILER);
    
    return len;
}

int nv_kern_read_agpinfo(char *page, char **start, off_t off,
        int count, int *eof, void *data)
{
    struct pci_dev *dev;
    struct host_bridge_t *host;
    char   *fw, *sba;
    u8     cap_ptr;
    u32    status, command, agp_rate;
    int    len = 0;
    
    nv_state_t *nv;
    nv = (nv_state_t *) data;

    if (nv) {
        dev = nv_find_pci_dev(nv);
        if (!dev)
            return 0;
    } else {
        dev = nvos_find_agp_by_class(PCI_CLASS_BRIDGE_HOST);
        if (!dev)
            return 0;

        host = known_hosts;
        do {
            if (dev->vendor != host->vendor && host->vendor != 0) {
                host++;
                continue;
            }
            if (dev->device == host->device || host->device == 0) {
                len += sprintf(page, "Host Bridge: \t %s\n", host->name);
                break;
            }
            host++;
        } while (1);
    }

    /* what can this AGP device do? */
    cap_ptr = nvos_find_agp_capability(dev);

    pci_read_config_dword(dev, cap_ptr + 4, &status);
    pci_read_config_dword(dev, cap_ptr + 8, &command);

    fw  = (status & 0x00000010) ? "Supported" : "Not Supported";
    sba = (status & 0x00000200) ? "Supported" : "Not Supported";

    len += sprintf(page+len, "Fast Writes: \t %s\n", fw);
    len += sprintf(page+len, "SBA: \t\t %s\n", sba);

    agp_rate = status & 0x7;
    if (status & 0x8) // agp 3.0
        agp_rate <<= 2;

    len += sprintf(page+len, "AGP Rates: \t %s%s%s%s\n",
            (agp_rate & 0x00000008) ? "8x " : "",
            (agp_rate & 0x00000004) ? "4x " : "",
            (agp_rate & 0x00000002) ? "2x " : "",
            (agp_rate & 0x00000001) ? "1x " : "");

    len += sprintf(page+len, "Registers: \t 0x%08x:0x%08x\n", status, command);

    return len;
}

int nv_kern_read_status(char *page, char **start, off_t off,
        int count, int *eof, void *data)
{
    struct pci_dev *dev;
    char   *fw, *sba, *drv;
    int    len = 0;
    u8     cap_ptr;
    u32    scratch;
    u32    status, command, agp_rate;

    nv_state_t *nv;
    nv = (nv_state_t *) data;

    dev = nvos_find_agp_by_class(PCI_CLASS_BRIDGE_HOST);
    if (!dev)
        return 0;
    cap_ptr = nvos_find_agp_capability(dev);

    pci_read_config_dword(dev, cap_ptr + 4, &status);
    pci_read_config_dword(dev, cap_ptr + 8, &command);

    dev = nvos_find_agp_by_class(PCI_CLASS_DISPLAY_VGA);
    if (!dev)
        return 0;
    cap_ptr = nvos_find_agp_capability(dev);

    pci_read_config_dword(dev, cap_ptr + 4, &scratch);
    status &= scratch;
    pci_read_config_dword(dev, cap_ptr + 8, &scratch);
    command &= scratch;

    if (command & 0x100) {
        len += sprintf(page+len, "Status: \t Enabled\n");

        drv = NV_OSAGP_ENABLED(nv) ? "AGPGART" : "NVIDIA";
        if (!NV_AGP_ENABLED(nv)) drv = "AGPGART (inactive)";
        len += sprintf(page+len, "Driver: \t %s\n", drv);

        // mask off agp rate. 
        // If this is agp 3.0, we need to shift the value
        agp_rate = command & 0x7;
        if (status & 0x8) // agp 3.0
            agp_rate <<= 2;

        len += sprintf(page+len, "AGP Rate: \t %dx\n", agp_rate);

        fw = (command & 0x00000010) ? "Enabled" : "Disabled";
        len += sprintf(page+len, "Fast Writes: \t %s\n", fw);

        sba = (command & 0x00000200) ? "Enabled" : "Disabled";
        len += sprintf(page+len, "SBA: \t\t %s\n", sba);
    } else {
        len += sprintf(page+len, "Status: \t Disabled\n");
    }

    return len;
}

int nv_kern_read_legacy(char *page, char **start, off_t off,
        int count, int *eof, void *data)
{
    char *txt = "This file has been deprecated. What you are looking for \n"
                "can now be found in /proc/driver/nvidia. Please see the \n"
                "README for more information on the new /proc interface. \n";

    return sprintf(page, txt);
}


/***
 *** EXPORTS to rest of resman
 ***/

void *nv_find_kernel_mapping(
    nv_state_t    *nv,
    unsigned long  address
)
{
    nv_alloc_t *at;

    at = nvl_find_alloc(NV_GET_NVL_FROM_NV_STATE(nv), address, 
                        NV_ALLOC_TYPE_PCI);
    if (at && at->page_table)
    {
        // we've found the mapping and associated 'at' (in theory)
        // track down the actual page within this allocation and return
        // a kernel virtual mapping to it.
        int i;
        unsigned long offset;

        // save the page offset so we can add it to the returned address
        // page-align our address to make finding it a little easier
        offset = address & ~PAGE_MASK;
        address &= PAGE_MASK;

        for (i = 0; i < at->num_pages; i++)
        {
            if (address == (unsigned long) at->page_table[i])
            {
                unsigned long retaddr = (unsigned long) at->page_table[i];
                return __va((retaddr + offset));
            }
        }
    }

    // check if this was a contiguous kernel mapping...
    // make sure to change our physcal addr to a kernel virtual address
    at = nvl_find_alloc(NV_GET_NVL_FROM_NV_STATE(nv), 
                        (unsigned long) __va(address),
                        NV_ALLOC_TYPE_PCI | NV_ALLOC_TYPE_CONTIG);
    if (at)
        return at->key_mapping;

    return NULL;
}

/* For some newer AGP chipsets, such as the 460GX, the user's virtual address 
 * is not mapped directly to the agp aperture on the CPU's page tables. Instead,
 * they map to the underlying physical pages. This function is passed the
 * address of the underlying physical page (which is loaded into the GART) and
 * returns the agp aperture that the page is mapped to, so we can load that
 * page into the graphics card.
 * use the standard nvl_find_alloc to search on the physical page and rely on
 * the TYPE_AGP flag to differeniate it from a PCI allocation.
 * failure is fine, we may just be checking if a given page is agp
 */
void *
nv_find_agp_kernel_mapping(
    nv_state_t    *nv,
    unsigned long  address
)
{
    nv_alloc_t *at = NULL;

    at = nvl_find_alloc(NV_GET_NVL_FROM_NV_STATE(nv), address, 
                        NV_ALLOC_TYPE_AGP);
    if (at && at->page_table)
    {
        // we've found the mapping and associated 'at' (in theory)
        // track down the actual page within this allocation and return
        // the agp aperture mapping to it (key_mapping should be the base
        // of this aperture mapping, so track down the page within that mapping)
        int i;
        for (i = 0; i < at->num_pages; i++)
        {
            if (address == (unsigned long) at->page_table[i])
            {
                return (void *)((unsigned long) at->key_mapping + 
                    (i * PAGE_SIZE));
            }
        }
    }

    return NULL;
}


#if defined(NVCPU_IA64)
#  define KERN_PAGE_MASK      _PFN_MASK
#else
#  define KERN_PAGE_MASK      PAGE_MASK
#endif

/* virtual address to physical page address */
unsigned long
nv_get_phys_address(unsigned long address)
{
    pgd_t *pg_dir;
    pmd_t *pg_mid_dir;
    pte_t *pte__, pte;

#if defined(NVCPU_IA64)
    if (address > __IA64_UNCACHED_OFFSET)
        return address;
#endif

    /* direct-mapped kernel address */
    /* is this ok for IA64? */
    if ((address > PAGE_OFFSET) && (address < VMALLOC_START))
        return __pa(address);

    if (address > VMALLOC_START)
       pg_dir = pgd_offset_k(address);
    else
       pg_dir = pgd_offset(current->mm, address);

    if (pgd_none(*pg_dir))
        goto failed;

    pg_mid_dir = pmd_offset(pg_dir, address);
    if (pmd_none(*pg_mid_dir))
        goto failed;

#if defined (pte_offset_atomic)
    pte__ = pte_offset_atomic(pg_mid_dir, address);
    pte = *pte__;
    pte_kunmap(pte__);
#else
    pte__ = NULL;
    pte = *pte_offset(pg_mid_dir, address);
#endif

    if (!pte_present(pte))
        goto failed;

    return ((pte_val(pte) & KERN_PAGE_MASK) | NV_MASK_OFFSET(address));

  failed:
    return (unsigned long) NULL;
}


/* allocate memory for DMA push buffers */
int
nv_alloc_pages(
    nv_state_t *nv,
    void **pAddress,
    unsigned int page_count,
    unsigned int agp_memory,
    unsigned int contiguous,
    unsigned int cache,
    unsigned int kernel,
    unsigned int class,
    void **priv_data
)
{
    nv_alloc_t *at;
    RM_STATUS rm_status = 0;
    nv_linux_state_t *nvl = (nv_linux_state_t *) nv;

    nv_printf(NV_DBG_MEMINFO, "nv_alloc_pages: %d pages\n", page_count);

    page_count = RM_PAGES_TO_OS_PAGES(page_count);
    at = nvos_create_alloc(page_count);
    if (at == NULL)
        return RM_ERROR;

    at->class = class;

    if (agp_memory)
    {
        int offset;

        if (NV_AGP_DISABLED(nv))
        {
            goto failed;
        }

        /* allocate agp-able memory */
        if (NV_OSAGP_ENABLED(nv))
        {
            /* agpgart will allocate all of the underlying memory */
            rm_status = KernAllocAGPPages(nv, pAddress, page_count, priv_data, &offset);
            if (rm_status)
                goto failed;

            at->priv_data = *priv_data;
            at->flags = NV_ALLOC_TYPE_AGP;
            nvl_add_alloc(nvl, at);
        } else {
            /* use nvidia's nvagp support */
            if (nvos_malloc_pages(at->page_table, page_count))
                goto failed;

            NV_SET_AMD_PAGE_ATTRIB(at->page_table, page_count);
            at->class = class;

            // set our 'key' to the page_table. rm_alloc_agp_pages will call
            // nv_translate_agp_address below, which will look up pages using
            // the value of *pAddress as a key, then index into the page_table
            // once we're done with rm_alloc_agp_pages, we no longer need
            // this, and the 'key' will be replaced below
            *pAddress = at->page_table;
            at->key_mapping = at->page_table;
            at->flags = NV_ALLOC_TYPE_AGP;

            /* the 'at' needs to be added before the alloc agp pages call */
            nvl_add_alloc(nvl, at);
            rm_status = rm_alloc_agp_pages(nv,
                                        pAddress,
                                        page_count,
                                        class,
                                        priv_data,
                                        &offset);
            if (rm_status)
            {
                nvl_remove_alloc(nvl, at);
                goto failed;
            }
            at->priv_data = *priv_data;
        }
        // return the physical address of the allocation for mmap
        // in this case, 'physical address' is within the agp aperture
        *pAddress = (void *) (nv->agp.address + (offset << PAGE_SHIFT));
        at->agp_offset = offset;
        nv->agp_buffers++;
    }
    else 
    {
        /* allocate general system memory */
        /* for now, treat kernel memory as contiguous */
        if (contiguous || kernel)
        {
            int i;

            rm_status = os_alloc_contig_pages(pAddress, page_count * PAGE_SIZE);
            if (RM_OK != rm_status)
                goto failed;

            // load these pages into our page table for consistency across
            // allocation types (nv_kern_mmap will expect it).
            // make sure to get the physical address, since they may be remapped
            // to user space in nv_kern_mmap
            for (i = 0; i < at->num_pages; i++)
            {
                unsigned long addr = (unsigned long) *pAddress;
                at->page_table[i] = (void *) __pa((addr + (i * PAGE_SIZE)));
            }

            at->flags = NV_ALLOC_TYPE_PCI | NV_ALLOC_TYPE_CONTIG;
            if (kernel)
                at->flags |= NV_ALLOC_TYPE_KERNEL;
            nvl_add_alloc(nvl, at);
        }
        else
        {
            if (nvos_malloc_pages(at->page_table, page_count))
                goto failed;

            /* must be page-aligned or mmap will fail
             * so use the first page, which is page-aligned. this way, our 
             * allocated page table does not need to be page-aligned
             */
            *pAddress = (void *) at->page_table[0];
            at->flags = NV_ALLOC_TYPE_PCI;
            nvl_add_alloc(nvl, at);
        }
    }

    at->key_mapping = *pAddress;
    at->usage_count++;

    return RM_OK;

failed:
    /* free any pages we may have allocated */
    if (at->page_table)
        nvos_unlock_and_free_pages(at->usage_count, at->page_table, at->num_pages);

    nvos_free_alloc(at);

    return -1;
}

#define NV_FAILED_TO_FIND_AT(nv, paddr) \
    { \
        nv_unlock_at(nv); \
        nv_printf(NV_DBG_ERRORS, "NVRM: couldn't find alloc for 0x%p\n", *paddr); \
        return -1; \
    }

int
nv_free_pages(
    nv_state_t *nv,
    void **pAddress,
    unsigned int page_count,
    unsigned int agp_memory,
    void **priv_data
)
{
    int rmStatus = 0;
    nv_alloc_t *at;
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);

    page_count = RM_PAGES_TO_OS_PAGES(page_count);
    nv_printf(NV_DBG_MEMINFO, "nv_free_pages: 0x%x 0x%x\n", *pAddress, page_count);

    if (agp_memory)
    {
        /* only lock ldata while removing 'at' from the list */
        nv_lock_at(nv);
        at = nvl_find_alloc(nvl, (unsigned long) *pAddress, NV_ALLOC_TYPE_AGP);
        if (at == NULL)
            NV_FAILED_TO_FIND_AT(nv, pAddress);
        if (at->num_pages != page_count)
            NV_FAILED_TO_FIND_AT(nv, pAddress);
        nvl_remove_alloc(nvl, at);
        nv_unlock_at(nv);

        at->usage_count--;

        if (NV_OSAGP_ENABLED(nv))
        {
            rmStatus = KernFreeAGPPages(nv, pAddress, priv_data);
        } else {
            rmStatus = rm_free_agp_pages(nv, pAddress, priv_data);
            if (rmStatus == RM_OK)
            {
                NV_CLEAR_AMD_PAGE_ATTRIB(at->page_table, page_count);
                nvos_unlock_and_free_pages(at->usage_count, at->page_table, at->num_pages);
            }
        }

        /* we may hold off on disabling agp until all buffers are freed */
        if (rmStatus == RM_OK)
        {
            nv->agp_buffers--;
            if (!nv->agp_buffers && nv->agp_teardown)
                nv_agp_teardown(nv);
        }
    } else {
        /* only lock ldata while removing 'at' from the list */
        nv_lock_at(nv);
        at = nvl_find_alloc(nvl, (unsigned long) *pAddress, NV_ALLOC_TYPE_PCI);
        if (at == NULL)
            NV_FAILED_TO_FIND_AT(nv, pAddress);
        if (at->num_pages != page_count)
            NV_FAILED_TO_FIND_AT(nv, pAddress);
        nvl_remove_alloc(nvl, at);
        nv_unlock_at(nv);

        at->usage_count--;

        if (at->flags & NV_ALLOC_TYPE_CONTIG)
        {
            os_free_contig_pages(at->key_mapping, at->num_pages * PAGE_SIZE);
        }
        else
        {
            nvos_unlock_and_free_pages(at->usage_count, at->page_table, at->num_pages);
        }
    }

    if (at->usage_count == 0)
        nvos_free_alloc(at);

    return rmStatus;
}


/* avoid compiler warnings on UP kernels, 
 * when spinlock macros are defined away 
 */
#define NO_COMPILER_WARNINGS(nvl) \
    if (nvl == NULL) return

static void nv_lock_init_locks
( 
    nv_state_t *nv
)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);

    NO_COMPILER_WARNINGS(nvl);

    spin_lock_init(&nvl->rm_lock);
    spin_lock_init(&nvl->ldata_lock);
    spin_lock_init(&nvl->at_lock);
}

void nv_lock_rm(
    nv_state_t *nv
)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NO_COMPILER_WARNINGS(nvl);
    spin_unlock_wait(&nvl->rm_lock);
    spin_lock_irq(&nvl->rm_lock);
}

void nv_unlock_rm(
    nv_state_t *nv
)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NO_COMPILER_WARNINGS(nvl);
    spin_unlock_irq(&nvl->rm_lock);
}

static void nv_lock_ldata(
    nv_state_t *nv
)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NO_COMPILER_WARNINGS(nvl);
    spin_unlock_wait(&nvl->ldata_lock);
    spin_lock(&nvl->ldata_lock);
}

static void nv_unlock_ldata(
    nv_state_t *nv
)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NO_COMPILER_WARNINGS(nvl);
    spin_unlock(&nvl->ldata_lock);
}


static void nv_lock_at(
    nv_state_t *nv
)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NO_COMPILER_WARNINGS(nvl);
    spin_unlock_wait(&nvl->at_lock);
    spin_lock(&nvl->at_lock);
}

static void nv_unlock_at(
    nv_state_t *nv
)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NO_COMPILER_WARNINGS(nvl);
    spin_unlock(&nvl->at_lock);
}



/*
** post the event
** Called from osNotifyEvent()
**
** We already hold the spinlock when we get in here...
*/
void nv_post_event(
    void     *file_void
)
{
    nv_linux_state_t *nvl;
    nv_file_private_t *nvfp;
    struct file *file = file_void;

    nvfp = file->private_data;
    nvl = NV_GET_NVL_FROM_FILEP(file);

    nv_printf(NV_DBG_INFO, "post\n");

    nvfp->any_fired_notifiers++;

    wake_up_interruptible(GET_EVENT_QUEUE(nvl));
}

/*
** post vblank specifically
** Called from osVblankNotify(), from VBlank()
*/
void nv_post_vblank(
    nv_state_t *nv
)
{
    nv_linux_state_t *nvl = (nv_linux_state_t *) nv->os_state;
    nv_printf(NV_DBG_INFO, "post\n");

    if (nvl->waiting_for_vblank)
        nvl->vblank_notifier++;

    wake_up_interruptible(GET_EVENT_QUEUE(nvl));
}


int
nv_agp_init(
    nv_state_t *nv,
    VOID **phys_start,
    VOID **linear_start,
    VOID *agp_limit,
    U032 config         /* passed in from XF86Config file */
)
{
    U032 status = 1;

    nv_printf(NV_DBG_SETUP, "NVRM: nv_agp_init\n");

    nv_lock_ldata(nv);

    if (config & NVOS_AGP_CONFIG_OSAGP)
    {
        status = KernInitAGP(nv, phys_start, linear_start, agp_limit);

        /* if enabling agpgart was successfull, register it,
         * and check about overrides
         */
        if (status == 0)
        {
            nv->agp_config = NVOS_AGP_CONFIG_OSAGP;
            /* make sure we apply our overrides in this case */
            rm_update_agp_config(nv);
        }

        /* if agpgart is loaded, but we failed to initialize it,
         * we'd better not attempt nvagp, or we're likely to lock
         * the machine.
         */
        if (status < 0)
        {
            nv_unlock_ldata(nv);
            return 1;
        }
    }

    /* we're either explicitly not using agpgart,
     * or trying to use agpgart failed
     * make sure the user did not specify "use agpgart only"
     */
    if ( (NV_AGP_DISABLED(nv)) && (config & NVOS_AGP_CONFIG_NVAGP) )
    {
        /* make sure the user does not have agpgart loaded */
#if !defined (KERNEL_2_2)
        if (inter_module_get("drm_agp")) {
            inter_module_put("drm_agp");
#else
        if (GET_MODULE_SYMBOL(0, __MODULE_STRING(agp_enable))) {
#endif
            nv_printf(NV_DBG_WARNINGS, "NVRM: not using NVAGP, AGPGART is loaded!!\n");
        } else
            status = rm_init_agp(nv);
            if (status == RM_OK)
                nv->agp_config = NVOS_AGP_CONFIG_NVAGP;
    }

    nv_unlock_ldata(nv);

    nv_printf(NV_DBG_SETUP, 
        "NVRM: agp_init finished with status 0x%x and config %d\n",
        status, nv->agp_config);

    return status;
}

int
nv_agp_teardown(
    nv_state_t *nv
)
{
    U032 status = 1;

    nv_printf(NV_DBG_SETUP, "NVRM: nv_agp_teardown\n");

    /* little sanity check won't hurt */
    if (NV_AGP_DISABLED(nv))
        return -1;

    nv_lock_ldata(nv);

    /* if agp buffers are still in use, don't teardown just yet */
    if (nv->agp_buffers)
    {
        nv->agp_teardown = 1;
        nv_unlock_ldata(nv);
        return 0;
    }

    if (NV_OSAGP_ENABLED(nv))
    {
        status = KernTeardownAGP(nv);
    } else
        status = rm_shutdown_agp(nv);

    nv->agp_teardown = 0;
    nv->agp_config = NVOS_AGP_CONFIG_DISABLE_AGP;
    nv_unlock_ldata(nv);

    nv_printf(NV_DBG_SETUP, "NVRM: teardown finished with status 0x%x\n", 
        status);

    return status;
}

/*
 * this takes a virtual address related to agp and returns the physical address
 * this part is a bit hacky.. nvagp expects a kernel-linear address mapping,
 * but we don't have that. So, what's really passed to us is a "fake linear"
 * address, based at our page_table. nvl_find_agp_alloc() knows this, and will
 * return the 'at' that has the page_table. From there, we deconstruct this
 * 'fake address' to get a "page index" into the virtual address. We use this
 * index to grab a page from our page_table and return that physical address.
 * (if the linear mapping was real, this is the page that would have been
 * addressed by the virtual address. I know, confusing).
 */
int
nv_agp_translate_address(
    nv_state_t *nv,
    void       *base,
    U032        index,
    U032       *paddr
)
{
    nv_alloc_t *at;

    /* find the 'at' */
    at = nvl_find_alloc(NV_GET_NVL_FROM_NV_STATE(nv), 
                        (unsigned long) base, NV_ALLOC_TYPE_AGP);
    if (at == NULL)
        return -1;

    if (index > at->num_pages)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: translate_address: ",
            "at has inconsistent number of pages\n");
        return -1;
    }

    /* get the physical address of this page */
    *paddr = (U032) ((NV_UINTPTR_T)at->page_table[index]);

    return 0x0;
}


int
nv_int10h_call(
    nv_state_t *nv,
    U032 *eax,
    U032 *ebx,
    U032 *ecx,
    U032 *edx,
    VOID *buffer
)
{

    // hacked override for the time being..
    if ( (*eax == 0x4f14) && (*ebx == 0x0186) )
    {
        *eax = 0x004f;
        *ecx = ((nv->regs->map[0x101000/4]>>16)&0xf);
    }

    return 0x0;
}

/* set a timer to go off every second */
int 
nv_start_rc_timer(
    nv_state_t *nv
)
{
    nv_linux_state_t *nvl = (nv_linux_state_t *) nv;

    if (nv->rc_timer_enabled)
        return -1;

    nv_printf(NV_DBG_INFO, "NVRM: initializing rc timer\n");
    init_timer(&nvl->rc_timer);
    nvl->rc_timer.function = nv_kern_rc_timer;
    nvl->rc_timer.data = (unsigned long) nv;
    nv->rc_timer_enabled = 1;
    mod_timer(&nvl->rc_timer, jiffies + HZ); /* set our timeout for 1 second */
    nv_printf(NV_DBG_INFO, "NVRM: rc timer initialized\n");

    return 0;
}

int 
nv_stop_rc_timer(
    nv_state_t *nv
)
{
    nv_linux_state_t *nvl = (nv_linux_state_t *) nv;

    if (!nv->rc_timer_enabled)
        return -1;

    nv_printf(NV_DBG_INFO, "NVRM: stopping rc timer\n");
    nv->rc_timer_enabled = 0;
    del_timer(&nvl->rc_timer);
    nv_printf(NV_DBG_INFO, "NVRM: rc timer stopped\n");

    return 0;
}

