lkml.org 
[lkml]   [2002]   [Oct]   [25]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[PATCH] 2/6 2.5.44 scsi multi-path IO - mid layer changes
    SCSI mid-layer multi-path IO changes

    scsi_ioctl.c | 62 +-
    scsi_lib.c | 348 ++++++++-----
    scsi_merge.c | 57 ++
    scsi_paths.c | 1499 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    4 files changed, 1827 insertions(+), 139 deletions(-)

    diff -Nru a/drivers/scsi/scsi_ioctl.c b/drivers/scsi/scsi_ioctl.c
    --- a/drivers/scsi/scsi_ioctl.c Fri Oct 25 11:26:48 2002
    +++ b/drivers/scsi/scsi_ioctl.c Fri Oct 25 11:26:48 2002
    @@ -129,10 +129,15 @@
    break;
    }
    default: /* Fall through for non-removable media */
    - printk("SCSI error: host %d id %d lun %d return code = %x\n",
    - dev->host->host_no,
    - dev->id,
    - dev->lun,
    + /*
    + * This is a bit klugey, since we rely on SRpnt
    + * to contain the scsi_cmnd for the IO.
    + */
    + printk("SCSI error: host %d channel %d id %d lun %d return code = %x\n",
    + SRpnt->sr_command->host->host_no,
    + SRpnt->sr_command->channel,
    + SRpnt->sr_command->target,
    + SRpnt->sr_command->lun,
    SRpnt->sr_result);
    printk("\tSense class %x, sense error %x, extended sense %x\n",
    sense_class(SRpnt->sr_sense_buffer[0]),
    @@ -214,6 +219,7 @@
    char *cmd_in;
    Scsi_Request *SRpnt;
    Scsi_Device *SDpnt;
    + struct Scsi_Host *SHpnt;
    unsigned char opcode;
    unsigned int inlen, outlen, cmdlen;
    unsigned int needed, buf_needed;
    @@ -223,7 +229,9 @@
    if (!sic)
    return -EINVAL;

    - if (dev->host->unchecked_isa_dma)
    + if ((SHpnt = scsi_get_host(dev)) == NULL)
    + return -ENODEV;
    + if (SHpnt->unchecked_isa_dma)
    gfp_mask |= GFP_DMA;

    /*
    @@ -395,10 +403,16 @@
    static int
    scsi_ioctl_get_pci(Scsi_Device * dev, void *arg)
    {
    + struct Scsi_Host *SHpnt;

    - if (!dev->host->pci_dev) return -ENXIO;
    - return copy_to_user(arg, dev->host->pci_dev->slot_name,
    - sizeof(dev->host->pci_dev->slot_name));
    + /*
    + * XXX this ioctl should be replaced by a host specific interface
    + */
    + if ((SHpnt = scsi_get_host(dev)) == NULL)
    + return -ENODEV;
    + if (!SHpnt->pci_dev) return -ENXIO;
    + return copy_to_user(arg, SHpnt->pci_dev->slot_name,
    + sizeof(SHpnt->pci_dev->slot_name));
    }


    @@ -410,6 +424,8 @@
    int scsi_ioctl(Scsi_Device * dev, int cmd, void *arg)
    {
    char scsi_cmd[MAX_COMMAND_SIZE];
    + struct scsi_path_id scsi_path;
    + struct Scsi_Host *SHpnt;

    /* No idea how this happens.... */
    if (!dev)
    @@ -430,15 +446,23 @@
    if (verify_area(VERIFY_WRITE, arg, sizeof(Scsi_Idlun)))
    return -EFAULT;

    - __put_user((dev->id & 0xff)
    - + ((dev->lun & 0xff) << 8)
    - + ((dev->channel & 0xff) << 16)
    - + ((dev->host->host_no & 0xff) << 24),
    + /*
    + * Just get first path, and use its
    + * id/lun/channel/host_no. This is fine for single path
    + * devices, or when no multi-path IO.
    + */
    + scsi_get_path(dev, &scsi_path);
    + __put_user((scsi_path.spi_id & 0xff)
    + + ((scsi_path.spi_lun & 0xff) << 8)
    + + ((scsi_path.spi_channel & 0xff) << 16)
    + + ((scsi_path.spi_shpnt->host_no & 0xff) << 24),
    &((Scsi_Idlun *) arg)->dev_id);
    - __put_user(dev->host->unique_id, &((Scsi_Idlun *) arg)->host_unique_id);
    + __put_user(scsi_path.spi_shpnt->unique_id, &((Scsi_Idlun *) arg)->host_unique_id);
    return 0;
    case SCSI_IOCTL_GET_BUS_NUMBER:
    - return put_user(dev->host->host_no, (int *) arg);
    + if ((SHpnt = scsi_get_host(dev)) == NULL)
    + return -ENODEV;
    + return put_user(SHpnt->host_no, (int *) arg);
    /*
    * The next two ioctls either need to go or need to be changed to
    * pass tagged queueing changes through the low level drivers.
    @@ -464,7 +488,9 @@
    dev->current_tag = 0;
    return 0;
    case SCSI_IOCTL_PROBE_HOST:
    - return ioctl_probe(dev->host, arg);
    + if ((SHpnt = scsi_get_host(dev)) == NULL)
    + return -ENODEV;
    + return ioctl_probe(SHpnt, arg);
    case SCSI_IOCTL_SEND_COMMAND:
    if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_RAWIO))
    return -EACCES;
    @@ -502,8 +528,10 @@
    return scsi_ioctl_get_pci(dev, arg);
    break;
    default:
    - if (dev->host->hostt->ioctl)
    - return dev->host->hostt->ioctl(dev, cmd, arg);
    + if ((SHpnt = scsi_get_host(dev)) == NULL)
    + return -ENODEV;
    + if (SHpnt->hostt->ioctl)
    + return SHpnt->hostt->ioctl(dev, cmd, arg);
    return -EINVAL;
    }
    return -EINVAL;
    diff -Nru a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
    --- a/drivers/scsi/scsi_lib.c Fri Oct 25 11:26:47 2002
    +++ b/drivers/scsi/scsi_lib.c Fri Oct 25 11:26:47 2002
    @@ -37,6 +37,7 @@
    #define __KERNEL_SYSCALLS__

    #include <linux/unistd.h>
    +#include <linux/slab.h>

    #include <asm/system.h>
    #include <asm/irq.h>
    @@ -46,6 +47,8 @@
    #include "hosts.h"
    #include <scsi/scsi_ioctl.h>

    +struct scsi_device *scsi_sdev_list;
    +
    /*
    * This entire source file deals with the new queueing code.
    */
    @@ -126,7 +129,14 @@
    SCpnt->serial_number = 0;
    SCpnt->serial_number_at_timeout = 0;
    SCpnt->flags = 0;
    +#if 0
    + /*
    + * FIXME - what do we do for multi-path? Not resetting this means that
    + * QUEUE_FULL and host busy cases will not be retried. There are
    + * probably other cases where we should also retry longer.
    + */
    SCpnt->retries = 0;
    +#endif

    SCpnt->abort_reason = 0;

    @@ -219,12 +229,14 @@
    * permutations grows as 2**N, and if too many more special cases
    * get added, we start to get screwed.
    */
    -void scsi_queue_next_request(request_queue_t * q, Scsi_Cmnd * SCpnt)
    +void scsi_queue_next_request(request_queue_t * q, Scsi_Cmnd * SCpnt,
    + struct Scsi_Host * SHpnt)
    {
    - int all_clear;
    unsigned long flags;
    Scsi_Device *SDpnt;
    - struct Scsi_Host *SHpnt;
    + /* see comment further below */
    + int all_clear;
    + struct scsi_traverse_hndl strav_hndl;

    ASSERT_LOCK(q->queue_lock, 0);

    @@ -249,7 +261,6 @@
    q->request_fn(q);

    SDpnt = (Scsi_Device *) q->queuedata;
    - SHpnt = SDpnt->host;

    /*
    * If this is a single-lun device, and we are currently finished
    @@ -261,7 +272,7 @@
    if (SDpnt->single_lun && blk_queue_empty(q) && SDpnt->device_busy ==0) {
    request_queue_t *q;

    - for (SDpnt = SHpnt->host_queue; SDpnt; SDpnt = SDpnt->next) {
    + scsi_for_each_host_sdev(&strav_hndl, SDpnt, SHpnt->host_no) {
    if (((SHpnt->can_queue > 0)
    && (SHpnt->host_busy >= SHpnt->can_queue))
    || (SHpnt->host_blocked)
    @@ -285,7 +296,7 @@
    */
    all_clear = 1;
    if (SHpnt->some_device_starved) {
    - for (SDpnt = SHpnt->host_queue; SDpnt; SDpnt = SDpnt->next) {
    + scsi_for_each_host_sdev(&strav_hndl, SDpnt, SHpnt->host_no) {
    request_queue_t *q;
    if ((SHpnt->can_queue > 0 && (SHpnt->host_busy >= SHpnt->can_queue))
    || (SHpnt->host_blocked)
    @@ -338,6 +349,7 @@
    request_queue_t *q = &SCpnt->device->request_queue;
    struct request *req = SCpnt->request;
    unsigned long flags;
    + struct Scsi_Host * SHpnt = SCpnt->host;

    ASSERT_LOCK(q->queue_lock, 0);

    @@ -353,7 +365,7 @@
    * Bleah. Leftovers again. Stick the leftovers in
    * the front of the queue, and goose the queue again.
    */
    - scsi_queue_next_request(q, SCpnt);
    + scsi_queue_next_request(q, SCpnt, SHpnt);
    return SCpnt;
    }

    @@ -375,7 +387,7 @@
    __scsi_release_command(SCpnt);

    if (frequeue)
    - scsi_queue_next_request(q, NULL);
    + scsi_queue_next_request(q, NULL, SHpnt);

    return NULL;
    }
    @@ -491,38 +503,7 @@
    */
    ASSERT_LOCK(q->queue_lock, 0);

    - /*
    - * Free up any indirection buffers we allocated for DMA purposes.
    - * For the case of a READ, we need to copy the data out of the
    - * bounce buffer and into the real buffer.
    - */
    - if (SCpnt->use_sg) {
    - struct scatterlist *sgpnt;
    -
    - sgpnt = (struct scatterlist *) SCpnt->buffer;
    - scsi_free_sgtable(SCpnt->buffer, SCpnt->sglist_len);
    - } else {
    - if (SCpnt->buffer != req->buffer) {
    - if (rq_data_dir(req) == READ) {
    - unsigned long flags;
    - char *to = bio_kmap_irq(req->bio, &flags);
    -
    - memcpy(to, SCpnt->buffer, SCpnt->bufflen);
    - bio_kunmap_irq(to, &flags);
    - }
    - kfree(SCpnt->buffer);
    - }
    - }
    -
    - /*
    - * Zero these out. They now point to freed memory, and it is
    - * dangerous to hang onto the pointers.
    - */
    - SCpnt->buffer = NULL;
    - SCpnt->bufflen = 0;
    - SCpnt->request_buffer = NULL;
    - SCpnt->request_bufflen = 0;
    -
    + scsi_cleanup_io(SCpnt);
    /*
    * Next deal with any sectors which we were able to correctly
    * handle.
    @@ -582,7 +563,7 @@
    */
    if (SCpnt->sense_buffer[12] == 0x04 &&
    SCpnt->sense_buffer[13] == 0x01) {
    - scsi_queue_next_request(q, SCpnt);
    + scsi_queue_next_request(q, SCpnt, SCpnt->host);
    return;
    }
    if ((SCpnt->sense_buffer[2] & 0xf) == UNIT_ATTENTION) {
    @@ -600,7 +581,8 @@
    * media change, so we just retry the
    * request and see what happens.
    */
    - scsi_queue_next_request(q, SCpnt);
    + scsi_queue_next_request(q, SCpnt,
    + SCpnt->host);
    return;
    }
    }
    @@ -620,7 +602,7 @@
    * This will cause a retry with a 6-byte
    * command.
    */
    - scsi_queue_next_request(q, SCpnt);
    + scsi_queue_next_request(q, SCpnt, SCpnt->host);
    result = 0;
    } else {
    SCpnt = scsi_end_request(SCpnt, 0, this_count);
    @@ -652,7 +634,7 @@
    * recovery reasons. Just retry the request
    * and see what happens.
    */
    - scsi_queue_next_request(q, SCpnt);
    + scsi_queue_next_request(q, SCpnt, SCpnt->host);
    return;
    }
    if (result) {
    @@ -661,10 +643,10 @@
    STpnt = scsi_get_request_dev(SCpnt->request);
    printk("SCSI %s error : host %d channel %d id %d lun %d return code = %x\n",
    (STpnt ? STpnt->name : "device"),
    - SCpnt->device->host->host_no,
    - SCpnt->device->channel,
    - SCpnt->device->id,
    - SCpnt->device->lun, result);
    + SCpnt->host->host_no,
    + SCpnt->channel,
    + SCpnt->target,
    + SCpnt->lun, result);

    if (driver_byte(result) & DRIVER_SENSE)
    print_sense("sd", SCpnt);
    @@ -730,6 +712,36 @@
    }

    /*
    + * Function: scsi_dec_check_host_busy
    + *
    + * Purpose: Decrement host_busy, and see if any action should be taken.
    + *
    + * Arguments: SHpnt and request q.
    + *
    + * Lock status: Assumes that queue_lock is held when called.
    + *
    + * Returns: Nothing
    + *
    + * Notes: The q arg and local flags are only needed for the locking hack.
    + */
    +void scsi_dec_check_host_busy (struct Scsi_Host *SHpnt, request_queue_t * q)
    +{
    + unsigned long flags;
    +
    + /* XXX gross lock hack */
    + if (SHpnt->host_lock != q->queue_lock)
    + spin_lock_irqsave(SHpnt->host_lock, flags);
    + SHpnt->host_busy--;
    + /*
    + * XXX check to see if the change in host_busy should kick off
    + * error handling. This is really only needed if we drop and then
    + * later lock queue_lock, but it is OK to always check it.
    + */
    + if (SHpnt->host_lock != q->queue_lock)
    + spin_unlock_irqrestore(SHpnt->host_lock, flags);
    +}
    +
    +/*
    * Function: scsi_request_fn()
    *
    * Purpose: Generic version of request function for SCSI hosts.
    @@ -754,8 +766,9 @@
    Scsi_Cmnd *SCpnt;
    Scsi_Request *SRpnt;
    Scsi_Device *SDpnt;
    - struct Scsi_Host *SHpnt;
    struct Scsi_Device_Template *STpnt;
    + struct scsi_path_id path;
    + int res;

    ASSERT_LOCK(q->queue_lock, 1);

    @@ -763,7 +776,6 @@
    if (!SDpnt) {
    panic("Missing device");
    }
    - SHpnt = SDpnt->host;

    /*
    * To start with, we keep looping until the queue is empty, or until
    @@ -771,69 +783,41 @@
    */
    while (1 == 1) {
    /*
    - * Check this again - each time we loop through we will have
    - * released the lock and grabbed it again, so each time
    - * we need to check to see if the queue is plugged or not.
    + * Each time we loop through we will have released the
    + * lock and grabbed it again, so each time we need to
    + * check to see if the queue is plugged or not.
    */
    - if (SHpnt->in_recovery || blk_queue_plugged(q))
    + if (blk_queue_plugged(q))
    return;
    -
    - if(SHpnt->host_busy == 0 && SHpnt->host_blocked) {
    - /* unblock after host_blocked iterates to zero */
    - if(--SHpnt->host_blocked == 0) {
    - SCSI_LOG_MLQUEUE(3, printk("scsi%d unblocking host at zero depth\n", SHpnt->host_no));
    - } else {
    - blk_plug_device(q);
    - break;
    - }
    - }
    - if(SDpnt->device_busy == 0 && SDpnt->device_blocked) {
    - /* unblock after device_blocked iterates to zero */
    - if(--SDpnt->device_blocked == 0) {
    - SCSI_LOG_MLQUEUE(3, printk("scsi%d (%d:%d) unblocking device at zero depth\n", SHpnt->host_no, SDpnt->id, SDpnt->lun));
    - } else {
    - blk_plug_device(q);
    - break;
    - }
    - }
    - /*
    - * If the device cannot accept another request, then quit.
    - */
    - if (SDpnt->device_blocked) {
    - break;
    - }
    - if ((SHpnt->can_queue > 0 && (SHpnt->host_busy >= SHpnt->can_queue))
    - || (SHpnt->host_blocked)
    - || (SHpnt->host_self_blocked)) {
    - /*
    - * If we are unable to process any commands at all for
    - * this device, then we consider it to be starved.
    - * What this means is that there are no outstanding
    - * commands for this device and hence we need a
    - * little help getting it started again
    - * once the host isn't quite so busy.
    - */
    - if (SDpnt->device_busy == 0) {
    - SDpnt->starved = 1;
    - SHpnt->some_device_starved = 1;
    - }
    - break;
    - } else {
    - SDpnt->starved = 0;
    - }
    -
    /*
    * If we couldn't find a request that could be queued, then we
    * can also quit.
    */
    if (blk_queue_empty(q))
    break;
    -
    /*
    * get next queueable request.
    */
    req = elv_next_request(q);

    + res = scsi_get_best_path(SDpnt, &path, req);
    + if (res == 1) {
    + /*
    + * IO is blocked.
    + */
    + req->flags &= ~REQ_STARTED;
    + return;
    + } else if (res == 2) {
    + /*
    + * No active paths available.
    + */
    + blkdev_dequeue_request(req);
    + if (end_that_request_first(req, 0, req->nr_sectors))
    + BUG();
    + end_that_request_last(req);
    + return;
    + }
    +
    /*
    * Find the actual device driver associated with this command.
    * The SPECIAL requests are things like character device or
    @@ -849,12 +833,32 @@
    SCpnt = (Scsi_Cmnd *) req->special;
    SRpnt = (Scsi_Request *) req->special;

    + /*
    + * A scsi_do_cmd() or scsi_do_req().
    + *
    + * XXX modify Scsi_Request to include a path, if a
    + * path is set in Scsi_Request, use it here.
    + */
    if( SRpnt->sr_magic == SCSI_REQ_MAGIC ) {
    SCpnt = scsi_allocate_device(SRpnt->sr_device,
    - FALSE, FALSE);
    - if (!SCpnt)
    + FALSE, FALSE, &path);
    + if (!SCpnt) {
    + scsi_host_busy_dec_and_test(path.spi_shpnt, SDpnt);
    + req->flags &= ~REQ_STARTED;
    break;
    + }
    scsi_init_cmd_from_req(SCpnt, SRpnt);
    + } else {
    + /*
    + * Either a scsi_do_cmd() or a retry of
    + * scsi_do_req() or scsi_do_cmd().
    + *
    + * set SCpnt host/id/lun/channel
    + */
    + SCpnt->host = path.spi_shpnt;
    + SCpnt->target = path.spi_id;
    + SCpnt->lun = path.spi_lun;
    + SCpnt->channel = path.spi_channel;
    }

    } else if (req->flags & (REQ_CMD | REQ_BLOCK_PC)) {
    @@ -867,30 +871,37 @@
    * Now try and find a command block that we can use.
    */
    if (req->special) {
    + /*
    + * Retry.
    + */
    SCpnt = (Scsi_Cmnd *) req->special;
    + SCpnt->host = path.spi_shpnt;
    + SCpnt->target = path.spi_id;
    + SCpnt->lun = path.spi_lun;
    + SCpnt->channel = path.spi_channel;
    } else {
    - SCpnt = scsi_allocate_device(SDpnt, FALSE, FALSE);
    + SCpnt = scsi_allocate_device(SDpnt, FALSE, FALSE, &path);
    }
    /*
    * If so, we are ready to do something. Bump the count
    * while the queue is locked and then break out of the
    * loop. Otherwise loop around and try another request.
    */
    - if (!SCpnt)
    + if (!SCpnt) {
    + scsi_host_busy_dec_and_test(path.spi_shpnt, SDpnt);
    + req->flags &= ~REQ_STARTED;
    break;
    + }

    /* pull a tag out of the request if we have one */
    SCpnt->tag = req->tag;
    } else {
    blk_dump_rq_flags(req, "SCSI bad req");
    + scsi_host_busy_dec_and_test(path.spi_shpnt, SDpnt);
    + req->flags &= ~REQ_STARTED;
    break;
    }

    - /*
    - * Now bump the usage count for both the host and the
    - * device.
    - */
    - SHpnt->host_busy++;
    SDpnt->device_busy++;

    /*
    @@ -941,11 +952,11 @@
    */
    if (!scsi_init_io(SCpnt)) {
    spin_lock_irq(q->queue_lock);
    - SHpnt->host_busy--;
    - SDpnt->device_busy--;
    + scsi_host_busy_dec_and_test(path.spi_shpnt, SDpnt);
    + SDpnt->device_busy--; /* XXX race */
    if (SDpnt->device_busy == 0) {
    SDpnt->starved = 1;
    - SHpnt->some_device_starved = 1;
    + path.spi_shpnt->some_device_starved = 1;
    }
    SCpnt->request->special = SCpnt;
    SCpnt->request->flags |= REQ_SPECIAL;
    @@ -967,8 +978,8 @@
    panic("Should not have leftover blocks\n");
    }
    spin_lock_irq(q->queue_lock);
    - SHpnt->host_busy--;
    - SDpnt->device_busy--;
    + scsi_host_busy_dec_and_test(path.spi_shpnt, SDpnt);
    + SDpnt->device_busy--; /* XXX race */
    continue;
    }
    }
    @@ -1035,11 +1046,13 @@
    void scsi_unblock_requests(struct Scsi_Host * SHpnt)
    {
    Scsi_Device *SDloop;
    + struct scsi_traverse_hndl strav_hndl;

    SHpnt->host_self_blocked = FALSE;
    /* Now that we are unblocked, try to start the queues. */
    - for (SDloop = SHpnt->host_queue; SDloop; SDloop = SDloop->next)
    - scsi_queue_next_request(&SDloop->request_queue, NULL);
    + scsi_for_each_host_sdev(&strav_hndl, SDloop, SHpnt->host_no) {
    + scsi_queue_next_request(&SDloop->request_queue, NULL, SHpnt);
    + }
    }

    /*
    @@ -1066,11 +1079,11 @@
    void scsi_report_bus_reset(struct Scsi_Host * SHpnt, int channel)
    {
    Scsi_Device *SDloop;
    - for (SDloop = SHpnt->host_queue; SDloop; SDloop = SDloop->next) {
    - if (channel == SDloop->channel) {
    - SDloop->was_reset = 1;
    - SDloop->expecting_cc_ua = 1;
    - }
    + struct scsi_traverse_hndl strav_hndl;
    +
    + scsi_for_each_host_sdev(&strav_hndl, SDloop, SHpnt->host_no) {
    + SDloop->was_reset = 1;
    + SDloop->expecting_cc_ua = 1;
    }
    }

    @@ -1089,4 +1102,97 @@

    void scsi_deregister_blocked_host(struct Scsi_Host * SHpnt)
    {
    +}
    +
    +
    +/**
    + * scsi_lookup_id - Lookup a scsi device matching an id
    + * @id: id terminated with '\0'
    + * @sdev_skip: possibly NULL Scsi_Device to skip if matched
    + *
    + * Description:
    + * Look for a Scsi_Device matching *@id. Uses a linear search. If a
    + * matching Scsi_Device not equal to sdev_skip is found return a pointer
    + * to it, else return NULL.
    + *
    + * Notes:
    + * Change to use driverfs tree.
    + **/
    +Scsi_Device *scsi_lookup_id(char *id, Scsi_Device *sdev_skip)
    +{
    + Scsi_Device *sdev;
    + struct scsi_traverse_hndl strav_hndl;
    +
    + SCSI_LOG_SCAN_BUS(3, printk(KERN_INFO "scsi: id '%s'", id));
    + if (id[0] != SCSI_UID_UNKNOWN)
    + scsi_for_all_sdevs(&strav_hndl, sdev) {
    + /*
    + * Both id and name should be '\0' terminated.
    + */
    + if ((sdev != sdev_skip) &&
    + strcmp(sdev->sdev_driverfs_dev.name,
    + (char *) id) == 0) {
    + SCSI_LOG_SCAN_BUS(3, printk("; found\n"));
    + return sdev;
    + }
    + }
    + SCSI_LOG_SCAN_BUS(3, printk("; no match\n"));
    + return NULL;
    +}
    +
    +/*
    + * Function: scsi_add_scsi_device()
    + *
    + * Purpose: Add a SDpnt to the list of Scsi_Devices, add it to the
    + * "tail" of the list. If needed, add it to the list for
    + * shostpnt.
    + *
    + * Arguments: Scsi_Device pointer SDpnt.
    + *
    + * Notes: Doesn't do anything other than link in SDpnt, we should add
    + * other code that is common to the addition of all Scsi_Devices.
    + */
    +void scsi_add_scsi_device(Scsi_Device *SDpnt, struct Scsi_Host *shostpnt)
    +{
    + Scsi_Device *sdtailpnt;
    +
    + SDpnt->sdev_prev = NULL;
    + SDpnt->sdev_next = NULL;
    + if (scsi_sdev_list != NULL) {
    + sdtailpnt = scsi_sdev_list;
    + while (sdtailpnt->sdev_next != NULL)
    + sdtailpnt = sdtailpnt->sdev_next;
    + sdtailpnt->sdev_next = SDpnt;
    + SDpnt->sdev_prev = sdtailpnt;
    + } else
    + scsi_sdev_list = SDpnt;
    +}
    +
    +/*
    + * Function: scsi_remove_scsi_device
    + *
    + * Purpose: Remove SDpnt from the list of Scsi_Devices.
    + *
    + * Arguments: Scsi_Device pointer SDpnt.
    + *
    + * Notes: unlink and kfree SDpnt, we should add other code that is common
    + * to removal of all Scsi_Devices.
    + */
    +void scsi_remove_scsi_device(Scsi_Device *SDpnt)
    +{
    + if (SDpnt == NULL)
    + return;
    + if (SDpnt->sdev_next != NULL)
    + SDpnt->sdev_next->sdev_prev = SDpnt->sdev_prev;
    + if (SDpnt->sdev_prev != NULL)
    + SDpnt->sdev_prev->sdev_next = SDpnt->sdev_next;
    + if (scsi_sdev_list == SDpnt)
    + scsi_sdev_list = SDpnt->sdev_next;
    + blk_cleanup_queue(&SDpnt->request_queue);
    + scsi_release_commandblocks(SDpnt);
    + scsi_remove_path(SDpnt, SCSI_FIND_ALL_HOST_NO, SCSI_FIND_ALL_CHANNEL,
    + SCSI_FIND_ALL_ID, SCSI_FIND_ALL_LUN);
    + if (SDpnt->inquiry != NULL)
    + kfree(SDpnt->inquiry);
    + kfree((char *) SDpnt);
    }
    diff -Nru a/drivers/scsi/scsi_merge.c b/drivers/scsi/scsi_merge.c
    --- a/drivers/scsi/scsi_merge.c Fri Oct 25 11:26:48 2002
    +++ b/drivers/scsi/scsi_merge.c Fri Oct 25 11:26:48 2002
    @@ -22,6 +22,7 @@
    #include <linux/timer.h>
    #include <linux/string.h>
    #include <linux/slab.h>
    +#include <linux/bio.h>
    #include <linux/ioport.h>
    #include <linux/kernel.h>
    #include <linux/stat.h>
    @@ -130,6 +131,58 @@
    }

    /*
    + * Function: scsi_cleanup_io()
    + *
    + * Purpose: SCSI I/O cleanup io function.
    + *
    + * Arguments: SCpnt - Command descriptor we wish to cleanup
    + *
    + * Returns: N/A
    + *
    + * Lock status:
    + *
    + * Notes: Code was previously part of scsi_io_completion, moved here
    + * as we now also call it when redriving IO that does not make
    + * it to the upper layer.
    + */
    +void scsi_cleanup_io(Scsi_Cmnd *SCpnt)
    +{
    + struct request *req = SCpnt->request;
    +
    + /*
    + * Free up any indirection buffers we allocated for DMA purposes.
    + * For the case of a READ, we need to copy the data out of the
    + * bounce buffer and into the real buffer.
    + */
    + if (SCpnt->use_sg) {
    + struct scatterlist *sgpnt;
    +
    + sgpnt = (struct scatterlist *) SCpnt->buffer;
    + scsi_free_sgtable(SCpnt->buffer, SCpnt->sglist_len);
    + } else {
    + if (SCpnt->buffer != req->buffer) {
    + if (rq_data_dir(req) == READ) {
    + unsigned long flags;
    + char *to = bio_kmap_irq(req->bio, &flags);
    +
    + memcpy(to, SCpnt->buffer, SCpnt->bufflen);
    + bio_kunmap_irq(to, &flags);
    + }
    + kfree(SCpnt->buffer);
    + }
    + }
    +
    + /*
    + * Zero these out. They now point to freed memory, and it is
    + * dangerous to hang onto the pointers.
    + */
    + SCpnt->buffer = NULL;
    + SCpnt->bufflen = 0;
    + SCpnt->request_buffer = NULL;
    + SCpnt->request_bufflen = 0;
    +}
    +
    +/*
    * Function: scsi_initialize_merge_fn()
    *
    * Purpose: Initialize merge function for a host
    @@ -144,10 +197,12 @@
    */
    void scsi_initialize_merge_fn(Scsi_Device * SDpnt)
    {
    - struct Scsi_Host *SHpnt = SDpnt->host;
    + struct Scsi_Host *SHpnt;
    request_queue_t *q = &SDpnt->request_queue;
    u64 bounce_limit;

    + SHpnt = scsi_get_host(SDpnt);
    + BUG_ON(SHpnt == NULL);
    /*
    * The generic merging functions work just fine for us.
    * Enable highmem I/O, if appropriate.
    diff -Nru a/drivers/scsi/scsi_paths.c b/drivers/scsi/scsi_paths.c
    --- /dev/null Wed Dec 31 16:00:00 1969
    +++ b/drivers/scsi/scsi_paths.c Fri Oct 25 11:26:48 2002
    @@ -0,0 +1,1499 @@
    +/*
    + * Scsi Multi-path Support Library
    + *
    + * Copyright (c) 2002 IBM
    + *
    + * All rights reserved.
    + *
    + * This program is free software; you can redistribute it and/or modify
    + * it under the terms of the GNU General Public License as published by
    + * the Free Software Foundation; either version 2 of the License, or (at
    + * your option) any later version.
    + *
    + * This program is distributed in the hope that it will be useful, but
    + * WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
    + * NON INFRINGEMENT. See the GNU General Public License for more
    + * details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with this program; if not, write to the Free Software
    + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    + *
    + * Send feedback to patmans@us.ibm.com and/or andmike@us.ibm.com
    + *
    + */
    +
    +#include <linux/config.h>
    +#include <linux/module.h>
    +#include <linux/init.h>
    +#include <linux/errno.h>
    +#include <linux/blk.h>
    +#include <linux/bio.h>
    +#include <asm/uaccess.h>
    +
    +#include "scsi.h"
    +#include "hosts.h"
    +#include "scsi_paths.h"
    +
    +#define SCSI_PATHS_VERSION "Version: 0.05.00"
    +
    +#ifdef CONFIG_SCSI_MULTI_PATH_IO
    +
    +#if defined (CONFIG_SCSI_PATH_POLICY_LPU)
    +static enum scsi_path_policy scsi_path_dflt_path_policy = SCSI_PATH_POLICY_LPU;
    +#elif defined (CONFIG_SCSI_PATH_POLICY_RR)
    +static enum scsi_path_policy scsi_path_dflt_path_policy =
    + SCSI_PATH_POLICY_ROUND_ROBIN;
    +#else
    +#error "Unknown path selection policy"
    +#endif
    +
    +#ifdef MODULE
    +MODULE_PARM(scsi_path_dflt_path_policy, "i");
    +MODULE_PARM_DESC(scsi_path_dflt_path_policy,
    + "path selection policy, Last Path Used = 1, Round Robin = 2");
    +#else
    +
    +static int __init scsi_path_policy_setup(char *str)
    +{
    + unsigned int tmp;
    +
    + if (get_option(&str, &tmp) == 1) {
    + scsi_path_dflt_path_policy = tmp;
    + return 1;
    + } else {
    + printk(KERN_INFO "scsi_path_policy_setup: usage "
    + "scsi_path_dflt_path_policy=n "
    + "Last Path Used = 1, Round Robin = 2");
    + return 0;
    + }
    +}
    +
    +__setup("scsi_path_dflt_path_policy=", scsi_path_policy_setup);
    +
    +#endif
    +#else
    +/*
    + * The path policy has no affect, since there is only one path.
    + */
    +static enum scsi_path_policy scsi_path_dflt_path_policy =
    + SCSI_PATH_POLICY_ROUND_ROBIN;
    +#endif
    +
    +
    +/*
    + * XXX audit and ensure that we have a lock whenver accessing any paths.
    + */
    +
    +/**
    + * scsi_paths_printk - printk for each path using the specified format.
    + * @sdev: printk all paths of this Scsi_Device
    + * @prefix: prefix string
    + * @format: a printf format string
    + *
    + * Description: For each path on @sdev, printk the host/channel/id/lun
    + * using the specified @format. For all but the first printk, output
    + * @prefix before @format. @prefix can include a level (such as KERN_INFO).
    + **/
    +void scsi_paths_printk(Scsi_Device *sdev, char *prefix, char *format)
    +{
    + struct list_head *lh;
    + struct scsi_mpath *mpath;
    + struct scsi_path *pathcur;
    + int not_first_path = 0;
    +
    + if (sdev->sdev_paths != NULL) {
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + list_for_each(lh, &mpath->scsi_mp_paths_head) {
    + pathcur =
    + list_entry(lh, struct scsi_path, sp_path_list);
    + if (not_first_path && prefix)
    + printk(prefix);
    + else
    + not_first_path = 1;
    + printk(format, pathcur->sp_path_id.spi_shpnt->host_no,
    + pathcur->sp_path_id.spi_channel,
    + pathcur->sp_path_id.spi_id,
    + pathcur->sp_path_id.spi_lun);
    + }
    + } else
    + printk("no paths");
    +}
    +
    +/**
    + * scsi_remove_path_from_list - remove a path from an active list
    + * @mpath: get an active list from here
    + * @oldpath: path to remove
    + * @ind_active: remove from this last active list
    + *
    + * Description:
    + * Remove @oldpath from the active list @ind_active of @mpath. It is
    + * safe to call this function if path is not on the active list.
    + **/
    +void scsi_remove_path_from_list(struct scsi_mpath *mpath,
    + struct scsi_path *oldpath, int ind_active)
    +{
    + struct scsi_path *last_active;
    + struct scsi_path *pathcur;
    + int ind_next;
    +
    + SCSI_LOG_SCAN_BUS(4, printk(KERN_INFO
    + "%s: path <%d, %d, %d, %d>, ind_active %d\n",
    + __FUNCTION__, oldpath->sp_path_id.spi_shpnt->host_no,
    + oldpath->sp_path_id.spi_id, oldpath->sp_path_id.spi_lun,
    + oldpath->sp_path_id.spi_channel, ind_active));
    +
    + BUG_ON(ind_active >= SCSI_MAX_ACTIVE_LIST);
    + last_active = mpath->scsi_mp_last_active[ind_active];
    + if (last_active == NULL)
    + return;
    +
    + if (ind_active == SCSI_PATHS_ALL_LIST) {
    + ind_next = SCSI_ALL_ACTIVE_NEXT_LIST;
    + } else {
    + ind_next = SCSI_NID_ACTIVE_NEXT_LIST;
    + }
    + pathcur = last_active;
    + do {
    + if (pathcur->sp_active_next[ind_next] == oldpath) {
    + if (pathcur == oldpath)
    + mpath->scsi_mp_last_active[ind_active] = NULL;
    + else {
    + pathcur->sp_active_next[ind_next] =
    + oldpath->sp_active_next[ind_next];
    + if (last_active == oldpath)
    + mpath->scsi_mp_last_active[ind_active] =
    + oldpath->sp_active_next[ind_next];
    + }
    + --mpath->scsi_mp_path_active_cnt[ind_active];
    + oldpath->sp_active_next[ind_next] = NULL;
    + return;
    + }
    + pathcur = pathcur->sp_active_next[ind_next];
    + } while (pathcur != last_active);
    +}
    +
    +/**
    + * scsi_remove_active_path - Remove a path from all active lists
    + * @sdev: Scsi_Device
    + * @oldpath: A scsi path
    + *
    + * Description:
    + * Remove @oldpath from the active lists of Scsi_Device @sdev. It is safe to
    + * call this function if path is not on the active list.
    + **/
    +static void scsi_remove_active_path(Scsi_Device *sdev,
    + struct scsi_path *oldpath)
    +{
    + struct scsi_mpath *mpath;
    + int nid;
    +
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + if (mpath == NULL)
    + return;
    +
    + nid = scsihost_to_node(oldpath->sp_path_id.spi_shpnt);
    + scsi_remove_path_from_list(mpath, oldpath, nid);
    + if (nid != SCSI_PATHS_ALL_LIST)
    + /*
    + * No CONFIG_NUMA
    + */
    + scsi_remove_path_from_list(mpath, oldpath,
    + SCSI_PATHS_ALL_LIST);
    +}
    +
    +/**
    + * scsi_host_type_check - verify that the host-adapters are the same
    + * @host1 - the Scsi_Host pointer for the first adapter
    + * @host2 - the Scsi_Host pointer for the second adapter
    + *
    + * Description:
    + * Look at host pointers and check the values of certain fields,
    + * looking for incompatibilities that could cause problems.
    + *
    + * Return 1 if they don't match, 0 if they do.
    + **/
    +static int scsi_host_type_check(struct Scsi_Host *host1,
    + struct Scsi_Host *host2)
    +{
    + int ret = 0;
    +
    + static const char message[] = {
    + KERN_WARNING "scsi: the parameter '%s' differs for two"
    + " adapters attached to the same device.\n"
    + };
    +
    +#define CHECK_HOST_PARAMS(param) \
    + if (host1->param != host2->param) { \
    + printk(message, __stringify(param)); \
    + ret = 1; \
    + }
    +
    + CHECK_HOST_PARAMS(hostt);
    + CHECK_HOST_PARAMS(sg_tablesize);
    + CHECK_HOST_PARAMS(max_sectors);
    + CHECK_HOST_PARAMS(highmem_io);
    + CHECK_HOST_PARAMS(unchecked_isa_dma);
    + CHECK_HOST_PARAMS(use_clustering);
    + CHECK_HOST_PARAMS(max_cmd_len);
    +
    +#undef CHECK_HOST_PARAMS
    +
    + return ret;
    +}
    +
    +/**
    + * scsi_add_active_path - Add a path to an active list
    + * @sdev: Scsi_Device
    + * @pathcur: A scsi path
    + * @ind_active: index into the scsi_mp_last_active
    + * @ind_next: index into the sp_active_next
    + *
    + * Description: Add @pathcur to the active list of Scsi_Device @sdev. It is
    + * safe to add a path that is already on the active list (the list will
    + * not be changed).
    + **/
    +static void scsi_add_active_path(Scsi_Device *sdev, struct scsi_path *pathcur,
    + int ind_active)
    +{
    + struct scsi_mpath *mpath;
    + struct scsi_path *last_active;
    + int ind_next;
    +
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + if (mpath == NULL)
    + return;
    + BUG_ON(ind_active >= SCSI_MAX_ACTIVE_LIST);
    + if (ind_active == SCSI_PATHS_ALL_LIST)
    + ind_next = SCSI_ALL_ACTIVE_NEXT_LIST;
    + else
    + ind_next = SCSI_NID_ACTIVE_NEXT_LIST;
    + if (pathcur->sp_active_next[ind_next] != NULL)
    + /*
    + * The path is already on the active list.
    + */
    + return;
    + else {
    + SCSI_LOG_SCAN_BUS(4, printk(KERN_INFO
    + "%s: sdev 0x%p, path <%d, %d, %d, %d> ind_active %d\n",
    + __FUNCTION__, sdev,
    + pathcur->sp_path_id.spi_shpnt->host_no,
    + pathcur->sp_path_id.spi_id,
    + pathcur->sp_path_id.spi_lun,
    + pathcur->sp_path_id.spi_channel, ind_active));
    +
    + last_active = mpath->scsi_mp_last_active[ind_active];
    + if (last_active != NULL) {
    + pathcur->sp_active_next[ind_next] =
    + last_active->sp_active_next[ind_next];
    + last_active->sp_active_next[ind_next] = pathcur;
    + } else {
    + mpath->scsi_mp_last_active[ind_active] = pathcur;
    + pathcur->sp_active_next[ind_next] = pathcur;
    + }
    + ++mpath->scsi_mp_path_active_cnt[ind_active];
    + }
    +}
    +
    +/**
    + * scsi_add_path - Add a path to a scsi device
    + * @sdev: Scsi_Device to add a path to
    + * @shost: Scsi_Host of the path
    + * @channel: Channel value of the path
    + * @id: Id value of the path
    + * @lun: Lun value of the path
    + *
    + * Description:
    + * Add a path corresponding to @shost, @channel, @id, and @lun to the
    + * Scsi_Device @sdev. The new path is also added to the active path. Must
    + * be called with user context (kmalloc GFP_KERNEL).
    + *
    + * Return 1 on error, 0 on success.
    + **/
    +int scsi_add_path(Scsi_Device *sdev, struct Scsi_Host *shost,
    + unsigned int channel, unsigned int id, unsigned int lun)
    +{
    + struct scsi_mpath *mpath;
    + struct scsi_path *pathcur;
    + struct Scsi_Host *shostcur;
    + int i, nid;
    +
    + SCSI_LOG_SCAN_BUS(4, printk(KERN_INFO
    + "%s: sdev 0x%p path: <%d, %d, %d, %d>\n", __FUNCTION__,
    + sdev, shost->host_no, channel, id, lun));
    +
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    +
    + pathcur = kmalloc(sizeof(*pathcur), GFP_KERNEL);
    + if (pathcur == NULL) {
    + printk(KERN_WARNING
    + "%s: memory allocation failure.\n", __FUNCTION__);
    + return 1;
    + }
    + for (i = 0; i < SCSI_PATHS_MAX_PATH_LISTS; i++)
    + pathcur->sp_active_next[i] = NULL;
    + pathcur->sp_state = SCSI_PATH_STATE_GOOD;
    + pathcur->sp_failures = 0;
    + pathcur->sp_weight = 0; /* lowest is best */
    + pathcur->sp_path_id.spi_shpnt = shost;
    + pathcur->sp_path_id.spi_id = id;
    + pathcur->sp_path_id.spi_lun = lun;
    + pathcur->sp_path_id.spi_channel = channel;
    +
    + if (mpath == NULL) {
    + /*
    + * First path.
    + */
    + SCSI_LOG_SCAN_BUS(5, printk(KERN_INFO
    + "%s: adding first path\n", __FUNCTION__));
    + mpath = kmalloc(sizeof(*mpath), GFP_KERNEL);
    + if (mpath == NULL) {
    + printk(KERN_WARNING "%s: memory allocation failure.\n",
    + __FUNCTION__);
    + kfree(pathcur);
    + return 1;
    + }
    + INIT_LIST_HEAD(&mpath->scsi_mp_paths_head);
    + mpath->scsi_mp_path_policy = scsi_path_dflt_path_policy;
    + for (i = 0; i < SCSI_MAX_ACTIVE_LIST; i++) {
    + mpath->scsi_mp_last_active[i] = NULL;
    + mpath->scsi_mp_path_active_cnt[i] = 0;
    + }
    + (struct scsi_mpath *) sdev->sdev_paths = mpath;
    + } else {
    + if (sdev->single_lun) {
    + printk(KERN_WARNING
    + "scsi: Multi-path single LUN devices are not "
    + "supported. path host %d chan %d id %d lun %d "
    + "not added\n", shost->host_no, channel, id, lun);
    + kfree(pathcur);
    + return 1;
    + }
    + shostcur = scsi_get_host(sdev);
    + BUG_ON(shostcur == NULL);
    + if (scsi_host_type_check(shost, shostcur)) {
    + printk(KERN_ERR "scsi: path host %d chan %d id %d"
    + " lun %d not added\n", shost->host_no, channel,
    + id, lun);
    + kfree(pathcur);
    + return 1;
    + }
    + SCSI_LOG_SCAN_BUS(5, printk(KERN_INFO
    + "scsi: adding more paths\n"));
    + }
    +
    + list_add_tail(&pathcur->sp_path_list, &mpath->scsi_mp_paths_head);
    + /*
    + * Add the path to the all list, and possibly to a nid list.
    + */
    + nid = scsihost_to_node(shost);
    + scsi_add_active_path(sdev, pathcur, nid);
    + if (nid != SCSI_PATHS_ALL_LIST)
    + /*
    + * CONFIG_NUMA
    + */
    + scsi_add_active_path(sdev, pathcur, SCSI_PATHS_ALL_LIST);
    +
    + SCSI_LOG_SCAN_BUS(5, {
    + printk(KERN_INFO "%s: All paths for sdev 0x%p:\n ",
    + __FUNCTION__, sdev);
    + scsi_paths_printk(sdev, KERN_INFO " ",
    + "<%d, %d, %d, %d>\n");
    + }
    + );
    + return 0;
    +}
    +
    +/**
    + * scsi_remove_path - Remove a path from a scsi device
    + * @host_no: Host number of the path
    + * @channel: Channel value of the path
    + * @id: Id value of the path
    + * @lun: Lun value of the path
    + * @sdev: Scsi_Device to add a path to
    + *
    + * Description:
    + * Remove the path corresponding to @host_no, @channel, @id, and @lun from
    + * the Scsi_Device @sdev. The path does not have to exist, so interfaces
    + * can wild card removal of all paths without first checking if any of the
    + * paths actually exist.
    + **/
    +void scsi_remove_path(Scsi_Device *sdev, unsigned int host_no,
    + unsigned int channel, unsigned int id, unsigned int lun)
    +{
    + struct scsi_mpath *mpath;
    + struct list_head *lh, *tmplh;
    + struct scsi_path *pathcur;
    +
    + SCSI_LOG_SCAN_BUS(4, printk(KERN_INFO
    + "%s: sdev 0x%p path <%d, %d, %d, %d>\n", __FUNCTION__,
    + sdev, host_no, channel, id, lun));
    +
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + if (mpath == NULL)
    + return;
    +
    + /*
    + * Look for a matching path, and remove it.
    + *
    + * XXX should be common code used by the scsi iterators.
    + */
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + list_for_each_safe(lh, tmplh, &mpath->scsi_mp_paths_head) {
    + pathcur = list_entry(lh, struct scsi_path, sp_path_list);
    + if ((host_no == SCSI_FIND_ALL_HOST_NO ||
    + host_no == pathcur->sp_path_id.spi_shpnt->host_no) &&
    + (channel == SCSI_FIND_ALL_CHANNEL ||
    + channel == pathcur->sp_path_id.spi_channel) &&
    + (id == SCSI_FIND_ALL_ID ||
    + id == pathcur->sp_path_id.spi_id) &&
    + (lun == SCSI_FIND_ALL_LUN ||
    + lun == pathcur->sp_path_id.spi_lun)) {
    + scsi_remove_active_path(sdev, pathcur);
    + list_del(&pathcur->sp_path_list);
    + kfree(pathcur);
    + }
    + }
    +
    + if (list_empty(&mpath->scsi_mp_paths_head)) {
    + SCSI_LOG_SCAN_BUS(4, printk(KERN_INFO
    + "%s: sdev 0x%p removed last path\n", __FUNCTION__, sdev));
    + kfree(mpath);
    + sdev->sdev_paths = NULL;
    + }
    + return;
    +}
    +
    +/**
    + * scsi_replace_path - replace the current and only path to a Scsi_Device
    + * @sdev: replace a path on this Scsi_Device
    + * @host_no: new host number
    + * @channel: new channel value
    + * @id: new id
    + * @lun: new lun
    + *
    + * Description:
    + * Modify the one and only path on @sdev to use @host_no, @channel, @id,
    + * and @lun. This function is _only_ for use during scsi scan, where one
    + * sdev is used to scan an entire host. It is an optimization to avoid
    + * doing a scsi_remove_path and scsi_add_path for each LUN we want to
    + * scan.
    + **/
    +void scsi_replace_path(Scsi_Device *sdev, struct Scsi_Host *shost,
    + unsigned int channel, unsigned int id, unsigned int lun)
    +{
    + struct scsi_mpath *mpath;
    + struct scsi_path_id *pathid;
    + struct scsi_path *pathcur;
    + int nid;
    + struct list_head *lh;
    +
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + BUG_ON(mpath == NULL);
    + /*
    + * There should be one and only one path for this sdev.
    + */
    + list_for_each(lh, &mpath->scsi_mp_paths_head) {
    + pathcur = list_entry(lh, struct scsi_path, sp_path_list);
    + pathid = &pathcur->sp_path_id;
    + SCSI_LOG_SCAN_BUS(5, printk(KERN_INFO
    + "%s: sdev* 0x%p replace <%d, %d, %d, %d> with"
    + " <%d, %d, %d, %d>\n", __FUNCTION__, sdev,
    + pathid->spi_shpnt->host_no, pathid->spi_channel,
    + pathid->spi_id, pathid->spi_lun,
    + shost->host_no, channel, id, lun));
    + pathid->spi_shpnt = shost;
    + pathid->spi_channel = channel;
    + pathid->spi_id = id;
    + pathid->spi_lun = lun;
    +
    + /*
    + * Make sure it is still an active path, in case previous
    + * IO failed the path.
    + */
    + nid = scsihost_to_node(pathcur->sp_path_id.spi_shpnt);
    + scsi_add_active_path(sdev, pathcur, nid);
    + if (nid != SCSI_PATHS_ALL_LIST)
    + /*
    + * CONFIG_NUMA
    + */
    + scsi_add_active_path(sdev, pathcur,
    + SCSI_PATHS_ALL_LIST);
    + return;
    + }
    + BUG();
    +}
    +
    +/**
    + * scsi_get_host - get a Scsi_Host pointer to a Scsi_Device
    + *
    + * @sdev: Get the Scsi_Host of this Scsi_Device pointer
    + *
    + * Description:
    + * Return a pointer to a Scsi_Host so that the capabilities of the of the
    + * host driver can be determined; if no paths exist to sdev, return NULL.
    + *
    + * This should return some generic information rather than a Scsi_Host*.
    + * Alternatively (and probably even better) have one function for each
    + * value needed.
    + *
    + * This function assumes that all paths to a Scsi_Device use the same host
    + * driver - this is enforced during device scanning/discovery. This
    + * function must only be used to get capabilities or the template of the
    + * host - using it to set or get per-Scsi_Host (verus per-host driver)
    + * specific values is a bug (for example, using this to get a host pointer
    + * and then referencing host_busy).
    + **/
    +struct Scsi_Host *scsi_get_host(Scsi_Device *sdev)
    +{
    + struct scsi_path *pathcur;
    + struct list_head *lh;
    + struct scsi_mpath *mpath;
    +
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + if (mpath == NULL)
    + return NULL;
    + list_for_each(lh, &mpath->scsi_mp_paths_head) {
    + pathcur = list_entry(lh, struct scsi_path, sp_path_list);
    + return pathcur->sp_path_id.spi_shpnt;
    + }
    + return NULL;
    +}
    +
    +/**
    + * scsi_get_path - Get a path to a scsi device
    + * @sdev: Scsi_Device
    + * @pathid: path id (tuple)
    + *
    + * Description:
    + * Get any @pathid to a Scsi_Device, used by semi-broken code that wants
    + * one host/channel/id/lun of a device. The path returned might not be
    + * active. Return 0 if a path is found, else return 1.
    + **/
    +int scsi_get_path(Scsi_Device *sdev, struct scsi_path_id *pathid)
    +{
    + struct scsi_path *pathcur;
    + struct scsi_mpath *mpath;
    +
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + if (mpath == NULL)
    + return 1;
    + pathcur = list_entry(mpath->scsi_mp_paths_head.next, struct scsi_path,
    + sp_path_list);
    + *pathid = pathcur->sp_path_id;
    + return 0;
    +}
    +
    +/**
    + * scsi_path_traverse_sdevs - Return the next Scsi_Device matching the
    + * search requested
    + *
    + * @handle: Handle used to bookmark location of search thus far
    + * @host_no: Host to search for
    + * @channel: Channel to search for
    + * @id: Id to search for
    + * @lun: Lun to search for
    + *
    + * Description:
    + * Returns - A pointer to Scsi_Device, NULL when the list is complete.
    + * Notes - Assumes that we do not remove a device between calls.
    + **/
    +Scsi_Device *scsi_traverse_sdevs(struct scsi_traverse_hndl *handle, uint
    + host_no, uint channel, uint id, uint lun)
    +{
    + Scsi_Device *sdev;
    +
    + if (handle == NULL)
    + /*
    + * Really a locate and not a traversal.
    + */
    + sdev = scsi_sdev_list;
    + else
    + sdev = handle->next_sdev;
    +
    + for (; sdev; sdev = sdev->sdev_next) {
    + if (sdev->sdev_paths != NULL) {
    + struct list_head *lh;
    + struct scsi_mpath *mpath_iopnt;
    + struct scsi_path *pathpnt;
    +
    + mpath_iopnt = (struct scsi_mpath *) sdev->sdev_paths;
    + list_for_each(lh, &mpath_iopnt->scsi_mp_paths_head) {
    + pathpnt = list_entry(lh, struct scsi_path,
    + sp_path_list);
    + if ((host_no == SCSI_FIND_ALL_HOST_NO ||
    + host_no == pathpnt->sp_path_id.spi_shpnt->host_no ) &&
    + (channel == SCSI_FIND_ALL_CHANNEL ||
    + channel == pathpnt->sp_path_id.spi_channel ) &&
    + (id == SCSI_FIND_ALL_ID ||
    + id == pathpnt->sp_path_id.spi_id ) &&
    + (lun == SCSI_FIND_ALL_LUN ||
    + lun == pathpnt->sp_path_id.spi_lun )) {
    + goto done;
    + }
    + }
    + }
    + }
    +
    +done:
    + if (handle && sdev)
    + handle->next_sdev = sdev->sdev_next;
    + return sdev;
    +}
    +
    +/**
    + * scsi_get_best_path - Get a path for a scsi device.
    + * @sdev: Scsi_Device
    + * @pathid: A scsi path id (tuple)
    + * @req: request for NUMA so the node closest to req can be found
    + *
    + * Description:
    + * This code is used for normal IO. Get a @pathid for Scsi_Device @sdev.
    + * Skip paths on a busy or blocked host, mark hosts and devices as starved
    + * if needed. Return 2 if no paths at all, 1 if resources are busy, 0 if a
    + * path is found.
    + **/
    +int scsi_get_best_path(Scsi_Device *sdev, struct scsi_path_id *pathid,
    + struct request *req)
    +{
    + struct scsi_path *pathcur, *first_path;
    + struct scsi_mpath *mpath;
    + struct Scsi_Host *shost;
    + request_queue_t *q;
    + int found_path, starving, ind_active, ind_next;
    + unsigned long flags = 0;
    +
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + if ((mpath == NULL) ||
    + (mpath->scsi_mp_last_active[SCSI_PATHS_ALL_LIST] == NULL)) {
    + /*
    + * No paths or no active paths available.
    + *
    + * XXX Fix the following message to include a device name or ?
    + */
    + printk(KERN_WARNING
    + "scsi: No active paths available Scsi_Device 0x%p.\n",
    + sdev);
    + return 2;
    + }
    +
    + ind_active = req_to_nid(req);
    +#if (MAX_NUMNODES != 1)
    + /*
    + * If MAX_NUMNODES is 1, not NUMA, and the following check is not
    + * needed.
    + */
    + if (ind_active <= MAX_NUMNODES)
    + if (mpath->scsi_mp_last_active[ind_active] == NULL)
    + ind_active = SCSI_PATHS_ALL_LIST;
    +#endif
    + first_path = pathcur = mpath->scsi_mp_last_active[ind_active];
    + BUG_ON(ind_active >= SCSI_MAX_ACTIVE_LIST);
    + if (ind_active == SCSI_PATHS_ALL_LIST)
    + ind_next = SCSI_ALL_ACTIVE_NEXT_LIST;
    + else
    + ind_next = SCSI_NID_ACTIVE_NEXT_LIST;
    + found_path = 0;
    + starving = 0;
    + do {
    + shost = pathcur->sp_path_id.spi_shpnt;
    + /*
    + * XXX hack: host_lock might not equal queue_lock for
    + * multi-path, or if code is changed. We already hold
    + * queue_lock.
    + */
    + q = &sdev->request_queue;
    + if (shost->host_lock != q->queue_lock)
    + spin_lock_irqsave(shost->host_lock, flags);
    + if (!shost->in_recovery) {
    + if ((shost->can_queue > 0 &&
    + (shost->host_busy >= shost->can_queue))
    + || (shost->host_blocked)
    + || (shost->host_self_blocked)) {
    + if (sdev->device_busy == 0) {
    + starving = 1;
    + }
    + } else {
    + found_path = 1;
    + starving = 0;
    + /* do not unlock host shost */
    + break;
    + }
    + }
    + if (shost->host_lock != q->queue_lock)
    + spin_unlock_irqrestore(shost->host_lock, flags);
    + if (mpath->scsi_mp_path_policy != SCSI_PATH_POLICY_LPU)
    + pathcur = pathcur->sp_active_next[ind_next];
    + } while (first_path != pathcur);
    +
    + if (starving) {
    + /*
    + * For now, wait for the local node (or all paths) to get a
    + * free slot. This means NUMA systems might have a path
    + * available on a non-local node, but we do not try and
    + * use it. A hung device should eventually cause
    + * the local paths to fail, and then the non-local paths
    + * would be used for futher IO.
    + *
    + * All available hosts (on all paths) are busy, and sdev
    + * is starved. Mark all hosts as having a starved device.
    + *
    + * It would be better to put starved devices on a list,
    + * rather than set a flag.
    + */
    + sdev->starved = 1;
    + pathcur = first_path = mpath->scsi_mp_last_active[ind_active];
    + do {
    + if (shost->host_lock != q->queue_lock)
    + spin_lock_irqsave(shost->host_lock, flags);
    + shost->some_device_starved = 1;
    + if (shost->host_lock != q->queue_lock)
    + spin_unlock_irqrestore(shost->host_lock, flags);
    + pathcur = pathcur->sp_active_next[ind_next];
    + } while (first_path != pathcur);
    + return 1;
    + } else if (found_path) {
    + sdev->starved = 0;
    + shost->host_busy++;
    + if (shost->host_lock != q->queue_lock)
    + spin_unlock_irqrestore(shost->host_lock, flags);
    + } else
    + /*
    + * All available hosts (on all paths) are busy, and sdev
    + * is NOT starved.
    + */
    + return 1;
    +
    + if (mpath->scsi_mp_path_policy == SCSI_PATH_POLICY_ROUND_ROBIN)
    + /*
    + * This actually sets scsi_mp_last_active[ind_active] to the
    + * next path to be used, not the last path used.
    + */
    + mpath->scsi_mp_last_active[ind_active] =
    + mpath->scsi_mp_last_active[ind_active]->sp_active_next[ind_next];
    + else if (mpath->scsi_mp_path_policy != SCSI_PATH_POLICY_LPU) {
    + printk(KERN_ERR
    + "%s: invalid scsi_mp_path_policy %d\n", __FUNCTION__,
    + mpath->scsi_mp_path_policy);
    + return 1;
    + }
    +
    + /*
    + * XXX races with path becoming failed via /proc or user level
    + * change, this is not too horrible, as the IO will be sent and
    + * then fail the path again. Fix with proper SMP locking.
    + */
    + *pathid = pathcur->sp_path_id;
    + return 0;
    +}
    +
    +/**
    + * scsi_check_paths - Check a path in a scsi device.
    + * @path_state: Path state to upate paths with
    + * @check_path: Paths to match
    + * @sdev: Scsi_Device
    + *
    + * Description:
    + * For the paths in @sdev that match path_list_p, update its path state,
    + * and return a value based on the remaining available paths and the
    + * path_state argument. Called for every IO completion.
    + *
    + * XXX optimize so that the normal case (IO completed with no errors)
    + * requires no searching of all paths. Do this by caching if any paths are
    + * failing, and by caching paths_left. So, for an IO with no errors, the
    + * only time we have to search the paths is when there are any paths failing.
    + * Right now, just caching paths_left is enough since
    + * SCSI_PATH_STATE_FAILING and SCSI_PATH_STATE_DEAD are treated the same.
    + *
    + * Returns REQUEUE (resend the IO) if any paths are left, else returns
    + * SUCCESS (meaning the IO completed, not that it was successful).
    + **/
    +static int scsi_check_paths(enum scsi_path_state path_state,
    + struct scsi_path_id *check_path,
    + Scsi_Cmnd *scmd)
    +{
    + struct scsi_path *pathcur;
    + struct list_head *lh;
    + struct scsi_mpath *mpath;
    + unsigned int paths_left;
    + Scsi_Device *sdev = scmd->device;
    +
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + if ((mpath == NULL) || (sdev->scanning &&
    + (path_state != SCSI_PATH_STATE_GOOD)))
    + /*
    + * IO should not be retried if there is no sdev_paths, or
    + * if we have a failure during scanning.
    + */
    + return SUCCESS;
    + paths_left = 0;
    + list_for_each(lh, &mpath->scsi_mp_paths_head) {
    + pathcur = list_entry(lh, struct scsi_path, sp_path_list);
    + if ((check_path->spi_shpnt->host_no ==
    + pathcur->sp_path_id.spi_shpnt->host_no)
    + && (check_path->spi_channel ==
    + pathcur->sp_path_id.spi_channel)
    + && (check_path->spi_id == pathcur->sp_path_id.spi_id)
    + && (check_path->spi_lun == pathcur->sp_path_id.spi_lun)) {
    + if (path_state == SCSI_PATH_STATE_GOOD) {
    + /*
    + * Nothing for now - but this could be
    + * used to decrement or reset sp_failures.
    + */
    + } else if (path_state == SCSI_PATH_STATE_FAILING) {
    + pathcur->sp_failures++;
    + if (pathcur->sp_failures > 0) {
    + /*
    + * For now, treat a possibly failing
    + * path as a dead path.
    + */
    + pathcur->sp_state = SCSI_PATH_STATE_DEAD;
    + scsi_remove_active_path(sdev, pathcur);
    + printk(KERN_ERR "scsi: result 0x%x caused path host %d"
    + " channel %d id %d lun %d failure\n",
    + scmd->result,
    + pathcur->sp_path_id.spi_shpnt->host_no,
    + pathcur->sp_path_id.spi_channel,
    + pathcur->sp_path_id.spi_id,
    + pathcur->sp_path_id.spi_lun);
    + }
    + } else if (path_state == SCSI_PATH_STATE_DEAD) {
    + pathcur->sp_state = SCSI_PATH_STATE_DEAD;
    + scsi_remove_active_path(sdev, pathcur);
    + printk(KERN_ERR "scsi: result 0x%x caused path host %d"
    + " channel %d id %d lun %d failure\n",
    + scmd->result,
    + pathcur->sp_path_id.spi_shpnt->host_no,
    + pathcur->sp_path_id.spi_channel,
    + pathcur->sp_path_id.spi_id,
    + pathcur->sp_path_id.spi_lun);
    + } else
    + BUG();
    + }
    + if (pathcur->sp_state == SCSI_PATH_STATE_GOOD)
    + paths_left++;
    + }
    + /*
    + * Allow up to scmd->allowed retries per path. This is not ideal, but
    + * allows us to retry at least a few more times - this probably will
    + * work OK but for example if a new path is added and we get a
    + * CHECK_CONDITION, if we were in the middle of retrying, we might
    + * not retry enough times to clear the check on all initiators. There
    + * might be a similiar issue when failing or removing a path.
    + *
    + * A time limit on the IO rather than a retry count would help fix this
    + * issue - retry until so much time goes by, rather than retry a fixed
    + * number of times. There are still problems for commands that will
    + * keep getting (for example) a DID_ERROR.
    + *
    + * Also, for tape IO, this prevents any command from being requeued,
    + * a bad thing for errors that we know could not have gone out to
    + * the drive.
    + */
    + SCSI_LOG_ERROR_RECOVERY(5, printk("%s: retries %d, allowed %d,"
    + " paths_left %d\n", __FUNCTION__,
    + scmd->retries, scmd->allowed, paths_left));
    + if (++scmd->retries < (scmd->allowed * paths_left))
    + return REQUEUE;
    + else
    + return SUCCESS;
    +}
    +
    +/**
    + * scsi_decide_disposition - Check the result of a scsi command
    + * @scmd: Scsi command
    + *
    + * Description:
    + * Check the result of @scmd, and determine how to procede.
    + **/
    +int scsi_decide_disposition(Scsi_Cmnd *scmd)
    +{
    + int rtn;
    + struct scsi_path_id pathid;
    +
    + if (scmd->device->online == FALSE) {
    + SCSI_LOG_ERROR_RECOVERY(5, printk("%s: device offline - report"
    + " as SUCCESS\n",
    + __FUNCTION__));
    + return SUCCESS;
    + }
    + pathid.spi_shpnt = scmd->host;
    + pathid.spi_channel = scmd->channel;
    + pathid.spi_id = scmd->target;
    + pathid.spi_lun = scmd->lun;
    + /*
    + * First check the host byte, to see if there is anything in there
    + * that would indicate what we need to do.
    + */
    + switch (host_byte(scmd->result)) {
    + case DID_PASSTHROUGH:
    + /*
    + * No matter what, pass this through to the upper layer.
    + * Nuke this special code so that it looks like we are saying
    + * DID_OK.
    + */
    + scmd->result &= 0xff00ffff;
    + (void) scsi_check_paths(SCSI_PATH_STATE_GOOD, &pathid, scmd);
    + return SUCCESS;
    + case DID_OK:
    + /*
    + * Looks good. Drop through, and check the next byte.
    + */
    + break;
    + case DID_NO_CONNECT:
    + case DID_BAD_TARGET:
    + /*
    + * Assumes the IO could not have made it to the device.
    + */
    + rtn = scsi_check_paths(SCSI_PATH_STATE_DEAD, &pathid, scmd);
    + return rtn;
    + case DID_ABORT:
    + /*
    + * Could this IO have made it to the device?
    + */
    + rtn = scsi_check_paths(SCSI_PATH_STATE_FAILING, &pathid, scmd);
    + return rtn;
    + /*
    + * When the low level driver returns DID_SOFT_ERROR,
    + * it is responsible for keeping an internal retry counter
    + * in order to avoid endless loops (DB)
    + *
    + * Actually this is a bug in this function here. We should
    + * be mindful of the maximum number of retries specified
    + * and not get stuck in a loop.
    + */
    + case DID_SOFT_ERROR:
    + /*
    + * Assumes the IO might have made it out to the device.
    + */
    + goto maybe_retry;
    +
    + case DID_ERROR:
    + if (msg_byte(scmd->result) == COMMAND_COMPLETE &&
    + status_byte(scmd->result) == RESERVATION_CONFLICT)
    + /*
    + * execute reservation conflict processing code
    + * lower down
    + */
    + break;
    + /* fallthrough */
    +
    + case DID_BUS_BUSY:
    + case DID_PARITY:
    + /*
    + * XXX set path to SCSI_PATH_STATE_FAILING?
    + */
    + goto maybe_retry;
    + case DID_TIME_OUT:
    + /*
    + * When we scan the bus, we get timeout messages for
    + * these commands if there is no device available.
    + * Other hosts report DID_NO_CONNECT for the same thing.
    + */
    + if ((scmd->cmnd[0] == TEST_UNIT_READY ||
    + scmd->cmnd[0] == INQUIRY)) {
    + /*
    + * XXX check the sdev->scanning bit, return
    + * success if it is set. TUR's should no longer be
    + * a problem.
    + */
    + (void) scsi_check_paths(SCSI_PATH_STATE_GOOD, &pathid,
    + scmd);
    + return SUCCESS;
    + } else {
    + rtn = scsi_check_paths(SCSI_PATH_STATE_FAILING, &pathid,
    + scmd);
    + if (rtn == REQUEUE)
    + /*
    + * FIXME could cause scsi_check_paths to
    + * incorrectly be called again.
    + */
    + goto maybe_retry;
    + else
    + return FAILED;
    + }
    + case DID_RESET:
    + /*
    + * In the normal case where we haven't initiated a reset,
    + * this is a failure.
    + */
    + if (scmd->flags & IS_RESETTING) {
    + scmd->flags &= ~IS_RESETTING;
    + goto maybe_retry;
    + }
    + (void) scsi_check_paths(SCSI_PATH_STATE_GOOD, &pathid, scmd);
    + return SUCCESS;
    + default:
    + /*
    + * Likely a bug.
    + */
    + return FAILED;
    + }
    +
    + /*
    + * Next, check the message byte.
    + */
    + if (msg_byte(scmd->result) != COMMAND_COMPLETE) {
    + /*
    + * XXX Use SCSI_PATH_DEAD or SCSI_PATH_FAILING?
    + */
    + rtn = scsi_check_paths(SCSI_PATH_STATE_DEAD, &pathid, scmd);
    + return rtn;
    + }
    + /*
    + * Now, check the status byte to see if this indicates anything special.
    + */
    + switch (status_byte(scmd->result)) {
    + case QUEUE_FULL:
    + /*
    + * The case of trying to send too many commands to a tagged
    + * queueing device.
    + */
    + /* fallthrough */
    + case BUSY:
    + /*
    + * device can't talk to us at the moment. Should only
    + * occur (SAM-3) when the task queue is empty, so will cause
    + * the empty queue handling to trigger a stall in the
    + * device.
    + */
    + (void) scsi_check_paths(SCSI_PATH_STATE_GOOD, &pathid, scmd);
    + return ADD_TO_MLQUEUE;
    + case GOOD:
    + case COMMAND_TERMINATED:
    + /*
    + * Optimize IO completion with no errors case by not calling
    + * scsi_check_paths here. This is OK as long as there is no
    + * SCSI_PATH_STATE_FAILING support.
    + */
    + return SUCCESS;
    + case CHECK_CONDITION:
    + rtn = scsi_check_sense(scmd);
    + if (rtn == NEEDS_RETRY)
    + goto maybe_retry;
    + (void) scsi_check_paths(SCSI_PATH_STATE_GOOD, &pathid, scmd);
    + return rtn;
    + case CONDITION_GOOD:
    + case INTERMEDIATE_GOOD:
    + case INTERMEDIATE_C_GOOD:
    + /*
    + * Who knows? FIXME(eric)
    + */
    + (void) scsi_check_paths(SCSI_PATH_STATE_GOOD, &pathid, scmd);
    + return SUCCESS;
    + case RESERVATION_CONFLICT:
    + printk(KERN_ERR "scsi%d (%d,%d,%d) : RESERVATION CONFLICT\n",
    + scmd->host->host_no, scmd->channel,
    + scmd->target, scmd->lun);
    + (void) scsi_check_paths(SCSI_PATH_STATE_GOOD, &pathid, scmd);
    + return SUCCESS; /* causes immediate I/O error */
    + default:
    + (void) scsi_check_paths(SCSI_PATH_STATE_DEAD, &pathid, scmd);
    + return FAILED;
    + }
    + /*
    + * Dead code.
    + */
    + return FAILED;
    +
    + maybe_retry:
    +
    + return scsi_check_paths(SCSI_PATH_STATE_GOOD, &pathid, scmd);
    +}
    +
    +#ifdef CONFIG_PROC_FS
    +
    +/*
    + * FIXME Add locking to protect paths access via /proc.
    + */
    +
    +/*
    + * FIXME: generally, use driverfs for attributes rather than procfs.
    + */
    +
    +/**
    + * scsi_paths_modify - modify a path of a Scsi_Device.
    + *
    + * @sdev: Scsi_Device pointer to the device whose path is being
    + * modified.
    + * @host_no: Host number of path
    + * @channel: Channel value for lookup
    + * @dev: Id value for lookup
    + * @lun: Lun value for lookup
    + * @new_state: New value for the path's sp_state
    + * @new_failures: New value for the path's sp_failures count
    + * @new_weight: New value for the path's sp_weight
    + *
    + * Description:
    + * For @sdev, modify any path matching the path id with the new values
    + * passed. This is currently only used by the proc interface, but is
    + * likely useful for any user level interface (sysctl or ioctl).
    + **/
    +static void scsi_paths_modify(Scsi_Device *sdev, unsigned int host_no,
    + unsigned int channel, unsigned int id,
    + unsigned int lun, enum scsi_path_state new_state,
    + unsigned int new_failures,
    + unsigned int new_weight)
    +{
    + struct scsi_mpath *mpath;
    + struct list_head *lh;
    + struct scsi_path *pathcur;
    + unsigned int prev_path_state;
    +
    + if ((sdev == NULL) || (sdev->sdev_paths == NULL))
    + return;
    +
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + list_for_each(lh, &mpath->scsi_mp_paths_head) {
    + pathcur = list_entry(lh, struct scsi_path, sp_path_list);
    + if ((host_no == SCSI_FIND_ALL_HOST_NO ||
    + host_no == pathcur->sp_path_id.spi_shpnt->host_no) &&
    + (channel == SCSI_FIND_ALL_CHANNEL ||
    + channel == pathcur->sp_path_id.spi_channel) &&
    + (id == SCSI_FIND_ALL_ID ||
    + id == pathcur->sp_path_id.spi_id) &&
    + (lun == SCSI_FIND_ALL_LUN ||
    + lun == pathcur->sp_path_id.spi_lun)) {
    + prev_path_state = pathcur->sp_state;
    + pathcur->sp_state = new_state;
    + if (prev_path_state != new_state) {
    + if (new_state == SCSI_PATH_STATE_GOOD) {
    + int nid;
    +
    + printk(KERN_WARNING "scsi: setting path host %d"
    + " channel %d id %d lun %d"
    + " to good\n",
    + pathcur->sp_path_id.spi_shpnt->host_no,
    + pathcur->sp_path_id.spi_channel,
    + pathcur->sp_path_id.spi_id,
    + pathcur->sp_path_id.spi_lun);
    + nid = scsihost_to_node(pathcur->sp_path_id.spi_shpnt);
    + scsi_add_active_path(sdev, pathcur,
    + nid);
    + if (nid != SCSI_PATHS_ALL_LIST)
    + /*
    + * CONFIG_NUMA
    + */
    + scsi_add_active_path(sdev,
    + pathcur,
    + SCSI_PATHS_ALL_LIST);
    + } else if (new_state == SCSI_PATH_STATE_FAILING) {
    + /*
    + * XXX handle failing case.
    + */
    + printk(KERN_WARNING "scsi: setting path host %d"
    + " channel %d id %d lun %d"
    + " to failing\n",
    + pathcur->sp_path_id.spi_shpnt->host_no,
    + pathcur->sp_path_id.spi_channel,
    + pathcur->sp_path_id.spi_id,
    + pathcur->sp_path_id.spi_lun);
    + scsi_remove_active_path(sdev, pathcur);
    + } else {
    + /*
    + * SCSI_PATH_STATE_DEAD
    + */
    + printk(KERN_WARNING "scsi: setting path host %d"
    + " channel %d id %d lun %d"
    + " to failed\n",
    + pathcur->sp_path_id.spi_shpnt->host_no,
    + pathcur->sp_path_id.spi_channel,
    + pathcur->sp_path_id.spi_id,
    + pathcur->sp_path_id.spi_lun);
    + scsi_remove_active_path(sdev, pathcur);
    + }
    + }
    + if (pathcur->sp_failures != new_failures) {
    + pathcur->sp_failures = new_failures;
    + /*
    + * XXX handle failing case
    + */
    + }
    + if (pathcur->sp_weight != new_weight) {
    + pathcur->sp_weight = new_weight;
    + /*
    + * XXX call a function to re-check active
    + * paths.
    + */
    + }
    + }
    + }
    +}
    +
    +#include <linux/proc_fs.h>
    +static struct proc_dir_entry *scsi_paths_proc_mpp = NULL;
    +static const char *scsi_paths_proc_dirname = "scsi_path";
    +static const char *scsi_paths_proc_leaf_names[] = { "paths", "version" };
    +
    +static int scsi_paths_proc_paths_read(char *buffer, char **start, off_t offset,
    + int size, int *eof, void *data);
    +static int scsi_paths_proc_paths_write(struct file *filp, const char *buffer,
    + unsigned long count, void *data);
    +static int scsi_paths_proc_version_read(char *buffer, char **start,
    + off_t offset, int size, int *eof,
    + void *data);
    +static read_proc_t *scsi_paths_proc_leaf_reads[] = {
    + scsi_paths_proc_paths_read,
    + scsi_paths_proc_version_read
    +};
    +
    +static write_proc_t *scsi_paths_proc_leaf_writes[] = {
    + scsi_paths_proc_paths_write,
    + NULL /* version write */
    +};
    +
    +/*
    + * These two macros where copied from
    + * Douglas Gilbert's sg.c
    + */
    +#define PRINT_PROC(fmt,args...) \
    + do { \
    + *len += sprintf(buffer + *len, fmt, ##args); \
    + if (*begin + *len > offset + size) \
    + return 0; \
    + if (*begin + *len < offset) { \
    + *begin += *len; \
    + *len = 0; \
    + } \
    + } while(0)
    +
    +#define SP_PROC_READ_FN(infofp) \
    + do { \
    + int len = 0; \
    + off_t begin = 0; \
    + *eof = infofp(buffer, &len, &begin, offset, size); \
    + if (offset >= (begin + len)) \
    + return 0; \
    + *start = buffer + offset - begin; \
    + return (size < (begin + len - offset)) ? \
    + size : begin + len - offset; \
    + } while(0)
    +
    +int scsi_paths_proc_init(void)
    +{
    + int k, mask;
    + int leaves = sizeof(scsi_paths_proc_leaf_names) /
    + sizeof(scsi_paths_proc_leaf_names[0]);
    + struct proc_dir_entry *pdep;
    +
    + if (!proc_scsi)
    + return 1;
    + scsi_paths_proc_mpp = create_proc_entry(scsi_paths_proc_dirname,
    + S_IFDIR | S_IRUGO | S_IXUGO,
    + proc_scsi);
    + if (!scsi_paths_proc_mpp)
    + return 1;
    + for (k = 0; k < leaves; k++) {
    + mask = scsi_paths_proc_leaf_writes[k] ? S_IRUGO | S_IWUSR :
    + S_IRUGO;
    + pdep = create_proc_entry(scsi_paths_proc_leaf_names[k], mask,
    + scsi_paths_proc_mpp);
    + if (pdep) {
    + pdep->read_proc = scsi_paths_proc_leaf_reads[k];
    + /*
    + * scsi_paths_proc_leaf_writes[k] can be NULL.
    + */
    + pdep->write_proc = scsi_paths_proc_leaf_writes[k];
    + }
    + }
    +
    + return 0;
    +}
    +
    +void scsi_paths_proc_cleanup(void)
    +{
    + int k;
    + int leaves =
    + sizeof(scsi_paths_proc_leaf_names) /
    + sizeof(scsi_paths_proc_leaf_names[0]);
    +
    + if ((!proc_scsi) || (!scsi_paths_proc_mpp))
    + return;
    + for (k = 0; k < leaves; ++k)
    + remove_proc_entry(scsi_paths_proc_leaf_names[k],
    + scsi_paths_proc_mpp);
    + remove_proc_entry(scsi_paths_proc_dirname, proc_scsi);
    +}
    +
    +int scsi_paths_proc_print_paths(Scsi_Device *sdev, char *buffer,
    + char *format)
    +{
    + int len = 0;
    + struct list_head *lh;
    + struct scsi_mpath *mpath;
    + struct scsi_path *pathcur;
    +
    + if (sdev->sdev_paths != NULL) {
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + list_for_each(lh, &mpath->scsi_mp_paths_head) {
    + pathcur =
    + list_entry(lh, struct scsi_path, sp_path_list);
    + len += sprintf(buffer + len, format,
    + pathcur->sp_path_id.spi_shpnt->host_no,
    + pathcur->sp_path_id.spi_channel,
    + pathcur->sp_path_id.spi_id,
    + pathcur->sp_path_id.spi_lun);
    + }
    + }
    + return len;
    +}
    +
    +static int scsi_paths_proc_paths_info(char * buffer, int * len, off_t * begin,
    + off_t offset, int size)
    +{
    + Scsi_Device *sdev;
    + struct scsi_traverse_hndl strav_hndl;
    +
    + scsi_for_all_sdevs(&strav_hndl, sdev) {
    + if (sdev->sdev_paths != NULL) {
    + struct list_head *lh;
    + struct scsi_mpath *mpath;
    + struct scsi_path *pathcur;
    +
    + /*
    + * For each path of each device, display:
    + *
    + * name:host_no:chan:id:lun:state:failures:weight
    + *
    + * The name can now includes spaces, so add a ':'
    + * separator.
    + */
    + mpath = (struct scsi_mpath *) sdev->sdev_paths;
    + list_for_each(lh, &mpath->scsi_mp_paths_head) {
    + pathcur = list_entry(lh, struct scsi_path,
    + sp_path_list);
    + PRINT_PROC("%s", sdev->sdev_driverfs_dev.name);
    + PRINT_PROC(":%x:%x:%x:%x:%x:%x:%x\n",
    + pathcur->sp_path_id.spi_shpnt->
    + host_no,
    + pathcur->sp_path_id.spi_channel,
    + pathcur->sp_path_id.spi_id,
    + pathcur->sp_path_id.spi_lun,
    + pathcur->sp_state,
    + pathcur->sp_failures,
    + pathcur->sp_weight);
    + }
    + }
    + }
    +
    + return 1;
    +}
    +
    +static int scsi_paths_proc_paths_read(char * buffer, char ** start,
    + off_t offset, int size, int * eof,
    + void * data)
    +{
    + SP_PROC_READ_FN(scsi_paths_proc_paths_info);
    +}
    +
    +static int scsi_paths_proc_paths_write(struct file *filp, const char *buffer,
    + unsigned long count, void *data)
    +{
    + int res;
    + unsigned int host_no, channel, id, lun;
    + unsigned int path_failures, path_weight;
    + enum scsi_path_state path_state;
    + unsigned char *buff_in = NULL;
    + unsigned char *name_id = NULL;
    + unsigned char *ch_in, *ch_out;
    + Scsi_Device *sdev;
    +
    + if (!capable(CAP_SYS_ADMIN))
    + return -EACCES;
    +
    + /*
    + * We need one more byte for the '\0'
    + */
    + buff_in = kmalloc(count + 1, GFP_KERNEL);
    + if (buff_in == NULL) {
    + printk(KERN_WARNING "%s: could not malloc\n", __FUNCTION__);
    + count = -ENOMEM;
    + goto done;
    + }
    + name_id = kmalloc(count + 1, GFP_KERNEL);
    + if (name_id == NULL) {
    + printk(KERN_WARNING "%s: could not malloc\n", __FUNCTION__);
    + count = -ENOMEM;
    + goto done;
    + }
    + name_id[0] = '\0';
    +
    + if (copy_from_user(buff_in, buffer, count)) {
    + count = -EFAULT;
    + goto done;
    + }
    + buff_in[count] = '\0';
    +
    + /*
    + * For now, only support path state modification by passing in a
    + * long string. The input string must be 8 values of the form:
    + *
    + * name_id:host_no:chan:id:lun:path_state:path_failures:path_weight
    + *
    + * name_id is an ASCII string, each of the other values must be hex.
    + *
    + * For driverfs, maybe have each path have a field for each value of
    + * interest; this might be a bit execessive, as we will have (at
    + * least) 3 values for each path for each device, more if we also
    + * want to display non-writable values. For example, with 8 paths,
    + * this is 24 values per Scsi_Device; with 100 Scsi_Devices, this
    + * is 800 values (just path attributes).
    + */
    +
    + /*
    + * Since sscanf stops parsing at a space character, pull out the
    + * name_id first.
    + */
    + ch_in = buff_in;
    + ch_out = name_id;
    + while (*ch_in != '\0' && *ch_in != ':')
    + *ch_out++ = *ch_in++;
    + *ch_out = '\0';
    +
    + if (*ch_in == '\0') {
    + /*
    + * No ':' at all.
    + */
    + count = -EINVAL;
    + goto done;
    + }
    +
    + res = sscanf(ch_in, ":%x:%x:%x:%x:%x:%x:%x", &host_no, &channel, &id,
    + &lun, (unsigned int *) &path_state, &path_failures,
    + &path_weight);
    + if ((res != 7) || ((path_state != SCSI_PATH_STATE_GOOD) &&
    + (path_state != SCSI_PATH_STATE_FAILING) &&
    + (path_state != SCSI_PATH_STATE_DEAD))) {
    + count = -EINVAL;
    + goto done;
    + }
    +
    + sdev = scsi_lookup_id(name_id, NULL);
    + if (sdev != NULL)
    + scsi_paths_modify(sdev, host_no, channel, id, lun, path_state,
    + path_failures, path_weight);
    +
    + done:
    + if (buff_in != NULL)
    + kfree(buff_in);
    + if (name_id != NULL)
    + kfree(name_id);
    + return count;
    +}
    +
    +static int scsi_paths_proc_version_info(char *buffer, int *len, off_t *begin,
    + off_t offset, int size)
    +{
    + PRINT_PROC("%s\n", SCSI_PATHS_VERSION);
    + return 1;
    +}
    +
    +static int scsi_paths_proc_version_read(char *buffer, char **start,
    + off_t offset, int size, int *eof,
    + void *data)
    +{
    + SP_PROC_READ_FN(scsi_paths_proc_version_info);
    +}
    +
    +#endif /* CONFIG_PROC_FS */
    -
    To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
    the body of a message to majordomo@vger.kernel.org
    More majordomo info at http://vger.kernel.org/majordomo-info.html
    Please read the FAQ at http://www.tux.org/lkml/
    \
     
     \ /
      Last update: 2005-03-22 13:30    [W:0.183 / U:2.792 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site