


/****************************************************************************
;
; NAME: atto_cfg.c
;
;
; DESCRIPTION:
;
;       Driver to discover ATTO HBAs for the Configuration Tool
;
;
; REVISION HISTORY:
;
;       $Log: /ESASHBA2/Linux/driver/atto_cfg.c $
; 
; 19    10/07/08 4:47p Jseba
; ESAS2HBA support
; 
; 18    8/14/08 10:17a Jseba
; Fixes for 2.6.26
; 
; 17    5/22/08 2:16p Jseba
; Use module_init/exit; change return value of attocfg register function
; 
; 16    4/24/08 11:33a Jseba
; Removed "file_version" stuff, version info is now embedded in main
; driver
; 
; 15    4/17/08 4:50p Jseba
; CELERITYFC2 is now CELERITY8FC
; 
; 14    2/19/08 10:57a Jseba
; Add CELERITYFC2 support; create "/proc/attocfg" to allow manual rescans
; for new hosts
; 
; 13    1/25/08 4:25p Jseba
; Fix function declarations for 2.6.22 and up
; 
; 12    8/06/07 3:19p Jseba
; Support for ESASHBA driver
; 
; 11    4/27/07 4:53p Jseba
; Pick up errors from sysfs_create calls
; 
; 10    4/02/07 11:30a Jseba
; Fixed work_struct stuff for 2.6.20 kernel
; 
; 9     3/14/07 4:40p Jseba
; Only include config.h if ATTO_USE_CONFIG_H is defined
; 
; 8     2/27/07 11:10a Jseba
; Don't call vfree with IRQs disabled
; 
; 7     2/20/07 2:48p Jseba
; Add code to queue VDA events and schedule a non-interrupt context task
; to send them
; 
; 6     11/14/06 2:31p Jseba
; Add "fs" handlers for ESASRAID FS_API
; 
; 5     11/08/06 11:34a Jseba
; Interface version 3 - added VDA event notification and de-registration
; 
; 4     10/09/06 9:50a Jseba
; Added ESASRAID VDA node support
; 
; 3     9/20/06 9:55a Jseba
; Support for EXPRESS2 driver
; 
; 2     8/28/06 11:13a Jseba
; Support for ESASRAID driver
; 
; 1     1/03/06 10:47a Jseba
; Initial submission
; 
; 
; 
;
;
; Released under GPL v2
;
; COPYRIGHT (c) ATTO TECHNOLOGY, INC. 2006
; ALL RIGHTS RESERVED.
;
;***************************************************************************/

#ifdef ATTO_USE_CONFIG_H
#include <linux/config.h>
#endif
#define EXPORT_SYMTAB

#ifndef LINUX_VERSION_CODE
#include <linux/version.h>
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
#include <linux/modversions.h>
#endif

#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/vmalloc.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <linux/in.h>
#include <linux/workqueue.h>
#include <net/sock.h>
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#endif

#include "atto_cfg.h"

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#error !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#error !!! This driver will only work on version 2.6.x of the Linux kernel !!!
#error !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#endif

MODULE_DESCRIPTION ("ATTO Configuration Tool driver");
MODULE_AUTHOR ("ATTO Technology");
MODULE_LICENSE("GPL");

void scan_for_hba_drivers (void);

/* Global structures to track drivers and hosts */

typedef struct _attocfg_host       *PATTOCFG_HOST;
typedef struct _attocfg_hba_driver *PATTOCFG_HBA_DRVR;

typedef struct _attocfg_host
{
    PATTOCFG_HBA_DRVR driver;
    PATTOCFG_HOST     next;
    ATTOCFG_HANDLERS  handlers;
    struct kobject    *kobj;
} ATTOCFG_HOST;

#define ATTO_DRVR_NONE           0

#define ATTO_DRVR_CELERITYFC     1
extern attocfg_register_callback celerityfc_attocfg_register;

#define ATTO_DRVR_ESASRAID       2
extern attocfg_register_callback esasraid_attocfg_register;

