upgrade to fedora-2.6.12-1.1398.FC4 + vserver 2.0.rc7
[linux-2.6.git] / drivers / scsi / scsi_sysfs.c
index 79e47aa..e75ee46 100644 (file)
@@ -15,6 +15,7 @@
 #include <scsi/scsi.h>
 #include <scsi/scsi_device.h>
 #include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
 #include <scsi/scsi_transport.h>
 
 #include "scsi_priv.h"
@@ -153,31 +154,29 @@ void scsi_device_dev_release(struct device *dev)
 {
        struct scsi_device *sdev;
        struct device *parent;
+       struct scsi_target *starget;
        unsigned long flags;
-       int delete;
 
        parent = dev->parent;
        sdev = to_scsi_device(dev);
+       starget = to_scsi_target(parent);
 
        spin_lock_irqsave(sdev->host->host_lock, flags);
-       /* If we're the last LUN on the target, destroy the target */
-       delete = list_empty(&sdev->same_target_siblings);
+       starget->reap_ref++;
        list_del(&sdev->siblings);
        list_del(&sdev->same_target_siblings);
        list_del(&sdev->starved_entry);
        spin_unlock_irqrestore(sdev->host->host_lock, flags);
 
-       if (delete) {
-               struct scsi_target *starget = to_scsi_target(parent);
-               if (!starget->create) {
-                       device_del(parent);
-                       if (starget->transport_classdev.class)
-                               class_device_unregister(&starget->transport_classdev);
-               }
-               put_device(parent);
-       }
-       if (sdev->request_queue)
+       if (sdev->request_queue) {
+               sdev->request_queue->queuedata = NULL;
                scsi_free_queue(sdev->request_queue);
+               /* temporary expedient, try to catch use of queue lock
+                * after free of sdev */
+               sdev->request_queue = NULL;
+       }
+
+       scsi_target_reap(scsi_target(sdev));
 
        kfree(sdev->inquiry);
        kfree(sdev);
@@ -388,13 +387,52 @@ show_state_field(struct device *dev, char *buf)
        return snprintf(buf, 20, "%s\n", name);
 }
 
