


/****************************************************************************
;
; NAME: osutil.c
;
;
; DESCRIPTION:
;
;       Linux utility wrap functions
;
;
; REVISION HISTORY:
;
;       $Log: /ESASHBA2/Linux/driver/osutil.c $
; 
; 55    11/05/08 2:44p Jseba
; Make sure LinuxGetReqBuffer is properly kmapping the buffer, and add
; LinuxReleaseReqBuffer
; 
; 54    10/20/08 10:29a Jseba
; Fix compilation error on kernels 2.6.22 and older
; 
; 53    10/16/08 9:04a Jseba
; Added LinuxPciFindDevice, LinuxPciReleaseDevice, and LinuxGetBridgePdev
; 
; 52    10/07/08 4:50p Jseba
; Fix LinuxGetReqBuffer for 2.6.25
; 
; 51    9/30/08 10:14a Jseba
; Fix a bug in LinuxRescanHostRoutine that could lead to invalid
; outstanding references to the driver
; 
; 50    9/17/08 2:36p Jseba
; Add LinuxSscanf and LinuxGetReqBuffer; remove OsDebugHeader calls
; 
; 49    8/14/08 3:59p Jseba
; Add SCSICMD_FLAGS_USE_CMD_PTR for 2.6.26 
; 
; 48    7/14/08 3:22p Jseba
; Null timer pointer when killing it; change LinuxGetCurrentTimeMs to use
; do_gettimeofday to avoid jiffie wrap-around problems
; 
; 47    5/27/08 4:22p Jseba
; Add OsDebugPrint so wrapper can pass debug information to the core; add
; a scsicmd_off flag to specify whether the sense_buffer is a pointer 
; 
; 46    5/21/08 4:03p Jseba
; Add pci_set_consistent_dma_mask so Celerity can allocate memory over
; the 4GB barrier
; 
; 45    4/24/08 11:37a Jseba
; Fixes for kernel 2.6.25; change LinuxPciUnmapSg
; 
; 44    2/25/08 11:56a Jseba
; Removed LinuxGetSgDmaAddr
; 
; 43    2/19/08 10:58a Jseba
; Remove 64-bit pci_set_consistent_dma_mask call - we don't want
; consistent memory of the 4GB barrier
; 
; 42    1/23/08 2:15p Jseba
; Fix builds when ATTO_RESCANS_ENABLED is not defined
; 
; 41    1/08/08 9:19a Jseba
; Fix builds for older kernels
; 
; 40    1/07/08 3:45p Jseba
; Fix ESASRAID for 2.4 kernels
; 
; 39    11/27/07 5:05p Jseba
; Rework LinuxRescanHostRoutine to only add devices that do not already
; exist; remove atto_allow_rescans
; 
; 38    11/09/07 3:33p Jseba
; Fix LinuxDivideQwords for 2.4 builds
; 
; 37    11/06/07 12:58p Jseba
; Rework rescan code (change_notification=1) to fix problems with older
; kernels
; 
; 36    10/23/07 9:49a Jseba
; Don't call scsi_scan_target if SCAN_WILD_CARD is not defined
; 
; 35    10/17/07 4:13p Jseba
; Fixed rescan code for 2.6.9 and earlier
; 
; 34    10/16/07 10:01a Jseba
; Instead of using scsi_scan_host to rescan for new devices, use
; scsi_remove_device when devices are removed, and scsi_scan_target when
; devices are added
; 
; 33    9/21/07 3:11p Jseba
; Cleaned up print statements
; 
; 32    8/27/07 11:09a Jseba
; Use new flags for request_irq on newer kernel
; 
; 31    8/21/07 10:41a Jseba
; Changed LinuxRemoveDevice/LinuxAddDevice to LinuxRescanHost and
; implemented it
; 
; 30    8/06/07 3:27p Jseba
; Simplified LinuxCompleteRequest and LinuxDpcInit; use
; tasklet_hi_schedule for DPCs; removed LinuxPciMapSg (it is now done by
; this layer, not the core); added LinuxGetCurrentTimeUs and
; LinuxDivideQwords; added LinuxExchangeOffsets to allow core to directly
; access elements of command and SGL structures
; 
; 29    2/01/07 5:01p Jseba
; Added a workaround for a SCSI Tape driver bug in 2.4 kernels on x86_64
; platforms in LinuxPciMapSg/LinuxPciUnmapSg.
; 
; 28    11/08/06 11:36a Jseba
; Fix interruptible_sleep_on problem - kernel needs to be locked; move
; VDA event notification socket code to attocfg
; 
; 27    10/09/06 10:18a Jseba
; Use LinuxGetErrno for all drivers, not just ESASRAID
; 
; 26    10/09/06 9:49a Jseba
; Fix the waitqueue functions to pre-allocate and initialize the queue,
; and gave them more accurate names;  added LinuxGetErrno to translate
; ATTO error codes to Linux errno
; 
; 25    10/04/06 10:25a Jseba
; Restore LinuxMemMap/LinuxMemUnmap for use by celeritymem driver
; 
; 24    9/27/06 9:58a Jseba
; Remove unused LinuxCompareExchangeDword/Pointer
; 
; 23    9/22/06 5:21p Jseba
; Move pci_set_drvdata call from LinuxUnregisterScsiHost to new
; LinuxUnregisterPci; don't call cond_resched() if in interrupt context
; 
; 22    9/20/06 10:20a Jseba
; Move CompStatErr definitions to os.h; removed locking from
; LinuxCompleteRequest and added LinuxLockIo/UnlockIo for core to handle
; locking itself; removed LinuxClaimRegion and LinuxMemMap stuff,
; replaced with LinuxMapRegions/UnMapRegions
; 
; 21    9/14/06 11:36a Jseba
; ESASRAID async event support
; 
; 20    8/28/06 11:16a Jseba
; Support for ESASRAID driver; New LinuxMemMap/LinuxMemUnmap
; 
; 19    7/24/06 4:11p Jseba
; Conditionalize cond_resched call for 2.6 kernel only
; 
; 18    1/03/06 10:22a Jseba
; Remove deprecated "intermodule" functions; use conditional in
; LinuxSleepOn; fix bugs when initiator_mode==0 or !CONFIG_SMP; add
; LinuxYield
; 
; 17    8/04/05 11:52a Jseba
; Conditional out spinlock routines on uniprocessor systems; add
; LinuxGetKernelVersion.
; 
; 16    3/21/05 10:28a Jseba
; Remove LinuxLockIo/LinuxUnlockIo - just do the locking directly when
; necessary
; 
; 15    1/25/05 10:48a Jseba
; Added translations for RS_SEL2 and RS_ADDR_ERR
; 
; 14    12/28/04 3:45p Jseba
; Modify LinuxMemMap/Unmap to allow old-style usage
; 
; 13    12/28/04 9:51a Jseba
; Improved PCI initialization functions; misc. cleanup
; 
; 12    12/09/04 12:28p Jseba
; Use jiffies instead of do_gettimeofday for LinuxGetCurrentTimeMs - the
; resolution is good enough, and the overhead is much lower.
; 
; 11    11/29/04 3:48p Sschmitz
; Fix type mismatch in debug statement.
; 
; 10    11/19/04 11:22a Jseba
; 64-bit support; sysfs support
; 
; 9     7/16/04 2:12p Jseba
; Fixes for non-SMP systems (where spinlocks are NOPs)
; 
; 8     6/29/04 2:58p Jseba
; Modification so this module can be shared with celeritymem driver.
; 
; 7     6/16/04 1:12p Jseba
; Comment out unused declarations
; 
; 6     6/16/04 11:41a Jseba
; Use tasklets for DPC; make disable/enable IRQ a NOP for 2.6 kernel
; 
; 5     3/31/04 3:54p Jseba
; Initial support for 2.6 kernel
; 
; 4     12/10/03 3:22p Jseba
; Add LinuxDisableIrq & LinuxEnableIrq
; 
; 3     12/09/03 9:52a Jseba
; Rename to celerityfc
; 
; 2     11/18/03 10:24a Jseba
; Improved LinuxCompleteRequest
; 
; 1     10/24/03 10:38a Jseba
; Initial entry
; 
; 
;
;
; THIS PROGRAM AND THE INFORMATION CONTAINED HEREIN IS THE PROPERTY OF
; ATTO TECHNOLOGY, INC. AND SHALL NOT BE REPRODUCED, COPIED, OR USED IN
; WHOLE OR IN PART OTHER THAN AS PROVIDED FOR IN THE LICENSE AGREEMENT
; PURSUANT TO WHICH IT WAS FURNISHED.
;
; COPYRIGHT (c) ATTO TECHNOLOGY, INC. 2003
; ALL RIGHTS RESERVED.
;
;***************************************************************************/