#define ATTO_DRVR_EXPRESS2       3
extern attocfg_register_callback express2_attocfg_register;

#define ATTO_DRVR_ESASHBA        4
extern attocfg_register_callback esashba_attocfg_register;

#define ATTO_DRVR_CELERITY8FC    5
extern attocfg_register_callback celerity8fc_attocfg_register;

#define ATTO_DRVR_ESAS2HBA       6
extern attocfg_register_callback esas2hba_attocfg_register;

typedef struct _attocfg_hba_driver
{
    int                      type;
    PATTOCFG_HOST            hosts;
    drvr_attocfg_unregister  attocfg_unregister;
} ATTOCFG_HBA_DRVR;

ATTOCFG_HBA_DRVR hba_drvr [] =
{
    {ATTO_DRVR_CELERITYFC,  NULL, NULL},
    {ATTO_DRVR_ESASRAID,    NULL, NULL},
    {ATTO_DRVR_EXPRESS2,    NULL, NULL},
    {ATTO_DRVR_ESASHBA,     NULL, NULL},
    {ATTO_DRVR_CELERITY8FC, NULL, NULL},
    {ATTO_DRVR_ESAS2HBA,     NULL, NULL},
    {ATTO_DRVR_NONE,        NULL, NULL}
};


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

    FUNCTION:   PATTOCFG_HOST attocfg_find_host_by_kobj (struct kobject *kobj)

    PURPOSE:    Searches the list of registered hosts for a matching kobject

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

PATTOCFG_HOST attocfg_find_host_by_kobj (struct kobject *kobj)
{
    PATTOCFG_HBA_DRVR this_drvr;

    for (this_drvr = hba_drvr;
         this_drvr->type != ATTO_DRVR_NONE;
         this_drvr++)
    {
        PATTOCFG_HOST host;

        for (host = this_drvr->hosts;
             host;
             host = host->next)
        {
            if (host->kobj == kobj)
                return host;
        }
    }

    return NULL;
}


/* Callback handlers */

static ssize_t attocfg_read_default_nvram (struct kobject *kobj,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22)
                                           struct bin_attribute *bin_attr,
#endif
                                           char *buf,
                                           loff_t off,
                                           size_t count)
{
    PATTOCFG_HOST host = attocfg_find_host_by_kobj (kobj);

    if (host && host->handlers.hba_nvram_handler)
        return (*host->handlers.hba_nvram_handler) (kobj, buf, off, count, ATTOCFG_DEFAULT_NVRAM);
    else
        return 0;
}


static ssize_t attocfg_read_live_nvram (struct kobject *kobj,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22)
                                        struct bin_attribute *bin_attr,
#endif
                                        char *buf,
                                        loff_t off,
                                        size_t count)
{
    PATTOCFG_HOST host = attocfg_find_host_by_kobj (kobj);

    if (host && host->handlers.hba_nvram_handler)
        return (*host->handlers.hba_nvram_handler) (kobj, buf, off, count, ATTOCFG_READ_NVRAM);
    else
        return 0;
}


static ssize_t attocfg_write_live_nvram (struct kobject *kobj,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22)
                                         struct bin_attribute *bin_attr,
#endif
                                         char *buf,
                                         loff_t off,
                                         size_t count)
{
    PATTOCFG_HOST host = attocfg_find_host_by_kobj (kobj);

    if (host && host->handlers.hba_nvram_handler)
        return (*host->handlers.hba_nvram_handler) (kobj, buf, off, count, ATTOCFG_WRITE_NVRAM);
    else
        return 0;
}

static ssize_t attocfg_read_fw (struct kobject *kobj,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22)
                                struct bin_attribute *bin_attr,
#endif
                                char *buf,
                                loff_t off,
                                size_t count)
{
    PATTOCFG_HOST host = attocfg_find_host_by_kobj (kobj);

    if (host && host->handlers.hba_fw_handler)
        return (*host->handlers.hba_fw_handler) (kobj, buf, off, count, ATTOCFG_READ_FW);
    else
        return 0;
}


