config: tegra3: enable /dev mount with ACL
[linux-2.6.git] / drivers / scsi / hosts.c
index 5feb886..351dc0b 100644 (file)
 #include <linux/module.h>
 #include <linux/blkdev.h>
 #include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/kthread.h>
 #include <linux/string.h>
 #include <linux/mm.h>
 #include <linux/init.h>
 #include <linux/completion.h>
 #include <linux/transport_class.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 
 #include <scsi/scsi_device.h>
 #include <scsi/scsi_host.h>
 #include "scsi_logging.h"
 
 
-static int scsi_host_next_hn;          /* host_no for next new host */
+static atomic_t scsi_host_next_hn;     /* host_no for next new host */
 
 
-static void scsi_host_cls_release(struct class_device *class_dev)
+static void scsi_host_cls_release(struct device *dev)
 {
-       put_device(&class_to_shost(class_dev)->shost_gendev);
+       put_device(&class_to_shost(dev)->shost_gendev);
 }
 
 static struct class shost_class = {
        .name           = "scsi_host",
-       .release        = scsi_host_cls_release,
+       .dev_release    = scsi_host_cls_release,
 };
 
 /**
- * scsi_host_cancel - cancel outstanding IO to this host
- * @shost:     pointer to struct Scsi_Host
- * recovery:   recovery requested to run.
+ *     scsi_host_set_state - Take the given host through the host state model.
+ *     @shost: scsi host to change the state of.
+ *     @state: state to change to.
+ *
+ *     Returns zero if unsuccessful or an error if the requested
+ *     transition is illegal.
  **/