/* Pick up CompStatErr array for error code translation from os.h */
#define DECLARE_COMPSTAT

#include "os.h"
#include "oswrap.h"

int global_pci_dev_offset;

unsigned long LinuxLockIo (void)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
    unsigned long flags;
    spin_lock_irqsave (&io_request_lock, flags);
    return flags;
#else
    return 0;
#endif
}


void LinuxUnlockIo (unsigned long flags)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
    spin_unlock_irqrestore (&io_request_lock, flags);
#endif
}       

void OsDebugPrint (char *fmt, ...)
{
    char buffer[256];
    va_list ap;

    va_start (ap, fmt);

    vsprintf (buffer, fmt, ap);

    OsDebugString (buffer);
}


void LinuxCompleteRequest (void *_cmd, int scsi_stat, int req_stat, int resid)
{
    Scsi_Cmnd *cmd = (Scsi_Cmnd *) _cmd;

    if (req_stat != RS_SUCCESS)
    {
        if (req_stat == RS_SCSI_ERROR)
            cmd->result = ((DID_OK << 16) | (scsi_stat & STATUS_MASK));
        else 
        {
            if (req_stat < MaxCompStat)
                cmd->result = (((CompStatErr [req_stat]) << 16) | (scsi_stat & STATUS_MASK));
            else
                cmd->result = ((DID_ERROR << 16) | (scsi_stat & STATUS_MASK));
        }
    }
    else
        cmd->result = DID_OK << 16;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
    cmd->resid = resid;
#else
    scsi_set_resid (cmd, resid);
#endif

    cmd->scsi_done (cmd);
}