static ssize_t attocfg_write_fw (struct kobject *kobj,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22)
                                 struct bin_attribute *bin_attr,
#endif
                                 char *buf,
                                 loff_t off,
                                 size_t count)
{
    PATTOCFG_HOST host = attocfg_find_host_by_kobj (kobj);

    if (host && host->handlers.hba_fw_handler)
        return (*host->handlers.hba_fw_handler) (kobj, buf, off, count, ATTOCFG_WRITE_FW);
    else
        return 0;
}


static ssize_t attocfg_read_vda (struct kobject *kobj,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22)
                                 struct bin_attribute *bin_attr,
#endif
                                 char *buf,
                                 loff_t off,
                                 size_t count)
{
    PATTOCFG_HOST host = attocfg_find_host_by_kobj (kobj);

    if (host && host->handlers.hba_vda_handler)
        return (*host->handlers.hba_vda_handler) (kobj, buf, off, count, ATTOCFG_READ_VDA);
    else
        return 0;
}


static ssize_t attocfg_write_vda (struct kobject *kobj,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22)
                                  struct bin_attribute *bin_attr,
#endif
                                  char *buf,
                                  loff_t off,
                                  size_t count)
{
    PATTOCFG_HOST host = attocfg_find_host_by_kobj (kobj);

    if (host && host->handlers.hba_vda_handler)
        return (*host->handlers.hba_vda_handler) (kobj, buf, off, count, ATTOCFG_WRITE_VDA);
    else
        return 0;
}


static ssize_t attocfg_read_fs (struct kobject *kobj,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22)
                                struct bin_attribute *bin_attr,
#endif
                                char *buf,
                                loff_t off,
                                size_t count)
{
    PATTOCFG_HOST host = attocfg_find_host_by_kobj (kobj);

    if (host && host->handlers.hba_fs_handler)
        return (*host->handlers.hba_fs_handler) (kobj, buf, off, count, ATTOCFG_READ_FS);
    else
        return 0;
}


static ssize_t attocfg_write_fs (struct kobject *kobj,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22)
                                 struct bin_attribute *bin_attr,
#endif
                                 char *buf,
                                 loff_t off,
                                 size_t count)
{
    PATTOCFG_HOST host = attocfg_find_host_by_kobj (kobj);

    if (host && host->handlers.hba_fs_handler)
        return (*host->handlers.hba_fs_handler) (kobj, buf, off, count, ATTOCFG_WRITE_FS);
    else
        return 0;
}

#define BIN_ATTR( _name, _mode, _size, _read, _write )         \
    struct bin_attribute bin_attr_##_name = {                  \
        .attr  = { .name = __stringify(_name), .mode = _mode,  \
                   .owner = THIS_MODULE },                     \
        .size  = _size,                                        \
        .read  = _read,                                        \
        .write = _write }

/* Only root can read/write nvram and flash */

static BIN_ATTR (default_nvram,
                 S_IRUSR, 0,
                 attocfg_read_default_nvram,
                 NULL);

static BIN_ATTR (live_nvram,
                 S_IRUSR | S_IWUSR, 0,
                 attocfg_read_live_nvram,
                 attocfg_write_live_nvram);

static BIN_ATTR (fw,
                 S_IRUSR | S_IWUSR, 0,
                 attocfg_read_fw,
                 attocfg_write_fw);

static BIN_ATTR (vda,
                 S_IRUSR | S_IWUSR, 0,
                 attocfg_read_vda,
                 attocfg_write_vda);

static BIN_ATTR (fs,
                 S_IRUSR | S_IWUSR, 0,
                 attocfg_read_fs,
                 attocfg_write_fs);

#ifdef CONFIG_PROC_FS
static int attocfg_proc_major = 0;

