]> www.infradead.org Git - users/hch/misc.git/commitdiff
usb: gadget: f_tcm: Check overlapped command
authorThinh Nguyen <Thinh.Nguyen@synopsys.com>
Wed, 11 Dec 2024 00:33:56 +0000 (00:33 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 24 Dec 2024 07:56:07 +0000 (08:56 +0100)
If there's an overlapped command tag, cancel the command and respond
with RC_OVERLAPPED_TAG to host.

Signed-off-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Link: https://lore.kernel.org/r/6bffc2903d0cd1e7c7afca837053a48e883d8903.1733876548.git.Thinh.Nguyen@synopsys.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/gadget/function/f_tcm.c
drivers/usb/gadget/function/tcm.h

index 3e04ce40a4a09759658972f597bbaf02a6c78af5..0c7a41568f400a3423183262cc797aaad89fe222 100644 (file)
@@ -685,12 +685,25 @@ static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req)
                break;
 
        case UASP_QUEUE_COMMAND:
+               /*
+                * Overlapped command detected and cancelled.
+                * So send overlapped attempted status.
+                */
+               if (cmd->tmr_rsp == RC_OVERLAPPED_TAG &&
+                   req->status == -ECONNRESET) {
+                       uasp_send_tm_response(cmd);
+                       return;
+               }
+
+               hash_del(&stream->node);
+
                /*
                 * If no command submitted to target core here, just free the
                 * bitmap index. This is for the cases where f_tcm handles
                 * status response instead of the target core.
                 */
-               if (cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) {
+               if (cmd->tmr_rsp != RC_OVERLAPPED_TAG &&
+                   cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) {
                        struct se_session *se_sess;
 
                        se_sess = fu->tpg->tpg_nexus->tvn_se_sess;
@@ -702,6 +715,7 @@ static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req)
                }
 
                usb_ep_queue(fu->ep_cmd, cmd->req, GFP_ATOMIC);
+               complete(&stream->cmd_completion);
                break;
 
        default:
@@ -710,6 +724,7 @@ static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req)
        return;
 
 cleanup:
+       hash_del(&stream->node);
        transport_generic_free_cmd(&cmd->se_cmd, 0);
 }
 
@@ -842,6 +857,8 @@ static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req)
 
 static int uasp_alloc_stream_res(struct f_uas *fu, struct uas_stream *stream)
 {
+       init_completion(&stream->cmd_completion);
+
        stream->req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL);
        if (!stream->req_in)
                goto out;
@@ -1046,6 +1063,9 @@ static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req)
        cmd->state = UASP_QUEUE_COMMAND;
 
        if (req->status == -ESHUTDOWN) {
+               struct uas_stream *stream = &cmd->fu->stream[se_cmd->map_tag];
+
+               hash_del(&stream->node);
                target_put_sess_cmd(se_cmd);
                transport_generic_free_cmd(&cmd->se_cmd, 0);
                return;
@@ -1069,6 +1089,14 @@ static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req)
 
 cleanup:
        target_put_sess_cmd(se_cmd);
+
+       /* Command was aborted due to overlapped tag */
+       if (cmd->state == UASP_QUEUE_COMMAND &&
+           cmd->tmr_rsp == RC_OVERLAPPED_TAG) {
+               uasp_send_tm_response(cmd);
+               return;
+       }
+
        transport_send_check_condition_and_sense(se_cmd,
                        TCM_CHECK_CONDITION_ABORT_CMD, 0);
 }
@@ -1137,6 +1165,8 @@ static int usbg_send_read_response(struct se_cmd *se_cmd)
                return uasp_send_read_response(cmd);
 }
 
+static void usbg_aborted_task(struct se_cmd *se_cmd);
+
 static void usbg_submit_tmr(struct usbg_cmd *cmd)
 {
        struct se_session *se_sess;
@@ -1214,6 +1244,74 @@ static void usbg_cmd_work(struct work_struct *work)
        return;
 
 skip:
+       if (cmd->tmr_rsp == RC_OVERLAPPED_TAG) {
+               struct f_uas *fu = cmd->fu;
+               struct se_session *se_sess;
+               struct uas_stream *stream = NULL;
+               struct hlist_node *tmp;
+               struct usbg_cmd *active_cmd = NULL;
+
+               se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess;
+
+               hash_for_each_possible_safe(fu->stream_hash, stream, tmp, node, cmd->tag) {
+                       int i = stream - &fu->stream[0];
+
+                       active_cmd = &((struct usbg_cmd *)se_sess->sess_cmd_map)[i];
+                       if (active_cmd->tag == cmd->tag)
+                               break;
+               }
+
+               /* Sanity check */
+               if (!stream || (active_cmd && active_cmd->tag != cmd->tag)) {
+                       usbg_submit_command(cmd->fu, cmd->req);
+                       return;
+               }
+
+               reinit_completion(&stream->cmd_completion);
+
+               /*
+                * A UASP command consists of the command, data, and status
+                * stages, each operating sequentially from different endpoints.
+                *
+                * Each USB endpoint operates independently, and depending on
+                * hardware implementation, a completion callback for a transfer
+                * from one endpoint may not reflect the order of completion on
+                * the wire. This is particularly true for devices with
+                * endpoints that have independent interrupts and event buffers.
+                *
+                * The driver must still detect misbehaving hosts and respond
+                * with an overlap status. To reduce false overlap failures,
+                * allow the active and matching stream ID a brief 1ms to
+                * complete before responding with an overlap command failure.
+                * Overlap failure should be rare.
+                */
+               wait_for_completion_timeout(&stream->cmd_completion, msecs_to_jiffies(1));
+
+               /* If the previous stream is completed, retry the command. */
+               if (!hash_hashed(&stream->node)) {
+                       usbg_submit_command(cmd->fu, cmd->req);
+                       return;
+               }
+
+               /*
+                * The command isn't submitted to the target core, so we're safe
+                * to remove the bitmap index from the session tag pool.
+                */
+               sbitmap_queue_clear(&se_sess->sess_tag_pool,
+                                   cmd->se_cmd.map_tag,
+                                   cmd->se_cmd.map_cpu);
+
+               /*
+                * Overlap command tag detected. Cancel any pending transfer of
+                * the command submitted to target core.
+                */
+               active_cmd->tmr_rsp = RC_OVERLAPPED_TAG;
+               usbg_aborted_task(&active_cmd->se_cmd);
+
+               /* Send the response after the transfer is aborted. */
+               return;
+       }
+
        uasp_send_tm_response(cmd);
 }
 