QWORD LinuxGetSgDma (void *_sglist, PDWORD Len)
{
    struct scatterlist **sglist = (struct scatterlist **) _sglist;
    QWORD rez;

    *Len = sg_dma_len (*sglist);

    rez =  sg_dma_address (*sglist);

    (*sglist)++;

    return rez;
}


/****************************************************************************

    FUNCTION:   void LinuxStallExecution (u32 Delay)

    PURPOSE:    Delay for the given number of microseconds

****************************************************************************/

void LinuxStallExecution (u32 Delay)
{
    while (Delay)
    {
        if (Delay < 1000)
        {
            udelay (Delay);
            barrier ();
            return;
        }
        else
        {
            mdelay (1);
            barrier ();
            Delay -= 1000;
        }
    }
}


int LinuxSprintf(char *str, const char *format, ...)
{
    va_list ap;

    va_start (ap, format);

    return vsprintf (str, format, ap);
}


int LinuxVsprintf (char *str, const char *format, va_list ap)
{
    return vsprintf (str, format, ap);
}


int LinuxSscanf(char *str, const char *format, ...)
{
    va_list ap;

    va_start (ap, format);

    return vsscanf (str, format, ap);
}


void LinuxPrint (char *fmt, ...)
{
    va_list ap;
    char str [1024];

    va_start (ap, fmt);

    vsprintf (str, fmt, ap);

    printk (str);
}


int LinuxPciReadConfig (void *_pdev, int where, void *ptr, int size)
{
    struct pci_dev *pdev = (struct pci_dev *) _pdev;

    switch (size)
    {
    case 1: return pci_read_config_byte  (pdev, where, (u8 *)  ptr);
    case 2: return pci_read_config_word  (pdev, where, (u16 *) ptr);
    case 4: return pci_read_config_dword (pdev, where, (u32 *) ptr);
    }

    return 0;
}


int LinuxPciWriteConfig (void *_pdev, int where, u32 val, int size)
{
    struct pci_dev *pdev = (struct pci_dev *) _pdev;

    switch (size)
    {
    case 1: return pci_write_config_byte  (pdev, where, (u8)  val);
    case 2: return pci_write_config_word  (pdev, where, (u16) val);
    case 4: return pci_write_config_dword (pdev, where, (u32) val);
    }

    return 0;
}


void *LinuxPciAllocConsistent (void *_pdev, int size, QWORD *bus_addr)
{
    return pci_alloc_consistent ((struct pci_dev *) _pdev,
                                 (size_t) size,
                                 (dma_addr_t *) bus_addr);
}


void LinuxPciFreeConsistent (void *_pdev, int size, void *cpu_addr, QWORD bus_addr)
{
    pci_free_consistent ((struct pci_dev *) _pdev,
                         (size_t) size, cpu_addr,
                         (dma_addr_t) bus_addr);
}


void LinuxUnMapRegions (void *_pdev, int *bars, int num_bars, void **maps)
{
    struct pci_dev *pdev = (struct pci_dev *) _pdev;
    int cur_bar;

    for (cur_bar = 0; cur_bar < num_bars; cur_bar++)
    {
        if (maps [cur_bar])
            iounmap (maps [cur_bar]);

        maps [cur_bar] = NULL;

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
        pci_release_region (pdev, bars [cur_bar]);
#endif
    }

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
    pci_release_regions (pdev);
#endif
}