ssize_t attocfg_proc_write (struct file *fp, const char __user *buf,
                            size_t count, loff_t *data)
{
    ssize_t rez = -ENOMEM;
    char *page;

    if (count > 1024)
        return -EOVERFLOW;

    if (count < 6)
        return -EINVAL;

    page = (char *) __get_free_page (GFP_KERNEL);

    if (page)
    {
        rez = -EFAULT; /* Assume failure of copy... */

        if (copy_from_user (page, buf, count))
            goto ErrorExit;

        if (memcmp (page, "rescan", 6) != 0)
        {
            rez = -EINVAL;
            goto ErrorExit;
        }

        /* Somebody wrote "rescan" to us, so lets do that */

        rez = count;
        scan_for_hba_drivers ();
    }

ErrorExit:
    free_page ((unsigned long) page);
    return rez;
}

static struct file_operations attocfg_proc_fops =
{
    .write = attocfg_proc_write,
};

#endif



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

    FUNCTION:   int attocfg_register_host (void *context,
                                           struct kobject *kobj,
                                           PATTOCFG_HANDLERS handlers)

    PURPOSE:    Callback for discovered HBA drivers to register its hosts

    RETURNS:    1 on success, 0 on error

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

int attocfg_register_host (void *context,
                           struct kobject *kobj,
                           PATTOCFG_HANDLERS handlers)
{
    PATTOCFG_HBA_DRVR hba = context;
    PATTOCFG_HOST host;
    int error;

    host = vmalloc (sizeof (ATTOCFG_HOST));

    if (!host)
    {
        printk ("attocfg: Out of memory!\n");
        return 0;
    }

    /* Link the new guy in the chain. */

    memset (host, 0, sizeof (*host));

    host->driver = hba;
    host->kobj   = kobj;
    memcpy (&host->handlers, handlers, sizeof (*handlers));

    if (hba->hosts == NULL)
        hba->hosts = host;
    else
    {
        PATTOCFG_HOST tmp;

        for (tmp = hba->hosts;
             tmp->next;
             tmp = tmp->next);

        tmp->next = host;
    }

    if (handlers->hba_nvram_handler)
    {
        if ((error = sysfs_create_bin_file (kobj, &bin_attr_default_nvram)) != 0)
            printk ("attocfg: Warning, sysfs_create_bin_file returned %d, "
                    "default NVRAM access will not work.\n", error);

        if ((error = sysfs_create_bin_file (kobj, &bin_attr_live_nvram)) != 0)
            printk ("attocfg: Warning, sysfs_create_bin_file returned %d, "
                    "live NVRAM access will not work.\n", error);
    }

    if (handlers->hba_fw_handler)
        if ((error = sysfs_create_bin_file (kobj, &bin_attr_fw)) != 0)
            printk ("attocfg: Warning, sysfs_create_bin_file returned %d, "
                    "firmware access will not work.\n", error);

    if (handlers->hba_vda_handler)
        if ((error = sysfs_create_bin_file (kobj, &bin_attr_vda)) != 0)
            printk ("attocfg: Warning, sysfs_create_bin_file returned %d, "
                    "VDA access will not work.\n", error);

    if (handlers->hba_fs_handler)
        if ((error = sysfs_create_bin_file (kobj, &bin_attr_fs)) != 0)
            printk ("attocfg: Warning, sysfs_create_bin_file returned %d, "
                    "FS access will not work.\n", error);

    return 1;
}


/* Structure and functions to handle VDA event notification. */

typedef struct VdaEventQueue VDA_AE_Q, *PVDA_AE_Q;
#define VDA_AE_MAX_LENGTH    0x100

struct VdaEventQueue
{
    PVDA_AE_Q     next;
    int           length;
    unsigned char data [VDA_AE_MAX_LENGTH];
};

spinlock_t vda_event_lock;
PVDA_AE_Q  vda_event_queue = NULL;
PVDA_AE_Q  vda_event_free = NULL;
void      *vda_event_memory = NULL;
int        vda_events_initialized = 0;

/* Arbitrary count of the max VDA events we can handle at once. */

#define VDA_AE_COUNT   64

#define ATTO_EVENT_PORT   54414

struct socket *event_socket [2] = {NULL, NULL};


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

    FUNCTION:   void send_vda_event (void *data, int len)

    PURPOSE:    Send the specified data to the VDA ports.  Cannot be run
                from interrupt context!

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

