NetBSD-5.0.2/dist/iscsi/src/driver.c

Compare this file to the similar file:
Show the results in this format:

/*
 * IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. By downloading, copying, installing or
 * using the software you agree to this license. If you do not agree to this license, do not download, install,
 * copy or use the software. 
 *
 * Intel License Agreement 
 *
 * Copyright (c) 2000, Intel Corporation
 * All rights reserved. 
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that
 * the following conditions are met: 
 *
 * -Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *  following disclaimer. 
 *
 * -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *  following disclaimer in the documentation and/or other materials provided with the distribution. 
 *
 * -The name of Intel Corporation may not be used to endorse or promote products derived from this software
 *  without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE. 
 */

/* 
 * Intel SCSI device driver for iSCSI
 */

#ifdef __linux__
#include <linux/blk.h>
#include <linux/string.h>
#include <scsi.h>
#include <hosts.h>
#include <sd.h>
#endif

#include "iscsiutil.h"
#include "util.c"
#include "driver.h"
#include "iscsi.h"
#include "iscsi.c"
#include "tests.h"
#include "tests.c"
#include "osd_ops.h"
#include "osd_ops.c"
#include "parameters.h"
#include "parameters.c"
#include "initiator.h"
#include "initiator.c"

/*
 * Version-specific include files
 */

#if LINUX_VERSION_CODE >= LinuxVersionCode(2,4,0)
#include <linux/in.h>
#else
struct proc_dir_entry iscsi_proc_dir = {PROC_SCSI_NOT_PRESENT, 5, "iscsi", S_IFDIR | S_IRUGO | S_IXUGO, 2};
#endif

#ifdef MODULE
Scsi_Host_Template driver_template = ISCSI;
#include "scsi_module.c"
#endif

/*
 * Globals
 */

static initiator_cmd_t *g_cmd;
static iscsi_queue_t g_cmd_q;
static struct iovec **g_iov;
static iscsi_queue_t g_iovec_q;
static iscsi_driver_stats_t g_stats;

/*
 * Definitions
 */

/* 
 * Starting with kernel 2.4.10, we define a license string. This source is under BSD License. 
 * Consult <linux/include/linux/module.h> for details
 */
 
MODULE_AUTHOR("Intel Corporation, <http://www.intel.com>");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,10) 
MODULE_LICENSE("Dual BSD/GPL"); /*  This source is under BSD License. This is the closest ident that module.h provides  */
#  endif 

/* 
 * Private
 */

