diff --git a/scst/include/scst.h b/scst/include/scst.h index c7b489ee3..00d3a36ea 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -2213,6 +2213,12 @@ struct scst_cmd { /* Set if cmd has LINK bit set in CDB */ unsigned int cmd_linked:1; + /* Set if cmd is on scst_global_stpg_list */ + unsigned int cmd_on_global_stpg_list:1; + + /* Set if cmd was globally STPG blocked in __scst_check_blocked_dev() */ + unsigned int cmd_global_stpg_blocked:1; + /**************************************************************/ /* cmd's async flags */ @@ -2428,6 +2434,11 @@ struct scst_cmd { void *cmd_data_descriptors; int cmd_data_descriptors_cnt; }; + + /* STPG commands global serialization */ + struct { + struct list_head global_stpg_list_entry; + }; }; #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) @@ -3707,6 +3718,7 @@ bool scst_alua_configured(struct scst_device *dev); int scst_tg_get_group_info(void **buf, uint32_t *response_length, struct scst_device *dev, uint8_t data_format); int scst_tg_set_group_info(struct scst_cmd *cmd); +void scst_stpg_del_unblock_next(struct scst_cmd *cmd); /* * Get/set functions for dev's static DIF APP TAG diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index be6f489c2..f758c9f30 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -5294,6 +5294,8 @@ static enum compl_status_e vdisk_exec_set_tpgs(struct vdisk_cmd_params *p) rc = scst_tg_set_group_info(cmd); if (rc == 0) res = RUNNING_ASYNC; + else + scst_stpg_del_unblock_next(cmd); out: TRACE_EXIT_RES(res); diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index a707c5129..ddb64c354 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -61,6 +61,19 @@ #include "scst_mem.h" #include "scst_pres.h" +/* + * List and IRQ lock to globally serialize all STPG commands. Needed to + * prevent deadlock, if (1) a device group contains multiple devices and + * (2) STPG commands comes to 2 or more of them at about the same time. + * In this case they will be waiting for each other to finish all pending + * commands, i.e. the STPG commands waiting for each other. Strict + * serialization is per device, so can not help here. + * + * ToDo: make it per device group. + */ +static DEFINE_SPINLOCK(scst_global_stpg_list_lock); +static LIST_HEAD(scst_global_stpg_list); + static void scst_put_acg_work(struct work_struct *work); static void scst_del_acn(struct scst_acn *acn); static void scst_free_acn(struct scst_acn *acn, bool reassign); @@ -6472,6 +6485,60 @@ static void scst_destroy_cmd(struct scst_cmd *cmd) return; } +/* No locks */ +void scst_stpg_del_unblock_next(struct scst_cmd *cmd) +{ + struct scst_cmd *c; + + TRACE_ENTRY(); + + spin_lock_irq(&scst_global_stpg_list_lock); + + EXTRACHECKS_BUG_ON(!cmd->cmd_on_global_stpg_list); + + TRACE_DBG("STPG cmd %p: unblocking next", cmd); + + list_del(&cmd->global_stpg_list_entry); + cmd->cmd_on_global_stpg_list = 0; + + if (list_empty(&scst_global_stpg_list)) { + TRACE_DBG("No more STPG commands to unblock"); + spin_unlock_irq(&scst_global_stpg_list_lock); + goto out; + } + + c = list_first_entry(&scst_global_stpg_list, typeof(*c), + global_stpg_list_entry); + + spin_unlock_irq(&scst_global_stpg_list_lock); + + spin_lock_bh(&c->dev->dev_lock); + + if (!c->cmd_global_stpg_blocked) { + TRACE_DBG("STPG cmd %p is not cmd_global_stpg_blocked", c); + spin_unlock_bh(&c->dev->dev_lock); + goto out; + } + + TRACE_BLOCK("Unblocking serialized STPG cmd %p", c); + + list_del(&c->blocked_cmd_list_entry); + c->cmd_global_stpg_blocked = 0; + + spin_unlock_bh(&c->dev->dev_lock); + + spin_lock_irq(&c->cmd_threads->cmd_list_lock); + list_add(&c->cmd_list_entry, + &c->cmd_threads->active_cmd_list); + wake_up(&c->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&c->cmd_threads->cmd_list_lock); + +out: + TRACE_EXIT(); + return; +} +EXPORT_SYMBOL_GPL(scst_stpg_del_unblock_next); + /* No locks supposed to be held */ void scst_free_cmd(struct scst_cmd *cmd) { @@ -10526,10 +10593,18 @@ static int get_cdb_info_mo(struct scst_cmd *cmd, { switch (cmd->cdb[1] & 0x1f) { case MO_SET_TARGET_PGS: + { + unsigned long flags; cmd->op_name = "SET TARGET PORT GROUPS"; cmd->op_flags |= SCST_STRICTLY_SERIALIZED; + spin_lock_irqsave(&scst_global_stpg_list_lock, flags); + TRACE_DBG("Adding STPG cmd %p to global_stpg_list", cmd); + cmd->cmd_on_global_stpg_list = 1; + list_add_tail(&cmd->global_stpg_list_entry, &scst_global_stpg_list); + spin_unlock_irqrestore(&scst_global_stpg_list_lock, flags); break; } + } return get_cdb_info_len_4(cmd, sdbops); } @@ -11963,10 +12038,27 @@ bool __scst_check_blocked_dev(struct scst_cmd *cmd) scst_get_opcode_name(cmd), dev->virt_name); goto out_block; } else if ((cmd->op_flags & SCST_STRICTLY_SERIALIZED) == SCST_STRICTLY_SERIALIZED) { - TRACE_BLOCK("cmd %p (tag %llu, op %s): blocking further " - "cmds on dev %s due to strict serialization", cmd, - (unsigned long long int)cmd->tag, + TRACE_BLOCK("Strictly serialized cmd %p (tag %llu, op %s, dev %s)", + cmd, (unsigned long long int)cmd->tag, scst_get_opcode_name(cmd), dev->virt_name); + + if ((cmd->cdb[0] == MAINTENANCE_OUT) && + ((cmd->cdb[1] & 0x1f) == MO_SET_TARGET_PGS)) { + const struct scst_cmd *c; + /* + * It's OK to do that without lock, because we + * are interested only in comparison + */ + c = list_first_entry(&scst_global_stpg_list, typeof(*c), + global_stpg_list_entry); + if (cmd != c) { + TRACE_BLOCK("Blocking serialized STPG cmd %p " + "(head %p)", cmd, c); + cmd->cmd_global_stpg_blocked = 1; + goto out_block; + } + } + scst_block_dev(dev); if (dev->on_dev_cmd_count > 1) { TRACE_BLOCK("Delaying strictly serialized cmd %p " @@ -12019,10 +12111,17 @@ void scst_unblock_dev(struct scst_device *dev) local_irq_save_nort(flags); list_for_each_entry_safe(cmd, tcmd, &dev->blocked_cmd_list, blocked_cmd_list_entry) { - bool strictly_serialized; + bool strictly_serialized = + ((cmd->op_flags & SCST_STRICTLY_SERIALIZED) == SCST_STRICTLY_SERIALIZED); + + if (dev->strictly_serialized_cmd_waiting && + !strictly_serialized) + continue; + list_del(&cmd->blocked_cmd_list_entry); - TRACE_BLOCK("Adding blocked cmd %p to active cmd " - "list", cmd); + cmd->cmd_global_stpg_blocked = 0; + + TRACE_BLOCK("Adding blocked cmd %p to active cmd list", cmd); spin_lock(&cmd->cmd_threads->cmd_list_lock); if (cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE) list_add(&cmd->cmd_list_entry, @@ -12030,9 +12129,9 @@ void scst_unblock_dev(struct scst_device *dev) else list_add_tail(&cmd->cmd_list_entry, &cmd->cmd_threads->active_cmd_list); - strictly_serialized = ((cmd->op_flags & SCST_STRICTLY_SERIALIZED) == SCST_STRICTLY_SERIALIZED); wake_up(&cmd->cmd_threads->cmd_list_waitQ); spin_unlock(&cmd->cmd_threads->cmd_list_lock); + if (dev->strictly_serialized_cmd_waiting && strictly_serialized) break; } diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 6732a7649..512311068 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -4261,6 +4261,12 @@ static int scst_finish_cmd(struct scst_cmd *cmd) spin_unlock_irq(&sess->sess_list_lock); + if (unlikely(cmd->cmd_on_global_stpg_list)) { + TRACE_DBG("Unlisting being freed STPG cmd %p", cmd); + EXTRACHECKS_BUG_ON(cmd->cmd_global_stpg_blocked); + scst_stpg_del_unblock_next(cmd); + } + if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) scst_finish_cmd_mgmt(cmd); @@ -5620,11 +5626,13 @@ static int scst_set_mcmd_next_state(struct scst_mgmt_cmd *mcmd) /* IRQs supposed to be disabled */ static bool __scst_check_unblock_aborted_cmd(struct scst_cmd *cmd, - struct list_head *list_entry) + struct list_head *list_entry, bool blocked) { bool res; if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) { list_del(list_entry); + if (blocked) + cmd->cmd_global_stpg_blocked = 0; spin_lock(&cmd->cmd_threads->cmd_list_lock); list_add_tail(&cmd->cmd_list_entry, &cmd->cmd_threads->active_cmd_list); @@ -5667,7 +5675,7 @@ void scst_unblock_aborted_cmds(const struct scst_tgt *tgt, continue; if (__scst_check_unblock_aborted_cmd(cmd, - &cmd->blocked_cmd_list_entry)) { + &cmd->blocked_cmd_list_entry, true)) { TRACE_MGMT_DBG("Unblock aborted blocked cmd %p", cmd); } } @@ -5689,7 +5697,7 @@ void scst_unblock_aborted_cmds(const struct scst_tgt *tgt, continue; if (__scst_check_unblock_aborted_cmd(cmd, - &cmd->deferred_cmd_list_entry)) { + &cmd->deferred_cmd_list_entry, false)) { TRACE_MGMT_DBG("Unblocked aborted SN " "cmd %p (sn %u)", cmd, cmd->sn); order_data->def_cmd_count--; diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index e2fd4018f..a6ce4783c 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -919,6 +919,8 @@ out_unlock: mutex_unlock(&scst_dg_mutex); mutex_unlock(&scst_mutex); + scst_stpg_del_unblock_next(cmd); + cmd->completed = 1; cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD);