From af1a266670d040d2f4083ff309d732d648afba2a Mon Sep 17 00:00:00 2001 From: Angelos Mouzakitis Date: Tue, 10 Oct 2023 14:33:42 +0000 Subject: Add submodule dependency files Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec --- roms/edk2/NetworkPkg/IScsiDxe/IScsiProto.c | 3180 ++++++++++++++++++++++++++++ 1 file changed, 3180 insertions(+) create mode 100644 roms/edk2/NetworkPkg/IScsiDxe/IScsiProto.c (limited to 'roms/edk2/NetworkPkg/IScsiDxe/IScsiProto.c') diff --git a/roms/edk2/NetworkPkg/IScsiDxe/IScsiProto.c b/roms/edk2/NetworkPkg/IScsiDxe/IScsiProto.c new file mode 100644 index 000000000..6983f0fa5 --- /dev/null +++ b/roms/edk2/NetworkPkg/IScsiDxe/IScsiProto.c @@ -0,0 +1,3180 @@ +/** @file + The implementation of iSCSI protocol based on RFC3720. + +Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "IScsiImpl.h" + +UINT32 mDataSegPad = 0; + +/** + Attach the iSCSI connection to the iSCSI session. + + @param[in, out] Session The iSCSI session. + @param[in, out] Conn The iSCSI connection. + +**/ +VOID +IScsiAttatchConnection ( + IN OUT ISCSI_SESSION *Session, + IN OUT ISCSI_CONNECTION *Conn + ) +{ + InsertTailList (&Session->Conns, &Conn->Link); + Conn->Session = Session; + Session->NumConns++; +} + +/** + Detach the iSCSI connection from the session it belongs to. + + @param[in, out] Conn The iSCSI connection. + +**/ +VOID +IScsiDetatchConnection ( + IN OUT ISCSI_CONNECTION *Conn + ) +{ + RemoveEntryList (&Conn->Link); + Conn->Session->NumConns--; + Conn->Session = NULL; +} + + +/** + Check the sequence number according to RFC3720. + + @param[in, out] ExpSN The currently expected sequence number. + @param[in] NewSN The sequence number to check. + + @retval EFI_SUCCESS The check passed and the ExpSN is increased. + @retval EFI_NOT_READY Response was sent due to a retransmission request. + @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol error occurred. + +**/ +EFI_STATUS +IScsiCheckSN ( + IN OUT UINT32 *ExpSN, + IN UINT32 NewSN + ) +{ + if (!ISCSI_SEQ_EQ (NewSN, *ExpSN)) { + if (ISCSI_SEQ_LT (NewSN, *ExpSN)) { + // + // Duplicate + // + return EFI_NOT_READY; + } else { + return EFI_PROTOCOL_ERROR; + } + } else { + // + // Advance the ExpSN + // + (*ExpSN)++; + return EFI_SUCCESS; + } +} + + +/** + Update the sequence numbers for the iSCSI command. + + @param[in, out] Session The iSCSI session. + @param[in] MaxCmdSN Maximum CmdSN from the target. + @param[in] ExpCmdSN Next expected CmdSN from the target. + +**/ +VOID +IScsiUpdateCmdSN ( + IN OUT ISCSI_SESSION *Session, + IN UINT32 MaxCmdSN, + IN UINT32 ExpCmdSN + ) +{ + if (ISCSI_SEQ_LT (MaxCmdSN, ExpCmdSN - 1)) { + return ; + } + + if (ISCSI_SEQ_GT (MaxCmdSN, Session->MaxCmdSN)) { + Session->MaxCmdSN = MaxCmdSN; + } + + if (ISCSI_SEQ_GT (ExpCmdSN, Session->ExpCmdSN)) { + Session->ExpCmdSN = ExpCmdSN; + } +} + + +/** + This function does the iSCSI connection login. + + @param[in, out] Conn The iSCSI connection to login. + @param Timeout The timeout value in millisecond. + + @retval EFI_SUCCESS The iSCSI connection is logged into the iSCSI target. + @retval EFI_TIMEOUT Timeout occurred during the login procedure. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiConnLogin ( + IN OUT ISCSI_CONNECTION *Conn, + IN UINT16 Timeout + ) +{ + EFI_STATUS Status; + + // + // Start the timer, and wait Timeout seconds to establish the TCP connection. + // + Status = gBS->SetTimer ( + Conn->TimeoutEvent, + TimerRelative, + MultU64x32 (Timeout, TICKS_PER_MS) + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Try to establish the tcp connection. + // + Status = TcpIoConnect (&Conn->TcpIo, Conn->TimeoutEvent); + gBS->SetTimer (Conn->TimeoutEvent, TimerCancel, 0); + + if (EFI_ERROR (Status)) { + return Status; + } + + Conn->State = CONN_STATE_IN_LOGIN; + + // + // Connection is established, start the iSCSI Login. + // + do { + Status = IScsiSendLoginReq (Conn); + if (EFI_ERROR (Status)) { + break; + } + + Status = IScsiReceiveLoginRsp (Conn); + if (EFI_ERROR (Status)) { + break; + } + } while (Conn->CurrentStage != ISCSI_FULL_FEATURE_PHASE); + + return Status; +} + + +/** + Reset the iSCSI connection. + + @param[in, out] Conn The iSCSI connection to reset. + +**/ +VOID +IScsiConnReset ( + IN OUT ISCSI_CONNECTION *Conn + ) +{ + TcpIoReset (&Conn->TcpIo); +} + + +/** + Create a TCP connection for the iSCSI session. + + @param[in] Session Points to the iSCSI session. + + @return The newly created iSCSI connection. + +**/ +ISCSI_CONNECTION * +IScsiCreateConnection ( + IN ISCSI_SESSION *Session + ) +{ + ISCSI_DRIVER_DATA *Private; + ISCSI_SESSION_CONFIG_NVDATA *NvData; + ISCSI_CONNECTION *Conn; + TCP_IO_CONFIG_DATA TcpIoConfig; + TCP4_IO_CONFIG_DATA *Tcp4IoConfig; + TCP6_IO_CONFIG_DATA *Tcp6IoConfig; + EFI_STATUS Status; + + Private = Session->Private; + NvData = &Session->ConfigData->SessionConfigData; + + Conn = AllocateZeroPool (sizeof (ISCSI_CONNECTION)); + if (Conn == NULL) { + return NULL; + } + + Conn->Signature = ISCSI_CONNECTION_SIGNATURE; + Conn->State = CONN_STATE_FREE; + Conn->CurrentStage = ISCSI_SECURITY_NEGOTIATION; + Conn->NextStage = ISCSI_LOGIN_OPERATIONAL_NEGOTIATION; + Conn->AuthStep = ISCSI_AUTH_INITIAL; + Conn->ExpStatSN = 0; + Conn->PartialReqSent = FALSE; + Conn->PartialRspRcvd = FALSE; + Conn->ParamNegotiated = FALSE; + Conn->Cid = Session->NextCid++; + Conn->Ipv6Flag = NvData->IpMode == IP_MODE_IP6 || Session->ConfigData->AutoConfigureMode == IP_MODE_AUTOCONFIG_IP6; + + Status = gBS->CreateEvent ( + EVT_TIMER, + TPL_CALLBACK, + NULL, + NULL, + &Conn->TimeoutEvent + ); + if (EFI_ERROR (Status)) { + FreePool (Conn); + return NULL; + } + + NetbufQueInit (&Conn->RspQue); + + // + // Set the default connection-only parameters. + // + Conn->MaxRecvDataSegmentLength = DEFAULT_MAX_RECV_DATA_SEG_LEN; + Conn->HeaderDigest = IScsiDigestNone; + Conn->DataDigest = IScsiDigestNone; + + if (NvData->DnsMode) { + // + // perform dns process if target address expressed by domain name. + // + if (!Conn->Ipv6Flag) { + Status = IScsiDns4 (Private->Image, Private->Controller, NvData); + } else { + Status = IScsiDns6 (Private->Image, Private->Controller, NvData); + } + + if (EFI_ERROR(Status)) { + DEBUG ((EFI_D_ERROR, "The configuration of Target address or DNS server address is invalid!\n")); + FreePool (Conn); + return NULL; + } + } + + if (!Conn->Ipv6Flag) { + Tcp4IoConfig = &TcpIoConfig.Tcp4IoConfigData; + + CopyMem (&Tcp4IoConfig->LocalIp, &NvData->LocalIp, sizeof (EFI_IPv4_ADDRESS)); + CopyMem (&Tcp4IoConfig->SubnetMask, &NvData->SubnetMask, sizeof (EFI_IPv4_ADDRESS)); + CopyMem (&Tcp4IoConfig->Gateway, &NvData->Gateway, sizeof (EFI_IPv4_ADDRESS)); + CopyMem (&Tcp4IoConfig->RemoteIp, &NvData->TargetIp, sizeof (EFI_IPv4_ADDRESS)); + + Tcp4IoConfig->RemotePort = NvData->TargetPort; + Tcp4IoConfig->ActiveFlag = TRUE; + Tcp4IoConfig->StationPort = 0; + } else { + Tcp6IoConfig = &TcpIoConfig.Tcp6IoConfigData; + + CopyMem (&Tcp6IoConfig->RemoteIp, &NvData->TargetIp, sizeof (EFI_IPv6_ADDRESS)); + Tcp6IoConfig->RemotePort = NvData->TargetPort; + Tcp6IoConfig->ActiveFlag = TRUE; + Tcp6IoConfig->StationPort = 0; + } + + // + // Create the TCP IO for this connection. + // + Status = TcpIoCreateSocket ( + Private->Image, + Private->Controller, + (UINT8) (!Conn->Ipv6Flag ? TCP_VERSION_4: TCP_VERSION_6), + &TcpIoConfig, + &Conn->TcpIo + ); + if (EFI_ERROR (Status)) { + gBS->CloseEvent (Conn->TimeoutEvent); + FreePool (Conn); + Conn = NULL; + } + + return Conn; +} + + +/** + Destroy an iSCSI connection. + + @param[in] Conn The connection to destroy. + +**/ +VOID +IScsiDestroyConnection ( + IN ISCSI_CONNECTION *Conn + ) +{ + TcpIoDestroySocket (&Conn->TcpIo); + + NetbufQueFlush (&Conn->RspQue); + gBS->CloseEvent (Conn->TimeoutEvent); + FreePool (Conn); +} + +/** + Retrieve the IPv6 Address/Prefix/Gateway from the established TCP connection, these informations + will be filled in the iSCSI Boot Firmware Table. + + @param[in] Conn The connection used in the iSCSI login phase. + + @retval EFI_SUCCESS Get the NIC information successfully. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiGetIp6NicInfo ( + IN ISCSI_CONNECTION *Conn + ) +{ + ISCSI_SESSION_CONFIG_NVDATA *NvData; + EFI_TCP6_PROTOCOL *Tcp6; + EFI_IP6_MODE_DATA Ip6ModeData; + EFI_STATUS Status; + EFI_IPv6_ADDRESS *TargetIp; + UINTN Index; + UINT8 SubnetPrefixLength; + UINTN RouteEntry; + + NvData = &Conn->Session->ConfigData->SessionConfigData; + TargetIp = &NvData->TargetIp.v6; + Tcp6 = Conn->TcpIo.Tcp.Tcp6; + + ZeroMem (&Ip6ModeData, sizeof (EFI_IP6_MODE_DATA)); + Status = Tcp6->GetModeData ( + Tcp6, + NULL, + NULL, + &Ip6ModeData, + NULL, + NULL + ); + if (EFI_ERROR (Status)) { + return Status; + } + + if (!Ip6ModeData.IsConfigured) { + Status = EFI_ABORTED; + goto ON_EXIT; + } + + IP6_COPY_ADDRESS (&NvData->LocalIp, &Ip6ModeData.ConfigData.StationAddress); + + NvData->PrefixLength = 0; + for (Index = 0; Index < Ip6ModeData.AddressCount; Index++) { + if (EFI_IP6_EQUAL (&NvData->LocalIp.v6, &Ip6ModeData.AddressList[Index].Address)) { + NvData->PrefixLength = Ip6ModeData.AddressList[Index].PrefixLength; + break; + } + } + + SubnetPrefixLength = 0; + RouteEntry = Ip6ModeData.RouteCount; + for (Index = 0; Index < Ip6ModeData.RouteCount; Index++) { + if (NetIp6IsNetEqual (TargetIp, &Ip6ModeData.RouteTable[Index].Destination, Ip6ModeData.RouteTable[Index].PrefixLength)) { + if (SubnetPrefixLength < Ip6ModeData.RouteTable[Index].PrefixLength) { + SubnetPrefixLength = Ip6ModeData.RouteTable[Index].PrefixLength; + RouteEntry = Index; + } + } + } + if (RouteEntry != Ip6ModeData.RouteCount) { + IP6_COPY_ADDRESS (&NvData->Gateway, &Ip6ModeData.RouteTable[RouteEntry].Gateway); + } + +ON_EXIT: + if (Ip6ModeData.AddressList != NULL) { + FreePool (Ip6ModeData.AddressList); + } + if (Ip6ModeData.GroupTable!= NULL) { + FreePool (Ip6ModeData.GroupTable); + } + if (Ip6ModeData.RouteTable!= NULL) { + FreePool (Ip6ModeData.RouteTable); + } + if (Ip6ModeData.NeighborCache!= NULL) { + FreePool (Ip6ModeData.NeighborCache); + } + if (Ip6ModeData.PrefixTable!= NULL) { + FreePool (Ip6ModeData.PrefixTable); + } + if (Ip6ModeData.IcmpTypeList!= NULL) { + FreePool (Ip6ModeData.IcmpTypeList); + } + + return Status; +} + +/** + Login the iSCSI session. + + @param[in] Session The iSCSI session. + + @retval EFI_SUCCESS The iSCSI session login procedure finished. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. + @retval EFI_NO_MEDIA There was a media error. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiSessionLogin ( + IN ISCSI_SESSION *Session + ) +{ + EFI_STATUS Status; + ISCSI_CONNECTION *Conn; + VOID *Tcp; + EFI_GUID *ProtocolGuid; + UINT8 RetryCount; + EFI_STATUS MediaStatus; + + // + // Check media status before session login. + // + MediaStatus = EFI_SUCCESS; + NetLibDetectMediaWaitTimeout (Session->Private->Controller, ISCSI_CHECK_MEDIA_LOGIN_WAITING_TIME, &MediaStatus); + if (MediaStatus != EFI_SUCCESS) { + return EFI_NO_MEDIA; + } + + // + // Set session identifier + // + CopyMem (Session->Isid, Session->ConfigData->SessionConfigData.IsId, 6); + + RetryCount = 0; + + do { + // + // Create a connection for the session. + // + Conn = IScsiCreateConnection (Session); + if (Conn == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + IScsiAttatchConnection (Session, Conn); + + // + // Login through the newly created connection. + // + Status = IScsiConnLogin (Conn, Session->ConfigData->SessionConfigData.ConnectTimeout); + if (EFI_ERROR (Status)) { + IScsiConnReset (Conn); + IScsiDetatchConnection (Conn); + IScsiDestroyConnection (Conn); + } + + if (Status != EFI_TIMEOUT) { + break; + } + + RetryCount++; + } while (RetryCount <= Session->ConfigData->SessionConfigData.ConnectRetryCount); + + if (!EFI_ERROR (Status)) { + Session->State = SESSION_STATE_LOGGED_IN; + + if (!Conn->Ipv6Flag) { + ProtocolGuid = &gEfiTcp4ProtocolGuid; + } else { + ProtocolGuid = &gEfiTcp6ProtocolGuid; + } + + Status = gBS->OpenProtocol ( + Conn->TcpIo.Handle, + ProtocolGuid, + (VOID **) &Tcp, + Session->Private->Image, + Session->Private->ExtScsiPassThruHandle, + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER + ); + + ASSERT_EFI_ERROR (Status); + + if (Conn->Ipv6Flag) { + Status = IScsiGetIp6NicInfo (Conn); + } + } + + return Status; +} + + +/** + Wait for IPsec negotiation, then try to login the iSCSI session again. + + @param[in] Session The iSCSI session. + + @retval EFI_SUCCESS The iSCSI session login procedure finished. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. + @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol error occurred. + +**/ +EFI_STATUS +IScsiSessionReLogin ( + IN ISCSI_SESSION *Session + ) +{ + + EFI_STATUS Status; + EFI_STATUS TimerStatus; + EFI_EVENT Timer; + + Status = gBS->CreateEvent (EVT_TIMER, TPL_CALLBACK, NULL, NULL, &Timer); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = gBS->SetTimer ( + Timer, + TimerRelative, + ISCSI_WAIT_IPSEC_TIMEOUT + ); + + if (EFI_ERROR (Status)) { + gBS->CloseEvent (Timer); + return Status; + } + + do { + + TimerStatus = gBS->CheckEvent (Timer); + + if (!EFI_ERROR (TimerStatus)) { + Status = IScsiSessionLogin (Session); + } + + } while (TimerStatus == EFI_NOT_READY); + + gBS->CloseEvent (Timer); + return Status; +} + + +/** + Build and send the iSCSI login request to the iSCSI target according to + the current login stage. + + @param[in] Conn The connection in the iSCSI login phase. + + @retval EFI_SUCCESS The iSCSI login request PDU is built and sent on this + connection. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. + @retval EFI_DEVICE_ERROR Some kind of device error occurred. + +**/ +EFI_STATUS +IScsiSendLoginReq ( + IN ISCSI_CONNECTION *Conn + ) +{ + NET_BUF *Pdu; + EFI_STATUS Status; + + // + // Build the Login Request PDU. + // + Pdu = IScsiPrepareLoginReq (Conn); + if (Pdu == NULL) { + return EFI_DEVICE_ERROR; + } + // + // Send it to the iSCSI target. + // + Status = TcpIoTransmit (&Conn->TcpIo, Pdu); + + NetbufFree (Pdu); + + return Status; +} + + +/** + Receive and process the iSCSI login response. + + @param[in] Conn The connection in the iSCSI login phase. + + @retval EFI_SUCCESS The iSCSI login response PDU is received and processed. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiReceiveLoginRsp ( + IN ISCSI_CONNECTION *Conn + ) +{ + EFI_STATUS Status; + NET_BUF *Pdu; + + Pdu = NULL; + + // + // Receive the iSCSI login response. + // + Status = IScsiReceivePdu (Conn, &Pdu, NULL, FALSE, FALSE, NULL); + if (EFI_ERROR (Status)) { + return Status; + } + ASSERT (Pdu != NULL); + + // + // A Login Response is received; process it. + // + Status = IScsiProcessLoginRsp (Conn, Pdu); + + NetbufFree (Pdu); + + return Status; +} + + +/** + Add an iSCSI key-value pair as a string into the data segment of the Login Request PDU. + The DataSegmentLength and the actual size of the net buffer containing this PDU will be + updated. + + @param[in, out] Pdu The iSCSI PDU whose data segment the key-value pair will + be added to. + @param[in] Key The key name string. + @param[in] Value The value string. + + @retval EFI_SUCCESS The key-value pair is added to the PDU's data segment and + the correspondence length fields are updated. + @retval EFI_OUT_OF_RESOURCES There is not enough space in the PDU to add the key-value + pair. + @retval EFI_PROTOCOL_ERROR There is no such data in the net buffer. +**/ +EFI_STATUS +IScsiAddKeyValuePair ( + IN OUT NET_BUF *Pdu, + IN CHAR8 *Key, + IN CHAR8 *Value + ) +{ + UINT32 DataSegLen; + UINT32 KeyLen; + UINT32 ValueLen; + UINT32 TotalLen; + ISCSI_LOGIN_REQUEST *LoginReq; + CHAR8 *Data; + + LoginReq = (ISCSI_LOGIN_REQUEST *) NetbufGetByte (Pdu, 0, NULL); + if (LoginReq == NULL) { + return EFI_PROTOCOL_ERROR; + } + DataSegLen = NTOH24 (LoginReq->DataSegmentLength); + + KeyLen = (UINT32) AsciiStrLen (Key); + ValueLen = (UINT32) AsciiStrLen (Value); + + // + // 1 byte for the key value separator '=' and 1 byte for the null + // delimiter after the value. + // + TotalLen = KeyLen + 1 + ValueLen + 1; + + // + // Allocate the space for the key-value pair. + // + Data = (CHAR8 *) NetbufAllocSpace (Pdu, TotalLen, NET_BUF_TAIL); + if (Data == NULL) { + return EFI_OUT_OF_RESOURCES; + } + // + // Add the key. + // + CopyMem (Data, Key, KeyLen); + Data += KeyLen; + + *Data = '='; + Data++; + + // + // Add the value. + // + CopyMem (Data, Value, ValueLen); + Data += ValueLen; + + *Data = '\0'; + + // + // Update the DataSegmentLength + // + ISCSI_SET_DATASEG_LEN (LoginReq, DataSegLen + TotalLen); + + return EFI_SUCCESS; +} + + +/** + Prepare the iSCSI login request to be sent according to the current login status. + + @param[in, out] Conn The connection in the iSCSI login phase. + + @return The pointer to the net buffer containing the iSCSI login request built. + @retval NULL Other errors as indicated. + +**/ +NET_BUF * +IScsiPrepareLoginReq ( + IN OUT ISCSI_CONNECTION *Conn + ) +{ + ISCSI_SESSION *Session; + NET_BUF *Nbuf; + ISCSI_LOGIN_REQUEST *LoginReq; + EFI_STATUS Status; + + Session = Conn->Session; + + Nbuf = NetbufAlloc (sizeof (ISCSI_LOGIN_REQUEST) + DEFAULT_MAX_RECV_DATA_SEG_LEN); + if (Nbuf == NULL) { + return NULL; + } + + LoginReq = (ISCSI_LOGIN_REQUEST *) NetbufAllocSpace (Nbuf, sizeof (ISCSI_LOGIN_REQUEST), NET_BUF_TAIL); + if (LoginReq == NULL) { + NetbufFree (Nbuf); + return NULL; + } + ZeroMem (LoginReq, sizeof (ISCSI_LOGIN_REQUEST)); + + // + // Init the login request pdu + // + ISCSI_SET_OPCODE (LoginReq, ISCSI_OPCODE_LOGIN_REQ, ISCSI_REQ_IMMEDIATE); + ISCSI_SET_STAGES (LoginReq, Conn->CurrentStage, Conn->NextStage); + LoginReq->VersionMax = ISCSI_VERSION_MAX; + LoginReq->VersionMin = ISCSI_VERSION_MIN; + LoginReq->Tsih = HTONS (Session->Tsih); + LoginReq->InitiatorTaskTag = HTONL (Session->InitiatorTaskTag); + LoginReq->Cid = HTONS (Conn->Cid); + LoginReq->CmdSN = HTONL (Session->CmdSN); + + // + // For the first Login Request on a connection this is ExpStatSN for the + // old connection, and this field is only valid if the Login Request restarts + // a connection. + // For subsequent Login Requests it is used to acknowledge the Login Responses + // with their increasing StatSN values. + // + LoginReq->ExpStatSN = HTONL (Conn->ExpStatSN); + CopyMem (LoginReq->Isid, Session->Isid, sizeof (LoginReq->Isid)); + + if (Conn->PartialRspRcvd) { + // + // A partial response. The initiator must send an empty Login Request. + // + return Nbuf; + } + + Status = EFI_SUCCESS; + + switch (Conn->CurrentStage) { + case ISCSI_SECURITY_NEGOTIATION: + // + // Both none authentication and CHAP authentication share the CHAP path. + // + // + if (Session->AuthType != ISCSI_AUTH_TYPE_KRB) { + Status = IScsiCHAPToSendReq (Conn, Nbuf); + } + + break; + + case ISCSI_LOGIN_OPERATIONAL_NEGOTIATION: + // + // Only negotiate the parameter once. + // + if (!Conn->ParamNegotiated) { + IScsiFillOpParams (Conn, Nbuf); + } + + ISCSI_SET_FLAG (LoginReq, ISCSI_LOGIN_REQ_PDU_FLAG_TRANSIT); + break; + + default: + // + // An error occurs... + // + Status = EFI_DEVICE_ERROR; + break; + } + + if (EFI_ERROR (Status)) { + NetbufFree (Nbuf); + Nbuf = NULL; + } else { + // + // Pad the data segment if needed. + // + IScsiPadSegment (Nbuf, ISCSI_GET_DATASEG_LEN (LoginReq)); + // + // Check whether we will issue the stage transition signal? + // + Conn->TransitInitiated = ISCSI_FLAG_ON (LoginReq, ISCSI_LOGIN_REQ_PDU_FLAG_TRANSIT); + } + + return Nbuf; +} + + +/** + Process the iSCSI Login Response. + + @param[in, out] Conn The connection on which the iSCSI login response is received. + @param[in, out] Pdu The iSCSI login response PDU. + + @retval EFI_SUCCESS The iSCSI login response PDU is processed, and all checks are passed. + @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol error occurred. + @retval EFI_MEDIA_CHANGED Target is redirected. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiProcessLoginRsp ( + IN OUT ISCSI_CONNECTION *Conn, + IN OUT NET_BUF *Pdu + ) +{ + EFI_STATUS Status; + ISCSI_SESSION *Session; + ISCSI_LOGIN_RESPONSE *LoginRsp; + BOOLEAN Transit; + BOOLEAN Continue; + UINT8 CurrentStage; + UINT8 NextStage; + UINT8 *DataSeg; + UINT32 DataSegLen; + + Status = EFI_SUCCESS; + Session = Conn->Session; + + LoginRsp = (ISCSI_LOGIN_RESPONSE *) NetbufGetByte (Pdu, 0, NULL); + if (LoginRsp == NULL) { + return EFI_PROTOCOL_ERROR; + } + if (!ISCSI_CHECK_OPCODE (LoginRsp, ISCSI_OPCODE_LOGIN_RSP)) { + // + // It is not a Login Response. + // + return EFI_PROTOCOL_ERROR; + } + // + // Get the data segment, if any. + // + DataSegLen = ISCSI_GET_DATASEG_LEN (LoginRsp); + if (DataSegLen != 0) { + DataSeg = NetbufGetByte (Pdu, sizeof (ISCSI_LOGIN_RESPONSE), NULL); + } else { + DataSeg = NULL; + } + // + // Check the status class in the login response PDU. + // + switch (LoginRsp->StatusClass) { + case ISCSI_LOGIN_STATUS_SUCCESS: + // + // Just break here; the response and the data segment will be processed later. + // + break; + + case ISCSI_LOGIN_STATUS_REDIRECTION: + // + // The target may be moved to a different address. + // + if (DataSeg == NULL) { + return EFI_PROTOCOL_ERROR; + } + // + // Process the TargetAddress key-value strings in the data segment to update the + // target address info. + // + Status = IScsiUpdateTargetAddress (Session, (CHAR8 *) DataSeg, DataSegLen); + if (EFI_ERROR (Status)) { + return Status; + } + // + // Session will be restarted on this error status because the Target is + // redirected by this Login Response. + // + return EFI_MEDIA_CHANGED; + + default: + // + // Initiator Error, Target Error, or any other undefined error code. + // + return EFI_PROTOCOL_ERROR; + } + // + // The status is success; extract the wanted fields from the header segment. + // + Transit = ISCSI_FLAG_ON (LoginRsp, ISCSI_LOGIN_RSP_PDU_FLAG_TRANSIT); + Continue = ISCSI_FLAG_ON (LoginRsp, ISCSI_LOGIN_RSP_PDU_FLAG_CONTINUE); + + CurrentStage = ISCSI_GET_CURRENT_STAGE (LoginRsp); + NextStage = ISCSI_GET_NEXT_STAGE (LoginRsp); + + LoginRsp->InitiatorTaskTag = NTOHL (LoginRsp->InitiatorTaskTag); + + if ((Transit && Continue) || + (CurrentStage != Conn->CurrentStage) || + (!Conn->TransitInitiated && Transit) || + (Transit && (NextStage != Conn->NextStage)) || + (CompareMem (Session->Isid, LoginRsp->Isid, sizeof (LoginRsp->Isid)) != 0) || + (LoginRsp->InitiatorTaskTag != Session->InitiatorTaskTag) + ) { + // + // A Login Response with the C bit set to 1 MUST have the T bit set to 0. + // The CSG in the Login Response MUST be the same with the I-end of this connection. + // The T bit can't be 1 if the last Login Response sent by the initiator doesn't + // initiate the transition. + // The NSG MUST be the same with the I-end of this connection if Transit is required. + // The ISID in the Login Response MUST be the same with this session. + // + return EFI_PROTOCOL_ERROR; + } + + LoginRsp->StatSN = NTOHL (LoginRsp->StatSN); + LoginRsp->ExpCmdSN = NTOHL (LoginRsp->ExpCmdSN); + LoginRsp->MaxCmdSN = NTOHL (LoginRsp->MaxCmdSN); + + if ((Conn->CurrentStage == ISCSI_SECURITY_NEGOTIATION) && (Conn->AuthStep == ISCSI_AUTH_INITIAL)) { + // + // If the Login Request is a leading Login Request, the target MUST use + // the value presented in CmdSN as the target value for ExpCmdSN. + // + if ((Session->State == SESSION_STATE_FREE) && (Session->CmdSN != LoginRsp->ExpCmdSN)) { + return EFI_PROTOCOL_ERROR; + } + + // + // It's the initial Login Response, initialize the local ExpStatSN, MaxCmdSN + // and ExpCmdSN. + // + Conn->ExpStatSN = LoginRsp->StatSN + 1; + Session->MaxCmdSN = LoginRsp->MaxCmdSN; + Session->ExpCmdSN = LoginRsp->ExpCmdSN; + } else { + // + // Check the StatSN of this PDU. + // + Status = IScsiCheckSN (&Conn->ExpStatSN, LoginRsp->StatSN); + if (!EFI_ERROR (Status)) { + // + // Update the MaxCmdSN and ExpCmdSN. + // + IScsiUpdateCmdSN (Session, LoginRsp->MaxCmdSN, LoginRsp->ExpCmdSN); + } else { + return Status; + } + } + // + // Trim off the header segment. + // + NetbufTrim (Pdu, sizeof (ISCSI_LOGIN_RESPONSE), NET_BUF_HEAD); + + // + // Queue this login response first in case it's a partial response so that + // later when the full response list is received we can combine these scattered + // responses' data segment and then process it. + // + NET_GET_REF (Pdu); + NetbufQueAppend (&Conn->RspQue, Pdu); + + Conn->PartialRspRcvd = Continue; + if (Continue) { + // + // It is a partial response; must wait for another or more Request/Response + // conversations to get the full response. + // + return EFI_SUCCESS; + } + + switch (CurrentStage) { + case ISCSI_SECURITY_NEGOTIATION: + // + // In security negotiation stage, let CHAP module handle it. + // + if (Session->AuthType != ISCSI_AUTH_TYPE_KRB) { + Status = IScsiCHAPOnRspReceived (Conn); + } + break; + + case ISCSI_LOGIN_OPERATIONAL_NEGOTIATION: + // + // Response received with negotiation response on iSCSI parameters: check them. + // + Status = IScsiCheckOpParams (Conn); + if (!EFI_ERROR (Status)) { + Conn->ParamNegotiated = TRUE; + } + + break; + + default: + // + // Should never get here. + // + Status = EFI_PROTOCOL_ERROR; + break; + } + + if (Transit && (Status == EFI_SUCCESS)) { + // + // Do the state transition. + // + Conn->CurrentStage = Conn->NextStage; + + if (Conn->CurrentStage == ISCSI_LOGIN_OPERATIONAL_NEGOTIATION) { + Conn->NextStage = ISCSI_FULL_FEATURE_PHASE; + } else { + // + // CurrentStage is iSCSI Full Feature. It is the Login-Final Response; + // get the TSIH from the Login Response. + // + Session->Tsih = NTOHS (LoginRsp->Tsih); + } + } + // + // Flush the response(s) received. + // + NetbufQueFlush (&Conn->RspQue); + + return Status; +} + + +/** + Updated the target information according the data received in the iSCSI + login response with an target redirection status. + + @param[in, out] Session The iSCSI session. + @param[in] Data The data segment that should contain the + TargetAddress key-value list. + @param[in] Len Length of the data. + + @retval EFI_SUCCESS The target address is updated. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. + @retval EFI_NOT_FOUND The TargetAddress key is not found. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiUpdateTargetAddress ( + IN OUT ISCSI_SESSION *Session, + IN CHAR8 *Data, + IN UINT32 Len + ) +{ + LIST_ENTRY *KeyValueList; + CHAR8 *TargetAddress; + CHAR8 *IpStr; + EFI_STATUS Status; + UINTN Number; + UINT8 IpMode; + ISCSI_SESSION_CONFIG_NVDATA *NvData; + + KeyValueList = IScsiBuildKeyValueList (Data, Len); + if (KeyValueList == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = EFI_NOT_FOUND; + NvData = &Session->ConfigData->SessionConfigData; + + while (TRUE) { + TargetAddress = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_TARGET_ADDRESS); + if (TargetAddress == NULL) { + break; + } + + // + // RFC 3720 defines format of the TargetAddress=domainname[:port][,portal-group-tag] + // The domainname can be specified as either a DNS host name, adotted-decimal IPv4 address, + // or a bracketed IPv6 address as specified in [RFC2732]. + // + if (NET_IS_DIGIT (TargetAddress[0])) { + // + // The domainname of the target is presented in a dotted-decimal IPv4 address format. + // + IpStr = TargetAddress; + + while ((*TargetAddress != '\0') && (*TargetAddress != ':') && (*TargetAddress != ',')) { + // + // NULL, ':', or ',' ends the IPv4 string. + // + TargetAddress++; + } + } else if (*TargetAddress == ISCSI_REDIRECT_ADDR_START_DELIMITER){ + // + // The domainname of the target is presented in a bracketed IPv6 address format. + // + TargetAddress ++; + IpStr = TargetAddress; + while ((*TargetAddress != '\0') && (*TargetAddress != ISCSI_REDIRECT_ADDR_END_DELIMITER)) { + // + // ']' ends the IPv6 string. + // + TargetAddress++; + } + + if (*TargetAddress != ISCSI_REDIRECT_ADDR_END_DELIMITER) { + continue; + } + + *TargetAddress = '\0'; + TargetAddress ++; + + } else { + // + // The domainname of the target is presented in the format of a DNS host name. + // + IpStr = TargetAddress; + + while ((*TargetAddress != '\0') && (*TargetAddress != ':') && (*TargetAddress != ',')) { + TargetAddress++; + } + NvData->DnsMode = TRUE; + } + + // + // Save the original user setting which specifies the proxy/virtual iSCSI target. + // + NvData->OriginalTargetPort = NvData->TargetPort; + + if (*TargetAddress == ',') { + // + // Comma and the portal group tag MUST be omitted if the TargetAddress is sent + // as the result of a redirection. + // + continue; + } else if (*TargetAddress == ':') { + *TargetAddress = '\0'; + + TargetAddress++; + + Number = AsciiStrDecimalToUintn (TargetAddress); + if (Number > 0xFFFF) { + continue; + } else { + NvData->TargetPort = (UINT16) Number; + } + } else { + // + // The string only contains the Target address. Use the well-known port. + // + NvData->TargetPort = ISCSI_WELL_KNOWN_PORT; + } + + // + // Save the original user setting which specifies the proxy/virtual iSCSI target. + // + CopyMem (&NvData->OriginalTargetIp, &NvData->TargetIp, sizeof (EFI_IP_ADDRESS)); + + // + // Update the target IP address. + // + if (NvData->IpMode < IP_MODE_AUTOCONFIG) { + IpMode = NvData->IpMode; + } else { + IpMode = Session->ConfigData->AutoConfigureMode; + } + + if (NvData->DnsMode) { + // + // Target address is expressed as URL format, just save it and + // do DNS resolution when creating a TCP connection. + // + if (AsciiStrSize (IpStr) > sizeof (Session->ConfigData->SessionConfigData.TargetUrl)){ + return EFI_INVALID_PARAMETER; + } + CopyMem (&Session->ConfigData->SessionConfigData.TargetUrl, IpStr, AsciiStrSize (IpStr)); + } else { + Status = IScsiAsciiStrToIp ( + IpStr, + IpMode, + &Session->ConfigData->SessionConfigData.TargetIp + ); + + if (EFI_ERROR (Status)) { + continue; + } else { + NvData->RedirectFlag = TRUE; + break; + } + } + } + + IScsiFreeKeyValueList (KeyValueList); + + return Status; +} + + +/** + The callback function to free the net buffer list. + + @param[in] Arg The opaque parameter. + +**/ +VOID +EFIAPI +IScsiFreeNbufList ( + VOID *Arg + ) +{ + ASSERT (Arg != NULL); + + NetbufFreeList ((LIST_ENTRY *) Arg); + FreePool (Arg); +} + + +/** + The callback function called in NetBufFree; it does nothing. + + @param[in] Arg The opaque parameter. + +**/ +VOID +EFIAPI +IScsiNbufExtFree ( + VOID *Arg + ) +{ +} + + +/** + Receive an iSCSI response PDU. An iSCSI response PDU contains an iSCSI PDU header and + an optional data segment. The two parts will be put into two blocks of buffers in the + net buffer. The digest check will be conducted in this function if needed and the digests + will be trimmed from the PDU buffer. + + @param[in] Conn The iSCSI connection to receive data from. + @param[out] Pdu The received iSCSI pdu. + @param[in] Context The context used to describe information on the caller provided + buffer to receive data segment of the iSCSI pdu. It is optional. + @param[in] HeaderDigest Whether there will be header digest received. + @param[in] DataDigest Whether there will be data digest. + @param[in] TimeoutEvent The timeout event. It is optional. + + @retval EFI_SUCCESS An iSCSI pdu is received. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. + @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol error occurred. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiReceivePdu ( + IN ISCSI_CONNECTION *Conn, + OUT NET_BUF **Pdu, + IN ISCSI_IN_BUFFER_CONTEXT *Context, OPTIONAL + IN BOOLEAN HeaderDigest, + IN BOOLEAN DataDigest, + IN EFI_EVENT TimeoutEvent OPTIONAL + ) +{ + LIST_ENTRY *NbufList; + UINT32 Len; + NET_BUF *PduHdr; + UINT8 *Header; + EFI_STATUS Status; + UINT32 PadLen; + UINT32 InDataOffset; + NET_FRAGMENT Fragment[2]; + UINT32 FragmentCount; + NET_BUF *DataSeg; + UINT32 PadAndCRC32[2]; + + NbufList = AllocatePool (sizeof (LIST_ENTRY)); + if (NbufList == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + InitializeListHead (NbufList); + + // + // The header digest will be received together with the PDU header, if exists. + // + Len = sizeof (ISCSI_BASIC_HEADER) + (HeaderDigest ? sizeof (UINT32) : 0); + PduHdr = NetbufAlloc (Len); + if (PduHdr == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + Header = NetbufAllocSpace (PduHdr, Len, NET_BUF_TAIL); + if (Header == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + InsertTailList (NbufList, &PduHdr->List); + + // + // First step, receive the BHS of the PDU. + // + Status = TcpIoReceive (&Conn->TcpIo, PduHdr, FALSE, TimeoutEvent); + + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + if (HeaderDigest) { + // + // TODO: check the header-digest. + // + // + // Trim off the digest. + // + NetbufTrim (PduHdr, sizeof (UINT32), NET_BUF_TAIL); + } + + Len = ISCSI_GET_DATASEG_LEN (Header); + if (Len == 0) { + // + // No data segment. + // + goto FORM_PDU; + } + // + // Get the length of the padding bytes of the data segment. + // + PadLen = ISCSI_GET_PAD_LEN (Len); + + switch (ISCSI_GET_OPCODE (Header)) { + case ISCSI_OPCODE_SCSI_DATA_IN: + // + // To reduce memory copy overhead, try to use the buffer described by Context + // if the PDU is an iSCSI SCSI data. + // + InDataOffset = ISCSI_GET_BUFFER_OFFSET (Header); + if ((Context == NULL) || ((InDataOffset + Len) > Context->InDataLen)) { + Status = EFI_PROTOCOL_ERROR; + goto ON_EXIT; + } + + Fragment[0].Len = Len; + Fragment[0].Bulk = Context->InData + InDataOffset; + + if (DataDigest || (PadLen != 0)) { + // + // The data segment is padded. Use two fragments to receive it: + // the first to receive the useful data; the second to receive the padding. + // + Fragment[1].Len = PadLen + (DataDigest ? sizeof (UINT32) : 0); + Fragment[1].Bulk = (UINT8 *)PadAndCRC32 + (4 - PadLen); + + FragmentCount = 2; + } else { + FragmentCount = 1; + } + + DataSeg = NetbufFromExt (&Fragment[0], FragmentCount, 0, 0, IScsiNbufExtFree, NULL); + if (DataSeg == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + break; + + case ISCSI_OPCODE_SCSI_RSP: + case ISCSI_OPCODE_NOP_IN: + case ISCSI_OPCODE_LOGIN_RSP: + case ISCSI_OPCODE_TEXT_RSP: + case ISCSI_OPCODE_ASYNC_MSG: + case ISCSI_OPCODE_REJECT: + case ISCSI_OPCODE_VENDOR_T0: + case ISCSI_OPCODE_VENDOR_T1: + case ISCSI_OPCODE_VENDOR_T2: + // + // Allocate buffer to receive the data segment. + // + Len += PadLen + (DataDigest ? sizeof (UINT32) : 0); + DataSeg = NetbufAlloc (Len); + if (DataSeg == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + NetbufAllocSpace (DataSeg, Len, NET_BUF_TAIL); + break; + + default: + Status = EFI_PROTOCOL_ERROR; + goto ON_EXIT; + } + + InsertTailList (NbufList, &DataSeg->List); + + // + // Receive the data segment with the data digest, if any. + // + Status = TcpIoReceive (&Conn->TcpIo, DataSeg, FALSE, TimeoutEvent); + + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + if (DataDigest) { + // + // TODO: Check the data digest. + // + NetbufTrim (DataSeg, sizeof (UINT32), NET_BUF_TAIL); + } + + if (PadLen != 0) { + // + // Trim off the padding bytes in the data segment. + // + NetbufTrim (DataSeg, PadLen, NET_BUF_TAIL); + } + +FORM_PDU: + // + // Form the pdu from a list of pdu segments. + // + *Pdu = NetbufFromBufList (NbufList, 0, 0, IScsiFreeNbufList, NbufList); + if (*Pdu == NULL) { + Status = EFI_OUT_OF_RESOURCES; + } + +ON_EXIT: + + if (EFI_ERROR (Status)) { + // + // Free the Nbufs in this NbufList and the NbufList itself. + // + IScsiFreeNbufList (NbufList); + } + + return Status; +} + + +/** + Check and get the result of the parameter negotiation. + + @param[in, out] Conn The connection in iSCSI login. + + @retval EFI_SUCCESS The parameter check is passed and negotiation is finished. + @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol error occurred. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. + +**/ +EFI_STATUS +IScsiCheckOpParams ( + IN OUT ISCSI_CONNECTION *Conn + ) +{ + EFI_STATUS Status; + LIST_ENTRY *KeyValueList; + CHAR8 *Data; + UINT32 Len; + ISCSI_SESSION *Session; + CHAR8 *Value; + UINTN NumericValue; + + ASSERT (Conn->RspQue.BufNum != 0); + + Session = Conn->Session; + + Len = Conn->RspQue.BufSize; + Data = AllocatePool (Len); + if (Data == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + NetbufQueCopy (&Conn->RspQue, 0, Len, (UINT8 *) Data); + + Status = EFI_PROTOCOL_ERROR; + + // + // Extract the Key-Value pairs into a list. + // + KeyValueList = IScsiBuildKeyValueList (Data, Len); + if (KeyValueList == NULL) { + FreePool (Data); + return Status; + } + // + // HeaderDigest + // + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_HEADER_DIGEST); + if (Value == NULL) { + goto ON_ERROR; + } + + if (AsciiStrCmp (Value, "CRC32") == 0) { + if (Conn->HeaderDigest != IScsiDigestCRC32) { + goto ON_ERROR; + } + } else if (AsciiStrCmp (Value, ISCSI_KEY_VALUE_NONE) == 0) { + Conn->HeaderDigest = IScsiDigestNone; + } else { + goto ON_ERROR; + } + // + // DataDigest + // + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_DATA_DIGEST); + if (Value == NULL) { + goto ON_ERROR; + } + + if (AsciiStrCmp (Value, "CRC32") == 0) { + if (Conn->DataDigest != IScsiDigestCRC32) { + goto ON_ERROR; + } + } else if (AsciiStrCmp (Value, ISCSI_KEY_VALUE_NONE) == 0) { + Conn->DataDigest = IScsiDigestNone; + } else { + goto ON_ERROR; + } + // + // ErrorRecoveryLevel: result function is Minimum. + // + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_ERROR_RECOVERY_LEVEL); + if (Value == NULL) { + goto ON_ERROR; + } + + NumericValue = IScsiNetNtoi (Value); + if (NumericValue > 2) { + goto ON_ERROR; + } + + Session->ErrorRecoveryLevel = (UINT8) MIN (Session->ErrorRecoveryLevel, NumericValue); + + // + // InitialR2T: result function is OR. + // + if (!Session->InitialR2T) { + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_INITIAL_R2T); + if (Value == NULL) { + goto ON_ERROR; + } + + Session->InitialR2T = (BOOLEAN) (AsciiStrCmp (Value, "Yes") == 0); + } + + // + // ImmediateData: result function is AND. + // + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_IMMEDIATE_DATA); + if (Value == NULL) { + goto ON_ERROR; + } + + Session->ImmediateData = (BOOLEAN) (Session->ImmediateData && (BOOLEAN) (AsciiStrCmp (Value, "Yes") == 0)); + + // + // MaxRecvDataSegmentLength is declarative. + // + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_MAX_RECV_DATA_SEGMENT_LENGTH); + if (Value != NULL) { + Conn->MaxRecvDataSegmentLength = (UINT32) IScsiNetNtoi (Value); + } + // + // MaxBurstLength: result function is Minimum. + // + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_MAX_BURST_LENGTH); + if (Value == NULL) { + goto ON_ERROR; + } + + NumericValue = IScsiNetNtoi (Value); + Session->MaxBurstLength = (UINT32) MIN (Session->MaxBurstLength, NumericValue); + + // + // FirstBurstLength: result function is Minimum. Irrelevant when InitialR2T=Yes and + // ImmediateData=No. + // + if (!(Session->InitialR2T && !Session->ImmediateData)) { + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_FIRST_BURST_LENGTH); + if (Value == NULL) { + goto ON_ERROR; + } + + NumericValue = IScsiNetNtoi (Value); + Session->FirstBurstLength = (UINT32) MIN (Session->FirstBurstLength, NumericValue); + } + + // + // MaxConnections: result function is Minimum. + // + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_MAX_CONNECTIONS); + if (Value == NULL) { + goto ON_ERROR; + } + + NumericValue = IScsiNetNtoi (Value); + if ((NumericValue == 0) || (NumericValue > 65535)) { + goto ON_ERROR; + } + + Session->MaxConnections = (UINT32) MIN (Session->MaxConnections, NumericValue); + + // + // DataPDUInOrder: result function is OR. + // + if (!Session->DataPDUInOrder) { + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_DATA_PDU_IN_ORDER); + if (Value == NULL) { + goto ON_ERROR; + } + + Session->DataPDUInOrder = (BOOLEAN) (AsciiStrCmp (Value, "Yes") == 0); + } + + // + // DataSequenceInorder: result function is OR. + // + if (!Session->DataSequenceInOrder) { + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_DATA_SEQUENCE_IN_ORDER); + if (Value == NULL) { + goto ON_ERROR; + } + + Session->DataSequenceInOrder = (BOOLEAN) (AsciiStrCmp (Value, "Yes") == 0); + } + + // + // DefaultTime2Wait: result function is Maximum. + // + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_DEFAULT_TIME2WAIT); + if (Value == NULL) { + goto ON_ERROR; + } + + NumericValue = IScsiNetNtoi (Value); + if (NumericValue == 0) { + Session->DefaultTime2Wait = 0; + } else if (NumericValue > 3600) { + goto ON_ERROR; + } else { + Session->DefaultTime2Wait = (UINT32) MAX (Session->DefaultTime2Wait, NumericValue); + } + // + // DefaultTime2Retain: result function is Minimum. + // + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_DEFAULT_TIME2RETAIN); + if (Value == NULL) { + goto ON_ERROR; + } + + NumericValue = IScsiNetNtoi (Value); + if (NumericValue == 0) { + Session->DefaultTime2Retain = 0; + } else if (NumericValue > 3600) { + goto ON_ERROR; + } else { + Session->DefaultTime2Retain = (UINT32) MIN (Session->DefaultTime2Retain, NumericValue); + } + // + // MaxOutstandingR2T: result function is Minimum. + // + Value = IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_MAX_OUTSTANDING_R2T); + if (Value == NULL) { + goto ON_ERROR; + } + + NumericValue = IScsiNetNtoi (Value); + if ((NumericValue == 0) || (NumericValue > 65535)) { + goto ON_ERROR; + } + + Session->MaxOutstandingR2T = (UINT16) MIN (Session->MaxOutstandingR2T, NumericValue); + + // + // Remove declarative key-value pairs, if any. + // + IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_SESSION_TYPE); + IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_TARGET_ALIAS); + IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_TARGET_PORTAL_GROUP_TAG); + + + // + // Remove the key-value that may not needed for result function is OR. + // + IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_INITIAL_R2T); + IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_DATA_PDU_IN_ORDER); + IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_DATA_SEQUENCE_IN_ORDER); + + // + // Remove irrelevant parameter, if any. + // + if (Session->InitialR2T && !Session->ImmediateData) { + IScsiGetValueByKeyFromList (KeyValueList, ISCSI_KEY_FIRST_BURST_LENGTH); + } + + if (IsListEmpty (KeyValueList)) { + // + // Succeed if no more keys in the list. + // + Status = EFI_SUCCESS; + } + +ON_ERROR: + + IScsiFreeKeyValueList (KeyValueList); + + FreePool (Data); + + return Status; +} + + +/** + Fill the operational parameters. + + @param[in] Conn The connection in iSCSI login. + @param[in, out] Pdu The iSCSI login request PDU to fill the parameters. + +**/ +VOID +IScsiFillOpParams ( + IN ISCSI_CONNECTION *Conn, + IN OUT NET_BUF *Pdu + ) +{ + ISCSI_SESSION *Session; + CHAR8 Value[256]; + + Session = Conn->Session; + + AsciiSPrint (Value, sizeof (Value), "%a", (Conn->HeaderDigest == IScsiDigestCRC32) ? "None,CRC32" : "None"); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_HEADER_DIGEST, Value); + + AsciiSPrint (Value, sizeof (Value), "%a", (Conn->DataDigest == IScsiDigestCRC32) ? "None,CRC32" : "None"); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_DATA_DIGEST, Value); + + AsciiSPrint (Value, sizeof (Value), "%d", Session->ErrorRecoveryLevel); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_ERROR_RECOVERY_LEVEL, Value); + + AsciiSPrint (Value, sizeof (Value), "%a", Session->InitialR2T ? "Yes" : "No"); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_INITIAL_R2T, Value); + + AsciiSPrint (Value, sizeof (Value), "%a", Session->ImmediateData ? "Yes" : "No"); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_IMMEDIATE_DATA, Value); + + AsciiSPrint (Value, sizeof (Value), "%d", MAX_RECV_DATA_SEG_LEN_IN_FFP); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_MAX_RECV_DATA_SEGMENT_LENGTH, Value); + + AsciiSPrint (Value, sizeof (Value), "%d", Session->MaxBurstLength); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_MAX_BURST_LENGTH, Value); + + AsciiSPrint (Value, sizeof (Value), "%d", Session->FirstBurstLength); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_FIRST_BURST_LENGTH, Value); + + AsciiSPrint (Value, sizeof (Value), "%d", Session->MaxConnections); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_MAX_CONNECTIONS, Value); + + AsciiSPrint (Value, sizeof (Value), "%a", Session->DataPDUInOrder ? "Yes" : "No"); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_DATA_PDU_IN_ORDER, Value); + + AsciiSPrint (Value, sizeof (Value), "%a", Session->DataSequenceInOrder ? "Yes" : "No"); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_DATA_SEQUENCE_IN_ORDER, Value); + + AsciiSPrint (Value, sizeof (Value), "%d", Session->DefaultTime2Wait); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_DEFAULT_TIME2WAIT, Value); + + AsciiSPrint (Value, sizeof (Value), "%d", Session->DefaultTime2Retain); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_DEFAULT_TIME2RETAIN, Value); + + AsciiSPrint (Value, sizeof (Value), "%d", Session->MaxOutstandingR2T); + IScsiAddKeyValuePair (Pdu, ISCSI_KEY_MAX_OUTSTANDING_R2T, Value); +} + + +/** + Pad the iSCSI AHS or data segment to an integer number of 4 byte words. + + @param[in, out] Pdu The iSCSI pdu which contains segments to pad. + @param[in] Len The length of the last segment in the PDU. + + @retval EFI_SUCCESS The segment is padded or there is no need to pad it. + @retval EFI_OUT_OF_RESOURCES There is not enough remaining free space to add the + padding bytes. +**/ +EFI_STATUS +IScsiPadSegment ( + IN OUT NET_BUF *Pdu, + IN UINT32 Len + ) +{ + UINT32 PadLen; + UINT8 *Data; + + PadLen = ISCSI_GET_PAD_LEN (Len); + + if (PadLen != 0) { + Data = NetbufAllocSpace (Pdu, PadLen, NET_BUF_TAIL); + if (Data == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + ZeroMem (Data, PadLen); + } + + return EFI_SUCCESS; +} + + +/** + Build a key-value list from the data segment. + + @param[in] Data The data segment containing the key-value pairs. + @param[in] Len Length of the data segment. + + @return The key-value list. + @retval NULL Other errors as indicated. + +**/ +LIST_ENTRY * +IScsiBuildKeyValueList ( + IN CHAR8 *Data, + IN UINT32 Len + ) +{ + LIST_ENTRY *ListHead; + ISCSI_KEY_VALUE_PAIR *KeyValuePair; + + ListHead = AllocatePool (sizeof (LIST_ENTRY)); + if (ListHead == NULL) { + return NULL; + } + + InitializeListHead (ListHead); + + while (Len > 0) { + KeyValuePair = AllocatePool (sizeof (ISCSI_KEY_VALUE_PAIR)); + if (KeyValuePair == NULL) { + goto ON_ERROR; + } + + InitializeListHead (&KeyValuePair->List); + + KeyValuePair->Key = Data; + + while ((Len > 0) && (*Data != '=')) { + Len--; + Data++; + } + + if (*Data == '=') { + *Data = '\0'; + + Data++; + Len--; + } else { + FreePool (KeyValuePair); + goto ON_ERROR; + } + + KeyValuePair->Value = Data; + + InsertTailList (ListHead, &KeyValuePair->List);; + + Data += AsciiStrLen (KeyValuePair->Value) + 1; + Len -= (UINT32) AsciiStrLen (KeyValuePair->Value) + 1; + } + + return ListHead; + +ON_ERROR: + + IScsiFreeKeyValueList (ListHead); + + return NULL; +} + + +/** + Get the value string by the key name from the key-value list. If found, + the key-value entry will be removed from the list. + + @param[in, out] KeyValueList The key-value list. + @param[in] Key The key name to find. + + @return The value string. + @retval NULL The key value pair cannot be found. + +**/ +CHAR8 * +IScsiGetValueByKeyFromList ( + IN OUT LIST_ENTRY *KeyValueList, + IN CHAR8 *Key + ) +{ + LIST_ENTRY *Entry; + ISCSI_KEY_VALUE_PAIR *KeyValuePair; + CHAR8 *Value; + + Value = NULL; + + NET_LIST_FOR_EACH (Entry, KeyValueList) { + KeyValuePair = NET_LIST_USER_STRUCT (Entry, ISCSI_KEY_VALUE_PAIR, List); + + if (AsciiStrCmp (KeyValuePair->Key, Key) == 0) { + Value = KeyValuePair->Value; + + RemoveEntryList (&KeyValuePair->List); + FreePool (KeyValuePair); + break; + } + } + + return Value; +} + + +/** + Free the key-value list. + + @param[in] KeyValueList The key-value list. + +**/ +VOID +IScsiFreeKeyValueList ( + IN LIST_ENTRY *KeyValueList + ) +{ + LIST_ENTRY *Entry; + ISCSI_KEY_VALUE_PAIR *KeyValuePair; + + while (!IsListEmpty (KeyValueList)) { + Entry = NetListRemoveHead (KeyValueList); + KeyValuePair = NET_LIST_USER_STRUCT (Entry, ISCSI_KEY_VALUE_PAIR, List); + + FreePool (KeyValuePair); + } + + FreePool (KeyValueList); +} + + +/** + Normalize the iSCSI name according to RFC. + + @param[in, out] Name The iSCSI name. + @param[in] Len Length of the iSCSI name. + + @retval EFI_SUCCESS The iSCSI name is valid and normalized. + @retval EFI_PROTOCOL_ERROR The iSCSI name is malformatted or not in the IQN format. + +**/ +EFI_STATUS +IScsiNormalizeName ( + IN OUT CHAR8 *Name, + IN UINTN Len + ) +{ + UINTN Index; + + for (Index = 0; Index < Len; Index++) { + if (NET_IS_UPPER_CASE_CHAR (Name[Index])) { + // + // Convert the upper-case characters to lower-case ones. + // + Name[Index] = (CHAR8) (Name[Index] - 'A' + 'a'); + } + + if (!NET_IS_LOWER_CASE_CHAR (Name[Index]) && + !NET_IS_DIGIT (Name[Index]) && + (Name[Index] != '-') && + (Name[Index] != '.') && + (Name[Index] != ':') + ) { + // + // ASCII dash, dot, colon lower-case characters and digit characters + // are allowed. + // + return EFI_PROTOCOL_ERROR; + } + } + + if ((Len < 4) || (CompareMem (Name, "iqn.", 4) != 0)) { + // + // Only IQN format is accepted now. + // + return EFI_PROTOCOL_ERROR; + } + + return EFI_SUCCESS; +} + + +/** + Create an iSCSI task control block. + + @param[in] Conn The connection on which the task control block will be created. + @param[out] Tcb The newly created task control block. + + @retval EFI_SUCCESS The task control block is created. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. + @retval EFI_NOT_READY The target cannot accept new commands. + +**/ +EFI_STATUS +IScsiNewTcb ( + IN ISCSI_CONNECTION *Conn, + OUT ISCSI_TCB **Tcb + ) +{ + ISCSI_SESSION *Session; + ISCSI_TCB *NewTcb; + + ASSERT (Tcb != NULL); + + Session = Conn->Session; + + if (ISCSI_SEQ_GT (Session->CmdSN, Session->MaxCmdSN)) { + return EFI_NOT_READY; + } + + NewTcb = AllocateZeroPool (sizeof (ISCSI_TCB)); + if (NewTcb == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + InitializeListHead (&NewTcb->Link); + + NewTcb->SoFarInOrder = TRUE; + NewTcb->InitiatorTaskTag = Session->InitiatorTaskTag; + NewTcb->CmdSN = Session->CmdSN; + NewTcb->Conn = Conn; + + InsertTailList (&Session->TcbList, &NewTcb->Link); + + // + // Advance the initiator task tag. + // + Session->InitiatorTaskTag++; + Session->CmdSN++; + + *Tcb = NewTcb; + + return EFI_SUCCESS; +} + + +/** + Delete the tcb from the connection and destroy it. + + @param[in] Tcb The tcb to delete. + +**/ +VOID +IScsiDelTcb ( + IN ISCSI_TCB *Tcb + ) +{ + RemoveEntryList (&Tcb->Link); + + FreePool (Tcb); +} + + +/** + Create a data segment, pad it, and calculate the CRC if needed. + + @param[in] Data The data to fill into the data segment. + @param[in] Len Length of the data. + @param[in] DataDigest Whether to calculate CRC for this data segment. + + @return The net buffer wrapping the data segment. + +**/ +NET_BUF * +IScsiNewDataSegment ( + IN UINT8 *Data, + IN UINT32 Len, + IN BOOLEAN DataDigest + ) +{ + NET_FRAGMENT Fragment[2]; + UINT32 FragmentCount; + UINT32 PadLen; + NET_BUF *DataSeg; + + Fragment[0].Len = Len; + Fragment[0].Bulk = Data; + + PadLen = ISCSI_GET_PAD_LEN (Len); + if (PadLen != 0) { + Fragment[1].Len = PadLen; + Fragment[1].Bulk = (UINT8 *) &mDataSegPad; + + FragmentCount = 2; + } else { + FragmentCount = 1; + } + + DataSeg = NetbufFromExt (&Fragment[0], FragmentCount, 0, 0, IScsiNbufExtFree, NULL); + + return DataSeg; +} + + +/** + Create a iSCSI SCSI command PDU to encapsulate the command issued + by SCSI through the EXT SCSI PASS THRU Protocol. + + @param[in] Packet The EXT SCSI PASS THRU request packet containing the SCSI command. + @param[in] Lun The LUN. + @param[in] Tcb The tcb associated with this SCSI command. + + @return The created iSCSI SCSI command PDU. + @retval NULL Other errors as indicated. + +**/ +NET_BUF * +IScsiNewScsiCmdPdu ( + IN EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet, + IN UINT64 Lun, + IN ISCSI_TCB *Tcb + ) +{ + LIST_ENTRY *NbufList; + NET_BUF *Pdu; + NET_BUF *PduHeader; + NET_BUF *DataSeg; + SCSI_COMMAND *ScsiCmd; + UINT8 AHSLength; + UINT32 Length; + ISCSI_ADDITIONAL_HEADER *Header; + ISCSI_BI_EXP_READ_DATA_LEN_AHS *BiExpReadDataLenAHS; + ISCSI_SESSION *Session; + UINT32 ImmediateDataLen; + + AHSLength = 0; + + if (Packet->DataDirection == DataBi) { + // + // Bidirectional Read/Write command, the bidirectional expected + // read data length AHS is required. + // + AHSLength += sizeof (ISCSI_BI_EXP_READ_DATA_LEN_AHS); + } + + if (Packet->CdbLength > 16) { + // + // The CDB exceeds 16 bytes. An extended CDB AHS is required. + // + AHSLength = (UINT8) (AHSLength + ISCSI_ROUNDUP (Packet->CdbLength - 16) + sizeof (ISCSI_ADDITIONAL_HEADER)); + } + + Length = sizeof (SCSI_COMMAND) + AHSLength; + PduHeader = NetbufAlloc (Length); + if (PduHeader == NULL) { + return NULL; + } + + ScsiCmd = (SCSI_COMMAND *) NetbufAllocSpace (PduHeader, Length, NET_BUF_TAIL); + if (ScsiCmd == NULL) { + NetbufFree (PduHeader); + return NULL; + } + Header = (ISCSI_ADDITIONAL_HEADER *) (ScsiCmd + 1); + + ZeroMem (ScsiCmd, Length); + + ISCSI_SET_OPCODE (ScsiCmd, ISCSI_OPCODE_SCSI_CMD, 0); + ISCSI_SET_FLAG (ScsiCmd, ISCSI_TASK_ATTR_SIMPLE); + + // + // Set the READ/WRITE flags according to the IO type of this request. + // + switch (Packet->DataDirection) { + case DataIn: + ISCSI_SET_FLAG (ScsiCmd, SCSI_CMD_PDU_FLAG_READ); + ScsiCmd->ExpDataXferLength = NTOHL (Packet->InTransferLength); + break; + + case DataOut: + ISCSI_SET_FLAG (ScsiCmd, SCSI_CMD_PDU_FLAG_WRITE); + ScsiCmd->ExpDataXferLength = NTOHL (Packet->OutTransferLength); + break; + + case DataBi: + ISCSI_SET_FLAG (ScsiCmd, SCSI_CMD_PDU_FLAG_READ | SCSI_CMD_PDU_FLAG_WRITE); + ScsiCmd->ExpDataXferLength = NTOHL (Packet->OutTransferLength); + + // + // Fill the bidirectional expected read data length AHS. + // + BiExpReadDataLenAHS = (ISCSI_BI_EXP_READ_DATA_LEN_AHS *) Header; + Header = (ISCSI_ADDITIONAL_HEADER *) (BiExpReadDataLenAHS + 1); + + BiExpReadDataLenAHS->Length = NTOHS (5); + BiExpReadDataLenAHS->Type = ISCSI_AHS_TYPE_BI_EXP_READ_DATA_LEN; + BiExpReadDataLenAHS->ExpReadDataLength = NTOHL (Packet->InTransferLength); + + break; + } + + ScsiCmd->TotalAHSLength = AHSLength; + CopyMem (ScsiCmd->Lun, &Lun, sizeof (ScsiCmd->Lun)); + ScsiCmd->InitiatorTaskTag = NTOHL (Tcb->InitiatorTaskTag); + ScsiCmd->CmdSN = NTOHL (Tcb->CmdSN); + ScsiCmd->ExpStatSN = NTOHL (Tcb->Conn->ExpStatSN); + + CopyMem (ScsiCmd->Cdb, Packet->Cdb, sizeof (ScsiCmd->Cdb)); + + if (Packet->CdbLength > 16) { + Header->Length = NTOHS ((UINT16) (Packet->CdbLength - 15)); + Header->Type = ISCSI_AHS_TYPE_EXT_CDB; + + CopyMem (Header + 1, (UINT8 *) Packet->Cdb + 16, Packet->CdbLength - 16); + } + + Pdu = PduHeader; + Session = Tcb->Conn->Session; + ImmediateDataLen = 0; + + if (Session->ImmediateData && (Packet->OutTransferLength != 0)) { + // + // Send immediate data in this SCSI Command PDU. The length of the immediate + // data is the minimum of FirstBurstLength, the data length to be xfered, and + // the MaxRecvdataSegmentLength on this connection. + // + ImmediateDataLen = MIN (Session->FirstBurstLength, Packet->OutTransferLength); + ImmediateDataLen = MIN (ImmediateDataLen, Tcb->Conn->MaxRecvDataSegmentLength); + + // + // Update the data segment length in the PDU header. + // + ISCSI_SET_DATASEG_LEN (ScsiCmd, ImmediateDataLen); + + // + // Create the data segment. + // + DataSeg = IScsiNewDataSegment ((UINT8 *) Packet->OutDataBuffer, ImmediateDataLen, FALSE); + if (DataSeg == NULL) { + NetbufFree (PduHeader); + Pdu = NULL; + goto ON_EXIT; + } + + NbufList = AllocatePool (sizeof (LIST_ENTRY)); + if (NbufList == NULL) { + NetbufFree (PduHeader); + NetbufFree (DataSeg); + + Pdu = NULL; + goto ON_EXIT; + } + + InitializeListHead (NbufList); + InsertTailList (NbufList, &PduHeader->List); + InsertTailList (NbufList, &DataSeg->List); + + Pdu = NetbufFromBufList (NbufList, 0, 0, IScsiFreeNbufList, NbufList); + if (Pdu == NULL) { + IScsiFreeNbufList (NbufList); + } + } + + if (Session->InitialR2T || + (ImmediateDataLen == Session->FirstBurstLength) || + (ImmediateDataLen == Packet->OutTransferLength) + ) { + // + // Unsolicited data out sequence is not allowed, + // or FirstBustLength data is already sent out by immediate data, + // or all the OUT data accompany this SCSI packet are sent as + // immediate data. The final flag should be set on this SCSI Command + // PDU. + // + ISCSI_SET_FLAG (ScsiCmd, ISCSI_BHS_FLAG_FINAL); + } + +ON_EXIT: + + return Pdu; +} + + +/** + Create a new iSCSI SCSI Data Out PDU. + + @param[in] Data The data to put into the Data Out PDU. + @param[in] Len Length of the data. + @param[in] DataSN The DataSN of the Data Out PDU. + @param[in] Tcb The task control block of this Data Out PDU. + @param[in] Lun The LUN. + + @return The net buffer wrapping the Data Out PDU. + @retval NULL Other errors as indicated. + +**/ +NET_BUF * +IScsiNewDataOutPdu ( + IN UINT8 *Data, + IN UINT32 Len, + IN UINT32 DataSN, + IN ISCSI_TCB *Tcb, + IN UINT64 Lun + ) +{ + LIST_ENTRY *NbufList; + NET_BUF *PduHdr; + NET_BUF *DataSeg; + NET_BUF *Pdu; + ISCSI_SCSI_DATA_OUT *DataOutHdr; + ISCSI_XFER_CONTEXT *XferContext; + + NbufList = AllocatePool (sizeof (LIST_ENTRY)); + if (NbufList == NULL) { + return NULL; + } + + InitializeListHead (NbufList); + + // + // Allocate memory for the BHS. + // + PduHdr = NetbufAlloc (sizeof (ISCSI_SCSI_DATA_OUT)); + if (PduHdr == NULL) { + FreePool (NbufList); + return NULL; + } + // + // Insert the BHS into the buffer list. + // + InsertTailList (NbufList, &PduHdr->List); + + DataOutHdr = (ISCSI_SCSI_DATA_OUT *) NetbufAllocSpace (PduHdr, sizeof (ISCSI_SCSI_DATA_OUT), NET_BUF_TAIL); + if (DataOutHdr == NULL) { + IScsiFreeNbufList (NbufList); + return NULL; + } + XferContext = &Tcb->XferContext; + + ZeroMem (DataOutHdr, sizeof (ISCSI_SCSI_DATA_OUT)); + + // + // Set the flags and fields of the Data Out PDU BHS. + // + ISCSI_SET_OPCODE (DataOutHdr, ISCSI_OPCODE_SCSI_DATA_OUT, 0); + ISCSI_SET_DATASEG_LEN (DataOutHdr, Len); + + DataOutHdr->InitiatorTaskTag = HTONL (Tcb->InitiatorTaskTag); + DataOutHdr->TargetTransferTag = HTONL (XferContext->TargetTransferTag); + DataOutHdr->ExpStatSN = HTONL (Tcb->Conn->ExpStatSN); + DataOutHdr->DataSN = HTONL (DataSN); + DataOutHdr->BufferOffset = HTONL (XferContext->Offset); + + if (XferContext->TargetTransferTag != ISCSI_RESERVED_TAG) { + CopyMem (&DataOutHdr->Lun, &Lun, sizeof (DataOutHdr->Lun)); + } + // + // Build the data segment for this Data Out PDU. + // + DataSeg = IScsiNewDataSegment (Data, Len, FALSE); + if (DataSeg == NULL) { + IScsiFreeNbufList (NbufList); + return NULL; + } + // + // Put the data segment into the buffer list and combine it with the BHS + // into a full Data Out PDU. + // + InsertTailList (NbufList, &DataSeg->List); + Pdu = NetbufFromBufList (NbufList, 0, 0, IScsiFreeNbufList, NbufList); + if (Pdu == NULL) { + IScsiFreeNbufList (NbufList); + } + + return Pdu; +} + + +/** + Generate a consecutive sequence of iSCSI SCSI Data Out PDUs. + + @param[in] Data The data which will be carried by the sequence of iSCSI SCSI Data Out PDUs. + @param[in] Tcb The task control block of the data to send out. + @param[in] Lun The LUN the data will be sent to. + + @return A list of net buffers with each of them wrapping an iSCSI SCSI Data Out PDU. + @retval NULL Other errors as indicated. + +**/ +LIST_ENTRY * +IScsiGenerateDataOutPduSequence ( + IN UINT8 *Data, + IN ISCSI_TCB *Tcb, + IN UINT64 Lun + ) +{ + LIST_ENTRY *PduList; + UINT32 DataSN; + UINT32 DataLen; + NET_BUF *DataOutPdu; + ISCSI_CONNECTION *Conn; + ISCSI_XFER_CONTEXT *XferContext; + UINT8 *DataOutPacket; + + PduList = AllocatePool (sizeof (LIST_ENTRY)); + if (PduList == NULL) { + return NULL; + } + + InitializeListHead (PduList); + + DataSN = 0; + Conn = Tcb->Conn; + DataOutPdu = NULL; + XferContext = &Tcb->XferContext; + + while (XferContext->DesiredLength > 0) { + // + // Determine the length of data this Data Out PDU can carry. + // + DataLen = MIN (XferContext->DesiredLength, Conn->MaxRecvDataSegmentLength); + + // + // Create a Data Out PDU. + // + DataOutPdu = IScsiNewDataOutPdu (Data, DataLen, DataSN, Tcb, Lun); + if (DataOutPdu == NULL) { + IScsiFreeNbufList (PduList); + PduList = NULL; + + goto ON_EXIT; + } + + InsertTailList (PduList, &DataOutPdu->List); + + // + // Update the context and DataSN. + // + Data += DataLen; + XferContext->Offset += DataLen; + XferContext->DesiredLength -= DataLen; + DataSN++; + } + // + // Set the F bit for the last data out PDU in this sequence. + // + DataOutPacket = NetbufGetByte (DataOutPdu, 0, NULL); + if (DataOutPacket == NULL) { + IScsiFreeNbufList (PduList); + PduList = NULL; + goto ON_EXIT; + } + + ISCSI_SET_FLAG (DataOutPacket, ISCSI_BHS_FLAG_FINAL); + +ON_EXIT: + + return PduList; +} + +/** + Send the Data in a sequence of Data Out PDUs one by one. + + @param[in] Data The data to carry by Data Out PDUs. + @param[in] Lun The LUN the data will be sent to. + @param[in] Tcb The task control block. + + @retval EFI_SUCCESS The data is sent out to the LUN. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiSendDataOutPduSequence ( + IN UINT8 *Data, + IN UINT64 Lun, + IN ISCSI_TCB *Tcb + ) +{ + LIST_ENTRY *DataOutPduList; + LIST_ENTRY *Entry; + NET_BUF *Pdu; + EFI_STATUS Status; + + // + // Generate the Data Out PDU sequence. + // + DataOutPduList = IScsiGenerateDataOutPduSequence (Data, Tcb, Lun); + if (DataOutPduList == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = EFI_SUCCESS; + + // + // Send the Data Out PDU's one by one. + // + NET_LIST_FOR_EACH (Entry, DataOutPduList) { + Pdu = NET_LIST_USER_STRUCT (Entry, NET_BUF, List); + + Status = TcpIoTransmit (&Tcb->Conn->TcpIo, Pdu); + + if (EFI_ERROR (Status)) { + break; + } + } + + IScsiFreeNbufList (DataOutPduList); + + return Status; +} + + +/** + Process the received iSCSI SCSI Data In PDU. + + @param[in] Pdu The Data In PDU received. + @param[in] Tcb The task control block. + @param[in, out] Packet The EXT SCSI PASS THRU request packet. + + @retval EFI_SUCCESS The check on the Data IN PDU is passed and some update + actions are taken. + @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol error occurred. + @retval EFI_BAD_BUFFER_SIZEE The buffer was not the proper size for the request. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiOnDataInRcvd ( + IN NET_BUF *Pdu, + IN ISCSI_TCB *Tcb, + IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet + ) +{ + ISCSI_SCSI_DATA_IN *DataInHdr; + EFI_STATUS Status; + + DataInHdr = (ISCSI_SCSI_DATA_IN *) NetbufGetByte (Pdu, 0, NULL); + if (DataInHdr == NULL) { + return EFI_PROTOCOL_ERROR; + } + + DataInHdr->InitiatorTaskTag = NTOHL (DataInHdr->InitiatorTaskTag); + DataInHdr->ExpCmdSN = NTOHL (DataInHdr->ExpCmdSN); + DataInHdr->MaxCmdSN = NTOHL (DataInHdr->MaxCmdSN); + DataInHdr->DataSN = NTOHL (DataInHdr->DataSN); + + // + // Check the DataSN. + // + Status = IScsiCheckSN (&Tcb->ExpDataSN, DataInHdr->DataSN); + if (EFI_ERROR (Status)) { + return Status; + } + + if (DataInHdr->InitiatorTaskTag != Tcb->InitiatorTaskTag) { + return EFI_PROTOCOL_ERROR; + } + // + // Update the command related sequence numbers. + // + IScsiUpdateCmdSN (Tcb->Conn->Session, DataInHdr->MaxCmdSN, DataInHdr->ExpCmdSN); + + if (ISCSI_FLAG_ON (DataInHdr, SCSI_DATA_IN_PDU_FLAG_STATUS_VALID)) { + if (!ISCSI_FLAG_ON (DataInHdr, ISCSI_BHS_FLAG_FINAL)) { + // + // The S bit is on but the F bit is off. + // + return EFI_PROTOCOL_ERROR; + } + + Tcb->StatusXferd = TRUE; + + if (ISCSI_FLAG_ON (DataInHdr, SCSI_DATA_IN_PDU_FLAG_OVERFLOW | SCSI_DATA_IN_PDU_FLAG_UNDERFLOW)) { + // + // Underflow and Overflow are mutual flags. + // + return EFI_PROTOCOL_ERROR; + } + // + // S bit is on, the StatSN is valid. + // + Status = IScsiCheckSN (&Tcb->Conn->ExpStatSN, NTOHL (DataInHdr->StatSN)); + if (EFI_ERROR (Status)) { + return Status; + } + + Packet->HostAdapterStatus = 0; + Packet->TargetStatus = DataInHdr->Status; + + if (ISCSI_FLAG_ON (DataInHdr, SCSI_RSP_PDU_FLAG_OVERFLOW)) { + Packet->InTransferLength += NTOHL (DataInHdr->ResidualCount); + Status = EFI_BAD_BUFFER_SIZE; + } + + if (ISCSI_FLAG_ON (DataInHdr, SCSI_RSP_PDU_FLAG_UNDERFLOW)) { + Packet->InTransferLength -= NTOHL (DataInHdr->ResidualCount); + } + } + + return Status; +} + + +/** + Process the received iSCSI R2T PDU. + + @param[in] Pdu The R2T PDU received. + @param[in] Tcb The task control block. + @param[in] Lun The Lun. + @param[in, out] Packet The EXT SCSI PASS THRU request packet. + + @retval EFI_SUCCESS The R2T PDU is valid and the solicited data is sent out. + @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol error occurred. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiOnR2TRcvd ( + IN NET_BUF *Pdu, + IN ISCSI_TCB *Tcb, + IN UINT64 Lun, + IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet + ) +{ + ISCSI_READY_TO_TRANSFER *R2THdr; + EFI_STATUS Status; + ISCSI_XFER_CONTEXT *XferContext; + UINT8 *Data; + + R2THdr = (ISCSI_READY_TO_TRANSFER *) NetbufGetByte (Pdu, 0, NULL); + if (R2THdr == NULL) { + return EFI_PROTOCOL_ERROR; + } + + R2THdr->InitiatorTaskTag = NTOHL (R2THdr->InitiatorTaskTag); + R2THdr->TargetTransferTag = NTOHL (R2THdr->TargetTransferTag); + R2THdr->StatSN = NTOHL (R2THdr->StatSN); + R2THdr->R2TSeqNum = NTOHL (R2THdr->R2TSeqNum); + R2THdr->BufferOffset = NTOHL (R2THdr->BufferOffset); + R2THdr->DesiredDataTransferLength = NTOHL (R2THdr->DesiredDataTransferLength); + + if ((R2THdr->InitiatorTaskTag != Tcb->InitiatorTaskTag) || !ISCSI_SEQ_EQ (R2THdr->StatSN, Tcb->Conn->ExpStatSN)) { + return EFI_PROTOCOL_ERROR;; + } + // + // Check the sequence number. + // + Status = IScsiCheckSN (&Tcb->ExpDataSN, R2THdr->R2TSeqNum); + if (EFI_ERROR (Status)) { + return Status; + } + + XferContext = &Tcb->XferContext; + XferContext->TargetTransferTag = R2THdr->TargetTransferTag; + XferContext->Offset = R2THdr->BufferOffset; + XferContext->DesiredLength = R2THdr->DesiredDataTransferLength; + + if (((XferContext->Offset + XferContext->DesiredLength) > Packet->OutTransferLength) || + (XferContext->DesiredLength > Tcb->Conn->Session->MaxBurstLength) + ) { + return EFI_PROTOCOL_ERROR; + } + // + // Send the data solicited by this R2T. + // + Data = (UINT8 *) Packet->OutDataBuffer + XferContext->Offset; + Status = IScsiSendDataOutPduSequence (Data, Lun, Tcb); + + return Status; +} + + +/** + Process the received iSCSI SCSI Response PDU. + + @param[in] Pdu The Response PDU received. + @param[in] Tcb The task control block. + @param[in, out] Packet The EXT SCSI PASS THRU request packet. + + @retval EFI_SUCCESS The Response PDU is processed. + @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol error occurred. + @retval EFI_BAD_BUFFER_SIZEE The buffer was not the proper size for the request. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiOnScsiRspRcvd ( + IN NET_BUF *Pdu, + IN ISCSI_TCB *Tcb, + IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet + ) +{ + SCSI_RESPONSE *ScsiRspHdr; + ISCSI_SENSE_DATA *SenseData; + EFI_STATUS Status; + UINT32 DataSegLen; + + ScsiRspHdr = (SCSI_RESPONSE *) NetbufGetByte (Pdu, 0, NULL); + if (ScsiRspHdr == NULL) { + return EFI_PROTOCOL_ERROR; + } + + ScsiRspHdr->InitiatorTaskTag = NTOHL (ScsiRspHdr->InitiatorTaskTag); + if (ScsiRspHdr->InitiatorTaskTag != Tcb->InitiatorTaskTag) { + return EFI_PROTOCOL_ERROR; + } + + ScsiRspHdr->StatSN = NTOHL (ScsiRspHdr->StatSN); + + Status = IScsiCheckSN (&Tcb->Conn->ExpStatSN, ScsiRspHdr->StatSN); + if (EFI_ERROR (Status)) { + return Status; + } + + ScsiRspHdr->MaxCmdSN = NTOHL (ScsiRspHdr->MaxCmdSN); + ScsiRspHdr->ExpCmdSN = NTOHL (ScsiRspHdr->ExpCmdSN); + IScsiUpdateCmdSN (Tcb->Conn->Session, ScsiRspHdr->MaxCmdSN, ScsiRspHdr->ExpCmdSN); + + Tcb->StatusXferd = TRUE; + + Packet->HostAdapterStatus = ScsiRspHdr->Response; + if (Packet->HostAdapterStatus != ISCSI_SERVICE_RSP_COMMAND_COMPLETE_AT_TARGET) { + return EFI_SUCCESS; + } + + Packet->TargetStatus = ScsiRspHdr->Status; + + if (ISCSI_FLAG_ON (ScsiRspHdr, SCSI_RSP_PDU_FLAG_BI_READ_OVERFLOW | SCSI_RSP_PDU_FLAG_BI_READ_UNDERFLOW) || + ISCSI_FLAG_ON (ScsiRspHdr, SCSI_RSP_PDU_FLAG_OVERFLOW | SCSI_RSP_PDU_FLAG_UNDERFLOW) + ) { + return EFI_PROTOCOL_ERROR; + } + + if (ISCSI_FLAG_ON (ScsiRspHdr, SCSI_RSP_PDU_FLAG_BI_READ_OVERFLOW)) { + Packet->InTransferLength += NTOHL (ScsiRspHdr->BiReadResidualCount); + Status = EFI_BAD_BUFFER_SIZE; + } + + if (ISCSI_FLAG_ON (ScsiRspHdr, SCSI_RSP_PDU_FLAG_BI_READ_UNDERFLOW)) { + Packet->InTransferLength -= NTOHL (ScsiRspHdr->BiReadResidualCount); + } + + if (ISCSI_FLAG_ON (ScsiRspHdr, SCSI_RSP_PDU_FLAG_OVERFLOW)) { + if (Packet->DataDirection == DataIn) { + Packet->InTransferLength += NTOHL (ScsiRspHdr->ResidualCount); + } else { + Packet->OutTransferLength += NTOHL (ScsiRspHdr->ResidualCount); + } + + Status = EFI_BAD_BUFFER_SIZE; + } + + if (ISCSI_FLAG_ON (ScsiRspHdr, SCSI_RSP_PDU_FLAG_UNDERFLOW)) { + if (Packet->DataDirection == DataIn) { + Packet->InTransferLength -= NTOHL (ScsiRspHdr->ResidualCount); + } else { + Packet->OutTransferLength -= NTOHL (ScsiRspHdr->ResidualCount); + } + } + + DataSegLen = ISCSI_GET_DATASEG_LEN (ScsiRspHdr); + if (DataSegLen != 0) { + SenseData = (ISCSI_SENSE_DATA *) NetbufGetByte (Pdu, sizeof (SCSI_RESPONSE), NULL); + if (SenseData == NULL) { + return EFI_PROTOCOL_ERROR; + } + + SenseData->Length = NTOHS (SenseData->Length); + + Packet->SenseDataLength = (UINT8) MIN (SenseData->Length, Packet->SenseDataLength); + if (Packet->SenseDataLength != 0) { + CopyMem (Packet->SenseData, &SenseData->Data[0], Packet->SenseDataLength); + } + } else { + Packet->SenseDataLength = 0; + } + + return Status; +} + + +/** + Process the received NOP In PDU. + + @param[in] Pdu The NOP In PDU received. + @param[in] Tcb The task control block. + + @retval EFI_SUCCESS The NOP In PDU is processed and the related sequence + numbers are updated. + @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol error occurred. + +**/ +EFI_STATUS +IScsiOnNopInRcvd ( + IN NET_BUF *Pdu, + IN ISCSI_TCB *Tcb + ) +{ + ISCSI_NOP_IN *NopInHdr; + EFI_STATUS Status; + + NopInHdr = (ISCSI_NOP_IN *) NetbufGetByte (Pdu, 0, NULL); + if (NopInHdr == NULL) { + return EFI_PROTOCOL_ERROR; + } + + NopInHdr->StatSN = NTOHL (NopInHdr->StatSN); + NopInHdr->ExpCmdSN = NTOHL (NopInHdr->ExpCmdSN); + NopInHdr->MaxCmdSN = NTOHL (NopInHdr->MaxCmdSN); + + if (NopInHdr->InitiatorTaskTag == ISCSI_RESERVED_TAG) { + if (NopInHdr->StatSN != Tcb->Conn->ExpStatSN) { + return EFI_PROTOCOL_ERROR; + } + } else { + Status = IScsiCheckSN (&Tcb->Conn->ExpStatSN, NopInHdr->StatSN); + if (EFI_ERROR (Status)) { + return Status; + } + } + + IScsiUpdateCmdSN (Tcb->Conn->Session, NopInHdr->MaxCmdSN, NopInHdr->ExpCmdSN); + + return EFI_SUCCESS; +} + + +/** + Execute the SCSI command issued through the EXT SCSI PASS THRU protocol. + + @param[in] PassThru The EXT SCSI PASS THRU protocol. + @param[in] Target The target ID. + @param[in] Lun The LUN. + @param[in, out] Packet The request packet containing IO request, SCSI command + buffer and buffers to read/write. + + @retval EFI_SUCCESS The SCSI command is executed and the result is updated to + the Packet. + @retval EFI_DEVICE_ERROR Session state was not as required. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. + @retval EFI_PROTOCOL_ERROR There is no such data in the net buffer. + @retval EFI_NOT_READY The target can not accept new commands. + @retval Others Other errors as indicated. + +**/ +EFI_STATUS +IScsiExecuteScsiCommand ( + IN EFI_EXT_SCSI_PASS_THRU_PROTOCOL *PassThru, + IN UINT8 *Target, + IN UINT64 Lun, + IN OUT EFI_EXT_SCSI_PASS_THRU_SCSI_REQUEST_PACKET *Packet + ) +{ + EFI_STATUS Status; + ISCSI_DRIVER_DATA *Private; + ISCSI_SESSION *Session; + EFI_EVENT TimeoutEvent; + ISCSI_CONNECTION *Conn; + ISCSI_TCB *Tcb; + NET_BUF *Pdu; + ISCSI_XFER_CONTEXT *XferContext; + UINT8 *Data; + ISCSI_IN_BUFFER_CONTEXT InBufferContext; + UINT64 Timeout; + UINT8 *PduHdr; + + Private = ISCSI_DRIVER_DATA_FROM_EXT_SCSI_PASS_THRU (PassThru); + Session = Private->Session; + Status = EFI_SUCCESS; + Tcb = NULL; + TimeoutEvent = NULL; + Timeout = 0; + + if (Session->State != SESSION_STATE_LOGGED_IN) { + Status = EFI_DEVICE_ERROR; + goto ON_EXIT; + } + + Conn = NET_LIST_USER_STRUCT_S ( + Session->Conns.ForwardLink, + ISCSI_CONNECTION, + Link, + ISCSI_CONNECTION_SIGNATURE + ); + + if (Packet->Timeout != 0) { + Timeout = MultU64x32 (Packet->Timeout, 4); + } + + Status = IScsiNewTcb (Conn, &Tcb); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + // + // Encapsulate the SCSI request packet into an iSCSI SCSI Command PDU. + // + Pdu = IScsiNewScsiCmdPdu (Packet, Lun, Tcb); + if (Pdu == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + XferContext = &Tcb->XferContext; + PduHdr = NetbufGetByte (Pdu, 0, NULL); + if (PduHdr == NULL) { + Status = EFI_PROTOCOL_ERROR; + NetbufFree (Pdu); + goto ON_EXIT; + } + XferContext->Offset = ISCSI_GET_DATASEG_LEN (PduHdr); + + // + // Transmit the SCSI Command PDU. + // + Status = TcpIoTransmit (&Conn->TcpIo, Pdu); + + NetbufFree (Pdu); + + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + if (!Session->InitialR2T && + (XferContext->Offset < Session->FirstBurstLength) && + (XferContext->Offset < Packet->OutTransferLength) + ) { + // + // Unsolicited Data-Out sequence is allowed. There is remaining SCSI + // OUT data, and the limit of FirstBurstLength is not reached. + // + XferContext->TargetTransferTag = ISCSI_RESERVED_TAG; + XferContext->DesiredLength = MIN ( + Session->FirstBurstLength, + Packet->OutTransferLength - XferContext->Offset + ); + + Data = (UINT8 *) Packet->OutDataBuffer + XferContext->Offset; + Status = IScsiSendDataOutPduSequence (Data, Lun, Tcb); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + } + + InBufferContext.InData = (UINT8 *) Packet->InDataBuffer; + InBufferContext.InDataLen = Packet->InTransferLength; + + while (!Tcb->StatusXferd) { + // + // Start the timeout timer. + // + if (Timeout != 0) { + Status = gBS->SetTimer (Conn->TimeoutEvent, TimerRelative, Timeout); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + TimeoutEvent = Conn->TimeoutEvent; + } + + // + // Try to receive PDU from target. + // + Status = IScsiReceivePdu (Conn, &Pdu, &InBufferContext, FALSE, FALSE, TimeoutEvent); + if (EFI_ERROR (Status)) { + goto ON_EXIT; + } + + PduHdr = NetbufGetByte (Pdu, 0, NULL); + if (PduHdr == NULL) { + Status = EFI_PROTOCOL_ERROR; + NetbufFree (Pdu); + goto ON_EXIT; + } + switch (ISCSI_GET_OPCODE (PduHdr)) { + case ISCSI_OPCODE_SCSI_DATA_IN: + Status = IScsiOnDataInRcvd (Pdu, Tcb, Packet); + break; + + case ISCSI_OPCODE_R2T: + Status = IScsiOnR2TRcvd (Pdu, Tcb, Lun, Packet); + break; + + case ISCSI_OPCODE_SCSI_RSP: + Status = IScsiOnScsiRspRcvd (Pdu, Tcb, Packet); + break; + + case ISCSI_OPCODE_NOP_IN: + Status = IScsiOnNopInRcvd (Pdu, Tcb); + break; + + case ISCSI_OPCODE_VENDOR_T0: + case ISCSI_OPCODE_VENDOR_T1: + case ISCSI_OPCODE_VENDOR_T2: + // + // These messages are vendor specific. Skip them. + // + break; + + default: + Status = EFI_PROTOCOL_ERROR; + break; + } + + NetbufFree (Pdu); + + if (EFI_ERROR (Status)) { + break; + } + } + +ON_EXIT: + + if (TimeoutEvent != NULL) { + gBS->SetTimer (TimeoutEvent, TimerCancel, 0); + } + + if (Tcb != NULL) { + IScsiDelTcb (Tcb); + } + + return Status; +} + + +/** + Reinstate the session on some error. + + @param[in] Session The iSCSI session + + @retval EFI_SUCCESS The session is reinstated from some error. + @retval Other Reinstatement failed. + +**/ +EFI_STATUS +IScsiSessionReinstatement ( + IN ISCSI_SESSION *Session + ) +{ + EFI_STATUS Status; + + ASSERT (Session->State != SESSION_STATE_FREE); + + // + // Abort the session and re-init it. + // + IScsiSessionAbort (Session); + IScsiSessionInit (Session, TRUE); + + // + // Login again. + // + Status = IScsiSessionLogin (Session); + + return Status; +} + + +/** + Initialize some session parameters before login. + + @param[in, out] Session The iSCSI session. + @param[in] Recovery Whether the request is from a fresh new start or recovery. + +**/ +VOID +IScsiSessionInit ( + IN OUT ISCSI_SESSION *Session, + IN BOOLEAN Recovery + ) +{ + if (!Recovery) { + Session->Signature = ISCSI_SESSION_SIGNATURE; + Session->State = SESSION_STATE_FREE; + + InitializeListHead (&Session->Conns); + InitializeListHead (&Session->TcbList); + } + + Session->Tsih = 0; + + Session->CmdSN = 1; + Session->InitiatorTaskTag = 1; + Session->NextCid = 1; + + Session->TargetPortalGroupTag = 0; + Session->MaxConnections = ISCSI_MAX_CONNS_PER_SESSION; + Session->InitialR2T = FALSE; + Session->ImmediateData = TRUE; + Session->MaxBurstLength = 262144; + Session->FirstBurstLength = MAX_RECV_DATA_SEG_LEN_IN_FFP; + Session->DefaultTime2Wait = 2; + Session->DefaultTime2Retain = 20; + Session->MaxOutstandingR2T = DEFAULT_MAX_OUTSTANDING_R2T; + Session->DataPDUInOrder = TRUE; + Session->DataSequenceInOrder = TRUE; + Session->ErrorRecoveryLevel = 0; +} + + +/** + Abort the iSCSI session. That is, reset all the connection(s), and free the + resources. + + @param[in, out] Session The iSCSI session. + +**/ +VOID +IScsiSessionAbort ( + IN OUT ISCSI_SESSION *Session + ) +{ + ISCSI_CONNECTION *Conn; + EFI_GUID *ProtocolGuid; + + if (Session->State != SESSION_STATE_LOGGED_IN) { + return ; + } + + ASSERT (!IsListEmpty (&Session->Conns)); + + while (!IsListEmpty (&Session->Conns)) { + Conn = NET_LIST_USER_STRUCT_S ( + Session->Conns.ForwardLink, + ISCSI_CONNECTION, + Link, + ISCSI_CONNECTION_SIGNATURE + ); + if (!Conn->Ipv6Flag) { + ProtocolGuid = &gEfiTcp4ProtocolGuid; + } else { + ProtocolGuid = &gEfiTcp6ProtocolGuid; + } + + gBS->CloseProtocol ( + Conn->TcpIo.Handle, + ProtocolGuid, + Session->Private->Image, + Session->Private->ExtScsiPassThruHandle + ); + + IScsiConnReset (Conn); + + IScsiDetatchConnection (Conn); + IScsiDestroyConnection (Conn); + } + + Session->State = SESSION_STATE_FAILED; + + return ; +} -- cgit