Merge branch 'master' into iscsi-update
authorMichael Brown <mcb30@etherboot.org>
Sun, 8 Jul 2007 21:06:33 +0000 (22:06 +0100)
committerMichael Brown <mcb30@etherboot.org>
Sun, 8 Jul 2007 21:06:33 +0000 (22:06 +0100)
src/drivers/scsi/iscsidev.c [deleted file]
src/include/gpxe/iscsi.h
src/include/gpxe/scsi.h
src/net/tcp/iscsi.c
src/tests/iscsiboot.c
src/usr/autoboot.c

diff --git a/src/drivers/scsi/iscsidev.c b/src/drivers/scsi/iscsidev.c
deleted file mode 100644 (file)
index aab9903..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of the
- * License, or any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include <stddef.h>
-#include <gpxe/async.h>
-#include <gpxe/iscsi.h>
-
-/** @file
- *
- * iSCSI SCSI device
- *
- */
-
-/**
- * Issue SCSI command via iSCSI device
- *
- * @v scsi             SCSI device
- * @v command          SCSI command
- * @ret rc             Return status code
- */
-static int iscsi_command ( struct scsi_device *scsi,
-                          struct scsi_command *command ) {
-       struct iscsi_device *iscsidev
-               = container_of ( scsi, struct iscsi_device, scsi );
-       struct async async;
-
-       return async_block ( &async, iscsi_issue ( &iscsidev->iscsi, command,
-                                                  &async ) );
-}
-
-/**
- * Initialise iSCSI device
- *
- * @v iscsidev         iSCSI device
- */
-int init_iscsidev ( struct iscsi_device *iscsidev ) {
-       int rc;
-
-       iscsidev->scsi.command = iscsi_command;
-       iscsidev->scsi.lun = iscsidev->iscsi.lun;
-       if ( ( rc = init_scsidev ( &iscsidev->scsi ) ) != 0 )
-               goto err;
-
-       return 0;
-
- err:
-       fini_iscsidev ( iscsidev );
-       return rc;
-}
-
-/**
- * Shut down iSCSI device
- *
- * @v iscsidev         iSCSI device
- */
-void fini_iscsidev ( struct iscsi_device *iscsidev ) {
-       iscsi_shutdown ( &iscsidev->iscsi );
-}
index 62b789c..d9dd430 100644 (file)
@@ -8,10 +8,11 @@
  */
 
 #include <stdint.h>
-#include <gpxe/stream.h>
-#include <gpxe/async.h>
 #include <gpxe/scsi.h>
 #include <gpxe/chap.h>
+#include <gpxe/refcnt.h>
+#include <gpxe/xfer.h>
+#include <gpxe/process.h>
 
 /** Default iSCSI port */
 #define ISCSI_PORT 3260