@@ -1247,6 +1345,8 @@ static int usbg_submit_command(struct f_uas *fu, struct usb_request *req)
        struct usbg_cmd *cmd;
        struct usbg_tpg *tpg = fu->tpg;
        struct tcm_usbg_nexus *tv_nexus;
+       struct uas_stream *stream;
+       struct hlist_node *tmp;
        struct command_iu *cmd_iu;
        u32 cmd_len;
        u16 scsi_tag;
@@ -1282,6 +1382,23 @@ static int usbg_submit_command(struct f_uas *fu, struct usb_request *req)
                goto skip;
        }
 
+       hash_for_each_possible_safe(fu->stream_hash, stream, tmp, node, scsi_tag) {
+               struct usbg_cmd *active_cmd;
+               struct se_session *se_sess;
+               int i = stream - &fu->stream[0];
+
+               se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess;
+               active_cmd = &((struct usbg_cmd *)se_sess->sess_cmd_map)[i];
+
+               if (active_cmd->tag == scsi_tag) {
+                       cmd->tmr_rsp = RC_OVERLAPPED_TAG;
+                       goto skip;
+               }
+       }
+
+       stream = &fu->stream[cmd->se_cmd.map_tag];
+       hash_add(fu->stream_hash, &stream->node, scsi_tag);
+
        if (iu->iu_id == IU_ID_TASK_MGMT) {
                struct task_mgmt_iu *tm_iu;
 
@@ -1293,6 +1410,7 @@ static int usbg_submit_command(struct f_uas *fu, struct usb_request *req)
        cmd_len = (cmd_iu->len & ~0x3) + 16;
        if (cmd_len > USBG_MAX_CMD) {
                target_free_tag(tv_nexus->tvn_se_sess, &cmd->se_cmd);
+               hash_del(&stream->node);
                return -EINVAL;
        }
        memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len);
@@ -1443,6 +1561,7 @@ static void usbg_release_cmd(struct se_cmd *se_cmd)
                        se_cmd);
        struct se_session *se_sess = se_cmd->se_sess;
 
+       cmd->tag = 0;
        kfree(cmd->data_buf);
        target_free_tag(se_sess, se_cmd);
 }
@@ -2467,6 +2586,8 @@ static struct usb_function *tcm_alloc(struct usb_function_instance *fi)
        fu->function.disable = tcm_disable;
        fu->function.free_func = tcm_free;
        fu->tpg = tpg_instances[i].tpg;
+
+       hash_init(fu->stream_hash);
        mutex_unlock(&tpg_instances_lock);
 
        return &fu->function;
index d37358f09819ade58b6ff3a59789b5181093b6b5..f6d6c86d10b3f6d2e1ed00df1aa3efcb7cd2142a 100644 (file)
@@ -4,6 +4,7 @@
 
 #include <linux/kref.h>
 /* #include <linux/usb/uas.h> */
+#include <linux/hashtable.h>
 #include <linux/usb/composite.h>
 #include <linux/usb/uas.h>
 #include <linux/usb/storage.h>
@@ -103,6 +104,9 @@ struct uas_stream {
        struct usb_request      *req_in;
        struct usb_request      *req_out;
        struct usb_request      *req_status;
+
+       struct completion       cmd_completion;
+       struct hlist_node       node;
 };
 
 struct usbg_cdb {
@@ -135,6 +139,7 @@ struct f_uas {
        struct usb_ep           *ep_status;
        struct usb_ep           *ep_cmd;
        struct uas_stream       stream[USBG_NUM_CMDS];
+       DECLARE_HASHTABLE(stream_hash, UASP_SS_EP_COMP_LOG_STREAMS);
 
        /* BOT */
        struct bot_status       bot_status;