-DEVICE_ATTR(state, S_IRUGO | S_IWUSR, show_state_field, store_state_field);
+static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, show_state_field, store_state_field);
+
+static ssize_t
+show_queue_type_field(struct device *dev, char *buf)
+{
+       struct scsi_device *sdev = to_scsi_device(dev);
+       const char *name = "none";
+
+       if (sdev->ordered_tags)
+               name = "ordered";
+       else if (sdev->simple_tags)
+               name = "simple";
+
+       return snprintf(buf, 20, "%s\n", name);
+}
+
+static DEVICE_ATTR(queue_type, S_IRUGO, show_queue_type_field, NULL);
+
+static ssize_t
+show_iostat_counterbits(struct device *dev, char *buf)
+{
+       return snprintf(buf, 20, "%d\n", (int)sizeof(atomic_t) * 8);
+}
+
+static DEVICE_ATTR(iocounterbits, S_IRUGO, show_iostat_counterbits, NULL);
+
+#define show_sdev_iostat(field)                                                \
+static ssize_t                                                         \
+show_iostat_##field(struct device *dev, char *buf)                     \
+{                                                                      \
+       struct scsi_device *sdev = to_scsi_device(dev);                 \
+       unsigned long long count = atomic_read(&sdev->field);           \
+       return snprintf(buf, 20, "0x%llx\n", count);                    \
+}                                                                      \
+static DEVICE_ATTR(field, S_IRUGO, show_iostat_##field, NULL)
+
+show_sdev_iostat(iorequest_cnt);
+show_sdev_iostat(iodone_cnt);
+show_sdev_iostat(ioerr_cnt);
 
 
 /* Default template for device attributes.  May NOT be modified */
 static struct device_attribute *scsi_sysfs_sdev_attrs[] = {
        &dev_attr_device_blocked,
        &dev_attr_queue_depth,
+       &dev_attr_queue_type,
        &dev_attr_type,
        &dev_attr_scsi_level,
        &dev_attr_vendor,
@@ -404,9 +442,84 @@ static struct device_attribute *scsi_sysfs_sdev_attrs[] = {
        &dev_attr_delete,
        &dev_attr_state,
        &dev_attr_timeout,
+       &dev_attr_iocounterbits,
+       &dev_attr_iorequest_cnt,
+       &dev_attr_iodone_cnt,
+       &dev_attr_ioerr_cnt,
        NULL
 };
 
+static ssize_t sdev_store_queue_depth_rw(struct device *dev, const char *buf,
+                                        size_t count)
+{
+       int depth, retval;
+       struct scsi_device *sdev = to_scsi_device(dev);
+       struct scsi_host_template *sht = sdev->host->hostt;
+
+       if (!sht->change_queue_depth)
+               return -EINVAL;
+
+       depth = simple_strtoul(buf, NULL, 0);
+
+       if (depth < 1)
+               return -EINVAL;
+
+       retval = sht->change_queue_depth(sdev, depth);
+       if (retval < 0)
+               return retval;
+
+       return count;
+}
+
+static struct device_attribute sdev_attr_queue_depth_rw =
+       __ATTR(queue_depth, S_IRUGO | S_IWUSR, sdev_show_queue_depth,
+              sdev_store_queue_depth_rw);
+
+static ssize_t sdev_store_queue_type_rw(struct device *dev, const char *buf,
+                                       size_t count)
+{
+       struct scsi_device *sdev = to_scsi_device(dev);
+       struct scsi_host_template *sht = sdev->host->hostt;
+       int tag_type = 0, retval;
+       int prev_tag_type = scsi_get_tag_type(sdev);
+
+       if (!sdev->tagged_supported || !sht->change_queue_type)
+               return -EINVAL;
+
+       if (strncmp(buf, "ordered", 7) == 0)
+               tag_type = MSG_ORDERED_TAG;
+       else if (strncmp(buf, "simple", 6) == 0)
+               tag_type = MSG_SIMPLE_TAG;
+       else if (strncmp(buf, "none", 4) != 0)
+               return -EINVAL;
+
+       if (tag_type == prev_tag_type)
+               return count;
+
+       retval = sht->change_queue_type(sdev, tag_type);
+       if (retval < 0)
+               return retval;
+
+       return count;
+}
+
+static struct device_attribute sdev_attr_queue_type_rw =
+       __ATTR(queue_type, S_IRUGO | S_IWUSR, show_queue_type_field,
+              sdev_store_queue_type_rw);
+
+static struct device_attribute *attr_changed_internally(
+               struct Scsi_Host *shost,
+               struct device_attribute * attr)
+{
+       if (!strcmp("queue_depth", attr->attr.name)
+           && shost->hostt->change_queue_depth)
+               return &sdev_attr_queue_depth_rw;
+       else if (!strcmp("queue_type", attr->attr.name)
+           && shost->hostt->change_queue_type)
+               return &sdev_attr_queue_type_rw;
+       return attr;
+}
+
 
 static struct device_attribute *attr_overridden(
                struct device_attribute **attrs,
@@ -444,14 +557,6 @@ static int attr_add(struct device *dev, struct device_attribute *attr)
        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
@@ -461,49 +566,7 @@ static void scsi_target_dev_release(struct device *dev)
  **/
 int scsi_sysfs_add_sdev(struct scsi_device *sdev)
 {
-       struct class_device_attribute **attrs;
-       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;
-               }
-               if (starget->transport_classdev.class) {
-                       int i;
-                       struct class_device_attribute **attrs =
-                               sdev->host->transportt->target_attrs;
-
-                       error = class_device_add(&starget->transport_classdev);
-                       if (error) {
-                               dev_printk(KERN_ERR, &starget->dev,
-                                          "Target transport add failed\n");
-                               return error;
-                       }
-
-                       /* take a reference for the transport_classdev; this
-                        * is released by the transport_class .release */
-                       get_device(&starget->dev);
-                       for (i = 0; attrs[i]; i++) {
-                               error = class_device_create_file(&starget->transport_classdev,
-                                                                attrs[i]);
-                               if (error) {
-                                       dev_printk(KERN_ERR, &starget->dev,
-                                                  "Target transport attr add failed\n");
-                                       return error;
-                               }
-                       }
-               }
-       }
+       int error, i;
 
        if ((error = scsi_device_set_state(sdev, SDEV_RUNNING)) != 0)
                return error;
@@ -514,25 +577,15 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
                printk(KERN_INFO "error 1\n");
                return error;
        }
-
        error = class_device_add(&sdev->sdev_classdev);
        if (error) {
                printk(KERN_INFO "error 2\n");
                goto clean_device;
        }
+
        /* take a reference for the sdev_classdev; this is
         * released by the sdev_class .release */
        get_device(&sdev->sdev_gendev);
-       if (sdev->transport_classdev.class) {
-               error = class_device_add(&sdev->transport_classdev);
-               if (error)
-                       goto clean_device2;
-               /* take a reference for the transport_classdev; this
-                * is released by the transport_class .release */
-               get_device(&sdev->sdev_gendev);
-               
-       }
-
        if (sdev->host->hostt->sdev_attrs) {
                for (i = 0; sdev->host->hostt->sdev_attrs[i]; i++) {
                        error = attr_add(&sdev->sdev_gendev,
@@ -547,8 +600,10 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
        for (i = 0; scsi_sysfs_sdev_attrs[i]; i++) {
                if (!attr_overridden(sdev->host->hostt->sdev_attrs,
                                        scsi_sysfs_sdev_attrs[i])) {
-                       error = device_create_file(&sdev->sdev_gendev,
-                                       scsi_sysfs_sdev_attrs[i]);
+                       struct device_attribute * attr = 
+                               attr_changed_internally(sdev->host, 
+                                                       scsi_sysfs_sdev_attrs[i]);
+                       error = device_create_file(&sdev->sdev_gendev, attr);
                        if (error) {
                                scsi_remove_device(sdev);
                                goto out;
@@ -556,27 +611,15 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
                }
        }
 
-       if (sdev->transport_classdev.class) {
-               attrs = sdev->host->transportt->device_attrs;
-               for (i = 0; attrs[i]; i++) {
-                       error = class_device_create_file(&sdev->transport_classdev,
-                                                        attrs[i]);
-                       if (error) {
-                               scsi_remove_device(sdev);
-                               goto out;
-                       }
-               }
-       }
-
+       transport_add_device(&sdev->sdev_gendev);
  out:
        return error;
 
- clean_device2:
-       class_device_del(&sdev->sdev_classdev);
  clean_device:
        scsi_device_set_state(sdev, SDEV_CANCEL);
 
        device_del(&sdev->sdev_gendev);
+       transport_destroy_device(&sdev->sdev_gendev);
        put_device(&sdev->sdev_gendev);
 
        return error;
@@ -595,17 +638,62 @@ void scsi_remove_device(struct scsi_device *sdev)
                goto out;
 
        class_device_unregister(&sdev->sdev_classdev);
-       if (sdev->transport_classdev.class)
-               class_device_unregister(&sdev->transport_classdev);
        device_del(&sdev->sdev_gendev);
        scsi_device_set_state(sdev, SDEV_DEL);
        if (sdev->host->hostt->slave_destroy)
                sdev->host->hostt->slave_destroy(sdev);
+       transport_unregister_device(&sdev->sdev_gendev);
        put_device(&sdev->sdev_gendev);
-
 out:
        up(&shost->scan_mutex);
 }
+EXPORT_SYMBOL(scsi_remove_device);
+
+void __scsi_remove_target(struct scsi_target *starget)
+{
+       struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+       unsigned long flags;
+       struct scsi_device *sdev, *tmp;
+
+       spin_lock_irqsave(shost->host_lock, flags);
+       starget->reap_ref++;
+       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);
+       scsi_target_reap(starget);
+}
+
+/**
+ * scsi_remove_target - try to remove a target and all its devices
+ * @dev: generic starget or parent of generic stargets to be removed
+ *
+ * Note: This is slightly racy.  It is possible that if the user
+ * requests the addition of another device then the target won't be
+ * removed.
+ */
+void scsi_remove_target(struct device *dev)
+{
+       struct device *rdev, *idev, *next;
+
+       if (scsi_is_target_device(dev)) {
+               __scsi_remove_target(to_scsi_target(dev));
+               return;
+       }
+
+       rdev = get_device(dev);
+       list_for_each_entry_safe(idev, next, &dev->children, node) {
+               if (scsi_is_target_device(idev))
+                       __scsi_remove_target(to_scsi_target(idev));
+       }
+       put_device(rdev);
+}
+EXPORT_SYMBOL(scsi_remove_target);
 
 int scsi_register_driver(struct device_driver *drv)
 {
@@ -613,6 +701,7 @@ int scsi_register_driver(struct device_driver *drv)
 
        return driver_register(drv);
 }
+EXPORT_SYMBOL(scsi_register_driver);
 
 int scsi_register_interface(struct class_interface *intf)
 {
@@ -620,6 +709,7 @@ int scsi_register_interface(struct class_interface *intf)
 
        return class_interface_register(intf);
 }
+EXPORT_SYMBOL(scsi_register_interface);
 
 
 static struct class_device_attribute *class_attr_overridden(
@@ -687,34 +777,16 @@ int scsi_sysfs_add_host(struct Scsi_Host *shost)
                }
        }
 
-       class_device_initialize(&shost->transport_classdev);
-       shost->transport_classdev.class = shost->transportt->host_class;
-       shost->transport_classdev.dev = &shost->shost_gendev;
-       snprintf(shost->transport_classdev.class_id, BUS_ID_SIZE,
-                "host%d", shost->host_no);
-
-       if (shost->transport_classdev.class) {
-               struct class_device_attribute **attrs =
-                       shost->transportt->host_attrs;
-               error = class_device_add(&shost->transport_classdev);
-               if (error)
-                       return error;
-               /* take a reference for the transport_classdev; this
-                * is released by the transport_class .release */
-               get_device(&shost->shost_gendev);
-               for (i = 0; attrs[i]; i++) {
-                       error = class_device_create_file(&shost->transport_classdev,
-                                                        attrs[i]);
-                       if (error)
-                               return error;
-               }
-       }
-
+       transport_register_device(&shost->shost_gendev);
        return 0;
 }
 
-int scsi_sysfs_device_initialize(struct scsi_device *sdev)
+void scsi_sysfs_device_initialize(struct scsi_device *sdev)
 {
+       unsigned long flags;
+       struct Scsi_Host *shost = sdev->host;
+       struct scsi_target  *starget = sdev->sdev_target;
+
        device_initialize(&sdev->sdev_gendev);
        sdev->sdev_gendev.bus = &scsi_bus_type;
        sdev->sdev_gendev.release = scsi_device_dev_release;
@@ -728,83 +800,20 @@ int scsi_sysfs_device_initialize(struct scsi_device *sdev)
        snprintf(sdev->sdev_classdev.class_id, BUS_ID_SIZE,
                 "%d:%d:%d:%d", sdev->host->host_no,
                 sdev->channel, sdev->id, sdev->lun);
-
-       class_device_initialize(&sdev->transport_classdev);
-       sdev->transport_classdev.dev = &sdev->sdev_gendev;
-       sdev->transport_classdev.class = sdev->host->transportt->device_class;
-       snprintf(sdev->transport_classdev.class_id, BUS_ID_SIZE,
-                "%d:%d:%d:%d", sdev->host->host_no,
-                sdev->channel, sdev->id, sdev->lun);
-       return 0;
-}
-
-int scsi_sysfs_target_initialize(struct scsi_device *sdev)
-{
-       struct scsi_target *starget = NULL;
-       struct Scsi_Host *shost = sdev->host;
-       struct scsi_device *device;
-       struct device *dev = NULL;
-       unsigned long flags;
-       int create = 0;
-
+       sdev->scsi_level = SCSI_2;
+       transport_setup_device(&sdev->sdev_gendev);
        spin_lock_irqsave(shost->host_lock, flags);
-       /*
-        * Search for an existing target for this sdev.
-        */
-       list_for_each_entry(device, &shost->__devices, siblings) {
-               if (device->id == sdev->id &&
-                   device->channel == sdev->channel) {
-                       list_add_tail(&sdev->same_target_siblings,
-                                     &device->same_target_siblings);
-                       sdev->scsi_level = device->scsi_level;
-                       starget = device->sdev_target;
-                       break;
-               }
-       }
-                       
-       if (!starget) {
-               const int size = sizeof(*starget) +
-                       shost->transportt->target_size;
-               starget = kmalloc(size, GFP_ATOMIC);
-               if (!starget) {
-                       printk(KERN_ERR "%s: allocation failure\n", __FUNCTION__);
-                       spin_unlock_irqrestore(shost->host_lock,
-                                              flags);
-                       return -ENOMEM;
-               }
-               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);
-               class_device_initialize(&starget->transport_classdev);
-               starget->transport_classdev.dev = &starget->dev;
-               starget->transport_classdev.class = shost->transportt->target_class;
-               snprintf(starget->transport_classdev.class_id, BUS_ID_SIZE,
-                        "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
-                * until we know better
-                */
-               sdev->scsi_level = SCSI_2;
-       }
-       get_device(&starget->dev);
-       sdev->sdev_gendev.parent = &starget->dev;
-       sdev->sdev_target = starget;
+       list_add_tail(&sdev->same_target_siblings, &starget->devices);
        list_add_tail(&sdev->siblings, &shost->__devices);
        spin_unlock_irqrestore(shost->host_lock, flags);
-       if (create && shost->transportt->target_setup)
-               shost->transportt->target_setup(starget);
-       return 0;
 }
 
+int scsi_is_sdev_device(const struct device *dev)
+{
+       return dev->release == scsi_device_dev_release;
+}
+EXPORT_SYMBOL(scsi_is_sdev_device);
+
 /* A blank transport template that is used in drivers that don't
  * yet implement Transport Attributes */
-struct scsi_transport_template blank_transport_template = { NULL, };
+struct scsi_transport_template blank_transport_template = { { { {NULL, }, }, }, };