-static void scsi_host_cancel(struct Scsi_Host *shost, int recovery)
+int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state)
 {
-       struct scsi_device *sdev;
+       enum scsi_host_state oldstate = shost->shost_state;
+
+       if (state == oldstate)
+               return 0;
+
+       switch (state) {
+       case SHOST_CREATED:
+               /* There are no legal states that come back to
+                * created.  This is the manually initialised start
+                * state */
+               goto illegal;
+
+       case SHOST_RUNNING:
+               switch (oldstate) {
+               case SHOST_CREATED:
+               case SHOST_RECOVERY:
+                       break;
+               default:
+                       goto illegal;
+               }
+               break;
+
+       case SHOST_RECOVERY:
+               switch (oldstate) {
+               case SHOST_RUNNING:
+                       break;
+               default:
+                       goto illegal;
+               }
+               break;
+
+       case SHOST_CANCEL:
+               switch (oldstate) {
+               case SHOST_CREATED:
+               case SHOST_RUNNING:
+               case SHOST_CANCEL_RECOVERY:
+                       break;
+               default:
+                       goto illegal;
+               }
+               break;
+
+       case SHOST_DEL:
+               switch (oldstate) {
+               case SHOST_CANCEL:
+               case SHOST_DEL_RECOVERY:
+                       break;
+               default:
+                       goto illegal;
+               }
+               break;
+
+       case SHOST_CANCEL_RECOVERY:
+               switch (oldstate) {
+               case SHOST_CANCEL:
+               case SHOST_RECOVERY:
+                       break;
+               default:
+                       goto illegal;
+               }
+               break;
 
-       set_bit(SHOST_CANCEL, &shost->shost_state);
-       shost_for_each_device(sdev, shost) {
-               scsi_device_cancel(sdev, recovery);
+       case SHOST_DEL_RECOVERY:
+               switch (oldstate) {
+               case SHOST_CANCEL_RECOVERY:
+                       break;
+               default:
+                       goto illegal;
+               }
+               break;
        }
-       wait_event(shost->host_wait, (!test_bit(SHOST_RECOVERY,
-                                               &shost->shost_state)));
+       shost->shost_state = state;
+       return 0;
+
+ illegal:
+       SCSI_LOG_ERROR_RECOVERY(1,
+                               shost_printk(KERN_ERR, shost,
+                                            "Illegal host state transition"
+                                            "%s->%s\n",
+                                            scsi_host_state_name(oldstate),
+                                            scsi_host_state_name(state)));
+       return -EINVAL;
 }
+EXPORT_SYMBOL(scsi_host_set_state);
 
 /**
  * scsi_remove_host - remove a scsi host
@@ -74,27 +156,49 @@ static void scsi_host_cancel(struct Scsi_Host *shost, int recovery)
  **/
 void scsi_remove_host(struct Scsi_Host *shost)
 {
+       unsigned long flags;
+
+       mutex_lock(&shost->scan_mutex);
+       spin_lock_irqsave(shost->host_lock, flags);
+       if (scsi_host_set_state(shost, SHOST_CANCEL))
+               if (scsi_host_set_state(shost, SHOST_CANCEL_RECOVERY)) {
+                       spin_unlock_irqrestore(shost->host_lock, flags);
+                       mutex_unlock(&shost->scan_mutex);
+                       return;
+               }
+       spin_unlock_irqrestore(shost->host_lock, flags);
+
+       scsi_autopm_get_host(shost);
        scsi_forget_host(shost);
-       scsi_host_cancel(shost, 0);
+       mutex_unlock(&shost->scan_mutex);
        scsi_proc_host_rm(shost);
 
-       set_bit(SHOST_DEL, &shost->shost_state);
+       spin_lock_irqsave(shost->host_lock, flags);
+       if (scsi_host_set_state(shost, SHOST_DEL))
+               BUG_ON(scsi_host_set_state(shost, SHOST_DEL_RECOVERY));
+       spin_unlock_irqrestore(shost->host_lock, flags);
 
        transport_unregister_device(&shost->shost_gendev);
-       class_device_unregister(&shost->shost_classdev);
+       device_unregister(&shost->shost_dev);
        device_del(&shost->shost_gendev);
 }
 EXPORT_SYMBOL(scsi_remove_host);
 
 /**
- * scsi_add_host - add a scsi host
+ * scsi_add_host_with_dma - add a scsi host with dma device
  * @shost:     scsi host pointer to add
  * @dev:       a struct device of type scsi class
+ * @dma_dev:   dma device for the host
+ *
+ * Note: You rarely need to worry about this unless you're in a
+ * virtualised host environments, so use the simpler scsi_add_host()
+ * function instead.
  *
  * Return value: 
  *     0 on success / != 0 for error
  **/
-int scsi_add_host(struct Scsi_Host *shost, struct device *dev)
+int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,
+                          struct device *dma_dev)
 {
        struct scsi_host_template *sht = shost->hostt;
        int error = -EINVAL;
@@ -105,37 +209,54 @@ int scsi_add_host(struct Scsi_Host *shost, struct device *dev)
        if (!shost->can_queue) {
                printk(KERN_ERR "%s: can_queue = 0 no longer supported\n",
                                sht->name);
-               goto out;
+               goto fail;
        }
 
+       error = scsi_setup_command_freelist(shost);
+       if (error)
+               goto fail;
+
        if (!shost->shost_gendev.parent)
                shost->shost_gendev.parent = dev ? dev : &platform_bus;
+       shost->dma_dev = dma_dev;
 
        error = device_add(&shost->shost_gendev);
        if (error)
                goto out;
 
-       set_bit(SHOST_ADD, &shost->shost_state);
+       pm_runtime_set_active(&shost->shost_gendev);
+       pm_runtime_enable(&shost->shost_gendev);
+       device_enable_async_suspend(&shost->shost_gendev);
+
+       scsi_host_set_state(shost, SHOST_RUNNING);
        get_device(shost->shost_gendev.parent);
 
-       error = class_device_add(&shost->shost_classdev);
+       device_enable_async_suspend(&shost->shost_dev);
+
+       error = device_add(&shost->shost_dev);
        if (error)
                goto out_del_gendev;
 
        get_device(&shost->shost_gendev);
 
-       if (shost->transportt->host_size &&
-           (shost->shost_data = kmalloc(shost->transportt->host_size,
-                                        GFP_KERNEL)) == NULL)
-               goto out_del_classdev;
+       if (shost->transportt->host_size) {
+               shost->shost_data = kzalloc(shost->transportt->host_size,
+                                        GFP_KERNEL);
+               if (shost->shost_data == NULL) {
+                       error = -ENOMEM;
+                       goto out_del_dev;
+               }
+       }
 
        if (shost->transportt->create_work_queue) {
-               snprintf(shost->work_q_name, KOBJ_NAME_LEN, "scsi_wq_%d",
-                       shost->host_no);
+               snprintf(shost->work_q_name, sizeof(shost->work_q_name),
+                        "scsi_wq_%d", shost->host_no);
                shost->work_q = create_singlethread_workqueue(
                                        shost->work_q_name);
-               if (!shost->work_q)
+               if (!shost->work_q) {
+                       error = -EINVAL;
                        goto out_free_shost_data;
+               }
        }
 
        error = scsi_sysfs_add_host(shost);
@@ -150,34 +271,40 @@ int scsi_add_host(struct Scsi_Host *shost, struct device *dev)
                destroy_workqueue(shost->work_q);
  out_free_shost_data:
        kfree(shost->shost_data);
- out_del_classdev:
-       class_device_del(&shost->shost_classdev);
+ out_del_dev:
+       device_del(&shost->shost_dev);
  out_del_gendev:
        device_del(&shost->shost_gendev);
  out:
+       scsi_destroy_command_freelist(shost);
+ fail:
        return error;
 }