static int driver_init(void) {
  int i;

  iscsi_trace(TRACE_SCSI_DEBUG, __FILE__, __LINE__, "initializing iSCSI driver\n");
  if ((g_cmd=iscsi_malloc_atomic(sizeof(initiator_cmd_t)*CONFIG_INITIATOR_QUEUE_DEPTH))==NULL) {
    iscsi_trace_error("iscsi_malloc_atomic() failed\n");
    return -1;
  }
  if ((g_iov=iscsi_malloc_atomic(sizeof(struct iovec*)*CONFIG_INITIATOR_QUEUE_DEPTH))==NULL) {
    iscsi_trace_error("iscsi_malloc_atomic() failed\n");
    iscsi_free_atomic(g_cmd);
    return -1;
  }
  for (i=0; i<CONFIG_INITIATOR_QUEUE_DEPTH; i++) {
    g_iov[i] = NULL;
    g_cmd[i].ptr = NULL;
  }
#define DI_ERROR {                                                       \
      for (i=0; i<CONFIG_INITIATOR_QUEUE_DEPTH; i++) {                   \
        if (g_cmd[i].ptr != NULL) iscsi_free_atomic(g_cmd[i].ptr);       \
      }                                                                  \
      for (i=0; i<CONFIG_INITIATOR_QUEUE_DEPTH; i++) {                   \
        if (g_iov[i] != NULL) iscsi_free_atomic(g_iov[i]);               \
      }                                                                  \
      iscsi_free_atomic(g_iov);                                          \
      iscsi_free_atomic(g_cmd);                                          \
      return -1;                                                         \
}
  for (i=0; i<CONFIG_INITIATOR_QUEUE_DEPTH; i++) {
    if ((g_iov[i]=iscsi_malloc_atomic(sizeof(struct iovec)*SG_ALL))==NULL) {
      iscsi_trace_error("iscsi_malloc_atomic() failed\n");
      DI_ERROR;
    }
  }
  if (iscsi_queue_init(&g_cmd_q, CONFIG_INITIATOR_QUEUE_DEPTH)!=0) {
    iscsi_trace_error("iscsi_queue_init() failed\n");
    DI_ERROR;
  }
  if (iscsi_queue_init(&g_iovec_q, CONFIG_INITIATOR_QUEUE_DEPTH)!=0) {
    iscsi_trace_error("iscsi_queue_init() failed\n");
    DI_ERROR;
  }

  for (i=0; i<CONFIG_INITIATOR_QUEUE_DEPTH; i++) {
    if ((g_cmd[i].ptr = iscsi_malloc_atomic(sizeof(iscsi_scsi_cmd_args_t)))==NULL) {
      iscsi_trace_error("iscsi_malloc_atomic() failed\n");
      DI_ERROR;
    }
    g_cmd[i].type = ISCSI_SCSI_CMD;
    if (iscsi_queue_insert(&g_cmd_q, &g_cmd[i])!=0) {
      iscsi_trace_error("iscsi_queue_insert() failed\n");
      DI_ERROR;
    }
    if (iscsi_queue_insert(&g_iovec_q, g_iov[i])!=0) {
      iscsi_trace_error("iscsi_queue_insert() failed\n");
      DI_ERROR;
    }
  }
  memset(&g_stats, 0, sizeof(g_stats));
  iscsi_spin_init(&g_stats.lock);
  if (initiator_init()!=0) {
    iscsi_trace_error("initiator_init() failed\n");
    DI_ERROR;
  }
  iscsi_trace(TRACE_SCSI_DEBUG, "iSCSI initialization complete\n");
  return 0;
}

static int driver_shutdown(void) {
  int i;

  iscsi_trace(TRACE_SCSI_DEBUG, "shutting down iSCSI driver\n");
  if (initiator_shutdown()!=0) {
    iscsi_trace_error("initiator_shutdown() failed\n");
    return -1;
  }
  iscsi_spin_destroy(&g_stats.lock);
  iscsi_queue_destroy(&g_iovec_q);
  iscsi_queue_destroy(&g_cmd_q);
  for (i=0; i<CONFIG_INITIATOR_QUEUE_DEPTH; i++) {
    iscsi_free_atomic(g_iov[i]);
    iscsi_free_atomic(g_cmd[i].ptr);
  }
  iscsi_free_atomic(g_cmd);
  iscsi_free_atomic(g_iov);
  iscsi_trace(TRACE_SCSI_DEBUG, "iSCSI driver shutdown complete\n");

  return 0;
}

/*
 * Public
 */

int iscsi_detect(Scsi_Host_Template *tptr) {
  struct Scsi_Host *ptr;
   
  iscsi_trace(TRACE_SCSI_DEBUG, "detecting iSCSI host\n");
  spin_unlock(&io_request_lock);
  if (driver_init()!=0) {
    iscsi_trace_error("driver_init() failed\n");
    spin_lock(&io_request_lock);
    return 0; /*No 'SCSI' host detected, return 0 */
   	
  }
  ptr = scsi_register(tptr, 0);
  ptr->max_id = CONFIG_INITIATOR_NUM_TARGETS;
  ptr->max_lun = CONFIG_DRIVER_MAX_LUNS;
  ptr->max_cmd_len = 255;
  iscsi_trace(TRACE_SCSI_DEBUG, "iSCSI host detected\n");
   spin_lock(&io_request_lock);
  return 1; 
}

int iscsi_release(struct Scsi_Host *host) {
  iscsi_trace(TRACE_SCSI_DEBUG, "releasing iSCSI host\n");
  driver_shutdown();
  iscsi_trace(TRACE_SCSI_DEBUG, "iSCSI host released\n");
  return 0;
}

