add libiscsi
RFC PATCH 1/5 add libiscsi
Add libiscsi code. These functions are usable by HW or SW iscsi LLDs.
Signed-off-by Mike Christie <[email protected]>
drivers/scsi/libiscsi.c +473 -0
@@ -0,0 +1,473 @@ +/* +* iSCSI lib functions +* +* Copyright (C) IBM Corporation, 2004 +* Copyright (C) Mike Christie, 2004 - 2005 +* Copyright (C) Dmitry Yusupov, 2004 - 2005 +* Copyright (C) Alex Aizman, 2004 - 2005 +* +* 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. +* +* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_transport.h> +#include <scsi/scsi_transport_iscsi.h> +#include <scsi/iscsi_session.h> + +#define dev_to_ihost (_dev) \ + container_of(_dev, struct iscsi_host, dev) + +static LIST_HEAD (iscsi_hosts); +static DEFINE_SPINLOCK (iscsi_hosts_lock); + +static void iscsi_host_release (struct device *dev) +{ +struct iscsi_host *ihost =dev_to_ihost (dev); +kfree (ihost); +} + +/** +* iscsi_alloc_iscsi_host - allocate iscsi host +* iscsi_transport: iscsi_transport +* scsi_transport: scsi_transport returned from iscsi transport registration +* gpf: allocation mask +* +* HW iSCSI LLDs should allocte one of these per PCI device in their probe. +* SW iSCSI can allocate one per module/node or one per resource if it +* is being bound to HW resrouce. +**/ +struct iscsi_host * +iscsi_alloc_iscsi_host (struct iscsi_transport *iscsi_transport, +struct scsi_transport_template *scsi_transport) +{ +struct iscsi_host *ihost; +struct device *dev; + + ihost =kzalloc (sizeof (*ihost) + iscsi_transport->ihostdata_size, +GFP_KERNEL ); +if (!ihost) +return NULL ; + + ihost->scsi_transport = scsi_transport; + ihost->iscsi_transport = iscsi_transport; +/* +* The LLD can set the can_queue to HW resource per card limit +* or per target (for virtual LLDs) and we make sure the +* the correct number of commands gets to them. +*/ + ihost->cmds_per_iscsihost = iscsi_transport->host_template->can_queue; +INIT_LIST_HEAD (&ihost->sessions); +INIT_LIST_HEAD (&ihost->list); +spin_lock_init (&ihost->session_lock); + +if (iscsi_transport->ihostdata_size) + ihost->dd_data = &ihost[1 ]; + + dev = &ihost->dev; +device_initialize (dev); + dev->release = iscsi_host_release; +return ihost; +} + +EXPORT_SYMBOL_GPL (iscsi_alloc_iscsi_host); + +/** +* iscsi_add_iscsi_host - complete setup by adding the host +* @ihost: iscsi host +* @dev: parent device +* @name: if there is no parent, name must be a unique name for this host +* +* This should be called after the host has been setup. +**/ +int iscsi_add_iscsi_host (struct iscsi_host *ihost,struct device *dev, +const char *name) +{ +unsigned long flags; +int err; + +if (dev) { +strlcpy (ihost->dev.bus_id, dev->bus_id,BUS_ID_SIZE ); + ihost->dev.parent = dev; + }else { +strlcpy (ihost->dev.bus_id, name,BUS_ID_SIZE ); + ihost->dev.parent = &platform_bus; + } + + err =device_add (&ihost->dev); +if (err) +return err; + +spin_lock_irqsave (&iscsi_hosts_lock, flags); +list_add (&ihost->list, &iscsi_hosts); +spin_unlock_irqrestore (&iscsi_hosts_lock, flags); + +return err; +} + +EXPORT_SYMBOL_GPL (iscsi_add_iscsi_host); + +/** +* iscsi_remove_iscsi_host - remove iscsi host +* @ihost: iscsi host +**/ +void iscsi_remove_iscsi_host (struct iscsi_host *ihost) +{ +unsigned long flags; + +spin_lock_irqsave (&iscsi_hosts_lock, flags); +list_del (&ihost->list); +spin_unlock_irqrestore (&iscsi_hosts_lock, flags); +device_del (&ihost->dev); +} + +EXPORT_SYMBOL_GPL (iscsi_remove_iscsi_host); + +static void iscsi_task_done (struct scsi_cmnd *cmd) +{ +struct scsi_device *sdev = cmd->device; +struct iscsi_session *session =hostdata_session (sdev->host->hostdata); +unsigned long flags; + +/* +* Bleh. Must think of something else +*/ +spin_lock_irqsave (&session->ihost->session_lock, flags); + session->ihost->cmds_queued--; +spin_unlock_irqrestore (&session->ihost->session_lock, flags); + + cmd->scsi_done (cmd); +} + +/** +* iscsi_scsi_queuecmd - queue command to scsi_host +* @cmd: scsi cmd +* @done: scsi done function +* +* At this time we only check that scsi limits are honored. When iSER and +* Qlogic are closer to being merged we can move more state checking +* (iscsi eh state and iscsi sequence number checks) and iscsi_task +* allocation here so we can all share that code. Hopefully, we can +* share some TMF code with SAS. +**/ +int iscsi_scsi_queuecmd (struct scsi_cmnd *cmd,void (*done)(struct scsi_cmnd *)) +{ +struct scsi_device *sdev = cmd->device; +struct Scsi_Host *shost = sdev->host; +struct iscsi_session *session =hostdata_session (shost->hostdata); +struct iscsi_host *ihost = session->ihost; +int ret =0 ; + +spin_unlock (shost->host_lock); +spin_lock (&ihost->session_lock); + +if (ihost->cmds_queued >= ihost->cmds_per_iscsihost) { + ret =SCSI_MLQUEUE_HOST_BUSY ; + goto done; + } + ihost->cmds_queued++; + + cmd->scsi_done = done; + ret = ihost->iscsi_transport->execute_task (cmd, iscsi_task_done); +if (!ret) + goto done; + ihost->cmds_queued--; + +switch (ret) { +case ISCSI_FAILURE_WINDOW_CLOSED : + ret =SCSI_MLQUEUE_HOST_BUSY ; +break ; +default : + cmd->result = (DID_NO_CONNECT <<16 ); + cmd->resid = cmd->request_bufflen; +done (cmd); + ret =0 ; + } + +done: +spin_unlock (&ihost->session_lock); +spin_lock (shost->host_lock); +return ret; +} + +EXPORT_SYMBOL_GPL (iscsi_scsi_queuecmd); + +struct iscsi_host *iscsi_find_iscsi_host (const char *bus_id) +{ +unsigned long flags; +struct iscsi_host *ihost; + +spin_lock_irqsave (&iscsi_hosts_lock, flags); +list_for_each_entry (ihost, &iscsi_hosts, list) { +if (!strncmp (ihost->dev.bus_id, bus_id,BUS_ID_SIZE )) + goto done; + } + ihost =NULL ; + done: +spin_unlock_irqrestore (&iscsi_hosts_lock, flags); +return ihost; +} + +EXPORT_SYMBOL_GPL (iscsi_find_iscsi_host); + +/** +* iscsi_host_put - release handle to iscsi host +* @ihost: iscsi host +**/ +void iscsi_host_put (struct iscsi_host *ihost) +{ +put_device (&ihost->dev); +} + +EXPORT_SYMBOL_GPL (iscsi_host_put); + +/* +* iSCSI Session's hostdata organization: +* +* *------------------* <== host->hostdata +* | transport | +* |------------------| <== iscsi_hostdata(host->hostdata) +* | transport's data | +* |------------------| <== hostdata_session(host->hostdata) +* | classes's data | +* *------------------* +*/ + +#define hostdata_privsize (_t) (sizeof(unsigned long) + _t->hostdata_size + \ + _t->hostdata_size % sizeof(unsigned long) + \ + sizeof(struct iscsi_session)) + +static void iscsi_session_release (struct device *dev) +{ +struct iscsi_session *session =iscsi_dev_to_session (dev); +struct iscsi_transport *transport = session->ihost->iscsi_transport; +struct Scsi_Host *shost; + + shost =iscsi_session_to_shost (session); +scsi_host_put (shost); +module_put (transport->owner); +} + +int iscsi_is_session_dev (const struct device *dev) +{ +return dev->release == iscsi_session_release; +} + +/** +* iscsi_create_session - create iscsi class session +* @ihost: iscsi host +* @initial_cmdsn: initial iSCSI CmdSN +* +* This can be called from a LLD or iscsi_interface +**/ +struct iscsi_session * +iscsi_create_session (struct iscsi_host *ihost,uint32_t initial_cmdsn) +{ +struct iscsi_transport *transport = ihost->iscsi_transport; +struct iscsi_session *session; +struct Scsi_Host *shost; +unsigned long flags; +int err; + +if (!try_module_get (transport->owner)) +return NULL ; + + shost =scsi_host_alloc (transport->host_template, +hostdata_privsize (transport)); +if (!shost) { +printk (KERN_ERR "iscsi: can not allocate SCSI host for " +"session\n" ); + goto out_module_put; + } + + shost->max_id =1 ; + shost->max_channel =0 ; + shost->max_lun = transport->max_lun; + shost->max_cmd_len = transport->max_cmd_len; + shost->transportt = ihost->scsi_transport; + +/* store struct iscsi_transport in hostdata */ + *(uint64_t *)shost->hostdata =iscsi_handle (transport); + +/* initialize session */ +if (transport->create_session) { + err = transport->create_session (ihost, shost, initial_cmdsn); +if (err) + goto out_host_put; + } + + session =hostdata_session (shost->hostdata); +INIT_LIST_HEAD (&session->list); + session->ihost = ihost; + + err =scsi_add_host (shost, &ihost->dev); +if (err) + goto out_destroy_session; + +/* this is released in the dev's release function */ +scsi_host_get (shost); +snprintf (session->dev.bus_id,BUS_ID_SIZE ,"session%u" , shost->host_no); + session->dev.parent = &shost->shost_gendev; + session->dev.release = iscsi_session_release; + err =device_register (&session->dev); +if (err) { +dev_printk (KERN_ERR , &session->dev,"iscsi: could not " +"register session's dev\n" ); + goto out_remove_host; + } +transport_register_device (&session->dev); + + +/* add this session to the list of active sessions */ +spin_lock_irqsave (&ihost->session_lock, flags); +list_add (&session->list, &ihost->sessions); +spin_unlock_irqrestore (&ihost->session_lock, flags); + +return session; + +out_remove_host: +scsi_remove_host (shost); +out_destroy_session: +if (transport->destroy_session) + transport->destroy_session (ihost, shost); +out_host_put: +scsi_host_put (shost); +out_module_put: +module_put (transport->owner); +return NULL ; +} + +EXPORT_SYMBOL_GPL (iscsi_create_session); + +/** +* iscsi_destroy_session - destroy iscsi session +* @session: iscsi_session +* +* Can be called by a LLD or iscsi_interface. There must not be +* any running connections. +**/ +int iscsi_destroy_session (struct iscsi_session *session) +{ +struct iscsi_host *ihost = session->ihost; +struct iscsi_transport *transport = ihost->iscsi_transport; +struct Scsi_Host *shost; +unsigned long flags; + + shost =iscsi_session_to_shost (session); +scsi_remove_host (shost); + +if (transport->destroy_session) + transport->destroy_session (session->ihost, shost); + +transport_unregister_device (&session->dev); +device_unregister (&session->dev); + +/* remove this session from the list of active sessions */ +spin_lock_irqsave (&ihost->session_lock, flags); +list_del (&session->list); +spin_unlock_irqrestore (&ihost->session_lock, flags); + +/* +* ref from host alloc (there was an extra get from the session->dev +* relased in driver model release +*/ +scsi_host_put (shost); +return 0 ; +} + +EXPORT_SYMBOL_GPL (iscsi_destroy_session); + +static void iscsi_conn_release (struct device *dev) +{ +struct iscsi_conn *conn =iscsi_dev_to_conn (dev); +struct device *parent = conn->dev.parent; + +kfree (conn); +put_device (parent); +} + +int iscsi_is_conn_dev (const struct device *dev) +{ +return dev->release == iscsi_conn_release; +} + +struct iscsi_conn * +iscsi_create_conn (struct iscsi_session *session,uint32_t cid) +{ +struct iscsi_transport *transport = session->ihost->iscsi_transport; +struct Scsi_Host *shost =iscsi_session_to_shost (session); +struct iscsi_conn *conn; +int err; + + conn =kzalloc (sizeof (*conn) + transport->conndata_size,GFP_KERNEL ); +if (!conn) +return NULL ; + +if (transport->conndata_size) + conn->dd_data = &conn[1 ]; + +INIT_LIST_HEAD (&conn->conn_list); + conn->transport = transport; + +if (transport->create_conn) { +if (transport->create_conn (shost, conn->dd_data, cid)) + goto free_conn; + } + +/* this is released in the dev's release function */ +if (!get_device (&session->dev)) + goto destroy_conn; +snprintf (conn->dev.bus_id,BUS_ID_SIZE ,"connection%d:%u" , + shost->host_no, cid); + conn->dev.parent = &session->dev; + conn->dev.release = iscsi_conn_release; + err =device_register (&conn->dev); +if (err) { +dev_printk (KERN_ERR , &conn->dev,"iscsi: could not register " +"connection's dev\n" ); + goto release_parent_ref; + } +transport_register_device (&conn->dev); +return conn; + +release_parent_ref: +put_device (&session->dev); +destroy_conn: +if (transport->destroy_conn) + transport->destroy_conn (conn->dd_data); +free_conn: +kfree (conn); +return NULL ; +} + +EXPORT_SYMBOL_GPL (iscsi_create_conn); + +int iscsi_destroy_conn (struct iscsi_conn *conn) +{ +struct iscsi_session *session =iscsi_dev_to_session (conn->dev.parent); +struct iscsi_transport *transport = session->ihost->iscsi_transport; + +if (transport->destroy_conn) + transport->destroy_conn (conn->dd_data); + +transport_unregister_device (&conn->dev); +device_unregister (&conn->dev); +return 0 ; +} + +EXPORT_SYMBOL_GPL (iscsi_destroy_conn);
include/scsi/iscsi_session.h +90 -0
90 lines not on this page · raw message
include/scsi/scsi_transport_iscsi.h +65 -8
106 lines not on this page · raw message