void send_vda_event (void *data, int len)
{
    int res [2];
    mm_segment_t oldfs;
    struct iovec iov;
    struct msghdr msg;
    int sock_num;

    memset (&iov, 0, sizeof (iov));
    memset (&msg, 0, sizeof (msg));

    iov.iov_base = data; 
    iov.iov_len  = len;

    msg.msg_name       = 0; 
    msg.msg_namelen    = 0; 
    msg.msg_iov        = &iov; 
    msg.msg_iovlen     = 1; 
    msg.msg_control    = NULL; 
    msg.msg_controllen = 0; 
    msg.msg_flags      = MSG_DONTWAIT | MSG_NOSIGNAL;

    oldfs = get_fs();

    set_fs (KERNEL_DS); 

    for (sock_num = 0; sock_num < 2; sock_num++)
    {
        if (event_socket [sock_num])
            res [sock_num] = sock_sendmsg (event_socket [sock_num],
                                           &msg,
                                           len);
        else
            res [sock_num] = -ENOTSOCK;
    }

    set_fs (oldfs); 

#ifdef ATTO_DEBUG
    if (res [0] < 0 || res [1] < 0)
    {
        /* Note: if there is no listener for the given socket, */
        /* you'll get -ECONNREFUSED (-111) from sock_sendmsg.  */

        printk ("attocfg: error sending event message (%d, %d)\n",
                res [0], res [1]);
    }
#endif
}


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

    FUNCTION:   void handle_vda_event_queue (unsigned long unused)

    PURPOSE:    Clean off the VDA event queue.  This is a task that does
                not run in interrupt context.

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

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
void handle_vda_event_queue (void *unused)
#else
void handle_vda_event_queue (struct work_struct *work)
#endif
{
    unsigned long flags;

    /* Take the events off the queue in order and send them. */

    spin_lock_irqsave (&vda_event_lock, flags);

    while (1)
    {
        PVDA_AE_Q curr;

        curr = vda_event_queue;

        if (curr == NULL)
        {
            spin_unlock_irqrestore (&vda_event_lock, flags);
            break;
        }

        vda_event_queue = curr->next;

        spin_unlock_irqrestore (&vda_event_lock, flags);

        send_vda_event (curr->data, curr->length);

        spin_lock_irqsave (&vda_event_lock, flags);
        curr->next = vda_event_free;
        vda_event_free = curr;
    }
}


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

    FUNCTION:   int init_vda_event_queue (void)

    PURPOSE:    Initialize the VDA event queue

    RETURNS:    1 on success, 0 on failure

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

int init_vda_event_queue (void)
{
    unsigned long flags;
    PVDA_AE_Q queue;
    int alloc_size = sizeof (VDA_AE_Q) * VDA_AE_COUNT;
    int i;

    queue = vmalloc (alloc_size);

    if (!queue)
    {
        printk ("attocfg: could not alloc %d bytes for VDA event queue\n",
                alloc_size);

        return 0;
    }

    spin_lock_irqsave (&vda_event_lock, flags);

    if (vda_events_initialized)
    {
        printk ("attocfg: double initialization of VDA event queue detected\n");
error_exit:
        spin_unlock_irqrestore (&vda_event_lock, flags);
        vfree (queue);
        return 0;
    }

    /* Sanity check, all queues should be empty */

    if (vda_event_free || vda_event_queue)
    {
        printk ("attocfg: VDA event queue error during initialization (%p, %p)\n",
                vda_event_free, vda_event_queue);
        goto error_exit;
    }

    vda_events_initialized = 1;
    vda_event_memory = queue;

    /* Fill in the free VDA event queue */

    vda_event_free = queue;

    for (i = 0; i < VDA_AE_COUNT - 1; i++, queue++)
        queue->next = queue + 1;

    queue->next = NULL;

    spin_unlock_irqrestore (&vda_event_lock, flags);

    return 1;
}


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

    FUNCTION:   int free_vda_event_queue (void)

    PURPOSE:    Free (uninitialize) the VDA event queue

    RETURNS:    1 on success, 0 on failure

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

