

  This patch modifies the midlayer for the addition of transport
  objects to be layered between the scsi_host and the scsi_target.
  FC will be the first consumer - placing the remote port between
  the host and the target.

  There are 4 areas of modification:
  - Opening up the starget creation so that transports can
    control the allocation and teardown of SCSI targets.
  - Adding a linked list of targets to the shost, which is searched
    at sdev creation when looking for a target already allocated by
    a transport.
  - Dealing with all the code that expects the device above the scsi
    target to be a scsi host. The dev_to_shost() function is subverted
    to start at the supplied device and check for a scsi host device.
    If not a scsi host, go to the next parent and check again. Allows
    any number of objects between the starget and shost, while providing
    source compatibility.
  - Addition of a scsi_forget_target() routine. The transport will
    have a remove_host() function which will want to tear down the
    scsi devices under whatever transport objects exist. On unload,
    the driver will call the transport_remove_host() function
    immediately prior to the scsi_remove_host() call.
    In transport_remove_host(), in traversing the list of attached
    sdevs, functions such as shost_for_each_device() are unusable,
    as they utilize scsi_device_get(), which attempts to take
    out references on the module, which is marked for deletion.
    Therefore, we use a function that walks the __devices list w/o
    enforcing the references.


  Signed-off-by: James Smart <james.smart@emulex.com>

---

 b/drivers/scsi/hosts.c              |    1 
 b/drivers/scsi/scsi_priv.h          |    6 
 b/drivers/scsi/scsi_scan.c          |   45 +++-
 b/drivers/scsi/scsi_sysfs.c         |  212 +++++++++++++------
 b/drivers/scsi/scsi_transport_spi.c |    2 
 b/include/scsi/scsi_device.h        |    9 
 b/include/scsi/scsi_host.h          |   14 -
 7 files changed, 220 insertions(+), 69 deletions(-)

