summaryrefslogtreecommitdiffstats
path: root/src/drivers/block/scsi.c
diff options
context:
space:
mode:
authorMichael Brown2010-09-03 17:11:51 +0200
committerMichael Brown2010-09-14 21:37:15 +0200
commit220495f8bf2222e1dc1aa7db554d23997b545546 (patch)
treef04aa0d123a0b643fc41e178db81cfe63ba913bf /src/drivers/block/scsi.c
parent[hermon] Use correct alignment for doorbell records (diff)
downloadipxe-220495f8bf2222e1dc1aa7db554d23997b545546.tar.gz
ipxe-220495f8bf2222e1dc1aa7db554d23997b545546.tar.xz
ipxe-220495f8bf2222e1dc1aa7db554d23997b545546.zip
[block] Replace gPXE block-device API with an iPXE asynchronous interface
The block device interface used in gPXE predates the invention of even the old gPXE data-transfer interface, let alone the current iPXE generic asynchronous interface mechanism. Bring this old code up to date, with the following benefits: o Block device commands can be cancelled by the requestor. The INT 13 layer uses this to provide a global timeout on all INT 13 calls, with the result that an unexpected passive failure mode (such as an iSCSI target ACKing the request but never sending a response) will lead to a timeout that gets reported back to the INT 13 user, rather than simply freezing the system. o INT 13,00 (reset drive) is now able to reset the underlying block device. INT 13 users, such as DOS, that use INT 13,00 as a method for error recovery now have a chance of recovering. o All block device commands are tagged, with a numerical tag that will show up in debugging output and in packet captures; this will allow easier interpretation of bug reports that include both sources of information. o The extremely ugly hacks used to generate the boot firmware tables have been eradicated and replaced with a generic acpi_describe() method (exploiting the ability of iPXE interfaces to pass through methods to an underlying interface). The ACPI tables are now built in a shared data block within .bss16, rather than each requiring dedicated space in .data16. o The architecture-independent concept of a SAN device has been exposed to the iPXE core through the sanboot API, which provides calls to hook, unhook, boot, and describe SAN devices. This allows for much more flexible usage patterns (such as hooking an empty SAN device and then running an OS installer via TFTP). Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/drivers/block/scsi.c')
-rw-r--r--src/drivers/block/scsi.c878
1 files changed, 641 insertions, 237 deletions
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;
}