int iscsi_bios_param(Disk *disk, kdev_t dev, int *ip) {
  ip[0] = 32;                                  /*  heads */
  ip[1] = 63;                                  /*  sectors */
  if((ip[2] = disk->capacity >> 11) > 1024) {  /*  cylinders, test for big disk */
    ip[0] = 255;                               /*  heads */
    ip[1] = 63;                                /*  sectors */
    ip[2] = disk->capacity / (255 * 63);       /*  cylinders */
  }
  iscsi_trace(TRACE_SCSI_DEBUG, "%u sectors, H/S/C: %u/%u/%u\n", disk->capacity, ip[0], ip[1], ip[2]);
  return 0;
}

int iscsi_command(Scsi_Cmnd *SCpnt) {
  iscsi_trace(TRACE_SCSI_DEBUG, "0x%p: op 0x%x, chan %i, target %i, lun %i, bufflen %i, sg %i\n", 
        SCpnt, SCpnt->cmnd[0], SCpnt->channel, SCpnt->target, SCpnt->lun, 
        SCpnt->request_bufflen, SCpnt->use_sg);
  iscsi_trace_error("NOT IMPLEMENTED\n");
  return -1;
}

int iscsi_done(void *ptr) {
  initiator_cmd_t *cmd = (initiator_cmd_t *) ptr;
  iscsi_scsi_cmd_args_t *scsi_cmd = (iscsi_scsi_cmd_args_t *) cmd->ptr;
  Scsi_Cmnd *SCpnt = (Scsi_Cmnd *) cmd->callback_arg;
  unsigned long flags = 0;
  if (SCpnt==0) {
	  return 0;
  } 
  if (cmd->status==0) {
    SCpnt->result = scsi_cmd->status;
  } else {
    SCpnt->result = -1;
  } 
  iscsi_trace(TRACE_SCSI_DEBUG, "scsi_arg 0x%p SCpnt 0x%p op 0x%x done (result %i)\n",
        scsi_cmd, SCpnt, SCpnt->cmnd[0], SCpnt->result);
  if ((scsi_cmd->input)&&(scsi_cmd->output)) {
    iscsi_trace_error("bidi xfers not implemented\n");
    return -1;
  } else if (scsi_cmd->input) {
    iscsi_spin_lock_irqsave(&g_stats.lock, &flags);
    if ((g_stats.rx+SCpnt->request_bufflen)<g_stats.rx) {
      g_stats.rx_overflow++;
    }
    g_stats.rx += SCpnt->request_bufflen;
    g_stats.rx_queued -= SCpnt->request_bufflen;
    if (g_stats.num_rx_queued) g_stats.num_rx_queued--;
    g_stats.num_rx++;
    iscsi_spin_unlock_irqrestore(&g_stats.lock, &flags);
  } else if (scsi_cmd->output) {
    iscsi_spin_lock_irqsave(&g_stats.lock, &flags);
    if ((g_stats.tx+SCpnt->request_bufflen)<g_stats.tx) {
      g_stats.tx_overflow++;
    }
    g_stats.tx += SCpnt->request_bufflen;
    g_stats.tx_queued -= SCpnt->request_bufflen;
    if (g_stats.num_tx_queued) g_stats.num_tx_queued--;
    g_stats.num_tx++;
    iscsi_spin_unlock_irqrestore(&g_stats.lock, &flags);
  }
  if (SCpnt->host_scribble) {
    unsigned char *hs;
    hs = SCpnt->host_scribble;
    SCpnt->host_scribble = NULL; /*  for abort */
    if (iscsi_queue_insert(&g_iovec_q, hs)!=0) {
      iscsi_trace_error("iscsi_queue_insert() failed\n");
      return -1;
    }  
  }
  iscsi_free_atomic(scsi_cmd->ahs);
  if (iscsi_queue_insert(&g_cmd_q, cmd)!=0) {
    iscsi_trace_error("iscsi_queue_insert() failed\n");
  cmd->callback_arg = NULL;    /*  for abort */
    return -1;
  } 
  cmd->callback_arg = NULL;    /*  for abort */
  if (SCpnt->result==0) {
    SCpnt->scsi_done(SCpnt);
  } else {
    iscsi_trace_error("SCSI cmd 0x%x failed at iSCSI level (ignoring)\n", SCpnt->cmnd[0]);
  }
  return 0;
}