int free_vda_event_queue (void)
{
    unsigned long flags;
    void *addr_to_free;

    spin_lock_irqsave (&vda_event_lock, flags);

    if (!vda_events_initialized)
    {
        /* This is ok, we could get called even if the queue is uninited .*/
error_exit:
        spin_unlock_irqrestore (&vda_event_lock, flags);
        return 0;
    }

    /* Sanity check, all queues should be empty */

    if (vda_event_memory == NULL)
    {
        printk ("attocfg: VDA event queue memory NULL even though queue is initialized\n");
        goto error_exit;
    }

    /* Cannot vfree with interrupts disabled, so wait until we release spinlock. */

    addr_to_free = vda_event_memory;

    vda_event_memory = NULL;
    vda_event_free   = NULL;
    vda_event_queue  = NULL;
    vda_events_initialized = 0;

    spin_unlock_irqrestore (&vda_event_lock, flags);

    vfree (addr_to_free);

    return 1;
}


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

    FUNCTION:   int fire_vda_event (void *data, int len)

    PURPOSE:    Queue the specified data to be sent out the VDA event ports

    RETURNS:    1 on success, 0 on failure

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

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
DECLARE_WORK (vda_event_task, handle_vda_event_queue, NULL);
#else
DECLARE_WORK (vda_event_task, handle_vda_event_queue);
#endif

int fire_vda_event (void *data, int len)
{
    unsigned long flags;
    PVDA_AE_Q event;

    if (len > VDA_AE_MAX_LENGTH)
    {
        printk ("attocfg: illegal VDA event length of %d\n", len);
        return 0;
    }

    spin_lock_irqsave (&vda_event_lock, flags);

    if (!vda_events_initialized)
    {
        spin_unlock_irqrestore (&vda_event_lock, flags);
        printk ("attocfg: fire_vda_event called with an uninitialized queue\n");
        return 0;
    }

    event = vda_event_free;

    if (!event)
    {
        spin_unlock_irqrestore (&vda_event_lock, flags);
        printk ("attocfg: out of VDA events\n");
        return 0;
    }

    vda_event_free = event->next;

    event->next   = NULL;
    event->length = len;
    memcpy (event->data, data, len);

    /* To preserve event ordering, place this at the end of the queue */

    if (vda_event_queue == NULL)
        vda_event_queue = event;
    else
    {
        PVDA_AE_Q curr = vda_event_queue;

        while (curr->next)
            curr = curr->next;

        curr->next = event;
    }

    spin_unlock_irqrestore (&vda_event_lock, flags);

    schedule_work (&vda_event_task);

    return 1;
}


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

    FUNCTION:   void cleanup_vda_event_sockets (void)

    PURPOSE:    Cleans up the sockets for VDA event notification

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

void cleanup_vda_event_sockets (void)
{
    int i;

    for (i = 0; i < 2; i++)
    {
        if (event_socket [i])
        {
            sock_release (event_socket [i]);
            event_socket [i] = NULL;
        }
    }
}


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

    FUNCTION:   int init_vda_event_sockets (void)

    PURPOSE:    Initializes the sockets for VDA event notification

    RETURNS:    1 on success, 0 on failure

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

