698 lines
19 KiB
C
698 lines
19 KiB
C
|
||
/*++
|
||
|
||
Copyright (C) Microsoft Corporation, 2000
|
||
|
||
Module Name:
|
||
|
||
protocol.c
|
||
|
||
Abstract:
|
||
|
||
This file contains iSCSI protocol related routines.
|
||
|
||
Environment:
|
||
|
||
kernel mode only
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "port.h"
|
||
|
||
LONG GlobalSessionID;
|
||
|
||
BOOLEAN PrintDataBuffer = FALSE;
|
||
|
||
UCHAR
|
||
GetCdbLength(
|
||
IN UCHAR OpCode
|
||
);
|
||
|
||
|
||
NTSTATUS
|
||
iSpSendLoginResponse(
|
||
IN PDEVICE_OBJECT DeviceObject,
|
||
IN PVOID Context
|
||
)
|
||
{
|
||
PISCSI_FDO_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
|
||
PISCSI_CONNECTION iScsiConnection = fdoExtension->ServerNodeInfo;
|
||
PISCSI_LOGIN_RESPONSE iscsiLoginResponse;
|
||
PISCSI_LOGIN_COMMAND loginCommand;
|
||
NTSTATUS status;
|
||
ULONG bytesSent;
|
||
ULONG tempULong;
|
||
|
||
|
||
DelayThreadExecution(1);
|
||
|
||
IoFreeWorkItem((PIO_WORKITEM) Context);
|
||
|
||
ASSERT((iScsiConnection != NULL));
|
||
ASSERT((iScsiConnection->Type) == ISCSI_CONNECTION_TYPE);
|
||
|
||
|
||
loginCommand = (PISCSI_LOGIN_COMMAND)(iScsiConnection->IScsiHeader);
|
||
|
||
iscsiLoginResponse = iSpAllocatePool(NonPagedPool,
|
||
sizeof(ISCSI_LOGIN_RESPONSE),
|
||
ISCSI_TAG_LOGIN_RES);
|
||
if (iscsiLoginResponse == NULL) {
|
||
DebugPrint((0, "Failed to allocate logon response packet\n"));
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
RtlZeroMemory(iscsiLoginResponse, sizeof(ISCSI_LOGIN_RESPONSE));
|
||
|
||
iscsiLoginResponse->OpCode = ISCSIOP_LOGIN_RESPONSE;
|
||
|
||
//
|
||
// Copy client's Session ID
|
||
//
|
||
iscsiLoginResponse->ISID[0] = loginCommand->ISID[0];
|
||
iscsiLoginResponse->ISID[1] = loginCommand->ISID[1];
|
||
|
||
tempULong = InterlockedIncrement(&GlobalSessionID);
|
||
iscsiLoginResponse->TSID[0] = (UCHAR) ((tempULong & 0xFF00) >> 8);
|
||
iscsiLoginResponse->TSID[1] = (UCHAR) (tempULong & 0xFF);
|
||
|
||
iscsiLoginResponse->ExpCmdRN[0] = loginCommand->InitCmdRN[0];
|
||
iscsiLoginResponse->ExpCmdRN[1] = loginCommand->InitCmdRN[1];
|
||
iscsiLoginResponse->ExpCmdRN[2] = loginCommand->InitCmdRN[2];
|
||
iscsiLoginResponse->ExpCmdRN[3] = loginCommand->InitCmdRN[3];
|
||
|
||
iscsiLoginResponse->MaxCmdRN[3] = MAX_PENDING_REQUESTS;
|
||
|
||
iscsiLoginResponse->InitStatRN[3] = 1;
|
||
|
||
iscsiLoginResponse->Status = ISCSI_LOGINSTATUS_ACCEPT;
|
||
|
||
GetUlongFromArray((loginCommand->InitCmdRN),
|
||
(iScsiConnection->ExpCommandRefNum));
|
||
|
||
iScsiConnection->MaxCommandRefNum = MAX_PENDING_REQUESTS;
|
||
|
||
iScsiConnection->StatusRefNum = 1;
|
||
|
||
//
|
||
// Send logon response
|
||
//
|
||
fdoExtension->CurrentProtocolState = PSFullFeaturePhase;
|
||
status = iSpSendData(iScsiConnection->ConnectionDeviceObject,
|
||
iScsiConnection->ConnectionFileObject,
|
||
iscsiLoginResponse,
|
||
sizeof(ISCSI_LOGIN_RESPONSE),
|
||
&bytesSent);
|
||
if (NT_SUCCESS(status)) {
|
||
|
||
DebugPrint((3,
|
||
"Send succeeded for logon response. Bytes sent : %d\n",
|
||
bytesSent));
|
||
} else {
|
||
DebugPrint((0, "Could not send logon response. Status %x\n",
|
||
status));
|
||
|
||
fdoExtension->CurrentProtocolState = PSLogonFailed;
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
|
||
VOID
|
||
iSpProcessScsiCommand(
|
||
IN PVOID Context
|
||
)
|
||
{
|
||
PISCSI_FDO_EXTENSION fdoExtension = (PISCSI_FDO_EXTENSION) Context;
|
||
PISCSI_CONNECTION iScsiConnection = fdoExtension->ServerNodeInfo;
|
||
PISCSI_SCSI_COMMAND iScsiCommand;
|
||
PISCSI_SCSI_RESPONSE iScsiResponse = NULL;
|
||
PIRP irp = NULL;
|
||
PMDL mdl = NULL;
|
||
PACTIVE_REQUESTS currentRequest;
|
||
PIO_STACK_LOCATION irpStack;
|
||
PCDB cdb;
|
||
SCSI_REQUEST_BLOCK srb;
|
||
KEVENT event;
|
||
IO_STATUS_BLOCK ioStatus;
|
||
ULONG length =0;
|
||
ULONG sizeRequired;
|
||
ULONG inx;
|
||
ULONG bytesSent;
|
||
NTSTATUS status;
|
||
|
||
while (TRUE) {
|
||
|
||
KeWaitForSingleObject(
|
||
(PVOID) &(iScsiConnection->RequestSemaphore),
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
NULL
|
||
);
|
||
|
||
if ((iScsiConnection->TerminateThread) == TRUE) {
|
||
|
||
//
|
||
// This is an indication to terminate this thread
|
||
//
|
||
PsTerminateSystemThread(STATUS_SUCCESS);
|
||
}
|
||
|
||
inx = (iScsiConnection->ExpCommandRefNum) % MAX_PENDING_REQUESTS;
|
||
if (inx == 0) {
|
||
inx = MAX_PENDING_REQUESTS;
|
||
}
|
||
|
||
DebugPrint((3, "Will process request at index %d\n", inx));
|
||
|
||
currentRequest = &(iScsiConnection->ActiveRequests[inx]);
|
||
|
||
iScsiCommand = (PISCSI_SCSI_COMMAND) (currentRequest->IScsiHeader);
|
||
|
||
RtlZeroMemory(&srb, sizeof(SCSI_REQUEST_BLOCK));
|
||
|
||
//
|
||
// Set the size of the SCSI Request Block
|
||
//
|
||
srb.Length = SCSI_REQUEST_BLOCK_SIZE;
|
||
|
||
srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
|
||
|
||
if ((iScsiCommand->TurnOffAutoSense) == FALSE) {
|
||
srb.SenseInfoBuffer = currentRequest->SenseData;
|
||
|
||
srb.SenseInfoBufferLength = SENSE_BUFFER_SIZE;
|
||
}
|
||
|
||
//
|
||
// Get the CDB Length based on the CDB OpCode
|
||
//
|
||
srb.CdbLength = GetCdbLength(iScsiCommand->Cdb[0]);
|
||
|
||
cdb = (PCDB)(srb.Cdb);
|
||
RtlCopyMemory(cdb, iScsiCommand->Cdb, srb.CdbLength);
|
||
|
||
//
|
||
// Set DataBuffer pointer to the command buffer in
|
||
// the current request.
|
||
//
|
||
srb.DataBuffer = currentRequest->CommandBuffer;
|
||
|
||
GetUlongFromArray((iScsiCommand->Length),
|
||
length);
|
||
if (length == 0) {
|
||
DebugPrint((3, "Length 0. Probably READ command\n"));
|
||
GetUlongFromArray((iScsiCommand->ExpDataXferLength),
|
||
length);
|
||
}
|
||
|
||
//
|
||
// If length is non-zero at this point, then it's
|
||
// either a Read (ExpDataXferLength is non-zero), or
|
||
// Write (Immediate data length is non-zero) command.
|
||
//
|
||
if (length != 0) {
|
||
DebugPrint((3, "Read or Write data command\n"));
|
||
|
||
//
|
||
// Set the transfer length.
|
||
//
|
||
srb.DataTransferLength = length;
|
||
|
||
}
|
||
|
||
if (iScsiCommand->Read) {
|
||
|
||
srb.SrbFlags = SRB_FLAGS_DATA_IN;
|
||
|
||
} else if (length != 0) {
|
||
|
||
//
|
||
// If length is Non-Zero and Read bit is
|
||
// NOT set, then it should be a Write command
|
||
//
|
||
srb.SrbFlags = SRB_FLAGS_DATA_OUT;
|
||
|
||
}
|
||
|
||
switch (iScsiCommand->ATTR) {
|
||
case ISCSI_TASKATTR_UNTAGGED:
|
||
case ISCSI_TASKATTR_SIMPLE : {
|
||
srb.QueueAction = SRB_SIMPLE_TAG_REQUEST;
|
||
break;
|
||
}
|
||
|
||
case ISCSI_TASKATTR_ORDERED: {
|
||
srb.QueueAction = SRB_ORDERED_QUEUE_TAG_REQUEST;
|
||
break;
|
||
}
|
||
|
||
case ISCSI_TASKATTR_HEADOFQUEUE: {
|
||
srb.QueueAction = SRB_HEAD_OF_QUEUE_TAG_REQUEST;
|
||
break;
|
||
}
|
||
|
||
default: {
|
||
srb.QueueAction = SRB_SIMPLE_TAG_REQUEST;
|
||
break;
|
||
}
|
||
}
|
||
|
||
srb.QueueTag = SP_UNTAGGED;
|
||
|
||
SET_FLAG(srb.SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
|
||
SET_FLAG(srb.SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
|
||
|
||
//
|
||
// Set the event object to the unsignaled state.
|
||
// It will be used to signal request completion.
|
||
//
|
||
|
||
KeInitializeEvent(&event, NotificationEvent, FALSE);
|
||
|
||
//
|
||
// Build device I/O control request with METHOD_NEITHER data transfer.
|
||
// We'll queue a completion routine to cleanup the MDL's and such ourself.
|
||
//
|
||
|
||
irp = IoAllocateIrp(
|
||
(CCHAR) (fdoExtension->CommonExtension.LowerDeviceObject->StackSize + 1),
|
||
FALSE);
|
||
|
||
if(irp == NULL) {
|
||
|
||
DebugPrint((0, "Failed to allocate Irp\n"));
|
||
|
||
//
|
||
// ISSUE : Should handle this failure better
|
||
//
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Get next stack location.
|
||
//
|
||
|
||
irpStack = IoGetNextIrpStackLocation(irp);
|
||
|
||
//
|
||
// Set up SRB for execute scsi request. Save SRB address in next stack
|
||
// for the port driver.
|
||
//
|
||
|
||
irpStack->MajorFunction = IRP_MJ_SCSI;
|
||
irpStack->Parameters.Scsi.Srb = &srb;
|
||
|
||
IoSetCompletionRoutine(irp,
|
||
iSpSendSrbSynchronousCompletion,
|
||
&srb,
|
||
TRUE,
|
||
TRUE,
|
||
TRUE);
|
||
|
||
irp->UserIosb = &ioStatus;
|
||
irp->UserEvent = &event;
|
||
|
||
if (srb.DataTransferLength) {
|
||
//
|
||
// Build an MDL for the data buffer and stick it into the irp. The
|
||
// completion routine will unlock the pages and free the MDL.
|
||
//
|
||
|
||
irp->MdlAddress = IoAllocateMdl(srb.DataBuffer,
|
||
length,
|
||
FALSE,
|
||
FALSE,
|
||
irp );
|
||
if (irp->MdlAddress == NULL) {
|
||
IoFreeIrp( irp );
|
||
|
||
DebugPrint((0, "Failed to allocate MDL\n"));
|
||
|
||
//
|
||
// ISSUE : Should handle this failure better
|
||
//
|
||
continue;
|
||
}
|
||
|
||
try {
|
||
|
||
MmProbeAndLockPages( irp->MdlAddress,
|
||
KernelMode,
|
||
(iScsiCommand->Read ? IoWriteAccess :
|
||
IoWriteAccess));
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
status = GetExceptionCode();
|
||
|
||
IoFreeMdl(irp->MdlAddress);
|
||
|
||
IoFreeIrp( irp );
|
||
|
||
DebugPrint((0,
|
||
"Could not lock pages. Status : %x\n",
|
||
status));
|
||
|
||
//
|
||
// ISSUE : Should handle this failure better
|
||
//
|
||
continue;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Set timeout value for this request.
|
||
//
|
||
// N.B. The value should be chosen depending on
|
||
// the type of the device, and type of command.
|
||
// For now, just set some reasonable value
|
||
//
|
||
srb.TimeOutValue = 180;
|
||
|
||
//
|
||
// Zero out status.
|
||
//
|
||
|
||
srb.ScsiStatus = srb.SrbStatus = 0;
|
||
|
||
srb.NextSrb = 0;
|
||
|
||
//
|
||
// Set up IRP Address.
|
||
//
|
||
|
||
srb.OriginalRequest = irp;
|
||
|
||
//
|
||
// Call the port driver with the request and wait for it to complete.
|
||
//
|
||
|
||
status = IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, irp);
|
||
|
||
if (status == STATUS_PENDING) {
|
||
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
|
||
status = ioStatus.Status;
|
||
}
|
||
|
||
//
|
||
// Send the response to the client
|
||
//
|
||
sizeRequired = sizeof(ISCSI_SCSI_RESPONSE);
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
if (status == STATUS_BUFFER_OVERFLOW) {
|
||
|
||
if (length >= srb.DataTransferLength) {
|
||
DebugPrint((1, "DataUnderrun. Xp Len %d, XFer Len %d\n",
|
||
length, srb.DataTransferLength));
|
||
|
||
status = STATUS_SUCCESS;
|
||
|
||
sizeRequired += srb.DataTransferLength;
|
||
length = srb.DataTransferLength;
|
||
} else {
|
||
DebugPrint((0,
|
||
"Buffer overflow error. XLen %d, XFer Len %d\n",
|
||
length, srb.DataTransferLength));
|
||
|
||
sizeRequired += srb.SenseInfoBufferLength;
|
||
length = srb.SenseInfoBufferLength;
|
||
}
|
||
|
||
} else {
|
||
DebugPrint((0,
|
||
"Command failed. OpCode : 0x%x, Status : 0x%x\n",
|
||
srb.Cdb[0],
|
||
status));
|
||
|
||
DebugPrint((1, "Expected length : %d, Transfered length : %d\n",
|
||
length, srb.DataTransferLength));
|
||
|
||
sizeRequired += srb.SenseInfoBufferLength;
|
||
length = srb.SenseInfoBufferLength;
|
||
}
|
||
|
||
} else if (iScsiCommand->Read) {
|
||
|
||
ASSERT((length >= srb.DataTransferLength));
|
||
|
||
sizeRequired += srb.DataTransferLength;
|
||
length = srb.DataTransferLength;
|
||
|
||
} else {
|
||
|
||
length = 0;
|
||
|
||
}
|
||
|
||
DebugPrint((3, "Size of the response - %d. \n", sizeRequired));
|
||
|
||
if ((PrintDataBuffer == TRUE) && (length != 0)) {
|
||
|
||
for (inx = 0; inx < length; inx++) {
|
||
DebugPrint((0, "%02x ",
|
||
(((PUCHAR)(srb.DataBuffer))[inx])));
|
||
if ((inx != 0) && ((inx % 16) == 0)) {
|
||
DebugPrint((0, "\n"));
|
||
}
|
||
}
|
||
|
||
DebugPrint((0, "\n"));
|
||
}
|
||
|
||
iScsiResponse = iSpAllocatePool(NonPagedPool,
|
||
sizeRequired,
|
||
ISCSI_TAG_SCSIRES);
|
||
if (iScsiResponse != NULL) {
|
||
|
||
RtlZeroMemory(iScsiResponse, sizeRequired);
|
||
|
||
iScsiResponse->OpCode = ISCSIOP_SCSI_RESPONSE;
|
||
|
||
CopyFourBytes((iScsiResponse->TaskTag),
|
||
(iScsiCommand->TaskTag));
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
|
||
iScsiResponse->CmdStatus = SCSISTAT_GOOD;
|
||
iScsiResponse->iSCSIStatus = ISCSISTAT_GOOD;
|
||
|
||
} else {
|
||
|
||
DebugPrint((1, "Error. Response data size : %d\n",
|
||
sizeRequired));
|
||
|
||
iScsiResponse->CmdStatus = SCSISTAT_CHECK_CONDITION;
|
||
iScsiResponse->iSCSIStatus = ISCSISTAT_CHECK_CONDITION;
|
||
}
|
||
|
||
SetUlongInArray((iScsiResponse->StatusRN),
|
||
(iScsiConnection->StatusRefNum));
|
||
(iScsiConnection->StatusRefNum)++;
|
||
|
||
(iScsiConnection->ExpCommandRefNum)++;
|
||
SetUlongInArray((iScsiResponse->ExpCmdRN),
|
||
(iScsiConnection->ExpCommandRefNum));
|
||
|
||
SetUlongInArray((iScsiResponse->MaxCmdRN),
|
||
(iScsiConnection->ExpCommandRefNum) +
|
||
(MAX_PENDING_REQUESTS) - 1);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
|
||
if (srb.SrbStatus & SRB_STATUS_AUTOSENSE_VALID) {
|
||
DebugPrint((1, "Sense Data is valid\n"));
|
||
|
||
if ((iScsiCommand->TurnOffAutoSense) == FALSE) {
|
||
ULONG inx;
|
||
RtlCopyMemory((iScsiResponse + 1),
|
||
srb.SenseInfoBuffer,
|
||
srb.SenseInfoBufferLength);
|
||
|
||
DebugPrint((0, "OpCode : %x, Sense Data : ",
|
||
srb.Cdb[0]));
|
||
for (inx = 0; inx < (srb.SenseInfoBufferLength); inx++) {
|
||
DebugPrint((0, "%02x ", ((PUCHAR)(srb.SenseInfoBuffer))[inx]));
|
||
}
|
||
DebugPrint((0, "\n"));
|
||
|
||
SetUlongInArray((iScsiResponse->Length),
|
||
srb.SenseInfoBufferLength);
|
||
|
||
iScsiResponse->SenseDataLength[0] =
|
||
(UCHAR) ((srb.SenseInfoBufferLength) & 0x0000FF00);
|
||
iScsiResponse->SenseDataLength[1] =
|
||
(UCHAR) ((srb.SenseInfoBufferLength) & 0x000000FF);
|
||
}
|
||
|
||
} else {
|
||
ULONG inx0, inx1;
|
||
PUCHAR responseBuffer = (PUCHAR) iScsiResponse;
|
||
|
||
DebugPrint((0, "Sense Data is NOT valid\n"));
|
||
|
||
length = 0;
|
||
sizeRequired = sizeof(ISCSI_SCSI_RESPONSE);
|
||
|
||
DebugPrint((1, "\n Beginning Of Data\n"));
|
||
|
||
inx0 = 0;
|
||
|
||
while (inx0 < sizeRequired) {
|
||
|
||
inx1 = 0;
|
||
|
||
DebugPrint((1, "\t"));
|
||
|
||
while ((inx1 < 4) && ((inx0+inx1) < sizeRequired)) {
|
||
|
||
DebugPrint((1, "%02x ",
|
||
responseBuffer[inx0+inx1]));
|
||
|
||
inx1++;
|
||
|
||
}
|
||
|
||
DebugPrint((1, "\n"));
|
||
|
||
inx0 += 4;
|
||
}
|
||
|
||
DebugPrint((1, " End Of Data\n"));
|
||
}
|
||
|
||
} else if (length != 0) {
|
||
|
||
RtlCopyMemory((iScsiResponse + 1),
|
||
srb.DataBuffer,
|
||
length);
|
||
|
||
SetUlongInArray((iScsiResponse->Length),
|
||
length);
|
||
|
||
iScsiResponse->ResponseLength[0] = (UCHAR) (length & 0x0000FF00);
|
||
iScsiResponse->ResponseLength[1] = (UCHAR) (length & 0x000000FF);
|
||
|
||
}
|
||
|
||
status = iSpSendData(iScsiConnection->ConnectionDeviceObject,
|
||
iScsiConnection->ConnectionFileObject,
|
||
iScsiResponse,
|
||
sizeRequired,
|
||
&bytesSent);
|
||
if (NT_SUCCESS(status)) {
|
||
DebugPrint((3,
|
||
"Successfully sent SCSI Response. Bytes sent : %d\n",
|
||
bytesSent));
|
||
} else {
|
||
DebugPrint((0, "Failed to send SCSI response. Status : 0x%x\n",
|
||
status));
|
||
}
|
||
}
|
||
|
||
if (irp->MdlAddress) {
|
||
MmUnlockPages(irp->MdlAddress);
|
||
|
||
IoFreeMdl(irp->MdlAddress);
|
||
}
|
||
|
||
IoFreeIrp( irp );
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
iSpSendSrbSynchronousCompletion(
|
||
PDEVICE_OBJECT DeviceObject,
|
||
PIRP Irp,
|
||
PVOID Context
|
||
)
|
||
{
|
||
*(Irp->UserIosb) = Irp->IoStatus;
|
||
|
||
//
|
||
// Signal the caller's event.
|
||
//
|
||
|
||
KeSetEvent(Irp->UserEvent, IO_NO_INCREMENT, FALSE);
|
||
|
||
return STATUS_MORE_PROCESSING_REQUIRED;
|
||
}
|
||
|
||
|
||
UCHAR
|
||
GetCdbLength(
|
||
IN UCHAR OpCode
|
||
)
|
||
{
|
||
UCHAR commandGroup;
|
||
|
||
commandGroup = (OpCode >> 5) & 0x07;
|
||
DebugPrint((3, "Command Group - %d\n", commandGroup));
|
||
|
||
switch (commandGroup) {
|
||
case COMMAND_GROUP_0: {
|
||
return CDB6GENERIC_LENGTH;
|
||
}
|
||
|
||
case COMMAND_GROUP_1:
|
||
case COMMAND_GROUP_2: {
|
||
return CDB10GENERIC_LENGTH;
|
||
}
|
||
|
||
case COMMAND_GROUP_5: {
|
||
return CDB12GENERIC_LENGTH;
|
||
}
|
||
|
||
default: {
|
||
ASSERTMSG("Unknown CDB Opcode type\n",
|
||
FALSE);
|
||
}
|
||
} // switch (commandGroup)
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
ULONG
|
||
iSpGetActiveClientRequestIndex(
|
||
IN PISCSI_CONNECTION IScsiConnection,
|
||
IN PISCSI_SCSI_COMMAND IScsiCommand
|
||
)
|
||
{
|
||
ULONG cmdRefNum;
|
||
ULONG expCmdRefNum;
|
||
ULONG inx;
|
||
|
||
GetUlongFromArray((IScsiCommand->CmdRN),
|
||
cmdRefNum);
|
||
|
||
expCmdRefNum = IScsiConnection->ExpCommandRefNum;
|
||
|
||
if ((cmdRefNum < expCmdRefNum) ||
|
||
(cmdRefNum >= (expCmdRefNum + MAX_PENDING_REQUESTS))) {
|
||
|
||
DebugPrint((0, "Unexpected Command Ref Num : %d",
|
||
cmdRefNum));
|
||
ASSERT(FALSE);
|
||
}
|
||
|
||
inx = cmdRefNum % MAX_PENDING_REQUESTS;
|
||
if (inx == 0) {
|
||
inx = MAX_PENDING_REQUESTS;
|
||
}
|
||
|
||
DebugPrint((3, "Will copy request to slot %d\n", inx));
|
||
|
||
return inx;
|
||
}
|
||
|
||
|
||
|