int LinuxMapRegions (void *_pdev, char *name, int *bars, int num_bars, void **maps)
{
    struct pci_dev *pdev = (struct pci_dev *) _pdev;
    int cur_bar;

    /* First make sure the maps are cleared */

    for (cur_bar = 0; cur_bar < num_bars; cur_bar++)
        maps [cur_bar] = NULL;

    /* Reserve all requested regions */

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
    if (pci_request_regions (pdev, name))
    {
        printk (DEBUG_HEADER "Unable to reserve MMIO regions.\n");

        return 0;
    }
#else
    for (cur_bar = 0; cur_bar < num_bars; cur_bar++)
    {
        int error;

        if ((error = pci_request_region (pdev, bars [cur_bar], name)) != 0)
        {
            printk (DEBUG_HEADER "Unable to reserve MMIO region (%d, %d), error %d.\n",
                    cur_bar, bars [cur_bar], error);

            /* Release previously reserved regions */

            while (--cur_bar > -1)
                pci_release_region (pdev, bars [cur_bar]);

            return 0;
        }
    }
#endif

    /* Now, map everything */

    for (cur_bar = 0; cur_bar < num_bars; cur_bar++)
    {
        unsigned long reg = pci_resource_start (pdev, bars [cur_bar]);
        DWORD size        = pci_resource_len (pdev, bars [cur_bar]);

        if ((maps [cur_bar] = ioremap (reg, size)) == NULL)
        {
            printk (DEBUG_HEADER "Mem region at %p (size %d, %d [%d]) unavailable - already in use?\n",
                    (void *) reg, size, bars [cur_bar], cur_bar);

            /* Unmap and release previous maps */

            LinuxUnMapRegions (_pdev, bars, num_bars, maps);

            return 0;
        }
    }
        
    return 1;
}


void *LinuxMemMap (void *_pdev, char *name, unsigned long reg, u32 size)
{
    void *rez;
    struct pci_dev *pdev = (struct pci_dev *) _pdev;

    if (pdev)
        if (pci_request_regions (pdev, name))
        {
            printk (DEBUG_HEADER "Unable to reserve MMIO regions.\n");

            return NULL;
        }

    if ((rez = ioremap (reg, size)) == NULL)
    {
        printk (DEBUG_HEADER "Mem region at %p (size %d) unavailable - already in use?\n",
                (void *) reg, size);
    }

    return rez;
}


void LinuxMemUnmap (void *_pdev, void *reg)
{
    struct pci_dev *pdev = (struct pci_dev *) _pdev;

    if (pdev)
        pci_release_regions (pdev);

    iounmap (reg);
}


int LinuxClaimIrq (void *context, void *_pdev, char *name, void *isr)
{
    struct pci_dev *pdev = (struct pci_dev *) _pdev;

    if (request_irq (pdev->irq,
                     isr,
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
                     SA_INTERRUPT | SA_SHIRQ,
#else
                     IRQF_DISABLED | IRQF_SHARED,
#endif
                     name,
                     context))
    {
        printk (DEBUG_HEADER "Unable to request IRQ %02X.\n", pdev->irq);
        return 0;
    }

    return 1;
}


void LinuxReleaseIrq (void *context, void *_pdev)
{
    struct pci_dev *pdev = (struct pci_dev *) _pdev;

    free_irq (pdev->irq, context);
}    


void LinuxPciUnmapSg (void *_cmd)
{
    Scsi_Cmnd *cmd = (Scsi_Cmnd *) _cmd;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
    void *hostdata           = CMD_HOST(cmd)->hostdata;
    int nents                = cmd->use_sg;
    struct scatterlist *list = (struct scatterlist *) cmd->request_buffer;
    struct pci_dev *pdev     = GetPciDevFromHostData (hostdata);
    int dir                  = scsi_to_pci_dma_dir (cmd->sc_data_direction);

#ifdef ATTO_USE_KERNEL24_SCSI_TAPE_WORKAROUND
    /* See comment in atto_queuecommand for why this workaround is here. */

    int i;

    for (i = 0; i < nents; i++, list++)
        pci_unmap_single (pdev, list->dma_address, list->length, dir);

#else
    pci_unmap_sg (pdev, list, nents, dir);
#endif
#else
    scsi_dma_unmap (cmd);
#endif
}


QWORD LinuxPciMapSingle (void *_pdev, void *buffer, int size, int sc_dir)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
    /* This should never happen anymore with the new SCSI DMA stuff. */

    printk (DEBUG_HEADER "LinuxPciMapSingle called, that shouldn't happen!\n");

    return 0;
#else
    return pci_map_single ((struct pci_dev *) _pdev,
                           buffer,
                           (size_t) size,
                           scsi_to_pci_dma_dir (sc_dir));
#endif
}


void LinuxPciUnmapSingle (void *_pdev, QWORD bus_addr, int size, int sc_dir)
{
    pci_unmap_single ((struct pci_dev *) _pdev,
                      bus_addr,
                      (size_t) size,
                      scsi_to_pci_dma_dir (sc_dir));
}


