| /** @file | |
| The implementation of iSCSI protocol based on RFC3720. | |
| Copyright (c) 2004 - 2016, Intel Corporation. All rights reserved.<BR> | |
| This program and the accompanying materials | |
| are licensed and made available under the terms and conditions of the BSD License | |
| which accompanies this distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #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 (!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; | |
| BOOLEAN MediaPresent; | |
| // | |
| // Check media status before session login. | |
| // | |
| MediaPresent = TRUE; | |
| NetLibDetectMedia (Session->Private->Controller, &MediaPresent); | |
| if (!MediaPresent) { | |
| 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 througth 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 coonection 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 transistion. | |
| // 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. | |
| // Temporary not supported. | |
| continue; | |
| } | |
| // | |
| // Save the origial user setting which specifies the proxy/virtual iSCSI target. | |
| // | |
| NvData->OriginalTargetPort = NvData->TargetPort; | |
| if (*TargetAddress == ',') { | |
| // | |
| // Comma and the portal group tag MUST be ommitted 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 origial 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; | |
| } | |
| 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 parmeter 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 fuction 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 funtion is Mininum. | |
| // | |
| 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); | |
| } | |
| /** | |
| Find the task control block by the initator task tag. | |
| @param[in] TcbList The tcb list. | |
| @param[in] InitiatorTaskTag The initiator task tag. | |
| @return The task control block found. | |
| @retval NULL The task control block cannot be found. | |
| **/ | |
| ISCSI_TCB * | |
| IScsiFindTcbByITT ( | |
| IN LIST_ENTRY *TcbList, | |
| IN UINT32 InitiatorTaskTag | |
| ) | |
| { | |
| ISCSI_TCB *Tcb; | |
| LIST_ENTRY *Entry; | |
| Tcb = NULL; | |
| NET_LIST_FOR_EACH (Entry, TcbList) { | |
| Tcb = NET_LIST_USER_STRUCT (Entry, ISCSI_TCB, Link); | |
| if (Tcb->InitiatorTaskTag == InitiatorTaskTag) { | |
| break; | |
| } | |
| } | |
| return 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 immeidate | |
| // 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_SUCCES 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_SUCCES The check on the Data IN PDU is passed and some update | |
| actions are taken. | |
| @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol errror 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_SUCCES The R2T PDU is valid and the solicited data is sent out. | |
| @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol errror 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_SUCCES The Response PDU is processed. | |
| @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol errror 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_SUCCES The NOP In PDU is processed and the related sequence | |
| numbers are updated. | |
| @retval EFI_PROTOCOL_ERROR Some kind of iSCSI protocol errror 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_SUCCES 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 ; | |
| } |