int init_vda_event_sockets (void)
{
    int sock_num;

    if (!init_vda_event_queue ())
        return 0;

    for (sock_num = 0; sock_num < 2; sock_num++)
    {
        int res;
        struct sockaddr_in sin;

        if (event_socket [sock_num])
        {
            printk ("attocfg: tried to re-init event sockets!\n");
            return 0;
        }

        res = sock_create (AF_INET, SOCK_DGRAM, 0, &event_socket [sock_num]);

        if (res < 0)
        {
            printk ("attocfg: could not create event socket %d (%d)\n",
                    sock_num, res);
            cleanup_vda_event_sockets ();
            return 0;
        }

        memset (&sin, 0, sizeof (sin));
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
        sin.sin_port = htons (ATTO_EVENT_PORT + sock_num);

        res = event_socket [sock_num]->ops->connect (event_socket [sock_num],
                                                     (struct sockaddr *) &sin,
                                                     sizeof (sin),
                                                     0);

        if (res < 0)
        {
            printk ("attocfg:  could not bind event socket %d (%d)\n",
                    sock_num, res);
            cleanup_vda_event_sockets ();
            return 0;
        }
    }

    return 1;
}


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

    FUNCTION:   void *get_reg_symbol_by_type (int type)
                void *put_reg_symbol_by_type (int type)

    PURPOSE:    get - References the registration function for the given type
                put - Releases the registration function for the given type

    ARGUMENTS:  The type of ATTO HBA driver to look for

    RETURNS:    Pointer to the driver's registration function

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

void *get_put_reg_symbol_by_type (int type, int put)
{
    /* Unlike inter_module_get, symbol_get/put does not take a      */
    /* string as its argument. Instead, you must give it the actual */
    /* symbol you wish to get. That's the reason for the following  */
    /* goofy logic. Also, because of the way symbol_get is defined, */
    /* you can't use a #define for the symbol name here.            */

    if (type == ATTO_DRVR_CELERITYFC)
    {
        if (put)
        {
            symbol_put (celerityfc_attocfg_register);            
            return NULL;
        }
        else
            return symbol_get (celerityfc_attocfg_register);
    }
    else if (type == ATTO_DRVR_ESASRAID)
    {
        if (put)
        {
            symbol_put (esasraid_attocfg_register);            
            return NULL;
        }
        else
            return symbol_get (esasraid_attocfg_register);
    }
    else if (type == ATTO_DRVR_EXPRESS2)
    {
        if (put)
        {
            symbol_put (express2_attocfg_register);            
            return NULL;
        }
        else
            return symbol_get (express2_attocfg_register);
    }
    else if (type == ATTO_DRVR_ESASHBA)
    {
        if (put)
        {
            symbol_put (esashba_attocfg_register);            
            return NULL;
        }
        else
            return symbol_get (esashba_attocfg_register);
    }
    else if (type == ATTO_DRVR_CELERITY8FC)
    {
        if (put)
        {
            symbol_put (celerity8fc_attocfg_register);            
            return NULL;
        }
        else
            return symbol_get (celerity8fc_attocfg_register);
    }
    else if (type == ATTO_DRVR_ESAS2HBA)
    {
        if (put)
        {
            symbol_put (esas2hba_attocfg_register);            
            return NULL;
        }
        else
            return symbol_get (esas2hba_attocfg_register);
    }

    printk ("attocfg: bug! (%d)\n", type);
    return NULL;
}

#define get_reg_symbol_by_type(_t) get_put_reg_symbol_by_type (_t, 0)
#define put_reg_symbol_by_type(_t) get_put_reg_symbol_by_type (_t, 1)


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

    FUNCTION:   void scan_for_hba_drivers (void)

    PURPOSE:    Scans for loaded ATTO HBA drivers and connects to them.

    ARGUMENTS:  none

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

