/*++ 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; }