]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[block] Centralise SAN device abstraction
authorMichael Brown <mcb30@ipxe.org>
Sat, 4 Mar 2017 18:43:07 +0000 (18:43 +0000)
committerMichael Brown <mcb30@ipxe.org>
Tue, 7 Mar 2017 13:40:35 +0000 (13:40 +0000)
Create a central SAN device abstraction to be shared between BIOS and
UEFI.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/core/sanboot.c [new file with mode: 0644]
src/include/ipxe/errfile.h
src/include/ipxe/sanboot.h

diff --git a/src/core/sanboot.c b/src/core/sanboot.c
new file mode 100644 (file)
index 0000000..da1b68b
--- /dev/null
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2017 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+/**
+ * @file
+ *
+ * SAN booting
+ *
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <ipxe/xfer.h>
+#include <ipxe/open.h>
+#include <ipxe/timer.h>
+#include <ipxe/process.h>
+#include <ipxe/iso9660.h>
+#include <ipxe/sanboot.h>
+
+/**
+ * Timeout for block device commands (in ticks)
+ *
+ * Underlying devices should ideally never become totally stuck.
+ * However, if they do, then the blocking SAN APIs provide no means
+ * for the caller to cancel the operation, and the machine appears to
+ * hang.  Use an overall timeout for all commands to avoid this
+ * problem and bounce timeout failures to the caller.
+ */
+#define SAN_COMMAND_TIMEOUT ( 15 * TICKS_PER_SEC )
+
+/** List of SAN devices */
+LIST_HEAD ( san_devices );
+
+/**
+ * Find SAN device by drive number
+ *
+ * @v drive            Drive number
+ * @ret sandev         SAN device, or NULL
+ */
+struct san_device * sandev_find ( unsigned int drive ) {
+       struct san_device *sandev;
+
+       list_for_each_entry ( sandev, &san_devices, list ) {
+               if ( sandev->drive == drive )
+                       return sandev;
+       }
+       return NULL;
+}
+
+/**
+ * Free SAN device
+ *
+ * @v refcnt           Reference count
+ */
+static void sandev_free ( struct refcnt *refcnt ) {
+       struct san_device *sandev =
+               container_of ( refcnt, struct san_device, refcnt );
+
+       assert ( ! timer_running ( &sandev->timer ) );
+       uri_put ( sandev->uri );
+       free ( sandev );
+}
+
+/**
+ * Close SAN device command
+ *
+ * @v sandev           SAN device
+ * @v rc               Reason for close
+ */
+static void sandev_command_close ( struct san_device *sandev, int rc ) {
+
+       /* Stop timer */
+       stop_timer ( &sandev->timer );
+
+       /* Restart interface */
+       intf_restart ( &sandev->command, rc );
+
+       /* Record command status */
+       sandev->command_rc = rc;
+}
+
+/**
+ * Record SAN device capacity
+ *
+ * @v sandev           SAN device
+ * @v capacity         SAN device capacity
+ */
+static void sandev_command_capacity ( struct san_device *sandev,
+                                     struct block_device_capacity *capacity ) {
+
+       /* Record raw capacity information */
+       memcpy ( &sandev->capacity, capacity, sizeof ( sandev->capacity ) );
+}
+
+/** SAN device command interface operations */
+static struct interface_operation sandev_command_op[] = {
+       INTF_OP ( intf_close, struct san_device *, sandev_command_close ),
+       INTF_OP ( block_capacity, struct san_device *,
+                 sandev_command_capacity ),
+};
+
+/** SAN device command interface descriptor */
+static struct interface_descriptor sandev_command_desc =
+       INTF_DESC ( struct san_device, command, sandev_command_op );
+
+/**
+ * Handle SAN device command timeout
+ *
+ * @v retry            Retry timer
+ */
+static void sandev_command_expired ( struct retry_timer *timer,
+                                    int over __unused ) {
+       struct san_device *sandev =
+               container_of ( timer, struct san_device, timer );
+
+       sandev_command_close ( sandev, -ETIMEDOUT );
+}
+
+/**
+ * Restart SAN device interface
+ *
+ * @v sandev           SAN device
+ * @v rc               Reason for restart
+ */
+static void sandev_restart ( struct san_device *sandev, int rc ) {
+
+       /* Restart block device interface */
+       intf_nullify ( &sandev->command ); /* avoid potential loops */
+       intf_restart ( &sandev->block, rc );
+
+       /* Close any outstanding command */
+       sandev_command_close ( sandev, rc );
+
+       /* Record device error */
+       sandev->block_rc = rc;
+}
+
+/**
+ * (Re)open SAN device
+ *
+ * @v sandev           SAN device
+ * @ret rc             Return status code
+ *
+ * This function will block until the device is available.
+ */
+int sandev_reopen ( struct san_device *sandev ) {
+       int rc;
+
+       /* Close any outstanding command and restart interface */
+       sandev_restart ( sandev, -ECONNRESET );
+
+       /* Mark device as being not yet open */
+       sandev->block_rc = -EINPROGRESS;
+
+       /* Open block device interface */
+       if ( ( rc = xfer_open_uri ( &sandev->block, sandev->uri ) ) != 0 ) {
+               DBGC ( sandev, "SAN %#02x could not (re)open URI: %s\n",
+                      sandev->drive, strerror ( rc ) );
+               return rc;
+       }
+
+       /* Wait for device to become available */
+       while ( sandev->block_rc == -EINPROGRESS ) {
+               step();
+               if ( xfer_window ( &sandev->block ) != 0 ) {
+                       sandev->block_rc = 0;
+                       return 0;
+               }
+       }
+
+       DBGC ( sandev, "SAN %#02x never became available: %s\n",
+              sandev->drive, strerror ( sandev->block_rc ) );
+       return sandev->block_rc;
+}
+
+/**
+ * Handle closure of underlying block device interface
+ *
+ * @v sandev           SAN device
+ * @ret rc             Reason for close
+ */
+static void sandev_block_close ( struct san_device *sandev, int rc ) {
+
+       /* Any closure is an error from our point of view */
+       if ( rc == 0 )
+               rc = -ENOTCONN;
+       DBGC ( sandev, "SAN %#02x went away: %s\n",
+              sandev->drive, strerror ( rc ) );
+
+       /* Close any outstanding command and restart interface */
+       sandev_restart ( sandev, rc );
+}
+
+/**
+ * Check SAN device flow control window
+ *
+ * @v sandev           SAN device
+ */
+static size_t sandev_block_window ( struct san_device *sandev __unused ) {
+
+       /* We are never ready to receive data via this interface.
+        * This prevents objects that support both block and stream
+        * interfaces from attempting to send us stream data.
+        */
+       return 0;
+}
+
+/** SAN device block interface operations */
+static struct interface_operation sandev_block_op[] = {
+       INTF_OP ( intf_close, struct san_device *, sandev_block_close ),
+       INTF_OP ( xfer_window, struct san_device *, sandev_block_window ),
+};
+
+/** SAN device block interface descriptor */
+static struct interface_descriptor sandev_block_desc =
+       INTF_DESC ( struct san_device, block, sandev_block_op );
+
+/** SAN device read/write command parameters */
+struct san_command_rw_params {
+       /** SAN device read/write operation */
+       int ( * block_rw ) ( struct interface *control, struct interface *data,
+                            uint64_t lba, unsigned int count,
+                            userptr_t buffer, size_t len );
+       /** Data buffer */
+       userptr_t buffer;
+       /** Starting LBA */
+       uint64_t lba;
+       /** Block count */
+       unsigned int count;
+};
+
+/** SAN device command parameters */
+union san_command_params {
+       /** Read/write command parameters */
+       struct san_command_rw_params rw;
+};
+
+/**
+ * Initiate SAN device read/write command
+ *
+ * @v sandev           SAN device
+ * @v params           Command parameters
+ * @ret rc             Return status code
+ */
+static int sandev_command_rw ( struct san_device *sandev,
+                              const union san_command_params *params ) {
+       size_t len = ( params->rw.count * sandev->capacity.blksize );
+       int rc;
+
+       /* Initiate read/write command */
+       if ( ( rc = params->rw.block_rw ( &sandev->block, &sandev->command,
+                                         params->rw.lba, params->rw.count,
+                                         params->rw.buffer, len ) ) != 0 ) {
+               DBGC ( sandev, "SAN %#02x could not initiate read/write: "
+                      "%s\n", sandev->drive, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Initiate SAN device read capacity command
+ *
+ * @v sandev           SAN device
+ * @v params           Command parameters
+ * @ret rc             Return status code
+ */
+static int
+sandev_command_read_capacity ( struct san_device *sandev,
+                              const union san_command_params *params __unused){
+       int rc;
+
+       /* Initiate read capacity command */
+       if ( ( rc = block_read_capacity ( &sandev->block,
+                                         &sandev->command ) ) != 0 ) {
+               DBGC ( sandev, "SAN %#02x could not initiate read capacity: "
+                      "%s\n", sandev->drive, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Execute a single SAN device command and wait for completion
+ *
+ * @v sandev           SAN device
+ * @v command          Command
+ * @v params           Command parameters (if required)
+ * @ret rc             Return status code
+ */
+static int
+sandev_command ( struct san_device *sandev,
+                int ( * command ) ( struct san_device *sandev,
+                                    const union san_command_params *params ),
+                const union san_command_params *params ) {
+       int rc;
+
+       /* Sanity check */
+       assert ( ! timer_running ( &sandev->timer ) );
+
+       /* Reopen block device if applicable */
+       if ( sandev_needs_reopen ( sandev ) &&
+            ( ( rc = sandev_reopen ( sandev ) ) != 0 ) ) {
+            goto err_reopen;
+       }
+
+       /* Start expiry timer */
+       start_timer_fixed ( &sandev->timer, SAN_COMMAND_TIMEOUT );
+
+       /* Initiate command */
+       if ( ( rc = command ( sandev, params ) ) != 0 )
+               goto err_op;
+
+       /* Wait for command to complete */
+       while ( timer_running ( &sandev->timer ) )
+               step();
+
+       /* Collect return status */
+       rc = sandev->command_rc;
+
+       return rc;
+
+ err_op:
+       stop_timer ( &sandev->timer );
+ err_reopen:
+       return rc;
+}
+
+/**
+ * Reset SAN device
+ *
+ * @v sandev           SAN device
+ * @ret rc             Return status code
+ */
+int sandev_reset ( struct san_device *sandev ) {
+       int rc;
+
+       DBGC ( sandev, "SAN %#02x reset\n", sandev->drive );
+
+       /* Close and reopen underlying block device */
+       if ( ( rc = sandev_reopen ( sandev ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Read from or write to SAN device
+ *
+ * @v sandev           SAN device
+ * @v lba              Starting logical block address
+ * @v count            Number of logical blocks
+ * @v buffer           Data buffer
+ * @v block_rw         Block read/write method
+ * @ret rc             Return status code
+ */
+int sandev_rw ( struct san_device *sandev, uint64_t lba,
+               unsigned int count, userptr_t buffer,
+               int ( * block_rw ) ( struct interface *control,
+                                    struct interface *data,
+                                    uint64_t lba, unsigned int count,
+                                    userptr_t buffer, size_t len ) ) {
+       union san_command_params params;
+       unsigned int remaining;
+       size_t frag_len;
+       int rc;
+
+       /* Initialise command parameters */
+       params.rw.block_rw = block_rw;
+       params.rw.buffer = buffer;
+       params.rw.lba = ( lba << sandev->blksize_shift );
+       params.rw.count = sandev->capacity.max_count;
+       remaining = ( count << sandev->blksize_shift );
+
+       /* Read/write fragments */
+       while ( remaining ) {
+
+               /* Determine fragment length */
+               if ( params.rw.count > remaining )
+                       params.rw.count = remaining;
+
+               /* Execute command */
+               if ( ( rc = sandev_command ( sandev, sandev_command_rw,
+                                            &params ) ) != 0 )
+                       return rc;
+
+               /* Move to next fragment */
+               frag_len = ( sandev->capacity.blksize * params.rw.count );
+               params.rw.buffer = userptr_add ( params.rw.buffer, frag_len );
+               params.rw.lba += params.rw.count;
+               remaining -= params.rw.count;
+       }
+
+       return 0;
+}
+
+/**
+ * Configure SAN device as a CD-ROM, if applicable
+ *
+ * @v sandev           SAN device
+ * @ret rc             Return status code
+ *
+ * Both BIOS and UEFI require SAN devices to be accessed with a block
+ * size of 2048.  While we could require the user to configure the
+ * block size appropriately, this is non-trivial and would impose a
+ * substantial learning effort on the user.  Instead, we check for the
+ * presence of the ISO9660 primary volume descriptor and, if found,
+ * then we force a block size of 2048 and map read/write requests
+ * appropriately.
+ */
+static int sandev_parse_iso9660 ( struct san_device *sandev ) {
+       static const struct iso9660_primary_descriptor_fixed primary_check = {
+               .type = ISO9660_TYPE_PRIMARY,
+               .id = ISO9660_ID,
+       };
+       struct iso9660_primary_descriptor *primary;
+       unsigned int blksize;
+       unsigned int blksize_shift;
+       unsigned int lba;
+       unsigned int count;
+       int rc;
+
+       /* Calculate required blocksize shift for potential CD-ROM access */
+       blksize = sandev->capacity.blksize;
+       blksize_shift = 0;
+       while ( blksize < ISO9660_BLKSIZE ) {
+               blksize <<= 1;
+               blksize_shift++;
+       }
+       if ( blksize > ISO9660_BLKSIZE ) {
+               /* Cannot be a CD-ROM.  This is not an error. */
+               rc = 0;
+               goto invalid_blksize;
+       }
+       lba = ( ISO9660_PRIMARY_LBA << blksize_shift );
+       count = ( 1 << blksize_shift );
+
+       /* Allocate scratch area */
+       primary = malloc ( ISO9660_BLKSIZE );
+       if ( ! primary ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+
+       /* Read primary volume descriptor */
+       if ( ( rc = sandev_rw ( sandev, lba, count, virt_to_user ( primary ),
+                               block_read ) ) != 0 ) {
+               DBGC ( sandev, "SAN %#02x could not read ISO9660 primary"
+                      "volume descriptor: %s\n",
+                      sandev->drive, strerror ( rc ) );
+               goto err_rw;
+       }
+
+       /* Configure as CD-ROM if applicable */
+       if ( memcmp ( primary, &primary_check, sizeof ( primary_check ) ) == 0){
+               DBGC ( sandev, "SAN %#02x contains an ISO9660 filesystem; "
+                      "treating as CD-ROM\n", sandev->drive );
+               sandev->blksize_shift = blksize_shift;
+               sandev->is_cdrom = 1;
+       }
+
+ err_rw:
+       free ( primary );
+ err_alloc:
+ invalid_blksize:
+       return rc;
+}
+
+/**
+ * Allocate SAN device
+ *
+ * @ret sandev         SAN device, or NULL
+ */
+struct san_device * alloc_sandev ( struct uri *uri, size_t priv_size ) {
+       struct san_device *sandev;
+
+       /* Allocate and initialise structure */
+       sandev = zalloc ( sizeof ( *sandev ) + priv_size );
+       if ( ! sandev )
+               return NULL;
+       ref_init ( &sandev->refcnt, sandev_free );
+       sandev->uri = uri_get ( uri );
+       intf_init ( &sandev->block, &sandev_block_desc, &sandev->refcnt );
+       sandev->block_rc = -EINPROGRESS;
+       intf_init ( &sandev->command, &sandev_command_desc, &sandev->refcnt );
+       timer_init ( &sandev->timer, sandev_command_expired, &sandev->refcnt );
+       sandev->priv = ( ( ( void * ) sandev ) + sizeof ( *sandev ) );
+
+       return sandev;
+}
+
+/**
+ * Register SAN device
+ *
+ * @v sandev           SAN device
+ * @ret rc             Return status code
+ */
+int register_sandev ( struct san_device *sandev ) {
+       int rc;
+
+       /* Check that drive number is not in use */
+       if ( sandev_find ( sandev->drive ) != NULL ) {
+               DBGC ( sandev, "SAN %#02x is already in use\n", sandev->drive );
+               return -EADDRINUSE;
+       }
+
+       /* Read device capacity */
+       if ( ( rc = sandev_command ( sandev, sandev_command_read_capacity,
+                                    NULL ) ) != 0 )
+               return rc;
+
+       /* Configure as a CD-ROM, if applicable */
+       if ( ( rc = sandev_parse_iso9660 ( sandev ) ) != 0 )
+               return rc;
+
+       /* Add to list of SAN devices */
+       list_add_tail ( &sandev->list, &san_devices );
+
+       return 0;
+}
+
+/**
+ * Unregister SAN device
+ *
+ * @v sandev           SAN device
+ */
+void unregister_sandev ( struct san_device *sandev ) {
+
+       /* Sanity check */
+       assert ( ! timer_running ( &sandev->timer ) );
+
+       /* Shut down interfaces */
+       intfs_shutdown ( 0, &sandev->block, &sandev->command, NULL );
+
+       /* Remove from list of SAN devices */
+       list_del ( &sandev->list );
+}
index 1a037b10ed58faca1518c9e3c5b97c4a1fa4eded..cd5c1959c4ffe5d7da504f40f3702ad9579c4b68 100644 (file)
@@ -72,6 +72,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define ERRFILE_blocktrans            ( ERRFILE_CORE | 0x00200000 )
 #define ERRFILE_pixbuf                ( ERRFILE_CORE | 0x00210000 )
 #define ERRFILE_efi_block             ( ERRFILE_CORE | 0x00220000 )
+#define ERRFILE_sanboot                       ( ERRFILE_CORE | 0x00230000 )
 
 #define ERRFILE_eisa                ( ERRFILE_DRIVER | 0x00000000 )
 #define ERRFILE_isa                 ( ERRFILE_DRIVER | 0x00010000 )
index 1c68a58ca2dc399b9d2892e742e78b01bdebf375..420d4dbed52afeccbf6017231ecd85be1b9b02aa 100644 (file)
 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #include <ipxe/api.h>
+#include <ipxe/refcnt.h>
+#include <ipxe/list.h>
+#include <ipxe/uri.h>
+#include <ipxe/retry.h>
+#include <ipxe/blockdev.h>
 #include <config/sanboot.h>
 
-struct uri;
+/** A SAN device */
+struct san_device {
+       /** Reference count */
+       struct refcnt refcnt;
+       /** List of SAN devices */
+       struct list_head list;
+
+       /** SAN device URI */
+       struct uri *uri;
+       /** Drive number */
+       unsigned int drive;
+
+       /** Underlying block device interface */
+       struct interface block;
+       /** Current device status */
+       int block_rc;
+
+       /** Command interface */
+       struct interface command;
+       /** Command timeout timer */
+       struct retry_timer timer;
+       /** Command status */
+       int command_rc;
+
+       /** Raw block device capacity */
+       struct block_device_capacity capacity;
+       /** Block size shift
+        *
+        * To allow for emulation of CD-ROM access, this represents
+        * the left-shift required to translate from exposed logical
+        * I/O blocks to underlying blocks.
+        */
+       unsigned int blksize_shift;
+       /** Drive is a CD-ROM */
+       int is_cdrom;
+
+       /** Driver private data */
+       void *priv;
+};
 
 /**
  * Calculate static inline sanboot API function name
@@ -91,4 +134,83 @@ int san_boot ( unsigned int drive );
  */
 int san_describe ( unsigned int drive );
 
+extern struct list_head san_devices;
+
+/** Iterate over all SAN devices */
+#define for_each_sandev( sandev ) \
+       list_for_each_entry ( (sandev), &san_devices, list )
+
+/** There exist some SAN devices
+ *
+ * @ret existence      Existence of SAN devices
+ */
+static inline int have_sandevs ( void ) {
+       return ( ! list_empty ( &san_devices ) );
+}
+
+/**
+ * Get reference to SAN device
+ *
+ * @v sandev           SAN device
+ * @ret sandev         SAN device
+ */
+static inline __attribute__ (( always_inline )) struct san_device *
+sandev_get ( struct san_device *sandev ) {
+       ref_get ( &sandev->refcnt );
+       return sandev;
+}
+
+/**
+ * Drop reference to SAN device
+ *
+ * @v sandev           SAN device
+ */
+static inline __attribute__ (( always_inline )) void
+sandev_put ( struct san_device *sandev ) {
+       ref_put ( &sandev->refcnt );
+}
+
+/**
+ * Calculate SAN device block size
+ *
+ * @v sandev           SAN device
+ * @ret blksize                Sector size
+ */
+static inline size_t sandev_blksize ( struct san_device *sandev ) {
+       return ( sandev->capacity.blksize << sandev->blksize_shift );
+}
+
+/**
+ * Calculate SAN device capacity
+ *
+ * @v sandev           SAN device
+ * @ret blocks         Number of blocks
+ */
+static inline uint64_t sandev_capacity ( struct san_device *sandev ) {
+       return ( sandev->capacity.blocks >> sandev->blksize_shift );
+}
+
+/**
+ * Check if SAN device needs to be reopened
+ *
+ * @v sandev           SAN device
+ * @ret needs_reopen   SAN device needs to be reopened
+ */
+static inline int sandev_needs_reopen ( struct san_device *sandev ) {
+       return ( sandev->block_rc != 0 );
+}
+
+extern struct san_device * sandev_find ( unsigned int drive );
+extern int sandev_reopen ( struct san_device *sandev );
+extern int sandev_reset ( struct san_device *sandev );
+extern int sandev_rw ( struct san_device *sandev, uint64_t lba,
+                      unsigned int count, userptr_t buffer,
+                      int ( * block_rw ) ( struct interface *control,
+                                           struct interface *data,
+                                           uint64_t lba, unsigned int count,
+                                           userptr_t buffer, size_t len ) );
+extern struct san_device * alloc_sandev ( struct uri *uri, size_t priv_size );
+extern int register_sandev ( struct san_device *sandev );
+extern void unregister_sandev ( struct san_device *sandev );
+
 #endif /* _IPXE_SANBOOT_H */