Start restructuring pxe_tftp.c to cope with multicast (MTFTP) receives.
authorMichael Brown <mcb30@etherboot.org>
Sat, 2 Feb 2008 15:59:32 +0000 (15:59 +0000)
committerMichael Brown <mcb30@etherboot.org>
Sat, 2 Feb 2008 15:59:32 +0000 (15:59 +0000)
src/interface/pxe/pxe_tftp.c

index 64f7ffd..976298a 100644 (file)
 #include <gpxe/uaccess.h>
 #include <gpxe/in.h>
 #include <gpxe/tftp.h>
-#include <gpxe/posix_io.h>
+#include <gpxe/xfer.h>
+#include <gpxe/open.h>
+#include <gpxe/process.h>
 #include <pxe.h>
 
-/** File descriptor for "single-file-only" PXE TFTP transfer */
-static int pxe_single_fd = -1;
+/** A PXE TFTP connection */
+struct pxe_tftp_connection {
+       /** Data transfer interface */
+       struct xfer_interface xfer;
+       /** Data buffer */
+       userptr_t buffer;
+       /** Size of data buffer */
+       size_t size;
+       /** Starting offset of data buffer */
+       size_t start;
+       /** File position */
+       size_t offset;
+       /** Maximum file position */
+       size_t max_offset;
+       /** Block size */
+       size_t blksize;
+       /** Block index */
+       unsigned int blkidx;
+       /** Overall return status code */
+       int rc;
+};
+
+/** The PXE TFTP connection */
+static struct pxe_tftp_connection pxe_tftp = {
+       .xfer = XFER_INIT ( &null_xfer_ops ),
+};
+
+/**
+ * Close PXE TFTP connection
+ *
+ * @v rc               Final status code
+ */
+static void pxe_tftp_close ( int rc ) {
+       xfer_nullify ( &pxe_tftp.xfer );
+       xfer_close ( &pxe_tftp.xfer, rc );
+       pxe_tftp.rc = rc;
+}
+
+/**
+ * Receive new data
+ *
+ * @v xfer             Data transfer interface
+ * @v iobuf            I/O buffer
+ * @v meta             Transfer metadata
+ * @ret rc             Return status code
+ */
+static int pxe_tftp_xfer_deliver_iob ( struct xfer_interface *xfer __unused,
+                                      struct io_buffer *iobuf,
+                                      struct xfer_metadata *meta ) {
+       size_t len = iob_len ( iobuf );
+       int rc = 0;
+
+       /* Calculate new buffer position */
+       if ( meta->whence != SEEK_CUR )
+               pxe_tftp.offset = 0;
+       pxe_tftp.offset += meta->offset;
+
+       /* Copy data block to buffer */
+       if ( len == 0 ) {
+               /* No data (pure seek); treat as success */
+       } else if ( pxe_tftp.offset < pxe_tftp.start ) {
+               DBG ( " buffer underrun at %zx (min %zx)",
+                     pxe_tftp.offset, pxe_tftp.start );
+               rc = -ENOBUFS;
+       } else if ( ( pxe_tftp.offset + len ) >
+                   ( pxe_tftp.start + pxe_tftp.size ) ) {
+               DBG ( " buffer overrun at %zx (max %zx)",
+                     ( pxe_tftp.offset + len ),
+                     ( pxe_tftp.start + pxe_tftp.size ) );
+               rc = -ENOBUFS;
+       } else {
+               copy_to_user ( pxe_tftp.buffer,
+                              ( pxe_tftp.offset - pxe_tftp.start ),
+                              iobuf->data, len );
+       }
+
+       /* Calculate new buffer position */
+       pxe_tftp.offset += len;
+
+       /* Mildly ugly hack; assume that the first non-zero seek
+        * indicates the block size.
+        */
+       if ( pxe_tftp.blksize == 0 )
+               pxe_tftp.blksize = pxe_tftp.offset;
 
-/** Block size for "single-file-only" PXE TFTP transfer */
-static size_t pxe_single_blksize;
+       /* Record maximum offset as the file size */
+       if ( pxe_tftp.max_offset < pxe_tftp.offset )
+               pxe_tftp.max_offset = pxe_tftp.offset;
 
-/** Current block index for "single-file-only" PXE TFTP transfer */
-static unsigned int pxe_single_blkidx;
+       /* Terminate transfer on error */
+       if ( rc != 0 )
+               pxe_tftp_close ( rc );
 
-/** Length of a PXE-derived URI
+       free_iob ( iobuf );
+       return rc;
+}
+
+/**
+ * Handle close() event
  *
- * The "single-file-only" API calls use a filename field of 128 bytes.
- * 256 bytes provides plenty of space for constructing the (temporary)
- * full URI.
+ * @v xfer             Data transfer interface
+ * @v rc               Reason for close
  */
-#define PXE_URI_LEN 256
+static void pxe_tftp_xfer_close ( struct xfer_interface *xfer __unused,
+                                 int rc ) {
+       pxe_tftp_close ( rc );
+}
+
+static struct xfer_interface_operations pxe_tftp_xfer_ops = {
+       .close          = pxe_tftp_xfer_close,
+       .vredirect      = xfer_vopen,
+       .window         = unlimited_xfer_window,
+       .alloc_iob      = default_xfer_alloc_iob,
+       .deliver_iob    = pxe_tftp_xfer_deliver_iob,
+       .deliver_raw    = xfer_deliver_as_iob,
+};
 
 /**
- * Build PXE URI string
+ * Maximum length of a PXE TFTP URI
  *
- * @v uri_string       URI string to fill in
- * @v ipaddress                Server IP address (in network byte order)
- * @v port             Server port (in network byte order)
- * @v filename         File name
- * @v blksize          Requested block size, or 0
+ * The PXE TFTP API provides 128 characters for the filename; the
+ * extra 128 bytes allow for the remainder of the URI.
+ */
+#define PXE_TFTP_URI_LEN 256
+
+/**
+ * Open PXE TFTP connection
  *
- * The URI string buffer must be at least @c PXE_URI_LEN bytes long.
+ * @v ipaddress                IP address
+ * @v port             TFTP server port
+ * @v filename         File name
+ * @v blksize          Requested block size
+ * @ret rc             Return status code
  */
-static void pxe_tftp_build_uri ( char *uri_string,
-                                uint32_t ipaddress, unsigned int port,
-                                const unsigned char *filename,
-                                int blksize ) {
+static int pxe_tftp_open ( uint32_t ipaddress, unsigned int port,
+                          const unsigned char *filename, size_t blksize ) {
+       char uri_string[PXE_TFTP_URI_LEN];
        struct in_addr address;
+       int rc;
+
+       /* Intel bug-for-bug hack */
+       pxe_set_cached_filename ( filename );
+
+       /* Reset PXE TFTP connection structure */
+       memset ( &pxe_tftp, 0, sizeof ( pxe_tftp ) );
+       xfer_init ( &pxe_tftp.xfer, &pxe_tftp_xfer_ops, NULL );
+       pxe_tftp.rc = -EINPROGRESS;
 
+       /* Construct URI string */
        address.s_addr = ipaddress;
        if ( ! port )
                port = htons ( TFTP_PORT );
-       if ( ! blksize )
-               blksize = TFTP_MAX_BLKSIZE;
-       tftp_set_request_blksize ( blksize );
-
-       snprintf ( uri_string, PXE_URI_LEN, "tftp://%s:%d%s%s",
+       if ( blksize < TFTP_DEFAULT_BLKSIZE )
+               blksize = TFTP_DEFAULT_BLKSIZE;
+       snprintf ( uri_string, sizeof ( uri_string ),
+                  "tftp://%s:%d%s%s?blksize=%d",
                   inet_ntoa ( address ), ntohs ( port ),
-                  ( ( filename[0] == '/' ) ? "" : "/" ), filename );
-}
+                  ( ( filename[0] == '/' ) ? "" : "/" ), filename, blksize );
+       DBG ( " %s", uri_string );
 
-/**
- * Read as much as possible from file
- *
- * @v fd                               File descriptor
- * @v buffer                           Data buffer
- * @v max_len                          Maximum length to read
- * @ret len                            Actual length read, or negative error
- */
-static ssize_t pxe_tftp_read_all ( int fd, userptr_t buffer,
-                                  size_t max_len ) {
-       fd_set fdset;
-       off_t offset = 0;
-       int ready;
-       ssize_t len;
-
-       do {
-               FD_ZERO ( &fdset );
-               FD_SET ( fd, &fdset );
-               ready = select ( &fdset, 1 );
-               if ( ready < 0 )
-                       return ready;
-               len = read_user ( fd, buffer, offset, max_len );
-               if ( len < 0 )
-                       return len;
-               offset += len;
-               max_len -= len;
-       } while ( max_len && len );
-
-       return offset;
+       /* Open PXE TFTP connection */
+       if ( ( rc = xfer_open_uri_string ( &pxe_tftp.xfer,
+                                          uri_string ) ) != 0 ) {
+               DBG ( " could not open (%s)\n", strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
 }
 
 /**
@@ -131,11 +226,6 @@ static ssize_t pxe_tftp_read_all ( int fd, userptr_t buffer,
  * routing will take place.  See the relevant
  * @ref pxe_routing "implementation note" for more details.
  *
- * Because we support arbitrary protocols, most of which have no
- * notion of "block size" and will return data in arbitrary-sized
- * chunks, we cheat and pretend to the caller that the blocksize is
- * always accepted as-is.
- *
  * On x86, you must set the s_PXE::StatusCallout field to a nonzero
  * value before calling this function in protected mode.  You cannot
  * call this function with a 32-bit stack segment.  (See the relevant
@@ -157,33 +247,35 @@ static ssize_t pxe_tftp_read_all ( int fd, userptr_t buffer,
  * other PXE API call "if an MTFTP connection is active".
  */
 PXENV_EXIT_t pxenv_tftp_open ( struct s_PXENV_TFTP_OPEN *tftp_open ) {
-       char uri_string[PXE_URI_LEN];
+       int rc;
 
        DBG ( "PXENV_TFTP_OPEN" );
 
        /* Guard against callers that fail to close before re-opening */
-       close ( pxe_single_fd );
-       pxe_single_fd = -1;
-
-       /* Construct URI */
-       pxe_tftp_build_uri ( uri_string, tftp_open->ServerIPAddress,
-                            tftp_open->TFTPPort, tftp_open->FileName,
-                            tftp_open->PacketSize );
-       DBG ( " %s", uri_string );
-
-       /* Open URI */
-       pxe_single_fd = open ( uri_string );
-       if ( pxe_single_fd < 0 ) {
-               tftp_open->Status = PXENV_STATUS ( pxe_single_fd );
+       pxe_tftp_close ( 0 );
+
+       /* Open connection */
+       if ( ( rc = pxe_tftp_open ( tftp_open->ServerIPAddress,
+                                   tftp_open->TFTPPort,
+                                   tftp_open->FileName,
+                                   tftp_open->PacketSize ) ) != 0 ) {
+               tftp_open->Status = PXENV_STATUS ( rc );
                return PXENV_EXIT_FAILURE;
        }
 
-       /* Record parameters for later use */
-       pxe_single_blksize = tftp_open->PacketSize;
-       pxe_single_blkidx = 0;
+       /* Wait for OACK to arrive so that we have the block size */
+       while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
+               ( pxe_tftp.blksize == 0 ) ) {
+               step();
+       }
+       tftp_open->PacketSize = pxe_tftp.blksize;
+
+       /* EINPROGRESS is normal; we don't wait for the whole transfer */
+       if ( rc == -EINPROGRESS )
+               rc = 0;
 
-       tftp_open->Status = PXENV_STATUS_SUCCESS;
-       return PXENV_EXIT_SUCCESS;
+       tftp_open->Status = PXENV_STATUS ( rc );
+       return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
 }
 
 /**
@@ -206,8 +298,7 @@ PXENV_EXIT_t pxenv_tftp_open ( struct s_PXENV_TFTP_OPEN *tftp_open ) {
 PXENV_EXIT_t pxenv_tftp_close ( struct s_PXENV_TFTP_CLOSE *tftp_close ) {
        DBG ( "PXENV_TFTP_CLOSE" );
 
-       close ( pxe_single_fd );
-       pxe_single_fd = -1;
+       pxe_tftp_close ( 0 );
        tftp_close->Status = PXENV_STATUS_SUCCESS;
        return PXENV_EXIT_SUCCESS;
 }
@@ -274,25 +365,29 @@ PXENV_EXIT_t pxenv_tftp_close ( struct s_PXENV_TFTP_CLOSE *tftp_close ) {
  * @ref pxe_x86_pmode16 "implementation note" for more details.)
  */
 PXENV_EXIT_t pxenv_tftp_read ( struct s_PXENV_TFTP_READ *tftp_read ) {
-       userptr_t buffer;
-       ssize_t len;
+       int rc;
 
        DBG ( "PXENV_TFTP_READ to %04x:%04x",
              tftp_read->Buffer.segment, tftp_read->Buffer.offset );
 
-       buffer = real_to_user ( tftp_read->Buffer.segment,
-                               tftp_read->Buffer.offset );
-       len = pxe_tftp_read_all ( pxe_single_fd, buffer, pxe_single_blksize );
-       if ( len < 0 ) {
-               tftp_read->Status = PXENV_STATUS ( len );
-               return PXENV_EXIT_FAILURE;
-       }
-
-       tftp_read->BufferSize = len;
-       tftp_read->PacketNumber = ++pxe_single_blkidx;
-
-       tftp_read->Status = PXENV_STATUS_SUCCESS;
-       return PXENV_EXIT_SUCCESS;
+       /* Read single block into buffer */
+       pxe_tftp.buffer = real_to_user ( tftp_read->Buffer.segment,
+                                        tftp_read->Buffer.offset );
+       pxe_tftp.size = pxe_tftp.blksize;
+       pxe_tftp.start = pxe_tftp.offset;
+       while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
+               ( pxe_tftp.offset == pxe_tftp.start ) )
+               step();
+       pxe_tftp.buffer = UNULL;
+       tftp_read->BufferSize = ( pxe_tftp.offset - pxe_tftp.start );
+       tftp_read->PacketNumber = ++pxe_tftp.blkidx;
+
+       /* EINPROGRESS is normal if we haven't reached EOF yet */
+       if ( rc == -EINPROGRESS )
+               rc = 0;
+
+       tftp_read->Status = PXENV_STATUS ( rc );
+       return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
 }
 
 /**
@@ -388,40 +483,31 @@ PXENV_EXIT_t pxenv_tftp_read ( struct s_PXENV_TFTP_READ *tftp_read ) {
  */
 PXENV_EXIT_t pxenv_tftp_read_file ( struct s_PXENV_TFTP_READ_FILE
                                    *tftp_read_file ) {
-       char uri_string[PXE_URI_LEN];
-       int fd;
-       userptr_t buffer;
-       ssize_t len;
-       int rc = 0;
-
-       DBG ( "PXENV_TFTP_READ_FILE" );
-
-       /* Construct URI */
-       pxe_tftp_build_uri ( uri_string, tftp_read_file->ServerIPAddress,
-                            tftp_read_file->TFTPSrvPort,
-                            tftp_read_file->FileName, 0 );
-       DBG ( " %s", uri_string );
+       int rc;
 
-       DBG ( " to %08lx+%lx", tftp_read_file->Buffer,
+       DBG ( "PXENV_TFTP_READ_FILE to %08lx+%lx", tftp_read_file->Buffer,
              tftp_read_file->BufferSize );
 
-       /* Open URI */
-       fd = open ( uri_string );
-       if ( fd < 0 ) {
-               tftp_read_file->Status = PXENV_STATUS ( fd );
+       /* Open TFTP file */
+       if ( ( rc = pxe_tftp_open ( tftp_read_file->ServerIPAddress, 0,
+                                   tftp_read_file->FileName, 0 ) ) != 0 ) {
+               tftp_read_file->Status = PXENV_STATUS ( rc );
                return PXENV_EXIT_FAILURE;
        }
 
-       /* Read file */
-       buffer = phys_to_user ( tftp_read_file->Buffer );
-       len = pxe_tftp_read_all ( fd, buffer, tftp_read_file->BufferSize );
-       if ( len < 0 )
-               rc = len;
+       /* Read entire file */
+       pxe_tftp.buffer = phys_to_user ( tftp_read_file->Buffer );
+       pxe_tftp.size = tftp_read_file->BufferSize;
+       while ( ( rc = pxe_tftp.rc ) == -EINPROGRESS )
+               step();
+       pxe_tftp.buffer = UNULL;
+       tftp_read_file->BufferSize = pxe_tftp.max_offset;
+
+       /* Close TFTP file */
+       pxe_tftp_close ( rc );
 
-       close ( fd );
-       tftp_read_file->BufferSize = len;
        tftp_read_file->Status = PXENV_STATUS ( rc );
-       return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );        
+       return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
 }
 
 /**
@@ -468,33 +554,31 @@ PXENV_EXIT_t pxenv_tftp_read_file ( struct s_PXENV_TFTP_READ_FILE
  */
 PXENV_EXIT_t pxenv_tftp_get_fsize ( struct s_PXENV_TFTP_GET_FSIZE
                                    *tftp_get_fsize ) {
-       char uri_string[PXE_URI_LEN];
-       int fd;
-       ssize_t size;
+       int rc;
 
        DBG ( "PXENV_TFTP_GET_FSIZE" );
 
-       /* Construct URI */
-       pxe_tftp_build_uri ( uri_string, tftp_get_fsize->ServerIPAddress,
-                            0, tftp_get_fsize->FileName, 0 );
-       DBG ( " %s", uri_string );
-
-       /* Open URI */
-       fd = open ( uri_string );
-       if ( fd < 0 ) {
-               tftp_get_fsize->Status = PXENV_STATUS ( fd );
+       /* Open TFTP file */
+       if ( ( rc = pxe_tftp_open ( tftp_get_fsize->ServerIPAddress, 0,
+                                   tftp_get_fsize->FileName, 0 ) ) != 0 ) {
+               tftp_get_fsize->Status = PXENV_STATUS ( rc );
                return PXENV_EXIT_FAILURE;
        }
 
-       /* Determine size */
-       size = fsize ( fd );
-       close ( fd );
-       if ( size < 0 ) {
-               tftp_get_fsize->Status = PXENV_STATUS ( size );
-               return PXENV_EXIT_FAILURE;
+       /* Wait for initial seek to arrive, and record size */
+       while ( ( ( rc = pxe_tftp.rc ) == -EINPROGRESS ) &&
+               ( pxe_tftp.max_offset == 0 ) ) {
+               step();
        }
+       tftp_get_fsize->FileSize = pxe_tftp.max_offset;
 
-       tftp_get_fsize->FileSize = size;
-       tftp_get_fsize->Status = PXENV_STATUS_SUCCESS;
-       return PXENV_EXIT_SUCCESS;
+       /* EINPROGRESS is normal; we don't wait for the whole transfer */
+       if ( rc == -EINPROGRESS )
+               rc = 0;
+
+       /* Close TFTP file */
+       pxe_tftp_close ( rc );
+
+       tftp_get_fsize->Status = PXENV_STATUS ( rc );
+       return ( rc ? PXENV_EXIT_FAILURE : PXENV_EXIT_SUCCESS );
 }