int iscsi_queuecommand(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)) {
  initiator_cmd_t *cmd;
  iscsi_scsi_cmd_args_t *scsi_cmd;
  unsigned char *data;
  unsigned length, trans_len;
  int input, output;
  unsigned long flags =0;
  /*  Tagged command queuing is handled from within this SCSI driver. */
  if ((SCpnt->device->tagged_supported)&&(SCpnt->device->tagged_queue)) {
    SCpnt->tag = SCpnt->device->current_tag++;
  }

  spin_unlock(&io_request_lock);

  iscsi_trace(TRACE_SCSI_DEBUG, "SCpnt %p: tid %i lun %i op 0x%x tag %u len %i sg %i buff 0x%p\n",
        SCpnt, SCpnt->target, SCpnt->lun, SCpnt->cmnd[0], SCpnt->tag, SCpnt->request_bufflen, 
        SCpnt->use_sg, SCpnt->buffer);


  /*  Determine direction of data transfer */

  trans_len = length = output = input = 0;
  if ((SCpnt->cmnd[0]!=TEST_UNIT_READY)&&(SCpnt->request_bufflen)) {
    if (SCpnt->sc_data_direction == SCSI_DATA_WRITE) {
      output = 1; input = 0;
      length = trans_len = SCpnt->request_bufflen;
      iscsi_spin_lock_irqsave(&g_stats.lock, &flags);
      g_stats.num_tx_queued++;
      g_stats.tx_queued += trans_len;
      iscsi_spin_unlock_irqrestore(&g_stats.lock, &flags);
    } else if (SCpnt->sc_data_direction == SCSI_DATA_READ) {
      length = output = 0; input = 1;
      trans_len = SCpnt->request_bufflen;
      iscsi_spin_lock_irqsave(&g_stats.lock, &flags);
      g_stats.num_rx_queued++;
      g_stats.rx_queued += trans_len;
      iscsi_spin_unlock_irqrestore(&g_stats.lock, &flags);
    }
  } 

  /*  Convert scatterlist to iovec */

  if (SCpnt->use_sg) {
    struct scatterlist *sg = (struct scatterlist *) SCpnt->buffer;
    struct iovec *iov;
    int i;

    iov = iscsi_queue_remove(&g_iovec_q);
    if (iov == NULL) {
      iscsi_trace_error("iscsi_queue_remove() failed\n");
      spin_lock(&io_request_lock);
      return -1;
    }
    for (i=0; i<SCpnt->use_sg; i++) {
      iov[i].iov_base = sg[i].address; 
      iov[i].iov_len = sg[i].length;
    }
    data = SCpnt->host_scribble = (unsigned char *)iov;
  } else {
    data = SCpnt->buffer;
    SCpnt->host_scribble = NULL;
  } 

  /*  Get free cmd structure */

  if ((cmd=iscsi_queue_remove(&g_cmd_q))==NULL) {
    iscsi_trace_error("iscsi_queue_remove() failed\n");
    spin_lock(&io_request_lock);	
    return -1;
  } 
  scsi_cmd = (iscsi_scsi_cmd_args_t *) cmd->ptr;
  memset(scsi_cmd, 0, sizeof(*scsi_cmd));
  scsi_cmd->send_data = output?data:0;
  scsi_cmd->send_sg_len = output?SCpnt->use_sg:0;
  scsi_cmd->recv_data = input?data:NULL;
  scsi_cmd->recv_sg_len = input?SCpnt->use_sg:0;
  scsi_cmd->input = input;
  scsi_cmd->output = output;
  scsi_cmd->length = length;
  scsi_cmd->lun = SCpnt->lun;
  scsi_cmd->lun = scsi_cmd->lun << 32;
  scsi_cmd->trans_len = trans_len;
  scsi_cmd->cdb = SCpnt->cmnd;

  /*  AHS for CDBs larget than 16 bytes */

  if (SCpnt->cmd_len>16) {
    iscsi_trace(TRACE_ISCSI_DEBUG, "creating AHS for extended CDB (%i bytes)\n", SCpnt->cmd_len);
    if ((scsi_cmd->ahs=iscsi_malloc_atomic(SCpnt->cmd_len-16))==NULL) {
      iscsi_trace_error("iscsi_malloc_atomic() failed\n");
      spin_lock(&io_request_lock);
      return -1;
    }
    memset(scsi_cmd->ahs, 0, 4);
    *((uint16_t *)scsi_cmd->ahs) = HTONS(SCpnt->cmd_len-15);      /*  AHS length */
    scsi_cmd->ahs[2] = 0x01;                                      /*  Type */
    memcpy(scsi_cmd->ahs+4, SCpnt->cmnd+16, SCpnt->cmd_len-16);   /*  Copy in remaining CDB */
    scsi_cmd->ahs_len = SCpnt->cmd_len-16;
  }

  SCpnt->scsi_done = done;     /*  The midlayer's callback called in iscsi_done */
  SCpnt->result = 0x02;        /*  Default to a check condition */
  cmd->callback = iscsi_done;  /*  This driver's callback called by initiator library */
  cmd->callback_arg = SCpnt;
  cmd->isid = SCpnt->target;
  if (initiator_enqueue(cmd)!=0) {
    iscsi_trace_error("initiator_enqueue() failed\n");
    if (SCpnt->cmd_len>16) iscsi_free_atomic(scsi_cmd->ahs);
    spin_lock(&io_request_lock);
    return -1;
  }
    spin_lock(&io_request_lock);
  return 0;
}