int LinuxStartTimer (u32 context, void *_timer, u32 millisecs, void (*fn)(unsigned long))
{
    struct timer_list **timer = (struct timer_list **) _timer;

    /* If this is the first call to this function, */
    /* we need to allocate some space for it.      */

    if (!(*timer))
        if (((*timer) = vmalloc (sizeof (struct timer_list))) == NULL)
        {
            printk (DEBUG_HEADER "Unable to allocate timer_list.\n");
            return 0;
        }

    init_timer ((*timer));

    (*timer)->function = fn;
    (*timer)->data     = context;
    (*timer)->expires  = jiffies + (HZ * millisecs) / 1000;

    add_timer ((*timer));

    return 1;
}


void LinuxKillTimer (void *_timer)
{
    struct timer_list **timer = (struct timer_list **) _timer;

    if (*timer)
    {
        del_timer_sync (*timer);
        vfree (*timer);
        (*timer) = NULL;
    }
}


void *LinuxAlloc (u32 size)
{
    void *rez;

    rez = vmalloc (size);

    if (rez)
        memset (rez, 0, size);

    return rez;
}


void LinuxFree (void *addr)
{
    if (addr)
        vfree (addr);
}


void *LinuxIoRemap (u32 offset, u32 size)
{
    return ioremap (offset, size);
}


void LinuxIoUnmap (void *addr)
{
    iounmap (addr);
}


QWORD LinuxGetCurrentTimeSecs (void)
{
    struct timeval Now;

    do_gettimeofday (&Now);

    return ((QWORD)(Now.tv_sec));
}


DWORD LinuxGetCurrentTimeMs (void)
{
    struct timeval Now;
    QWORD Result;

    do_gettimeofday (&Now);

    Result = (QWORD)Now.tv_sec * 1000000L + (QWORD)Now.tv_usec;

    do_div (Result, 1000);

    return (DWORD) Result;
}


QWORD LinuxGetCurrentTimeUs (void)
{
    struct timeval Now;

    do_gettimeofday (&Now);

    return (QWORD)Now.tv_sec * 1000000L + (QWORD)Now.tv_usec;
}


int LinuxWaitInit (void **_queue)
{
    wait_queue_head_t **queue = (wait_queue_head_t **) _queue;

    if (*queue)
    {
        printk (DEBUG_HEADER "LinuxWaitInit called twice for same wait_queue!\n");
        vfree (*queue);
    }

    if (((*queue) = vmalloc (sizeof (wait_queue_head_t))) == NULL)
        return 0;

    init_waitqueue_head (*queue);

    return 1;
}


int LinuxWaitOn (void **_queue, int *conditional)
{
    wait_queue_head_t **queue = (wait_queue_head_t **) _queue;

    if (!(*queue))
    {
        printk (DEBUG_HEADER "LinuxWaitOn called for invalid wait_queue!\n");
        return 0;
    }

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11)
    wait_event_interruptible (**queue, (*conditional));
#else
    /* This is really silly, but we need to do it to get */
    /* around problems with interruptible_sleep_on.      */

    while (!(*conditional))
    {
        lock_kernel ();
        interruptible_sleep_on_timeout (*queue, 1);
        unlock_kernel ();
    };
#endif

    return 1;
}


void LinuxWaitWake (void **_queue)
{
    wait_queue_head_t **queue = (wait_queue_head_t **) _queue;

    if (!(*queue))
    {
        printk (DEBUG_HEADER "LinuxWaitWake called for invalid wait_queue!\n");
        return;
    }


    wake_up_interruptible (*queue);
}


void LinuxWaitFree (void **_queue)
{
    if (*_queue)
    {
        vfree (*_queue);
        (*_queue) = NULL;
    }
}


#ifdef ATTO_RESCANS_ENABLED

/* These get initialized along with the tasklets in LinuxDpcInit */

