From 3e082a910d217b2e7b186077ebf5a1126a68c62f Mon Sep 17 00:00:00 2001 From: Matthew Wilcox Date: Thu, 28 Sep 2006 15:19:20 -0600 Subject: [SCSI] Add ability to scan scsi busses asynchronously Since it often takes around 20-30 seconds to scan a scsi bus, it's highly advantageous to do this in parallel with other things. The bulk of this patch is ensuring that devices don't change numbering, and that all devices are discovered prior to trying to start init. For those who build SCSI as modules, there's a new scsi_wait_scan module that will ensure all bus scans are finished. This patch only handles drivers which call scsi_scan_host. Fibre Channel, SAS, SATA, USB and Firewire all need additional work. Signed-off-by: Matthew Wilcox Signed-off-by: James Bottomley --- include/scsi/scsi_device.h | 30 +++++++++++++++--------------- include/scsi/scsi_host.h | 3 +++ 2 files changed, 18 insertions(+), 15 deletions(-) (limited to 'include') diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h index b401c82036be..ebf31b16dc49 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -223,13 +223,13 @@ extern struct scsi_device *__scsi_iterate_devices(struct Scsi_Host *, struct scsi_device *); /** - * shost_for_each_device - iterate over all devices of a host - * @sdev: iterator - * @host: host whiches devices we want to iterate over + * shost_for_each_device - iterate over all devices of a host + * @sdev: the &struct scsi_device to use as a cursor + * @shost: the &struct scsi_host to iterate over * - * This traverses over each devices of @shost. The devices have - * a reference that must be released by scsi_host_put when breaking - * out of the loop. + * Iterator that returns each device attached to @shost. This loop + * takes a reference on each device and releases it at the end. If + * you break out of the loop, you must call scsi_device_put(sdev). */ #define shost_for_each_device(sdev, shost) \ for ((sdev) = __scsi_iterate_devices((shost), NULL); \ @@ -237,17 +237,17 @@ extern struct scsi_device *__scsi_iterate_devices(struct Scsi_Host *, (sdev) = __scsi_iterate_devices((shost), (sdev))) /** - * __shost_for_each_device - iterate over all devices of a host (UNLOCKED) - * @sdev: iterator - * @host: host whiches devices we want to iterate over + * __shost_for_each_device - iterate over all devices of a host (UNLOCKED) + * @sdev: the &struct scsi_device to use as a cursor + * @shost: the &struct scsi_host to iterate over * - * This traverses over each devices of @shost. It does _not_ take a - * reference on the scsi_device, thus it the whole loop must be protected - * by shost->host_lock. + * Iterator that returns each device attached to @shost. It does _not_ + * take a reference on the scsi_device, so the whole loop must be + * protected by shost->host_lock. * - * Note: The only reason why drivers would want to use this is because - * they're need to access the device list in irq context. Otherwise you - * really want to use shost_for_each_device instead. + * Note: The only reason to use this is because you need to access the + * device list in interrupt context. Otherwise you really want to use + * shost_for_each_device instead. */ #define __shost_for_each_device(sdev, shost) \ list_for_each_entry((sdev), &((shost)->__devices), siblings) diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index 39c6f8cc20c3..ba5b3eb6b43f 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -552,6 +552,9 @@ struct Scsi_Host { /* task mgmt function in progress */ unsigned tmf_in_progress:1; + /* Asynchronous scan in progress */ + unsigned async_scan:1; + /* * Optional work queue to be utilized by the transport */ -- cgit v1.2.3-58-ga151 From f456393e195e0aa16029985f63cd93b601a0d315 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 30 Oct 2006 15:18:39 -0800 Subject: [SCSI] libsas: modify error handler to use scsi_eh_* functions This patch adds an EH done queue to sas_ha, converts the error handling strategy function and the sas_scsi_task_done functions in libsas to use the scsi_eh_* commands for error'd commands, and adds checks for the INITIATOR_ABORTED flag so that we do the right thing if a sas_task has been aborted by the initiator. Signed-off-by: Darrick J. Wong Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_init.c | 2 ++ drivers/scsi/libsas/sas_scsi_host.c | 29 +++++++++++++++++++++++++---- include/scsi/libsas.h | 9 ++++++--- 3 files changed, 33 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/drivers/scsi/libsas/sas_init.c b/drivers/scsi/libsas/sas_init.c index c836a237fb79..a2b479a65908 100644 --- a/drivers/scsi/libsas/sas_init.c +++ b/drivers/scsi/libsas/sas_init.c @@ -112,6 +112,8 @@ int sas_register_ha(struct sas_ha_struct *sas_ha) } } + INIT_LIST_HEAD(&sas_ha->eh_done_q); + return 0; Undo_ports: diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c index e46e79355b77..6a97b07849b4 100644 --- a/drivers/scsi/libsas/sas_scsi_host.c +++ b/drivers/scsi/libsas/sas_scsi_host.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include "../scsi_sas_internal.h" @@ -46,6 +47,7 @@ static void sas_scsi_task_done(struct sas_task *task) { struct task_status_struct *ts = &task->task_status; struct scsi_cmnd *sc = task->uldd_task; + struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(sc->device->host); unsigned ts_flags = task->task_state_flags; int hs = 0, stat = 0; @@ -116,7 +118,7 @@ static void sas_scsi_task_done(struct sas_task *task) sas_free_task(task); /* This is very ugly but this is how SCSI Core works. */ if (ts_flags & SAS_TASK_STATE_ABORTED) - scsi_finish_command(sc); + scsi_eh_finish_cmd(sc, &sas_ha->eh_done_q); else sc->scsi_done(sc); } @@ -307,6 +309,15 @@ static enum task_disposition sas_scsi_find_task(struct sas_task *task) spin_unlock_irqrestore(&core->task_queue_lock, flags); } + spin_lock_irqsave(&task->task_state_lock, flags); + if (task->task_state_flags & SAS_TASK_INITIATOR_ABORTED) { + spin_unlock_irqrestore(&task->task_state_lock, flags); + SAS_DPRINTK("%s: task 0x%p already aborted\n", + __FUNCTION__, task); + return TASK_IS_ABORTED; + } + spin_unlock_irqrestore(&task->task_state_lock, flags); + for (i = 0; i < 5; i++) { SAS_DPRINTK("%s: aborting task 0x%p\n", __FUNCTION__, task); res = si->dft->lldd_abort_task(task); @@ -409,13 +420,16 @@ Again: SAS_DPRINTK("going over list...\n"); list_for_each_entry_safe(cmd, n, &error_q, eh_entry) { struct sas_task *task = TO_SAS_TASK(cmd); + list_del_init(&cmd->eh_entry); + if (!task) { + SAS_DPRINTK("%s: taskless cmd?!\n", __FUNCTION__); + continue; + } SAS_DPRINTK("trying to find task 0x%p\n", task); - list_del_init(&cmd->eh_entry); res = sas_scsi_find_task(task); cmd->eh_eflags = 0; - shost->host_failed--; switch (res) { case TASK_IS_DONE: @@ -491,6 +505,7 @@ Again: } } out: + scsi_eh_flush_done_q(&ha->eh_done_q); SAS_DPRINTK("--- Exit %s\n", __FUNCTION__); return; clear_q: @@ -508,12 +523,18 @@ enum scsi_eh_timer_return sas_scsi_timed_out(struct scsi_cmnd *cmd) unsigned long flags; if (!task) { - SAS_DPRINTK("command 0x%p, task 0x%p, timed out: EH_HANDLED\n", + SAS_DPRINTK("command 0x%p, task 0x%p, gone: EH_HANDLED\n", cmd, task); return EH_HANDLED; } spin_lock_irqsave(&task->task_state_lock, flags); + if (task->task_state_flags & SAS_TASK_INITIATOR_ABORTED) { + spin_unlock_irqrestore(&task->task_state_lock, flags); + SAS_DPRINTK("command 0x%p, task 0x%p, aborted by initiator: " + "EH_NOT_HANDLED\n", cmd, task); + return EH_NOT_HANDLED; + } if (task->task_state_flags & SAS_TASK_STATE_DONE) { spin_unlock_irqrestore(&task->task_state_lock, flags); SAS_DPRINTK("command 0x%p, task 0x%p, timed out: EH_HANDLED\n", diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index 9582e8401669..7bf2e8b9903c 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h @@ -338,6 +338,8 @@ struct sas_ha_struct { void (*notify_phy_event)(struct asd_sas_phy *, enum phy_event); void *lldd_ha; /* not touched by sas class code */ + + struct list_head eh_done_q; }; #define SHOST_TO_SAS_HA(_shost) (*(struct sas_ha_struct **)(_shost)->hostdata) @@ -530,9 +532,10 @@ struct sas_task { -#define SAS_TASK_STATE_PENDING 1 -#define SAS_TASK_STATE_DONE 2 -#define SAS_TASK_STATE_ABORTED 4 +#define SAS_TASK_STATE_PENDING 1 +#define SAS_TASK_STATE_DONE 2 +#define SAS_TASK_STATE_ABORTED 4 +#define SAS_TASK_INITIATOR_ABORTED 8 static inline struct sas_task *sas_alloc_task(gfp_t flags) { -- cgit v1.2.3-58-ga151 From 79a5eb609b74e7b3638861c41b98eafa74920a1f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 30 Oct 2006 15:18:50 -0800 Subject: [SCSI] libsas: add sas_abort_task This patch adds an external function, sas_abort_task, to enable LLDDs to abort sas_tasks. It also adds a work_struct so that the actual work of aborting a task can be shifted from tasklet context (in the LLDD) onto the scsi_host's workqueue. Signed-off-by: Darrick J. Wong Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_scsi_host.c | 60 +++++++++++++++++++++++++++++++++++++ include/scsi/libsas.h | 4 +++ 2 files changed, 64 insertions(+) (limited to 'include') diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c index 6a97b07849b4..c5fd37522728 100644 --- a/drivers/scsi/libsas/sas_scsi_host.c +++ b/drivers/scsi/libsas/sas_scsi_host.c @@ -33,6 +33,7 @@ #include #include #include "../scsi_sas_internal.h" +#include "../scsi_transport_api.h" #include #include @@ -798,6 +799,64 @@ void sas_shutdown_queue(struct sas_ha_struct *sas_ha) spin_unlock_irqrestore(&core->task_queue_lock, flags); } +static int do_sas_task_abort(struct sas_task *task) +{ + struct scsi_cmnd *sc = task->uldd_task; + struct sas_internal *si = + to_sas_internal(task->dev->port->ha->core.shost->transportt); + unsigned long flags; + int res; + + spin_lock_irqsave(&task->task_state_lock, flags); + if (task->task_state_flags & SAS_TASK_STATE_ABORTED) { + spin_unlock_irqrestore(&task->task_state_lock, flags); + SAS_DPRINTK("%s: Task %p already aborted.\n", __FUNCTION__, + task); + return 0; + } + + task->task_state_flags |= SAS_TASK_INITIATOR_ABORTED; + if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) + task->task_state_flags |= SAS_TASK_STATE_ABORTED; + spin_unlock_irqrestore(&task->task_state_lock, flags); + + if (!si->dft->lldd_abort_task) + return -ENODEV; + + res = si->dft->lldd_abort_task(task); + if ((task->task_state_flags & SAS_TASK_STATE_DONE) || + (res == TMF_RESP_FUNC_COMPLETE)) + { + /* SMP commands don't have scsi_cmds(?) */ + if (!sc) { + task->task_done(task); + return 0; + } + scsi_req_abort_cmd(sc); + scsi_schedule_eh(sc->device->host); + return 0; + } + + spin_lock_irqsave(&task->task_state_lock, flags); + task->task_state_flags &= ~SAS_TASK_INITIATOR_ABORTED; + if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) + task->task_state_flags &= ~SAS_TASK_STATE_ABORTED; + spin_unlock_irqrestore(&task->task_state_lock, flags); + + return -EAGAIN; +} + +void sas_task_abort(struct sas_task *task) +{ + int i; + + for (i = 0; i < 5; i++) + if (!do_sas_task_abort(task)) + return; + + SAS_DPRINTK("%s: Could not kill task!\n", __FUNCTION__); +} + EXPORT_SYMBOL_GPL(sas_queuecommand); EXPORT_SYMBOL_GPL(sas_target_alloc); EXPORT_SYMBOL_GPL(sas_slave_configure); @@ -805,3 +864,4 @@ EXPORT_SYMBOL_GPL(sas_slave_destroy); EXPORT_SYMBOL_GPL(sas_change_queue_depth); EXPORT_SYMBOL_GPL(sas_change_queue_type); EXPORT_SYMBOL_GPL(sas_bios_param); +EXPORT_SYMBOL_GPL(sas_task_abort); diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index 7bf2e8b9903c..a1fc20a47c50 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h @@ -528,6 +528,8 @@ struct sas_task { void *lldd_task; /* for use by LLDDs */ void *uldd_task; + + struct work_struct abort_work; }; @@ -627,4 +629,6 @@ void sas_unregister_dev(struct domain_device *); void sas_init_dev(struct domain_device *); +void sas_task_abort(struct sas_task *task); + #endif /* _SASLIB_H_ */ -- cgit v1.2.3-58-ga151 From 4f777ed26086452737ea52597cf8de26137090d5 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Sat, 4 Nov 2006 20:11:36 +0100 Subject: [SCSI] kill scsi_assign_lock scsi_assign_lock has been unused for a long time and is a bad idea in general, so kill it. Signed-off-by: Christoph Hellwig Signed-off-by: James Bottomley --- Documentation/scsi/scsi_mid_low_api.txt | 31 +++++-------------------------- drivers/scsi/hosts.c | 4 ++-- include/scsi/scsi_host.h | 5 ----- 3 files changed, 7 insertions(+), 33 deletions(-) (limited to 'include') diff --git a/Documentation/scsi/scsi_mid_low_api.txt b/Documentation/scsi/scsi_mid_low_api.txt index 75a535a975c3..6f70f2b9327e 100644 --- a/Documentation/scsi/scsi_mid_low_api.txt +++ b/Documentation/scsi/scsi_mid_low_api.txt @@ -375,7 +375,6 @@ Summary: scsi_add_device - creates new scsi device (lu) instance scsi_add_host - perform sysfs registration and set up transport class scsi_adjust_queue_depth - change the queue depth on a SCSI device - scsi_assign_lock - replace default host_lock with given lock scsi_bios_ptable - return copy of block device's partition table scsi_block_requests - prevent further commands being queued to given host scsi_deactivate_tcq - turn off tag command queueing @@ -488,20 +487,6 @@ void scsi_adjust_queue_depth(struct scsi_device * sdev, int tagged, int tags) -/** - * scsi_assign_lock - replace default host_lock with given lock - * @shost: a pointer to a scsi host instance - * @lock: pointer to lock to replace host_lock for this host - * - * Returns nothing - * - * Might block: no - * - * Defined in: include/scsi/scsi_host.h . - **/ -void scsi_assign_lock(struct Scsi_Host *shost, spinlock_t *lock) - - /** * scsi_bios_ptable - return copy of block device's partition table * @dev: pointer to block device @@ -1366,17 +1351,11 @@ Locks Each struct Scsi_Host instance has a spin_lock called struct Scsi_Host::default_lock which is initialized in scsi_host_alloc() [found in hosts.c]. Within the same function the struct Scsi_Host::host_lock pointer -is initialized to point at default_lock with the scsi_assign_lock() function. -Thereafter lock and unlock operations performed by the mid level use the -struct Scsi_Host::host_lock pointer. - -LLDs can override the use of struct Scsi_Host::default_lock by -using scsi_assign_lock(). The earliest opportunity to do this would -be in the detect() function after it has invoked scsi_register(). It -could be replaced by a coarser grain lock (e.g. per driver) or a -lock of equal granularity (i.e. per host). Using finer grain locks -(e.g. per SCSI device) may be possible by juggling locks in -queuecommand(). +is initialized to point at default_lock. Thereafter lock and unlock +operations performed by the mid level use the struct Scsi_Host::host_lock +pointer. Previously drivers could override the host_lock pointer but +this is not allowed anymore. + Autosense ========= diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index 68ef1636678d..2ffdc9e0532d 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -301,8 +301,8 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize) if (!shost) return NULL; - 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); diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index ba5b3eb6b43f..e618e711ea57 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -651,11 +651,6 @@ extern const char *scsi_host_state_name(enum scsi_host_state); extern u64 scsi_calculate_bounce_limit(struct Scsi_Host *); -static inline void scsi_assign_lock(struct Scsi_Host *shost, spinlock_t *lock) -{ - shost->host_lock = lock; -} - static inline struct device *scsi_get_device(struct Scsi_Host *shost) { return shost->shost_gendev.parent; -- cgit v1.2.3-58-ga151 From dea22214790d1306f3a3444db13d2c726037b189 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 7 Nov 2006 17:28:55 -0800 Subject: [PATCH] aic94xx: handle REQ_DEVICE_RESET This patch implements a REQ_DEVICE_RESET handler for the aic94xx driver. Like the earlier REQ_TASK_ABORT patch, this patch defers the device reset to the Scsi_Host's workqueue, which has the added benefit of ensuring that the device reset does not happen at the same time that the abort tmfs are being processed. After the phy reset, the busted drive should go away and be re-detected later, which is indeed what I've seen on both a x260 and a x206m. Signed-off-by: Darrick J. Wong Signed-off-by: James Bottomley --- drivers/scsi/aic94xx/aic94xx_scb.c | 51 ++++++++++++++++++++++++++++++++----- drivers/scsi/libsas/sas_init.c | 2 +- drivers/scsi/libsas/sas_scsi_host.c | 1 + include/scsi/libsas.h | 1 + include/scsi/scsi_transport_sas.h | 2 ++ 5 files changed, 49 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/drivers/scsi/aic94xx/aic94xx_scb.c b/drivers/scsi/aic94xx/aic94xx_scb.c index 1911c5d17875..a014418d670e 100644 --- a/drivers/scsi/aic94xx/aic94xx_scb.c +++ b/drivers/scsi/aic94xx/aic94xx_scb.c @@ -343,6 +343,27 @@ void asd_invalidate_edb(struct asd_ascb *ascb, int edb_id) } } +/* hard reset a phy later */ +static void do_phy_reset_later(void *data) +{ + struct sas_phy *sas_phy = data; + int error; + + ASD_DPRINTK("%s: About to hard reset phy %d\n", __FUNCTION__, + sas_phy->identify.phy_identifier); + /* Reset device port */ + error = sas_phy_reset(sas_phy, 1); + if (error) + ASD_DPRINTK("%s: Hard reset of phy %d failed (%d).\n", + __FUNCTION__, sas_phy->identify.phy_identifier, error); +} + +static void phy_reset_later(struct sas_phy *sas_phy, struct Scsi_Host *shost) +{ + INIT_WORK(&sas_phy->reset_work, do_phy_reset_later, sas_phy); + queue_work(shost->work_q, &sas_phy->reset_work); +} + /* start up the ABORT TASK tmf... */ static void task_kill_later(struct asd_ascb *ascb) { @@ -402,7 +423,9 @@ static void escb_tasklet_complete(struct asd_ascb *ascb, goto out; } case REQ_DEVICE_RESET: { - struct asd_ascb *a, *b; + struct Scsi_Host *shost = sas_ha->core.shost; + struct sas_phy *dev_phy; + struct asd_ascb *a; u16 conn_handle; conn_handle = *((u16*)(&dl->status_block[1])); @@ -412,17 +435,31 @@ static void escb_tasklet_complete(struct asd_ascb *ascb, dl->status_block[3]); /* Kill all pending tasks and reset the device */ - list_for_each_entry_safe(a, b, &asd_ha->seq.pend_q, list) { - struct sas_task *task = a->uldd_task; - struct domain_device *dev = task->dev; + dev_phy = NULL; + list_for_each_entry(a, &asd_ha->seq.pend_q, list) { + struct sas_task *task; + struct domain_device *dev; u16 x; - x = *((u16*)(&dev->lldd_dev)); - if (x == conn_handle) + task = a->uldd_task; + if (!task) + continue; + dev = task->dev; + + x = (u16)dev->lldd_dev; + if (x == conn_handle) { + dev_phy = dev->port->phy; task_kill_later(a); + } } - /* FIXME: Reset device port (huh?) */ + /* Reset device port */ + if (!dev_phy) { + ASD_DPRINTK("%s: No pending commands; can't reset.\n", + __FUNCTION__); + goto out; + } + phy_reset_later(dev_phy, shost); goto out; } case SIGNAL_NCQ_ERROR: diff --git a/drivers/scsi/libsas/sas_init.c b/drivers/scsi/libsas/sas_init.c index a2b479a65908..0fb347b4b1a2 100644 --- a/drivers/scsi/libsas/sas_init.c +++ b/drivers/scsi/libsas/sas_init.c @@ -144,7 +144,7 @@ static int sas_get_linkerrors(struct sas_phy *phy) return sas_smp_get_phy_events(phy); } -static int sas_phy_reset(struct sas_phy *phy, int hard_reset) +int sas_phy_reset(struct sas_phy *phy, int hard_reset) { int ret; enum phy_func reset_type; diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c index c5fd37522728..e064aac06b90 100644 --- a/drivers/scsi/libsas/sas_scsi_host.c +++ b/drivers/scsi/libsas/sas_scsi_host.c @@ -865,3 +865,4 @@ EXPORT_SYMBOL_GPL(sas_change_queue_depth); EXPORT_SYMBOL_GPL(sas_change_queue_type); EXPORT_SYMBOL_GPL(sas_bios_param); EXPORT_SYMBOL_GPL(sas_task_abort); +EXPORT_SYMBOL_GPL(sas_phy_reset); diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index a1fc20a47c50..29f6e1af1bf9 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h @@ -597,6 +597,7 @@ struct sas_domain_function_template { extern int sas_register_ha(struct sas_ha_struct *); extern int sas_unregister_ha(struct sas_ha_struct *); +int sas_phy_reset(struct sas_phy *phy, int hard_reset); extern int sas_queuecommand(struct scsi_cmnd *, void (*scsi_done)(struct scsi_cmnd *)); extern int sas_target_alloc(struct scsi_target *); diff --git a/include/scsi/scsi_transport_sas.h b/include/scsi/scsi_transport_sas.h index 53024377f3b8..59633a82de47 100644 --- a/include/scsi/scsi_transport_sas.h +++ b/include/scsi/scsi_transport_sas.h @@ -73,6 +73,8 @@ struct sas_phy { /* for the list of phys belonging to a port */ struct list_head port_siblings; + + struct work_struct reset_work; }; #define dev_to_phy(d) \ -- cgit v1.2.3-58-ga151 From 1aa8fab2acf1cb8b341131b726773fcff0abc707 Mon Sep 17 00:00:00 2001 From: Matthew Wilcox Date: Wed, 22 Nov 2006 13:24:54 -0700 Subject: [SCSI] Make scsi_scan_host work for drivers which find their own targets If a driver can find its own targets, it can now fill in scan_finished and (optionally) scan_start in the scsi_host_template. Then, when it calls scsi_scan_host(), it will be called back (from a thread if asynchronous discovery is enabled), first to start the scan, and then at intervals to check if the scan is completed. Also make scsi_prep_async_scan and scsi_finish_async_scan static. Signed-off-by: Matthew Wilcox Signed-off-by: James Bottomley --- drivers/scsi/scsi_scan.c | 27 ++++++++++++++++++++------- include/scsi/scsi_host.h | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index 3ccaa4be92d8..4d656148bd67 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -1642,7 +1642,7 @@ static void scsi_sysfs_add_devices(struct Scsi_Host *shost) * that other asynchronous scans started after this one won't affect the * ordering of the discovered devices. */ -struct async_scan_data *scsi_prep_async_scan(struct Scsi_Host *shost) +static struct async_scan_data *scsi_prep_async_scan(struct Scsi_Host *shost) { struct async_scan_data *data; @@ -1686,7 +1686,7 @@ struct async_scan_data *scsi_prep_async_scan(struct Scsi_Host *shost) * This function announces all the devices it has found to the rest * of the system. */ -void scsi_finish_async_scan(struct async_scan_data *data) +static void scsi_finish_async_scan(struct async_scan_data *data) { struct Scsi_Host *shost; @@ -1719,12 +1719,25 @@ void scsi_finish_async_scan(struct async_scan_data *data) kfree(data); } -static int do_scan_async(void *_data) +static void do_scsi_scan_host(struct Scsi_Host *shost) { - struct async_scan_data *data = _data; - scsi_scan_host_selected(data->shost, SCAN_WILD_CARD, SCAN_WILD_CARD, + if (shost->hostt->scan_finished) { + unsigned long start = jiffies; + if (shost->hostt->scan_start) + shost->hostt->scan_start(shost); + + while (!shost->hostt->scan_finished(shost, jiffies - start)) + msleep(10); + } else { + scsi_scan_host_selected(shost, SCAN_WILD_CARD, SCAN_WILD_CARD, SCAN_WILD_CARD, 0); + } +} +static int do_scan_async(void *_data) +{ + struct async_scan_data *data = _data; + do_scsi_scan_host(data->shost); scsi_finish_async_scan(data); return 0; } @@ -1742,10 +1755,10 @@ void scsi_scan_host(struct Scsi_Host *shost) data = scsi_prep_async_scan(shost); if (!data) { - scsi_scan_host_selected(shost, SCAN_WILD_CARD, SCAN_WILD_CARD, - SCAN_WILD_CARD, 0); + do_scsi_scan_host(shost); return; } + kthread_run(do_scan_async, data, "scsi_scan_%d", shost->host_no); } EXPORT_SYMBOL(scsi_scan_host); diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index e618e711ea57..6d8945d71c65 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -240,6 +240,24 @@ struct scsi_host_template { */ void (* target_destroy)(struct scsi_target *); + /* + * If a host has the ability to discover targets on its own instead + * of scanning the entire bus, it can fill in this function and + * call scsi_scan_host(). This function will be called periodically + * until it returns 1 with the scsi_host and the elapsed time of + * the scan in jiffies. + * + * Status: OPTIONAL + */ + int (* scan_finished)(struct Scsi_Host *, unsigned long); + + /* + * If the host wants to be called before the scan starts, but + * after the midlayer has set up ready for the scan, it can fill + * in this function. + */ + void (* scan_start)(struct Scsi_Host *); + /* * fill in this function to allow the queue depth of this host * to be changeable (on a per device basis). returns either -- cgit v1.2.3-58-ga151 From b58d91547fb17c65ad621f3f98b1f2c228c812a5 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Thu, 16 Nov 2006 19:24:10 +0900 Subject: [SCSI] export scsi-ml functions needed by tgt_scsi_lib and its LLDs This patch contains the needed changes to the scsi-ml for the target mode support. Note, per the last review we moved almost all the fields we added to the scsi_cmnd to our internal data structure which we are going to try and kill off when we can replace it with support from other parts of the kernel. The one field we left on was the offset variable. This is needed to handle the case where the target gets request that is so large that it cannot execute it in one dma operation. So max_secotors or a segment limit may limit the size of the transfer. In this case our tgt core code will break up the command into managable transfers and send them to the LLD one at a time. The offset is then used to tell the LLD where in the command we are at. Is there another field on the scsi_cmd for that? Signed-off-by: Mike Christie Signed-off-by: FUJITA Tomonori Signed-off-by: James Bottomley --- drivers/scsi/hosts.c | 4 ++++ drivers/scsi/scsi.c | 43 ++++++++++++++++++++++++++----------------- drivers/scsi/scsi_lib.c | 33 ++++++++++++++++++++++++--------- include/scsi/scsi_cmnd.h | 10 ++++++++++ include/scsi/scsi_host.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 26 deletions(-) (limited to 'include') diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index 2ffdc9e0532d..38c3a291efac 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -263,6 +263,10 @@ static void scsi_host_dev_release(struct device *dev) kthread_stop(shost->ehandler); if (shost->work_q) destroy_workqueue(shost->work_q); + if (shost->uspace_req_q) { + kfree(shost->uspace_req_q->queuedata); + scsi_free_queue(shost->uspace_req_q); + } scsi_destroy_command_freelist(shost); if (shost->bqt) diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c index 780d6dc92b42..fafc00deaade 100644 --- a/drivers/scsi/scsi.c +++ b/drivers/scsi/scsi.c @@ -156,8 +156,7 @@ static struct scsi_host_cmd_pool scsi_cmd_dma_pool = { static DEFINE_MUTEX(host_cmd_pool_mutex); -static struct scsi_cmnd *__scsi_get_command(struct Scsi_Host *shost, - gfp_t gfp_mask) +struct scsi_cmnd *__scsi_get_command(struct Scsi_Host *shost, gfp_t gfp_mask) { struct scsi_cmnd *cmd; @@ -178,6 +177,7 @@ static struct scsi_cmnd *__scsi_get_command(struct Scsi_Host *shost, return cmd; } +EXPORT_SYMBOL_GPL(__scsi_get_command); /* * Function: scsi_get_command() @@ -214,9 +214,29 @@ struct scsi_cmnd *scsi_get_command(struct scsi_device *dev, gfp_t gfp_mask) put_device(&dev->sdev_gendev); return cmd; -} +} EXPORT_SYMBOL(scsi_get_command); +void __scsi_put_command(struct Scsi_Host *shost, struct scsi_cmnd *cmd, + struct device *dev) +{ + unsigned long flags; + + /* changing locks here, don't need to restore the irq state */ + spin_lock_irqsave(&shost->free_list_lock, flags); + if (unlikely(list_empty(&shost->free_list))) { + list_add(&cmd->list, &shost->free_list); + cmd = NULL; + } + spin_unlock_irqrestore(&shost->free_list_lock, flags); + + if (likely(cmd != NULL)) + kmem_cache_free(shost->cmd_pool->slab, cmd); + + put_device(dev); +} +EXPORT_SYMBOL(__scsi_put_command); + /* * Function: scsi_put_command() * @@ -231,26 +251,15 @@ EXPORT_SYMBOL(scsi_get_command); void scsi_put_command(struct scsi_cmnd *cmd) { struct scsi_device *sdev = cmd->device; - struct Scsi_Host *shost = sdev->host; unsigned long flags; - + /* serious error if the command hasn't come from a device list */ spin_lock_irqsave(&cmd->device->list_lock, flags); BUG_ON(list_empty(&cmd->list)); list_del_init(&cmd->list); - spin_unlock(&cmd->device->list_lock); - /* changing locks here, don't need to restore the irq state */ - spin_lock(&shost->free_list_lock); - if (unlikely(list_empty(&shost->free_list))) { - list_add(&cmd->list, &shost->free_list); - cmd = NULL; - } - spin_unlock_irqrestore(&shost->free_list_lock, flags); + spin_unlock_irqrestore(&cmd->device->list_lock, flags); - if (likely(cmd != NULL)) - kmem_cache_free(shost->cmd_pool->slab, cmd); - - put_device(&sdev->sdev_gendev); + __scsi_put_command(cmd->device->host, cmd, &sdev->sdev_gendev); } EXPORT_SYMBOL(scsi_put_command); diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 2f12f9f12fcb..fb616c69151f 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -704,7 +704,7 @@ static struct scsi_cmnd *scsi_end_request(struct scsi_cmnd *cmd, int uptodate, return NULL; } -static struct scatterlist *scsi_alloc_sgtable(struct scsi_cmnd *cmd, gfp_t gfp_mask) +struct scatterlist *scsi_alloc_sgtable(struct scsi_cmnd *cmd, gfp_t gfp_mask) { struct scsi_host_sg_pool *sgp; struct scatterlist *sgl; @@ -745,7 +745,9 @@ static struct scatterlist *scsi_alloc_sgtable(struct scsi_cmnd *cmd, gfp_t gfp_m return sgl; } -static void scsi_free_sgtable(struct scatterlist *sgl, int index) +EXPORT_SYMBOL(scsi_alloc_sgtable); + +void scsi_free_sgtable(struct scatterlist *sgl, int index) { struct scsi_host_sg_pool *sgp; @@ -755,6 +757,8 @@ static void scsi_free_sgtable(struct scatterlist *sgl, int index) mempool_free(sgl, sgp->pool); } +EXPORT_SYMBOL(scsi_free_sgtable); + /* * Function: scsi_release_buffers() * @@ -1567,29 +1571,40 @@ u64 scsi_calculate_bounce_limit(struct Scsi_Host *shost) } EXPORT_SYMBOL(scsi_calculate_bounce_limit); -struct request_queue *scsi_alloc_queue(struct scsi_device *sdev) +struct request_queue *__scsi_alloc_queue(struct Scsi_Host *shost, + request_fn_proc *request_fn) { - struct Scsi_Host *shost = sdev->host; struct request_queue *q; - q = blk_init_queue(scsi_request_fn, NULL); + q = blk_init_queue(request_fn, NULL); if (!q) return NULL; - blk_queue_prep_rq(q, scsi_prep_fn); - blk_queue_max_hw_segments(q, shost->sg_tablesize); blk_queue_max_phys_segments(q, SCSI_MAX_PHYS_SEGMENTS); blk_queue_max_sectors(q, shost->max_sectors); blk_queue_bounce_limit(q, scsi_calculate_bounce_limit(shost)); blk_queue_segment_boundary(q, shost->dma_boundary); - blk_queue_issue_flush_fn(q, scsi_issue_flush_fn); - blk_queue_softirq_done(q, scsi_softirq_done); if (!shost->use_clustering) clear_bit(QUEUE_FLAG_CLUSTER, &q->queue_flags); return q; } +EXPORT_SYMBOL(__scsi_alloc_queue); + +struct request_queue *scsi_alloc_queue(struct scsi_device *sdev) +{ + struct request_queue *q; + + q = __scsi_alloc_queue(sdev->host, scsi_request_fn); + if (!q) + return NULL; + + blk_queue_prep_rq(q, scsi_prep_fn); + blk_queue_issue_flush_fn(q, scsi_issue_flush_fn); + blk_queue_softirq_done(q, scsi_softirq_done); + return q; +} void scsi_free_queue(struct request_queue *q) { diff --git a/include/scsi/scsi_cmnd.h b/include/scsi/scsi_cmnd.h index be117f812deb..d6948d0e8cdb 100644 --- a/include/scsi/scsi_cmnd.h +++ b/include/scsi/scsi_cmnd.h @@ -8,6 +8,7 @@ struct request; struct scatterlist; +struct Scsi_Host; struct scsi_device; @@ -72,6 +73,9 @@ struct scsi_cmnd { unsigned short use_sg; /* Number of pieces of scatter-gather */ unsigned short sglist_len; /* size of malloc'd scatter-gather list */ + /* offset in cmd we are at (for multi-transfer tgt cmds) */ + unsigned offset; + unsigned underflow; /* Return error if less than this amount is transferred */ @@ -119,7 +123,10 @@ struct scsi_cmnd { }; extern struct scsi_cmnd *scsi_get_command(struct scsi_device *, gfp_t); +extern struct scsi_cmnd *__scsi_get_command(struct Scsi_Host *, gfp_t); extern void scsi_put_command(struct scsi_cmnd *); +extern void __scsi_put_command(struct Scsi_Host *, struct scsi_cmnd *, + struct device *); extern void scsi_io_completion(struct scsi_cmnd *, unsigned int); extern void scsi_finish_command(struct scsi_cmnd *cmd); extern void scsi_req_abort_cmd(struct scsi_cmnd *cmd); @@ -128,4 +135,7 @@ extern void *scsi_kmap_atomic_sg(struct scatterlist *sg, int sg_count, size_t *offset, size_t *len); extern void scsi_kunmap_atomic_sg(void *virt); +extern struct scatterlist *scsi_alloc_sgtable(struct scsi_cmnd *, gfp_t); +extern void scsi_free_sgtable(struct scatterlist *, int); + #endif /* _SCSI_SCSI_CMND_H */ diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index 6d8945d71c65..7f1f411d07af 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -7,6 +7,7 @@ #include #include +struct request_queue; struct block_device; struct completion; struct module; @@ -123,6 +124,39 @@ struct scsi_host_template { int (* queuecommand)(struct scsi_cmnd *, void (*done)(struct scsi_cmnd *)); + /* + * The transfer functions are used to queue a scsi command to + * the LLD. When the driver is finished processing the command + * the done callback is invoked. + * + * return values: see queuecommand + * + * If the LLD accepts the cmd, it should set the result to an + * appropriate value when completed before calling the done function. + * + * STATUS: REQUIRED FOR TARGET DRIVERS + */ + /* TODO: rename */ + int (* transfer_response)(struct scsi_cmnd *, + void (*done)(struct scsi_cmnd *)); + /* + * This is called to inform the LLD to transfer cmd->request_bufflen + * bytes of the cmd at cmd->offset in the cmd. The cmd->use_sg + * speciefies the number of scatterlist entried in the command + * and cmd->request_buffer contains the scatterlist. + * + * If the command cannot be processed in one transfer_data call + * becuase a scatterlist within the LLD's limits cannot be + * created then transfer_data will be called multiple times. + * It is initially called from process context, and later + * calls are from the interrup context. + */ + int (* transfer_data)(struct scsi_cmnd *, + void (*done)(struct scsi_cmnd *)); + + /* Used as callback for the completion of task management request. */ + int (* tsk_mgmt_response)(u64 mid, int result); + /* * This is an error handling strategy routine. You don't need to * define one of these if you don't want to - there is a default @@ -589,6 +623,12 @@ struct Scsi_Host { */ unsigned int max_host_blocked; + /* + * q used for scsi_tgt msgs, async events or any other requests that + * need to be processed in userspace + */ + struct request_queue *uspace_req_q; + /* legacy crap */ unsigned long base; unsigned long io_port; @@ -687,6 +727,9 @@ extern void scsi_unblock_requests(struct Scsi_Host *); extern void scsi_block_requests(struct Scsi_Host *); struct class_container; + +extern struct request_queue *__scsi_alloc_queue(struct Scsi_Host *shost, + void (*) (struct request_queue *)); /* * These two functions are used to allocate and free a pseudo device * which will connect to the host adapter itself rather than any -- cgit v1.2.3-58-ga151 From 5a55c2596f55e3a60f7502d0dfcfa0d20241e5ac Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Thu, 16 Nov 2006 19:24:13 +0900 Subject: [SCSI] scsi tgt: scsi target lib functionality The core scsi target lib functions. TODO: - mv md/dm-bio-list.h to linux/bio-list.h so md and us do not have to do that weird include. - convert scsi_tgt_cmd's work struct to James's execute code. And try to kill our scsi_tgt_cmd. - add host state checking. We do refcouting so hotplug is partially supported, but we need to add state checking to make it easier on the LLD. Signed-off-by: Mike Christie Signed-off-by: FUJITA Tomonori Signed-off-by: James Bottomley --- drivers/scsi/scsi_tgt_lib.c | 742 +++++++++++++++++++++++++++++++++++++++++++ drivers/scsi/scsi_tgt_priv.h | 25 ++ include/scsi/scsi_tgt.h | 19 ++ 3 files changed, 786 insertions(+) create mode 100644 drivers/scsi/scsi_tgt_lib.c create mode 100644 drivers/scsi/scsi_tgt_priv.h create mode 100644 include/scsi/scsi_tgt.h (limited to 'include') diff --git a/drivers/scsi/scsi_tgt_lib.c b/drivers/scsi/scsi_tgt_lib.c new file mode 100644 index 000000000000..39da5cd6fb6f --- /dev/null +++ b/drivers/scsi/scsi_tgt_lib.c @@ -0,0 +1,742 @@ +/* + * SCSI target lib functions + * + * Copyright (C) 2005 Mike Christie + * Copyright (C) 2005 FUJITA Tomonori + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include <../drivers/md/dm-bio-list.h> + +#include "scsi_tgt_priv.h" + +static struct workqueue_struct *scsi_tgtd; +static kmem_cache_t *scsi_tgt_cmd_cache; + +/* + * TODO: this struct will be killed when the block layer supports large bios + * and James's work struct code is in + */ +struct scsi_tgt_cmd { + /* TODO replace work with James b's code */ + struct work_struct work; + /* TODO replace the lists with a large bio */ + struct bio_list xfer_done_list; + struct bio_list xfer_list; + + struct list_head hash_list; + struct request *rq; + u64 tag; + + void *buffer; + unsigned bufflen; +}; + +#define TGT_HASH_ORDER 4 +#define cmd_hashfn(tag) hash_long((unsigned long) (tag), TGT_HASH_ORDER) + +struct scsi_tgt_queuedata { + struct Scsi_Host *shost; + struct list_head cmd_hash[1 << TGT_HASH_ORDER]; + spinlock_t cmd_hash_lock; +}; + +/* + * Function: scsi_host_get_command() + * + * Purpose: Allocate and setup a scsi command block and blk request + * + * Arguments: shost - scsi host + * data_dir - dma data dir + * gfp_mask- allocator flags + * + * Returns: The allocated scsi command structure. + * + * This should be called by target LLDs to get a command. + */ +struct scsi_cmnd *scsi_host_get_command(struct Scsi_Host *shost, + enum dma_data_direction data_dir, + gfp_t gfp_mask) +{ + int write = (data_dir == DMA_TO_DEVICE); + struct request *rq; + struct scsi_cmnd *cmd; + struct scsi_tgt_cmd *tcmd; + + /* Bail if we can't get a reference to the device */ + if (!get_device(&shost->shost_gendev)) + return NULL; + + tcmd = kmem_cache_alloc(scsi_tgt_cmd_cache, GFP_ATOMIC); + if (!tcmd) + goto put_dev; + + rq = blk_get_request(shost->uspace_req_q, write, gfp_mask); + if (!rq) + goto free_tcmd; + + cmd = __scsi_get_command(shost, gfp_mask); + if (!cmd) + goto release_rq; + + memset(cmd, 0, sizeof(*cmd)); + cmd->sc_data_direction = data_dir; + cmd->jiffies_at_alloc = jiffies; + cmd->request = rq; + + rq->special = cmd; + rq->cmd_type = REQ_TYPE_SPECIAL; + rq->cmd_flags |= REQ_TYPE_BLOCK_PC; + rq->end_io_data = tcmd; + + bio_list_init(&tcmd->xfer_list); + bio_list_init(&tcmd->xfer_done_list); + tcmd->rq = rq; + + return cmd; + +release_rq: + blk_put_request(rq); +free_tcmd: + kmem_cache_free(scsi_tgt_cmd_cache, tcmd); +put_dev: + put_device(&shost->shost_gendev); + return NULL; + +} +EXPORT_SYMBOL_GPL(scsi_host_get_command); + +/* + * Function: scsi_host_put_command() + * + * Purpose: Free a scsi command block + * + * Arguments: shost - scsi host + * cmd - command block to free + * + * Returns: Nothing. + * + * Notes: The command must not belong to any lists. + */ +void scsi_host_put_command(struct Scsi_Host *shost, struct scsi_cmnd *cmd) +{ + struct request_queue *q = shost->uspace_req_q; + struct request *rq = cmd->request; + struct scsi_tgt_cmd *tcmd = rq->end_io_data; + unsigned long flags; + + kmem_cache_free(scsi_tgt_cmd_cache, tcmd); + + spin_lock_irqsave(q->queue_lock, flags); + __blk_put_request(q, rq); + spin_unlock_irqrestore(q->queue_lock, flags); + + __scsi_put_command(shost, cmd, &shost->shost_gendev); +} +EXPORT_SYMBOL_GPL(scsi_host_put_command); + +static void scsi_unmap_user_pages(struct scsi_tgt_cmd *tcmd) +{ + struct bio *bio; + + /* must call bio_endio in case bio was bounced */ + while ((bio = bio_list_pop(&tcmd->xfer_done_list))) { + bio_endio(bio, bio->bi_size, 0); + bio_unmap_user(bio); + } + + while ((bio = bio_list_pop(&tcmd->xfer_list))) { + bio_endio(bio, bio->bi_size, 0); + bio_unmap_user(bio); + } +} + +static void cmd_hashlist_del(struct scsi_cmnd *cmd) +{ + struct request_queue *q = cmd->request->q; + struct scsi_tgt_queuedata *qdata = q->queuedata; + unsigned long flags; + struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; + + spin_lock_irqsave(&qdata->cmd_hash_lock, flags); + list_del(&tcmd->hash_list); + spin_unlock_irqrestore(&qdata->cmd_hash_lock, flags); +} + +static void scsi_tgt_cmd_destroy(void *data) +{ + struct scsi_cmnd *cmd = data; + struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; + + dprintk("cmd %p %d %lu\n", cmd, cmd->sc_data_direction, + rq_data_dir(cmd->request)); + /* + * We fix rq->cmd_flags here since when we told bio_map_user + * to write vm for WRITE commands, blk_rq_bio_prep set + * rq_data_dir the flags to READ. + */ + if (cmd->sc_data_direction == DMA_TO_DEVICE) + cmd->request->cmd_flags |= REQ_RW; + else + cmd->request->cmd_flags &= ~REQ_RW; + + scsi_unmap_user_pages(tcmd); + scsi_host_put_command(scsi_tgt_cmd_to_host(cmd), cmd); +} + +static void init_scsi_tgt_cmd(struct request *rq, struct scsi_tgt_cmd *tcmd, + u64 tag) +{ + struct scsi_tgt_queuedata *qdata = rq->q->queuedata; + unsigned long flags; + struct list_head *head; + + tcmd->tag = tag; + spin_lock_irqsave(&qdata->cmd_hash_lock, flags); + head = &qdata->cmd_hash[cmd_hashfn(tag)]; + list_add(&tcmd->hash_list, head); + spin_unlock_irqrestore(&qdata->cmd_hash_lock, flags); +} + +/* + * scsi_tgt_alloc_queue - setup queue used for message passing + * shost: scsi host + * + * This should be called by the LLD after host allocation. + * And will be released when the host is released. + */ +int scsi_tgt_alloc_queue(struct Scsi_Host *shost) +{ + struct scsi_tgt_queuedata *queuedata; + struct request_queue *q; + int err, i; + + /* + * Do we need to send a netlink event or should uspace + * just respond to the hotplug event? + */ + q = __scsi_alloc_queue(shost, NULL); + if (!q) + return -ENOMEM; + + queuedata = kzalloc(sizeof(*queuedata), GFP_KERNEL); + if (!queuedata) { + err = -ENOMEM; + goto cleanup_queue; + } + queuedata->shost = shost; + q->queuedata = queuedata; + + /* + * this is a silly hack. We should probably just queue as many + * command as is recvd to userspace. uspace can then make + * sure we do not overload the HBA + */ + q->nr_requests = shost->hostt->can_queue; + /* + * We currently only support software LLDs so this does + * not matter for now. Do we need this for the cards we support? + * If so we should make it a host template value. + */ + blk_queue_dma_alignment(q, 0); + shost->uspace_req_q = q; + + for (i = 0; i < ARRAY_SIZE(queuedata->cmd_hash); i++) + INIT_LIST_HEAD(&queuedata->cmd_hash[i]); + spin_lock_init(&queuedata->cmd_hash_lock); + + return 0; + +cleanup_queue: + blk_cleanup_queue(q); + return err; +} +EXPORT_SYMBOL_GPL(scsi_tgt_alloc_queue); + +void scsi_tgt_free_queue(struct Scsi_Host *shost) +{ + int i; + unsigned long flags; + struct request_queue *q = shost->uspace_req_q; + struct scsi_cmnd *cmd; + struct scsi_tgt_queuedata *qdata = q->queuedata; + struct scsi_tgt_cmd *tcmd, *n; + LIST_HEAD(cmds); + + spin_lock_irqsave(&qdata->cmd_hash_lock, flags); + + for (i = 0; i < ARRAY_SIZE(qdata->cmd_hash); i++) { + list_for_each_entry_safe(tcmd, n, &qdata->cmd_hash[i], + hash_list) { + list_del(&tcmd->hash_list); + list_add(&tcmd->hash_list, &cmds); + } + } + + spin_unlock_irqrestore(&qdata->cmd_hash_lock, flags); + + while (!list_empty(&cmds)) { + tcmd = list_entry(cmds.next, struct scsi_tgt_cmd, hash_list); + list_del(&tcmd->hash_list); + cmd = tcmd->rq->special; + + shost->hostt->eh_abort_handler(cmd); + scsi_tgt_cmd_destroy(cmd); + } +} +EXPORT_SYMBOL_GPL(scsi_tgt_free_queue); + +struct Scsi_Host *scsi_tgt_cmd_to_host(struct scsi_cmnd *cmd) +{ + struct scsi_tgt_queuedata *queue = cmd->request->q->queuedata; + return queue->shost; +} +EXPORT_SYMBOL_GPL(scsi_tgt_cmd_to_host); + +/* + * scsi_tgt_queue_command - queue command for userspace processing + * @cmd: scsi command + * @scsilun: scsi lun + * @tag: unique value to identify this command for tmf + */ +int scsi_tgt_queue_command(struct scsi_cmnd *cmd, struct scsi_lun *scsilun, + u64 tag) +{ + struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; + int err; + + init_scsi_tgt_cmd(cmd->request, tcmd, tag); + err = scsi_tgt_uspace_send_cmd(cmd, scsilun, tag); + if (err) + cmd_hashlist_del(cmd); + + return err; +} +EXPORT_SYMBOL_GPL(scsi_tgt_queue_command); + +/* + * This is run from a interrpt handler normally and the unmap + * needs process context so we must queue + */ +static void scsi_tgt_cmd_done(struct scsi_cmnd *cmd) +{ + struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; + + dprintk("cmd %p %lu\n", cmd, rq_data_dir(cmd->request)); + + scsi_tgt_uspace_send_status(cmd, tcmd->tag); + INIT_WORK(&tcmd->work, scsi_tgt_cmd_destroy, cmd); + queue_work(scsi_tgtd, &tcmd->work); +} + +static int __scsi_tgt_transfer_response(struct scsi_cmnd *cmd) +{ + struct Scsi_Host *shost = scsi_tgt_cmd_to_host(cmd); + int err; + + dprintk("cmd %p %lu\n", cmd, rq_data_dir(cmd->request)); + + err = shost->hostt->transfer_response(cmd, scsi_tgt_cmd_done); + switch (err) { + case SCSI_MLQUEUE_HOST_BUSY: + case SCSI_MLQUEUE_DEVICE_BUSY: + return -EAGAIN; + } + + return 0; +} + +static void scsi_tgt_transfer_response(struct scsi_cmnd *cmd) +{ + struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; + int err; + + err = __scsi_tgt_transfer_response(cmd); + if (!err) + return; + + cmd->result = DID_BUS_BUSY << 16; + err = scsi_tgt_uspace_send_status(cmd, tcmd->tag); + if (err <= 0) + /* the eh will have to pick this up */ + printk(KERN_ERR "Could not send cmd %p status\n", cmd); +} + +static int scsi_tgt_init_cmd(struct scsi_cmnd *cmd, gfp_t gfp_mask) +{ + struct request *rq = cmd->request; + struct scsi_tgt_cmd *tcmd = rq->end_io_data; + int count; + + cmd->use_sg = rq->nr_phys_segments; + cmd->request_buffer = scsi_alloc_sgtable(cmd, gfp_mask); + if (!cmd->request_buffer) + return -ENOMEM; + + cmd->request_bufflen = rq->data_len; + + dprintk("cmd %p addr %p cnt %d %lu\n", cmd, tcmd->buffer, cmd->use_sg, + rq_data_dir(rq)); + count = blk_rq_map_sg(rq->q, rq, cmd->request_buffer); + if (likely(count <= cmd->use_sg)) { + cmd->use_sg = count; + return 0; + } + + eprintk("cmd %p addr %p cnt %d\n", cmd, tcmd->buffer, cmd->use_sg); + scsi_free_sgtable(cmd->request_buffer, cmd->sglist_len); + return -EINVAL; +} + +/* TODO: test this crap and replace bio_map_user with new interface maybe */ +static int scsi_map_user_pages(struct scsi_tgt_cmd *tcmd, struct scsi_cmnd *cmd, + int rw) +{ + struct request_queue *q = cmd->request->q; + struct request *rq = cmd->request; + void *uaddr = tcmd->buffer; + unsigned int len = tcmd->bufflen; + struct bio *bio; + int err; + + while (len > 0) { + dprintk("%lx %u\n", (unsigned long) uaddr, len); + bio = bio_map_user(q, NULL, (unsigned long) uaddr, len, rw); + if (IS_ERR(bio)) { + err = PTR_ERR(bio); + dprintk("fail to map %lx %u %d %x\n", + (unsigned long) uaddr, len, err, cmd->cmnd[0]); + goto unmap_bios; + } + + uaddr += bio->bi_size; + len -= bio->bi_size; + + /* + * The first bio is added and merged. We could probably + * try to add others using scsi_merge_bio() but for now + * we keep it simple. The first bio should be pretty large + * (either hitting the 1 MB bio pages limit or a queue limit) + * already but for really large IO we may want to try and + * merge these. + */ + if (!rq->bio) { + blk_rq_bio_prep(q, rq, bio); + rq->data_len = bio->bi_size; + } else + /* put list of bios to transfer in next go around */ + bio_list_add(&tcmd->xfer_list, bio); + } + + cmd->offset = 0; + err = scsi_tgt_init_cmd(cmd, GFP_KERNEL); + if (err) + goto unmap_bios; + + return 0; + +unmap_bios: + if (rq->bio) { + bio_unmap_user(rq->bio); + while ((bio = bio_list_pop(&tcmd->xfer_list))) + bio_unmap_user(bio); + } + + return err; +} + +static int scsi_tgt_transfer_data(struct scsi_cmnd *); + +static void scsi_tgt_data_transfer_done(struct scsi_cmnd *cmd) +{ + struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; + struct bio *bio; + int err; + + /* should we free resources here on error ? */ + if (cmd->result) { +send_uspace_err: + err = scsi_tgt_uspace_send_status(cmd, tcmd->tag); + if (err <= 0) + /* the tgt uspace eh will have to pick this up */ + printk(KERN_ERR "Could not send cmd %p status\n", cmd); + return; + } + + dprintk("cmd %p request_bufflen %u bufflen %u\n", + cmd, cmd->request_bufflen, tcmd->bufflen); + + scsi_free_sgtable(cmd->request_buffer, cmd->sglist_len); + bio_list_add(&tcmd->xfer_done_list, cmd->request->bio); + + tcmd->buffer += cmd->request_bufflen; + cmd->offset += cmd->request_bufflen; + + if (!tcmd->xfer_list.head) { + scsi_tgt_transfer_response(cmd); + return; + } + + dprintk("cmd2 %p request_bufflen %u bufflen %u\n", + cmd, cmd->request_bufflen, tcmd->bufflen); + + bio = bio_list_pop(&tcmd->xfer_list); + BUG_ON(!bio); + + blk_rq_bio_prep(cmd->request->q, cmd->request, bio); + cmd->request->data_len = bio->bi_size; + err = scsi_tgt_init_cmd(cmd, GFP_ATOMIC); + if (err) { + cmd->result = DID_ERROR << 16; + goto send_uspace_err; + } + + if (scsi_tgt_transfer_data(cmd)) { + cmd->result = DID_NO_CONNECT << 16; + goto send_uspace_err; + } +} + +static int scsi_tgt_transfer_data(struct scsi_cmnd *cmd) +{ + int err; + struct Scsi_Host *host = scsi_tgt_cmd_to_host(cmd); + + err = host->hostt->transfer_data(cmd, scsi_tgt_data_transfer_done); + switch (err) { + case SCSI_MLQUEUE_HOST_BUSY: + case SCSI_MLQUEUE_DEVICE_BUSY: + return -EAGAIN; + default: + return 0; + } +} + +static int scsi_tgt_copy_sense(struct scsi_cmnd *cmd, unsigned long uaddr, + unsigned len) +{ + char __user *p = (char __user *) uaddr; + + if (copy_from_user(cmd->sense_buffer, p, + min_t(unsigned, SCSI_SENSE_BUFFERSIZE, len))) { + printk(KERN_ERR "Could not copy the sense buffer\n"); + return -EIO; + } + return 0; +} + +static int scsi_tgt_abort_cmd(struct Scsi_Host *shost, struct scsi_cmnd *cmd) +{ + int err; + + err = shost->hostt->eh_abort_handler(cmd); + if (err) + eprintk("fail to abort %p\n", cmd); + + scsi_tgt_cmd_destroy(cmd); + return err; +} + +static struct request *tgt_cmd_hash_lookup(struct request_queue *q, u64 tag) +{ + struct scsi_tgt_queuedata *qdata = q->queuedata; + struct request *rq = NULL; + struct list_head *head; + struct scsi_tgt_cmd *tcmd; + unsigned long flags; + + head = &qdata->cmd_hash[cmd_hashfn(tag)]; + spin_lock_irqsave(&qdata->cmd_hash_lock, flags); + list_for_each_entry(tcmd, head, hash_list) { + if (tcmd->tag == tag) { + rq = tcmd->rq; + list_del(&tcmd->hash_list); + break; + } + } + spin_unlock_irqrestore(&qdata->cmd_hash_lock, flags); + + return rq; +} + +int scsi_tgt_kspace_exec(int host_no, u64 tag, int result, u32 len, + unsigned long uaddr, u8 rw) +{ + struct Scsi_Host *shost; + struct scsi_cmnd *cmd; + struct request *rq; + struct scsi_tgt_cmd *tcmd; + int err = 0; + + dprintk("%d %llu %d %u %lx %u\n", host_no, (unsigned long long) tag, + result, len, uaddr, rw); + + /* TODO: replace with a O(1) alg */ + shost = scsi_host_lookup(host_no); + if (IS_ERR(shost)) { + printk(KERN_ERR "Could not find host no %d\n", host_no); + return -EINVAL; + } + + if (!shost->uspace_req_q) { + printk(KERN_ERR "Not target scsi host %d\n", host_no); + goto done; + } + + rq = tgt_cmd_hash_lookup(shost->uspace_req_q, tag); + if (!rq) { + printk(KERN_ERR "Could not find tag %llu\n", + (unsigned long long) tag); + err = -EINVAL; + goto done; + } + cmd = rq->special; + + dprintk("cmd %p result %d len %d bufflen %u %lu %x\n", cmd, + result, len, cmd->request_bufflen, rq_data_dir(rq), cmd->cmnd[0]); + + if (result == TASK_ABORTED) { + scsi_tgt_abort_cmd(shost, cmd); + goto done; + } + /* + * store the userspace values here, the working values are + * in the request_* values + */ + tcmd = cmd->request->end_io_data; + tcmd->buffer = (void *)uaddr; + tcmd->bufflen = len; + cmd->result = result; + + if (!tcmd->bufflen || cmd->request_buffer) { + err = __scsi_tgt_transfer_response(cmd); + goto done; + } + + /* + * TODO: Do we need to handle case where request does not + * align with LLD. + */ + err = scsi_map_user_pages(rq->end_io_data, cmd, rw); + if (err) { + eprintk("%p %d\n", cmd, err); + err = -EAGAIN; + goto done; + } + + /* userspace failure */ + if (cmd->result) { + if (status_byte(cmd->result) == CHECK_CONDITION) + scsi_tgt_copy_sense(cmd, uaddr, len); + err = __scsi_tgt_transfer_response(cmd); + goto done; + } + /* ask the target LLD to transfer the data to the buffer */ + err = scsi_tgt_transfer_data(cmd); + +done: + scsi_host_put(shost); + return err; +} + +int scsi_tgt_tsk_mgmt_request(struct Scsi_Host *shost, int function, u64 tag, + struct scsi_lun *scsilun, void *data) +{ + int err; + + /* TODO: need to retry if this fails. */ + err = scsi_tgt_uspace_send_tsk_mgmt(shost->host_no, function, + tag, scsilun, data); + if (err < 0) + eprintk("The task management request lost!\n"); + return err; +} +EXPORT_SYMBOL_GPL(scsi_tgt_tsk_mgmt_request); + +int scsi_tgt_kspace_tsk_mgmt(int host_no, u64 mid, int result) +{ + struct Scsi_Host *shost; + int err = -EINVAL; + + dprintk("%d %d %llx\n", host_no, result, (unsigned long long) mid); + + shost = scsi_host_lookup(host_no); + if (IS_ERR(shost)) { + printk(KERN_ERR "Could not find host no %d\n", host_no); + return err; + } + + if (!shost->uspace_req_q) { + printk(KERN_ERR "Not target scsi host %d\n", host_no); + goto done; + } + + err = shost->hostt->tsk_mgmt_response(mid, result); +done: + scsi_host_put(shost); + return err; +} + +static int __init scsi_tgt_init(void) +{ + int err; + + scsi_tgt_cmd_cache = kmem_cache_create("scsi_tgt_cmd", + sizeof(struct scsi_tgt_cmd), + 0, 0, NULL, NULL); + if (!scsi_tgt_cmd_cache) + return -ENOMEM; + + scsi_tgtd = create_workqueue("scsi_tgtd"); + if (!scsi_tgtd) { + err = -ENOMEM; + goto free_kmemcache; + } + + err = scsi_tgt_if_init(); + if (err) + goto destroy_wq; + + return 0; + +destroy_wq: + destroy_workqueue(scsi_tgtd); +free_kmemcache: + kmem_cache_destroy(scsi_tgt_cmd_cache); + return err; +} + +static void __exit scsi_tgt_exit(void) +{ + destroy_workqueue(scsi_tgtd); + scsi_tgt_if_exit(); + kmem_cache_destroy(scsi_tgt_cmd_cache); +} + +module_init(scsi_tgt_init); +module_exit(scsi_tgt_exit); + +MODULE_DESCRIPTION("SCSI target core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/scsi_tgt_priv.h b/drivers/scsi/scsi_tgt_priv.h new file mode 100644 index 000000000000..84488c51ff62 --- /dev/null +++ b/drivers/scsi/scsi_tgt_priv.h @@ -0,0 +1,25 @@ +struct scsi_cmnd; +struct scsi_lun; +struct Scsi_Host; +struct task_struct; + +/* tmp - will replace with SCSI logging stuff */ +#define eprintk(fmt, args...) \ +do { \ + printk("%s(%d) " fmt, __FUNCTION__, __LINE__, ##args); \ +} while (0) + +#define dprintk(fmt, args...) +/* #define dprintk eprintk */ + +extern void scsi_tgt_if_exit(void); +extern int scsi_tgt_if_init(void); + +extern int scsi_tgt_uspace_send_cmd(struct scsi_cmnd *cmd, struct scsi_lun *lun, + u64 tag); +extern int scsi_tgt_uspace_send_status(struct scsi_cmnd *cmd, u64 tag); +extern int scsi_tgt_kspace_exec(int host_no, u64 tag, int result, u32 len, + unsigned long uaddr, u8 rw); +extern int scsi_tgt_uspace_send_tsk_mgmt(int host_no, int function, u64 tag, + struct scsi_lun *scsilun, void *data); +extern int scsi_tgt_kspace_tsk_mgmt(int host_no, u64 mid, int result); diff --git a/include/scsi/scsi_tgt.h b/include/scsi/scsi_tgt.h new file mode 100644 index 000000000000..4f4427937af2 --- /dev/null +++ b/include/scsi/scsi_tgt.h @@ -0,0 +1,19 @@ +/* + * SCSI target definitions + */ + +#include + +struct Scsi_Host; +struct scsi_cmnd; +struct scsi_lun; + +extern struct Scsi_Host *scsi_tgt_cmd_to_host(struct scsi_cmnd *); +extern int scsi_tgt_alloc_queue(struct Scsi_Host *); +extern void scsi_tgt_free_queue(struct Scsi_Host *); +extern int scsi_tgt_queue_command(struct scsi_cmnd *, struct scsi_lun *, u64); +extern int scsi_tgt_tsk_mgmt_request(struct Scsi_Host *, int, u64, struct scsi_lun *, + void *); +extern struct scsi_cmnd *scsi_host_get_command(struct Scsi_Host *, + enum dma_data_direction, gfp_t); +extern void scsi_host_put_command(struct Scsi_Host *, struct scsi_cmnd *); -- cgit v1.2.3-58-ga151 From 97f78759ea1cb7d27b61095582de78b8e7486e85 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Thu, 16 Nov 2006 19:24:16 +0900 Subject: [SCSI] scsi tgt: scsi target user and kernel communication interface The user-space daemon and tgt kernel module need bi-directional kernel/user high-performance interface, however, mainline provides no standard interface like that. This patch adds shared memory interface between kernel and user spaces like some other drivers do by using own character device. The user-space daemon and tgt kernel module creates shared memory via mmap and use it like ring buffer. poll (kernel to user) and write (user to kernel) system calls are used for notification. Signed-off-by: FUJITA Tomonori Signed-off-by: Mike Christie Signed-off-by: James Bottomley --- drivers/scsi/scsi_tgt_if.c | 350 +++++++++++++++++++++++++++++++++++++++++++++ include/scsi/scsi_tgt_if.h | 90 ++++++++++++ 2 files changed, 440 insertions(+) create mode 100644 drivers/scsi/scsi_tgt_if.c create mode 100644 include/scsi/scsi_tgt_if.h (limited to 'include') diff --git a/drivers/scsi/scsi_tgt_if.c b/drivers/scsi/scsi_tgt_if.c new file mode 100644 index 000000000000..28fd2223765b --- /dev/null +++ b/drivers/scsi/scsi_tgt_if.c @@ -0,0 +1,350 @@ +/* + * SCSI target kernel/user interface functions + * + * Copyright (C) 2005 FUJITA Tomonori + * Copyright (C) 2005 Mike Christie + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scsi_tgt_priv.h" + +struct tgt_ring { + u32 tr_idx; + unsigned long tr_pages[TGT_RING_PAGES]; + spinlock_t tr_lock; +}; + +/* tx_ring : kernel->user, rx_ring : user->kernel */ +static struct tgt_ring tx_ring, rx_ring; +static DECLARE_WAIT_QUEUE_HEAD(tgt_poll_wait); + +static inline void tgt_ring_idx_inc(struct tgt_ring *ring) +{ + if (ring->tr_idx == TGT_MAX_EVENTS - 1) + ring->tr_idx = 0; + else + ring->tr_idx++; +} + +static struct tgt_event *tgt_head_event(struct tgt_ring *ring, u32 idx) +{ + u32 pidx, off; + + pidx = idx / TGT_EVENT_PER_PAGE; + off = idx % TGT_EVENT_PER_PAGE; + + return (struct tgt_event *) + (ring->tr_pages[pidx] + sizeof(struct tgt_event) * off); +} + +static int tgt_uspace_send_event(u32 type, struct tgt_event *p) +{ + struct tgt_event *ev; + struct tgt_ring *ring = &tx_ring; + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&ring->tr_lock, flags); + + ev = tgt_head_event(ring, ring->tr_idx); + if (!ev->hdr.status) + tgt_ring_idx_inc(ring); + else + err = -BUSY; + + spin_unlock_irqrestore(&ring->tr_lock, flags); + + if (err) + return err; + + memcpy(ev, p, sizeof(*ev)); + ev->hdr.type = type; + mb(); + ev->hdr.status = 1; + + flush_dcache_page(virt_to_page(ev)); + + wake_up_interruptible(&tgt_poll_wait); + + return 0; +} + +int scsi_tgt_uspace_send_cmd(struct scsi_cmnd *cmd, struct scsi_lun *lun, u64 tag) +{ + struct Scsi_Host *shost = scsi_tgt_cmd_to_host(cmd); + struct tgt_event ev; + int err; + + memset(&ev, 0, sizeof(ev)); + ev.p.cmd_req.host_no = shost->host_no; + ev.p.cmd_req.data_len = cmd->request_bufflen; + memcpy(ev.p.cmd_req.scb, cmd->cmnd, sizeof(ev.p.cmd_req.scb)); + memcpy(ev.p.cmd_req.lun, lun, sizeof(ev.p.cmd_req.lun)); + ev.p.cmd_req.attribute = cmd->tag; + ev.p.cmd_req.tag = tag; + + dprintk("%p %d %u %x %llx\n", cmd, shost->host_no, + ev.p.cmd_req.data_len, cmd->tag, + (unsigned long long) ev.p.cmd_req.tag); + + err = tgt_uspace_send_event(TGT_KEVENT_CMD_REQ, &ev); + if (err) + eprintk("tx buf is full, could not send\n"); + + return err; +} + +int scsi_tgt_uspace_send_status(struct scsi_cmnd *cmd, u64 tag) +{ + struct Scsi_Host *shost = scsi_tgt_cmd_to_host(cmd); + struct tgt_event ev; + int err; + + memset(&ev, 0, sizeof(ev)); + ev.p.cmd_done.host_no = shost->host_no; + ev.p.cmd_done.tag = tag; + ev.p.cmd_done.result = cmd->result; + + dprintk("%p %d %llu %u %x\n", cmd, shost->host_no, + (unsigned long long) ev.p.cmd_req.tag, + ev.p.cmd_req.data_len, cmd->tag); + + err = tgt_uspace_send_event(TGT_KEVENT_CMD_DONE, &ev); + if (err) + eprintk("tx buf is full, could not send\n"); + + return err; +} + +int scsi_tgt_uspace_send_tsk_mgmt(int host_no, int function, u64 tag, + struct scsi_lun *scsilun, void *data) +{ + struct tgt_event ev; + int err; + + memset(&ev, 0, sizeof(ev)); + ev.p.tsk_mgmt_req.host_no = host_no; + ev.p.tsk_mgmt_req.function = function; + ev.p.tsk_mgmt_req.tag = tag; + memcpy(ev.p.tsk_mgmt_req.lun, scsilun, sizeof(ev.p.tsk_mgmt_req.lun)); + ev.p.tsk_mgmt_req.mid = (u64) (unsigned long) data; + + dprintk("%d %x %llx %llx\n", host_no, function, (unsigned long long) tag, + (unsigned long long) ev.p.tsk_mgmt_req.mid); + + err = tgt_uspace_send_event(TGT_KEVENT_TSK_MGMT_REQ, &ev); + if (err) + eprintk("tx buf is full, could not send\n"); + + return err; +} + +static int event_recv_msg(struct tgt_event *ev) +{ + int err = 0; + + switch (ev->hdr.type) { + case TGT_UEVENT_CMD_RSP: + err = scsi_tgt_kspace_exec(ev->p.cmd_rsp.host_no, + ev->p.cmd_rsp.tag, + ev->p.cmd_rsp.result, + ev->p.cmd_rsp.len, + ev->p.cmd_rsp.uaddr, + ev->p.cmd_rsp.rw); + break; + case TGT_UEVENT_TSK_MGMT_RSP: + err = scsi_tgt_kspace_tsk_mgmt(ev->p.tsk_mgmt_rsp.host_no, + ev->p.tsk_mgmt_rsp.mid, + ev->p.tsk_mgmt_rsp.result); + break; + default: + eprintk("unknown type %d\n", ev->hdr.type); + err = -EINVAL; + } + + return err; +} + +static ssize_t tgt_write(struct file *file, const char __user * buffer, + size_t count, loff_t * ppos) +{ + struct tgt_event *ev; + struct tgt_ring *ring = &rx_ring; + + while (1) { + ev = tgt_head_event(ring, ring->tr_idx); + /* do we need this? */ + flush_dcache_page(virt_to_page(ev)); + + if (!ev->hdr.status) + break; + + tgt_ring_idx_inc(ring); + event_recv_msg(ev); + ev->hdr.status = 0; + }; + + return count; +} + +static unsigned int tgt_poll(struct file * file, struct poll_table_struct *wait) +{ + struct tgt_event *ev; + struct tgt_ring *ring = &tx_ring; + unsigned long flags; + unsigned int mask = 0; + u32 idx; + + poll_wait(file, &tgt_poll_wait, wait); + + spin_lock_irqsave(&ring->tr_lock, flags); + + idx = ring->tr_idx ? ring->tr_idx - 1 : TGT_MAX_EVENTS - 1; + ev = tgt_head_event(ring, idx); + if (ev->hdr.status) + mask |= POLLIN | POLLRDNORM; + + spin_unlock_irqrestore(&ring->tr_lock, flags); + + return mask; +} + +static int uspace_ring_map(struct vm_area_struct *vma, unsigned long addr, + struct tgt_ring *ring) +{ + int i, err; + + for (i = 0; i < TGT_RING_PAGES; i++) { + struct page *page = virt_to_page(ring->tr_pages[i]); + err = vm_insert_page(vma, addr, page); + if (err) + return err; + addr += PAGE_SIZE; + } + + return 0; +} + +static int tgt_mmap(struct file *filp, struct vm_area_struct *vma) +{ + unsigned long addr; + int err; + + if (vma->vm_pgoff) + return -EINVAL; + + if (vma->vm_end - vma->vm_start != TGT_RING_SIZE * 2) { + eprintk("mmap size must be %lu, not %lu \n", + TGT_RING_SIZE * 2, vma->vm_end - vma->vm_start); + return -EINVAL; + } + + addr = vma->vm_start; + err = uspace_ring_map(vma, addr, &tx_ring); + if (err) + return err; + err = uspace_ring_map(vma, addr + TGT_RING_SIZE, &rx_ring); + + return err; +} + +static int tgt_open(struct inode *inode, struct file *file) +{ + tx_ring.tr_idx = rx_ring.tr_idx = 0; + + return 0; +} + +static struct file_operations tgt_fops = { + .owner = THIS_MODULE, + .open = tgt_open, + .poll = tgt_poll, + .write = tgt_write, + .mmap = tgt_mmap, +}; + +static struct miscdevice tgt_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "tgt", + .fops = &tgt_fops, +}; + +static void tgt_ring_exit(struct tgt_ring *ring) +{ + int i; + + for (i = 0; i < TGT_RING_PAGES; i++) + free_page(ring->tr_pages[i]); +} + +static int tgt_ring_init(struct tgt_ring *ring) +{ + int i; + + spin_lock_init(&ring->tr_lock); + + for (i = 0; i < TGT_RING_PAGES; i++) { + ring->tr_pages[i] = get_zeroed_page(GFP_KERNEL); + if (!ring->tr_pages[i]) { + eprintk("out of memory\n"); + return -ENOMEM; + } + } + + return 0; +} + +void scsi_tgt_if_exit(void) +{ + tgt_ring_exit(&tx_ring); + tgt_ring_exit(&rx_ring); + misc_deregister(&tgt_miscdev); +} + +int scsi_tgt_if_init(void) +{ + int err; + + err = tgt_ring_init(&tx_ring); + if (err) + return err; + + err = tgt_ring_init(&rx_ring); + if (err) + goto free_tx_ring; + + err = misc_register(&tgt_miscdev); + if (err) + goto free_rx_ring; + + return 0; +free_rx_ring: + tgt_ring_exit(&rx_ring); +free_tx_ring: + tgt_ring_exit(&tx_ring); + + return err; +} diff --git a/include/scsi/scsi_tgt_if.h b/include/scsi/scsi_tgt_if.h new file mode 100644 index 000000000000..46d5e70d7215 --- /dev/null +++ b/include/scsi/scsi_tgt_if.h @@ -0,0 +1,90 @@ +/* + * SCSI target kernel/user interface + * + * Copyright (C) 2005 FUJITA Tomonori + * Copyright (C) 2005 Mike Christie + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef __SCSI_TARGET_IF_H +#define __SCSI_TARGET_IF_H + +/* user -> kernel */ +#define TGT_UEVENT_CMD_RSP 0x0001 +#define TGT_UEVENT_TSK_MGMT_RSP 0x0002 + +/* kernel -> user */ +#define TGT_KEVENT_CMD_REQ 0x1001 +#define TGT_KEVENT_CMD_DONE 0x1002 +#define TGT_KEVENT_TSK_MGMT_REQ 0x1003 + +struct tgt_event_hdr { + uint16_t version; + uint16_t status; + uint16_t type; + uint16_t len; +} __attribute__ ((aligned (sizeof(uint64_t)))); + +struct tgt_event { + struct tgt_event_hdr hdr; + + union { + /* user-> kernel */ + struct { + int host_no; + uint32_t len; + int result; + aligned_u64 uaddr; + uint8_t rw; + aligned_u64 tag; + } cmd_rsp; + struct { + int host_no; + aligned_u64 mid; + int result; + } tsk_mgmt_rsp; + + + /* kernel -> user */ + struct { + int host_no; + uint32_t data_len; + uint8_t scb[16]; + uint8_t lun[8]; + int attribute; + aligned_u64 tag; + } cmd_req; + struct { + int host_no; + aligned_u64 tag; + int result; + } cmd_done; + struct { + int host_no; + int function; + aligned_u64 tag; + uint8_t lun[8]; + aligned_u64 mid; + } tsk_mgmt_req; + } p; +} __attribute__ ((aligned (sizeof(uint64_t)))); + +#define TGT_RING_SIZE (1UL << 16) +#define TGT_RING_PAGES (TGT_RING_SIZE >> PAGE_SHIFT) +#define TGT_EVENT_PER_PAGE (PAGE_SIZE / sizeof(struct tgt_event)) +#define TGT_MAX_EVENTS (TGT_EVENT_PER_PAGE * TGT_RING_PAGES) + +#endif -- cgit v1.2.3-58-ga151 From 26b14823441382264e6f3dfd01b8687dc18196fe Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 1 Dec 2006 03:00:50 +0900 Subject: [SCSI] scsi tgt: SCSI RDMA Protocol library functions libsrp provides helper functions for SRP target drivers. Some SRP target drivers would be out of drivers/scsi/ so we added an entry for libsrp in drivers/scsi/Kconfig. Signed-off-by: FUJITA Tomonori Signed-off-by: Mike Christie Signed-off-by: Santiago Leon Signed-off-by: James Bottomley --- drivers/scsi/Kconfig | 10 ++ drivers/scsi/Makefile | 1 + drivers/scsi/libsrp.c | 441 ++++++++++++++++++++++++++++++++++++++++++++++++++ include/scsi/libsrp.h | 77 +++++++++ 4 files changed, 529 insertions(+) create mode 100644 drivers/scsi/libsrp.c create mode 100644 include/scsi/libsrp.h (limited to 'include') diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index 1301d5222c07..5570cdbf8f11 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -1760,6 +1760,16 @@ config ZFCP called zfcp. If you want to compile it as a module, say M here and read . +config SCSI_SRP + tristate "SCSI RDMA Protocol helper library" + depends on SCSI && PCI + select SCSI_TGT + help + If you wish to use SRP target drivers, say Y. + + To compile this driver as a module, choose M here: the + module will be called libsrp. + endmenu source "drivers/scsi/pcmcia/Kconfig" diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 8f86c1b96ff7..00691c4c3a6f 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -126,6 +126,7 @@ obj-$(CONFIG_SCSI_FCAL) += fcal.o obj-$(CONFIG_SCSI_LASI700) += 53c700.o lasi700.o obj-$(CONFIG_SCSI_NSP32) += nsp32.o obj-$(CONFIG_SCSI_IPR) += ipr.o +obj-$(CONFIG_SCSI_SRP) += libsrp.o obj-$(CONFIG_SCSI_IBMVSCSI) += ibmvscsi/ obj-$(CONFIG_SCSI_HPTIOP) += hptiop.o obj-$(CONFIG_SCSI_STEX) += stex.o diff --git a/drivers/scsi/libsrp.c b/drivers/scsi/libsrp.c new file mode 100644 index 000000000000..89403b00e042 --- /dev/null +++ b/drivers/scsi/libsrp.c @@ -0,0 +1,441 @@ +/* + * SCSI RDAM Protocol lib functions + * + * Copyright (C) 2006 FUJITA Tomonori + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum srp_task_attributes { + SRP_SIMPLE_TASK = 0, + SRP_HEAD_TASK = 1, + SRP_ORDERED_TASK = 2, + SRP_ACA_TASK = 4 +}; + +/* tmp - will replace with SCSI logging stuff */ +#define eprintk(fmt, args...) \ +do { \ + printk("%s(%d) " fmt, __FUNCTION__, __LINE__, ##args); \ +} while (0) +/* #define dprintk eprintk */ +#define dprintk(fmt, args...) + +static int srp_iu_pool_alloc(struct srp_queue *q, size_t max, + struct srp_buf **ring) +{ + int i; + struct iu_entry *iue; + + q->pool = kcalloc(max, sizeof(struct iu_entry *), GFP_KERNEL); + if (!q->pool) + return -ENOMEM; + q->items = kcalloc(max, sizeof(struct iu_entry), GFP_KERNEL); + if (!q->items) + goto free_pool; + + spin_lock_init(&q->lock); + q->queue = kfifo_init((void *) q->pool, max * sizeof(void *), + GFP_KERNEL, &q->lock); + if (IS_ERR(q->queue)) + goto free_item; + + for (i = 0, iue = q->items; i < max; i++) { + __kfifo_put(q->queue, (void *) &iue, sizeof(void *)); + iue->sbuf = ring[i]; + iue++; + } + return 0; + +free_item: + kfree(q->items); +free_pool: + kfree(q->pool); + return -ENOMEM; +} + +static void srp_iu_pool_free(struct srp_queue *q) +{ + kfree(q->items); + kfree(q->pool); +} + +static struct srp_buf **srp_ring_alloc(struct device *dev, + size_t max, size_t size) +{ + int i; + struct srp_buf **ring; + + ring = kcalloc(max, sizeof(struct srp_buf *), GFP_KERNEL); + if (!ring) + return NULL; + + for (i = 0; i < max; i++) { + ring[i] = kzalloc(sizeof(struct srp_buf), GFP_KERNEL); + if (!ring[i]) + goto out; + ring[i]->buf = dma_alloc_coherent(dev, size, &ring[i]->dma, + GFP_KERNEL); + if (!ring[i]->buf) + goto out; + } + return ring; + +out: + for (i = 0; i < max && ring[i]; i++) { + if (ring[i]->buf) + dma_free_coherent(dev, size, ring[i]->buf, ring[i]->dma); + kfree(ring[i]); + } + kfree(ring); + + return NULL; +} + +static void srp_ring_free(struct device *dev, struct srp_buf **ring, size_t max, + size_t size) +{ + int i; + + for (i = 0; i < max; i++) { + dma_free_coherent(dev, size, ring[i]->buf, ring[i]->dma); + kfree(ring[i]); + } +} + +int srp_target_alloc(struct srp_target *target, struct device *dev, + size_t nr, size_t iu_size) +{ + int err; + + spin_lock_init(&target->lock); + INIT_LIST_HEAD(&target->cmd_queue); + + target->dev = dev; + target->dev->driver_data = target; + + target->srp_iu_size = iu_size; + target->rx_ring_size = nr; + target->rx_ring = srp_ring_alloc(target->dev, nr, iu_size); + if (!target->rx_ring) + return -ENOMEM; + err = srp_iu_pool_alloc(&target->iu_queue, nr, target->rx_ring); + if (err) + goto free_ring; + + return 0; + +free_ring: + srp_ring_free(target->dev, target->rx_ring, nr, iu_size); + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(srp_target_alloc); + +void srp_target_free(struct srp_target *target) +{ + srp_ring_free(target->dev, target->rx_ring, target->rx_ring_size, + target->srp_iu_size); + srp_iu_pool_free(&target->iu_queue); +} +EXPORT_SYMBOL_GPL(srp_target_free); + +struct iu_entry *srp_iu_get(struct srp_target *target) +{ + struct iu_entry *iue = NULL; + + kfifo_get(target->iu_queue.queue, (void *) &iue, sizeof(void *)); + if (!iue) + return iue; + iue->target = target; + INIT_LIST_HEAD(&iue->ilist); + iue->flags = 0; + return iue; +} +EXPORT_SYMBOL_GPL(srp_iu_get); + +void srp_iu_put(struct iu_entry *iue) +{ + kfifo_put(iue->target->iu_queue.queue, (void *) &iue, sizeof(void *)); +} +EXPORT_SYMBOL_GPL(srp_iu_put); + +static int srp_direct_data(struct scsi_cmnd *sc, struct srp_direct_buf *md, + enum dma_data_direction dir, srp_rdma_t rdma_io, + int dma_map, int ext_desc) +{ + struct iu_entry *iue = NULL; + struct scatterlist *sg = NULL; + int err, nsg = 0, len; + + if (dma_map) { + iue = (struct iu_entry *) sc->SCp.ptr; + sg = sc->request_buffer; + + dprintk("%p %u %u %d\n", iue, sc->request_bufflen, + md->len, sc->use_sg); + + nsg = dma_map_sg(iue->target->dev, sg, sc->use_sg, + DMA_BIDIRECTIONAL); + if (!nsg) { + printk("fail to map %p %d\n", iue, sc->use_sg); + return 0; + } + len = min(sc->request_bufflen, md->len); + } else + len = md->len; + + err = rdma_io(sc, sg, nsg, md, 1, dir, len); + + if (dma_map) + dma_unmap_sg(iue->target->dev, sg, nsg, DMA_BIDIRECTIONAL); + + return err; +} + +static int srp_indirect_data(struct scsi_cmnd *sc, struct srp_cmd *cmd, + struct srp_indirect_buf *id, + enum dma_data_direction dir, srp_rdma_t rdma_io, + int dma_map, int ext_desc) +{ + struct iu_entry *iue = NULL; + struct srp_direct_buf *md = NULL; + struct scatterlist dummy, *sg = NULL; + dma_addr_t token = 0; + long err; + unsigned int done = 0; + int nmd, nsg = 0, len; + + if (dma_map || ext_desc) { + iue = (struct iu_entry *) sc->SCp.ptr; + sg = sc->request_buffer; + + dprintk("%p %u %u %d %d\n", + iue, sc->request_bufflen, id->len, + cmd->data_in_desc_cnt, cmd->data_out_desc_cnt); + } + + nmd = id->table_desc.len / sizeof(struct srp_direct_buf); + + if ((dir == DMA_FROM_DEVICE && nmd == cmd->data_in_desc_cnt) || + (dir == DMA_TO_DEVICE && nmd == cmd->data_out_desc_cnt)) { + md = &id->desc_list[0]; + goto rdma; + } + + if (ext_desc && dma_map) { + md = dma_alloc_coherent(iue->target->dev, id->table_desc.len, + &token, GFP_KERNEL); + if (!md) { + eprintk("Can't get dma memory %u\n", id->table_desc.len); + return -ENOMEM; + } + + sg_init_one(&dummy, md, id->table_desc.len); + sg_dma_address(&dummy) = token; + err = rdma_io(sc, &dummy, 1, &id->table_desc, 1, DMA_TO_DEVICE, + id->table_desc.len); + if (err < 0) { + eprintk("Error copying indirect table %ld\n", err); + goto free_mem; + } + } else { + eprintk("This command uses external indirect buffer\n"); + return -EINVAL; + } + +rdma: + if (dma_map) { + nsg = dma_map_sg(iue->target->dev, sg, sc->use_sg, DMA_BIDIRECTIONAL); + if (!nsg) { + eprintk("fail to map %p %d\n", iue, sc->use_sg); + goto free_mem; + } + len = min(sc->request_bufflen, id->len); + } else + len = id->len; + + err = rdma_io(sc, sg, nsg, md, nmd, dir, len); + + if (dma_map) + dma_unmap_sg(iue->target->dev, sg, nsg, DMA_BIDIRECTIONAL); + +free_mem: + if (token && dma_map) + dma_free_coherent(iue->target->dev, id->table_desc.len, md, token); + + return done; +} + +static int data_out_desc_size(struct srp_cmd *cmd) +{ + int size = 0; + u8 fmt = cmd->buf_fmt >> 4; + + switch (fmt) { + case SRP_NO_DATA_DESC: + break; + case SRP_DATA_DESC_DIRECT: + size = sizeof(struct srp_direct_buf); + break; + case SRP_DATA_DESC_INDIRECT: + size = sizeof(struct srp_indirect_buf) + + sizeof(struct srp_direct_buf) * cmd->data_out_desc_cnt; + break; + default: + eprintk("client error. Invalid data_out_format %x\n", fmt); + break; + } + return size; +} + +/* + * TODO: this can be called multiple times for a single command if it + * has very long data. + */ +int srp_transfer_data(struct scsi_cmnd *sc, struct srp_cmd *cmd, + srp_rdma_t rdma_io, int dma_map, int ext_desc) +{ + struct srp_direct_buf *md; + struct srp_indirect_buf *id; + enum dma_data_direction dir; + int offset, err = 0; + u8 format; + + offset = cmd->add_cdb_len * 4; + + dir = srp_cmd_direction(cmd); + if (dir == DMA_FROM_DEVICE) + offset += data_out_desc_size(cmd); + + if (dir == DMA_TO_DEVICE) + format = cmd->buf_fmt >> 4; + else + format = cmd->buf_fmt & ((1U << 4) - 1); + + switch (format) { + case SRP_NO_DATA_DESC: + break; + case SRP_DATA_DESC_DIRECT: + md = (struct srp_direct_buf *) + (cmd->add_data + offset); + err = srp_direct_data(sc, md, dir, rdma_io, dma_map, ext_desc); + break; + case SRP_DATA_DESC_INDIRECT: + id = (struct srp_indirect_buf *) + (cmd->add_data + offset); + err = srp_indirect_data(sc, cmd, id, dir, rdma_io, dma_map, + ext_desc); + break; + default: + eprintk("Unknown format %d %x\n", dir, format); + break; + } + + return err; +} +EXPORT_SYMBOL_GPL(srp_transfer_data); + +static int vscsis_data_length(struct srp_cmd *cmd, enum dma_data_direction dir) +{ + struct srp_direct_buf *md; + struct srp_indirect_buf *id; + int len = 0, offset = cmd->add_cdb_len * 4; + u8 fmt; + + if (dir == DMA_TO_DEVICE) + fmt = cmd->buf_fmt >> 4; + else { + fmt = cmd->buf_fmt & ((1U << 4) - 1); + offset += data_out_desc_size(cmd); + } + + switch (fmt) { + case SRP_NO_DATA_DESC: + break; + case SRP_DATA_DESC_DIRECT: + md = (struct srp_direct_buf *) (cmd->add_data + offset); + len = md->len; + break; + case SRP_DATA_DESC_INDIRECT: + id = (struct srp_indirect_buf *) (cmd->add_data + offset); + len = id->len; + break; + default: + eprintk("invalid data format %x\n", fmt); + break; + } + return len; +} + +int srp_cmd_queue(struct Scsi_Host *shost, struct srp_cmd *cmd, void *info, + u64 addr) +{ + enum dma_data_direction dir; + struct scsi_cmnd *sc; + int tag, len, err; + + switch (cmd->task_attr) { + case SRP_SIMPLE_TASK: + tag = MSG_SIMPLE_TAG; + break; + case SRP_ORDERED_TASK: + tag = MSG_ORDERED_TAG; + break; + case SRP_HEAD_TASK: + tag = MSG_HEAD_TAG; + break; + default: + eprintk("Task attribute %d not supported\n", cmd->task_attr); + tag = MSG_ORDERED_TAG; + } + + dir = srp_cmd_direction(cmd); + len = vscsis_data_length(cmd, dir); + + dprintk("%p %x %lx %d %d %d %llx\n", info, cmd->cdb[0], + cmd->lun, dir, len, tag, (unsigned long long) cmd->tag); + + sc = scsi_host_get_command(shost, dir, GFP_KERNEL); + if (!sc) + return -ENOMEM; + + sc->SCp.ptr = info; + memcpy(sc->cmnd, cmd->cdb, MAX_COMMAND_SIZE); + sc->request_bufflen = len; + sc->request_buffer = (void *) (unsigned long) addr; + sc->tag = tag; + err = scsi_tgt_queue_command(sc, (struct scsi_lun *) &cmd->lun, cmd->tag); + if (err) + scsi_host_put_command(shost, sc); + + return err; +} +EXPORT_SYMBOL_GPL(srp_cmd_queue); + +MODULE_DESCRIPTION("SCSI RDAM Protocol lib functions"); +MODULE_AUTHOR("FUJITA Tomonori"); +MODULE_LICENSE("GPL"); diff --git a/include/scsi/libsrp.h b/include/scsi/libsrp.h new file mode 100644 index 000000000000..d143171896ae --- /dev/null +++ b/include/scsi/libsrp.h @@ -0,0 +1,77 @@ +#ifndef __LIBSRP_H__ +#define __LIBSRP_H__ + +#include +#include +#include +#include + +enum iue_flags { + V_DIOVER, + V_WRITE, + V_LINKED, + V_FLYING, +}; + +struct srp_buf { + dma_addr_t dma; + void *buf; +}; + +struct srp_queue { + void *pool; + void *items; + struct kfifo *queue; + spinlock_t lock; +}; + +struct srp_target { + struct Scsi_Host *shost; + struct device *dev; + + spinlock_t lock; + struct list_head cmd_queue; + + size_t srp_iu_size; + struct srp_queue iu_queue; + size_t rx_ring_size; + struct srp_buf **rx_ring; + + void *ldata; +}; + +struct iu_entry { + struct srp_target *target; + + struct list_head ilist; + dma_addr_t remote_token; + unsigned long flags; + + struct srp_buf *sbuf; +}; + +typedef int (srp_rdma_t)(struct scsi_cmnd *, struct scatterlist *, int, + struct srp_direct_buf *, int, + enum dma_data_direction, unsigned int); +extern int srp_target_alloc(struct srp_target *, struct device *, size_t, size_t); +extern void srp_target_free(struct srp_target *); + +extern struct iu_entry *srp_iu_get(struct srp_target *); +extern void srp_iu_put(struct iu_entry *); + +extern int srp_cmd_queue(struct Scsi_Host *, struct srp_cmd *, void *, u64); +extern int srp_transfer_data(struct scsi_cmnd *, struct srp_cmd *, + srp_rdma_t, int, int); + + +static inline struct srp_target *host_to_srp_target(struct Scsi_Host *host) +{ + return (struct srp_target *) host->hostdata; +} + +static inline int srp_cmd_direction(struct srp_cmd *cmd) +{ + return (cmd->buf_fmt >> 4) ? DMA_TO_DEVICE : DMA_FROM_DEVICE; +} + +#endif -- cgit v1.2.3-58-ga151 From 1d2c90425d5b0dcbf4a0fab2053d5087758b76a0 Mon Sep 17 00:00:00 2001 From: Dominik Brodowski Date: Mon, 6 Nov 2006 21:52:16 -0500 Subject: [PATCH] pcmcia: multifunction card handling fixes s->functions needs to be initialized earlier, for the "let's see how high it increases" approach means that pcmcia_request_irq() (which makes use of this value) is confused, and might request an exclusive IRQ first even though it is not supposed to. Also, a CIS override autoloaded using the firmware loader may allow for the use of more or less functions in a multifunction card. Therefore, we may need to schedule a call to add this second function later on, or simply remove the other function (it's always the first -valid- function which reaches this codepath). Many thanks to Fabrice Bellet for debugging and testing patches. Signed-off-by: Dominik Brodowski --- drivers/pcmcia/ds.c | 153 +++++++++++++++++++++++++++++----------------------- include/pcmcia/ss.h | 5 +- 2 files changed, 90 insertions(+), 68 deletions(-) (limited to 'include') diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c index 7f6e94cd067a..da7ceb523b48 100644 --- a/drivers/pcmcia/ds.c +++ b/drivers/pcmcia/ds.c @@ -231,65 +231,6 @@ static void pcmcia_check_driver(struct pcmcia_driver *p_drv) } -#ifdef CONFIG_PCMCIA_LOAD_CIS - -/** - * pcmcia_load_firmware - load CIS from userspace if device-provided is broken - * @dev - the pcmcia device which needs a CIS override - * @filename - requested filename in /lib/firmware/ - * - * This uses the in-kernel firmware loading mechanism to use a "fake CIS" if - * the one provided by the card is broken. The firmware files reside in - * /lib/firmware/ in userspace. - */ -static int pcmcia_load_firmware(struct pcmcia_device *dev, char * filename) -{ - struct pcmcia_socket *s = dev->socket; - const struct firmware *fw; - char path[20]; - int ret=-ENOMEM; - cisdump_t *cis; - - if (!filename) - return -EINVAL; - - ds_dbg(1, "trying to load firmware %s\n", filename); - - if (strlen(filename) > 14) - return -EINVAL; - - snprintf(path, 20, "%s", filename); - - if (request_firmware(&fw, path, &dev->dev) == 0) { - if (fw->size >= CISTPL_MAX_CIS_SIZE) - goto release; - - cis = kzalloc(sizeof(cisdump_t), GFP_KERNEL); - if (!cis) - goto release; - - cis->Length = fw->size + 1; - memcpy(cis->Data, fw->data, fw->size); - - if (!pcmcia_replace_cis(s, cis)) - ret = 0; - } - release: - release_firmware(fw); - - return (ret); -} - -#else /* !CONFIG_PCMCIA_LOAD_CIS */ - -static inline int pcmcia_load_firmware(struct pcmcia_device *dev, char * filename) -{ - return -ENODEV; -} - -#endif - - /*======================================================================*/ @@ -356,10 +297,11 @@ static void pcmcia_release_dev(struct device *dev) kfree(p_dev); } -static void pcmcia_add_pseudo_device(struct pcmcia_socket *s) +static void pcmcia_add_device_later(struct pcmcia_socket *s, int mfc) { if (!s->pcmcia_state.device_add_pending) { s->pcmcia_state.device_add_pending = 1; + s->pcmcia_state.mfc_pfc = mfc; schedule_work(&s->device_add); } return; @@ -400,7 +342,7 @@ static int pcmcia_device_probe(struct device * dev) did = p_dev->dev.driver_data; if (did && (did->match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO) && (p_dev->socket->device_count == 1) && (p_dev->device_no == 0)) - pcmcia_add_pseudo_device(p_dev->socket); + pcmcia_add_device_later(p_dev->socket, 0); put_module: if (ret) @@ -598,8 +540,6 @@ struct pcmcia_device * pcmcia_device_add(struct pcmcia_socket *s, unsigned int f p_dev->socket = s; p_dev->device_no = (s->device_count++); p_dev->func = function; - if (s->functions <= function) - s->functions = function + 1; p_dev->dev.bus = &pcmcia_bus_type; p_dev->dev.parent = s->dev.dev; @@ -690,6 +630,7 @@ static int pcmcia_card_add(struct pcmcia_socket *s) no_funcs = mfc.nfn; else no_funcs = 1; + s->functions = no_funcs; for (i=0; i < no_funcs; i++) pcmcia_device_add(s, i); @@ -698,11 +639,12 @@ static int pcmcia_card_add(struct pcmcia_socket *s) } -static void pcmcia_delayed_add_pseudo_device(void *data) +static void pcmcia_delayed_add_device(void *data) { struct pcmcia_socket *s = data; - pcmcia_device_add(s, 0); + pcmcia_device_add(s, s->pcmcia_state.mfc_pfc); s->pcmcia_state.device_add_pending = 0; + s->pcmcia_state.mfc_pfc = 0; } static int pcmcia_requery(struct device *dev, void * _data) @@ -751,6 +693,85 @@ static void pcmcia_bus_rescan(struct pcmcia_socket *skt, int new_cis) printk(KERN_INFO "pcmcia: bus_rescan_devices failed\n"); } +#ifdef CONFIG_PCMCIA_LOAD_CIS + +/** + * pcmcia_load_firmware - load CIS from userspace if device-provided is broken + * @dev - the pcmcia device which needs a CIS override + * @filename - requested filename in /lib/firmware/ + * + * This uses the in-kernel firmware loading mechanism to use a "fake CIS" if + * the one provided by the card is broken. The firmware files reside in + * /lib/firmware/ in userspace. + */ +static int pcmcia_load_firmware(struct pcmcia_device *dev, char * filename) +{ + struct pcmcia_socket *s = dev->socket; + const struct firmware *fw; + char path[20]; + int ret = -ENOMEM; + int no_funcs; + int old_funcs; + cisdump_t *cis; + cistpl_longlink_mfc_t mfc; + + if (!filename) + return -EINVAL; + + ds_dbg(1, "trying to load firmware %s\n", filename); + + if (strlen(filename) > 14) + return -EINVAL; + + snprintf(path, 20, "%s", filename); + + if (request_firmware(&fw, path, &dev->dev) == 0) { + if (fw->size >= CISTPL_MAX_CIS_SIZE) + goto release; + + cis = kzalloc(sizeof(cisdump_t), GFP_KERNEL); + if (!cis) + goto release; + + cis->Length = fw->size + 1; + memcpy(cis->Data, fw->data, fw->size); + + if (!pcmcia_replace_cis(s, cis)) + ret = 0; + + /* update information */ + pcmcia_device_query(dev); + + /* does this cis override add or remove functions? */ + old_funcs = s->functions; + + if (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_LONGLINK_MFC, &mfc)) + no_funcs = mfc.nfn; + else + no_funcs = 1; + s->functions = no_funcs; + + if (old_funcs > no_funcs) + pcmcia_card_remove(s, dev); + else if (no_funcs > old_funcs) + pcmcia_add_device_later(s, 1); + } + release: + release_firmware(fw); + + return (ret); +} + +#else /* !CONFIG_PCMCIA_LOAD_CIS */ + +static inline int pcmcia_load_firmware(struct pcmcia_device *dev, char * filename) +{ + return -ENODEV; +} + +#endif + + static inline int pcmcia_devmatch(struct pcmcia_device *dev, struct pcmcia_device_id *did) { @@ -1250,7 +1271,7 @@ static int __devinit pcmcia_bus_add_socket(struct class_device *class_dev, init_waitqueue_head(&socket->queue); #endif INIT_LIST_HEAD(&socket->devices_list); - INIT_WORK(&socket->device_add, pcmcia_delayed_add_pseudo_device, socket); + INIT_WORK(&socket->device_add, pcmcia_delayed_add_device, socket); memset(&socket->pcmcia_state, 0, sizeof(u8)); socket->device_count = 0; diff --git a/include/pcmcia/ss.h b/include/pcmcia/ss.h index ede639812f8a..623a0fc0dae1 100644 --- a/include/pcmcia/ss.h +++ b/include/pcmcia/ss.h @@ -262,9 +262,10 @@ struct pcmcia_socket { u8 present:1, /* PCMCIA card is present in socket */ busy:1, /* "master" ioctl is used */ dead:1, /* pcmcia module is being unloaded */ - device_add_pending:1, /* a pseudo-multifunction-device + device_add_pending:1, /* a multifunction-device * add event is pending */ - reserved:4; + mfc_pfc:1, /* the pending event adds a mfc (1) or pfc (0) */ + reserved:3; } pcmcia_state; struct work_struct device_add; /* for adding further pseudo-multifunction -- cgit v1.2.3-58-ga151