int iscsi_proc_info (char *buffer, char **start, off_t offset, int length, int hostno, int writing) {
  unsigned char *info = NULL;
  uint32_t	infolen = 8192;
  int len = 0;

  iscsi_trace(TRACE_SCSI_DEBUG, "buffer = 0x%p, offset %u, length = %i, hostno %i, writing %i\n",
        buffer, (unsigned) offset, length, hostno, writing);

  /* writing resets counters */

  if (writing) {
    iscsi_spin_lock(&g_stats.lock);
    g_stats.num_tx = g_stats.num_tx_queued = 0;
    g_stats.num_rx = g_stats.num_rx_queued = 0;
    g_stats.tx_queued = g_stats.tx = g_stats.tx_overflow = g_stats.tx_error = 0;
    g_stats.rx_queued = g_stats.rx = g_stats.rx_overflow = g_stats.rx_error = 0;
    g_stats.aborts_success = g_stats.aborts_failed = 0;
    g_stats.device_resets = g_stats.bus_resets = g_stats.host_resets = 0;
    iscsi_spin_unlock(&g_stats.lock);
    return 0;
  } else {
    if ((info=iscsi_malloc_atomic(infolen))==NULL) {
      iscsi_trace_error("iscsi_malloc_atomic() failed\n");
      return -1;
    }
    len += snprintf(info, infolen, "%s\n\n", driver_template.name);
    len += snprintf(&info[len], infolen - len, "Write file to reset counters (e.g., \"echo reset > /proc/scsi/iscsi/2\").\n\n");
    len += snprintf(&info[len], infolen - len, "--------------------\n");
    len += snprintf(&info[len], infolen - len, "Driver Configuration\n");

    len += snprintf(&info[len], infolen - len, "--------------------\n\n");
    len += snprintf(&info[len], infolen - len, "  CONFIG_INITIATOR_NUM_TARGETS: %u\n", CONFIG_INITIATOR_NUM_TARGETS);
    len += snprintf(&info[len], infolen - len, "  CONFIG_INITIATOR_QUEUE_DEPTH: %u\n\n", CONFIG_INITIATOR_QUEUE_DEPTH);
    len += snprintf(&info[len], infolen - len, "---------------\n");
    len += snprintf(&info[len], infolen - len, "SCSI Statistics\n");
    len += snprintf(&info[len], infolen - len, "---------------\n\n");
    len += snprintf(&info[len], infolen - len, "  Tx:\n");
    len += snprintf(&info[len], infolen - len, "    queued:          %u\n", g_stats.num_tx_queued);
    len += snprintf(&info[len], infolen - len, "    completed:       %u\n", g_stats.num_tx);
    len += snprintf(&info[len], infolen - len, "    avg size:        %u\n", g_stats.num_tx?(g_stats.tx/g_stats.num_tx):0);
    len += snprintf(&info[len], infolen - len, "    total bytes:     %u MB\n", g_stats.tx/1048576 + g_stats.tx_overflow*4096);
    len += snprintf(&info[len], infolen - len, "    total overflow:  %u\n", g_stats.tx_overflow);
    len += snprintf(&info[len], infolen - len, "  Rx:\n");
    len += snprintf(&info[len], infolen - len, "    queued:          %u\n", g_stats.num_rx_queued);
    len += snprintf(&info[len], infolen - len, "    completed:       %u\n", g_stats.num_rx);
    len += snprintf(&info[len], infolen - len, "    avg size:        %u\n", g_stats.num_rx?(g_stats.rx/g_stats.num_rx):0);
    len += snprintf(&info[len], infolen - len, "    total bytes:     %u MB\n", g_stats.rx/1048576 + g_stats.rx_overflow*4096);
    len += snprintf(&info[len], infolen - len, "    total overflow:  %u\n", g_stats.rx_overflow);
    len += snprintf(&info[len], infolen - len, "  Errors:\n");
    len += snprintf(&info[len], infolen - len, "    aborts:          %u\n", g_stats.aborts_success);
    /* len += snprintf(&info[len], infolen - len, "    failed aborts:   %u\n", g_stats.aborts_failed); */
    len += snprintf(&info[len], infolen - len, "    device resets:   %u\n", g_stats.device_resets);
    len += snprintf(&info[len], infolen - len, "    bus resets:      %u\n", g_stats.bus_resets);
    len += snprintf(&info[len], infolen - len, "    host resets:     %u\n", g_stats.host_resets);
    len += snprintf(&info[len], infolen - len, "    Tx error bytes:  %u\n", g_stats.tx_error);
    len += snprintf(&info[len], infolen - len, "    Rx error bytes:  %u\n\n", g_stats.rx_error);
    len += snprintf(&info[len], infolen - len, "--------------------\n");
    len += snprintf(&info[len], infolen - len, "iSCSI Initiator Info\n");
    len += snprintf(&info[len], infolen - len, "--------------------\n\n");

    if ((len += initiator_info(&info[len], infolen, len))==-1) {
      iscsi_trace_error("initiator_info() failed\n");
      if (info != NULL) iscsi_free_atomic(info);
      return -1;
    }
    if (offset>len-1) {
      len = 0;
    } else if (offset+length>len-1) {
      len -= offset;
    } else {
      len = length;
    }
    *start = buffer;
    memcpy(buffer, info+offset, len);
    if (info != NULL) iscsi_free_atomic(info);
    return len;
  }
}

