linux-iscsi driver
RFC PATCH 3/10 linux-iscsi driver
Driver file implementing the SCSI template functions and module initialization.
drivers/scsi/iscsi-sfnet/iscsi-initiator.c +581 -0
@@ -0,0 +1,581 @@ +/* +* iSCSI driver for Linux +* Copyright (C) 2001 Cisco Systems, Inc. +* Copyright (C) 2004 Mike Christie +* Copyright (C) 2004 IBM Corporation +* maintained by [email protected] +* +* 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. See the GNU +* General Public License for more details. +* +* See the file COPYING included with this distribution for more details. +* +* This file contains interfaces required by SCSI mid layer, module +* initialization and shutdown routines. +*/ +#include <linux/version.h> +#include <linux/moduleparam.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/in.h> +#include <scsi/scsi_tcq.h> +#include <scsi/scsi_transport.h> +#include <scsi/scsi_transport_iscsi.h> + +#include "iscsi-sfnet.h" +#include "iscsi-session.h" +#include "iscsi-protocol.h" +#include "iscsi-task.h" + +/* +* IMPORTANT NOTE: to prevent deadlock, when holding multiple locks, +* the following locking order must be followed at all times: +* +* session->portal_lock - access to a session's portal info +* session->task_lock - access to a session's collections of tasks +* host_lock - mid-layer acquires before calling queuecommand, +* and eh_*. +* +* Note for grabbing task_lock: queuecommand and eh_timed_out are invoked in +* soft_irq context. The former can be invoked in process context as well. +* Every other function where we grab task_lock, we have process context. +* Hence we use spin_lock in eh_timed_out and spin_lock_bh every where else to +* grab the task lock. +*/ + +MODULE_AUTHOR ("Cisco Systems, Inc." ); +MODULE_DESCRIPTION ("iSCSI initiator" ); +MODULE_LICENSE ("GPL" ); +MODULE_VERSION (ISCSI_DRIVER_VERSION ); + +kmem_cache_t *iscsi_task_cache; +static struct scsi_transport_template *iscsi_transportt; + +/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */ +#define SNA32_CHECK 2147483648UL + +int +iscsi_sna_lt (u32 n1,u32 n2) +{ +return n1 != n2 && ((n1 < n2 && (n2 - n1 <SNA32_CHECK )) || + (n1 > n2 && (n2 - n1 <SNA32_CHECK ))); +} + +/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */ +int +iscsi_sna_lte (u32 n1,u32 n2) +{ +return n1 == n2 || ((n1 < n2 && (n2 - n1 <SNA32_CHECK )) || + (n1 > n2 && (n2 - n1 <SNA32_CHECK ))); +} + +/* mark a scsi_cmnd as having a LUN communication failure */ +static inline void +set_lun_comm_failure (struct scsi_cmnd *sc) +{ + sc->sense_buffer[0 ] =0x70 ; + sc->sense_buffer[2 ] =NOT_READY ; + sc->sense_buffer[7 ] =0x6 ; + sc->sense_buffer[12 ] =0x08 ; + sc->sense_buffer[13 ] =0x00 ; +} + +u32 +iscsi_command_attr (struct scsi_cmnd *cmd) +{ +unsigned int attr =ISCSI_ATTR_UNTAGGED ; +char msg[2 ]; + +if (scsi_populate_tag_msg (cmd, msg) ==2 ) { +switch (msg[0 ]) { +case MSG_SIMPLE_TAG : + attr =ISCSI_ATTR_SIMPLE ; +break ; +case MSG_HEAD_TAG : + attr =ISCSI_ATTR_HEAD_OF_QUEUE ; +break ; +case MSG_ORDERED_TAG : + attr =ISCSI_ATTR_ORDERED ; +break ; + }; + } + +return attr; +} + +static int +iscsi_slave_configure (struct scsi_device *sdev) +{ +int depth =1 , tag =0 ; + +/* +* TODO (one day) - when tcq is not supported we should +* internally queue a command to have one ready to go right +* away when the outstanding one completes. +*/ +if (sdev->tagged_supported) { +scsi_activate_tcq (sdev,ISCSI_CMDS_PER_LUN ); + depth =ISCSI_CMDS_PER_LUN ; + tag =MSG_ORDERED_TAG ; + } + +scsi_adjust_queue_depth (sdev, tag, depth); +return 0 ; +} + +/** +* iscsi_eh_timed_out - handle scsi command timeouts +* sc: scsi command +* +* Note: +* In the normal case where the session is ok or the command was queued +* when the session was down (and it never came up but did not yet timeout +* - TODO use device block/unblock), we will fail the command here if +* we can. For all other race cases where for example the session changed +* state at the same time the command timed out but we had the task lock +* we just ask for more time since those events will clean themselves up. +**/ +static enum scsi_eh_timer_return +iscsi_eh_timed_out (struct scsi_cmnd *sc) +{ +struct Scsi_Host *shost = sc->device->host; +struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; +struct iscsi_task *task; +enum scsi_eh_timer_return ret =EH_RESET_TIMER ; +int result =DID_BUS_BUSY <<16 ; + +spin_lock (&session->task_lock); + + task = (struct iscsi_task *)sc->SCp.ptr; +if (!task) { +/* +* completed while timer was firing +*/ + ret =EH_HANDLED ; + goto done; + } + +if (test_bit (SESSION_ESTABLISHED , &session->control_bits)) + ret =EH_NOT_HANDLED ; +else if (test_bit (SESSION_REPLACEMENT_TIMEDOUT , &session->control_bits)) + result =DID_NO_CONNECT <<16 ; + +if (task->itt ==ISCSI_RSVD_TASK_TAG ) { + sc->result = result; +__iscsi_complete_task (task); + ret =EH_HANDLED ; + } + + done: +spin_unlock (&session->task_lock); + +return ret; +} + +static int +iscsi_eh_abort (struct scsi_cmnd *sc) +{ +struct Scsi_Host *shost = sc->device->host; +struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; +struct iscsi_task *task, *tmf_task; +int ret =FAILED ; + +spin_unlock_irq (shost->host_lock); +spin_lock_bh (&session->task_lock); + +/* +* TODO must fix these type of tests +*/ +if (!test_bit (SESSION_ESTABLISHED , &session->control_bits)) + goto done; + + task = (struct iscsi_task *)sc->SCp.ptr; +if (!task) { +iscsi_host_err (session,"eh_abort cmnd already done\n" ); + ret =SUCCESS ; + goto done; + } + +if (task->itt ==ISCSI_RSVD_TASK_TAG ) { +__iscsi_complete_task (task); + ret =SUCCESS ; + goto done; + } + +/* +* TODO need a iscsi_dev_info +*/ +iscsi_host_info (session,"Sending ABORT TASK for task itt %u\n" , + task->itt); + + tmf_task = session->mgmt_task; +memset (tmf_task,0 ,sizeof (*tmf_task)); +iscsi_init_task (tmf_task); + tmf_task->session = session; + tmf_task->lun = task->lun; +/* +* this will become the refcmdsn +*/ + tmf_task->cmdsn = task->cmdsn; + tmf_task->rtt = task->itt; +set_bit (ISCSI_TASK_ABORT , &tmf_task->flags); + +if (!iscsi_exec_task_mgmt (tmf_task, session->abort_timeout)) { + ret =SUCCESS ; + goto done; + } +/* +* TMF may have failed if the task completed first (check here) +*/ +if (!sc->SCp.ptr) + ret =SUCCESS ; + done: +spin_unlock_bh (&session->task_lock); +spin_lock_irq (shost->host_lock); + +return ret; +} + +static int +iscsi_eh_device_reset (struct scsi_cmnd *sc) +{ +struct Scsi_Host *shost = sc->device->host; +struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; +struct iscsi_task *task; +int ret =FAILED ; + +spin_unlock_irq (shost->host_lock); +spin_lock_bh (&session->task_lock); + +if (!test_bit (SESSION_ESTABLISHED , &session->control_bits)) + goto done; + + task = session->mgmt_task; +memset (task,0 ,sizeof (*task)); +iscsi_init_task (task); + task->session = session; + task->lun = sc->device->lun; +__set_bit (ISCSI_TASK_ABORT_TASK_SET , &task->flags); + +/* +* need a iscsi_dev_info +*/ +iscsi_host_info (session,"Sending ABORT TASK SET\n" ); +if (!iscsi_exec_task_mgmt (task, session->abort_timeout)) { + ret =SUCCESS ; + goto done; + } + +iscsi_init_task (task); +__set_bit (ISCSI_TASK_LU_RESET , &task->flags); + +iscsi_host_info (session,"Sending LU RESET\n" ); +if (!iscsi_exec_task_mgmt (task, session->reset_timeout)) + ret =SUCCESS ; + done: +spin_unlock_bh (&session->task_lock); +spin_lock_irq (shost->host_lock); + +return ret; +} + +static int +iscsi_eh_host_reset (struct scsi_cmnd *sc) +{ +struct Scsi_Host *shost = sc->device->host; +struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; +struct iscsi_task *task; +int ret =FAILED ; + +spin_unlock_irq (shost->host_lock); +spin_lock_bh (&session->task_lock); + +if (!test_bit (SESSION_ESTABLISHED , &session->control_bits)) + goto done; + + task = session->mgmt_task; +memset (task,0 ,sizeof (*task)); +iscsi_init_task (task); + task->session = session; +__set_bit (ISCSI_TASK_TGT_WARM_RESET , &task->flags); + +iscsi_host_info (session,"Sending TARGET WARM RESET\n" ); +if (iscsi_exec_task_mgmt (task, session->reset_timeout)) +/* +* no other options +*/ +iscsi_drop_session (session); + + done: +/* +* if we failed, scsi-ml will put us offline +* and if we were successful it will redrive the +* commands, so we clean everything up from our side +* so scsi-ml can retake ownership of the commands. +* (At this point the tx and rx threads will not be +* touching the commands since either the session +* was dropped or we just did a target reset) +*/ +iscsi_flush_queues (session,ISCSI_MAX_LUNS ,DID_BUS_BUSY ); + +spin_unlock_bh (&session->task_lock); +if (iscsi_wait_for_session (session,0 )) + ret =SUCCESS ; +spin_lock_irq (shost->host_lock); + +return ret; +} + +void +iscsi_complete_command (struct scsi_cmnd *sc) +{ + sc->SCp.ptr =NULL ; + sc->scsi_done (sc); +} + +/** +* iscsi_queuecommand - queuecommand interface for the iSCSI driver. +* @sc: scsi command from the midlayer +* @done: Call back function to be called once the command is executed. +**/ +static int +iscsi_queuecommand (struct scsi_cmnd *sc,void (*done) (struct scsi_cmnd *)) +{ +struct Scsi_Host *host = sc->device->host; +struct iscsi_session *session = (struct iscsi_session *)host->hostdata; +struct iscsi_task *task; +int ret =0 ; + +spin_unlock_irq (host->host_lock); + +/* +* record whether I/O commands have been ever been sent on this +* session, to help us decide when we need the session and should +* retry logins regardless of the login status. Ignore all the +* commands sent by default as part of the LUN being scanned or a +* device being opened, so that sessions that have always been idle +* can be dropped. Of course, this is always true for disks, since +* Linux will do reads looking for a partition table. +*/ +switch (sc->cmnd[0 ]) { +case INQUIRY : +case REPORT_LUNS : +case TEST_UNIT_READY : +case READ_CAPACITY : +case START_STOP : +case MODE_SENSE : +break ; +default : + session->commands_queued =1 ; +break ; + } + +if (test_bit (SESSION_REPLACEMENT_TIMEDOUT , &session->control_bits)) { +if (printk_ratelimit ()) +iscsi_host_warn (session,"lun%u: Session terminating, " +"failing to queue cdb 0x%x and any " +"following commands\n" , sc->device->lun, sc->cmnd[0 ]); + goto fail; + } + +/* make sure we can complete it properly later */ + sc->scsi_done = done; + sc->result =0 ; +memset (&sc->SCp,0 ,sizeof (sc->SCp)); + +spin_lock_bh (&session->task_lock); + +/* +* alloc a task and add it to the pending queue so +* the tx-thread will run it +*/ + task =iscsi_alloc_task (session); +if (!task) { + ret =SCSI_MLQUEUE_HOST_BUSY ; + goto done; + } + + task->lun = sc->device->lun; + task->scsi_cmnd = sc; + sc->SCp.ptr = (char *)task; +list_add_tail (&task->queue, &session->pending_queue); + +iscsi_wake_tx_thread (TX_SCSI_COMMAND , session); + done: +spin_unlock_bh (&session->task_lock); +spin_lock_irq (host->host_lock); +return ret; + + fail: +spin_lock_irq (host->host_lock); + sc->result =DID_NO_CONNECT <<16 ; + sc->resid = sc->request_bufflen; +set_lun_comm_failure (sc); + +done (sc); +return 0 ; +} + +int +iscsi_destroy_host (struct Scsi_Host *shost) +{ +struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + +if (!test_bit (SESSION_CREATED , &session->control_bits)) +return -EINVAL ; + +if (test_and_set_bit (SESSION_RELEASING , &session->control_bits)) +return -EINVAL ; + +scsi_remove_host (shost); +iscsi_destroy_session (session); +scsi_host_put (shost); +return 0 ; +} + +static struct scsi_host_template iscsi_driver_template = { + .name ="SFNet iSCSI driver" , + .proc_name =ISCSI_PROC_NAME , + .module =THIS_MODULE , + .queuecommand = iscsi_queuecommand, + .eh_timed_out = iscsi_eh_timed_out, + .eh_abort_handler = iscsi_eh_abort, + .eh_device_reset_handler = iscsi_eh_device_reset, + .eh_host_reset_handler = iscsi_eh_host_reset, + .skip_settle_delay =1 , + .slave_configure = iscsi_slave_configure, + .this_id =-1 , + .can_queue =ISCSI_CANQUEUE , + .sg_tablesize =ISCSI_MAX_SG , + .cmd_per_lun =ISCSI_CMDS_PER_LUN , + .use_clustering =ENABLE_CLUSTERING , + .max_sectors =256 , + .emulated =1 , + .shost_attrs = iscsi_host_attrs, +}; + +int +iscsi_create_host (struct iscsi_session_ioctl *ioctld) +{ +struct Scsi_Host *shost; +struct iscsi_session *session; +int rc; + + shost =scsi_host_alloc (&iscsi_driver_template,sizeof (*session)); +if (!shost) +return -ENOMEM ; + + shost->max_id =ISCSI_MAX_TARGETS ; + shost->max_lun =ISCSI_MAX_LUNS ; + shost->max_channel =ISCSI_MAX_CHANNELS ; + shost->max_cmd_len =ISCSI_MAX_CMD_LEN ; + shost->transportt = iscsi_transportt; + + session = (struct iscsi_session *)shost->hostdata; +memset (session,0 ,sizeof (*session)); + session->shost = shost; + + rc =iscsi_create_session (session, ioctld); +if (rc) { +scsi_host_put (shost); +return rc; + } + + rc =scsi_add_host (shost,NULL ); +if (rc) { +iscsi_destroy_session (session); +scsi_host_put (shost); +return rc; + } + +scsi_scan_host (shost); +set_bit (SESSION_CREATED , &session->control_bits); + +return 0 ; +} + +/* +* This function must only be called when the sysfs and +* ioctl interfaces are inaccessible. For example when +* the module_exit function is executed the driver's sysfs +* and ioctl entry points will return "no device". +*/ +static void +iscsi_destroy_all_hosts (void ) +{ +struct iscsi_session *session, *tmp; + +list_for_each_entry_safe (session, tmp, &iscsi_sessions, list) +iscsi_destroy_host (session->shost); +} + +static int +iscsi_reboot_notifier_function (struct notifier_block *this, +unsigned long code,void *unused) +{ +iscsi_destroy_all_hosts (); +iscsi_notice ("Driver shutdown completed\n" ); +return NOTIFY_DONE ; +} + +/* XXX move this to driver model shutdown */ +static struct notifier_block iscsi_reboot_notifier = { + .notifier_call = iscsi_reboot_notifier_function, + .next =NULL , + .priority =255 ,/* priority, might need to have a +* relook at the value +*/ +}; + +static int +__init iscsi_init (void ) +{ +iscsi_notice ("%s (%s) built for Linux %s\n" ,ISCSI_DRIVER_VERSION , +ISCSI_DATE ,UTS_RELEASE ); + +/* pool of iscsi tasks */ + iscsi_task_cache =kmem_cache_create ("iscsi_task_cache" , +sizeof (struct iscsi_task ),0 , +SLAB_NO_REAP ,NULL ,NULL ); + +if (!iscsi_task_cache) { +iscsi_err ("kmem_cache_create failed\n" ); +return -ENOMEM ; + } + + iscsi_transportt =iscsi_attach_transport (&iscsi_fnt); +if (!iscsi_transportt) + goto free_cache; + +if (iscsi_register_interface ()) + goto release_transport; + +register_reboot_notifier (&iscsi_reboot_notifier); +return 0 ; + + release_transport: +iscsi_release_transport (iscsi_transportt); + free_cache: +kmem_cache_destroy (iscsi_task_cache); +iscsi_err ("Failed to init driver\n" ); +return -ENODEV ; +} + +static void +__exit iscsi_cleanup(void) +{ +unregister_reboot_notifier (&iscsi_reboot_notifier); +iscsi_unregister_interface (); +iscsi_destroy_all_hosts (); +iscsi_release_transport (iscsi_transportt); +kmem_cache_destroy (iscsi_task_cache); +} +module_init (iscsi_init); +module_exit (iscsi_cleanup);
drivers/scsi/iscsi-sfnet/iscsi-sfnet.h +130 -0
130 lines not on this page · raw message