-EXPORT_SYMBOL(scsi_add_host);
+EXPORT_SYMBOL(scsi_add_host_with_dma);
 
 static void scsi_host_dev_release(struct device *dev)
 {
        struct Scsi_Host *shost = dev_to_shost(dev);
        struct device *parent = dev->parent;
+       struct request_queue *q;
 
-       if (shost->ehandler) {
-               DECLARE_COMPLETION(sem);
-               shost->eh_notify = &sem;
-               shost->eh_kill = 1;
-               up(shost->eh_wait);
-               wait_for_completion(&sem);
-               shost->eh_notify = NULL;
-       }
+       scsi_proc_hostdir_rm(shost->hostt);
 
+       if (shost->ehandler)
+               kthread_stop(shost->ehandler);
        if (shost->work_q)
                destroy_workqueue(shost->work_q);
+       q = shost->uspace_req_q;
+       if (q) {
+               kfree(q->queuedata);
+               q->queuedata = NULL;
+               scsi_free_queue(q);
+       }
 
-       scsi_proc_hostdir_rm(shost->hostt);
        scsi_destroy_command_freelist(shost);
+       if (shost->bqt)
+               blk_free_tags(shost->bqt);
+
        kfree(shost->shost_data);
 
        if (parent)
@@ -185,6 +312,11 @@ static void scsi_host_dev_release(struct device *dev)
        kfree(shost);
 }
 
+static struct device_type scsi_host_type = {
+       .name =         "scsi_host",
+       .release =      scsi_host_dev_release,
+};
+
 /**
  * scsi_host_alloc - register a scsi host adapter instance.
  * @sht:       pointer to scsi host template
@@ -201,40 +333,31 @@ static void scsi_host_dev_release(struct device *dev)
 struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
 {
        struct Scsi_Host *shost;
-       int gfp_mask = GFP_KERNEL, rval;
-       DECLARE_COMPLETION(complete);
+       gfp_t gfp_mask = GFP_KERNEL;
 
        if (sht->unchecked_isa_dma && privsize)
                gfp_mask |= __GFP_DMA;
 
-        /* Check to see if this host has any error handling facilities */
-        if (!sht->eh_strategy_handler && !sht->eh_abort_handler &&
-           !sht->eh_device_reset_handler && !sht->eh_bus_reset_handler &&
-            !sht->eh_host_reset_handler) {
-               printk(KERN_ERR "ERROR: SCSI host `%s' has no error handling\n"
-                               "ERROR: This is not a safe way to run your "
-                                       "SCSI host\n"
-                               "ERROR: The error handling must be added to "
-                               "this driver\n", sht->proc_name);
-               dump_stack();
-        }
-
-       shost = kmalloc(sizeof(struct Scsi_Host) + privsize, gfp_mask);
+       shost = kzalloc(sizeof(struct Scsi_Host) + privsize, gfp_mask);
        if (!shost)
                return NULL;