int iscsi_ioctl (Scsi_Device *dev, int cmd, void *argp) {
  int i;
  int lun = 0;

  /*  Run tests for each target */

  for (i=0; i<CONFIG_INITIATOR_NUM_TARGETS; i++) {
    if (test_all(i, lun)!=0) {
      iscsi_trace_error("test_all() failed\n");
      return -1;
    }
  }
  return 0;
}

void iscsi_select_queue_depths(struct Scsi_Host *host, Scsi_Device *scsi_devs) {
  struct scsi_device *device;

  for (device = scsi_devs; device; device = device->next) {
    if (device->host != host) {
      iscsi_trace_error("got device for different host\n");
      continue;
    }
    if (device->tagged_supported) {
      iscsi_trace(TRACE_SCSI_DEBUG, "target %i lun %i supports TCQ\n", device->id, device->lun);
      device->tagged_queue = 1;
      device->current_tag = 0;
      device->queue_depth = CONFIG_INITIATOR_QUEUE_DEPTH;
      iscsi_trace(TRACE_SCSI_DEBUG, "device queue depth set to %i\n", device->queue_depth);
    } else {
      iscsi_trace(TRACE_SCSI_DEBUG, "target %i lun %i does NOT support TCQ\n", device->id, device->lun);
      device->queue_depth = 1;
    }
  }
}

/*
 * Error Handling Routines
 */


