X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fscsi%2Fhosts.c;h=dfcb96f3e60ce2782a39fffc415010ef54c272d0;hb=43bc926fffd92024b46cafaf7350d669ba9ca884;hp=12c5245c26fd5669895e70258ad1e09fef4a143c;hpb=9bf4aaab3e101692164d49b7ca357651eb691cb6;p=linux-2.6.git diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index 12c5245c2..dfcb96f3e 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -24,10 +24,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include @@ -50,24 +53,101 @@ static struct class shost_class = { .release = scsi_host_cls_release, }; -static int scsi_device_cancel_cb(struct device *dev, void *data) -{ - return scsi_device_cancel(to_scsi_device(dev), *(int *)data); -} - /** - * 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. **/ -void scsi_host_cancel(struct Scsi_Host *shost, int recovery) +int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state) { - set_bit(SHOST_CANCEL, &shost->shost_state); - device_for_each_child(&shost->shost_gendev, &recovery, - scsi_device_cancel_cb); - wait_event(shost->host_wait, (!test_bit(SHOST_RECOVERY, - &shost->shost_state))); + 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; + + case SHOST_DEL_RECOVERY: + switch (oldstate) { + case SHOST_CANCEL_RECOVERY: + break; + default: + goto illegal; + } + break; + } + 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 @@ -75,15 +155,31 @@ void scsi_host_cancel(struct Scsi_Host *shost, int recovery) **/ void scsi_remove_host(struct Scsi_Host *shost) { - scsi_host_cancel(shost, 0); - scsi_proc_host_rm(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); + mutex_unlock(&shost->scan_mutex); scsi_forget_host(shost); + 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_del(&shost->shost_gendev); + scsi_proc_hostdir_rm(shost->hostt); } +EXPORT_SYMBOL(scsi_remove_host); /** * scsi_add_host - add a scsi host @@ -96,7 +192,7 @@ void scsi_remove_host(struct Scsi_Host *shost) int scsi_add_host(struct Scsi_Host *shost, struct device *dev) { struct scsi_host_template *sht = shost->hostt; - int error; + int error = -EINVAL; printk(KERN_INFO "scsi%d : %s\n", shost->host_no, sht->info ? sht->info(shost) : sht->name); @@ -104,7 +200,7 @@ 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); - return -EINVAL; + goto out; } if (!shost->shost_gendev.parent) @@ -114,7 +210,7 @@ int scsi_add_host(struct Scsi_Host *shost, struct device *dev) if (error) goto out; - set_bit(SHOST_ADD, &shost->shost_state); + scsi_host_set_state(shost, SHOST_RUNNING); get_device(shost->shost_gendev.parent); error = class_device_add(&shost->shost_classdev); @@ -123,13 +219,32 @@ int scsi_add_host(struct Scsi_Host *shost, struct device *dev) 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->create_work_queue) { + snprintf(shost->work_q_name, KOBJ_NAME_LEN, "scsi_wq_%d", + shost->host_no); + shost->work_q = create_singlethread_workqueue( + shost->work_q_name); + if (!shost->work_q) + goto out_free_shost_data; + } + error = scsi_sysfs_add_host(shost); if (error) - goto out_del_classdev; + goto out_destroy_host; scsi_proc_host_add(shost); return error; + out_destroy_host: + if (shost->work_q) + destroy_workqueue(shost->work_q); + out_free_shost_data: + kfree(shost->shost_data); out_del_classdev: class_device_del(&shost->shost_classdev); out_del_gendev: @@ -137,29 +252,21 @@ int scsi_add_host(struct Scsi_Host *shost, struct device *dev) out: return error; } +EXPORT_SYMBOL(scsi_add_host); static void scsi_host_dev_release(struct device *dev) { struct Scsi_Host *shost = dev_to_shost(dev); struct device *parent = dev->parent; - 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; - } + if (shost->ehandler) + kthread_stop(shost->ehandler); + if (shost->work_q) + destroy_workqueue(shost->work_q); - scsi_proc_hostdir_rm(shost->hostt); scsi_destroy_command_freelist(shost); + kfree(shost->shost_data); - /* - * Some drivers (eg aha1542) do scsi_register()/scsi_unregister() - * during probing without performing a scsi_set_device() in between. - * In this case dev->parent is NULL. - */ if (parent) put_device(parent); kfree(shost); @@ -181,37 +288,26 @@ 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; + int rval; 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->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 */ shost->dma_channel = 0xff; @@ -221,10 +317,8 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize) shost->max_id = 8; shost->max_lun = 8; - /* Give each shost a default transportt if the driver - * doesn't yet support Transport Attributes */ - if (!shost->transportt) - shost->transportt = &blank_transport_template; + /* Give each shost a default transportt */ + shost->transportt = &blank_transport_template; /* * All drivers right now should be able to handle 12 byte @@ -240,6 +334,7 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize) shost->cmd_per_lun = sht->cmd_per_lun; shost->unchecked_isa_dma = sht->unchecked_isa_dma; shost->use_clustering = sht->use_clustering; + shost->ordered_tag = sht->ordered_tag; if (sht->max_host_blocked) shost->max_host_blocked = sht->max_host_blocked; @@ -278,12 +373,13 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize) 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) + shost->ehandler = kthread_run(scsi_error_handler, shost, + "scsi_eh_%d", shost->host_no); + if (IS_ERR(shost->ehandler)) { + rval = PTR_ERR(shost->ehandler); goto fail_destroy_freelist; - wait_for_completion(&complete); - shost->eh_notify = NULL; + } + scsi_proc_hostdir_add(shost->hostt); return shost; @@ -293,6 +389,7 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize) kfree(shost); return NULL; } +EXPORT_SYMBOL(scsi_host_alloc); struct Scsi_Host *scsi_register(struct scsi_host_template *sht, int privsize) { @@ -308,12 +405,14 @@ struct Scsi_Host *scsi_register(struct scsi_host_template *sht, int privsize) list_add_tail(&shost->sht_legacy_list, &sht->legacy_hosts); return shost; } +EXPORT_SYMBOL(scsi_register); void scsi_unregister(struct Scsi_Host *shost) { list_del(&shost->sht_legacy_list); scsi_host_put(shost); } +EXPORT_SYMBOL(scsi_unregister); /** * scsi_host_lookup - get a reference to a Scsi_Host by host no @@ -341,6 +440,7 @@ struct Scsi_Host *scsi_host_lookup(unsigned short hostnum) return shost; } +EXPORT_SYMBOL(scsi_host_lookup); /** * scsi_host_get - inc a Scsi_Host ref count @@ -348,11 +448,12 @@ struct Scsi_Host *scsi_host_lookup(unsigned short hostnum) **/ 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; } +EXPORT_SYMBOL(scsi_host_get); /** * scsi_host_put - dec a Scsi_Host ref count @@ -362,6 +463,7 @@ void scsi_host_put(struct Scsi_Host *shost) { put_device(&shost->shost_gendev); } +EXPORT_SYMBOL(scsi_host_put); int scsi_init_hosts(void) { @@ -372,3 +474,50 @@ void scsi_exit_hosts(void) { class_unregister(&shost_class); } + +int scsi_is_host_device(const struct device *dev) +{ + return dev->release == scsi_host_dev_release; +} +EXPORT_SYMBOL(scsi_is_host_device); + +/** + * scsi_queue_work - Queue work to the Scsi_Host workqueue. + * @shost: Pointer to Scsi_Host. + * @work: Work to queue for execution. + * + * Return value: + * 0 on success / != 0 for error + **/ +int scsi_queue_work(struct Scsi_Host *shost, struct work_struct *work) +{ + if (unlikely(!shost->work_q)) { + printk(KERN_ERR + "ERROR: Scsi host '%s' attempted to queue scsi-work, " + "when no workqueue created.\n", shost->hostt->name); + dump_stack(); + + return -EINVAL; + } + + return queue_work(shost->work_q, work); +} +EXPORT_SYMBOL_GPL(scsi_queue_work); + +/** + * scsi_flush_work - Flush a Scsi_Host's workqueue. + * @shost: Pointer to Scsi_Host. + **/ +void scsi_flush_work(struct Scsi_Host *shost) +{ + if (!shost->work_q) { + printk(KERN_ERR + "ERROR: Scsi host '%s' attempted to flush scsi-work, " + "when no workqueue created.\n", shost->hostt->name); + dump_stack(); + return; + } + + flush_workqueue(shost->work_q); +} +EXPORT_SYMBOL_GPL(scsi_flush_work);