From: Nicholas Bellinger <nab@xxxxxxxxxxxxxxx> This patch adds support for invoking TFO completion callbacks directly from IRQ context in target_complete_cmd(). Some fabric drivers like loopback and vhost can invoke their response callbacks directly from IRQ context, and this patch allows the extra queue_work() dispatch to a seperate work-queue process context to be avoided for fast-path operation. This includes the refactoring of target_complete_ok_work(), so that transport_complete_task_attr() and target_complete_irq() can be invoked directly from target_complete_cmd() context. Also, target_restart_delayed_cmds() has been converted to use llist, and will only be invoked from !in_interrupt() with a llist_del_all() compare and exchange when draining the list of ordered tags. Cc: Christoph Hellwig <hch@xxxxxx> Cc: Hannes Reinecke <hare@xxxxxxx> Cc: Sagi Grimberg <sagig@xxxxxxxxxxxx> Signed-off-by: Nicholas Bellinger <nab@xxxxxxxxxxxxxxx> --- drivers/target/target_core_device.c | 2 +- drivers/target/target_core_tpg.c | 1 + drivers/target/target_core_transport.c | 165 ++++++++++++++++++++++++--------- include/target/target_core_base.h | 5 +- include/target/target_core_fabric.h | 1 + 5 files changed, 126 insertions(+), 48 deletions(-) diff --git a/drivers/target/target_core_device.c b/drivers/target/target_core_device.c index 1d98033..70213fa 100644 --- a/drivers/target/target_core_device.c +++ b/drivers/target/target_core_device.c @@ -739,7 +739,7 @@ struct se_device *target_alloc_device(struct se_hba *hba, const char *name) INIT_LIST_HEAD(&dev->dev_list); INIT_LIST_HEAD(&dev->dev_sep_list); INIT_LIST_HEAD(&dev->dev_tmr_list); - INIT_LIST_HEAD(&dev->delayed_cmd_list); + init_llist_head(&dev->delayed_cmd_llist); INIT_LIST_HEAD(&dev->state_list); INIT_LIST_HEAD(&dev->qf_cmd_list); INIT_LIST_HEAD(&dev->g_dev_node); diff --git a/drivers/target/target_core_tpg.c b/drivers/target/target_core_tpg.c index 3fbb0d4..aa08d6b 100644 --- a/drivers/target/target_core_tpg.c +++ b/drivers/target/target_core_tpg.c @@ -37,6 +37,7 @@ #include <target/target_core_base.h> #include <target/target_core_backend.h> +#include <target/target_core_configfs.h> #include <target/target_core_fabric.h> #include "target_core_internal.h" diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index 2ccaeff..a98a23c 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -63,10 +63,11 @@ struct kmem_cache *t10_alua_tg_pt_gp_cache; struct kmem_cache *t10_alua_lba_map_cache; struct kmem_cache *t10_alua_lba_map_mem_cache; -static void transport_complete_task_attr(struct se_cmd *cmd); +static bool transport_complete_task_attr(struct se_cmd *cmd); static void transport_handle_queue_full(struct se_cmd *cmd, struct se_device *dev); static int transport_put_cmd(struct se_cmd *cmd); +static void target_complete_irq(struct se_cmd *cmd, bool); static void target_complete_ok_work(struct work_struct *work); int init_se_kmem_caches(void) @@ -711,16 +712,37 @@ void target_complete_cmd(struct se_cmd *cmd, u8 scsi_status) spin_unlock_irqrestore(&cmd->t_state_lock, flags); complete_all(&cmd->t_transport_stop_comp); return; - } else if (!success) { - INIT_WORK(&cmd->work, target_complete_failure_work); - } else { + } + if (success) { + /* + * Invoke TFO completion callback now if fabric driver can + * queue response in IRQ context, and special case descriptor + * handling requirements in process context for ORDERED task + * and friends do not exist. + */ + if (cmd->se_cmd_flags & SCF_COMPLETE_IRQ && + !(cmd->se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) && + !cmd->transport_complete_callback) { + + cmd->t_state = TRANSPORT_COMPLETE; + cmd->transport_state |= (CMD_T_COMPLETE | CMD_T_ACTIVE); + spin_unlock_irqrestore(&cmd->t_state_lock, flags); + + if (!transport_complete_task_attr(cmd)) + goto do_work; + + target_complete_irq(cmd, true); + return; + } INIT_WORK(&cmd->work, target_complete_ok_work); + } else { + INIT_WORK(&cmd->work, target_complete_failure_work); } cmd->t_state = TRANSPORT_COMPLETE; cmd->transport_state |= (CMD_T_COMPLETE | CMD_T_ACTIVE); spin_unlock_irqrestore(&cmd->t_state_lock, flags); - +do_work: queue_work(target_completion_wq, &cmd->work); } EXPORT_SYMBOL(target_complete_cmd); @@ -1145,7 +1167,6 @@ void transport_init_se_cmd( int task_attr, unsigned char *sense_buffer) { - INIT_LIST_HEAD(&cmd->se_delayed_node); INIT_LIST_HEAD(&cmd->se_qf_node); INIT_LIST_HEAD(&cmd->se_cmd_list); INIT_LIST_HEAD(&cmd->state_list); @@ -1155,6 +1176,8 @@ void transport_init_se_cmd( spin_lock_init(&cmd->t_state_lock); kref_init(&cmd->cmd_kref); cmd->transport_state = CMD_T_DEV_ACTIVE; + if (tfo->complete_irq) + cmd->se_cmd_flags |= SCF_COMPLETE_IRQ; cmd->se_tfo = tfo; cmd->se_sess = se_sess; @@ -1803,9 +1826,7 @@ static bool target_handle_task_attr(struct se_cmd *cmd) if (atomic_read(&dev->dev_ordered_sync) == 0) return false; - spin_lock(&dev->delayed_cmd_lock); - list_add_tail(&cmd->se_delayed_node, &dev->delayed_cmd_list); - spin_unlock(&dev->delayed_cmd_lock); + llist_add(&cmd->se_delayed_node, &dev->delayed_cmd_llist); pr_debug("Added CDB: 0x%02x Task Attr: 0x%02x to" " delayed CMD list, se_ordered_id: %u\n", @@ -1856,28 +1877,32 @@ EXPORT_SYMBOL(target_execute_cmd); /* * Process all commands up to the last received ORDERED task attribute which - * requires another blocking boundary + * requires another blocking boundary. */ static void target_restart_delayed_cmds(struct se_device *dev) { - for (;;) { - struct se_cmd *cmd; + bool ordered = false; + struct se_cmd *cmd; + struct llist_node *llnode; - spin_lock(&dev->delayed_cmd_lock); - if (list_empty(&dev->delayed_cmd_list)) { - spin_unlock(&dev->delayed_cmd_lock); - break; + llnode = llist_del_all(&dev->delayed_cmd_llist); + while (llnode) { + cmd = llist_entry(llnode, struct se_cmd, se_delayed_node); + llnode = llist_next(llnode); + /* + * Re-add outstanding command to se_device delayed llist to + * satisfy ordered tag execution requirements. + */ + if (ordered) { + llist_add(&cmd->se_delayed_node, &dev->delayed_cmd_llist); + continue; } - - cmd = list_entry(dev->delayed_cmd_list.next, - struct se_cmd, se_delayed_node); - list_del(&cmd->se_delayed_node); - spin_unlock(&dev->delayed_cmd_lock); - __target_execute_cmd(cmd); - if (cmd->sam_task_attr == TCM_ORDERED_TAG) - break; + if (cmd->sam_task_attr == TCM_ORDERED_TAG) { + ordered = true; + continue; + } } } @@ -1885,12 +1910,12 @@ static void target_restart_delayed_cmds(struct se_device *dev) * Called from I/O completion to determine which dormant/delayed * and ordered cmds need to have their tasks added to the execution queue. */ -static void transport_complete_task_attr(struct se_cmd *cmd) +static bool transport_complete_task_attr(struct se_cmd *cmd) { struct se_device *dev = cmd->se_dev; if (dev->transport->transport_type == TRANSPORT_PLUGIN_PHBA_PDEV) - return; + return true; if (cmd->sam_task_attr == TCM_SIMPLE_TAG) { atomic_dec_mb(&dev->simple_cmds); @@ -1910,8 +1935,23 @@ static void transport_complete_task_attr(struct se_cmd *cmd) pr_debug("Incremented dev_cur_ordered_id: %u for ORDERED:" " %u\n", dev->dev_cur_ordered_id, cmd->se_ordered_id); } + /* + * Check for special in_interrupt() case where completion can happen + * for certain fabrics from IRQ context, as long as no outstanding + * ordered tags exist. + */ + if (in_interrupt()) { + if (atomic_read(&dev->dev_ordered_sync)) + return false; + return true; + } + /* + * If called from process context, go ahead and drain the current + * se_device->delayed_cmd_llist of ordered tags if any exist. + */ target_restart_delayed_cmds(dev); + return true; } static void transport_complete_qf(struct se_cmd *cmd) @@ -1995,18 +2035,18 @@ static bool target_read_prot_action(struct se_cmd *cmd) return false; } -static void target_complete_ok_work(struct work_struct *work) -{ - struct se_cmd *cmd = container_of(work, struct se_cmd, work); - int ret; - /* - * Check if we need to move delayed/dormant tasks from cmds on the - * delayed execution list after a HEAD_OF_QUEUE or ORDERED Task - * Attribute. - */ - transport_complete_task_attr(cmd); +static void target_complete_queue_full(struct se_cmd *cmd) +{ + pr_debug("Handling complete_ok QUEUE_FULL: se_cmd: %p," + " data_direction: %d\n", cmd, cmd->data_direction); + cmd->t_state = TRANSPORT_COMPLETE_QF_OK; + transport_handle_queue_full(cmd, cmd->se_dev); +} +static bool target_complete_ok_pre(struct se_cmd *cmd) +{ + int ret; /* * Check to schedule QUEUE_FULL work, or execute an existing * cmd->transport_qf_callback() @@ -2027,7 +2067,7 @@ static void target_complete_ok_work(struct work_struct *work) transport_lun_remove_cmd(cmd); transport_cmd_check_stop_to_fabric(cmd); - return; + return true; } /* * Check for a callback, used by amongst other things @@ -2040,9 +2080,9 @@ static void target_complete_ok_work(struct work_struct *work) if (!rc && !(cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE_POST)) { if ((cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE) && !cmd->data_length) - goto queue_rsp; + return false; - return; + return true; } else if (rc) { ret = transport_send_check_condition_and_sense(cmd, rc, 0); @@ -2051,11 +2091,27 @@ static void target_complete_ok_work(struct work_struct *work) transport_lun_remove_cmd(cmd); transport_cmd_check_stop_to_fabric(cmd); - return; + return true; } } -queue_rsp: + return false; + +queue_full: + target_complete_queue_full(cmd); + return true; +} + +static void target_complete_irq(struct se_cmd *cmd, bool check_qf) +{ + int ret; + /* + * Check to schedule QUEUE_FULL work, or execute an existing + * cmd->transport_qf_callback() + */ + if (check_qf && atomic_read(&cmd->se_dev->dev_qf_count) != 0) + schedule_work(&cmd->se_dev->qf_work_queue); + switch (cmd->data_direction) { case DMA_FROM_DEVICE: atomic_long_add(cmd->data_length, @@ -2111,10 +2167,29 @@ queue_rsp: return; queue_full: - pr_debug("Handling complete_ok QUEUE_FULL: se_cmd: %p," - " data_direction: %d\n", cmd, cmd->data_direction); - cmd->t_state = TRANSPORT_COMPLETE_QF_OK; - transport_handle_queue_full(cmd, cmd->se_dev); + target_complete_queue_full(cmd); +} + +static void target_complete_ok_work(struct work_struct *work) +{ + struct se_cmd *cmd = container_of(work, struct se_cmd, work); + /* + * Check if we need to move delayed/dormant tasks from cmds on the + * delayed execution list after a HEAD_OF_QUEUE or ORDERED Task + * Attribute. + */ + transport_complete_task_attr(cmd); + /* + * Invoke special case handling for QUEUE_FULL, backend TASK_SENSE or + * transport_complete_callback() cases. + */ + if (target_complete_ok_pre(cmd)) + return; + /* + * Perform the se_tfo->queue_data_in() and/or >se_tfo->queue_status() + * callbacks into fabric code. + */ + target_complete_irq(cmd, false); } static inline void transport_free_sgl(struct scatterlist *sgl, int nents) diff --git a/include/target/target_core_base.h b/include/target/target_core_base.h index 9f3878f..bc07c8b 100644 --- a/include/target/target_core_base.h +++ b/include/target/target_core_base.h @@ -156,6 +156,7 @@ enum se_cmd_flags_table { SCF_COMPARE_AND_WRITE = 0x00080000, SCF_COMPARE_AND_WRITE_POST = 0x00100000, SCF_PASSTHROUGH_PROT_SG_TO_MEM_NOALLOC = 0x00200000, + SCF_COMPLETE_IRQ = 0x00400000, }; /* struct se_dev_entry->lun_flags and struct se_lun->lun_access */ @@ -487,7 +488,7 @@ struct se_cmd { u64 pr_res_key; /* Used for sense data */ void *sense_buffer; - struct list_head se_delayed_node; + struct llist_node se_delayed_node; struct list_head se_qf_node; struct se_device *se_dev; struct se_lun *se_lun; @@ -795,7 +796,7 @@ struct se_device { struct list_head dev_tmr_list; struct workqueue_struct *tmr_wq; struct work_struct qf_work_queue; - struct list_head delayed_cmd_list; + struct llist_head delayed_cmd_llist; struct list_head state_list; struct list_head qf_cmd_list; struct list_head g_dev_node; diff --git a/include/target/target_core_fabric.h b/include/target/target_core_fabric.h index d6216b7..72c4a12 100644 --- a/include/target/target_core_fabric.h +++ b/include/target/target_core_fabric.h @@ -4,6 +4,7 @@ struct target_core_fabric_ops { struct module *module; const char *name; + bool complete_irq; size_t node_acl_size; char *(*get_fabric_name)(void); char *(*tpg_get_wwn)(struct se_portal_group *); -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html