static struct work_struct atto_rescan_workers [ATTO_MAX_DEVICES];

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
void LinuxRescanHostRoutine (void *worker)
#else
void LinuxRescanHostRoutine (struct work_struct *worker)
#endif
{
    DWORD index = (struct work_struct *) worker - atto_rescan_workers;
    struct Scsi_Host *sh = (struct Scsi_Host *) OsGetAdapterHost (index);

    if (sh)
    {
        unsigned int scan_id = -1;
        BYTE scan_state;

        if ((scan_id = OsGetNextScanId (index, &scan_state)) != -1)
        {
            int lun;
            struct scsi_device *scsi_dev;

            if (scan_state == SCAN_STATE_LOGOUT
            ||  scan_state == SCAN_STATE_LUN_CHANGE)
            {
                for (lun = 0; lun < ATTO_MAX_LUN; lun++)
                {
                    scsi_dev = scsi_device_lookup (sh, 0, scan_id, lun);

                    if (scsi_dev)
                    {
#ifdef SDEV_OFFLINE
                        scsi_device_set_state (scsi_dev, SDEV_OFFLINE);
#endif
                        scsi_remove_device (scsi_dev);
                        scsi_device_put (scsi_dev);
                    }
                }
            }
            if (scan_state == SCAN_STATE_LOGIN
            ||  scan_state == SCAN_STATE_LUN_CHANGE)
            {
                int lun;

                for (lun = 0; lun < ATTO_MAX_LUN; lun++)
                {
                    scsi_dev = scsi_device_lookup (sh, 0, scan_id, lun);

                    if (scsi_dev)
                        scsi_device_put (scsi_dev);
                    else
                        scsi_add_device (sh, 0, scan_id, lun);
                }
            }

            /* Reschedule this task to pick up any other notifications. */

            schedule_work (&atto_rescan_workers [index]);
        }
    }

}
#endif /* ATTO_RESCANS_ENABLED */

void LinuxRescanHost (DWORD index)
{
#ifdef ATTO_RESCANS_ENABLED
    if (index >= ATTO_MAX_DEVICES)
    {
        printk (DEBUG_HEADER "LinuxRescanHost tried to use an invalid index.\n");
        return;
    }

    schedule_work (&atto_rescan_workers [index]);
#else
    printk (DEBUG_HEADER "LinuxRescanHost called for 2.4 kernel.\n");
#endif
}


static struct tasklet_struct atto_tasklet [ATTO_MAX_DEVICES];

void *LinuxDpcInit (DWORD index, void *callback)
{
    if (index >= ATTO_MAX_DEVICES)
    {
        printk (DEBUG_HEADER "LinuxDpcInit tried to initialize an invalid index.\n");
        return NULL;
    }

    memset (&atto_tasklet [index], 0, sizeof (struct tasklet_struct));

    tasklet_init (&atto_tasklet [index],
                  (void (*)(unsigned long)) callback,
                  (unsigned long) index);

#ifdef ATTO_RESCANS_ENABLED
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
    memset (&atto_rescan_workers [index], 0, sizeof (struct work_struct));

    INIT_WORK (&atto_rescan_workers [index],
               LinuxRescanHostRoutine
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
               , &atto_rescan_workers [index]
#endif
        );
#endif
#endif

    return &atto_tasklet [index];
}


void LinuxDpcQueue (void *dpc_context)
{
    tasklet_hi_schedule (dpc_context);
}


void LinuxDisableIrq (unsigned int irq)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
    disable_irq (irq);
#endif
}


void LinuxEnableIrq (unsigned int irq)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
    enable_irq (irq);
#endif
}


void LinuxUnregisterScsiHost (void *_host)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
    struct Scsi_Host *host = _host;

    if (host)
    {
        scsi_remove_host (host);
        scsi_host_put (host);
    }
#endif
}


void LinuxUnregisterPci (void *_pcid)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
    struct pci_dev *pcid = (struct pci_dev *) _pcid;

    if (pcid)
        pci_set_drvdata (pcid, NULL);
#endif
}

void LinuxResetDetected (void *_sh, int channel)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
    struct Scsi_Host *sh = (struct Scsi_Host *) _sh;

    if (sh)
        scsi_report_bus_reset (sh, channel);
#endif
}


void LinuxAcquireSpinLock (void *_lock, unsigned long *_flags)
{
#ifdef CONFIG_SMP
    spinlock_t *lock = (spinlock_t *) _lock;
    spin_lock_irqsave (lock, *(_flags));
#endif
}


void LinuxReleaseSpinLock (void *_lock, unsigned long *_flags)
{
#ifdef CONFIG_SMP
    spinlock_t *lock = (spinlock_t *) _lock;
    spin_unlock_irqrestore (lock, *(_flags));
#endif
}


int LinuxIsSpinLockHeld (void *_lock)
{
#ifdef CONFIG_SMP
    spinlock_t *lock = (spinlock_t *) _lock;
    return spin_is_locked (lock);
#else
    return 0;
#endif
}


void *LinuxInitSpinLock (void)
{
#ifdef CONFIG_SMP
    /* On non-SMP systems, sizeof (spinlock_t) is 0, and */
    /* vmalloc crashes when asked to allocate 0 bytes.   */

    spinlock_t *lock;

    if ((lock = (spinlock_t *) vmalloc (sizeof (*lock))) != NULL)
        spin_lock_init (lock);

    return lock;
#else
    /* Caller expects non-NULL return if successful.  Since this */
    /* value will never actually be used for anything on non-SMP */
    /* machines, just return a meaningless non-NULL value.       */

    return ((void *) 42);
#endif
}