-       memset(shost, 0, sizeof(struct Scsi_Host) + privsize);
 
-       spin_lock_init(&shost->default_lock);
-       scsi_assign_lock(shost, &shost->default_lock);
+       shost->host_lock = &shost->default_lock;
+       spin_lock_init(shost->host_lock);
+       shost->shost_state = SHOST_CREATED;
        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);
 
-       init_MUTEX(&shost->scan_mutex);
+       mutex_init(&shost->scan_mutex);
 
-       shost->host_no = scsi_host_next_hn++; /* XXX(hch): still racy */
+       /*
+        * subtract one because we increment first then return, but we need to
+        * know what the next host number was before increment
+        */
+       shost->host_no = atomic_inc_return(&scsi_host_next_hn) - 1;
        shost->dma_channel = 0xff;
 
        /* These three are default values which can be overridden */
@@ -256,19 +379,17 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
        shost->this_id = sht->this_id;
        shost->can_queue = sht->can_queue;
        shost->sg_tablesize = sht->sg_tablesize;
+       shost->sg_prot_tablesize = sht->sg_prot_tablesize;
        shost->cmd_per_lun = sht->cmd_per_lun;
        shost->unchecked_isa_dma = sht->unchecked_isa_dma;
        shost->use_clustering = sht->use_clustering;
-       shost->ordered_flush = sht->ordered_flush;
        shost->ordered_tag = sht->ordered_tag;
 
-       /*
-        * hosts/devices that do queueing must support ordered tags
-        */
-       if (shost->can_queue > 1 && shost->ordered_flush) {
-               printk(KERN_ERR "scsi: ordered flushes don't support queueing\n");
-               shost->ordered_flush = 0;
-       }
+       if (sht->supported_mode == MODE_UNKNOWN)
+               /* means we didn't set it ... default to INITIATOR */
+               shost->active_mode = MODE_INITIATOR;
+       else
+               shost->active_mode = sht->supported_mode;
 
        if (sht->max_host_blocked)
                shost->max_host_blocked = sht->max_host_blocked;
@@ -292,33 +413,28 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
        else
                shost->dma_boundary = 0xffffffff;
 
-       rval = scsi_setup_command_freelist(shost);
-       if (rval)
-               goto fail_kfree;
-
        device_initialize(&shost->shost_gendev);
-       snprintf(shost->shost_gendev.bus_id, BUS_ID_SIZE, "host%d",
-               shost->host_no);
-       shost->shost_gendev.release = scsi_host_dev_release;
-
-       class_device_initialize(&shost->shost_classdev);
-       shost->shost_classdev.dev = &shost->shost_gendev;
-       shost->shost_classdev.class = &shost_class;
-       snprintf(shost->shost_classdev.class_id, BUS_ID_SIZE, "host%d",
-                 shost->host_no);
-
-       shost->eh_notify = &complete;
-       rval = kernel_thread(scsi_error_handler, shost, 0);
-       if (rval < 0)
-               goto fail_destroy_freelist;
-       wait_for_completion(&complete);
-       shost->eh_notify = NULL;
+       dev_set_name(&shost->shost_gendev, "host%d", shost->host_no);
+       shost->shost_gendev.bus = &scsi_bus_type;
+       shost->shost_gendev.type = &scsi_host_type;
+
+       device_initialize(&shost->shost_dev);
+       shost->shost_dev.parent = &shost->shost_gendev;
+       shost->shost_dev.class = &shost_class;
+       dev_set_name(&shost->shost_dev, "host%d", shost->host_no);
+       shost->shost_dev.groups = scsi_sysfs_shost_attr_groups;
+
+       shost->ehandler = kthread_run(scsi_error_handler, shost,
+                       "scsi_eh_%d", shost->host_no);
+       if (IS_ERR(shost->ehandler)) {
+               printk(KERN_WARNING "scsi%d: error handler thread failed to spawn, error = %ld\n",
+                       shost->host_no, PTR_ERR(shost->ehandler));
+               goto fail_kfree;
+       }
 
        scsi_proc_hostdir_add(shost->hostt);
        return shost;
 