void scan_for_hba_drivers (void)
{
    PATTOCFG_HBA_DRVR this_drvr;

    /* Find ATTO HBA drivers. */

    for (this_drvr = hba_drvr;
         this_drvr->type != ATTO_DRVR_NONE;
         this_drvr++)
    {
        attocfg_register_callback register_drvr;

        if (this_drvr->attocfg_unregister)
            continue;

        register_drvr = (attocfg_register_callback) 
                         get_reg_symbol_by_type (this_drvr->type);

        if (register_drvr)
        {
            int num_registered;

            /* Attempt to register the HBA driver. */

            ATTOCFG_REGISTER reg;

            memset (&reg,
                    0,
                    sizeof (ATTOCFG_REGISTER));

            reg.version = ATTOCFG_VERSION;
            reg.context = this_drvr;
            reg.attocfg_register_host  = attocfg_register_host;
            reg.init_vda_event_sockets = init_vda_event_sockets;
            reg.fire_vda_event         = fire_vda_event;

            /* Note that the HBA driver may fail the registration */
            /* after registering some hosts.  This is perfectly   */
            /* ok; the hosts will get cleaned up in clean_module. */

            num_registered = (*register_drvr) (&reg);

            if (num_registered > 0)
            {
                /* Registered at least one, save the unregister callback. */

                this_drvr->attocfg_unregister = reg.attocfg_unregister;
            }
            else
            {

                /* Nothing registered, release the symbol & check for error. */
                
                put_reg_symbol_by_type (this_drvr->type);

                if (num_registered < 0)
                {
                    printk ("attocfg: registration with %d failed (%d)\n",
                            this_drvr->type, num_registered);

                    if (reg.version != ATTOCFG_VERSION)
                    {
                        printk ("attocfg: there appears to be a version "
                                "mismatch, you probably need to update "
                                "your ATTO driver(s)\n");
                    }
                }
            }
            
        }
    }
}


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

    FUNCTION:   int attocfg_init (void)

    PURPOSE:    Called when the module loads.

    ARGUMENTS:  none

    RETURNS:    0 on success
                -1 on error

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

static int __init attocfg_init (void)
{
    spin_lock_init (&vda_event_lock);

    scan_for_hba_drivers ();

#ifdef CONFIG_PROC_FS
    /* Register as a char driver and stick a node under "/proc/attocfg" */

    attocfg_proc_major = register_chrdev (0, "attocfg", &attocfg_proc_fops);

    if (attocfg_proc_major > 0)
    {
        struct proc_dir_entry *pde;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
        pde = create_proc_entry ("attocfg", S_IWUSR, &proc_root);
#else
        pde = create_proc_entry ("attocfg", S_IWUSR, NULL);
#endif

        if (!pde)
            attocfg_proc_major = -1;
        else
            pde->proc_fops = &attocfg_proc_fops;
    }
#endif

    return 0;
}


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

    FUNCTION:   void attocfg_exit (void)

    PURPOSE:    Called when module unloads.

    ARGUMENTS:  none

    RETURNS:    Nothing

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

static void __exit attocfg_exit (void)
{
    PATTOCFG_HBA_DRVR this_drvr;

    for (this_drvr = hba_drvr;
         this_drvr->type != ATTO_DRVR_NONE;
         this_drvr++)
    {
        PATTOCFG_HOST host = this_drvr->hosts;

        /* Free registered hosts. */

        while (host)
        {
            PATTOCFG_HOST next = host->next;

            if (host->handlers.hba_nvram_handler)
            {
                sysfs_remove_bin_file (host->kobj, &bin_attr_default_nvram);
                sysfs_remove_bin_file (host->kobj, &bin_attr_live_nvram);
            }

            if (host->handlers.hba_fw_handler)
                sysfs_remove_bin_file (host->kobj, &bin_attr_fw);

            if (host->handlers.hba_vda_handler)
                sysfs_remove_bin_file (host->kobj, &bin_attr_vda);

            if (host->handlers.hba_fs_handler)
                sysfs_remove_bin_file (host->kobj, &bin_attr_fs);

            vfree (host);
            host = next;
        }

        if (this_drvr->attocfg_unregister)
        {
            (*this_drvr->attocfg_unregister) ();
            this_drvr->attocfg_unregister = NULL;
            put_reg_symbol_by_type (this_drvr->type);
        }
    }

    free_vda_event_queue ();
    cleanup_vda_event_sockets ();

#ifdef CONFIG_PROC_FS
    if (attocfg_proc_major > 0)
    {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
        remove_proc_entry ("attocfg", &proc_root);
#else
        remove_proc_entry ("attocfg", NULL);
#endif

        unregister_chrdev (attocfg_proc_major, "attocfg");
        attocfg_proc_major = 0;
    }
#endif
}

module_init(attocfg_init);
module_exit(attocfg_exit);