void LinuxUninitSpinLock (void *lock)
{
#ifdef CONFIG_SMP
    if (lock)
        vfree (lock);
#endif
}


int LinuxGetPageSize (void)
{
    return PAGE_SIZE;
}


void LinuxOutb (unsigned char v, unsigned long a)
{
    outb (v, a);
}


unsigned char LinuxInb (unsigned long a)
{
    return inb (a);
}


int LinuxSetDmaMask (void *_pdev, int size)
{
    struct pci_dev *pdev = (struct pci_dev *) _pdev;

    if (size == 32)
    {
        if (pci_set_dma_mask (pdev, (u64) 0xFFFFFFFFULL) == 0)
            return 1;
        else
        {
            printk (DEBUG_HEADER "Error setting 32-bit DMA mask!\n");
            return 0;
        }
    }

    if (size == 64)
    {
        if (pci_set_dma_mask (pdev, 0xFFFFFFFFFFFFFFFFULL) == 0)
        {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
            return 1;
#else
#ifdef CELERITYFC
            if (pci_set_consistent_dma_mask (pdev, 0xFFFFFFFFFFFFFFFFULL) == 0)
#else
            if (pci_set_consistent_dma_mask (pdev, (u64) 0xFFFFFFFFULL) == 0)
#endif
                return 1;
            else
            {
                printk (DEBUG_HEADER "Error setting 64-bit consistent DMA mask!\n");
                return 0;
            }
#endif
        }
        else
        {
            printk (DEBUG_HEADER "Error setting 64-bit DMA mask!\n");
            return 0;
        }
    }

    printk (DEBUG_HEADER "LinuxSetDmaMask invalid size %d!?\n", size);
    return 0;
}


char *LinuxGetKernelVersion (void)
{
    return UTS_RELEASE;
}


void LinuxYield (void)
{
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
    /* Yield the CPU if we are doing something potentially */
    /* time-consuming.  However, this is not safe to do    */
    /* in certain contexts, because it will schedule().    */

    if (!in_atomic () && !irqs_disabled ())
        cond_resched ();
#endif
}


int LinuxGetErrno (int atto_errno)
{
    switch (atto_errno)
    {
    case ATTO_BUSY:           return -EBUSY;
    case ATTO_NO_DEVICE:      return -ENODEV;
    case ATTO_NOT_IMPLMNTD:   return -ENOSYS;
    case ATTO_INVALID_ARG:    return -EINVAL;
    case ATTO_OUT_OF_MEMORY:  return -ENOMEM;
    }

    return -ENOSYS;
}


void LinuxExchangeOffsets (PSGL_OFFSETS sgl_off,
                           PSCSICMD_OFFSETS scsicmd_off,
                           int pci_dev_offset)
{
    struct scatterlist *null_sg = NULL;
    Scsi_Cmnd *null_cmd = NULL;

    sgl_off->dma_len_offset  = POINTER_TO_DWORD (&sg_dma_len (null_sg));
    sgl_off->dma_addr_offset = POINTER_TO_DWORD (&sg_dma_address (null_sg));
    sgl_off->dma_addr_size   = sizeof (sg_dma_address (null_sg));
    sgl_off->sgl_size        = sizeof (struct scatterlist);

    scsicmd_off->scsi_done_offset    = POINTER_TO_DWORD (&null_cmd->scsi_done);
    scsicmd_off->cmd_len_offset      = POINTER_TO_DWORD (&null_cmd->cmd_len);
    scsicmd_off->sense_buffer_offset = POINTER_TO_DWORD (&null_cmd->sense_buffer);
    scsicmd_off->data_dir_offset     = POINTER_TO_DWORD (&null_cmd->sc_data_direction);
    scsicmd_off->cmnd_offset         = POINTER_TO_DWORD (&null_cmd->cmnd);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
    scsicmd_off->req_bufflen_offset  = POINTER_TO_DWORD (&null_cmd->request_bufflen);
    scsicmd_off->req_buffer_offset   = POINTER_TO_DWORD (&null_cmd->request_buffer);
    scsicmd_off->use_sg_offset       = POINTER_TO_DWORD (&null_cmd->use_sg);
#else
    scsicmd_off->req_bufflen_offset  = POINTER_TO_DWORD (&null_cmd->sdb.length);
    scsicmd_off->req_buffer_offset   = POINTER_TO_DWORD (&null_cmd->sdb.table.sgl);
    scsicmd_off->use_sg_offset       = POINTER_TO_DWORD (&null_cmd->sdb.table.nents);
    scsicmd_off->flags               = SCSICMD_FLAGS_USE_SENSE_PTR;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,25)
    scsicmd_off->flags              |= SCSICMD_FLAGS_USE_CMD_PTR;
