First draft of iSCSI protocol support. Is capable of retrieving disk
authorMichael Brown <mcb30@etherboot.org>
Wed, 5 Apr 2006 11:44:56 +0000 (11:44 +0000)
committerMichael Brown <mcb30@etherboot.org>
Wed, 5 Apr 2006 11:44:56 +0000 (11:44 +0000)
blocks.

src/include/gpxe/iscsi.h [new file with mode: 0644]
src/include/gpxe/scsi.h [new file with mode: 0644]
src/proto/iscsi.c [new file with mode: 0644]

diff --git a/src/include/gpxe/iscsi.h b/src/include/gpxe/iscsi.h
new file mode 100644 (file)
index 0000000..639da5e
--- /dev/null
@@ -0,0 +1,460 @@
+#ifndef _ISCSI_H
+#define _ISCSI_H
+
+/** @file
+ *
+ * iSCSI protocol
+ *
+ */
+
+#include <stdint.h>
+#include <gpxe/tcp.h>
+#include <gpxe/scsi.h>
+
+/**
+ * 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 (file)
index 0000000..05f9010
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef _SCSI_H
+#define _SCSI_H
+
+#include <stdint.h>
+
+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 (file)
index 0000000..40e48e1
--- /dev/null
@@ -0,0 +1,558 @@
+#include <stddef.h>
+#include <string.h>
+#include <vsprintf.h>
+#include <assert.h>
+#include <byteswap.h>
+#include <gpxe/iscsi.h>
+
+/** @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;
+       }
+}