@@ -486,21 +487,21 @@ enum iscsi_rx_state {
 
 /** An iSCSI session */
 struct iscsi_session {
-       /** Initiator IQN */
-       const char *initiator_iqn;
+       /** Reference counter */
+       struct refcnt refcnt;
+
+       /** Transport-layer socket */
+       struct xfer_interface socket;
+
        /** Target address */
-       struct sockaddr target;
+       char *target_address;
+       /** Target port */
+       unsigned int target_port;
        /** Target IQN */
-       const char *target_iqn;
+       char *target_iqn;
        /** Logical Unit Number (LUN) */
        uint64_t lun;
-       /** Username */
-       const char *username;
-       /** Password */
-       const char *password;
 
-       /** Stream application for this session */
-       struct stream_application stream;
        /** Session status
         *
         * This is the bitwise-OR of zero or more ISCSI_STATUS_XXX
@@ -569,10 +570,8 @@ struct iscsi_session {
        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;
-       /** Length of the current TX state */
-       size_t tx_len;
+       /** TX process */
+       struct process process;
 
        /** Basic header segment for current RX PDU */
        union iscsi_bhs rx_bhs;
@@ -590,8 +589,11 @@ struct iscsi_session {
         * Set to NULL when command is complete.
         */
        struct scsi_command *command;
-       /** Asynchronous operation for the current iSCSI operation */
-       struct async async;
+       /** SCSI command return code
+        *
+        * Set to -EINPROGRESS while command is processing.
+        */
+       int rc;
        /** Instant return code
         *
         * Set to a non-zero value if all requests should return
@@ -637,20 +639,7 @@ struct iscsi_session {
 /** Maximum number of retries at connecting */
 #define ISCSI_MAX_RETRIES 2
 
-extern int iscsi_issue ( struct iscsi_session *iscsi,
-                        struct scsi_command *command,
-                        struct async *parent );
-extern void iscsi_shutdown ( struct iscsi_session *iscsi );
-
-/** An iSCSI device */
-struct iscsi_device {
-       /** SCSI device interface */
-       struct scsi_device scsi;
-       /** iSCSI protocol instance */
-       struct iscsi_session iscsi;
-};
-
-extern int init_iscsidev ( struct iscsi_device *iscsidev );
-extern void fini_iscsidev ( struct iscsi_device *iscsidev );
+extern int iscsi_attach ( struct scsi_device *scsi, const char *root_path );
+extern void iscsi_detach ( struct scsi_device *scsi );
 
 #endif /* _GPXE_ISCSI_H */
index 9d5952d..e820117 100644 (file)
@@ -4,6 +4,7 @@
 #include <stdint.h>
 #include <gpxe/blockdev.h>
 #include <gpxe/uaccess.h>
+#include <gpxe/refcnt.h>
 
 /** @file
  *
@@ -229,7 +230,7 @@ struct scsi_command {
         * Must be zero if @c data_in is NULL
         */
        size_t data_in_len;
-       /** SCSI statua code */
+       /** SCSI status code */
        uint8_t status;
        /** SCSI sense response code */
        uint8_t sense_response;
@@ -260,6 +261,8 @@ struct scsi_device {
         */
        int ( * command ) ( struct scsi_device *scsi,
                            struct scsi_command *command );
+       /** Backing device */
+       struct refcnt *backend;
 };
 
 extern int init_scsidev ( struct scsi_device *scsi );
index c986d71..0bfb19e 100644 (file)
 #include <assert.h>
 #include <byteswap.h>
 #include <gpxe/vsprintf.h>
+#include <gpxe/socket.h>
+#include <gpxe/xfer.h>
+#include <gpxe/open.h>
 #include <gpxe/scsi.h>
 #include <gpxe/process.h>
 #include <gpxe/uaccess.h>
-#include <gpxe/tcp.h>
+#include <gpxe/tcpip.h>
+#include <gpxe/dhcp.h>
 #include <gpxe/iscsi.h>
 
 /** @file
  *
  */
 
-#warning "Update to use data-xfer interface"
-extern int tcp_open ( struct stream_application *stream );
+/** iSCSI initiator name (explicitly specified) */
+char *iscsi_initiator_iqn;
+
+/** Default iSCSI initiator name (constructed from hostname) */
+char *iscsi_default_initiator_iqn;
+
+/** iSCSI username */
+char *iscsi_username;
+
+/** iSCSI password */
+char *iscsi_password;
 
 static void iscsi_start_tx ( struct iscsi_session *iscsi );
+static void iscsi_start_login ( struct iscsi_session *iscsi );
 static void iscsi_start_data_out ( struct iscsi_session *iscsi,
                                   unsigned int datasn );
 
 /**
- * Receive PDU data into buffer
+ * Finish receiving PDU data into buffer
  *
  * @v iscsi            iSCSI session
- * @v data             Data to receive
- * @v len              Length of data
- * @ret rc             Return status code
- *
- * This can be used when the RX PDU type handler wishes to buffer up
- * all received data and process the PDU as a single unit.  The caller
- * is repsonsible for calling iscsi_rx_buffered_data_done() after
- * processing the data.
  */
-static int iscsi_rx_buffered_data ( struct iscsi_session *iscsi,
-                                   const void *data, size_t len ) {
-
-       /* Allocate buffer on first call */
-       if ( ! iscsi->rx_buffer ) {
-               iscsi->rx_buffer = malloc ( iscsi->rx_len );
-               if ( ! iscsi->rx_buffer )
-                       return -ENOMEM;
-       }
+static void iscsi_rx_buffered_data_done ( struct iscsi_session *iscsi ) {
+       free ( iscsi->rx_buffer );
+       iscsi->rx_buffer = NULL;
+}
 
-       /* Copy data to buffer */
-       assert ( ( iscsi->rx_offset + len ) <= iscsi->rx_len );
-       memcpy ( ( iscsi->rx_buffer + iscsi->rx_offset ), data, len );
+/**
+ * Free iSCSI session
+ *
+ * @v refcnt           Reference counter
+ */
+static void iscsi_free ( struct refcnt *refcnt ) {
+       struct iscsi_session *iscsi =
+               container_of ( refcnt, struct iscsi_session, refcnt );
 
-       return 0;
+       free ( iscsi->target_address );
+       free ( iscsi->target_iqn );
+       chap_finish ( &iscsi->chap );
+       iscsi_rx_buffered_data_done ( iscsi );
+       free ( iscsi );
 }
 
 /**
- * Finish receiving PDU data into buffer
+ * Open iSCSI transport-layer connection
  *
  * @v iscsi            iSCSI session
+ * @ret rc             Return status code
  */
-static void iscsi_rx_buffered_data_done ( struct iscsi_session *iscsi ) {
-       free ( iscsi->rx_buffer );
-       iscsi->rx_buffer = NULL;
+static int iscsi_open_connection ( struct iscsi_session *iscsi ) {
+       struct sockaddr_tcpip target;
+       int rc;
+
+       assert ( iscsi->tx_state == ISCSI_TX_IDLE );
+       assert ( iscsi->rx_state == ISCSI_RX_BHS );
+       assert ( iscsi->rx_offset == 0 );
+
+       /* Open socket */
+       memset ( &target, 0, sizeof ( target ) );
+       target.st_port = htons ( iscsi->target_port );
+       if ( ( rc = xfer_open_named_socket ( &iscsi->socket, SOCK_STREAM,
+                                            ( struct sockaddr * ) &target,
+                                            iscsi->target_address,
+                                            NULL ) ) != 0 ) {
+               DBGC ( iscsi, "iSCSI %p could not open socket: %s\n",
+                      iscsi, strerror ( rc ) );
+               return rc;
+       }
+
+       /* Enter security negotiation phase */
+       iscsi->status = ( ISCSI_STATUS_SECURITY_NEGOTIATION_PHASE |
+                         ISCSI_STATUS_STRINGS_SECURITY );
+
+       /* Assign fresh initiator task tag */
+       iscsi->itt++;
+
+       /* Initiate login */
+       iscsi_start_login ( iscsi );
+
+       return 0;
 }
 
 /**
- * Close iSCSI connection
+ * Close iSCSI transport-layer connection
  *
  * @v iscsi            iSCSI session
+ * @v rc               Reason for close
+ *
+ * Closes the transport-layer connection and resets the session state
+ * ready to attempt a fresh login.
  */
-static void iscsi_close ( struct iscsi_session *iscsi ) {
+static void iscsi_close_connection ( struct iscsi_session *iscsi, int rc ) {
 
-       /* Close stream connection */
-       stream_close ( &iscsi->stream );
+       /* Close all data transfer interfaces */
+       xfer_close ( &iscsi->socket, rc );
 
        /* Clear connection status */
        iscsi->status = 0;
@@ -100,33 +143,30 @@ static void iscsi_close ( struct iscsi_session *iscsi ) {
        iscsi->tx_state = ISCSI_TX_IDLE;
        iscsi->rx_state = ISCSI_RX_BHS;
 
-       /* Free any dynamically allocated memory */
+       /* Free any temporary dynamically allocated memory */
        chap_finish ( &iscsi->chap );
        iscsi_rx_buffered_data_done ( iscsi );
 }
 
 /**
- * Mark iSCSI operation as complete
+ * Mark iSCSI SCSI operation as complete
  *
  * @v iscsi            iSCSI session
  * @v rc               Return status code
  *
- * Note that iscsi_done() will not close the connection, and must
+ * Note that iscsi_scsi_done() will not close the connection, and must
  * therefore be called only when the internal state machines are in an
  * appropriate state, otherwise bad things may happen on the next call
- * to iscsi_issue().  The general rule is to call iscsi_done() only at
- * the end of receiving a PDU; at this point the TX and RX engines
- * should both be idle.
+ * to iscsi_issue().  The general rule is to call iscsi_scsi_done()
+ * only at the end of receiving a PDU; at this point the TX and RX
+ * engines should both be idle.
  */
-static void iscsi_done ( struct iscsi_session *iscsi, int rc ) {
+static void iscsi_scsi_done ( struct iscsi_session *iscsi, int rc ) {
 
        assert ( iscsi->tx_state == ISCSI_TX_IDLE );
 
-       /* Clear current SCSI command */
        iscsi->command = NULL;
-
-       /* Mark asynchronous operation as complete */
-       async_done ( &iscsi->async, rc );
+       iscsi->rc = rc;
 }
 
 /****************************************************************************
@@ -181,10 +221,11 @@ static void iscsi_start_command ( struct iscsi_session *iscsi ) {
  * @v data             Received data
  * @v len              Length of received data
  * @v remaining                Data remaining after this data
- * 
+ * @ret rc             Return status code
  */
-static void iscsi_rx_scsi_response ( struct iscsi_session *iscsi, void *data,
-                                    size_t len, size_t remaining ) {
+static int iscsi_rx_scsi_response ( struct iscsi_session *iscsi,
+                                   const void *data, size_t len,
+                                   size_t remaining ) {
        struct iscsi_bhs_scsi_response *response
                = &iscsi->rx_bhs.scsi_response;
        int sense_offset;
@@ -198,17 +239,18 @@ static void iscsi_rx_scsi_response ( struct iscsi_session *iscsi, void *data,
 
        /* Wait for whole SCSI response to arrive */
        if ( remaining )
-               return;
+               return 0;
        
        /* Record SCSI status code */
        iscsi->command->status = response->status;
 
-       /* Mark as completed, with error if applicable */
-       if ( response->response == ISCSI_RESPONSE_COMMAND_COMPLETE ) {
-               iscsi_done ( iscsi, 0 );
-       } else {
-               iscsi_done ( iscsi, -EIO );
-       }
+       /* Check for errors */
+       if ( response->response != ISCSI_RESPONSE_COMMAND_COMPLETE )
+               return -EIO;
+
+       /* Mark as completed */
+       iscsi_scsi_done ( iscsi, 0 );
+       return 0;
 }
 
 /**
@@ -218,10 +260,11 @@ static void iscsi_rx_scsi_response ( struct iscsi_session *iscsi, void *data,
  * @v data             Received data
  * @v len              Length of received data
  * @v remaining                Data remaining after this data
- * 
+ * @ret rc             Return status code
  */
-static void iscsi_rx_data_in ( struct iscsi_session *iscsi, void *data,
-                              size_t len, size_t remaining __unused ) {
+static int iscsi_rx_data_in ( struct iscsi_session *iscsi,
+                             const void *data, size_t len,
+                             size_t remaining __unused ) {
        struct iscsi_bhs_data_in *data_in = &iscsi->rx_bhs.data_in;
        unsigned long offset;
 
@@ -240,8 +283,10 @@ static void iscsi_rx_data_in ( struct iscsi_session *iscsi, void *data,
        if ( ( offset + len ) == iscsi->command->data_in_len ) {
                assert ( data_in->flags & ISCSI_FLAG_FINAL );
                assert ( remaining == 0 );
-               iscsi_done ( iscsi, 0 );
+               iscsi_scsi_done ( iscsi, 0 );
        }
+
+       return 0;
 }
 
 /**
@@ -251,10 +296,11 @@ static void iscsi_rx_data_in ( struct iscsi_session *iscsi, void *data,
  * @v data             Received data
  * @v len              Length of received data
  * @v remaining                Data remaining after this data
- * 
+ * @ret rc             Return status code
  */
-static void iscsi_rx_r2t ( struct iscsi_session *iscsi, void *data __unused,
-                          size_t len __unused, size_t remaining __unused ) {
+static int iscsi_rx_r2t ( struct iscsi_session *iscsi,
+                         const void *data __unused, size_t len __unused,
+                         size_t remaining __unused ) {
        struct iscsi_bhs_r2t *r2t = &iscsi->rx_bhs.r2t;
 
        /* Record transfer parameters and trigger first data-out */
@@ -262,6 +308,8 @@ static void iscsi_rx_r2t ( struct iscsi_session *iscsi, void *data __unused,
        iscsi->transfer_offset = ntohl ( r2t->offset );
        iscsi->transfer_len = ntohl ( r2t->len );
        iscsi_start_data_out ( iscsi, 0 );
+
+       return 0;
 }
 
 /**
@@ -323,27 +371,29 @@ static void iscsi_data_out_done ( struct iscsi_session *iscsi ) {
  * Send iSCSI data-out data segment
  *
  * @v iscsi            iSCSI session
- * @v buf              Temporary data buffer
- * @v len              Length of temporary data buffer
+ * @ret rc             Return status code
  */
-static void iscsi_tx_data_out ( struct iscsi_session *iscsi,
-                               void *buf, size_t len ) {
+static int iscsi_tx_data_out ( struct iscsi_session *iscsi ) {
        struct iscsi_bhs_data_out *data_out = &iscsi->tx_bhs.data_out;
+       struct io_buffer *iobuf;
        unsigned long offset;
-       unsigned long remaining;
+       size_t len;
+
+       offset = ntohl ( data_out->offset );
+       len = ISCSI_DATA_LEN ( data_out->lengths );
 
-       offset = ( iscsi->transfer_offset + ntohl ( data_out->offset ) +
-                  iscsi->tx_offset );
-       remaining = ( iscsi->tx_len - iscsi->tx_offset );
        assert ( iscsi->command != NULL );
        assert ( iscsi->command->data_out );
-       assert ( ( offset + remaining ) <= iscsi->command->data_out_len );
+       assert ( ( offset + len ) <= iscsi->command->data_out_len );
+
+       iobuf = xfer_alloc_iob ( &iscsi->socket, len );
+       if ( ! iobuf )
+               return -ENOMEM;
        
-       if ( remaining < len )
-               len = remaining;
-       copy_from_user ( buf, iscsi->command->data_out, offset, len );
+       copy_from_user ( iob_put ( iobuf, len ),
+                        iscsi->command->data_out, offset, len );
 
-       stream_send ( &iscsi->stream, buf, len );
+       return xfer_deliver_iob ( &iscsi->socket, iobuf );
 }
 
 /****************************************************************************
@@ -385,16 +435,22 @@ static void iscsi_tx_data_out ( struct iscsi_session *iscsi,
  */
 static int iscsi_build_login_request_strings ( struct iscsi_session *iscsi,
                                               void *data, size_t len ) {
+       char *initiator_iqn;
        unsigned int used = 0;
        unsigned int i;
 
        if ( iscsi->status & ISCSI_STATUS_STRINGS_SECURITY ) {
+               initiator_iqn = iscsi_initiator_iqn;
+               if ( ! initiator_iqn )
+                       initiator_iqn = iscsi_default_initiator_iqn;
+               if ( ! initiator_iqn )
+                       initiator_iqn = "iqn.2000-09.org.etherboot:UNKNOWN";
                used += ssnprintf ( data + used, len - used,
                                    "InitiatorName=%s%c"
                                    "TargetName=%s%c"
                                    "SessionType=Normal%c"
                                    "AuthMethod=CHAP,None%c",
-                                   iscsi->initiator_iqn, 0,
+                                   initiator_iqn, 0,
                                    iscsi->target_iqn, 0, 0, 0 );
        }
 
@@ -403,10 +459,10 @@ static int iscsi_build_login_request_strings ( struct iscsi_session *iscsi,
        }
        
        if ( ( iscsi->status & ISCSI_STATUS_STRINGS_CHAP_RESPONSE ) &&
-            iscsi->username ) {
+            iscsi_username ) {
                used += ssnprintf ( data + used, len - used,
                                    "CHAP_N=%s%cCHAP_R=0x",
-                                   iscsi->username, 0 );
+                                   iscsi_username, 0 );
                for ( i = 0 ; i < iscsi->chap.response_len ; i++ ) {
                        used += ssnprintf ( data + used, len - used, "%02x",
                                            iscsi->chap.response[i] );
@@ -478,16 +534,22 @@ static void iscsi_login_request_done ( struct iscsi_session *iscsi ) {
  * Transmit data segment of an iSCSI login request PDU
  *
  * @v iscsi            iSCSI session
- * @v buf              Temporary data buffer
- * @v len              Length of temporary data buffer
+ * @ret rc             Return status code
  *
  * For login requests, the data segment consists of the login strings.
  */
-static void iscsi_tx_login_request ( struct iscsi_session *iscsi,
-                                    void *buf, size_t len ) {
-       len = iscsi_build_login_request_strings ( iscsi, buf, len );
-       stream_send ( &iscsi->stream, buf + iscsi->tx_offset,
-                  len - iscsi->tx_offset );
+static int iscsi_tx_login_request ( struct iscsi_session *iscsi ) {
+       struct iscsi_bhs_login_request *request = &iscsi->tx_bhs.login_request;
+       struct io_buffer *iobuf;
+       size_t len;
+
+       len = ISCSI_DATA_LEN ( request->lengths );
+       iobuf = xfer_alloc_iob ( &iscsi->socket, len );
+       if ( ! iobuf )
+               return -ENOMEM;
+       iob_put ( iobuf, len );
+       iscsi_build_login_request_strings ( iscsi, iobuf->data, len );
+       return xfer_deliver_iob ( &iscsi->socket, iobuf );
 }
 
 /**
@@ -495,20 +557,19 @@ static void iscsi_tx_login_request ( struct iscsi_session *iscsi,
  *
  * @v iscsi            iSCSI session
  * @v value            TargetAddress value
+ * @ret rc             Return status code
  */
-static void iscsi_handle_targetaddress_value ( struct iscsi_session *iscsi,
-                                              const char *value ) {
-       struct in_addr address;
-       struct sockaddr_in *sin = ( struct sockaddr_in * ) &iscsi->target;
-
-       if ( inet_aton ( value, &address ) == 0 ) {
-               DBGC ( iscsi, "iSCSI %p received invalid TargetAddress "
-                      "\"%s\"\n", iscsi, value );
-               return;
-       }
+static int iscsi_handle_targetaddress_value ( struct iscsi_session *iscsi,
+                                             const char *value ) {
 
        DBGC ( iscsi, "iSCSI %p will redirect to %s\n", iscsi, value );
-       sin->sin_addr = address;
+
+       /* Replace target address */
+       free ( iscsi->target_address );
+       iscsi->target_address = strdup ( value );
+       if ( ! iscsi->target_address )
+               return -ENOMEM;
+       return 0;
 }
 
 /**
@@ -516,9 +577,10 @@ static void iscsi_handle_targetaddress_value ( struct iscsi_session *iscsi,
  *
  * @v iscsi            iSCSI session
  * @v value            AuthMethod value
+ * @ret rc             Return status code
  */
-static void iscsi_handle_authmethod_value ( struct iscsi_session *iscsi,
-                                           const char *value ) {
+static int iscsi_handle_authmethod_value ( struct iscsi_session *iscsi,
+                                          const char *value ) {
 
        /* If server requests CHAP, send the CHAP_A string */
        if ( strcmp ( value, "CHAP" ) == 0 ) {
@@ -526,6 +588,7 @@ static void iscsi_handle_authmethod_value ( struct iscsi_session *iscsi,
                       iscsi );
                iscsi->status |= ISCSI_STATUS_STRINGS_CHAP_ALGORITHM;
        }
+       return 0;
 }
 
 /**
@@ -533,9 +596,10 @@ static void iscsi_handle_authmethod_value ( struct iscsi_session *iscsi,
  *
  * @v iscsi            iSCSI session
  * @v value            CHAP_A value
+ * @ret rc             Return status code
  */
-static void iscsi_handle_chap_a_value ( struct iscsi_session *iscsi,
-                                       const char *value ) {
+static int iscsi_handle_chap_a_value ( struct iscsi_session *iscsi,
+                                      const char *value ) {
        int rc;
 
        /* We only ever offer "5" (i.e. MD5) as an algorithm, so if
@@ -545,15 +609,17 @@ static void iscsi_handle_chap_a_value ( struct iscsi_session *iscsi,
        if ( strcmp ( value, "5" ) != 0 ) {
                DBGC ( iscsi, "iSCSI %p got invalid CHAP algorithm \"%s\"\n",
                       iscsi, value );
+               return -EPROTO;
        }
 
        /* Prepare for CHAP with MD5 */
        if ( ( rc = chap_init ( &iscsi->chap, &md5_algorithm ) ) != 0 ) {
                DBGC ( iscsi, "iSCSI %p could not initialise CHAP: %s\n",
                       iscsi, strerror ( rc ) );
-               iscsi_close ( iscsi );
-               iscsi_done ( iscsi, rc );
+               return rc;
        }
+
+       return 0;
 }
 
 /**
@@ -561,9 +627,10 @@ static void iscsi_handle_chap_a_value ( struct iscsi_session *iscsi,
  *
  * @v iscsi            iSCSI session
  * @v value            CHAP_I value
+ * @ret rc             Return status code
  */
-static void iscsi_handle_chap_i_value ( struct iscsi_session *iscsi,
-                                       const char *value ) {
+static int iscsi_handle_chap_i_value ( struct iscsi_session *iscsi,
+                                      const char *value ) {
        unsigned int identifier;
        char *endp;
 
@@ -572,16 +639,19 @@ static void iscsi_handle_chap_i_value ( struct iscsi_session *iscsi,
        if ( *endp != '\0' ) {
                DBGC ( iscsi, "iSCSI %p saw invalid CHAP identifier \"%s\"\n",
                       iscsi, value );
+               return -EPROTO;
        }
 
        /* Identifier and secret are the first two components of the
         * challenge.
         */
        chap_set_identifier ( &iscsi->chap, identifier );
-       if ( iscsi->password ) {
-               chap_update ( &iscsi->chap, iscsi->password,
-                             strlen ( iscsi->password ) );
+       if ( iscsi_password ) {
+               chap_update ( &iscsi->chap, iscsi_password,
+                             strlen ( iscsi_password ) );
        }
+
+       return 0;
 }
 
 /**
@@ -589,9 +659,10 @@ static void iscsi_handle_chap_i_value ( struct iscsi_session *iscsi,
  *
  * @v iscsi            iSCSI session
  * @v value            CHAP_C value
+ * @ret rc             Return status code
  */
-static void iscsi_handle_chap_c_value ( struct iscsi_session *iscsi,
-                                       const char *value ) {
+static int iscsi_handle_chap_c_value ( struct iscsi_session *iscsi,
+                                      const char *value ) {
        char buf[3];
        char *endp;
        uint8_t byte;
@@ -611,6 +682,7 @@ static void iscsi_handle_chap_c_value ( struct iscsi_session *iscsi,
                if ( *endp != '\0' ) {
                        DBGC ( iscsi, "iSCSI %p saw invalid CHAP challenge "
                               "byte \"%s\"\n", iscsi, buf );
+                       return -EPROTO;
                }
                chap_update ( &iscsi->chap, &byte, sizeof ( byte ) );
        }
@@ -619,6 +691,8 @@ static void iscsi_handle_chap_c_value ( struct iscsi_session *iscsi,
        DBGC ( iscsi, "iSCSI %p sending CHAP response\n", iscsi );
        chap_respond ( &iscsi->chap );
        iscsi->status |= ISCSI_STATUS_STRINGS_CHAP_RESPONSE;
+
+       return 0;
 }
 
 /** An iSCSI text string that we want to handle */
@@ -633,9 +707,9 @@ struct iscsi_string_type {
         *
         * @v iscsi             iSCSI session
         * @v value             iSCSI string value
+        * @ret rc              Return status code
         */
-       void ( * handle_value ) ( struct iscsi_session *iscsi,
-                                 const char *value );
+       int ( * handle ) ( struct iscsi_session *iscsi, const char *value );
 };
 
 /** iSCSI text strings that we want to handle */
@@ -653,22 +727,29 @@ struct iscsi_string_type iscsi_string_types[] = {
  *
  * @v iscsi            iSCSI session
  * @v string           iSCSI string (in "key=value" format)
+ * @ret rc             Return status code
  */
-static void iscsi_handle_string ( struct iscsi_session *iscsi,
-                                 const char *string ) {
+static int iscsi_handle_string ( struct iscsi_session *iscsi,
+                                const char *string ) {
        struct iscsi_string_type *type;
        size_t key_len;
+       int rc;
 
        for ( type = iscsi_string_types ; type->key ; type++ ) {
                key_len = strlen ( type->key );
-               if ( strncmp ( string, type->key, key_len ) == 0 ) {
-                       DBGC ( iscsi, "iSCSI %p handling %s\n",
-                              iscsi, string );
-                       type->handle_value ( iscsi, ( string + key_len ) );
-                       return;
+               if ( strncmp ( string, type->key, key_len ) != 0 )
+                       continue;
+               DBGC ( iscsi, "iSCSI %p handling %s\n", iscsi, string );
+               if ( ( rc = type->handle ( iscsi,
+                                          ( string + key_len ) ) ) != 0 ) {
+                       DBGC ( iscsi, "iSCSI %p could not handle %s: %s\n",
+                              iscsi, string, strerror ( rc ) );
+                       return rc;
                }
+               return 0;
        }
        DBGC ( iscsi, "iSCSI %p ignoring %s\n", iscsi, string );
+       return 0;
 }
 
 /**
@@ -677,10 +758,12 @@ static void iscsi_handle_string ( struct iscsi_session *iscsi,
  * @v iscsi            iSCSI session
  * @v string           iSCSI string buffer
  * @v len              Length of string buffer
+ * @ret rc             Return status code
  */
-static void iscsi_handle_strings ( struct iscsi_session *iscsi,
-                                  const char *strings, size_t len ) {
+static int iscsi_handle_strings ( struct iscsi_session *iscsi,
+                                 const char *strings, size_t len ) {
        size_t string_len;
+       int rc;
 
        /* Handle each string in turn, taking care not to overrun the
         * data buffer in case of badly-terminated data.
@@ -689,10 +772,42 @@ static void iscsi_handle_strings ( struct iscsi_session *iscsi,
                string_len = ( strnlen ( strings, len ) + 1 );
                if ( string_len > len )
                        break;
-               iscsi_handle_string ( iscsi, strings );
+               if ( ( rc = iscsi_handle_string ( iscsi, strings ) ) != 0 )
+                       return rc;
                strings += string_len;
                len -= string_len;
        }
+       return 0;
+}
+
+/**
+ * Receive PDU data into buffer
+ *
+ * @v iscsi            iSCSI session
+ * @v data             Data to receive
+ * @v len              Length of data
+ * @ret rc             Return status code
+ *
+ * This can be used when the RX PDU type handler wishes to buffer up
+ * all received data and process the PDU as a single unit.  The caller
+ * is repsonsible for calling iscsi_rx_buffered_data_done() after
+ * processing the data.
+ */
+static int iscsi_rx_buffered_data ( struct iscsi_session *iscsi,
+                                   const void *data, size_t len ) {
+
+       /* Allocate buffer on first call */
+       if ( ! iscsi->rx_buffer ) {
+               iscsi->rx_buffer = malloc ( iscsi->rx_len );
+               if ( ! iscsi->rx_buffer )
+                       return -ENOMEM;
+       }
+
+       /* Copy data to buffer */
+       assert ( ( iscsi->rx_offset + len ) <= iscsi->rx_len );
+       memcpy ( ( iscsi->rx_buffer + iscsi->rx_offset ), data, len );
+
+       return 0;
 }
 
 /**
@@ -702,10 +817,11 @@ static void iscsi_handle_strings ( struct iscsi_session *iscsi,
  * @v data             Received data
  * @v len              Length of received data
  * @v remaining                Data remaining after this data
- * 
+ * @ret rc             Return status code
  */
-static void iscsi_rx_login_response ( struct iscsi_session *iscsi, void *data,
-                                     size_t len, size_t remaining ) {
+static int iscsi_rx_login_response ( struct iscsi_session *iscsi,
+                                    const void *data, size_t len,
+                                    size_t remaining ) {
        struct iscsi_bhs_login_response *response
                = &iscsi->rx_bhs.login_response;
        int rc;
@@ -714,35 +830,27 @@ static void iscsi_rx_login_response ( struct iscsi_session *iscsi, void *data,
        if ( ( rc = iscsi_rx_buffered_data ( iscsi, data, len ) ) != 0 ) {
                DBGC ( iscsi, "iSCSI %p could not buffer login response: %s\n",
                       iscsi, strerror ( rc ) );
-               iscsi_close ( iscsi );
-               iscsi_done ( iscsi, rc );
-               return;
+               return rc;
        }
        if ( remaining )
-               return;
+               return 0;
 
        /* Process string data and discard string buffer */
-       iscsi_handle_strings ( iscsi, iscsi->rx_buffer, iscsi->rx_len );
+       if ( ( rc = iscsi_handle_strings ( iscsi, iscsi->rx_buffer,
+                                          iscsi->rx_len ) ) != 0 )
+               return rc;
        iscsi_rx_buffered_data_done ( iscsi );
 
        /* Check for login redirection */
        if ( response->status_class == ISCSI_STATUS_REDIRECT ) {
                DBGC ( iscsi, "iSCSI %p redirecting to new server\n", iscsi );
-               iscsi_close ( iscsi );
-               if ( ( rc = tcp_open ( &iscsi->stream ) ) != 0 ) {
-                       DBGC ( iscsi, "iSCSI %p could not open stream: %s\n ",
+               iscsi_close_connection ( iscsi, 0 );
+               if ( ( rc = iscsi_open_connection ( iscsi ) ) != 0 ) {
+                       DBGC ( iscsi, "iSCSI %p could not redirect: %s\n ",
                               iscsi, strerror ( rc ) );
-                       iscsi_done ( iscsi, rc );
-                       return;
-               }
-               if ( ( rc = stream_connect ( &iscsi->stream,
-                                            &iscsi->target ) != 0 ) != 0 ) {
-                       DBGC ( iscsi, "iSCSI %p could not connect: %s\n ",
-                              iscsi, strerror ( rc ) );
-                       iscsi_done ( iscsi, rc );
-                       return;
+                       return rc;
                }
-               return;
+               return 0;
        }
 
        /* Check for fatal errors */
@@ -750,9 +858,7 @@ static void iscsi_rx_login_response ( struct iscsi_session *iscsi, void *data,
                DBGC ( iscsi, "iSCSI login failure: class %02x detail %02x\n",
                       response->status_class, response->status_detail );
                iscsi->instant_rc = -EPERM;
-               iscsi_close ( iscsi );
-               iscsi_done ( iscsi, -EPERM );
-               return;
+               return -EPERM;
        }
 
        /* Handle login transitions */
@@ -769,9 +875,7 @@ static void iscsi_rx_login_response ( struct iscsi_session *iscsi, void *data,
                default:
                        DBGC ( iscsi, "iSCSI %p got invalid response flags "
                               "%02x\n", iscsi, response->flags );
-                       iscsi_close ( iscsi );
-                       iscsi_done ( iscsi, -EIO );
-                       return;
+                       return -EIO;
                }
        }
 
@@ -781,7 +885,7 @@ static void iscsi_rx_login_response ( struct iscsi_session *iscsi, void *data,
        if ( ( iscsi->status & ISCSI_STATUS_PHASE_MASK ) !=
             ISCSI_STATUS_FULL_FEATURE_PHASE ) {
                iscsi_start_login ( iscsi );
-               return;
+               return 0;
        }
 
        /* Reset retry count */
@@ -792,19 +896,16 @@ static void iscsi_rx_login_response ( struct iscsi_session *iscsi, void *data,
        
        /* Send the actual SCSI command */
        iscsi_start_command ( iscsi );
+
+       return 0;
 }
 
 /****************************************************************************
  *
- * iSCSI to stream interface
+ * iSCSI to socket interface
  *
  */
 
-static inline struct iscsi_session *
-stream_to_iscsi ( struct stream_application *app ) {
-       return container_of ( app, struct iscsi_session, stream );
-}
-
 /**
  * Start up a new TX PDU
  *
@@ -821,36 +922,63 @@ static void iscsi_start_tx ( struct iscsi_session *iscsi ) {
 
        /* Flag TX engine to start transmitting */
        iscsi->tx_state = ISCSI_TX_BHS;
-       iscsi->tx_offset = 0;
+}
+
+/**
+ * Transmit basic header segment of an iSCSI PDU
+ *
+ * @v iscsi            iSCSI session
+ * @ret rc             Return status code
+ */
+static int iscsi_tx_bhs ( struct iscsi_session *iscsi ) {
+       return xfer_deliver_raw ( &iscsi->socket,  &iscsi->tx_bhs,
+                                 sizeof ( iscsi->tx_bhs ) );
 }
 
 /**
  * Transmit data segment of an iSCSI PDU
  *
  * @v iscsi            iSCSI session
- * @v buf              Temporary data buffer
- * @v len              Length of temporary data buffer
+ * @ret rc             Return status code
  * 
  * 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,
-                           void *buf, size_t len ) {
+static int 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_DATA_OUT:
-               iscsi_tx_data_out ( iscsi, buf, len );
-               break;
+               return iscsi_tx_data_out ( iscsi );
        case ISCSI_OPCODE_LOGIN_REQUEST:
-               iscsi_tx_login_request ( iscsi, buf, len );
-               break;
+               return iscsi_tx_login_request ( iscsi );
        default:
                assert ( 0 );
-               break;
+               return -EINVAL;
        }
 }
 
+/**
+ * Transmit data padding of an iSCSI PDU
+ *
+ * @v iscsi            iSCSI session
+ * @ret rc             Return status code
+ * 
+ * Handle transmission of any data padding in a PDU data segment.
+ * iscsi::tx_bhs will be valid when this is called.
+ */
+static int iscsi_tx_data_padding ( struct iscsi_session *iscsi ) {
+       static const char pad[] = { '\0', '\0', '\0' };
+       struct iscsi_bhs_common *common = &iscsi->tx_bhs.common;
+       size_t pad_len;
+       
+       pad_len = ISCSI_DATA_PAD_LEN ( common->lengths );
+       if ( ! pad_len )
+               return 0;
+
+       return xfer_deliver_raw ( &iscsi->socket, pad, pad_len );
+}
+
 /**
  * Complete iSCSI PDU transmission
  *
@@ -874,62 +1002,6 @@ static void iscsi_tx_done ( struct iscsi_session *iscsi ) {
        }
 }
 
-/**
- * Handle stream ACKs
- *
- * @v iscsi            iSCSI session
- * 
- * Updates iscsi->tx_offset and, if applicable, transitions to the
- * next TX state.
- */
-static void iscsi_acked ( struct stream_application *app, size_t len ) {
-       struct iscsi_session *iscsi = stream_to_iscsi ( app );
-       struct iscsi_bhs_common *common = &iscsi->tx_bhs.common;
-       enum iscsi_tx_state next_state;
-       
-       iscsi->tx_offset += len;
-       while ( 1 ) {
-               switch ( iscsi->tx_state ) {
-               case ISCSI_TX_BHS:
-                       iscsi->tx_len = sizeof ( iscsi->tx_bhs );
-                       next_state = ISCSI_TX_AHS;
-                       break;
-               case ISCSI_TX_AHS:
-                       iscsi->tx_len = 4 * ISCSI_AHS_LEN ( common->lengths );
-                       next_state = ISCSI_TX_DATA;
-                       break;
-               case ISCSI_TX_DATA:
-                       iscsi->tx_len = ISCSI_DATA_LEN ( common->lengths );
-                       next_state = ISCSI_TX_DATA_PADDING;
-                       break;
-               case ISCSI_TX_DATA_PADDING:
-                       iscsi->tx_len = 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 <= iscsi->tx_len );
-
-               /* If the whole of the current portion has not yet
-                * been acked, stay in this state for now.
-                */
-               if ( iscsi->tx_offset != iscsi->tx_len )
-                       return;
-
-               /* Move to next state.  Call iscsi_tx_done() when PDU
-                * transmission is complete.
-                */
-               iscsi->tx_state = next_state;
-               iscsi->tx_offset = 0;
-               if ( next_state == ISCSI_TX_IDLE )
-                       iscsi_tx_done ( iscsi );
-       }
-}
-
 /**
  * Transmit iSCSI PDU
  *
@@ -939,30 +1011,37 @@ static void iscsi_acked ( struct stream_application *app, size_t len ) {
  * 
  * Constructs data to be sent for the current TX state
  */
-static void iscsi_senddata ( struct stream_application *app,
-                            void *buf, size_t len ) {
-       struct iscsi_session *iscsi = stream_to_iscsi ( app );
-       struct iscsi_bhs_common *common = &iscsi->tx_bhs.common;
-       static const char pad[] = { '\0', '\0', '\0' };
+static void iscsi_tx_step ( struct process *process ) {
+       struct iscsi_session *iscsi =
+               container_of ( process, struct iscsi_session, process );
+       int rc = 0;
+
+       if ( xfer_window ( &iscsi->socket ) == 0 )
+               return;
 
        switch ( iscsi->tx_state ) {
        case ISCSI_TX_IDLE:
                /* Nothing to send */
                break;
        case ISCSI_TX_BHS:
-               stream_send ( app, &iscsi->tx_bhs.bytes[iscsi->tx_offset],
-                          ( sizeof ( iscsi->tx_bhs ) - iscsi->tx_offset ) );
+               if ( ( rc = iscsi_tx_bhs ( iscsi ) ) != 0 )
+                       break;
+               iscsi->tx_state = ISCSI_TX_AHS;
                break;
        case ISCSI_TX_AHS:
                /* We don't yet have an AHS transmission mechanism */
-               assert ( 0 );
+               iscsi->tx_state = ISCSI_TX_DATA;
                break;
        case ISCSI_TX_DATA:
-               iscsi_tx_data ( iscsi, buf, len );
+               if ( ( rc = iscsi_tx_data ( iscsi ) ) != 0 )
+                       break;
+               iscsi->tx_state = ISCSI_TX_DATA_PADDING;
                break;
        case ISCSI_TX_DATA_PADDING:
-               stream_send ( app, pad, ( ISCSI_DATA_PAD_LEN( common->lengths )
-                                         - iscsi->tx_offset ) );
+               if ( ( rc = iscsi_tx_data_padding ( iscsi ) ) != 0 )
+                       break;
+               iscsi->tx_state = ISCSI_TX_IDLE;
+               iscsi_tx_done ( iscsi );
                break;
        default:
                assert ( 0 );
@@ -971,47 +1050,26 @@ static void iscsi_senddata ( struct stream_application *app,
 }
 
 /**
- * Receive data segment of an iSCSI PDU
+ * 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
+ * @ret rc             Return status code
  *
- * Handle processing of part of a PDU data segment.  iscsi::rx_bhs
- * will be valid when this is called.
+ * This fills in iscsi::rx_bhs with the data from the BHS portion of
+ * the received PDU.
  */
-static void iscsi_rx_data ( struct iscsi_session *iscsi, void *data,
-                           size_t len, size_t remaining ) {
-       struct iscsi_bhs_common_response *response
-               = &iscsi->rx_bhs.common_response;
-
-       /* Update cmdsn and statsn */
-       iscsi->cmdsn = ntohl ( response->expcmdsn );
-       iscsi->statsn = ntohl ( response->statsn );
-
-       switch ( response->opcode & ISCSI_OPCODE_MASK ) {
-       case ISCSI_OPCODE_LOGIN_RESPONSE:
-               iscsi_rx_login_response ( iscsi, data, len, remaining );
-               break;
-       case ISCSI_OPCODE_SCSI_RESPONSE:
-               iscsi_rx_scsi_response ( iscsi, data, len, remaining );
-               break;
-       case ISCSI_OPCODE_DATA_IN:
-               iscsi_rx_data_in ( iscsi, data, len, remaining );
-               break;
-       case ISCSI_OPCODE_R2T:
-               iscsi_rx_r2t ( iscsi, data, len, remaining );
-               break;
-       default:
-               if ( remaining )
-                       return;
-               DBGC ( iscsi, "iSCSI %p unknown opcode %02x\n", iscsi,
-                      response->opcode );
-               iscsi_close ( iscsi );
-               iscsi_done ( iscsi, -EOPNOTSUPP );
-               break;
+static int iscsi_rx_bhs ( struct iscsi_session *iscsi, const void *data,
+                         size_t len, size_t remaining __unused ) {
+       memcpy ( &iscsi->rx_bhs.bytes[iscsi->rx_offset], data, len );
+       if ( ( iscsi->rx_offset + len ) >= sizeof ( iscsi->rx_bhs ) ) {
+               DBGC ( iscsi, "iSCSI %p received PDU opcode %#x len %#lx\n",
+                      iscsi, iscsi->rx_bhs.common.opcode,
+                      ISCSI_DATA_LEN ( iscsi->rx_bhs.common.lengths ) );
        }
+       return 0;
 }
 
 /**
@@ -1021,42 +1079,63 @@ static void iscsi_rx_data ( struct iscsi_session *iscsi, void *data,
  * @v data             Received data
  * @v len              Length of received data
  * @v remaining                Data remaining after this data
+ * @ret rc             Return status code
  *
  * 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 ) {
+static int iscsi_rx_discard ( struct iscsi_session *iscsi __unused,
+                             const void *data __unused, size_t len __unused,
+                             size_t remaining __unused ) {
        /* Do nothing */
+       return 0;
 }
 
 /**
- * Receive basic header segment of an iSCSI PDU
+ * 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
+ * @ret rc             Return status code
  *
- * This fills in iscsi::rx_bhs with the data from the BHS portion of
- * the received PDU.
+ * Handle processing of part of a PDU data segment.  iscsi::rx_bhs
+ * will be valid when this is called.
  */
-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 );
-       if ( ( iscsi->rx_offset + len ) >= sizeof ( iscsi->rx_bhs ) ) {
-               DBGC ( iscsi, "iSCSI %p received PDU opcode %#x len %#lx\n",
-                      iscsi, iscsi->rx_bhs.common.opcode,
-                      ISCSI_DATA_LEN ( iscsi->rx_bhs.common.lengths ) );
+static int iscsi_rx_data ( struct iscsi_session *iscsi, const void *data,
+                          size_t len, size_t remaining ) {
+       struct iscsi_bhs_common_response *response
+               = &iscsi->rx_bhs.common_response;
+
+       /* Update cmdsn and statsn */
+       iscsi->cmdsn = ntohl ( response->expcmdsn );
+       iscsi->statsn = ntohl ( response->statsn );
+
+       switch ( response->opcode & ISCSI_OPCODE_MASK ) {
+       case ISCSI_OPCODE_LOGIN_RESPONSE:
+               return iscsi_rx_login_response ( iscsi, data, len, remaining );
+       case ISCSI_OPCODE_SCSI_RESPONSE:
+               return iscsi_rx_scsi_response ( iscsi, data, len, remaining );
+       case ISCSI_OPCODE_DATA_IN:
+               return iscsi_rx_data_in ( iscsi, data, len, remaining );
+       case ISCSI_OPCODE_R2T:
+               return iscsi_rx_r2t ( iscsi, data, len, remaining );
+       default:
+               if ( remaining )
+                       return 0;
+               DBGC ( iscsi, "iSCSI %p unknown opcode %02x\n", iscsi,
+                      response->opcode );
+               return -EOPNOTSUPP;
        }
 }
 
 /**
  * Receive new data
  *
- * @v stream           Stream application
+ * @v socket           Transport layer interface
  * @v data             Received data
  * @v len              Length of received data
+ * @ret rc             Return status code
  *
  * This handles received PDUs.  The receive strategy is to fill in
  * iscsi::rx_bhs with the contents of the BHS portion of the PDU,
@@ -1065,15 +1144,17 @@ static void iscsi_rx_bhs ( struct iscsi_session *iscsi, void *data,
  * 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 stream_application *app, void *data,
-                           size_t len ) {
-       struct iscsi_session *iscsi = stream_to_iscsi ( app );
+static int iscsi_socket_deliver_raw ( struct xfer_interface *socket,
+                                     const void *data, size_t len ) {
+       struct iscsi_session *iscsi =
+               container_of ( socket, struct iscsi_session, socket );
        struct iscsi_bhs_common *common = &iscsi->rx_bhs.common;
-       void ( *process ) ( struct iscsi_session *iscsi, void *data,
-                           size_t len, size_t remaining );
+       int ( *process ) ( struct iscsi_session *iscsi, const void *data,
+                          size_t len, size_t remaining );
        enum iscsi_rx_state next_state;
        size_t frag_len;
        size_t remaining;
+       int rc;
 
        while ( 1 ) {
                switch ( iscsi->rx_state ) {
@@ -1099,14 +1180,21 @@ static void iscsi_newdata ( struct stream_application *app, void *data,
                        break;
                default:
                        assert ( 0 );
-                       return;
+                       return -EINVAL;
                }
 
                frag_len = iscsi->rx_len - iscsi->rx_offset;
                if ( frag_len > len )
                        frag_len = len;
                remaining = iscsi->rx_len - iscsi->rx_offset - frag_len;
-               process ( iscsi, data, frag_len, remaining );
+               if ( ( rc = process ( iscsi, data, frag_len,
+                                     remaining ) ) != 0 ) {
+                       DBGC ( iscsi, "iSCSI %p could not process received "
+                              "data: %s\n", iscsi, strerror ( rc ) );
+                       iscsi_close_connection ( iscsi, rc );
+                       iscsi_scsi_done ( iscsi, rc );
+                       return rc;
+               }
 
                iscsi->rx_offset += frag_len;
                data += frag_len;
@@ -1116,135 +1204,351 @@ static void iscsi_newdata ( struct stream_application *app, void *data,
                 * received, stay in this state for now.
                 */
                if ( iscsi->rx_offset != iscsi->rx_len )
-                       return;
+                       return 0;
 
                iscsi->rx_state = next_state;
                iscsi->rx_offset = 0;
        }
+
+       return 0;
 }
 
 /**
  * Handle stream connection closure
  *
- * @v app              Stream application
- * @v status           Error code, if any
+ * @v socket           Transport layer interface
+ * @v rc               Reason for close
  *
  */
-static void iscsi_closed ( struct stream_application *app, int status ) {
-       struct iscsi_session *iscsi = stream_to_iscsi ( app );
-       int rc;
+static void iscsi_socket_close ( struct xfer_interface *socket, int rc ) {
+       struct iscsi_session *iscsi =
+               container_of ( socket, struct iscsi_session, socket );
 
        /* Even a graceful close counts as an error for iSCSI */
-       if ( ! status )
-               status = -ECONNRESET;
+       if ( ! rc )
+               rc = -ECONNRESET;
 
        /* Close session cleanly */
-       iscsi_close ( iscsi );
+       iscsi_close_connection ( iscsi, rc );
 
        /* Retry connection if within the retry limit, otherwise fail */
        if ( ++iscsi->retry_count <= ISCSI_MAX_RETRIES ) {
                DBGC ( iscsi, "iSCSI %p retrying connection (retry #%d)\n",
                       iscsi, iscsi->retry_count );
-               if ( ( rc = tcp_open ( app ) ) != 0 ) {
-                       DBGC ( iscsi, "iSCSI %p could not open stream: %s\n ",
-                              iscsi, strerror ( rc ) );
-                       iscsi_done ( iscsi, rc );
-                       return;
-               }
-               if ( ( rc = stream_connect ( app, &iscsi->target ) ) != 0 ){
-                       DBGC ( iscsi, "iSCSI %p could not connect: %s\n",
+               if ( ( rc = iscsi_open_connection ( iscsi ) ) != 0 ) {
+                       DBGC ( iscsi, "iSCSI %p could not reconnect: %s\n",
                               iscsi, strerror ( rc ) );
-                       iscsi_done ( iscsi, rc );
-                       return;
+                       iscsi_scsi_done ( iscsi, rc );
                }
        } else {
                DBGC ( iscsi, "iSCSI %p retry count exceeded\n", iscsi );
-               iscsi->instant_rc = status;
-               iscsi_done ( iscsi, status );
-               return;
+               iscsi->instant_rc = rc;
+               iscsi_scsi_done ( iscsi, rc );
        }
 }
 
-/**
- * Handle stream connection opening
+/** iSCSI socket operations */
+static struct xfer_interface_operations iscsi_socket_operations = {
+       .close          = iscsi_socket_close,
+       .vredirect      = xfer_vopen,
+       .seek           = ignore_xfer_seek,
+       .window         = unlimited_xfer_window,
+       .alloc_iob      = default_xfer_alloc_iob,
+       .deliver_iob    = xfer_deliver_as_raw,
+       .deliver_raw    = iscsi_socket_deliver_raw,
+};
+
+
+/****************************************************************************
  *
- * @v app              Stream application
+ * iSCSI command issuing
  *
  */
-static void iscsi_connected ( struct stream_application *app ) {
-       struct iscsi_session *iscsi = stream_to_iscsi ( app );
 
-       assert ( iscsi->rx_state == ISCSI_RX_BHS );
-       assert ( iscsi->rx_offset == 0 );
+/**
+ * Issue SCSI command
+ *
+ * @v scsi             SCSI device
+ * @v command          SCSI command
+ * @ret rc             Return status code
+ */
+static int iscsi_command ( struct scsi_device *scsi,
+                          struct scsi_command *command ) {
+       struct iscsi_session *iscsi =
+               container_of ( scsi->backend, struct iscsi_session, refcnt );
+       int rc;
 
-       /* Enter security negotiation phase */
-       iscsi->status = ( ISCSI_STATUS_SECURITY_NEGOTIATION_PHASE |
-                         ISCSI_STATUS_STRINGS_SECURITY );
+       /* Record SCSI command */
+       iscsi->command = command;
 
-       /* Assign fresh initiator task tag */
-       iscsi->itt++;
+       /* Abort immediately if we have a recorded permanent failure */
+       if ( iscsi->instant_rc ) {
+               rc = iscsi->instant_rc;
+               goto done;
+       }
 
-       /* Start logging in */
-       iscsi_start_login ( iscsi );
+       /* Issue command or open connection as appropriate */
+       if ( iscsi->status ) {
+               iscsi_start_command ( iscsi );
+       } else {
+               if ( ( rc = iscsi_open_connection ( iscsi ) ) != 0 )
+                       goto done;
+       }
+
+       /* Wait for command to complete */
+       iscsi->rc = -EINPROGRESS;
+       while ( iscsi->rc == -EINPROGRESS )
+               step();
+       rc = iscsi->rc;
+
+ done:
+       iscsi->command = NULL;
+       return rc;
 }
 
-/** iSCSI stream operations */
-static struct stream_application_operations iscsi_stream_operations = {
-       .closed         = iscsi_closed,
-       .connected      = iscsi_connected,
-       .acked          = iscsi_acked,
-       .newdata        = iscsi_newdata,
-       .senddata       = iscsi_senddata,
+static int iscsi_detached_command ( struct scsi_device *scsi __unused,
+                                   struct scsi_command *command __unused ) {
+       return -ENODEV;
+}
+
+/**
+ * Shut down iSCSI interface
+ *
+ * @v scsi             SCSI device
+ */
+void iscsi_detach ( struct scsi_device *scsi ) {
+       struct iscsi_session *iscsi =
+               container_of ( scsi->backend, struct iscsi_session, refcnt );
+
+       iscsi_close_connection ( iscsi, 0 );
+       process_del ( &iscsi->process );
+       scsi->command = iscsi_detached_command;
+       ref_put ( scsi->backend );
+       scsi->backend = NULL;
+}
+
+/****************************************************************************
+ *
+ * Instantiator
+ *
+ */
+
+/** iSCSI root path components (as per RFC4173) */
+enum iscsi_root_path_component {
+       RP_LITERAL = 0,
+       RP_SERVERNAME,
+       RP_PROTOCOL,
+       RP_PORT,
+       RP_LUN,
+       RP_TARGETNAME,
+       NUM_RP_COMPONENTS
 };
 
 /**
- * Issue SCSI command via iSCSI session
+ * Parse iSCSI LUN
  *
  * @v iscsi            iSCSI session
- * @v command          SCSI command
- * @v parent           Parent asynchronous operation
+ * @v lun_string       LUN string representation (as per RFC4173)
  * @ret rc             Return status code
  */
-int iscsi_issue ( struct iscsi_session *iscsi, struct scsi_command *command,
-                 struct async *parent ) {
-       int rc;
+static int iscsi_parse_lun ( struct iscsi_session *iscsi,
+                            const char *lun_string ) {
+       char *p = ( char * ) lun_string;
+       union {
+               uint64_t u64;
+               uint16_t u16[4];
+       } lun;
+       int i;
+
+       for ( i = 0 ; i < 4 ; i++ ) {
+               lun.u16[i] = strtoul ( p, &p, 16 );
+               if ( *p != '-' )
+                       return -EINVAL;
+               p++;
+       }
+       if ( *p )
+               return -EINVAL;
 
-       assert ( iscsi->command == NULL );
-       iscsi->command = command;
+       iscsi->lun = lun.u64;
+       return 0;
+}
 
-       if ( iscsi->instant_rc ) {
-               /* Abort immediately rather than retrying */
-               return iscsi->instant_rc;
-       } else if ( iscsi->status ) {
-               /* Session already open: issue command */
-               iscsi_start_command ( iscsi );
-               stream_kick ( &iscsi->stream );
-       } else {
-               /* Session not open: initiate login */
-               iscsi->stream.op = &iscsi_stream_operations;
-               if ( ( rc = tcp_open ( &iscsi->stream ) ) != 0 ) {
-                       DBGC ( iscsi, "iSCSI %p could not open stream: %s\n ",
-                              iscsi, strerror ( rc ) );
-                       return rc;
+/**
+ * Parse iSCSI root path
+ *
+ * @v iscsi            iSCSI session
+ * @v root_path                iSCSI root path (as per RFC4173)
+ * @ret rc             Return status code
+ */
+static int iscsi_parse_root_path ( struct iscsi_session *iscsi,
+                                  const char *root_path ) {
+       const char *p = root_path;
+       char *fragment;
+       size_t len;
+       enum iscsi_root_path_component i;
+       int rc;
+
+       for ( i = 0 ; i < NUM_RP_COMPONENTS ; i++ ) {
+               len = strcspn ( p, ":" );
+               fragment = strndup ( p, len );
+               if ( ! fragment ) {
+                       DBGC ( iscsi, "iSCSI %p could not duplicate root "
+                              "path component at %s\n", iscsi, p );
+                       return -ENOMEM;
                }
-               if ( ( rc = stream_connect ( &iscsi->stream,
-                                            &iscsi->target ) ) != 0 ) {
-                       DBGC ( iscsi, "iSCSI %p could not connect: %s\n",
-                              iscsi, strerror ( rc ) );
-                       return rc;
+               switch ( i ) {
+               case RP_SERVERNAME:
+                       iscsi->target_address = fragment;
+                       break;
+               case RP_PORT:
+                       iscsi->target_port = strtoul ( fragment, NULL, 10 );
+                       if ( ! iscsi->target_port )
+                               iscsi->target_port = ISCSI_PORT;
+                       free ( fragment );
+                       break;
+               case RP_LUN:
+                       rc = iscsi_parse_lun ( iscsi, fragment );
+                       free ( fragment );
+                       if ( rc != 0 ) {
+                               DBGC ( iscsi, "iSCSI %p invalid LUN %s\n",
+                                      iscsi, fragment );
+                               return rc;
+                       }
+                       break;
+               case RP_TARGETNAME:
+                       iscsi->target_iqn = fragment;
+                       break;
+               default:
+                       free ( fragment );
+                       break;
                }
+               p += len;
        }
 
-       async_init ( &iscsi->async, &default_async_operations, parent );
        return 0;
 }
 
 /**
- * Close down iSCSI session
+ * Attach iSCSI interface
  *
- * @v iscsi            iSCSI session
- * @ret aop            Asynchronous operation
+ * @v scsi             SCSI device
+ * @v root_path                iSCSI root path (as per RFC4173)
+ * @ret rc             Return status code
  */
-void iscsi_shutdown ( struct iscsi_session *iscsi ) {
-       iscsi_close ( iscsi );
+int iscsi_attach ( struct scsi_device *scsi, const char *root_path ) {
+       struct iscsi_session *iscsi;
+       int rc;
+
+       /* Allocate and initialise structure */
+       iscsi = zalloc ( sizeof ( *iscsi ) );
+       if ( ! iscsi )
+               return -ENOMEM;
+       iscsi->refcnt.free = iscsi_free;
+       xfer_init ( &iscsi->socket, &iscsi_socket_operations, &iscsi->refcnt );
+       process_init ( &iscsi->process, iscsi_tx_step, &iscsi->refcnt );
+
+       /* Parse root path */
+       if ( ( rc = iscsi_parse_root_path ( iscsi, root_path ) ) != 0 )
+               goto err;
+
+       /* Sanity checks */
+       if ( ! iscsi->target_address ) {
+               DBGC ( iscsi, "iSCSI %p does not yet support discovery\n",
+                      iscsi );
+               rc = -ENOTSUP;
+               goto err;
+       }
+       if ( ! iscsi->target_iqn ) {
+               DBGC ( iscsi, "iSCSI %p no target address supplied in %s\n",
+                      iscsi, root_path );
+               rc = -EINVAL;
+               goto err;
+       }
+
+       /* Attach parent interface, mortalise self, and return */
+       scsi->backend = ref_get ( &iscsi->refcnt );
+       scsi->command = iscsi_command;
+       scsi->lun = iscsi->lun;
+       ref_put ( &iscsi->refcnt );
+       return 0;
+       
+ err:
+       ref_put ( &iscsi->refcnt );
+       return rc;
 }
+
+/****************************************************************************
+ *
+ * DHCP option applicators
+ *
+ */
+
+/**
+ * Apply DHCP iSCSI option
+ *
+ * @v tag              DHCP option tag
+ * @v option           DHCP option
+ * @ret rc             Return status code
+ */
+static int apply_dhcp_iscsi_string ( unsigned int tag,
+                                    struct dhcp_option *option ) {
+       char *prefix = "";
+       size_t prefix_len;
+       size_t len;
+       char **string;
+       char *p;
+
+       /* Identify string and prefix */
+       switch ( tag ) {
+       case DHCP_ISCSI_INITIATOR_IQN:
+               string = &iscsi_initiator_iqn;
+               break;
+       case DHCP_EB_USERNAME:
+               string = &iscsi_username;
+               break;
+       case DHCP_EB_PASSWORD:
+               string = &iscsi_password;
+               break;
+       case DHCP_HOST_NAME:
+               string = &iscsi_default_initiator_iqn;
+               prefix = "iqn.2000-09.org.etherboot:";
+               break;
+       default:
+               assert ( 0 );
+               return -EINVAL;
+       }
+
+       /* Free old string */
+       free ( *string );
+       *string = NULL;
+
+       /* Allocate and fill new string */
+       prefix_len = strlen ( prefix );
+       len = ( prefix_len + option->len + 1 );
+       p = *string = malloc ( len );
+       if ( ! p )
+               return -ENOMEM;
+       strcpy ( p, prefix );
+       dhcp_snprintf ( ( p + prefix_len ), ( len - prefix_len ), option );
+       return 0;
+}
+
+/** DHCP iSCSI option applicators */
+struct dhcp_option_applicator dhcp_iscsi_applicators[] __dhcp_applicator = {
+       {
+               .tag = DHCP_ISCSI_INITIATOR_IQN,
+               .apply = apply_dhcp_iscsi_string,
+       },
+       {
+               .tag = DHCP_EB_USERNAME,
+               .apply = apply_dhcp_iscsi_string,
+       },
+       {
+               .tag = DHCP_EB_PASSWORD,
+               .apply = apply_dhcp_iscsi_string,
+       },
+       {
+               .tag = DHCP_HOST_NAME,
+               .apply = apply_dhcp_iscsi_string,
+       },
+};
index 22dccb1..9331357 100644 (file)
@@ -1,48 +1,36 @@
 #include <stdint.h>
 #include <string.h>
 #include <stdio.h>
-#include <byteswap.h>
-#include <gpxe/netdevice.h>
 #include <gpxe/iscsi.h>
-#include <gpxe/ibft.h>
-#include <gpxe/tcpip.h>
+#include <gpxe/dhcp.h>
 #include <int13.h>
 
-static struct iscsi_device test_iscsidev;
-
-int test_iscsiboot ( const char *initiator_iqn,
-                    struct sockaddr_tcpip *target,
-                    const char *target_iqn,
-                    unsigned int lun,
-                    const char *username,
-                    const char *password,
-                    struct net_device *netdev,
-                    unsigned int drivenum ) {
+int iscsiboot ( const char *root_path ) {
+       struct scsi_device scsi;
        struct int13_drive drive;
        int rc;
 
-       memset ( &test_iscsidev, 0, sizeof ( test_iscsidev ) );
-       memcpy ( &test_iscsidev.iscsi.target, target,
-                sizeof ( test_iscsidev.iscsi.target ) );
-       test_iscsidev.iscsi.initiator_iqn = initiator_iqn;
-       test_iscsidev.iscsi.target_iqn = target_iqn;
-       test_iscsidev.iscsi.lun = lun;
-       test_iscsidev.iscsi.username = username;
-       test_iscsidev.iscsi.password = password;
-
-       printf ( "Initialising %s\n", target_iqn );
-       if ( ( rc = init_iscsidev ( &test_iscsidev ) ) != 0 ) {
-               printf ( "Could not reach %s: %s\n", target_iqn,
+       memset ( &scsi, 0, sizeof ( scsi ) );
+       memset ( &drive, 0, sizeof ( drive ) );
+
+       printf ( "iSCSI booting from %s\n", root_path );
+
+       if ( ( rc = iscsi_attach ( &scsi, root_path ) ) != 0 ) {
+               printf ( "Could not attach iSCSI device: %s\n",
                         strerror ( rc ) );
-               return rc;
+               goto error_attach;
        }
-       ibft_fill_data ( netdev, &test_iscsidev.iscsi );
-       memset ( &drive, 0, sizeof ( drive ) );
-       drive.drive = drivenum;
-       drive.blockdev = &test_iscsidev.scsi.blockdev;
+       if ( ( rc = init_scsidev ( &scsi ) ) != 0 ) {
+               printf ( "Could not initialise iSCSI device: %s\n",
+                        strerror ( rc ) );
+               goto error_init;
+       }
+
+       drive.drive = find_global_dhcp_num_option ( DHCP_EB_BIOS_DRIVE );
+       drive.blockdev = &scsi.blockdev;
+
        register_int13_drive ( &drive );
-       printf ( "Registered %s as BIOS drive %#02x\n",
-                target_iqn, drive.drive );
+       printf ( "Registered as BIOS drive %#02x\n", drive.drive );
        printf ( "Booting from BIOS drive %#02x\n", drive.drive );
        rc = int13_boot ( drive.drive );
        printf ( "Boot failed\n" );
@@ -50,7 +38,8 @@ int test_iscsiboot ( const char *initiator_iqn,
        printf ( "Unregistering BIOS drive %#02x\n", drive.drive );
        unregister_int13_drive ( &drive );
 
-       fini_iscsidev ( &test_iscsidev );
-
+ error_init:
+       iscsi_detach ( &scsi );
+ error_attach:
        return rc;
 }
index 302e189..277e8b0 100644 (file)
@@ -44,56 +44,96 @@ static struct net_device * find_boot_netdev ( void ) {
 }
 
 /**
- * Boot from a network device
+ * Boot using filename
  *
- * @v netdev           Network device
+ * @v filename         Boot filename
+ * @ret rc             Return status code
  */
-void netboot ( struct net_device *netdev ) {
-       char filename[256];
+static int boot_filename ( const char *filename ) {
        struct image *image;
        int rc;
 
-       /* Open device and display device status */
-       if ( ( rc = ifopen ( netdev ) ) != 0 )
-               return;
-       ifstat ( netdev );
-
-       /* Configure device via DHCP */
-       if ( ( rc = dhcp ( netdev ) ) != 0 )
-               return;
-       route();
-
-       /* Try to download and boot whatever we are given as a filename */
-       dhcp_snprintf ( filename, sizeof ( filename ),
-                       find_global_dhcp_option ( DHCP_BOOTFILE_NAME ) );
-       if ( ! filename[0] ) {
-               printf ( "No boot filename\n" );
-               return;
-       }
-       printf ( "Booting \"%s\"\n", filename );
        image = alloc_image();
        if ( ! image ) {
                printf ( "Out of memory\n" );
-               return;
+               return -ENOMEM;
        }
        if ( ( rc = imgfetch ( image, filename, 0 ) ) != 0 ) {
                printf ( "Could not retrieve %s: %s\n",
                         filename, strerror ( rc ) );
                image_put ( image );
-               return;
+               return rc;
        }
        if ( ( rc = imgload ( image ) ) != 0 ) {
                printf ( "Could not load %s: %s\n", image->name,
                         strerror ( rc ) );
                image_put ( image );
-               return;
+               return rc;
        }
        if ( ( rc = imgexec ( image ) ) != 0 ) {
                printf ( "Could not execute %s: %s\n", image->name,
                         strerror ( rc ) );
                image_put ( image );
-               return;
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Boot using root path
+ *
+ * @v root_path                Root path
+ * @ret rc             Return status code
+ */
+static int boot_root_path ( const char *root_path ) {
+       int rc;
+
+       /* Quick hack */
+       if ( ( rc = iscsiboot ( root_path ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Boot from a network device
+ *
+ * @v netdev           Network device
+ * @ret rc             Return status code
+ */
+int netboot ( struct net_device *netdev ) {
+       char buf[256];
+       int rc;
+
+       /* Open device and display device status */
+       if ( ( rc = ifopen ( netdev ) ) != 0 )
+               return rc;
+       ifstat ( netdev );
+
+       /* Configure device via DHCP */
+       if ( ( rc = dhcp ( netdev ) ) != 0 )
+               return rc;
+       route();
+
+       /* Try to download and boot whatever we are given as a filename */
+       dhcp_snprintf ( buf, sizeof ( buf ),
+                       find_global_dhcp_option ( DHCP_BOOTFILE_NAME ) );
+       if ( buf[0] ) {
+               printf ( "Booting from filename \"%s\"\n", buf );
+               return boot_filename ( buf );
        }
+       
+       /* No filename; try the root path */
+       dhcp_snprintf ( buf, sizeof ( buf ),
+                       find_global_dhcp_option ( DHCP_ROOT_PATH ) );
+       if ( buf[0] ) {
+               printf ( "Booting from root path \"%s\"\n", buf );
+               return boot_root_path ( buf );
+       }
+
+       printf ( "No filename or root path specified\n" );
+       return -ENOENT;
 }
 
 /**