Jelajahi Sumber

SCSI commands will now be executed in the correct order

Tal Aloni 8 tahun lalu
induk
melakukan
7a282c8a7b

+ 1 - 1
ISCSI/Server/ISCSIServer.cs

@@ -458,7 +458,7 @@ namespace ISCSI.Server
                 }
                 else if (pdu is SCSIDataOutPDU || pdu is SCSICommandPDU)
                 {
-                    // FIXME: the iSCSI target layer MUST deliver the commands for execution (to the SCSI execution engine) in the order specified by CmdSN.
+                    // RFC 3720: the iSCSI target layer MUST deliver the commands for execution (to the SCSI execution engine) in the order specified by CmdSN.
                     // e.g. read requests should not be executed while previous write request data is being received (via R2T)
                     List<SCSICommandPDU> commandsToExecute;
                     List<ISCSIPDU> responseList;

+ 73 - 0
ISCSI/Server/SessionParameters.cs

@@ -83,6 +83,9 @@ namespace ISCSI.Server
         public bool CommandNumberingStarted;
         public uint ExpCmdSN;
 
+        public List<uint> CommandsInTransfer = new List<uint>();
+        public List<SCSICommandPDU> DelayedCommands = new List<SCSICommandPDU>();
+
         /// <summary>
         /// Target Transfer Tag:
         /// There are no protocol specific requirements with regard to the value of these tags,
@@ -96,5 +99,75 @@ namespace ISCSI.Server
             m_nextTransferTag++;
             return transferTag;
         }
+
+        public bool IsPrecedingCommandPending(uint cmdSN)
+        {
+            foreach (uint entry in CommandsInTransfer)
+            {
+                if (IsFirstCmdSNPreceding(entry, cmdSN))
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public List<SCSICommandPDU> GetDelayedCommandsReadyForExecution()
+        {
+            List<SCSICommandPDU> result = new List<SCSICommandPDU>();
+            if (CommandsInTransfer.Count == 0)
+            {
+                result.AddRange(DelayedCommands);
+                DelayedCommands.Clear();
+                return result;
+            }
+
+            // We find the earliest CmdSN of the commands in transfer
+            uint earliestCmdSN = CommandsInTransfer[0];
+            for(int index = 1; index < CommandsInTransfer.Count; index++)
+            {
+                if (IsFirstCmdSNPreceding(CommandsInTransfer[index], earliestCmdSN))
+                {
+                    earliestCmdSN = CommandsInTransfer[index];
+                }
+            }
+
+            // Any command that is preceding minCmdSN should be executed
+            for(int index = 0; index < DelayedCommands.Count; index++)
+            {
+                SCSICommandPDU delayedCommand = DelayedCommands[index];
+                if (IsFirstCmdSNPreceding(delayedCommand.CmdSN, earliestCmdSN))
+                {
+                    result.Add(delayedCommand);
+                    DelayedCommands.RemoveAt(index);
+                    index--;
+                }
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// Returns true if cmdSN1 should be executed before cmdSN2
+        /// </summary>
+        public static bool IsFirstCmdSNPreceding(uint cmdSN1, uint cmdSN2)
+        {
+            // The iSCSI protocol is designed to avoid having old, retried command instances appear in a valid command window after a command sequence number wrap around.
+            const uint commandWindow = 2 ^ 31 - 1;
+            if (cmdSN2 >= commandWindow)
+            {
+                if ((cmdSN1 > cmdSN2 - commandWindow) && (cmdSN1 < cmdSN2))
+                {
+                    return true;
+                }
+            }
+            else
+            {
+                if ((cmdSN1 > cmdSN2 - commandWindow) || (cmdSN1 < cmdSN2))
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
     }
 }

+ 26 - 2
ISCSI/Server/TargetResponseHelper.cs

@@ -40,12 +40,20 @@ namespace ISCSI.Server
                 response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, command.ExpectedDataTransferLength - response.BufferOffset);
 
                 connection.AddTransfer(transferTag, command, 1);
+                session.CommandsInTransfer.Add(command.CmdSN);
 
                 responseList.Add(response);
                 return responseList;
             }
 
-            commandsToExecute.Add(command);
+            if (session.IsPrecedingCommandPending(command.CmdSN))
+            {
+                session.DelayedCommands.Add(command);
+            }
+            else
+            {
+                commandsToExecute.Add(command);
+            }
             return responseList;
         }
 
@@ -80,7 +88,23 @@ namespace ISCSI.Server
             if (offset + request.DataSegmentLength == totalLength)
             {
                 // Last Data-out PDU
-                commandsToExecute.Add(transfer.Command);
+                if (session.IsPrecedingCommandPending(transfer.Command.CmdSN))
+                {
+                    session.DelayedCommands.Add(transfer.Command);
+                }
+                else
+                {
+                    commandsToExecute.Add(transfer.Command);
+                    connection.RemoveTransfer(request.TargetTransferTag);
+                    session.CommandsInTransfer.Remove(transfer.Command.CmdSN);
+                    // Check if delayed commands are ready to be executed
+                    List<SCSICommandPDU> pendingCommands = session.GetDelayedCommandsReadyForExecution();
+                    foreach (SCSICommandPDU pendingCommand in pendingCommands)
+                    {
+                        ISCSIServer.Log("[{0}] Queuing Command: CmdSN: {1}", connectionIdentifier, pendingCommand.CmdSN);
+                        commandsToExecute.Add(pendingCommand);
+                    }
+                }
                 return responseList;
             }
             else