Skip to contentSkip to conversation

add libiscsi

RFC PATCH New Mike Christie 1 participant 1 message Nov 20, 2005

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/5 add libiscsi

Add libiscsi code. These functions are usable by HW or SW iscsi LLDs.

drivers/scsi/libiscsi.c +473 -0
diff -Naurp linux-2.6.15-rc2/drivers/scsi/libiscsi.c linux-2.6.15-rc2.diff/drivers/scsi/libiscsi.c
@@ -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