#endif
#endif
    scsicmd_off->sense_buffer_size   = SCSI_SENSE_BUFFERSIZE;
    scsicmd_off->data_dir_unknown    = SCSI_DATA_UNKNOWN;
    scsicmd_off->data_dir_write      = SCSI_DATA_WRITE;
    scsicmd_off->data_dir_read       = SCSI_DATA_READ;
    scsicmd_off->data_dir_none       = SCSI_DATA_NONE;
    scsicmd_off->tag_value_head      = HEAD_OF_QUEUE_TAG;
    scsicmd_off->tag_value_simple    = SIMPLE_QUEUE_TAG;
    scsicmd_off->tag_value_ordered   = ORDERED_QUEUE_TAG; 

    global_pci_dev_offset = pci_dev_offset;
}


void LinuxDivideQwords (PQWORD Numerator, PDWORD Denominator, PQWORD Result)
{
    QWORD tmp = (*Numerator);
    do_div (tmp, (*Denominator));
    (*Result) = tmp;
}


void *LinuxGetReqBuffer (void *_cmd, PDWORD BuffLength)
{
    Scsi_Cmnd *cmd = (Scsi_Cmnd *) _cmd;
    struct scatterlist *sgl = 
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
            (struct scatterlist *) cmd->request_buffer;

    if (cmd->use_sg)
#else
            (struct scatterlist *) scsi_sglist (cmd);

    if (scsi_sg_count (cmd))
#endif
    {
        if (((*BuffLength) = sg_dma_len (&sgl [0])) == 0)
            return NULL;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
        return kmap_atomic (sgl [0].page, KM_IRQ0) + sgl [0].offset;
#else
        return kmap_atomic (sg_page (&sgl [0]), KM_IRQ0) + sgl [0].offset;
#endif
    }

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
    if (((*BuffLength) = cmd->request_bufflen) == 0)
#else
    if (((*BuffLength) = scsi_bufflen (cmd)) == 0)
#endif
        return NULL;

    return (void *) sgl;
}


void LinuxReleaseReqBuffer (void *_cmd, void *_buf)
{
    Scsi_Cmnd *cmd;
    unsigned char *buf;
    struct scatterlist *sgl;

    /* We don't just assign these values when we declare them because */
    /* kunmap_atomic is a NOP on some kernels (x64?), so assigning    */
    /* them up there leads to spurious "unused variable" warnings.    */

    cmd = (Scsi_Cmnd *) _cmd;
    buf = (unsigned char *) _buf;
    sgl = 
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
            (struct scatterlist *) cmd->request_buffer;

    if (cmd->use_sg)
#else
            (struct scatterlist *) scsi_sglist (cmd);

    if (scsi_sg_count (cmd))
#endif
        kunmap_atomic (buf - sgl [0].offset, KM_IRQ0);
}


#ifdef ESASRAID
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
extern attocfg_fire_vda_event fire_vda_event;

void LinuxFireEvent (void *data, int len)
{
    if (!fire_vda_event)
    {
        printk (DEBUG_HEADER "Error sending event message - handler not registered.\n");
        return;
    }

    if (!(*fire_vda_event) (data, len))
        printk (DEBUG_HEADER "Error sending event message.\n");
}
#else

void LinuxFireEvent (void *data, int len)
{
    printk (DEBUG_HEADER "VDA event occurred, but not supported on this kernel.\n");
}

#endif
#endif /* ESASRAID */

#if defined(ESASRAID) || (defined(ESASHBA) && !defined(ESAS2HBA))
void *LinuxPciFindDevice (unsigned int vendor, unsigned int device, void *_prev)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
    return (void *) pci_get_device  (vendor, device, (struct pci_dev *) _prev);
#else
    return (void *) pci_find_device (vendor, device, (struct pci_dev *) _prev);
#endif
}

void LinuxPciReleaseDevice (void *_pdev)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
    struct pci_dev *pdev = (struct pci_dev *) _pdev;

    pci_dev_put (pdev);
#endif
}

void *LinuxGetBridgePdev (void *_pdev)
{
    struct pci_dev *pdev = (struct pci_dev *) _pdev;
    struct pci_bus *bus = NULL;
    struct pci_dev *bus_pdev = NULL;

    if ((pdev == NULL)
    || ((bus = pdev->bus) == NULL)
    || ((bus_pdev = bus->self) == NULL))
    {
        printk (DEBUG_HEADER "Error accessing bridge config space (%p, %p, %p)\n",
                pdev, bus, bus_pdev);
        return NULL;
    }

    return bus_pdev;
}
#endif