diff -puN a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
--- a/include/scsi/scsi_device.h	2005-02-08 21:05:49.000000000 -0500
+++ b/include/scsi/scsi_device.h	2005-02-08 21:05:49.000000000 -0500
@@ -139,12 +139,19 @@ struct scsi_device {
  * starget_sdev_user is NULL, else it points to the active sdev.
  */
 struct scsi_target {
+	struct list_head    	siblings; /* list of all targets on this host */
 	struct scsi_device	*starget_sdev_user;
 	struct device		dev;
 	unsigned int		channel;
 	unsigned int		id; /* target id ... replace
 				     * scsi_device.id eventually */
-	unsigned long		create:1; /* signal that it needs to be added */
+	unsigned long		scan_created:1; /* signal that target was
+						 * created by scanning, not
+						 * by a scsi transport.
+						 */
+	unsigned long		create_sysfs:1; /* signal that it needs to be
+						 * added to sysfs
+						 */
 	unsigned long		starget_data[0];
 } __attribute__((aligned(sizeof(unsigned long))));
 
diff -puN a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h
--- a/include/scsi/scsi_host.h	2005-02-08 21:05:49.000000000 -0500
+++ b/include/scsi/scsi_host.h	2005-02-08 21:07:29.000000000 -0500
@@ -417,6 +417,7 @@ struct Scsi_Host {
 	 */
 	struct list_head	__devices;
 	
+	struct list_head	__targets;
 	struct scsi_host_cmd_pool *cmd_pool;
 	spinlock_t		free_list_lock;
 	struct list_head	free_list; /* backup store of cmd structs */
@@ -548,11 +549,19 @@ struct Scsi_Host {
 	unsigned long hostdata[0]  /* Used for storage of host specific stuff */
 		__attribute__ ((aligned (sizeof(unsigned long))));
 };
-#define		dev_to_shost(d)		\
-	container_of(d, struct Scsi_Host, shost_gendev)
 #define		class_to_shost(d)	\
 	container_of(d, struct Scsi_Host, shost_classdev)
 
+int scsi_is_host_device(const struct device *);
+
+static inline struct Scsi_Host * dev_to_shost(struct device *dev)
+{
+	while (!scsi_is_host_device(dev))
+		dev = dev->parent;
+	return container_of(dev, struct Scsi_Host, shost_gendev);
+}
+
+
 
 extern struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *, int);
 extern int __must_check scsi_add_host(struct Scsi_Host *, struct device *);
@@ -596,7 +605,6 @@ struct class_container;
  */
 extern void scsi_free_host_dev(struct scsi_device *);
 extern struct scsi_device *scsi_get_host_dev(struct Scsi_Host *);
-int scsi_is_host_device(const struct device *);
 
 
 /* legacy interfaces */
diff -puN a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
--- a/drivers/scsi/scsi_sysfs.c	2005-02-08 21:05:49.000000000 -0500
+++ b/drivers/scsi/scsi_sysfs.c	2005-02-08 21:05:49.000000000 -0500
@@ -170,12 +170,8 @@ void scsi_device_dev_release(struct devi
 
 	if (delete) {
 		struct scsi_target *starget = to_scsi_target(parent);
-		if (!starget->create) {
-			transport_remove_device(&starget->dev);
-			device_del(parent);
-		}
-		transport_destroy_device(&starget->dev);
-
+		if (starget->scan_created)
+			__scsi_remove_target(starget);
 		put_device(parent);
 	}
 	if (sdev->request_queue)
@@ -534,14 +530,6 @@ static int attr_add(struct device *dev, 
 	return device_create_file(dev, attr);
 }
 
-static void scsi_target_dev_release(struct device *dev)
-{
-	struct scsi_target *starget = to_scsi_target(dev);
-	struct device *parent = dev->parent;
-	kfree(starget);
-	put_device(parent);
-}
-
 /**
  * scsi_sysfs_add_sdev - add scsi device to sysfs
  * @sdev:	scsi_device to add
@@ -551,24 +539,7 @@ static void scsi_target_dev_release(stru
  **/
 int scsi_sysfs_add_sdev(struct scsi_device *sdev)
 {
-	struct scsi_target *starget = sdev->sdev_target;
-	struct Scsi_Host *shost = sdev->host;
-	int error, i, create;
-	unsigned long flags;
-
-	spin_lock_irqsave(shost->host_lock, flags);
-	create = starget->create;
-	starget->create = 0;
-	spin_unlock_irqrestore(shost->host_lock, flags);
-
-	if (create) {
-		error = device_add(&starget->dev);
-		if (error) {
-			printk(KERN_ERR "Target device_add failed\n");
-			return error;
-		}
-		transport_add_device(&starget->dev);
-	}
+	int error, i;
 
 	if ((error = scsi_device_set_state(sdev, SDEV_RUNNING)) != 0)
 		return error;
@@ -764,12 +735,114 @@ int scsi_is_sdev_device(const struct dev
 }
 EXPORT_SYMBOL(scsi_is_sdev_device);
 
+static void scsi_target_dev_release(struct device *dev)
+{
+	struct scsi_target *starget = to_scsi_target(dev);
+	struct device *parent = dev->parent;
+	kfree(starget);
+	put_device(parent);
+}
+
+int scsi_is_target_device(const struct device *dev)
+{
+	return dev->release == scsi_target_dev_release;
+}
+EXPORT_SYMBOL(scsi_is_target_device);
+
+/**
+ * __scsi_alloc_target - allocate and initialize a target object
+ * @shost:	host target is attached to
+ * @channel:	channel on host where target is attached
+ * @id:		target id for target
+ * @parent;	the parent object for the target
+ *
+ * This function assumes that it is called with the shost->host_lock held.
+ *
+ * The callee must call __scsi_setup_target() after calling this routine.
+ *
+ * NOTES:
+ *   The parent may not be the Scsi_Host. The parent may be an
+ *     intervening transport object.
+ *   This routine does not check for the pre-existence of the target.
+ *     It assumes that the callee has already validated the target does
+ *     not exist.
+ *
+ * Return value:
+ * 	NULL on Failure / non-NULL on Success
+ **/
+struct scsi_target *__scsi_alloc_target(struct Scsi_Host *shost,
+				uint channel, uint id, struct device *parent)
+{
+	struct scsi_target *starget;
+	struct device *dev = NULL;
+	const int size = sizeof(*starget) + shost->transportt->target_size;
+
+	starget = kmalloc(size, GFP_ATOMIC);
+	if (unlikely(!starget)) {
+		printk(KERN_ERR "%s: allocation failure\n", __FUNCTION__);
+		return NULL;
+	}
+	memset(starget, 0, size);
+	dev = &starget->dev;
+	device_initialize(dev);
+	dev->parent = get_device(parent);
+	dev->release = scsi_target_dev_release;
+	sprintf(dev->bus_id, "target%d:%d:%d",
+		shost->host_no, channel, id);
+	starget->id = id;
+	starget->channel = channel;
+	starget->create_sysfs = 1;
+	list_add_tail(&starget->siblings, &shost->__targets);
+
+	return starget;
+}
+EXPORT_SYMBOL_GPL(__scsi_alloc_target);
+
+/**
+ * __scsi_setup_target - invoke the setup functions for the target
+ * @starget:	target to be setup
+ *
+ * This function assumes that it is called with no locks held.
+ * (which is why it is not part of __scsi_alloc_target() )
+ *
+ **/
+void __scsi_setup_target(struct scsi_target *starget)
+{
+	transport_setup_device(&starget->dev);
+}
+EXPORT_SYMBOL_GPL(__scsi_setup_target);
+
+/**
+ * __scsi_remove_target - remove and deallocate a target object
+ * @starget:	target to be removed
+ *
+ * This function assumes that it is called with no locks held.
+ *
+ **/
+void __scsi_remove_target(struct scsi_target *starget)
+{
+	struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+	struct device *dev = &starget->dev;
+	unsigned long flags;
+
+	spin_lock_irqsave(shost->host_lock, flags);
+	list_del(&starget->siblings);
+	spin_unlock_irqrestore(shost->host_lock, flags);
+
+	if (!starget->create_sysfs) {
+		transport_remove_device(dev);
+		device_del(dev);
+	}
+	transport_destroy_device(dev);
+	put_device(dev);
+}
+EXPORT_SYMBOL_GPL(__scsi_remove_target);
+
 int scsi_sysfs_target_initialize(struct scsi_device *sdev)
 {
-	struct scsi_target *starget = NULL;
+	struct scsi_target *starget = NULL, *ttarget;
 	struct Scsi_Host *shost = sdev->host;
 	struct scsi_device *device;
-	struct device *dev = NULL;
 	unsigned long flags;
 	int create = 0;
 
@@ -787,27 +860,27 @@ int scsi_sysfs_target_initialize(struct 
 			break;
 		}
 	}
-			
+
 	if (!starget) {
-		const int size = sizeof(*starget) +
-			shost->transportt->target_size;
-		starget = kmalloc(size, GFP_ATOMIC);
+		/*
+		 * Search pre-allocated targets from the transport
+		 */
+		list_for_each_entry(ttarget, &shost->__targets, siblings) {
+			if (ttarget->id == sdev->id &&
+			    ttarget->channel == sdev->channel) {
+			    	starget = ttarget;
+				break;
+			}
+		}
 		if (!starget) {
-			printk(KERN_ERR "%s: allocation failure\n", __FUNCTION__);
-			spin_unlock_irqrestore(shost->host_lock,
-					       flags);
-			return -ENOMEM;
+			starget = __scsi_alloc_target(shost, sdev->channel,
+					sdev->id, &shost->shost_gendev);
+			if (unlikely(!starget)) {
+				spin_unlock_irqrestore(shost->host_lock, flags);
+				return -ENOMEM;
+			}
+			create = starget->scan_created = 1;
 		}
-		memset(starget, 0, size);
-		dev = &starget->dev;
-		device_initialize(dev);
-		dev->parent = get_device(&shost->shost_gendev);
-		dev->release = scsi_target_dev_release;
-		sprintf(dev->bus_id, "target%d:%d:%d",
-			shost->host_no, sdev->channel, sdev->id);
-		starget->id = sdev->id;
-		starget->channel = sdev->channel;
-		create = starget->create = 1;
 		/*
 		 * If there wasn't another lun already configured at
 		 * this target, then default this device to SCSI_2
@@ -821,15 +894,42 @@ int scsi_sysfs_target_initialize(struct 
 	list_add_tail(&sdev->siblings, &shost->__devices);
 	spin_unlock_irqrestore(shost->host_lock, flags);
 	if (create)
-		transport_setup_device(&starget->dev);
+		__scsi_setup_target(starget);
 	return 0;
 }
 
-int scsi_is_target_device(const struct device *dev)
+/**
+ * scsi_sysfs_add_target - add scsi target to sysfs
+ * @starget:	scsi_target to add
+ *
+ * Return value:
+ * 	0 on Success / non-zero on Failure
+ **/
+int scsi_sysfs_add_target(struct scsi_target *starget)
 {
-	return dev->release == scsi_target_dev_release;
+	struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+	struct device *dev = &starget->dev;
+	int error, create;
+	unsigned long flags;
+
+	spin_lock_irqsave(shost->host_lock, flags);
+	create = starget->create_sysfs;
+	starget->create_sysfs = 0;
+	spin_unlock_irqrestore(shost->host_lock, flags);
+
+	if (create) {
+		error = device_add(dev);
+		if (error) {
+			printk(KERN_ERR "Target device_add failed\n");
+			return error;
+		}
+		transport_add_device(dev);
+		transport_configure_device(dev);
+	}
+	return 0;
 }
-EXPORT_SYMBOL(scsi_is_target_device);
+EXPORT_SYMBOL_GPL(scsi_sysfs_add_target);
+
 
 /* A blank transport template that is used in drivers that don't
  * yet implement Transport Attributes */
diff -puN a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
--- a/drivers/scsi/scsi_scan.c	2005-02-08 21:05:49.000000000 -0500
+++ b/drivers/scsi/scsi_scan.c	2005-02-08 21:08:12.000000000 -0500
@@ -256,6 +256,11 @@ static struct scsi_device *scsi_alloc_sd
 
 	scsi_sysfs_device_initialize(sdev);
 
+	/* NOTE: this target initialisation code depends critically on
+	 * lun scanning being sequential. */
+	if (scsi_sysfs_target_initialize(sdev))
+		goto out_remove_siblings;
+
 	if (shost->hostt->slave_alloc) {
 		ret = shost->hostt->slave_alloc(sdev);
 		if (ret) {
@@ -269,11 +274,6 @@ static struct scsi_device *scsi_alloc_sd
 		}
 	}
 
-	/* NOTE: this target initialisation code depends critically on
-	 * lun scanning being sequential. */
-	if (scsi_sysfs_target_initialize(sdev))
-		goto out_remove_siblings;
-
 	return sdev;
 
 out_remove_siblings:
@@ -281,9 +281,6 @@ out_remove_siblings:
 	list_del(&sdev->siblings);
 	list_del(&sdev->same_target_siblings);
 	spin_unlock_irqrestore(shost->host_lock, flags);
-
-	if (shost->hostt->slave_destroy)
-		shost->hostt->slave_destroy(sdev);
 out_device_destroy:
 	transport_destroy_device(&sdev->sdev_gendev);
 	scsi_free_queue(sdev->request_queue);
@@ -622,6 +619,10 @@ static int scsi_add_lun(struct scsi_devi
 
 	transport_configure_device(&sdev->sdev_gendev);
 
+	if (scsi_sysfs_add_target(sdev->sdev_target))
+		/* failure - act as if there is no device */
+		return SCSI_SCAN_NO_RESPONSE;
+
 	if (sdev->host->hostt->slave_configure)
 		sdev->host->hostt->slave_configure(sdev);
 
@@ -1268,6 +1269,34 @@ void scsi_forget_host(struct Scsi_Host *
 	spin_unlock_irqrestore(shost->host_lock, flags);
 }
 
+void scsi_forget_target(struct scsi_target *starget)
+{
+	struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+	struct scsi_device *sdev, *tmp;
+	unsigned long flags;
+
+	/*
+	 * Ok, this look a bit strange.  We always look for the first device
+	 * on the list as scsi_remove_device removes them from it - thus we
+	 * also have to release the lock.
+	 * We don't need to get another reference to the device before
+	 * releasing the lock as we already own the reference from
+	 * scsi_register_device that's release in scsi_remove_device.  And
+	 * after that we don't look at sdev anymore.
+	 */
+	spin_lock_irqsave(shost->host_lock, flags);
+	list_for_each_entry_safe(sdev, tmp, &shost->__devices, siblings) {
+		if ((sdev->channel != starget->channel) ||
+		    (sdev->id != starget->id))
+			continue;
+		spin_unlock_irqrestore(shost->host_lock, flags);
+		scsi_remove_device(sdev);
+		spin_lock_irqsave(shost->host_lock, flags);
+	}
+	spin_unlock_irqrestore(shost->host_lock, flags);
+}
+EXPORT_SYMBOL_GPL(scsi_forget_target);
+
 /*
  * Function:    scsi_get_host_dev()
  *
diff -puN a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h
--- a/drivers/scsi/scsi_priv.h	2005-02-08 21:05:49.000000000 -0500
+++ b/drivers/scsi/scsi_priv.h	2005-02-08 21:05:49.000000000 -0500
@@ -129,6 +129,7 @@ extern void scsi_exit_procfs(void);
 extern int scsi_scan_host_selected(struct Scsi_Host *, unsigned int,
 				   unsigned int, unsigned int, int);
 extern void scsi_forget_host(struct Scsi_Host *);
+extern void scsi_forget_target(struct scsi_target *);
 extern void scsi_rescan_device(struct device *);
 
 /* scsi_sysctl.c */
@@ -147,7 +148,12 @@ extern int scsi_sysfs_add_host(struct Sc
 extern int scsi_sysfs_register(void);
 extern void scsi_sysfs_unregister(void);
 extern void scsi_sysfs_device_initialize(struct scsi_device *);
+extern struct scsi_target *__scsi_alloc_target(struct Scsi_Host *,
+				uint, uint, struct device *);
+extern void __scsi_setup_target(struct scsi_target *);
+extern void __scsi_remove_target(struct scsi_target *);
 extern int scsi_sysfs_target_initialize(struct scsi_device *);
+extern int scsi_sysfs_add_target(struct scsi_target *);
 extern struct scsi_transport_template blank_transport_template;
 
 extern struct class sdev_class;
diff -puN a/drivers/scsi/scsi_transport_spi.c b/drivers/scsi/scsi_transport_spi.c
--- a/drivers/scsi/scsi_transport_spi.c	2005-02-08 21:05:49.000000000 -0500
+++ b/drivers/scsi/scsi_transport_spi.c	2005-02-08 21:05:49.000000000 -0500
@@ -28,8 +28,8 @@
 #include <asm/scatterlist.h>
 #include <asm/io.h>
 #include <scsi/scsi.h>
-#include "scsi_priv.h"
 #include <scsi/scsi_device.h>
+#include "scsi_priv.h"
 #include <scsi/scsi_host.h>
 #include <scsi/scsi_request.h>
 #include <scsi/scsi_eh.h>
diff -puN a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
--- a/drivers/scsi/hosts.c	2005-02-08 21:05:49.000000000 -0500
+++ b/drivers/scsi/hosts.c	2005-02-08 21:05:49.000000000 -0500
@@ -216,6 +216,7 @@ struct Scsi_Host *scsi_host_alloc(struct
 	spin_lock_init(&shost->default_lock);
 	scsi_assign_lock(shost, &shost->default_lock);
 	INIT_LIST_HEAD(&shost->__devices);
+	INIT_LIST_HEAD(&shost->__targets);
 	INIT_LIST_HEAD(&shost->eh_cmd_q);
 	INIT_LIST_HEAD(&shost->starved_list);
 	init_waitqueue_head(&shost->host_wait);
_