int iscsi_abort_handler (Scsi_Cmnd *SCpnt) {
  iscsi_scsi_cmd_args_t *scsi_cmd;
  initiator_session_t *sess;
  int i;
  unsigned long flags;

  spin_unlock_irq(&io_request_lock);
  iscsi_trace_error("aborting SCSI cmd 0x%p (op 0x%x, tid %i, lun %i)\n", 
              SCpnt, SCpnt->cmnd[0], SCpnt->target, SCpnt->lun);


  for (i=0; i<CONFIG_INITIATOR_QUEUE_DEPTH; i++) {
   
    /*  Find the cmd ptr in g_cmd.  We look for the callback_arg that's equal */
    /*  to SCpnt. iscsi_done() sets callback_arg to NULL when a command */
    /*  completes.  So we know that any non_NULL callback_arg is associated */
    /*  with an outstanding command. */

    if (g_cmd[i].callback_arg == SCpnt) {

      /*  Abort the command */

      if (initiator_abort(&g_cmd[i])!=0) {
        iscsi_trace_error("initiator_abort() failed\n");
        spin_lock_irq(&io_request_lock);
        return FAILED;
      }

      /*  Update counters */

      scsi_cmd = (iscsi_scsi_cmd_args_t *) g_cmd[i].ptr;
      if ((scsi_cmd->input)&&(scsi_cmd->output)) {
        iscsi_trace_error("bidi xfers not implemented\n");
        spin_lock_irq(&io_request_lock);
        return FAILED;
      } else if (scsi_cmd->input) {
        iscsi_spin_lock_irqsave(&g_stats.lock,&flags);
        g_stats.rx_error += SCpnt->request_bufflen; 
        g_stats.rx_queued -= SCpnt->request_bufflen;
	iscsi_spin_unlock_irqrestore(&g_stats.lock, &flags);
      } else if (scsi_cmd->output) {
	iscsi_spin_lock_irqsave(&g_stats.lock, &flags);
        g_stats.tx_error += SCpnt->request_bufflen;
        g_stats.tx_queued -= SCpnt->request_bufflen;
	iscsi_spin_unlock_irqrestore(&g_stats.lock, &flags);
      }
      break;
    } 
  }

  /*  Destroy session */

  if (g_target[SCpnt->target].has_session) {
    sess = g_target[SCpnt->target].sess;

#if LINUX_VERSION_CODE >= LinuxVersionCode(2,4,0)
    if (in_interrupt()) {
      iscsi_trace_error("aborting within interrupt (killing Tx and Rx threads)\n");
#endif
      iscsi_trace_error("killing Tx and Rx threads\n");
      kill_proc(sess->rx_worker.pid, SIGKILL, 1);
      kill_proc(sess->tx_worker.pid, SIGKILL, 1);
      sess->tx_worker.state = 0;
      sess->rx_worker.state = 0;
#if LINUX_VERSION_CODE >= LinuxVersionCode(2,4,0)
    } else {
      iscsi_trace_error("aborting outside interrupt (gracefully ending Tx and Rx)\n");
    }
#endif

    iscsi_trace(TRACE_ISCSI_DEBUG, "destroying session\n");
    if (session_destroy_i(sess)!=0) {
      iscsi_trace_error("session_destroy() failed\n");
      g_stats.aborts_failed++;
      spin_lock_irq(&io_request_lock);
      return FAILED;
    }
  } else {
    iscsi_trace(TRACE_ISCSI_DEBUG, "no session\n");
  }

  g_stats.aborts_success++;

  iscsi_trace_error("successfully aborted SCSI cmd 0x%p (op 0x%x, tid %i, lun %i)\n",
              SCpnt, SCpnt->cmnd[0], SCpnt->target, SCpnt->lun);
  spin_lock_irq(&io_request_lock);
  return SUCCESS;
}

int iscsi_device_reset_handler (Scsi_Cmnd *SCpnt) {
  iscsi_trace_error("***********************\n");
  iscsi_trace_error("*** DEVICE %i RESET ***\n", SCpnt->target);
  iscsi_trace_error("***********************\n");
  g_stats.device_resets++;
  return SUCCESS;
}

int iscsi_bus_reset_handler (Scsi_Cmnd *SCpnt) {
  iscsi_trace_error("********************\n");
  iscsi_trace_error("*** BUS %i RESET ***\n", SCpnt->target);
  iscsi_trace_error("********************\n");
  g_stats.bus_resets++;
  return SUCCESS;
}

int iscsi_host_reset_handler(Scsi_Cmnd *SCpnt) {
  iscsi_trace_error("*********************\n");
  iscsi_trace_error("*** HOST RESET %i ***\n", SCpnt->target);
  iscsi_trace_error("*********************\n");
  g_stats.host_resets++;
  return SUCCESS;
}