- fail_destroy_freelist:
-       scsi_destroy_command_freelist(shost);
  fail_kfree:
        kfree(shost);
        return NULL;
@@ -348,30 +464,37 @@ void scsi_unregister(struct Scsi_Host *shost)
 }
 EXPORT_SYMBOL(scsi_unregister);
 
+static int __scsi_host_match(struct device *dev, void *data)
+{
+       struct Scsi_Host *p;
+       unsigned short *hostnum = (unsigned short *)data;
+
+       p = class_to_shost(dev);
+       return p->host_no == *hostnum;
+}
+
 /**
  * scsi_host_lookup - get a reference to a Scsi_Host by host no
- *
  * @hostnum:   host number to locate
  *
  * Return value:
  *     A pointer to located Scsi_Host or NULL.
+ *
+ *     The caller must do a scsi_host_put() to drop the reference
+ *     that scsi_host_get() took. The put_device() below dropped
+ *     the reference from class_find_device().
  **/
 struct Scsi_Host *scsi_host_lookup(unsigned short hostnum)
 {
-       struct class *class = &shost_class;
-       struct class_device *cdev;
-       struct Scsi_Host *shost = ERR_PTR(-ENXIO), *p;
-
-       down_read(&class->subsys.rwsem);
-       list_for_each_entry(cdev, &class->children, node) {
-               p = class_to_shost(cdev);
-               if (p->host_no == hostnum) {
-                       shost = scsi_host_get(p);
-                       break;
-               }
+       struct device *cdev;
+       struct Scsi_Host *shost = NULL;
+
+       cdev = class_find_device(&shost_class, NULL, &hostnum,
+                                __scsi_host_match);
+       if (cdev) {
+               shost = scsi_host_get(class_to_shost(cdev));
+               put_device(cdev);
        }
-       up_read(&class->subsys.rwsem);
-
        return shost;
 }
 EXPORT_SYMBOL(scsi_host_lookup);
@@ -382,7 +505,7 @@ EXPORT_SYMBOL(scsi_host_lookup);
  **/
 struct Scsi_Host *scsi_host_get(struct Scsi_Host *shost)
 {
-       if (test_bit(SHOST_DEL, &shost->shost_state) ||
+       if ((shost->shost_state == SHOST_DEL) ||
                !get_device(&shost->shost_gendev))
                return NULL;
        return shost;
@@ -411,7 +534,7 @@ void scsi_exit_hosts(void)
 
 int scsi_is_host_device(const struct device *dev)
 {
-       return dev->release == scsi_host_dev_release;
+       return dev->type == &scsi_host_type;
 }
 EXPORT_SYMBOL(scsi_is_host_device);
 
@@ -421,7 +544,9 @@ EXPORT_SYMBOL(scsi_is_host_device);
  * @work:      Work to queue for execution.
  *
  * Return value:
- *     0 on success / != 0 for error
+ *     1 - work queued for execution
+ *     0 - work is already queued
+ *     -EINVAL - work queue doesn't exist
  **/
 int scsi_queue_work(struct Scsi_Host *shost, struct work_struct *work)
 {