summaryrefslogtreecommitdiffstats
path: root/src/drivers/block
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers/block')
-rw-r--r--src/drivers/block/ata.c683
-rw-r--r--src/drivers/block/ibft.c471
-rw-r--r--src/drivers/block/ramdisk.c97
-rw-r--r--src/drivers/block/scsi.c878
-rw-r--r--src/drivers/block/srp.c830
5 files changed, 2242 insertions, 717 deletions
diff --git a/src/drivers/block/ata.c b/src/drivers/block/ata.c
index c59e788a4..0197483b3 100644
--- a/src/drivers/block/ata.c
+++ b/src/drivers/block/ata.c
@@ -19,12 +19,14 @@
FILE_LICENCE ( GPL2_OR_LATER );
#include <stddef.h>
+#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <byteswap.h>
+#include <ipxe/list.h>
+#include <ipxe/interface.h>
#include <ipxe/blockdev.h>
-#include <ipxe/process.h>
#include <ipxe/ata.h>
/** @file
@@ -33,156 +35,625 @@ FILE_LICENCE ( GPL2_OR_LATER );
*
*/
-static inline __attribute__ (( always_inline )) struct ata_device *
-block_to_ata ( struct block_device *blockdev ) {
- return container_of ( blockdev, struct ata_device, blockdev );
-}
+/******************************************************************************
+ *
+ * Interface methods
+ *
+ ******************************************************************************
+ */
/**
* Issue ATA command
*
- * @v ata ATA device
+ * @v control ATA control interface
+ * @v data ATA data interface
* @v command ATA command
- * @ret rc Return status code
+ * @ret tag Command tag, or negative error
*/
-static inline __attribute__ (( always_inline )) int
-ata_command ( struct ata_device *ata, struct ata_command *command ) {
- int rc;
+int ata_command ( struct interface *control, struct interface *data,
+ struct ata_cmd *command ) {
+ struct interface *dest;
+ ata_command_TYPE ( void * ) *op =
+ intf_get_dest_op ( control, ata_command, &dest );
+ void *object = intf_object ( dest );
+ int tag;
+
+ if ( op ) {
+ tag = op ( object, data, command );
+ } else {
+ /* Default is to fail to issue the command */
+ tag = -EOPNOTSUPP;
+ }
+
+ intf_put ( dest );
+ return tag;
+}
+
+/******************************************************************************
+ *
+ * ATA devices and commands
+ *
+ ******************************************************************************
+ */
+
+/** List of all ATA commands */
+static LIST_HEAD ( ata_commands );
+
+/** An ATA device */
+struct ata_device {
+ /** Reference count */
+ struct refcnt refcnt;
+ /** Block control interface */
+ struct interface block;
+ /** ATA control interface */
+ struct interface ata;
+
+ /** Device number
+ *
+ * Must be ATA_DEV_MASTER or ATA_DEV_SLAVE.
+ */
+ unsigned int device;
+ /** Maximum number of blocks per single transfer */
+ unsigned int max_count;
+ /** Device uses LBA48 extended addressing */
+ int lba48;
+};
+
+/** An ATA command */
+struct ata_command {
+ /** Reference count */
+ struct refcnt refcnt;
+ /** ATA device */
+ struct ata_device *atadev;
+ /** List of ATA commands */
+ struct list_head list;
+
+ /** Block data interface */
+ struct interface block;
+ /** ATA data interface */
+ struct interface ata;
+
+ /** Command type */
+ struct ata_command_type *type;
+ /** Command tag */
+ uint32_t tag;
+
+ /** Private data */
+ uint8_t priv[0];
+};
+
+/** An ATA command type */
+struct ata_command_type {
+ /** Name */
+ const char *name;
+ /** Additional working space */
+ size_t priv_len;
+ /** Command for non-LBA48-capable devices */
+ uint8_t cmd_lba;
+ /** Command for LBA48-capable devices */
+ uint8_t cmd_lba48;
+ /**
+ * Calculate data-in buffer
+ *
+ * @v atacmd ATA command
+ * @v buffer Available buffer
+ * @v len Available buffer length
+ * @ret data_in Data-in buffer
+ * @ret data_in_len Data-in buffer length
+ */
+ void ( * data_in ) ( struct ata_command *atacmd, userptr_t buffer,
+ size_t len, userptr_t *data_in,
+ size_t *data_in_len );
+ /**
+ * Calculate data-out buffer
+ *
+ *
+ * @v atacmd ATA command
+ * @v buffer Available buffer
+ * @v len Available buffer length
+ * @ret data_out Data-out buffer
+ * @ret data_out_len Data-out buffer length
+ */
+ void ( * data_out ) ( struct ata_command *atacmd, userptr_t buffer,
+ size_t len, userptr_t *data_out,
+ size_t *data_out_len );
+ /**
+ * Handle ATA command completion
+ *
+ * @v atacmd ATA command
+ * @v rc Reason for completion
+ */
+ void ( * done ) ( struct ata_command *atacmd, int rc );
+};
+
+/**
+ * Get reference to ATA device
+ *
+ * @v atadev ATA device
+ * @ret atadev ATA device
+ */
+static inline __attribute__ (( always_inline )) struct ata_device *
+atadev_get ( struct ata_device *atadev ) {
+ ref_get ( &atadev->refcnt );
+ return atadev;
+}
+
+/**
+ * Drop reference to ATA device
+ *
+ * @v atadev ATA device
+ */
+static inline __attribute__ (( always_inline )) void
+atadev_put ( struct ata_device *atadev ) {
+ ref_put ( &atadev->refcnt );
+}
+
+/**
+ * Get reference to ATA command
+ *
+ * @v atacmd ATA command
+ * @ret atacmd ATA command
+ */
+static inline __attribute__ (( always_inline )) struct ata_command *
+atacmd_get ( struct ata_command *atacmd ) {
+ ref_get ( &atacmd->refcnt );
+ return atacmd;
+}
+
+/**
+ * Drop reference to ATA command
+ *
+ * @v atacmd ATA command
+ */
+static inline __attribute__ (( always_inline )) void
+atacmd_put ( struct ata_command *atacmd ) {
+ ref_put ( &atacmd->refcnt );
+}
+
+/**
+ * Get ATA command private data
+ *
+ * @v atacmd ATA command
+ * @ret priv Private data
+ */
+static inline __attribute__ (( always_inline )) void *
+atacmd_priv ( struct ata_command *atacmd ) {
+ return atacmd->priv;
+}
+
+/**
+ * Free ATA command
+ *
+ * @v refcnt Reference count
+ */
+static void atacmd_free ( struct refcnt *refcnt ) {
+ struct ata_command *atacmd =
+ container_of ( refcnt, struct ata_command, refcnt );
+
+ /* Remove from list of commands */
+ list_del ( &atacmd->list );
+ atadev_put ( atacmd->atadev );
+
+ /* Free command */
+ free ( atacmd );
+}
+
+/**
+ * Close ATA command
+ *
+ * @v atacmd ATA command
+ * @v rc Reason for close
+ */
+static void atacmd_close ( struct ata_command *atacmd, int rc ) {
+ struct ata_device *atadev = atacmd->atadev;
+
+ if ( rc != 0 ) {
+ DBGC ( atadev, "ATA %p tag %08x closed: %s\n",
+ atadev, atacmd->tag, strerror ( rc ) );
+ }
+
+ /* Shut down interfaces */
+ intf_shutdown ( &atacmd->ata, rc );
+ intf_shutdown ( &atacmd->block, rc );
+}
+
+/**
+ * Handle ATA command completion
+ *
+ * @v atacmd ATA command
+ * @v rc Reason for close
+ */
+static void atacmd_done ( struct ata_command *atacmd, int rc ) {
+
+ /* Hand over to the command completion handler */
+ atacmd->type->done ( atacmd, rc );
+}
+
+/**
+ * Use provided data buffer for ATA command
+ *
+ * @v atacmd ATA command
+ * @v buffer Available buffer
+ * @v len Available buffer length
+ * @ret data Data buffer
+ * @ret data_len Data buffer length
+ */
+static void atacmd_data_buffer ( struct ata_command *atacmd __unused,
+ userptr_t buffer, size_t len,
+ userptr_t *data, size_t *data_len ) {
+ *data = buffer;
+ *data_len = len;
+}
- DBG ( "ATA cmd %02x dev %02x LBA%s %llx count %04x\n",
- command->cb.cmd_stat, command->cb.device,
- ( command->cb.lba48 ? "48" : "" ),
- ( unsigned long long ) command->cb.lba.native,
- command->cb.count.native );
+/**
+ * Use no data buffer for ATA command
+ *
+ * @v atacmd ATA command
+ * @v buffer Available buffer
+ * @v len Available buffer length
+ * @ret data Data buffer
+ * @ret data_len Data buffer length
+ */
+static void atacmd_data_none ( struct ata_command *atacmd __unused,
+ userptr_t buffer __unused, size_t len __unused,
+ userptr_t *data __unused,
+ size_t *data_len __unused ) {
+ /* Nothing to do */
+}
+
+/**
+ * Use private data buffer for ATA command
+ *
+ * @v atacmd ATA command
+ * @v buffer Available buffer
+ * @v len Available buffer length
+ * @ret data Data buffer
+ * @ret data_len Data buffer length
+ */
+static void atacmd_data_priv ( struct ata_command *atacmd,
+ userptr_t buffer __unused, size_t len __unused,
+ userptr_t *data, size_t *data_len ) {
+ *data = virt_to_user ( atacmd_priv ( atacmd ) );
+ *data_len = atacmd->type->priv_len;
+}
+
+/** ATA READ command type */
+static struct ata_command_type atacmd_read = {
+ .name = "READ",
+ .cmd_lba = ATA_CMD_READ,
+ .cmd_lba48 = ATA_CMD_READ_EXT,
+ .data_in = atacmd_data_buffer,
+ .data_out = atacmd_data_none,
+ .done = atacmd_close,
+};
+
+/** ATA WRITE command type */
+static struct ata_command_type atacmd_write = {
+ .name = "WRITE",
+ .cmd_lba = ATA_CMD_WRITE,
+ .cmd_lba48 = ATA_CMD_WRITE_EXT,
+ .data_in = atacmd_data_none,
+ .data_out = atacmd_data_buffer,
+ .done = atacmd_close,
+};
+
+/** ATA IDENTIFY private data */
+struct ata_identify_private {
+ /** Identity data */
+ struct ata_identity identity;
+};
+
+/**
+ * Return ATA model string (for debugging)
+ *
+ * @v identify ATA identity data
+ * @ret model Model string
+ */
+static const char * ata_model ( struct ata_identity *identity ) {
+ static union {
+ uint16_t words[ sizeof ( identity->model ) / 2 ];
+ char text[ sizeof ( identity->model ) + 1 /* NUL */ ];
+ } buf;
+ unsigned int i;
- /* Flag command as in-progress */
- command->rc = -EINPROGRESS;
+ for ( i = 0 ; i < ( sizeof ( identity->model ) / 2 ) ; i++ )
+ buf.words[i] = bswap_16 ( identity->model[i] );
- /* Issue ATA command */
- if ( ( rc = ata->command ( ata, command ) ) != 0 ) {
- /* Something went wrong with the issuing mechanism */
- DBG ( "ATA could not issue command: %s\n", strerror ( rc ) );
- return rc;
+ return buf.text;
+}
+
+/**
+ * Handle ATA IDENTIFY command completion
+ *
+ * @v atacmd ATA command
+ * @v rc Reason for completion
+ */
+static void atacmd_identify_done ( struct ata_command *atacmd, int rc ) {
+ struct ata_device *atadev = atacmd->atadev;
+ struct ata_identify_private *priv = atacmd_priv ( atacmd );
+ struct ata_identity *identity = &priv->identity;
+ struct block_device_capacity capacity;
+
+ /* Close if command failed */
+ if ( rc != 0 ) {
+ atacmd_close ( atacmd, rc );
+ return;
}
- /* Wait for command to complete */
- while ( command->rc == -EINPROGRESS )
- step();
- if ( ( rc = command->rc ) != 0 ) {
- /* Something went wrong with the command execution */
- DBG ( "ATA command failed: %s\n", strerror ( rc ) );
- return rc;
+ /* Extract capacity */
+ if ( identity->supports_lba48 & cpu_to_le16 ( ATA_SUPPORTS_LBA48 ) ) {
+ atadev->lba48 = 1;
+ capacity.blocks = le64_to_cpu ( identity->lba48_sectors );
+ } else {
+ capacity.blocks = le32_to_cpu ( identity->lba_sectors );
}
+ capacity.blksize = ATA_SECTOR_SIZE;
+ capacity.max_count = atadev->max_count;
+ DBGC ( atadev, "ATA %p is a %s\n", atadev, ata_model ( identity ) );
+ DBGC ( atadev, "ATA %p has %#llx blocks (%ld MB) and uses %s\n",
+ atadev, capacity.blocks,
+ ( ( signed long ) ( capacity.blocks >> 11 ) ),
+ ( atadev->lba48 ? "LBA48" : "LBA" ) );
- return 0;
+ /* Return capacity to caller */
+ block_capacity ( &atacmd->block, &capacity );
+
+ /* Close command */
+ atacmd_close ( atacmd, 0 );
}
+/** ATA IDENTITY command type */
+static struct ata_command_type atacmd_identify = {
+ .name = "IDENTIFY",
+ .priv_len = sizeof ( struct ata_identify_private ),
+ .cmd_lba = ATA_CMD_IDENTIFY,
+ .cmd_lba48 = ATA_CMD_IDENTIFY,
+ .data_in = atacmd_data_priv,
+ .data_out = atacmd_data_none,
+ .done = atacmd_identify_done,
+};
+
+/** ATA command block interface operations */
+static struct interface_operation atacmd_block_op[] = {
+ INTF_OP ( intf_close, struct ata_command *, atacmd_close ),
+};
+
+/** ATA command block interface descriptor */
+static struct interface_descriptor atacmd_block_desc =
+ INTF_DESC_PASSTHRU ( struct ata_command, block,
+ atacmd_block_op, ata );
+
+/** ATA command ATA interface operations */
+static struct interface_operation atacmd_ata_op[] = {
+ INTF_OP ( intf_close, struct ata_command *, atacmd_done ),
+};
+
+/** ATA command ATA interface descriptor */
+static struct interface_descriptor atacmd_ata_desc =
+ INTF_DESC_PASSTHRU ( struct ata_command, ata,
+ atacmd_ata_op, block );
+
/**
- * Read block from ATA device
+ * Create ATA command
*
- * @v blockdev Block device
- * @v block LBA block number
- * @v count Block count
+ * @v atadev ATA device
+ * @v block Block data interface
+ * @v type ATA command type
+ * @v lba Starting logical block address
+ * @v count Number of blocks to transfer
* @v buffer Data buffer
+ * @v len Length of data buffer
* @ret rc Return status code
*/
-static int ata_read ( struct block_device *blockdev, uint64_t block,
- unsigned long count, userptr_t buffer ) {
- struct ata_device *ata = block_to_ata ( blockdev );
- struct ata_command command;
+static int atadev_command ( struct ata_device *atadev,
+ struct interface *block,
+ struct ata_command_type *type,
+ uint64_t lba, unsigned int count,
+ userptr_t buffer, size_t len ) {
+ struct ata_command *atacmd;
+ struct ata_cmd command;
+ int tag;
+ int rc;
+
+ /* Allocate and initialise structure */
+ atacmd = zalloc ( sizeof ( *atacmd ) + type->priv_len );
+ if ( ! atacmd ) {
+ rc = -ENOMEM;
+ goto err_zalloc;
+ }
+ ref_init ( &atacmd->refcnt, atacmd_free );
+ intf_init ( &atacmd->block, &atacmd_block_desc, &atacmd->refcnt );
+ intf_init ( &atacmd->ata, &atacmd_ata_desc,
+ &atacmd->refcnt );
+ atacmd->atadev = atadev_get ( atadev );
+ list_add ( &atacmd->list, &ata_commands );
+ atacmd->type = type;
+ /* Sanity check */
+ if ( len != ( count * ATA_SECTOR_SIZE ) ) {
+ DBGC ( atadev, "ATA %p tag %08x buffer length mismatch (count "
+ "%d len %zd)\n", atadev, atacmd->tag, count, len );
+ rc = -EINVAL;
+ goto err_len;
+ }
+
+ /* Construct command */
memset ( &command, 0, sizeof ( command ) );
- command.cb.lba.native = block;
+ command.cb.lba.native = lba;
command.cb.count.native = count;
- command.cb.device = ( ata->device | ATA_DEV_OBSOLETE | ATA_DEV_LBA );
- command.cb.lba48 = ata->lba48;
- if ( ! ata->lba48 )
+ command.cb.device = ( atadev->device | ATA_DEV_OBSOLETE | ATA_DEV_LBA );
+ command.cb.lba48 = atadev->lba48;
+ if ( ! atadev->lba48 )
command.cb.device |= command.cb.lba.bytes.low_prev;
- command.cb.cmd_stat = ( ata->lba48 ? ATA_CMD_READ_EXT : ATA_CMD_READ );
- command.data_in = buffer;
- return ata_command ( ata, &command );
+ command.cb.cmd_stat =
+ ( atadev->lba48 ? type->cmd_lba48 : type->cmd_lba );
+ type->data_in ( atacmd, buffer, len,
+ &command.data_in, &command.data_in_len );
+ type->data_out ( atacmd, buffer, len,
+ &command.data_out, &command.data_out_len );
+
+ /* Issue command */
+ if ( ( tag = ata_command ( &atadev->ata, &atacmd->ata,
+ &command ) ) < 0 ) {
+ rc = tag;
+ DBGC ( atadev, "ATA %p tag %08x could not issue command: %s\n",
+ atadev, atacmd->tag, strerror ( rc ) );
+ goto err_command;
+ }
+ atacmd->tag = tag;
+
+ DBGC2 ( atadev, "ATA %p tag %08x %s cmd %02x dev %02x LBA%s %08llx "
+ "count %04x\n", atadev, atacmd->tag, atacmd->type->name,
+ command.cb.cmd_stat, command.cb.device,
+ ( command.cb.lba48 ? "48" : "" ),
+ ( unsigned long long ) command.cb.lba.native,
+ command.cb.count.native );
+
+ /* Attach to parent interface, mortalise self, and return */
+ intf_plug_plug ( &atacmd->block, block );
+ ref_put ( &atacmd->refcnt );
+ return 0;
+
+ err_command:
+ err_len:
+ atacmd_close ( atacmd, rc );
+ ref_put ( &atacmd->refcnt );
+ err_zalloc:
+ return rc;
}
/**
- * Write block to ATA device
+ * Issue ATA block read
*
- * @v blockdev Block device
- * @v block LBA block number
- * @v count Block count
+ * @v atadev ATA device
+ * @v block Block data interface
+ * @v lba Starting logical block address
+ * @v count Number of blocks to transfer
* @v buffer Data buffer
+ * @v len Length of data buffer
* @ret rc Return status code
+
*/
-static int ata_write ( struct block_device *blockdev, uint64_t block,
- unsigned long count, userptr_t buffer ) {
- struct ata_device *ata = block_to_ata ( blockdev );
- struct ata_command command;
-
- memset ( &command, 0, sizeof ( command ) );
- command.cb.lba.native = block;
- command.cb.count.native = count;
- command.cb.device = ( ata->device | ATA_DEV_OBSOLETE | ATA_DEV_LBA );
- command.cb.lba48 = ata->lba48;
- if ( ! ata->lba48 )
- command.cb.device |= command.cb.lba.bytes.low_prev;
- command.cb.cmd_stat = ( ata->lba48 ?
- ATA_CMD_WRITE_EXT : ATA_CMD_WRITE );
- command.data_out = buffer;
- return ata_command ( ata, &command );
+static int atadev_read ( struct ata_device *atadev,
+ struct interface *block,
+ uint64_t lba, unsigned int count,
+ userptr_t buffer, size_t len ) {
+ return atadev_command ( atadev, block, &atacmd_read,
+ lba, count, buffer, len );
}
/**
- * Identify ATA device
+ * Issue ATA block write
*
- * @v blockdev Block device
+ * @v atadev ATA device
+ * @v block Block data interface
+ * @v lba Starting logical block address
+ * @v count Number of blocks to transfer
+ * @v buffer Data buffer
+ * @v len Length of data buffer
* @ret rc Return status code
*/
-static int ata_identify ( struct block_device *blockdev ) {
- struct ata_device *ata = block_to_ata ( blockdev );
- struct ata_command command;
- struct ata_identity identity;
- int rc;
+static int atadev_write ( struct ata_device *atadev,
+ struct interface *block,
+ uint64_t lba, unsigned int count,
+ userptr_t buffer, size_t len ) {
+ return atadev_command ( atadev, block, &atacmd_write,
+ lba, count, buffer, len );
+}
- /* Issue IDENTIFY */
- memset ( &command, 0, sizeof ( command ) );
- command.cb.count.native = 1;
- command.cb.device = ( ata->device | ATA_DEV_OBSOLETE | ATA_DEV_LBA );
- command.cb.cmd_stat = ATA_CMD_IDENTIFY;
- command.data_in = virt_to_user ( &identity );
- linker_assert ( sizeof ( identity ) == ATA_SECTOR_SIZE,
- __ata_identity_bad_size__ );
- if ( ( rc = ata_command ( ata, &command ) ) != 0 )
- return rc;
-
- /* Fill in block device parameters */
- blockdev->blksize = ATA_SECTOR_SIZE;
- if ( identity.supports_lba48 & cpu_to_le16 ( ATA_SUPPORTS_LBA48 ) ) {
- ata->lba48 = 1;
- blockdev->blocks = le64_to_cpu ( identity.lba48_sectors );
- } else {
- blockdev->blocks = le32_to_cpu ( identity.lba_sectors );
+/**
+ * Read ATA device capacity
+ *
+ * @v atadev ATA device
+ * @v block Block data interface
+ * @ret rc Return status code
+ */
+static int atadev_read_capacity ( struct ata_device *atadev,
+ struct interface *block ) {
+ struct ata_identity *identity;
+
+ assert ( atacmd_identify.priv_len == sizeof ( *identity ) );
+ assert ( atacmd_identify.priv_len == ATA_SECTOR_SIZE );
+ return atadev_command ( atadev, block, &atacmd_identify,
+ 0, 1, UNULL, ATA_SECTOR_SIZE );
+}
+
+/**
+ * Close ATA device
+ *
+ * @v atadev ATA device
+ * @v rc Reason for close
+ */
+static void atadev_close ( struct ata_device *atadev, int rc ) {
+ struct ata_command *atacmd;
+ struct ata_command *tmp;
+
+ /* Shut down interfaces */
+ intf_shutdown ( &atadev->block, rc );
+ intf_shutdown ( &atadev->ata, rc );
+
+ /* Shut down any remaining commands */
+ list_for_each_entry_safe ( atacmd, tmp, &ata_commands, list ) {
+ if ( atacmd->atadev != atadev )
+ continue;
+ atacmd_get ( atacmd );
+ atacmd_close ( atacmd, rc );
+ atacmd_put ( atacmd );
}
- return 0;
}
-static struct block_device_operations ata_operations = {
- .read = ata_read,
- .write = ata_write
+/** ATA device block interface operations */
+static struct interface_operation atadev_block_op[] = {
+ INTF_OP ( block_read, struct ata_device *, atadev_read ),
+ INTF_OP ( block_write, struct ata_device *, atadev_write ),
+ INTF_OP ( block_read_capacity, struct ata_device *,
+ atadev_read_capacity ),
+ INTF_OP ( intf_close, struct ata_device *, atadev_close ),
};
+/** ATA device block interface descriptor */
+static struct interface_descriptor atadev_block_desc =
+ INTF_DESC_PASSTHRU ( struct ata_device, block,
+ atadev_block_op, ata );
+
+/** ATA device ATA interface operations */
+static struct interface_operation atadev_ata_op[] = {
+ INTF_OP ( intf_close, struct ata_device *, atadev_close ),
+};
+
+/** ATA device ATA interface descriptor */
+static struct interface_descriptor atadev_ata_desc =
+ INTF_DESC_PASSTHRU ( struct ata_device, ata,
+ atadev_ata_op, block );
+
/**
- * Initialise ATA device
+ * Open ATA device
*
- * @v ata ATA device
+ * @v block Block control interface
+ * @v ata ATA control interface
+ * @v device ATA device number
+ * @v max_count Maximum number of blocks per single transfer
* @ret rc Return status code
- *
- * Initialises an ATA device. The ata_device::command field and the
- * @c ATA_FL_SLAVE portion of the ata_device::flags field must already
- * be filled in. This function will configure ata_device::blockdev,
- * including issuing an IDENTIFY DEVICE call to determine the block
- * size and total device size.
*/
-int init_atadev ( struct ata_device *ata ) {
- /** Fill in read and write methods, and get device capacity */
- ata->blockdev.op = &ata_operations;
- return ata_identify ( &ata->blockdev );
+int ata_open ( struct interface *block, struct interface *ata,
+ unsigned int device, unsigned int max_count ) {
+ struct ata_device *atadev;
+
+ /* Allocate and initialise structure */
+ atadev = zalloc ( sizeof ( *atadev ) );
+ if ( ! atadev )
+ return -ENOMEM;
+ ref_init ( &atadev->refcnt, NULL );
+ intf_init ( &atadev->block, &atadev_block_desc, &atadev->refcnt );
+ intf_init ( &atadev->ata, &atadev_ata_desc, &atadev->refcnt );
+ atadev->device = device;
+ atadev->max_count = max_count;
+
+ /* Attach to ATA and parent and interfaces, mortalise self,
+ * and return
+ */
+ intf_plug_plug ( &atadev->ata, ata );
+ intf_plug_plug ( &atadev->block, block );
+ ref_put ( &atadev->refcnt );
+ return 0;
}
diff --git a/src/drivers/block/ibft.c b/src/drivers/block/ibft.c
new file mode 100644
index 000000000..adf1d7d59
--- /dev/null
+++ b/src/drivers/block/ibft.c
@@ -0,0 +1,471 @@
+/*
+ * Copyright Fen Systems Ltd. 2007. Portions of this code are derived
+ * from IBM Corporation Sample Programs. Copyright IBM Corporation
+ * 2004, 2007. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+FILE_LICENCE ( BSD2 );
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <ipxe/pci.h>
+#include <ipxe/acpi.h>
+#include <ipxe/in.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/ethernet.h>
+#include <ipxe/dhcp.h>
+#include <ipxe/iscsi.h>
+#include <ipxe/ibft.h>
+
+/** @file
+ *
+ * iSCSI boot firmware table
+ *
+ * The information in this file is derived from the document "iSCSI
+ * Boot Firmware Table (iBFT)" as published by IBM at
+ *
+ * ftp://ftp.software.ibm.com/systems/support/system_x_pdf/ibm_iscsi_boot_firmware_table_v1.02.pdf
+ *
+ */
+
+/**
+ * An iBFT created by iPXE
+ *
+ */
+struct ipxe_ibft {
+ /** The fixed section */
+ struct ibft_table table;
+ /** The Initiator section */
+ struct ibft_initiator initiator __attribute__ (( aligned ( 16 ) ));
+ /** The NIC section */
+ struct ibft_nic nic __attribute__ (( aligned ( 16 ) ));
+ /** The Target section */
+ struct ibft_target target __attribute__ (( aligned ( 16 ) ));
+ /** Strings block */
+ char strings[0];
+} __attribute__ (( packed, aligned ( 16 ) ));
+
+/**
+ * iSCSI string block descriptor
+ *
+ * This is an internal structure that we use to keep track of the
+ * allocation of string data.
+ */
+struct ibft_strings {
+ /** The iBFT containing these strings */
+ struct ibft_table *table;
+ /** Offset of first free byte within iBFT */
+ size_t offset;
+ /** Total length of the iBFT */
+ size_t len;
+};
+
+/**
+ * Fill in an IP address field within iBFT
+ *
+ * @v ipaddr IP address field
+ * @v in IPv4 address
+ */
+static void ibft_set_ipaddr ( struct ibft_ipaddr *ipaddr, struct in_addr in ) {
+ memset ( ipaddr, 0, sizeof ( ipaddr ) );
+ if ( in.s_addr ) {
+ ipaddr->in = in;
+ ipaddr->ones = 0xffff;
+ }
+}
+
+/**
+ * Fill in an IP address within iBFT from configuration setting
+ *
+ * @v ipaddr IP address field
+ * @v setting Configuration setting
+ * @v tag DHCP option tag
+ */
+static void ibft_set_ipaddr_setting ( struct ibft_ipaddr *ipaddr,
+ struct setting *setting ) {
+ struct in_addr in;
+ fetch_ipv4_setting ( NULL, setting, &in );
+ ibft_set_ipaddr ( ipaddr, in );
+}
+
+/**
+ * Read IP address from iBFT (for debugging)
+ *
+ * @v strings iBFT string block descriptor
+ * @v string String field
+ * @ret ipaddr IP address string
+ */
+static const char * ibft_ipaddr ( struct ibft_ipaddr *ipaddr ) {
+ return inet_ntoa ( ipaddr->in );
+}
+
+/**
+ * Allocate a string within iBFT
+ *
+ * @v strings iBFT string block descriptor
+ * @v string String field to fill in
+ * @v len Length of string to allocate (excluding NUL)
+ * @ret dest String destination, or NULL
+ */
+static char * ibft_alloc_string ( struct ibft_strings *strings,
+ struct ibft_string *string, size_t len ) {
+
+ if ( ( strings->offset + len ) >= strings->len )
+ return NULL;
+
+ string->offset = cpu_to_le16 ( strings->offset );
+ string->len = cpu_to_le16 ( len );
+ strings->offset += ( len + 1 );
+
+ return ( ( ( char * ) strings->table ) + string->offset );
+}
+
+/**
+ * Fill in a string field within iBFT
+ *
+ * @v strings iBFT string block descriptor
+ * @v string String field
+ * @v data String to fill in, or NULL
+ * @ret rc Return status code
+ */
+static int ibft_set_string ( struct ibft_strings *strings,
+ struct ibft_string *string, const char *data ) {
+ char *dest;
+
+ if ( ! data )
+ return 0;
+
+ dest = ibft_alloc_string ( strings, string, strlen ( data ) );
+ if ( ! dest )
+ return -ENOBUFS;
+ strcpy ( dest, data );
+
+ return 0;
+}
+
+/**
+ * Fill in a string field within iBFT from configuration setting
+ *
+ * @v strings iBFT string block descriptor
+ * @v string String field
+ * @v setting Configuration setting
+ * @ret rc Return status code
+ */
+static int ibft_set_string_setting ( struct ibft_strings *strings,
+ struct ibft_string *string,
+ struct setting *setting ) {
+ int len;
+ char *dest;
+
+ len = fetch_setting_len ( NULL, setting );
+ if ( len < 0 ) {
+ string->offset = 0;
+ string->len = 0;
+ return 0;
+ }
+
+ dest = ibft_alloc_string ( strings, string, len );
+ if ( ! dest )
+ return -ENOBUFS;
+ fetch_string_setting ( NULL, setting, dest, ( len + 1 ) );
+
+ return 0;
+}
+
+/**
+ * Read string from iBFT (for debugging)
+ *
+ * @v strings iBFT string block descriptor
+ * @v string String field
+ * @ret data String content (or "<empty>")
+ */
+static const char * ibft_string ( struct ibft_strings *strings,
+ struct ibft_string *string ) {
+ return ( string->offset ?
+ ( ( ( char * ) strings->table ) + string->offset ) : NULL );
+}
+
+/**
+ * Fill in NIC portion of iBFT
+ *
+ * @v nic NIC portion of iBFT
+ * @v strings iBFT string block descriptor
+ * @v netdev Network device
+ * @ret rc Return status code
+ */
+static int ibft_fill_nic ( struct ibft_nic *nic,
+ struct ibft_strings *strings,
+ struct net_device *netdev ) {
+ struct ll_protocol *ll_protocol = netdev->ll_protocol;
+ struct in_addr netmask_addr = { 0 };
+ unsigned int netmask_count = 0;
+ int rc;
+
+ /* Fill in common header */
+ nic->header.structure_id = IBFT_STRUCTURE_ID_NIC;
+ nic->header.version = 1;
+ nic->header.length = cpu_to_le16 ( sizeof ( *nic ) );
+ nic->header.flags = ( IBFT_FL_NIC_BLOCK_VALID |
+ IBFT_FL_NIC_FIRMWARE_BOOT_SELECTED );
+
+ /* Extract values from configuration settings */
+ ibft_set_ipaddr_setting ( &nic->ip_address, &ip_setting );
+ DBG ( "iBFT NIC IP = %s\n", ibft_ipaddr ( &nic->ip_address ) );
+ ibft_set_ipaddr_setting ( &nic->gateway, &gateway_setting );
+ DBG ( "iBFT NIC gateway = %s\n", ibft_ipaddr ( &nic->gateway ) );
+ ibft_set_ipaddr_setting ( &nic->dns[0], &dns_setting );
+ DBG ( "iBFT NIC DNS = %s\n", ibft_ipaddr ( &nic->dns[0] ) );
+ if ( ( rc = ibft_set_string_setting ( strings, &nic->hostname,
+ &hostname_setting ) ) != 0 )
+ return rc;
+ DBG ( "iBFT NIC hostname = %s\n",
+ ibft_string ( strings, &nic->hostname ) );
+
+ /* Derive subnet mask prefix from subnet mask */
+ fetch_ipv4_setting ( NULL, &netmask_setting, &netmask_addr );
+ while ( netmask_addr.s_addr ) {
+ if ( netmask_addr.s_addr & 0x1 )
+ netmask_count++;
+ netmask_addr.s_addr >>= 1;
+ }
+ nic->subnet_mask_prefix = netmask_count;
+ DBG ( "iBFT NIC subnet = /%d\n", nic->subnet_mask_prefix );
+
+ /* Extract values from net-device configuration */
+ if ( ( rc = ll_protocol->eth_addr ( netdev->ll_addr,
+ nic->mac_address ) ) != 0 ) {
+ DBG ( "Could not determine iBFT MAC: %s\n", strerror ( rc ) );
+ return rc;
+ }
+ DBG ( "iBFT NIC MAC = %s\n", eth_ntoa ( nic->mac_address ) );
+ nic->pci_bus_dev_func = cpu_to_le16 ( netdev->dev->desc.location );
+ DBG ( "iBFT NIC PCI = %04x\n", le16_to_cpu ( nic->pci_bus_dev_func ) );
+
+ return 0;
+}
+
+/**
+ * Fill in Initiator portion of iBFT
+ *
+ * @v initiator Initiator portion of iBFT
+ * @v strings iBFT string block descriptor
+ * @ret rc Return status code
+ */
+static int ibft_fill_initiator ( struct ibft_initiator *initiator,
+ struct ibft_strings *strings ) {
+ const char *initiator_iqn = iscsi_initiator_iqn();
+ int rc;
+
+ /* Fill in common header */
+ initiator->header.structure_id = IBFT_STRUCTURE_ID_INITIATOR;
+ initiator->header.version = 1;
+ initiator->header.length = cpu_to_le16 ( sizeof ( *initiator ) );
+ initiator->header.flags = ( IBFT_FL_INITIATOR_BLOCK_VALID |
+ IBFT_FL_INITIATOR_FIRMWARE_BOOT_SELECTED );
+
+ /* Fill in hostname */
+ if ( ( rc = ibft_set_string ( strings, &initiator->initiator_name,
+ initiator_iqn ) ) != 0 )
+ return rc;
+ DBG ( "iBFT initiator hostname = %s\n",
+ ibft_string ( strings, &initiator->initiator_name ) );
+
+ return 0;
+}
+
+/**
+ * Fill in Target CHAP portion of iBFT
+ *
+ * @v target Target portion of iBFT
+ * @v strings iBFT string block descriptor
+ * @v iscsi iSCSI session
+ * @ret rc Return status code
+ */
+static int ibft_fill_target_chap ( struct ibft_target *target,
+ struct ibft_strings *strings,
+ struct iscsi_session *iscsi ) {
+ int rc;
+
+ if ( ! ( iscsi->status & ISCSI_STATUS_AUTH_FORWARD_REQUIRED ) )
+ return 0;
+
+ assert ( iscsi->initiator_username );
+ assert ( iscsi->initiator_password );
+
+ target->chap_type = IBFT_CHAP_ONE_WAY;
+ if ( ( rc = ibft_set_string ( strings, &target->chap_name,
+ iscsi->initiator_username ) ) != 0 )
+ return rc;
+ DBG ( "iBFT target username = %s\n",
+ ibft_string ( strings, &target->chap_name ) );
+ if ( ( rc = ibft_set_string ( strings, &target->chap_secret,
+ iscsi->initiator_password ) ) != 0 )
+ return rc;
+ DBG ( "iBFT target password = <redacted>\n" );
+
+ return 0;
+}
+
+/**
+ * Fill in Target Reverse CHAP portion of iBFT
+ *
+ * @v target Target portion of iBFT
+ * @v strings iBFT string block descriptor
+ * @v iscsi iSCSI session
+ * @ret rc Return status code
+ */
+static int ibft_fill_target_reverse_chap ( struct ibft_target *target,
+ struct ibft_strings *strings,
+ struct iscsi_session *iscsi ) {
+ int rc;
+
+ if ( ! ( iscsi->status & ISCSI_STATUS_AUTH_REVERSE_REQUIRED ) )
+ return 0;
+
+ assert ( iscsi->initiator_username );
+ assert ( iscsi->initiator_password );
+ assert ( iscsi->target_username );
+ assert ( iscsi->target_password );
+
+ target->chap_type = IBFT_CHAP_MUTUAL;
+ if ( ( rc = ibft_set_string ( strings, &target->reverse_chap_name,
+ iscsi->target_username ) ) != 0 )
+ return rc;
+ DBG ( "iBFT target reverse username = %s\n",
+ ibft_string ( strings, &target->chap_name ) );
+ if ( ( rc = ibft_set_string ( strings, &target->reverse_chap_secret,
+ iscsi->target_password ) ) != 0 )
+ return rc;
+ DBG ( "iBFT target reverse password = <redacted>\n" );
+
+ return 0;
+}
+
+/**
+ * Fill in Target portion of iBFT
+ *
+ * @v target Target portion of iBFT
+ * @v strings iBFT string block descriptor
+ * @v iscsi iSCSI session
+ * @ret rc Return status code
+ */
+static int ibft_fill_target ( struct ibft_target *target,
+ struct ibft_strings *strings,
+ struct iscsi_session *iscsi ) {
+ struct sockaddr_in *sin_target =
+ ( struct sockaddr_in * ) &iscsi->target_sockaddr;
+ int rc;
+
+ /* Fill in common header */
+ target->header.structure_id = IBFT_STRUCTURE_ID_TARGET;
+ target->header.version = 1;
+ target->header.length = cpu_to_le16 ( sizeof ( *target ) );
+ target->header.flags = ( IBFT_FL_TARGET_BLOCK_VALID |
+ IBFT_FL_TARGET_FIRMWARE_BOOT_SELECTED );
+
+ /* Fill in Target values */
+ ibft_set_ipaddr ( &target->ip_address, sin_target->sin_addr );
+ DBG ( "iBFT target IP = %s\n", ibft_ipaddr ( &target->ip_address ) );
+ target->socket = cpu_to_le16 ( ntohs ( sin_target->sin_port ) );
+ DBG ( "iBFT target port = %d\n", target->socket );
+ memcpy ( &target->boot_lun, &iscsi->lun, sizeof ( target->boot_lun ) );
+ DBG ( "iBFT target boot LUN = " SCSI_LUN_FORMAT "\n",
+ SCSI_LUN_DATA ( target->boot_lun ) );
+ if ( ( rc = ibft_set_string ( strings, &target->target_name,
+ iscsi->target_iqn ) ) != 0 )
+ return rc;
+ DBG ( "iBFT target name = %s\n",
+ ibft_string ( strings, &target->target_name ) );
+ if ( ( rc = ibft_fill_target_chap ( target, strings, iscsi ) ) != 0 )
+ return rc;
+ if ( ( rc = ibft_fill_target_reverse_chap ( target, strings,
+ iscsi ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
+ * Fill in iBFT
+ *
+ * @v iscsi iSCSI session
+ * @v acpi ACPI table
+ * @v len Length of ACPI table
+ * @ret rc Return status code
+ */
+int ibft_describe ( struct iscsi_session *iscsi,
+ struct acpi_description_header *acpi,
+ size_t len ) {
+ struct ipxe_ibft *ibft =
+ container_of ( acpi, struct ipxe_ibft, table.acpi );
+ struct ibft_strings strings = {
+ .table = &ibft->table,
+ .offset = offsetof ( typeof ( *ibft ), strings ),
+ .len = len,
+ };
+ struct net_device *netdev;
+ int rc;
+
+ /* Ugly hack. Now that we have a generic interface mechanism
+ * that can support ioctls, we can potentially eliminate this.
+ */
+ netdev = last_opened_netdev();
+ if ( ! netdev ) {
+ DBGC ( iscsi, "iSCSI %p cannot guess network device\n",
+ iscsi );
+ return -ENODEV;
+ }
+
+ /* Fill in ACPI header */
+ ibft->table.acpi.signature = cpu_to_le32 ( IBFT_SIG );
+ ibft->table.acpi.length = cpu_to_le32 ( len );
+ ibft->table.acpi.revision = 1;
+
+ /* Fill in Control block */
+ ibft->table.control.header.structure_id = IBFT_STRUCTURE_ID_CONTROL;
+ ibft->table.control.header.version = 1;
+ ibft->table.control.header.length =
+ cpu_to_le16 ( sizeof ( ibft->table.control ) );
+ ibft->table.control.initiator =
+ cpu_to_le16 ( offsetof ( typeof ( *ibft ), initiator ) );
+ ibft->table.control.nic_0 =
+ cpu_to_le16 ( offsetof ( typeof ( *ibft ), nic ) );
+ ibft->table.control.target_0 =
+ cpu_to_le16 ( offsetof ( typeof ( *ibft ), target ) );
+
+ /* Fill in NIC, Initiator and Target blocks */
+ if ( ( rc = ibft_fill_nic ( &ibft->nic, &strings, netdev ) ) != 0 )
+ return rc;
+ if ( ( rc = ibft_fill_initiator ( &ibft->initiator,
+ &strings ) ) != 0 )
+ return rc;
+ if ( ( rc = ibft_fill_target ( &ibft->target, &strings,
+ iscsi ) ) != 0 )
+ return rc;
+
+ return 0;
+}
diff --git a/src/drivers/block/ramdisk.c b/src/drivers/block/ramdisk.c
deleted file mode 100644
index 55c0d2d52..000000000
--- a/src/drivers/block/ramdisk.c
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2007 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.
- */
-
-FILE_LICENCE ( GPL2_OR_LATER );
-
-#include <ipxe/blockdev.h>
-#include <ipxe/ramdisk.h>
-
-/**
- * @file
- *
- * RAM disks
- *
- */
-
-static inline __attribute__ (( always_inline )) struct ramdisk *
-block_to_ramdisk ( struct block_device *blockdev ) {
- return container_of ( blockdev, struct ramdisk, blockdev );
-}
-
-/**
- * Read block
- *
- * @v blockdev Block device
- * @v block Block number
- * @v count Block count
- * @v buffer Data buffer
- * @ret rc Return status code
- */
-static int ramdisk_read ( struct block_device *blockdev, uint64_t block,
- unsigned long count, userptr_t buffer ) {
- struct ramdisk *ramdisk = block_to_ramdisk ( blockdev );
- unsigned long offset = ( block * blockdev->blksize );
- unsigned long length = ( count * blockdev->blksize );
-
- DBGC ( ramdisk, "RAMDISK %p reading [%lx,%lx)\n",
- ramdisk, offset, length );
-
- memcpy_user ( buffer, 0, ramdisk->data, offset, length );
- return 0;
-}
-
-/**
- * Write block
- *
- * @v blockdev Block device
- * @v block Block number
- * @v count Block count
- * @v buffer Data buffer
- * @ret rc Return status code
- */
-static int ramdisk_write ( struct block_device *blockdev, uint64_t block,
- unsigned long count, userptr_t buffer ) {
- struct ramdisk *ramdisk = block_to_ramdisk ( blockdev );
- unsigned long offset = ( block * blockdev->blksize );
- unsigned long length = ( count * blockdev->blksize );
-
- DBGC ( ramdisk, "RAMDISK %p writing [%lx,%lx)\n",
- ramdisk, offset, length );
-
- memcpy_user ( ramdisk->data, offset, buffer, 0, length );
- return 0;
-}
-
-static struct block_device_operations ramdisk_operations = {
- .read = ramdisk_read,
- .write = ramdisk_write
-};
-
-int init_ramdisk ( struct ramdisk *ramdisk, userptr_t data, size_t len,
- unsigned int blksize ) {
-
- if ( ! blksize )
- blksize = 512;
-
- ramdisk->data = data;
- ramdisk->blockdev.op = &ramdisk_operations;
- ramdisk->blockdev.blksize = blksize;
- ramdisk->blockdev.blocks = ( len / blksize );
-
- return 0;
-}
diff --git a/src/drivers/block/scsi.c b/src/drivers/block/scsi.c
index 94e283b63..d7f7b4dbc 100644
--- a/src/drivers/block/scsi.c
+++ b/src/drivers/block/scsi.c
@@ -23,8 +23,8 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <string.h>
#include <byteswap.h>
#include <errno.h>
+#include <ipxe/list.h>
#include <ipxe/blockdev.h>
-#include <ipxe/process.h>
#include <ipxe/scsi.h>
/** @file
@@ -33,334 +33,738 @@ FILE_LICENCE ( GPL2_OR_LATER );
*
*/
-/** Maximum number of dummy "read capacity (10)" operations
+/** Maximum number of command retries */
+#define SCSICMD_MAX_RETRIES 10
+
+/******************************************************************************
+ *
+ * Utility functions
*
- * These are issued at connection setup to draw out various useless
- * power-on messages.
+ ******************************************************************************
*/
-#define SCSI_MAX_DUMMY_READ_CAP 10
-
-static inline __attribute__ (( always_inline )) struct scsi_device *
-block_to_scsi ( struct block_device *blockdev ) {
- return container_of ( blockdev, struct scsi_device, blockdev );
-}
/**
- * Handle SCSI command with no backing device
+ * Parse SCSI LUN
*
- * @v scsi SCSI device
- * @v command SCSI command
+ * @v lun_string LUN string representation
+ * @v lun LUN to fill in
* @ret rc Return status code
*/
-int scsi_detached_command ( struct scsi_device *scsi __unused,
- struct scsi_command *command __unused ) {
- return -ENODEV;
+int scsi_parse_lun ( const char *lun_string, struct scsi_lun *lun ) {
+ char *p;
+ int i;
+
+ memset ( lun, 0, sizeof ( *lun ) );
+ if ( lun_string ) {
+ p = ( char * ) lun_string;
+ for ( i = 0 ; i < 4 ; i++ ) {
+ lun->u16[i] = htons ( strtoul ( p, &p, 16 ) );
+ if ( *p == '\0' )
+ break;
+ if ( *p != '-' )
+ return -EINVAL;
+ p++;
+ }
+ if ( *p )
+ return -EINVAL;
+ }
+
+ return 0;
}
+/******************************************************************************
+ *
+ * Interface methods
+ *
+ ******************************************************************************
+ */
+
/**
* Issue SCSI command
*
- * @v scsi SCSI device
+ * @v control SCSI control interface
+ * @v data SCSI data interface
* @v command SCSI command
- * @ret rc Return status code
+ * @ret tag Command tag, or negative error
*/
-static int scsi_command ( struct scsi_device *scsi,
- struct scsi_command *command ) {
- int rc;
+int scsi_command ( struct interface *control, struct interface *data,
+ struct scsi_cmd *command ) {
+ struct interface *dest;
+ scsi_command_TYPE ( void * ) *op =
+ intf_get_dest_op ( control, scsi_command, &dest );
+ void *object = intf_object ( dest );
+ int tap;
+
+ if ( op ) {
+ tap = op ( object, data, command );
+ } else {
+ /* Default is to fail to issue the command */
+ tap = -EOPNOTSUPP;
+ }
- DBGC2 ( scsi, "SCSI %p " SCSI_CDB_FORMAT "\n",
- scsi, SCSI_CDB_DATA ( command->cdb ) );
+ intf_put ( dest );
+ return tap;
+}
- /* Clear sense response code before issuing command */
- command->sense_response = 0;
+/**
+ * Report SCSI response
+ *
+ * @v interface SCSI command interface
+ * @v response SCSI response
+ */
+void scsi_response ( struct interface *intf, struct scsi_rsp *response ) {
+ struct interface *dest;
+ scsi_response_TYPE ( void * ) *op =
+ intf_get_dest_op ( intf, scsi_response, &dest );
+ void *object = intf_object ( dest );
+
+ if ( op ) {
+ op ( object, response );
+ } else {
+ /* Default is to ignore the response */
+ }
- /* Flag command as in-progress */
- command->rc = -EINPROGRESS;
+ intf_put ( dest );
+}
- /* Issue SCSI command */
- if ( ( rc = scsi->command ( scsi, command ) ) != 0 ) {
- /* Something went wrong with the issuing mechanism */
- DBGC ( scsi, "SCSI %p " SCSI_CDB_FORMAT " err %s\n",
- scsi, SCSI_CDB_DATA ( command->cdb ), strerror ( rc ) );
- return rc;
- }
+/******************************************************************************
+ *
+ * SCSI devices and commands
+ *
+ ******************************************************************************
+ */
- /* Wait for command to complete */
- while ( command->rc == -EINPROGRESS )
- step();
- if ( ( rc = command->rc ) != 0 ) {
- /* Something went wrong with the command execution */
- DBGC ( scsi, "SCSI %p " SCSI_CDB_FORMAT " err %s\n",
- scsi, SCSI_CDB_DATA ( command->cdb ), strerror ( rc ) );
- return rc;
- }
+/** A SCSI device */
+struct scsi_device {
+ /** Reference count */
+ struct refcnt refcnt;
+ /** Block control interface */
+ struct interface block;
+ /** SCSI control interface */
+ struct interface scsi;
- /* Check for SCSI errors */
- if ( command->status != 0 ) {
- DBGC ( scsi, "SCSI %p " SCSI_CDB_FORMAT " status %02x sense "
- "%02x\n", scsi, SCSI_CDB_DATA ( command->cdb ),
- command->status, command->sense_response );
- return -EIO;
- }
+ /** SCSI LUN */
+ struct scsi_lun lun;
- return 0;
+ /** List of commands */
+ struct list_head cmds;
+};
+
+/** A SCSI command */
+struct scsi_command {
+ /** Reference count */
+ struct refcnt refcnt;
+ /** SCSI device */
+ struct scsi_device *scsidev;
+ /** List of SCSI commands */
+ struct list_head list;
+
+ /** Block data interface */
+ struct interface block;
+ /** SCSI data interface */
+ struct interface scsi;
+
+ /** Command type */
+ struct scsi_command_type *type;
+ /** Starting logical block address */
+ uint64_t lba;
+ /** Number of blocks */
+ unsigned int count;
+ /** Data buffer */
+ userptr_t buffer;
+ /** Length of data buffer */
+ size_t len;
+ /** Command tag */
+ uint32_t tag;
+
+ /** Retry count */
+ unsigned int retries;
+
+ /** Private data */
+ uint8_t priv[0];
+};
+
+/** A SCSI command type */
+struct scsi_command_type {
+ /** Name */
+ const char *name;
+ /** Additional working space */
+ size_t priv_len;
+ /**
+ * Construct SCSI command IU
+ *
+ * @v scsicmd SCSI command
+ * @v command SCSI command IU
+ */
+ void ( * cmd ) ( struct scsi_command *scsicmd,
+ struct scsi_cmd *command );
+ /**
+ * Handle SCSI command completion
+ *
+ * @v scsicmd SCSI command
+ * @v rc Reason for completion
+ */
+ void ( * done ) ( struct scsi_command *scsicmd, int rc );
+};
+
+/**
+ * Get reference to SCSI device
+ *
+ * @v scsidev SCSI device
+ * @ret scsidev SCSI device
+ */
+static inline __attribute__ (( always_inline )) struct scsi_device *
+scsidev_get ( struct scsi_device *scsidev ) {
+ ref_get ( &scsidev->refcnt );
+ return scsidev;
}
/**
- * Read block from SCSI device using READ (10)
+ * Drop reference to SCSI device
*
- * @v blockdev Block device
- * @v block LBA block number
- * @v count Block count
- * @v buffer Data buffer
- * @ret rc Return status code
+ * @v scsidev SCSI device
*/
-static int scsi_read_10 ( struct block_device *blockdev, uint64_t block,
- unsigned long count, userptr_t buffer ) {
- struct scsi_device *scsi = block_to_scsi ( blockdev );
- struct scsi_command command;
- struct scsi_cdb_read_10 *cdb = &command.cdb.read10;
+static inline __attribute__ (( always_inline )) void
+scsidev_put ( struct scsi_device *scsidev ) {
+ ref_put ( &scsidev->refcnt );
+}
- /* Issue READ (10) */
- memset ( &command, 0, sizeof ( command ) );
- cdb->opcode = SCSI_OPCODE_READ_10;
- cdb->lba = cpu_to_be32 ( block );
- cdb->len = cpu_to_be16 ( count );
- command.data_in = buffer;
- command.data_in_len = ( count * blockdev->blksize );
- return scsi_command ( scsi, &command );
+/**
+ * Get reference to SCSI command
+ *
+ * @v scsicmd SCSI command
+ * @ret scsicmd SCSI command
+ */
+static inline __attribute__ (( always_inline )) struct scsi_command *
+scsicmd_get ( struct scsi_command *scsicmd ) {
+ ref_get ( &scsicmd->refcnt );
+ return scsicmd;
}
/**
- * Read block from SCSI device using READ (16)
+ * Drop reference to SCSI command
*
- * @v blockdev Block device
- * @v block LBA block number
- * @v count Block count
- * @v buffer Data buffer
- * @ret rc Return status code
+ * @v scsicmd SCSI command
*/
-static int scsi_read_16 ( struct block_device *blockdev, uint64_t block,
- unsigned long count, userptr_t buffer ) {
- struct scsi_device *scsi = block_to_scsi ( blockdev );
- struct scsi_command command;
- struct scsi_cdb_read_16 *cdb = &command.cdb.read16;
+static inline __attribute__ (( always_inline )) void
+scsicmd_put ( struct scsi_command *scsicmd ) {
+ ref_put ( &scsicmd->refcnt );
+}
- /* Issue READ (16) */
- memset ( &command, 0, sizeof ( command ) );
- cdb->opcode = SCSI_OPCODE_READ_16;
- cdb->lba = cpu_to_be64 ( block );
- cdb->len = cpu_to_be32 ( count );
- command.data_in = buffer;
- command.data_in_len = ( count * blockdev->blksize );
- return scsi_command ( scsi, &command );
+/**
+ * Get SCSI command private data
+ *
+ * @v scsicmd SCSI command
+ * @ret priv Private data
+ */
+static inline __attribute__ (( always_inline )) void *
+scsicmd_priv ( struct scsi_command *scsicmd ) {
+ return scsicmd->priv;
}
/**
- * Write block to SCSI device using WRITE (10)
+ * Free SCSI command
*
- * @v blockdev Block device
- * @v block LBA block number
- * @v count Block count
- * @v buffer Data buffer
- * @ret rc Return status code
+ * @v refcnt Reference count
*/
-static int scsi_write_10 ( struct block_device *blockdev, uint64_t block,
- unsigned long count, userptr_t buffer ) {
- struct scsi_device *scsi = block_to_scsi ( blockdev );
- struct scsi_command command;
- struct scsi_cdb_write_10 *cdb = &command.cdb.write10;
+static void scsicmd_free ( struct refcnt *refcnt ) {
+ struct scsi_command *scsicmd =
+ container_of ( refcnt, struct scsi_command, refcnt );
- /* Issue WRITE (10) */
- memset ( &command, 0, sizeof ( command ) );
- cdb->opcode = SCSI_OPCODE_WRITE_10;
- cdb->lba = cpu_to_be32 ( block );
- cdb->len = cpu_to_be16 ( count );
- command.data_out = buffer;
- command.data_out_len = ( count * blockdev->blksize );
- return scsi_command ( scsi, &command );
+ /* Remove from list of commands */
+ list_del ( &scsicmd->list );
+ scsidev_put ( scsicmd->scsidev );
+
+ /* Free command */
+ free ( scsicmd );
}
/**
- * Write block to SCSI device using WRITE (16)
+ * Close SCSI command
*
- * @v blockdev Block device
- * @v block LBA block number
- * @v count Block count
- * @v buffer Data buffer
- * @ret rc Return status code
+ * @v scsicmd SCSI command
+ * @v rc Reason for close
*/
-static int scsi_write_16 ( struct block_device *blockdev, uint64_t block,
- unsigned long count, userptr_t buffer ) {
- struct scsi_device *scsi = block_to_scsi ( blockdev );
- struct scsi_command command;
- struct scsi_cdb_write_16 *cdb = &command.cdb.write16;
+static void scsicmd_close ( struct scsi_command *scsicmd, int rc ) {
+ struct scsi_device *scsidev = scsicmd->scsidev;
- /* Issue WRITE (16) */
- memset ( &command, 0, sizeof ( command ) );
- cdb->opcode = SCSI_OPCODE_WRITE_16;
- cdb->lba = cpu_to_be64 ( block );
- cdb->len = cpu_to_be32 ( count );
- command.data_out = buffer;
- command.data_out_len = ( count * blockdev->blksize );
- return scsi_command ( scsi, &command );
+ if ( rc != 0 ) {
+ DBGC ( scsidev, "SCSI %p tag %08x closed: %s\n",
+ scsidev, scsicmd->tag, strerror ( rc ) );
+ }
+
+ /* Shut down interfaces */
+ intf_shutdown ( &scsicmd->scsi, rc );
+ intf_shutdown ( &scsicmd->block, rc );
}
/**
- * Read capacity of SCSI device via READ CAPACITY (10)
+ * Construct and issue SCSI command
*
- * @v blockdev Block device
* @ret rc Return status code
*/
-static int scsi_read_capacity_10 ( struct block_device *blockdev ) {
- struct scsi_device *scsi = block_to_scsi ( blockdev );
- struct scsi_command command;
- struct scsi_cdb_read_capacity_10 *cdb = &command.cdb.readcap10;
- struct scsi_capacity_10 capacity;
+static int scsicmd_command ( struct scsi_command *scsicmd ) {
+ struct scsi_device *scsidev = scsicmd->scsidev;
+ struct scsi_cmd command;
+ int tag;
int rc;
- /* Issue READ CAPACITY (10) */
+ /* Construct command */
memset ( &command, 0, sizeof ( command ) );
- cdb->opcode = SCSI_OPCODE_READ_CAPACITY_10;
- command.data_in = virt_to_user ( &capacity );
- command.data_in_len = sizeof ( capacity );
-
- if ( ( rc = scsi_command ( scsi, &command ) ) != 0 )
+ memcpy ( &command.lun, &scsidev->lun, sizeof ( command.lun ) );
+ scsicmd->type->cmd ( scsicmd, &command );
+
+ /* Issue command */
+ if ( ( tag = scsi_command ( &scsidev->scsi, &scsicmd->scsi,
+ &command ) ) < 0 ) {
+ rc = tag;
+ DBGC ( scsidev, "SCSI %p could not issue command: %s\n",
+ scsidev, strerror ( rc ) );
return rc;
+ }
- /* Fill in block device fields */
- blockdev->blksize = be32_to_cpu ( capacity.blksize );
- blockdev->blocks = ( be32_to_cpu ( capacity.lba ) + 1 );
+ /* Record tag */
+ if ( scsicmd->tag ) {
+ DBGC ( scsidev, "SCSI %p tag %08x is now tag %08x\n",
+ scsidev, scsicmd->tag, tag );
+ }
+ scsicmd->tag = tag;
+ DBGC2 ( scsidev, "SCSI %p tag %08x %s " SCSI_CDB_FORMAT "\n",
+ scsidev, scsicmd->tag, scsicmd->type->name,
+ SCSI_CDB_DATA ( command.cdb ) );
return 0;
}
/**
- * Read capacity of SCSI device via READ CAPACITY (16)
+ * Handle SCSI command completion
*
- * @v blockdev Block device
- * @ret rc Return status code
+ * @v scsicmd SCSI command
+ * @v rc Reason for close
*/
-static int scsi_read_capacity_16 ( struct block_device *blockdev ) {
- struct scsi_device *scsi = block_to_scsi ( blockdev );
- struct scsi_command command;
- struct scsi_cdb_read_capacity_16 *cdb = &command.cdb.readcap16;
- struct scsi_capacity_16 capacity;
- int rc;
+static void scsicmd_done ( struct scsi_command *scsicmd, int rc ) {
+ struct scsi_device *scsidev = scsicmd->scsidev;
- /* Issue READ CAPACITY (16) */
- memset ( &command, 0, sizeof ( command ) );
- cdb->opcode = SCSI_OPCODE_SERVICE_ACTION_IN;
- cdb->service_action = SCSI_SERVICE_ACTION_READ_CAPACITY_16;
- cdb->len = cpu_to_be32 ( sizeof ( capacity ) );
- command.data_in = virt_to_user ( &capacity );
- command.data_in_len = sizeof ( capacity );
+ /* Restart SCSI interface */
+ intf_restart ( &scsicmd->scsi, rc );
- if ( ( rc = scsi_command ( scsi, &command ) ) != 0 )
- return rc;
+ /* SCSI targets have an annoying habit of returning occasional
+ * pointless "error" messages such as "power-on occurred", so
+ * we have to be prepared to retry commands.
+ */
+ if ( ( rc != 0 ) && ( scsicmd->retries++ < SCSICMD_MAX_RETRIES ) ) {
+ /* Retry command */
+ DBGC ( scsidev, "SCSI %p tag %08x failed: %s\n",
+ scsidev, scsicmd->tag, strerror ( rc ) );
+ DBGC ( scsidev, "SCSI %p tag %08x retrying (retry %d)\n",
+ scsidev, scsicmd->tag, scsicmd->retries );
+ if ( ( rc = scsicmd_command ( scsicmd ) ) == 0 )
+ return;
+ }
- /* Fill in block device fields */
- blockdev->blksize = be32_to_cpu ( capacity.blksize );
- blockdev->blocks = ( be64_to_cpu ( capacity.lba ) + 1 );
- return 0;
+ /* If we didn't (successfully) reissue the command, hand over
+ * to the command completion handler.
+ */
+ scsicmd->type->done ( scsicmd, rc );
}
-static struct block_device_operations scsi_operations_16 = {
- .read = scsi_read_16,
- .write = scsi_write_16,
-};
+/**
+ * Handle SCSI response
+ *
+ * @v scsicmd SCSI command
+ * @v response SCSI response
+ */
+static void scsicmd_response ( struct scsi_command *scsicmd,
+ struct scsi_rsp *response ) {
+ struct scsi_device *scsidev = scsicmd->scsidev;
+ size_t overrun;
+ size_t underrun;
+
+ if ( response->status == 0 ) {
+ scsicmd_done ( scsicmd, 0 );
+ } else {
+ DBGC ( scsidev, "SCSI %p tag %08x status %02x",
+ scsidev, scsicmd->tag, response->status );
+ if ( response->overrun > 0 ) {
+ overrun = response->overrun;
+ DBGC ( scsidev, " overrun +%zd", overrun );
+ } else if ( response->overrun < 0 ) {
+ underrun = -(response->overrun);
+ DBGC ( scsidev, " underrun -%zd", underrun );
+ }
+ DBGC ( scsidev, " sense %02x:%02x:%08x\n",
+ response->sense.code, response->sense.key,
+ ntohl ( response->sense.info ) );
+ scsicmd_done ( scsicmd, -EIO );
+ }
+}
-static struct block_device_operations scsi_operations_10 = {
- .read = scsi_read_10,
- .write = scsi_write_10,
+/**
+ * Construct SCSI READ command
+ *
+ * @v scsicmd SCSI command
+ * @v command SCSI command IU
+ */
+static void scsicmd_read_cmd ( struct scsi_command *scsicmd,
+ struct scsi_cmd *command ) {
+
+ if ( ( scsicmd->lba + scsicmd->count ) > SCSI_MAX_BLOCK_10 ) {
+ /* Use READ (16) */
+ command->cdb.read16.opcode = SCSI_OPCODE_READ_16;
+ command->cdb.read16.lba = cpu_to_be64 ( scsicmd->lba );
+ command->cdb.read16.len = cpu_to_be32 ( scsicmd->count );
+ } else {
+ /* Use READ (10) */
+ command->cdb.read10.opcode = SCSI_OPCODE_READ_10;
+ command->cdb.read10.lba = cpu_to_be32 ( scsicmd->lba );
+ command->cdb.read10.len = cpu_to_be16 ( scsicmd->count );
+ }
+ command->data_in = scsicmd->buffer;
+ command->data_in_len = scsicmd->len;
+}
+
+/** SCSI READ command type */
+static struct scsi_command_type scsicmd_read = {
+ .name = "READ",
+ .cmd = scsicmd_read_cmd,
+ .done = scsicmd_close,
};
/**
- * Initialise SCSI device
+ * Construct SCSI WRITE command
*
- * @v scsi SCSI device
- * @ret rc Return status code
- *
- * Initialises a SCSI device. The scsi_device::command and
- * scsi_device::lun fields must already be filled in. This function
- * will configure scsi_device::blockdev, including issuing a READ
- * CAPACITY call to determine the block size and total device size.
+ * @v scsicmd SCSI command
+ * @v command SCSI command IU
*/
-int init_scsidev ( struct scsi_device *scsi ) {
- unsigned int i;
- int rc;
+static void scsicmd_write_cmd ( struct scsi_command *scsicmd,
+ struct scsi_cmd *command ) {
+
+ if ( ( scsicmd->lba + scsicmd->count ) > SCSI_MAX_BLOCK_10 ) {
+ /* Use WRITE (16) */
+ command->cdb.write16.opcode = SCSI_OPCODE_WRITE_16;
+ command->cdb.write16.lba = cpu_to_be64 ( scsicmd->lba );
+ command->cdb.write16.len = cpu_to_be32 ( scsicmd->count );
+ } else {
+ /* Use WRITE (10) */
+ command->cdb.write10.opcode = SCSI_OPCODE_WRITE_10;
+ command->cdb.write10.lba = cpu_to_be32 ( scsicmd->lba );
+ command->cdb.write10.len = cpu_to_be16 ( scsicmd->count );
+ }
+ command->data_out = scsicmd->buffer;
+ command->data_out_len = scsicmd->len;
+}
- /* Issue some theoretically extraneous READ CAPACITY (10)
- * commands, solely in order to draw out the "CHECK CONDITION
- * (power-on occurred)", "CHECK CONDITION (reported LUNs data
- * has changed)" etc. that some dumb targets insist on sending
- * as an error at start of day. The precise command that we
- * use is unimportant; we just need to provide the target with
- * an opportunity to send its responses.
- */
- for ( i = 0 ; i < SCSI_MAX_DUMMY_READ_CAP ; i++ ) {
- if ( ( rc = scsi_read_capacity_10 ( &scsi->blockdev ) ) == 0 )
- break;
- DBGC ( scsi, "SCSI %p ignoring start-of-day error (#%d)\n",
- scsi, ( i + 1 ) );
+/** SCSI WRITE command type */
+static struct scsi_command_type scsicmd_write = {
+ .name = "WRITE",
+ .cmd = scsicmd_write_cmd,
+ .done = scsicmd_close,
+};
+
+/** SCSI READ CAPACITY private data */
+struct scsi_read_capacity_private {
+ /** Use READ CAPACITY (16) */
+ int use16;
+ /** Data buffer for READ CAPACITY commands */
+ union {
+ /** Data buffer for READ CAPACITY (10) */
+ struct scsi_capacity_10 capacity10;
+ /** Data buffer for READ CAPACITY (16) */
+ struct scsi_capacity_16 capacity16;
+ } capacity;
+};
+
+/**
+ * Construct SCSI READ CAPACITY command
+ *
+ * @v scsicmd SCSI command
+ * @v command SCSI command IU
+ */
+static void scsicmd_read_capacity_cmd ( struct scsi_command *scsicmd,
+ struct scsi_cmd *command ) {
+ struct scsi_read_capacity_private *priv = scsicmd_priv ( scsicmd );
+ struct scsi_cdb_read_capacity_16 *readcap16 = &command->cdb.readcap16;
+ struct scsi_cdb_read_capacity_10 *readcap10 = &command->cdb.readcap10;
+ struct scsi_capacity_16 *capacity16 = &priv->capacity.capacity16;
+ struct scsi_capacity_10 *capacity10 = &priv->capacity.capacity10;
+
+ if ( priv->use16 ) {
+ /* Use READ CAPACITY (16) */
+ readcap16->opcode = SCSI_OPCODE_SERVICE_ACTION_IN;
+ readcap16->service_action =
+ SCSI_SERVICE_ACTION_READ_CAPACITY_16;
+ readcap16->len = cpu_to_be32 ( sizeof ( *capacity16 ) );
+ command->data_in = virt_to_user ( capacity16 );
+ command->data_in_len = sizeof ( *capacity16 );
+ } else {
+ /* Use READ CAPACITY (10) */
+ readcap10->opcode = SCSI_OPCODE_READ_CAPACITY_10;
+ command->data_in = virt_to_user ( capacity10 );
+ command->data_in_len = sizeof ( *capacity10 );
}
+}
- /* Try READ CAPACITY (10), which is a mandatory command, first. */
- scsi->blockdev.op = &scsi_operations_10;
- if ( ( rc = scsi_read_capacity_10 ( &scsi->blockdev ) ) != 0 ) {
- DBGC ( scsi, "SCSI %p could not READ CAPACITY (10): %s\n",
- scsi, strerror ( rc ) );
- return rc;
+/**
+ * Handle SCSI READ CAPACITY command completion
+ *
+ * @v scsicmd SCSI command
+ * @v rc Reason for completion
+ */
+static void scsicmd_read_capacity_done ( struct scsi_command *scsicmd,
+ int rc ) {
+ struct scsi_read_capacity_private *priv = scsicmd_priv ( scsicmd );
+ struct scsi_capacity_16 *capacity16 = &priv->capacity.capacity16;
+ struct scsi_capacity_10 *capacity10 = &priv->capacity.capacity10;
+ struct block_device_capacity capacity;
+
+ /* Close if command failed */
+ if ( rc != 0 ) {
+ scsicmd_close ( scsicmd, rc );
+ return;
}
- /* If capacity range was exceeded (i.e. capacity.lba was
- * 0xffffffff, meaning that blockdev->blocks is now zero), use
- * READ CAPACITY (16) instead. READ CAPACITY (16) is not
- * mandatory, so we can't just use it straight off.
- */
- if ( scsi->blockdev.blocks == 0 ) {
- scsi->blockdev.op = &scsi_operations_16;
- if ( ( rc = scsi_read_capacity_16 ( &scsi->blockdev ) ) != 0 ){
- DBGC ( scsi, "SCSI %p could not READ CAPACITY (16): "
- "%s\n", scsi, strerror ( rc ) );
- return rc;
+ /* Extract capacity */
+ if ( priv->use16 ) {
+ capacity.blocks = ( be64_to_cpu ( capacity16->lba ) + 1 );
+ capacity.blksize = be32_to_cpu ( capacity16->blksize );
+ } else {
+ capacity.blocks = ( be32_to_cpu ( capacity10->lba ) + 1 );
+ capacity.blksize = be32_to_cpu ( capacity10->blksize );
+
+ /* If capacity range was exceeded (i.e. capacity.lba
+ * was 0xffffffff, meaning that blockdev->blocks is
+ * now zero), use READ CAPACITY (16) instead. READ
+ * CAPACITY (16) is not mandatory, so we can't just
+ * use it straight off.
+ */
+ if ( capacity.blocks == 0 ) {
+ priv->use16 = 1;
+ if ( ( rc = scsicmd_command ( scsicmd ) ) != 0 ) {
+ scsicmd_close ( scsicmd, rc );
+ return;
+ }
+ return;
}
}
+ capacity.max_count = -1U;
+
+ /* Return capacity to caller */
+ block_capacity ( &scsicmd->block, &capacity );
+
+ /* Close command */
+ scsicmd_close ( scsicmd, 0 );
+}
+
+/** SCSI READ CAPACITY command type */
+static struct scsi_command_type scsicmd_read_capacity = {
+ .name = "READ CAPACITY",
+ .priv_len = sizeof ( struct scsi_read_capacity_private ),
+ .cmd = scsicmd_read_capacity_cmd,
+ .done = scsicmd_read_capacity_done,
+};
+
+/** SCSI command block interface operations */
+static struct interface_operation scsicmd_block_op[] = {
+ INTF_OP ( intf_close, struct scsi_command *, scsicmd_close ),
+};
+
+/** SCSI command block interface descriptor */
+static struct interface_descriptor scsicmd_block_desc =
+ INTF_DESC_PASSTHRU ( struct scsi_command, block,
+ scsicmd_block_op, scsi );
+
+/** SCSI command SCSI interface operations */
+static struct interface_operation scsicmd_scsi_op[] = {
+ INTF_OP ( intf_close, struct scsi_command *, scsicmd_done ),
+ INTF_OP ( scsi_response, struct scsi_command *, scsicmd_response ),
+};
- DBGC ( scsi, "SCSI %p using READ/WRITE (%d) commands\n", scsi,
- ( ( scsi->blockdev.op == &scsi_operations_10 ) ? 10 : 16 ) );
- DBGC ( scsi, "SCSI %p capacity is %ld MB (%#llx blocks)\n", scsi,
- ( ( unsigned long ) ( scsi->blockdev.blocks >> 11 ) ),
- scsi->blockdev.blocks );
+/** SCSI command SCSI interface descriptor */
+static struct interface_descriptor scsicmd_scsi_desc =
+ INTF_DESC_PASSTHRU ( struct scsi_command, scsi,
+ scsicmd_scsi_op, block );
+/**
+ * Create SCSI command
+ *
+ * @v scsidev SCSI device
+ * @v block Block data interface
+ * @v type SCSI command type
+ * @v lba Starting logical block address
+ * @v count Number of blocks to transfer
+ * @v buffer Data buffer
+ * @v len Length of data buffer
+ * @ret rc Return status code
+ */
+static int scsidev_command ( struct scsi_device *scsidev,
+ struct interface *block,
+ struct scsi_command_type *type,
+ uint64_t lba, unsigned int count,
+ userptr_t buffer, size_t len ) {
+ struct scsi_command *scsicmd;
+ int rc;
+
+ /* Allocate and initialise structure */
+ scsicmd = zalloc ( sizeof ( *scsicmd ) + type->priv_len );
+ if ( ! scsicmd ) {
+ rc = -ENOMEM;
+ goto err_zalloc;
+ }
+ ref_init ( &scsicmd->refcnt, scsicmd_free );
+ intf_init ( &scsicmd->block, &scsicmd_block_desc, &scsicmd->refcnt );
+ intf_init ( &scsicmd->scsi, &scsicmd_scsi_desc,
+ &scsicmd->refcnt );
+ scsicmd->scsidev = scsidev_get ( scsidev );
+ list_add ( &scsicmd->list, &scsidev->cmds );
+ scsicmd->type = type;
+ scsicmd->lba = lba;
+ scsicmd->count = count;
+ scsicmd->buffer = buffer;
+ scsicmd->len = len;
+
+ /* Issue SCSI command */
+ if ( ( rc = scsicmd_command ( scsicmd ) ) != 0 )
+ goto err_command;
+
+ /* Attach to parent interface, mortalise self, and return */
+ intf_plug_plug ( &scsicmd->block, block );
+ ref_put ( &scsicmd->refcnt );
return 0;
+
+ err_command:
+ scsicmd_close ( scsicmd, rc );
+ ref_put ( &scsicmd->refcnt );
+ err_zalloc:
+ return rc;
}
/**
- * Parse SCSI LUN
+ * Issue SCSI block read
*
- * @v lun_string LUN string representation
- * @v lun LUN to fill in
+ * @v scsidev SCSI device
+ * @v block Block data interface
+ * @v lba Starting logical block address
+ * @v count Number of blocks to transfer
+ * @v buffer Data buffer
+ * @v len Length of data buffer
* @ret rc Return status code
+
*/
-int scsi_parse_lun ( const char *lun_string, struct scsi_lun *lun ) {
- char *p;
- int i;
+static int scsidev_read ( struct scsi_device *scsidev,
+ struct interface *block,
+ uint64_t lba, unsigned int count,
+ userptr_t buffer, size_t len ) {
+ return scsidev_command ( scsidev, block, &scsicmd_read,
+ lba, count, buffer, len );
+}
- memset ( lun, 0, sizeof ( *lun ) );
- if ( lun_string ) {
- p = ( char * ) lun_string;
- for ( i = 0 ; i < 4 ; i++ ) {
- lun->u16[i] = htons ( strtoul ( p, &p, 16 ) );
- if ( *p == '\0' )
- break;
- if ( *p != '-' )
- return -EINVAL;
- p++;
- }
- if ( *p )
- return -EINVAL;
+/**
+ * Issue SCSI block write
+ *
+ * @v scsidev SCSI device
+ * @v block Block data interface
+ * @v lba Starting logical block address
+ * @v count Number of blocks to transfer
+ * @v buffer Data buffer
+ * @v len Length of data buffer
+ * @ret rc Return status code
+ */
+static int scsidev_write ( struct scsi_device *scsidev,
+ struct interface *block,
+ uint64_t lba, unsigned int count,
+ userptr_t buffer, size_t len ) {
+ return scsidev_command ( scsidev, block, &scsicmd_write,
+ lba, count, buffer, len );
+}
+
+/**
+ * Read SCSI device capacity
+ *
+ * @v scsidev SCSI device
+ * @v block Block data interface
+ * @ret rc Return status code
+ */
+static int scsidev_read_capacity ( struct scsi_device *scsidev,
+ struct interface *block ) {
+ return scsidev_command ( scsidev, block, &scsicmd_read_capacity,
+ 0, 0, UNULL, 0 );
+}
+
+/**
+ * Close SCSI device
+ *
+ * @v scsidev SCSI device
+ * @v rc Reason for close
+ */
+static void scsidev_close ( struct scsi_device *scsidev, int rc ) {
+ struct scsi_command *scsicmd;
+ struct scsi_command *tmp;
+
+ /* Shut down interfaces */
+ intf_shutdown ( &scsidev->block, rc );
+ intf_shutdown ( &scsidev->scsi, rc );
+
+ /* Shut down any remaining commands */
+ list_for_each_entry_safe ( scsicmd, tmp, &scsidev->cmds, list ) {
+ scsicmd_get ( scsicmd );
+ scsicmd_close ( scsicmd, rc );
+ scsicmd_put ( scsicmd );
}
+}
+
+/** SCSI device block interface operations */
+static struct interface_operation scsidev_block_op[] = {
+ INTF_OP ( block_read, struct scsi_device *, scsidev_read ),
+ INTF_OP ( block_write, struct scsi_device *, scsidev_write ),
+ INTF_OP ( block_read_capacity, struct scsi_device *,
+ scsidev_read_capacity ),
+ INTF_OP ( intf_close, struct scsi_device *, scsidev_close ),
+};
+
+/** SCSI device block interface descriptor */
+static struct interface_descriptor scsidev_block_desc =
+ INTF_DESC_PASSTHRU ( struct scsi_device, block,
+ scsidev_block_op, scsi );
+
+/** SCSI device SCSI interface operations */
+static struct interface_operation scsidev_scsi_op[] = {
+ INTF_OP ( intf_close, struct scsi_device *, scsidev_close ),
+};
+/** SCSI device SCSI interface descriptor */
+static struct interface_descriptor scsidev_scsi_desc =
+ INTF_DESC_PASSTHRU ( struct scsi_device, scsi,
+ scsidev_scsi_op, block );
+
+/**
+ * Open SCSI device
+ *
+ * @v block Block control interface
+ * @v scsi SCSI control interface
+ * @v lun SCSI LUN
+ * @ret rc Return status code
+ */
+int scsi_open ( struct interface *block, struct interface *scsi,
+ struct scsi_lun *lun ) {
+ struct scsi_device *scsidev;
+
+ /* Allocate and initialise structure */
+ scsidev = zalloc ( sizeof ( *scsidev ) );
+ if ( ! scsidev )
+ return -ENOMEM;
+ ref_init ( &scsidev->refcnt, NULL );
+ intf_init ( &scsidev->block, &scsidev_block_desc, &scsidev->refcnt );
+ intf_init ( &scsidev->scsi, &scsidev_scsi_desc, &scsidev->refcnt );
+ INIT_LIST_HEAD ( &scsidev->cmds );
+ memcpy ( &scsidev->lun, lun, sizeof ( scsidev->lun ) );
+ DBGC ( scsidev, "SCSI %p created for LUN " SCSI_LUN_FORMAT "\n",
+ scsidev, SCSI_LUN_DATA ( scsidev->lun ) );
+
+ /* Attach to SCSI and parent and interfaces, mortalise self,
+ * and return
+ */
+ intf_plug_plug ( &scsidev->scsi, scsi );
+ intf_plug_plug ( &scsidev->block, block );
+ ref_put ( &scsidev->refcnt );
return 0;
}
diff --git a/src/drivers/block/srp.c b/src/drivers/block/srp.c
index a8deab15d..4b592e95c 100644
--- a/src/drivers/block/srp.c
+++ b/src/drivers/block/srp.c
@@ -36,7 +36,6 @@ FILE_LICENCE ( BSD2 );
#include <ipxe/scsi.h>
#include <ipxe/xfer.h>
#include <ipxe/features.h>
-#include <ipxe/ib_srp.h>
#include <ipxe/srp.h>
/**
@@ -48,334 +47,555 @@ FILE_LICENCE ( BSD2 );
FEATURE ( FEATURE_PROTOCOL, "SRP", DHCP_EB_FEATURE_SRP, 1 );
-/** Tag to be used for next SRP IU */
-static unsigned int srp_tag = 0;
+/** Maximum length of any initiator-to-target IU that we will send
+ *
+ * The longest IU is a SRP_CMD with no additional CDB and two direct
+ * data buffer descriptors, which comes to 80 bytes.
+ */
+#define SRP_MAX_I_T_IU_LEN 80
+
+/** An SRP device */
+struct srp_device {
+ /** Reference count */
+ struct refcnt refcnt;
+
+ /** SCSI command issuing interface */
+ struct interface scsi;
+ /** Underlying data transfer interface */
+ struct interface socket;
+
+ /** RDMA memory handle */
+ uint32_t memory_handle;
+ /** Login completed successfully */
+ int logged_in;
+
+ /** Initiator port ID (for boot firmware table) */
+ union srp_port_id initiator;
+ /** Target port ID (for boot firmware table) */
+ union srp_port_id target;
+ /** SCSI LUN (for boot firmware table) */
+ struct scsi_lun lun;
+
+ /** List of active commands */
+ struct list_head commands;
+};
+
+/** An SRP command */
+struct srp_command {
+ /** Reference count */
+ struct refcnt refcnt;
+ /** SRP device */
+ struct srp_device *srpdev;
+ /** List of active commands */
+ struct list_head list;
+
+ /** SCSI command interface */
+ struct interface scsi;
+ /** Command tag */
+ uint32_t tag;
+};
+
+/**
+ * Get reference to SRP device
+ *
+ * @v srpdev SRP device
+ * @ret srpdev SRP device
+ */
+static inline __attribute__ (( always_inline )) struct srp_device *
+srpdev_get ( struct srp_device *srpdev ) {
+ ref_get ( &srpdev->refcnt );
+ return srpdev;
+}
-static void srp_login ( struct srp_device *srp );
-static void srp_cmd ( struct srp_device *srp );
+/**
+ * Drop reference to SRP device
+ *
+ * @v srpdev SRP device
+ */
+static inline __attribute__ (( always_inline )) void
+srpdev_put ( struct srp_device *srpdev ) {
+ ref_put ( &srpdev->refcnt );
+}
+
+/**
+ * Get reference to SRP command
+ *
+ * @v srpcmd SRP command
+ * @ret srpcmd SRP command
+ */
+static inline __attribute__ (( always_inline )) struct srp_command *
+srpcmd_get ( struct srp_command *srpcmd ) {
+ ref_get ( &srpcmd->refcnt );
+ return srpcmd;
+}
/**
- * Mark SRP SCSI command as complete
+ * Drop reference to SRP command
*
- * @v srp SRP device
- * @v rc Status code
+ * @v srpcmd SRP command
*/
-static void srp_scsi_done ( struct srp_device *srp, int rc ) {
- if ( srp->command )
- srp->command->rc = rc;
- srp->command = NULL;
+static inline __attribute__ (( always_inline )) void
+srpcmd_put ( struct srp_command *srpcmd ) {
+ ref_put ( &srpcmd->refcnt );
}
/**
- * Handle SRP session failure
+ * Free SRP command
*
- * @v srp SRP device
- * @v rc Reason for failure
+ * @v refcnt Reference count
*/
-static void srp_fail ( struct srp_device *srp, int rc ) {
+static void srpcmd_free ( struct refcnt *refcnt ) {
+ struct srp_command *srpcmd =
+ container_of ( refcnt, struct srp_command, refcnt );
- /* Close underlying socket */
- intf_restart ( &srp->socket, rc );
+ assert ( list_empty ( &srpcmd->list ) );
- /* Clear session state */
- srp->state = 0;
+ srpdev_put ( srpcmd->srpdev );
+ free ( srpcmd );
+}
- /* If we have reached the retry limit, report the failure */
- if ( srp->retry_count >= SRP_MAX_RETRIES ) {
- srp_scsi_done ( srp, rc );
- return;
+/**
+ * Close SRP command
+ *
+ * @v srpcmd SRP command
+ * @v rc Reason for close
+ */
+static void srpcmd_close ( struct srp_command *srpcmd, int rc ) {
+ struct srp_device *srpdev = srpcmd->srpdev;
+
+ if ( rc != 0 ) {
+ DBGC ( srpdev, "SRP %p tag %08x closed: %s\n",
+ srpdev, srpcmd->tag, strerror ( rc ) );
}
- /* Otherwise, increment the retry count and try to reopen the
- * connection
- */
- srp->retry_count++;
- srp_login ( srp );
+ /* Remove from list of commands */
+ if ( ! list_empty ( &srpcmd->list ) ) {
+ list_del ( &srpcmd->list );
+ INIT_LIST_HEAD ( &srpcmd->list );
+ srpcmd_put ( srpcmd );
+ }
+
+ /* Shut down interfaces */
+ intf_shutdown ( &srpcmd->scsi, rc );
}
/**
- * Initiate SRP login
+ * Close SRP device
*
- * @v srp SRP device
+ * @v srpdev SRP device
+ * @v rc Reason for close
*/
-static void srp_login ( struct srp_device *srp ) {
- struct io_buffer *iobuf;
- struct srp_login_req *login_req;
- int rc;
+static void srpdev_close ( struct srp_device *srpdev, int rc ) {
+ struct srp_command *srpcmd;
+ struct srp_command *tmp;
- assert ( ! ( srp->state & SRP_STATE_SOCKET_OPEN ) );
+ if ( rc != 0 ) {
+ DBGC ( srpdev, "SRP %p closed: %s\n",
+ srpdev, strerror ( rc ) );
+ }
- /* Open underlying socket */
- if ( ( rc = srp->transport->connect ( srp ) ) != 0 ) {
- DBGC ( srp, "SRP %p could not open socket: %s\n",
- srp, strerror ( rc ) );
- goto err;
+ /* Shut down interfaces */
+ intf_shutdown ( &srpdev->socket, rc );
+ intf_shutdown ( &srpdev->scsi, rc );
+
+ /* Shut down any active commands */
+ list_for_each_entry_safe ( srpcmd, tmp, &srpdev->commands, list ) {
+ srpcmd_get ( srpcmd );
+ srpcmd_close ( srpcmd, rc );
+ srpcmd_put ( srpcmd );
}
- srp->state |= SRP_STATE_SOCKET_OPEN;
+}
- /* Allocate I/O buffer */
- iobuf = xfer_alloc_iob ( &srp->socket, sizeof ( *login_req ) );
- if ( ! iobuf ) {
- rc = -ENOMEM;
- goto err;
+/**
+ * Identify SRP command by tag
+ *
+ * @v srpdev SRP device
+ * @v tag Command tag
+ * @ret srpcmd SRP command, or NULL
+ */
+static struct srp_command * srp_find_tag ( struct srp_device *srpdev,
+ uint32_t tag ) {
+ struct srp_command *srpcmd;
+
+ list_for_each_entry ( srpcmd, &srpdev->commands, list ) {
+ if ( srpcmd->tag == tag )
+ return srpcmd;
+ }
+ return NULL;
+}
+
+/**
+ * Choose an SRP command tag
+ *
+ * @v srpdev SRP device
+ * @ret tag New tag, or negative error
+ */
+static int srp_new_tag ( struct srp_device *srpdev ) {
+ static uint16_t tag_idx;
+ unsigned int i;
+
+ for ( i = 0 ; i < 65536 ; i++ ) {
+ tag_idx++;
+ if ( srp_find_tag ( srpdev, tag_idx ) == NULL )
+ return tag_idx;
}
+ return -EADDRINUSE;
+}
+
+/**
+ * Transmit SRP login request
+ *
+ * @v srpdev SRP device
+ * @v initiator Initiator port ID
+ * @v target Target port ID
+ * @v tag Command tag
+ * @ret rc Return status code
+ */
+static int srp_login ( struct srp_device *srpdev, union srp_port_id *initiator,
+ union srp_port_id *target, uint32_t tag ) {
+ struct io_buffer *iobuf;
+ struct srp_login_req *login_req;
+ int rc;
+
+ /* Allocate I/O buffer */
+ iobuf = xfer_alloc_iob ( &srpdev->socket, sizeof ( *login_req ) );
+ if ( ! iobuf )
+ return -ENOMEM;
/* Construct login request IU */
login_req = iob_put ( iobuf, sizeof ( *login_req ) );
memset ( login_req, 0, sizeof ( *login_req ) );
login_req->type = SRP_LOGIN_REQ;
- login_req->tag.dwords[1] = htonl ( ++srp_tag );
+ login_req->tag.dwords[0] = htonl ( SRP_TAG_MAGIC );
+ login_req->tag.dwords[1] = htonl ( tag );
login_req->max_i_t_iu_len = htonl ( SRP_MAX_I_T_IU_LEN );
login_req->required_buffer_formats = SRP_LOGIN_REQ_FMT_DDBD;
- memcpy ( &login_req->port_ids, &srp->port_ids,
- sizeof ( login_req->port_ids ) );
+ memcpy ( &login_req->initiator, initiator,
+ sizeof ( login_req->initiator ) );
+ memcpy ( &login_req->target, target, sizeof ( login_req->target ) );
- DBGC2 ( srp, "SRP %p TX login request tag %08x%08x\n",
- srp, ntohl ( login_req->tag.dwords[0] ),
- ntohl ( login_req->tag.dwords[1] ) );
- DBGC2_HDA ( srp, 0, iobuf->data, iob_len ( iobuf ) );
+ DBGC ( srpdev, "SRP %p tag %08x LOGIN_REQ:\n", srpdev, tag );
+ DBGC_HDA ( srpdev, 0, iobuf->data, iob_len ( iobuf ) );
/* Send login request IU */
- if ( ( rc = xfer_deliver_iob ( &srp->socket, iobuf ) ) != 0 ) {
- DBGC ( srp, "SRP %p could not send login request: %s\n",
- srp, strerror ( rc ) );
- goto err;
+ if ( ( rc = xfer_deliver_iob ( &srpdev->socket, iobuf ) ) != 0 ) {
+ DBGC ( srpdev, "SRP %p tag %08x could not send LOGIN_REQ: "
+ "%s\n", srpdev, tag, strerror ( rc ) );
+ return rc;
}
- return;
-
- err:
- srp_fail ( srp, rc );
+ return 0;
}
/**
- * Handle SRP login response
+ * Receive SRP login response
*
- * @v srp SRP device
- * @v iobuf I/O buffer
+ * @v srpdev SRP device
+ * @v data SRP IU
+ * @v len Length of SRP IU
* @ret rc Return status code
*/
-static int srp_login_rsp ( struct srp_device *srp, struct io_buffer *iobuf ) {
- struct srp_login_rsp *login_rsp = iobuf->data;
- int rc;
-
- DBGC2 ( srp, "SRP %p RX login response tag %08x%08x\n",
- srp, ntohl ( login_rsp->tag.dwords[0] ),
- ntohl ( login_rsp->tag.dwords[1] ) );
+static int srp_login_rsp ( struct srp_device *srpdev,
+ const void *data, size_t len ) {
+ const struct srp_login_rsp *login_rsp = data;
/* Sanity check */
- if ( iob_len ( iobuf ) < sizeof ( *login_rsp ) ) {
- DBGC ( srp, "SRP %p RX login response too short (%zd bytes)\n",
- srp, iob_len ( iobuf ) );
- rc = -EINVAL;
- goto out;
+ if ( len < sizeof ( *login_rsp ) ) {
+ DBGC ( srpdev, "SRP %p LOGIN_RSP too short (%zd bytes)\n",
+ srpdev, len );
+ return -EINVAL;
}
-
- DBGC ( srp, "SRP %p logged in\n", srp );
+ DBGC ( srpdev, "SRP %p tag %08x LOGIN_RSP:\n",
+ srpdev, ntohl ( login_rsp->tag.dwords[1] ) );
+ DBGC_HDA ( srpdev, 0, data, len );
/* Mark as logged in */
- srp->state |= SRP_STATE_LOGGED_IN;
+ srpdev->logged_in = 1;
+ DBGC ( srpdev, "SRP %p logged in\n", srpdev );
- /* Reset error counter */
- srp->retry_count = 0;
+ /* Notify of window change */
+ xfer_window_changed ( &srpdev->scsi );
- /* Issue pending command */
- srp_cmd ( srp );
-
- rc = 0;
- out:
- free_iob ( iobuf );
- return rc;
+ return 0;
}
/**
- * Handle SRP login rejection
+ * Receive SRP login rejection
*
- * @v srp SRP device
- * @v iobuf I/O buffer
+ * @v srpdev SRP device
+ * @v data SRP IU
+ * @v len Length of SRP IU
* @ret rc Return status code
*/
-static int srp_login_rej ( struct srp_device *srp, struct io_buffer *iobuf ) {
- struct srp_login_rej *login_rej = iobuf->data;
- int rc;
-
- DBGC2 ( srp, "SRP %p RX login rejection tag %08x%08x\n",
- srp, ntohl ( login_rej->tag.dwords[0] ),
- ntohl ( login_rej->tag.dwords[1] ) );
+static int srp_login_rej ( struct srp_device *srpdev,
+ const void *data, size_t len ) {
+ const struct srp_login_rej *login_rej = data;
/* Sanity check */
- if ( iob_len ( iobuf ) < sizeof ( *login_rej ) ) {
- DBGC ( srp, "SRP %p RX login rejection too short (%zd "
- "bytes)\n", srp, iob_len ( iobuf ) );
- rc = -EINVAL;
- goto out;
+ if ( len < sizeof ( *login_rej ) ) {
+ DBGC ( srpdev, "SRP %p LOGIN_REJ too short (%zd bytes)\n",
+ srpdev, len );
+ return -EINVAL;
}
+ DBGC ( srpdev, "SRP %p tag %08x LOGIN_REJ:\n",
+ srpdev, ntohl ( login_rej->tag.dwords[1] ) );
+ DBGC_HDA ( srpdev, 0, data, len );
/* Login rejection always indicates an error */
- DBGC ( srp, "SRP %p login rejected (reason %08x)\n",
- srp, ntohl ( login_rej->reason ) );
- rc = -EPERM;
-
- out:
- free_iob ( iobuf );
- return rc;
+ DBGC ( srpdev, "SRP %p login rejected (reason %08x)\n",
+ srpdev, ntohl ( login_rej->reason ) );
+ return -EPERM;
}
/**
* Transmit SRP SCSI command
*
- * @v srp SRP device
+ * @v srpdev SRP device
+ * @v command SCSI command
+ * @v tag Command tag
+ * @ret rc Return status code
*/
-static void srp_cmd ( struct srp_device *srp ) {
+static int srp_cmd ( struct srp_device *srpdev,
+ struct scsi_cmd *command,
+ uint32_t tag ) {
struct io_buffer *iobuf;
struct srp_cmd *cmd;
struct srp_memory_descriptor *data_out;
struct srp_memory_descriptor *data_in;
int rc;
- assert ( srp->state & SRP_STATE_LOGGED_IN );
+ /* Sanity check */
+ if ( ! srpdev->logged_in ) {
+ DBGC ( srpdev, "SRP %p tag %08x cannot send CMD before "
+ "login completes\n", srpdev, tag );
+ return -EBUSY;
+ }
/* Allocate I/O buffer */
- iobuf = xfer_alloc_iob ( &srp->socket, SRP_MAX_I_T_IU_LEN );
- if ( ! iobuf ) {
- rc = -ENOMEM;
- goto err;
- }
+ iobuf = xfer_alloc_iob ( &srpdev->socket, SRP_MAX_I_T_IU_LEN );
+ if ( ! iobuf )
+ return -ENOMEM;
/* Construct base portion */
cmd = iob_put ( iobuf, sizeof ( *cmd ) );
memset ( cmd, 0, sizeof ( *cmd ) );
cmd->type = SRP_CMD;
- cmd->tag.dwords[1] = htonl ( ++srp_tag );
- cmd->lun = srp->lun;
- memcpy ( &cmd->cdb, &srp->command->cdb, sizeof ( cmd->cdb ) );
+ cmd->tag.dwords[0] = htonl ( SRP_TAG_MAGIC );
+ cmd->tag.dwords[1] = htonl ( tag );
+ memcpy ( &cmd->lun, &command->lun, sizeof ( cmd->lun ) );
+ memcpy ( &cmd->cdb, &command->cdb, sizeof ( cmd->cdb ) );
/* Construct data-out descriptor, if present */
- if ( srp->command->data_out ) {
+ if ( command->data_out ) {
cmd->data_buffer_formats |= SRP_CMD_DO_FMT_DIRECT;
data_out = iob_put ( iobuf, sizeof ( *data_out ) );
data_out->address =
- cpu_to_be64 ( user_to_phys ( srp->command->data_out, 0 ) );
- data_out->handle = ntohl ( srp->memory_handle );
- data_out->len = ntohl ( srp->command->data_out_len );
+ cpu_to_be64 ( user_to_phys ( command->data_out, 0 ) );
+ data_out->handle = ntohl ( srpdev->memory_handle );
+ data_out->len = ntohl ( command->data_out_len );
}
/* Construct data-in descriptor, if present */
- if ( srp->command->data_in ) {
+ if ( command->data_in ) {
cmd->data_buffer_formats |= SRP_CMD_DI_FMT_DIRECT;
data_in = iob_put ( iobuf, sizeof ( *data_in ) );
data_in->address =
- cpu_to_be64 ( user_to_phys ( srp->command->data_in, 0 ) );
- data_in->handle = ntohl ( srp->memory_handle );
- data_in->len = ntohl ( srp->command->data_in_len );
+ cpu_to_be64 ( user_to_phys ( command->data_in, 0 ) );
+ data_in->handle = ntohl ( srpdev->memory_handle );
+ data_in->len = ntohl ( command->data_in_len );
}
- DBGC2 ( srp, "SRP %p TX SCSI command tag %08x%08x\n", srp,
- ntohl ( cmd->tag.dwords[0] ), ntohl ( cmd->tag.dwords[1] ) );
- DBGC2_HDA ( srp, 0, iobuf->data, iob_len ( iobuf ) );
+ DBGC2 ( srpdev, "SRP %p tag %08x CMD " SCSI_CDB_FORMAT "\n",
+ srpdev, tag, SCSI_CDB_DATA ( cmd->cdb ) );
/* Send IU */
- if ( ( rc = xfer_deliver_iob ( &srp->socket, iobuf ) ) != 0 ) {
- DBGC ( srp, "SRP %p could not send command: %s\n",
- srp, strerror ( rc ) );
- goto err;
+ if ( ( rc = xfer_deliver_iob ( &srpdev->socket, iobuf ) ) != 0 ) {
+ DBGC ( srpdev, "SRP %p tag %08x could not send CMD: %s\n",
+ srpdev, tag, strerror ( rc ) );
+ return rc;
}
- return;
-
- err:
- srp_fail ( srp, rc );
+ return 0;
}
/**
- * Handle SRP SCSI response
+ * Receive SRP SCSI response
*
- * @v srp SRP device
- * @v iobuf I/O buffer
+ * @v srpdev SRP device
+ * @v data SRP IU
+ * @v len Length of SRP IU
* @ret rc Returns status code
*/
-static int srp_rsp ( struct srp_device *srp, struct io_buffer *iobuf ) {
- struct srp_rsp *rsp = iobuf->data;
- int rc;
-
- DBGC2 ( srp, "SRP %p RX SCSI response tag %08x%08x\n", srp,
- ntohl ( rsp->tag.dwords[0] ), ntohl ( rsp->tag.dwords[1] ) );
+static int srp_rsp ( struct srp_device *srpdev,
+ const void *data, size_t len ) {
+ const struct srp_rsp *rsp = data;
+ struct srp_command *srpcmd;
+ struct scsi_rsp response;
+ const void *sense;
+ ssize_t data_out_residual_count;
+ ssize_t data_in_residual_count;
/* Sanity check */
- if ( iob_len ( iobuf ) < sizeof ( *rsp ) ) {
- DBGC ( srp, "SRP %p RX SCSI response too short (%zd bytes)\n",
- srp, iob_len ( iobuf ) );
- rc = -EINVAL;
- goto out;
+ if ( len < sizeof ( *rsp ) ) {
+ DBGC ( srpdev, "SRP %p RSP too short (%zd bytes)\n",
+ srpdev, len );
+ return -EINVAL;
}
-
- /* Report SCSI errors */
- if ( rsp->status != 0 ) {
- DBGC ( srp, "SRP %p response status %02x\n",
- srp, rsp->status );
- if ( srp_rsp_sense_data ( rsp ) ) {
- DBGC ( srp, "SRP %p sense data:\n", srp );
- DBGC_HDA ( srp, 0, srp_rsp_sense_data ( rsp ),
- srp_rsp_sense_data_len ( rsp ) );
- }
+ DBGC2 ( srpdev, "SRP %p tag %08x RSP stat %02x dores %08x dires "
+ "%08x valid %02x%s%s%s%s%s%s\n",
+ srpdev, ntohl ( rsp->tag.dwords[1] ), rsp->status,
+ ntohl ( rsp->data_out_residual_count ),
+ ntohl ( rsp->data_in_residual_count ), rsp->valid,
+ ( ( rsp->valid & SRP_RSP_VALID_DIUNDER ) ? " diunder" : "" ),
+ ( ( rsp->valid & SRP_RSP_VALID_DIOVER ) ? " diover" : "" ),
+ ( ( rsp->valid & SRP_RSP_VALID_DOUNDER ) ? " dounder" : "" ),
+ ( ( rsp->valid & SRP_RSP_VALID_DOOVER ) ? " doover" : "" ),
+ ( ( rsp->valid & SRP_RSP_VALID_SNSVALID ) ? " sns" : "" ),
+ ( ( rsp->valid & SRP_RSP_VALID_RSPVALID ) ? " rsp" : "" ) );
+
+ /* Identify command by tag */
+ srpcmd = srp_find_tag ( srpdev, ntohl ( rsp->tag.dwords[1] ) );
+ if ( ! srpcmd ) {
+ DBGC ( srpdev, "SRP %p tag %08x unrecognised RSP\n",
+ srpdev, ntohl ( rsp->tag.dwords[1] ) );
+ return -ENOENT;
}
- if ( rsp->valid & ( SRP_RSP_VALID_DOUNDER | SRP_RSP_VALID_DOOVER ) ) {
- DBGC ( srp, "SRP %p response data-out %srun by %#x bytes\n",
- srp, ( ( rsp->valid & SRP_RSP_VALID_DOUNDER )
- ? "under" : "over" ),
- ntohl ( rsp->data_out_residual_count ) );
- }
- if ( rsp->valid & ( SRP_RSP_VALID_DIUNDER | SRP_RSP_VALID_DIOVER ) ) {
- DBGC ( srp, "SRP %p response data-in %srun by %#x bytes\n",
- srp, ( ( rsp->valid & SRP_RSP_VALID_DIUNDER )
- ? "under" : "over" ),
- ntohl ( rsp->data_in_residual_count ) );
+
+ /* Hold command reference for remainder of function */
+ srpcmd_get ( srpcmd );
+
+ /* Build SCSI response */
+ memset ( &response, 0, sizeof ( response ) );
+ response.status = rsp->status;
+ data_out_residual_count = ntohl ( rsp->data_out_residual_count );
+ data_in_residual_count = ntohl ( rsp->data_in_residual_count );
+ if ( rsp->valid & SRP_RSP_VALID_DOOVER ) {
+ response.overrun = data_out_residual_count;
+ } else if ( rsp->valid & SRP_RSP_VALID_DOUNDER ) {
+ response.overrun = -(data_out_residual_count);
+ } else if ( rsp->valid & SRP_RSP_VALID_DIOVER ) {
+ response.overrun = data_in_residual_count;
+ } else if ( rsp->valid & SRP_RSP_VALID_DIUNDER ) {
+ response.overrun = -(data_in_residual_count);
}
- srp->command->status = rsp->status;
+ sense = srp_rsp_sense_data ( rsp );
+ if ( sense )
+ memcpy ( &response.sense, sense, sizeof ( response.sense ) );
- /* Mark SCSI command as complete */
- srp_scsi_done ( srp, 0 );
+ /* Report SCSI response */
+ scsi_response ( &srpcmd->scsi, &response );
- rc = 0;
- out:
- free_iob ( iobuf );
- return rc;
+ /* Close SCSI command */
+ srpcmd_close ( srpcmd, 0 );
+
+ /* Drop temporary command reference */
+ srpcmd_put ( srpcmd );
+
+ return 0;
}
/**
- * Handle SRP unrecognised response
+ * Receive SRP unrecognised response IU
*
- * @v srp SRP device
- * @v iobuf I/O buffer
+ * @v srpdev SRP device
+ * @v data SRP IU
+ * @v len Length of SRP IU
* @ret rc Returns status code
*/
-static int srp_unrecognised ( struct srp_device *srp,
- struct io_buffer *iobuf ) {
- struct srp_common *common = iobuf->data;
+static int srp_unrecognised ( struct srp_device *srpdev,
+ const void *data, size_t len ) {
+ const struct srp_common *common = data;
- DBGC ( srp, "SRP %p RX unrecognised IU tag %08x%08x type %02x\n",
- srp, ntohl ( common->tag.dwords[0] ),
- ntohl ( common->tag.dwords[1] ), common->type );
+ DBGC ( srpdev, "SRP %p tag %08x unrecognised IU type %02x:\n",
+ srpdev, ntohl ( common->tag.dwords[1] ), common->type );
+ DBGC_HDA ( srpdev, 0, data, len );
- free_iob ( iobuf );
return -ENOTSUP;
}
+/** SRP command SCSI interface operations */
+static struct interface_operation srpcmd_scsi_op[] = {
+ INTF_OP ( intf_close, struct srp_command *, srpcmd_close ),
+};
+
+/** SRP command SCSI interface descriptor */
+static struct interface_descriptor srpcmd_scsi_desc =
+ INTF_DESC ( struct srp_command, scsi, srpcmd_scsi_op );
+
+/**
+ * Issue SRP SCSI command
+ *
+ * @v srpdev SRP device
+ * @v parent Parent interface
+ * @v command SCSI command
+ * @ret tag Command tag, or negative error
+ */
+static int srpdev_scsi_command ( struct srp_device *srpdev,
+ struct interface *parent,
+ struct scsi_cmd *command ) {
+ struct srp_command *srpcmd;
+ int tag;
+ int rc;
+
+ /* Allocate command tag */
+ tag = srp_new_tag ( srpdev );
+ if ( tag < 0 ) {
+ rc = tag;
+ goto err_tag;
+ }
+
+ /* Allocate and initialise structure */
+ srpcmd = zalloc ( sizeof ( *srpcmd ) );
+ if ( ! srpcmd ) {
+ rc = -ENOMEM;
+ goto err_zalloc;
+ }
+ ref_init ( &srpcmd->refcnt, srpcmd_free );
+ intf_init ( &srpcmd->scsi, &srpcmd_scsi_desc, &srpcmd->refcnt );
+ srpcmd->srpdev = srpdev_get ( srpdev );
+ list_add ( &srpcmd->list, &srpdev->commands );
+ srpcmd->tag = tag;
+
+ /* Send command IU */
+ if ( ( rc = srp_cmd ( srpdev, command, srpcmd->tag ) ) != 0 )
+ goto err_cmd;
+
+ /* Attach to parent interface, leave reference with command
+ * list, and return.
+ */
+ intf_plug_plug ( &srpcmd->scsi, parent );
+ return srpcmd->tag;
+
+ err_cmd:
+ srpcmd_close ( srpcmd, rc );
+ err_zalloc:
+ err_tag:
+ return rc;
+}
+
/**
- * Receive data from underlying socket
+ * Receive data from SRP socket
*
- * @v srp SRP device
+ * @v srpdev SRP device
* @v iobuf Datagram I/O buffer
* @v meta Data transfer metadata
* @ret rc Return status code
*/
-static int srp_xfer_deliver ( struct srp_device *srp,
- struct io_buffer *iobuf,
- struct xfer_metadata *meta __unused ) {
+static int srpdev_deliver ( struct srp_device *srpdev,
+ struct io_buffer *iobuf,
+ struct xfer_metadata *meta __unused ) {
struct srp_common *common = iobuf->data;
- int ( * type ) ( struct srp_device *srp, struct io_buffer *iobuf );
+ int ( * type ) ( struct srp_device *srp, const void *data, size_t len );
int rc;
+ /* Sanity check */
+ if ( iob_len ( iobuf ) < sizeof ( *common ) ) {
+ DBGC ( srpdev, "SRP %p IU too short (%zd bytes)\n",
+ srpdev, iob_len ( iobuf ) );
+ rc = -EINVAL;
+ goto err;
+ }
+
/* Determine IU type */
switch ( common->type ) {
case SRP_LOGIN_RSP:
@@ -393,114 +613,170 @@ static int srp_xfer_deliver ( struct srp_device *srp,
}
/* Handle IU */
- if ( ( rc = type ( srp, iobuf ) ) != 0 )
+ if ( ( rc = type ( srpdev, iobuf->data, iob_len ( iobuf ) ) ) != 0 )
goto err;
+ free_iob ( iobuf );
return 0;
err:
- srp_fail ( srp, rc );
+ DBGC ( srpdev, "SRP %p closing due to received IU (%s):\n",
+ srpdev, strerror ( rc ) );
+ DBGC_HDA ( srpdev, 0, iobuf->data, iob_len ( iobuf ) );
+ free_iob ( iobuf );
+ srpdev_close ( srpdev, rc );
return rc;
}
-/** SRP data transfer interface operations */
-static struct interface_operation srp_xfer_operations[] = {
- INTF_OP ( xfer_deliver, struct srp_device *, srp_xfer_deliver ),
- INTF_OP ( intf_close, struct srp_device *, srp_fail ),
-};
+/**
+ * Check SRP device flow-control window
+ *
+ * @v srpdev SRP device
+ * @ret len Length of window
+ */
+static size_t srpdev_window ( struct srp_device *srpdev ) {
+ return ( srpdev->logged_in ? ~( ( size_t ) 0 ) : 0 );
+}
-/** SRP data transfer interface descriptor */
-static struct interface_descriptor srp_xfer_desc =
- INTF_DESC ( struct srp_device, socket, srp_xfer_operations );
+/**
+ * A (transport-independent) sBFT created by iPXE
+ */
+struct ipxe_sbft {
+ /** The table header */
+ struct sbft_table table;
+ /** The SCSI subtable */
+ struct sbft_scsi_subtable scsi;
+ /** The SRP subtable */
+ struct sbft_srp_subtable srp;
+} __attribute__ (( packed, aligned ( 16 ) ));
/**
- * Issue SCSI command via SRP
+ * Describe SRP device in an ACPI table
*
- * @v scsi SCSI device
- * @v command SCSI command
+ * @v srpdev SRP device
+ * @v acpi ACPI table
+ * @v len Length of ACPI table
* @ret rc Return status code
*/
-static int srp_command ( struct scsi_device *scsi,
- struct scsi_command *command ) {
- struct srp_device *srp =
- container_of ( scsi->backend, struct srp_device, refcnt );
-
- /* Store SCSI command */
- if ( srp->command ) {
- DBGC ( srp, "SRP %p cannot handle concurrent SCSI commands\n",
- srp );
- return -EBUSY;
- }
- srp->command = command;
-
- /* Log in or issue command as appropriate */
- if ( ! ( srp->state & SRP_STATE_SOCKET_OPEN ) ) {
- srp_login ( srp );
- } else if ( srp->state & SRP_STATE_LOGGED_IN ) {
- srp_cmd ( srp );
- } else {
- /* Still waiting for login; do nothing */
+static int srpdev_describe ( struct srp_device *srpdev,
+ struct acpi_description_header *acpi,
+ size_t len ) {
+ struct ipxe_sbft *sbft =
+ container_of ( acpi, struct ipxe_sbft, table.acpi );
+ int rc;
+
+ /* Sanity check */
+ if ( len < sizeof ( *sbft ) )
+ return -ENOBUFS;
+
+ /* Populate table */
+ sbft->table.acpi.signature = cpu_to_le32 ( SBFT_SIG );
+ sbft->table.acpi.length = cpu_to_le32 ( sizeof ( *sbft ) );
+ sbft->table.acpi.revision = 1;
+ sbft->table.scsi_offset =
+ cpu_to_le16 ( offsetof ( typeof ( *sbft ), scsi ) );
+ memcpy ( &sbft->scsi.lun, &srpdev->lun, sizeof ( sbft->scsi.lun ) );
+ sbft->table.srp_offset =
+ cpu_to_le16 ( offsetof ( typeof ( *sbft ), srp ) );
+ memcpy ( &sbft->srp.initiator, &srpdev->initiator,
+ sizeof ( sbft->srp.initiator ) );
+ memcpy ( &sbft->srp.target, &srpdev->target,
+ sizeof ( sbft->srp.target ) );
+
+ /* Ask transport layer to describe transport-specific portions */
+ if ( ( rc = acpi_describe ( &srpdev->socket, acpi, len ) ) != 0 ) {
+ DBGC ( srpdev, "SRP %p cannot describe transport layer: %s\n",
+ srpdev, strerror ( rc ) );
+ return rc;
}
return 0;
}
+/** SRP device socket interface operations */
+static struct interface_operation srpdev_socket_op[] = {
+ INTF_OP ( xfer_deliver, struct srp_device *, srpdev_deliver ),
+ INTF_OP ( intf_close, struct srp_device *, srpdev_close ),
+};
+
+/** SRP device socket interface descriptor */
+static struct interface_descriptor srpdev_socket_desc =
+ INTF_DESC ( struct srp_device, socket, srpdev_socket_op );
+
+/** SRP device SCSI interface operations */
+static struct interface_operation srpdev_scsi_op[] = {
+ INTF_OP ( scsi_command, struct srp_device *, srpdev_scsi_command ),
+ INTF_OP ( xfer_window, struct srp_device *, srpdev_window ),
+ INTF_OP ( intf_close, struct srp_device *, srpdev_close ),
+ INTF_OP ( acpi_describe, struct srp_device *, srpdev_describe ),
+};
+
+/** SRP device SCSI interface descriptor */
+static struct interface_descriptor srpdev_scsi_desc =
+ INTF_DESC ( struct srp_device, scsi, srpdev_scsi_op );
+
/**
- * Attach SRP device
+ * Open SRP device
*
- * @v scsi SCSI device
- * @v root_path Root path
+ * @v block Block control interface
+ * @v socket Socket interface
+ * @v initiator Initiator port ID
+ * @v target Target port ID
+ * @v memory_handle RDMA memory handle
+ * @v lun SCSI LUN
+ * @ret rc Return status code
*/
-int srp_attach ( struct scsi_device *scsi, const char *root_path ) {
- struct srp_transport_type *transport;
- struct srp_device *srp;
+int srp_open ( struct interface *block, struct interface *socket,
+ union srp_port_id *initiator, union srp_port_id *target,
+ uint32_t memory_handle, struct scsi_lun *lun ) {
+ struct srp_device *srpdev;
+ int tag;
int rc;
- /* Hard-code an IB SRP back-end for now */
- transport = &ib_srp_transport;
-
/* Allocate and initialise structure */
- srp = zalloc ( sizeof ( *srp ) + transport->priv_len );
- if ( ! srp ) {
+ srpdev = zalloc ( sizeof ( *srpdev ) );
+ if ( ! srpdev ) {
rc = -ENOMEM;
- goto err_alloc;
+ goto err_zalloc;
}
- ref_init ( &srp->refcnt, NULL );
- intf_init ( &srp->socket, &srp_xfer_desc, &srp->refcnt );
- srp->transport = transport;
- DBGC ( srp, "SRP %p using %s\n", srp, root_path );
-
- /* Parse root path */
- if ( ( rc = transport->parse_root_path ( srp, root_path ) ) != 0 ) {
- DBGC ( srp, "SRP %p could not parse root path: %s\n",
- srp, strerror ( rc ) );
- goto err_parse_root_path;
+ ref_init ( &srpdev->refcnt, NULL );
+ intf_init ( &srpdev->scsi, &srpdev_scsi_desc, &srpdev->refcnt );
+ intf_init ( &srpdev->socket, &srpdev_socket_desc, &srpdev->refcnt );
+ INIT_LIST_HEAD ( &srpdev->commands );
+ srpdev->memory_handle = memory_handle;
+ DBGC ( srpdev, "SRP %p %08x%08x%08x%08x->%08x%08x%08x%08x\n", srpdev,
+ ntohl ( initiator->dwords[0] ), ntohl ( initiator->dwords[1] ),
+ ntohl ( initiator->dwords[2] ), ntohl ( initiator->dwords[3] ),
+ ntohl ( target->dwords[0] ), ntohl ( target->dwords[1] ),
+ ntohl ( target->dwords[2] ), ntohl ( target->dwords[3] ) );
+
+ /* Preserve parameters required for boot firmware table */
+ memcpy ( &srpdev->initiator, initiator, sizeof ( srpdev->initiator ) );
+ memcpy ( &srpdev->target, target, sizeof ( srpdev->target ) );
+ memcpy ( &srpdev->lun, lun, sizeof ( srpdev->lun ) );
+
+ /* Attach to socket interface and initiate login */
+ intf_plug_plug ( &srpdev->socket, socket );
+ tag = srp_new_tag ( srpdev );
+ assert ( tag >= 0 ); /* Cannot fail when no commands in progress */
+ if ( ( rc = srp_login ( srpdev, initiator, target, tag ) ) != 0 )
+ goto err_login;
+
+ /* Attach SCSI device to parent interface */
+ if ( ( rc = scsi_open ( block, &srpdev->scsi, lun ) ) != 0 ) {
+ DBGC ( srpdev, "SRP %p could not create SCSI device: %s\n",
+ srpdev, strerror ( rc ) );
+ goto err_scsi_open;
}
- /* Attach parent interface, mortalise self, and return */
- scsi->backend = ref_get ( &srp->refcnt );
- scsi->command = srp_command;
- ref_put ( &srp->refcnt );
+ /* Mortalise self and return */
+ ref_put ( &srpdev->refcnt );
return 0;
- err_parse_root_path:
- ref_put ( &srp->refcnt );
- err_alloc:
+ err_scsi_open:
+ err_login:
+ srpdev_close ( srpdev, rc );
+ ref_put ( &srpdev->refcnt );
+ err_zalloc:
return rc;
}
-
-/**
- * Detach SRP device
- *
- * @v scsi SCSI device
- */
-void srp_detach ( struct scsi_device *scsi ) {
- struct srp_device *srp =
- container_of ( scsi->backend, struct srp_device, refcnt );
-
- /* Close socket */
- intf_shutdown ( &srp->socket, 0 );
- scsi->command = scsi_detached_command;
- ref_put ( scsi->backend );
- scsi->backend = NULL;
-}