diff --git a/src/include/gpxe/iscsi.h b/src/include/gpxe/iscsi.h new file mode 100644 index 00000000..639da5e5 --- /dev/null +++ b/src/include/gpxe/iscsi.h @@ -0,0 +1,460 @@ +#ifndef _ISCSI_H +#define _ISCSI_H + +/** @file + * + * iSCSI protocol + * + */ + +#include +#include +#include + +/** + * iSCSI segment lengths + * + * iSCSI uses an icky structure with one one-byte field (a dword + * count) and one three-byte field (a byte count). This structure, + * and the accompanying macros, relieve some of the pain. + */ +union iscsi_segment_lengths { + struct { + /** The AHS length (measured in dwords) */ + uint8_t ahs_len; + /** The data length (measured in bytes), in network + * byte order + */ + uint8_t data_len[3]; + } bytes; + /** Ths data length (measured in bytes), in network byte + * order, with ahs_len as the first byte. + */ + uint32_t ahs_and_data_len; +}; + +/** The length of the additional header segment, in dwords */ +#define ISCSI_AHS_LEN( segment_lengths ) \ + ( (segment_lengths).bytes.ahs_len ) + +/** The length of the data segment, in bytes, excluding any padding */ +#define ISCSI_DATA_LEN( segment_lengths ) \ + ( ntohl ( (segment_lengths).ahs_and_data_len ) & 0xffffff ) + +/** The padding of the data segment, in bytes */ +#define ISCSI_DATA_PAD_LEN( segment_lengths ) \ + ( ( 0 - (segment_lengths).bytes.data_len[2] ) & 0x03 ) + +/** Set additional header and data segment lengths */ +#define ISCSI_SET_LENGTHS( segment_lengths, ahs_len, data_len ) do { \ + (segment_lengths).ahs_and_data_len = \ + htonl ( data_len | ( ahs_len << 24 ) ); \ + } while ( 0 ) + +/** + * iSCSI basic header segment common fields + * + */ +struct iscsi_bhs_common { + /** Opcode */ + uint8_t opcode; + /** Flags */ + uint8_t flags; + /** Fields specific to the PDU type */ + uint8_t other_a[2]; + /** Segment lengths */ + union iscsi_segment_lengths lengths; + /** Fields specific to the PDU type */ + uint8_t other_b[8]; + /** Initiator Task Tag */ + uint32_t itt; + /** Fields specific to the PDU type */ + uint8_t other_c[28]; +}; + +/** Opcode mask */ +#define ISCSI_OPCODE_MASK 0x3f + +/** Immediate delivery */ +#define ISCSI_FLAG_IMMEDIATE 0x40 + +/** Final PDU of a sequence */ +#define ISCSI_FLAG_FINAL 0x80 + +/** + * iSCSI login request basic header segment + * + */ +struct iscsi_bhs_login_request { + /** Opcode */ + uint8_t opcode; + /** Flags */ + uint8_t flags; + /** Maximum supported version number */ + uint8_t version_max; + /** Minimum supported version number */ + uint8_t version_min; + /** Segment lengths */ + union iscsi_segment_lengths lengths; + /** Initiator session ID (IANA format) enterprise number and flags */ + uint32_t isid_iana_en; + /** Initiator session ID (IANA format) qualifier */ + uint16_t isid_iana_qual; + /** Target session identifying handle */ + uint16_t tsih; + /** Initiator Task Tag */ + uint32_t itt; + /** Connection ID */ + uint16_t cid; + /** Reserved */ + uint16_t reserved_a; + /** Command sequence number */ + uint32_t cmdsn; + /** Expected status sequence number */ + uint32_t expstatsn; + /** Reserved */ + uint8_t reserved_b[16]; +}; + +/** Login request opcode */ +#define ISCSI_OPCODE_LOGIN_REQUEST 0x03 + +/** Willingness to transition to next stage */ +#define ISCSI_LOGIN_FLAG_TRANSITION 0x80 + +/** Key=value pairs continued in subsequent request */ +#define ISCSI_LOGIN_FLAG_CONTINUE 0x40 + +/* Current stage values and mask */ +#define ISCSI_LOGIN_CSG_MASK 0x0c +#define ISCSI_LOGIN_CSG_SECURITY_NEGOTIATION 0x00 +#define ISCSI_LOGIN_CSG_OPERATIONAL_NEGOTIATION 0x04 +#define ISCSI_LOGIN_CSG_FULL_FEATURE_PHASE 0x0c + +/* Next stage values and mask */ +#define ISCSI_LOGIN_NSG_MASK 0x03 +#define ISCSI_LOGIN_NSG_SECURITY_NEGOTIATION 0x00 +#define ISCSI_LOGIN_NSG_OPERATIONAL_NEGOTIATION 0x01 +#define ISCSI_LOGIN_NSG_FULL_FEATURE_PHASE 0x03 + +/** ISID IANA format marker */ +#define ISCSI_ISID_IANA 0x40000000 + +/** Fen Systems Ltd. IANA enterprise number + * + * Permission is hereby granted to use Fen Systems Ltd.'s IANA + * enterprise number with this iSCSI implementation. + */ +#define IANA_EN_FEN_SYSTEMS 10019 + +/** + * iSCSI login response basic header segment + * + */ +struct iscsi_bhs_login_response { + /** Opcode */ + uint8_t opcode; + /** Flags */ + uint8_t flags; + /** Maximum supported version number */ + uint8_t version_max; + /** Minimum supported version number */ + uint8_t version_min; + /** Segment lengths */ + union iscsi_segment_lengths lengths; + /** Initiator session ID (IANA format) enterprise number and flags */ + uint32_t isid_iana_en; + /** Initiator session ID (IANA format) qualifier */ + uint16_t isid_iana_qual; + /** Target session identifying handle */ + uint16_t tsih; + /** Initiator Task Tag */ + uint32_t itt; + /** Reserved */ + uint32_t reserved_a; + /** Status sequence number */ + uint32_t statsn; + /** Expected command sequence number */ + uint32_t expcmdsn; + /** Maximum command sequence number */ + uint32_t maxcmdsn; + /** Status class */ + uint8_t status_class; + /** Status detail */ + uint8_t status_detail; + /** Reserved */ + uint8_t reserved_b[10]; +}; + +/** Login response opcode */ +#define ISCSI_OPCODE_LOGIN_RESPONSE 0x23 + +/** + * iSCSI SCSI command basic header segment + * + */ +struct iscsi_bhs_scsi_command { + /** Opcode */ + uint8_t opcode; + /** Flags */ + uint8_t flags; + /** Reserved */ + uint16_t reserved_a; + /** Segment lengths */ + union iscsi_segment_lengths lengths; + /** SCSI Logical Unit Number */ + uint8_t lun[8]; + /** Initiator Task Tag */ + uint32_t itt; + /** Expected data transfer length */ + uint32_t exp_len; + /** Command sequence number */ + uint32_t cmdsn; + /** Expected status sequence number */ + uint32_t expstatsn; + /** SCSI Command Descriptor Block (CDB) */ + union scsi_cdb cdb; +}; + +/** SCSI command opcode */ +#define ISCSI_OPCODE_SCSI_COMMAND 0x01 + +/** Command will read data */ +#define ISCSI_COMMAND_FLAG_READ 0x40 + +/** Command will write data */ +#define ISCSI_COMMAND_FLAG_WRITE 0x20 + +/* Task attributes */ +#define ISCSI_COMMAND_ATTR_UNTAGGED 0x00 +#define ISCSI_COMMAND_ATTR_SIMPLE 0x01 +#define ISCSI_COMMAND_ATTR_ORDERED 0x02 +#define ISCSI_COMMAND_ATTR_HEAD_OF_QUEUE 0x03 +#define ISCSI_COMMAND_ATTR_ACA 0x04 + +/** + * iSCSI SCSI response basic header segment + * + */ +struct iscsi_bhs_scsi_response { + /** Opcode */ + uint8_t opcode; + /** Flags */ + uint8_t flags; + /** Response code */ + uint8_t response; + /** SCSI status code */ + uint8_t status; + /** Segment lengths */ + union iscsi_segment_lengths lengths; + /** Reserved */ + uint8_t reserved_a[8]; + /** Initiator Task Tag */ + uint32_t itt; + /** SNACK tag */ + uint32_t snack; + /** Status sequence number */ + uint32_t statsn; + /** Expected command sequence number */ + uint32_t expcmdsn; + /** Maximum command sequence number */ + uint32_t maxcmdsn; + /** Expected data sequence number */ + uint32_t expdatasn; + /** Reserved */ + uint8_t reserved_b[8]; +}; + +/** SCSI response opcode */ +#define ISCSI_OPCODE_SCSI_RESPONSE 0x21 + +/** SCSI command completed at target */ +#define ISCSI_RESPONSE_COMMAND_COMPLETE 0x00 + +/** SCSI target failure */ +#define ISCSI_RESPONSE_TARGET_FAILURE 0x01 + +/** + * iSCSI data in basic header segment + * + */ +struct iscsi_bhs_data_in { + /** Opcode */ + uint8_t opcode; + /** Flags */ + uint8_t flags; + /** Reserved */ + uint8_t reserved_a; + /** SCSI status code */ + uint8_t status; + /** Segment lengths */ + union iscsi_segment_lengths lengths; + /** Logical Unit Number */ + uint8_t lun[8]; + /** Initiator Task Tag */ + uint32_t itt; + /** Target Transfer Tag */ + uint32_t ttt; + /** Status sequence number */ + uint32_t statsn; + /** Expected command sequence number */ + uint32_t expcmdsn; + /** Maximum command sequence number */ + uint32_t maxcmdsn; + /** Data sequence number */ + uint32_t datasn; + /** Buffer offset */ + uint32_t offset; + /** Residual count */ + uint32_t residual_count; +}; + +/** Data in opcode */ +#define ISCSI_OPCODE_DATA_IN 0x25 + +/** Data requires acknowledgement */ +#define ISCSI_DATA_FLAG_ACKNOWLEDGE 0x40 + +/** Data overflow occurred */ +#define ISCSI_DATA_FLAG_OVERFLOW 0x04 + +/** Data underflow occurred */ +#define ISCSI_DATA_FLAG_UNDERFLOW 0x02 + +/** SCSI status code and verflow/underflow flags are valid */ +#define ISCSI_DATA_FLAG_STATUS 0x01 + +/** + * An iSCSI basic header segment + */ +union iscsi_bhs { + struct iscsi_bhs_common common; + struct iscsi_bhs_login_request login_request; + struct iscsi_bhs_login_response login_response; + struct iscsi_bhs_scsi_command scsi_command; + struct iscsi_bhs_scsi_response scsi_response; + struct iscsi_bhs_data_in data_in; + unsigned char bytes[ sizeof ( struct iscsi_bhs_common ) ]; +}; + +/** State */ +enum iscsi_state { + /** In the process of logging in */ + ISCSI_STATE_FAILED = -1, + ISCSI_STATE_NOT_CONNECTED = 0, + ISCSI_STATE_IDLE, + ISCSI_STATE_LOGGING_IN, + ISCSI_STATE_READING_DATA, +}; + +/** State of an iSCSI TX engine */ +enum iscsi_tx_state { + /** Nothing to send */ + ISCSI_TX_IDLE = 0, + /** Sending the basic header segment */ + ISCSI_TX_BHS, + /** Sending the additional header segment */ + ISCSI_TX_AHS, + /** Sending the data segment */ + ISCSI_TX_DATA, + /** Sending the data segment padding */ + ISCSI_TX_DATA_PADDING, +}; + +/** State of an iSCSI RX engine */ +enum iscsi_rx_state { + /** Receiving the basic header segment */ + ISCSI_RX_BHS = 0, + /** Receiving the additional header segment */ + ISCSI_RX_AHS, + /** Receiving the data segment */ + ISCSI_RX_DATA, + /** Receiving the data segment padding */ + ISCSI_RX_DATA_PADDING, +}; + +/** An iSCSI session */ +struct iscsi_session { + /** TCP connection for this session */ + struct tcp_connection tcp; + + /** Initiator IQN */ + const char *initiator; + /** Target IQN */ + const char *target; + + /** Block size in bytes */ + size_t block_size; + /** Starting block number of the current data transfer */ + unsigned long block_start; + /** Block count of the current data transfer */ + unsigned long block_count; + /** Block read callback function + * + * Note that this may be called several times, since it is + * called per-packet rather than per-block. + */ + void ( * block_read_callback ) ( void *private, const void *data, + unsigned long offset, size_t len ); + /** Block read callback private data + * + * This is passed to block_read_callback() + */ + void *block_read_private; + + /** State of the session */ + enum iscsi_state state; + /** Target session identifying handle + * + * This is assigned by the target when we first log in, and + * must be reused on subsequent login attempts. + */ + uint16_t tsih; + + /** Initiator task tag + * + * This is the tag of the current command. It is incremented + * whenever a final response PDU is received. + */ + uint32_t itt; + /** Command sequence number + * + * This is the sequence number of the current command, used to + * fill out the CmdSN field in iSCSI request PDUs. It is + * updated with the value of the ExpCmdSN field whenever we + * receive an iSCSI response PDU containing such a field. + */ + uint32_t cmdsn; + /** Status sequence number + * + * This is the most recent status sequence number present in + * the StatSN field of an iSCSI response PDU containing such a + * field. Whenever we send an iSCSI request PDU, we fill out + * the ExpStatSN field with this value plus one. + */ + uint32_t statsn; + + /** Basic header segment for current TX PDU */ + union iscsi_bhs tx_bhs; + /** State of the TX engine */ + enum iscsi_tx_state tx_state; + /** Byte offset within the current TX state */ + size_t tx_offset; + + /** Basic header segment for current RX PDU */ + union iscsi_bhs rx_bhs; + /** State of the RX engine */ + enum iscsi_rx_state rx_state; + /** Byte offset within the current RX state */ + size_t rx_offset; +}; + +static inline int iscsi_busy ( struct iscsi_session *iscsi ) { + return ( iscsi->state > ISCSI_STATE_IDLE ); +} + +static inline int iscsi_error ( struct iscsi_session *iscsi ) { + return ( iscsi->state == ISCSI_STATE_FAILED ); +} + +#endif /* _ISCSI_H */ diff --git a/src/include/gpxe/scsi.h b/src/include/gpxe/scsi.h new file mode 100644 index 00000000..05f9010c --- /dev/null +++ b/src/include/gpxe/scsi.h @@ -0,0 +1,34 @@ +#ifndef _SCSI_H +#define _SCSI_H + +#include + +struct scsi_cdb_read_10 { + /** Opcode */ + uint8_t opcode; + /** Flags */ + uint8_t flags; + /** Start address + * + * This is a logical block number, in big-endian order. + */ + uint32_t lba; + /** Group number */ + uint8_t group; + /** Transfer length + * + * This is a logical block count. + */ + uint16_t len; + /** Control byte */ + uint8_t control; +} __attribute__ (( packed )); + +#define SCSI_OPCODE_READ_10 0x28 + +union scsi_cdb { + struct scsi_cdb_read_10 read_10; + char bytes[16]; +}; + +#endif /* _SCSI_H */ diff --git a/src/proto/iscsi.c b/src/proto/iscsi.c new file mode 100644 index 00000000..40e48e11 --- /dev/null +++ b/src/proto/iscsi.c @@ -0,0 +1,558 @@ +#include +#include +#include +#include +#include +#include + +/** @file + * + * iSCSI protocol + * + */ + +/**************************************************************************** + * + * Utility functions + * + */ + +/** + * Start up a new TX PDU + * + * @v iscsi iSCSI session + * + * This initiates the process of sending a new PDU. Only one PDU may + * be in transit at any one time. + */ +static void iscsi_start_tx ( struct iscsi_session *iscsi ) { + assert ( iscsi->tx_state == ISCSI_TX_IDLE ); + iscsi->tx_state = ISCSI_TX_BHS; + iscsi->tx_offset = 0; +} + +/** + * Mark session as failed + * + * @v iscsi iSCSI session + * + * This marks the session as permanently failed. The session will not + * be automatically logged back in. + */ +static void iscsi_fail ( struct iscsi_session *iscsi ) { + iscsi->state = ISCSI_STATE_FAILED; + tcp_close ( &iscsi->tcp ); +} + +/**************************************************************************** + * + * iSCSI SCSI command issuing + * + */ + +/** + * Start up a block read + * + * @v iscsi iSCSI session + * + */ +static void iscsi_start_read_block ( struct iscsi_session *iscsi ) { + struct iscsi_bhs_scsi_command *command = &iscsi->tx_bhs.scsi_command; + struct scsi_cdb_read_10 *read = &command->cdb.read_10; + + assert ( iscsi->block_size != 0 ); + assert ( iscsi->block_count != 0 ); + assert ( iscsi->block_read_callback != NULL ); + + /* Construct BHS */ + memset ( command, 0, sizeof ( *command ) ); + command->opcode = ISCSI_OPCODE_SCSI_COMMAND; + command->flags = ( ISCSI_FLAG_FINAL | + ISCSI_COMMAND_FLAG_READ | + ISCSI_COMMAND_ATTR_SIMPLE ); + /* lengths left as zero */ + /* lun left as zero, on the assumption that no-one uses LUNs > 0 */ + command->itt = htonl ( iscsi->itt ); + command->exp_len = htonl ( iscsi->block_count * iscsi->block_size ); + command->cmdsn = htonl ( iscsi->cmdsn ); + command->expstatsn = htonl ( iscsi->statsn + 1 ); + read->opcode = SCSI_OPCODE_READ_10; + read->lba = htonl ( iscsi->block_start ); + read->len = htons ( iscsi->block_count ); + + iscsi->state = ISCSI_STATE_READING_DATA; + iscsi_start_tx ( iscsi ); +} + +/** + * Receive data segment of an iSCSI data-in PDU + * + * @v iscsi iSCSI session + * @v data Received data + * @v len Length of received data + * @v remaining Data remaining after this data + * + */ +static void iscsi_rx_data_in ( struct iscsi_session *iscsi, void *data, + size_t len, size_t remaining ) { + struct iscsi_bhs_data_in *data_in = &iscsi->rx_bhs.data_in; + unsigned long offset; + + /* Update cmdsn and statsn */ + iscsi->cmdsn = ntohl ( data_in->expcmdsn ); + iscsi->statsn = ntohl ( data_in->statsn ); + + /* Process data via callback */ + offset = ntohl ( data_in->offset ) + iscsi->rx_offset; + iscsi->block_read_callback ( iscsi->block_read_private, + data, offset, len ); + + /* If this is the end, mark state as idle */ + if ( ( data_in->flags & ISCSI_FLAG_FINAL ) && ( remaining == 0 ) ) + iscsi->state = ISCSI_STATE_IDLE; +} + +/**************************************************************************** + * + * iSCSI login + * + */ + +/** + * Build iSCSI login request strings + * + * @v iscsi iSCSI session + * + * These are the initial set of strings sent in the first login + * request PDU. + */ +static int iscsi_build_login_request_strings ( struct iscsi_session *iscsi, + void *data, size_t len ) { + return snprintf ( data, len, + "InitiatorName=%s:initiator%c" + "TargetName=%s%c" + "MaxRecvDataSegmentLength=512%c" + "SessionType=Normal%c" + "DataDigest=None%c" + "HeaderDigest=None%c", + iscsi->initiator, 0, iscsi->target, 0, + 0, 0, 0, 0 ); +} + +/** + * Transmit data segment of an iSCSI login request PDU + * + * @v iscsi iSCSI session + * + * For login requests, the data segment consists of the login strings. + */ +static void iscsi_tx_login_request ( struct iscsi_session *iscsi ) { + int len; + + len = iscsi_build_login_request_strings ( iscsi, tcp_buffer, + tcp_buflen ); + tcp_send ( &iscsi->tcp, tcp_buffer + iscsi->tx_offset, + len - iscsi->tx_offset ); +} + +/** + * Start up a login request + * + * @v iscsi iSCSI session + * + */ +static void iscsi_start_login ( struct iscsi_session *iscsi ) { + struct iscsi_bhs_login_request *request = &iscsi->tx_bhs.login_request; + int len; + + /* Construct login request BHS */ + memset ( request, 0, sizeof ( *request ) ); + request->opcode = ( ISCSI_OPCODE_LOGIN_REQUEST | + ISCSI_FLAG_IMMEDIATE ); + request->flags = ( ISCSI_LOGIN_FLAG_TRANSITION | + ISCSI_LOGIN_CSG_OPERATIONAL_NEGOTIATION | + ISCSI_LOGIN_NSG_FULL_FEATURE_PHASE ); + /* version_max and version_min left as zero */ + len = iscsi_build_login_request_strings ( iscsi, NULL, 0 ); + ISCSI_SET_LENGTHS ( request->lengths, 0, len ); + request->isid_iana_en = htonl ( ISCSI_ISID_IANA | + IANA_EN_FEN_SYSTEMS ); + /* isid_iana_qual left as zero */ + request->tsih = htons ( iscsi->tsih ); + /* itt left as zero */ + /* cid left as zero */ + request->cmdsn = htonl ( iscsi->cmdsn ); + request->expstatsn = htonl ( iscsi->statsn + 1 ); + + iscsi->state = ISCSI_STATE_LOGGING_IN; + iscsi_start_tx ( iscsi ); +} + +/** + * Receive data segment of an iSCSI login response PDU + * + * @v iscsi iSCSI session + * @v data Received data + * @v len Length of received data + * @v remaining Data remaining after this data + * + */ +static void iscsi_rx_login_response ( struct iscsi_session *iscsi, + void *data __unused, + size_t len __unused, + size_t remaining __unused ) { + struct iscsi_bhs_login_request *request = &iscsi->tx_bhs.login_request; + struct iscsi_bhs_login_response *response + = &iscsi->rx_bhs.login_response; + + /* Sanity check */ + if ( iscsi->state != ISCSI_STATE_LOGGING_IN ) { + printf ( "Spurious iSCSI login response\n" ); + iscsi_fail ( iscsi ); + return; + } + + /* Check for fatal errors */ + if ( response->status_class != 0 ) { + printf ( "iSCSI login failure: class %02x detail %02x\n", + response->status_class, response->status_detail ); + iscsi_fail ( iscsi ); + return; + } + + /* Update cmdsn and statsn */ + iscsi->cmdsn = ntohl ( response->expcmdsn ); + iscsi->statsn = ntohl ( response->statsn ); + + /* If server did not transition, we send it another login + * request with empty strings. + */ + if ( ! ( response->flags & ISCSI_LOGIN_FLAG_TRANSITION ) ) { + ISCSI_SET_LENGTHS ( request->lengths, 0, 0 ); + iscsi_start_tx ( iscsi ); + return; + } + + /* Record TSIH for future reference */ + iscsi->tsih = ntohl ( response->tsih ); + + /* Start reading data */ + iscsi_start_read_block ( iscsi ); +} + +/**************************************************************************** + * + * iSCSI to TCP interface + * + */ + +static inline struct iscsi_session * +tcp_to_iscsi ( struct tcp_connection *conn ) { + return container_of ( conn, struct iscsi_session, tcp ); +} + +static void iscsi_aborted ( struct tcp_connection *conn ) { + struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); + +} + +static void iscsi_timedout ( struct tcp_connection *conn ) { + struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); + +} + +static void iscsi_closed ( struct tcp_connection *conn ) { + struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); + +} + +static void iscsi_connected ( struct tcp_connection *conn ) { + struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); + + /* Prepare to receive PDUs. */ + iscsi->rx_state = ISCSI_RX_BHS; + iscsi->rx_offset = 0; + + /* TX state should already have been set up */ + assert ( iscsi->tx_state != ISCSI_TX_IDLE ); + assert ( iscsi->tx_offset == 0 ); +} + +/** + * Transmit data segment of an iSCSI PDU + * + * @v iscsi iSCSI session + * + * Handle transmission of part of a PDU data segment. iscsi::tx_bhs + * will be valid when this is called. + */ +static void iscsi_tx_data ( struct iscsi_session *iscsi ) { + struct iscsi_bhs_common *common = &iscsi->tx_bhs.common; + + switch ( common->opcode & ISCSI_OPCODE_MASK ) { + case ISCSI_OPCODE_LOGIN_REQUEST: + iscsi_tx_login_request ( iscsi ); + break; + default: + assert ( 0 ); + break; + } +} + +/** + * Handle TCP ACKs + * + * @v iscsi iSCSI session + * + * Updates iscsi->tx_offset and, if applicable, transitions to the + * next TX state. + */ +static void iscsi_acked ( struct tcp_connection *conn, size_t len ) { + struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); + struct iscsi_bhs_common *common = &iscsi->tx_bhs.common; + size_t max_tx_offset; + enum iscsi_tx_state next_state; + + iscsi->tx_offset += len; + while ( 1 ) { + switch ( iscsi->tx_state ) { + case ISCSI_TX_BHS: + max_tx_offset = sizeof ( iscsi->tx_bhs ); + next_state = ISCSI_TX_AHS; + break; + case ISCSI_TX_AHS: + max_tx_offset = 4 * ISCSI_AHS_LEN ( common->lengths ); + next_state = ISCSI_TX_DATA; + break; + case ISCSI_TX_DATA: + max_tx_offset = ISCSI_DATA_LEN ( common->lengths ); + next_state = ISCSI_TX_DATA_PADDING; + break; + case ISCSI_TX_DATA_PADDING: + max_tx_offset = ISCSI_DATA_PAD_LEN ( common->lengths ); + next_state = ISCSI_TX_IDLE; + break; + case ISCSI_TX_IDLE: + return; + default: + assert ( 0 ); + return; + } + assert ( iscsi->tx_offset <= max_tx_offset ); + + /* If the whole of the current portion has not yet + * been acked, stay in this state for now. + */ + if ( iscsi->tx_offset != max_tx_offset ) + return; + + iscsi->tx_state = next_state; + iscsi->tx_offset = 0; + } +} + +/** + * Transmit iSCSI PDU + * + * @v iscsi iSCSI session + * + * Constructs data to be sent for the current TX state + */ +static void iscsi_senddata ( struct tcp_connection *conn ) { + struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); + struct iscsi_bhs_common *common = &iscsi->tx_bhs.common; + static const char pad[] = { '\0', '\0', '\0' }; + + switch ( iscsi->tx_state ) { + case ISCSI_TX_IDLE: + /* Do nothing */ + break; + case ISCSI_TX_BHS: + tcp_send ( conn, &iscsi->tx_bhs.bytes[iscsi->tx_offset], + ( sizeof ( iscsi->tx_bhs ) - iscsi->tx_offset ) ); + break; + case ISCSI_TX_AHS: + /* We don't yet have an AHS transmission mechanism */ + assert ( 0 ); + break; + case ISCSI_TX_DATA: + iscsi_tx_data ( iscsi ); + break; + case ISCSI_TX_DATA_PADDING: + tcp_send ( conn, pad, ( ISCSI_DATA_PAD_LEN ( common->lengths ) + - iscsi->tx_offset ) ); + break; + default: + assert ( 0 ); + break; + } +} + +/** + * Receive data segment of an iSCSI PDU + * + * @v iscsi iSCSI session + * @v data Received data + * @v len Length of received data + * @v remaining Data remaining after this data + * + * Handle processing of part of a PDU data segment. iscsi::rx_bhs + * will be valid when this is called. + */ +static void iscsi_rx_data ( struct iscsi_session *iscsi, void *data, + size_t len, size_t remaining ) { + struct iscsi_bhs_common *common = &iscsi->rx_bhs.common; + + switch ( common->opcode & ISCSI_OPCODE_MASK ) { + case ISCSI_OPCODE_LOGIN_RESPONSE: + iscsi_rx_login_response ( iscsi, data, len, remaining ); + break; + case ISCSI_OPCODE_DATA_IN: + iscsi_rx_data_in ( iscsi, data, len, remaining ); + break; + default: + printf ( "Unknown iSCSI opcode %02x\n", common->opcode ); + break; + } +} + +/** + * Discard portion of an iSCSI PDU. + * + * @v iscsi iSCSI session + * @v data Received data + * @v len Length of received data + * @v remaining Data remaining after this data + * + * This discards data from a portion of a received PDU. + */ +static void iscsi_rx_discard ( struct iscsi_session *iscsi __unused, + void *data __unused, size_t len __unused, + size_t remaining __unused ) { + /* Do nothing */ +} + +/** + * Receive basic header segment of an iSCSI PDU + * + * @v iscsi iSCSI session + * @v data Received data + * @v len Length of received data + * @v remaining Data remaining after this data + * + * This fills in iscsi::rx_bhs with the data from the BHS portion of + * the received PDU. + */ +static void iscsi_rx_bhs ( struct iscsi_session *iscsi, void *data, + size_t len, size_t remaining __unused ) { + memcpy ( &iscsi->rx_bhs.bytes[iscsi->rx_offset], data, len ); +} + +/** + * Receive new data + * + * @v tcp TCP connection + * @v data Received data + * @v len Length of received data + * + * This handles received PDUs. The receive strategy is to fill in + * iscsi::rx_bhs with the contents of the BHS portion of the PDU, + * throw away any AHS portion, and then process each part of the data + * portion as it arrives. The data processing routine therefore + * always has a full copy of the BHS available, even for portions of + * the data in different packets to the BHS. + */ +static void iscsi_newdata ( struct tcp_connection *conn, void *data, + size_t len ) { + struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); + struct iscsi_bhs_common *common = &iscsi->rx_bhs.common; + void ( *process ) ( struct iscsi_session *iscsi, void *data, + size_t len, size_t remaining ); + size_t max_rx_offset; + enum iscsi_rx_state next_state; + size_t frag_len; + size_t remaining; + + while ( 1 ) { + switch ( iscsi->rx_state ) { + case ISCSI_RX_BHS: + process = iscsi_rx_bhs; + max_rx_offset = sizeof ( iscsi->rx_bhs ); + next_state = ISCSI_RX_AHS; + break; + case ISCSI_RX_AHS: + process = iscsi_rx_discard; + max_rx_offset = 4 * ISCSI_AHS_LEN ( common->lengths ); + next_state = ISCSI_RX_DATA; + break; + case ISCSI_RX_DATA: + process = iscsi_rx_data; + max_rx_offset = ISCSI_DATA_LEN ( common->lengths ); + next_state = ISCSI_RX_DATA_PADDING; + break; + case ISCSI_RX_DATA_PADDING: + process = iscsi_rx_discard; + max_rx_offset = ISCSI_DATA_PAD_LEN ( common->lengths ); + next_state = ISCSI_RX_BHS; + break; + default: + assert ( 0 ); + return; + } + + frag_len = max_rx_offset - iscsi->rx_offset; + if ( frag_len > len ) + frag_len = len; + remaining = max_rx_offset - iscsi->rx_offset - frag_len; + process ( iscsi, data, frag_len, remaining ); + + iscsi->rx_offset += frag_len; + data += frag_len; + len -= frag_len; + + /* If all the data for this state has not yet been + * received, stay in this state for now. + */ + if ( iscsi->rx_offset != max_rx_offset ) + return; + + iscsi->rx_state = next_state; + iscsi->rx_offset = 0; + } +} + +/** iSCSI TCP operations */ +static struct tcp_operations iscsi_tcp_operations = { + .aborted = iscsi_aborted, + .timedout = iscsi_timedout, + .closed = iscsi_closed, + .connected = iscsi_connected, + .acked = iscsi_acked, + .newdata = iscsi_newdata, + .senddata = iscsi_senddata, +}; + +/** + * Wake up session + * + * @v iscsi iSCSI session + * + */ +void iscsi_wakeup ( struct iscsi_session *iscsi ) { + iscsi->tcp.tcp_op = &iscsi_tcp_operations; + + switch ( iscsi->state ) { + case ISCSI_STATE_NOT_CONNECTED: + case ISCSI_STATE_FAILED: + if ( tcp_connect ( &iscsi->tcp ) != 0 ) + iscsi_fail ( iscsi ); + iscsi_start_login ( iscsi ); + break; + case ISCSI_STATE_IDLE: + iscsi_start_read_block ( iscsi ); + break; + default: + /* Stay in same state */ + break; + } +}