diff options
Diffstat (limited to 'qemu/roms/ipxe/src/drivers/block/scsi.c')
-rw-r--r-- | qemu/roms/ipxe/src/drivers/block/scsi.c | 999 |
1 files changed, 999 insertions, 0 deletions
diff --git a/qemu/roms/ipxe/src/drivers/block/scsi.c b/qemu/roms/ipxe/src/drivers/block/scsi.c new file mode 100644 index 000000000..64d692986 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/block/scsi.c @@ -0,0 +1,999 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <byteswap.h> +#include <errno.h> +#include <ipxe/list.h> +#include <ipxe/process.h> +#include <ipxe/xfer.h> +#include <ipxe/blockdev.h> +#include <ipxe/scsi.h> + +/** @file + * + * SCSI block device + * + */ + +/** Maximum number of command retries */ +#define SCSICMD_MAX_RETRIES 10 + +/* Error numbers generated by SCSI sense data */ +#define EIO_NO_SENSE __einfo_error ( EINFO_EIO_NO_SENSE ) +#define EINFO_EIO_NO_SENSE \ + __einfo_uniqify ( EINFO_EIO, 0x00, "No sense" ) +#define EIO_RECOVERED_ERROR __einfo_error ( EINFO_EIO_RECOVERED_ERROR ) +#define EINFO_EIO_RECOVERED_ERROR \ + __einfo_uniqify ( EINFO_EIO, 0x01, "Recovered error" ) +#define EIO_NOT_READY __einfo_error ( EINFO_EIO_NOT_READY ) +#define EINFO_EIO_NOT_READY \ + __einfo_uniqify ( EINFO_EIO, 0x02, "Not ready" ) +#define EIO_MEDIUM_ERROR __einfo_error ( EINFO_EIO_MEDIUM_ERROR ) +#define EINFO_EIO_MEDIUM_ERROR \ + __einfo_uniqify ( EINFO_EIO, 0x03, "Medium error" ) +#define EIO_HARDWARE_ERROR __einfo_error ( EINFO_EIO_HARDWARE_ERROR ) +#define EINFO_EIO_HARDWARE_ERROR \ + __einfo_uniqify ( EINFO_EIO, 0x04, "Hardware error" ) +#define EIO_ILLEGAL_REQUEST __einfo_error ( EINFO_EIO_ILLEGAL_REQUEST ) +#define EINFO_EIO_ILLEGAL_REQUEST \ + __einfo_uniqify ( EINFO_EIO, 0x05, "Illegal request" ) +#define EIO_UNIT_ATTENTION __einfo_error ( EINFO_EIO_UNIT_ATTENTION ) +#define EINFO_EIO_UNIT_ATTENTION \ + __einfo_uniqify ( EINFO_EIO, 0x06, "Unit attention" ) +#define EIO_DATA_PROTECT __einfo_error ( EINFO_EIO_DATA_PROTECT ) +#define EINFO_EIO_DATA_PROTECT \ + __einfo_uniqify ( EINFO_EIO, 0x07, "Data protect" ) +#define EIO_BLANK_CHECK __einfo_error ( EINFO_EIO_BLANK_CHECK ) +#define EINFO_EIO_BLANK_CHECK \ + __einfo_uniqify ( EINFO_EIO, 0x08, "Blank check" ) +#define EIO_VENDOR_SPECIFIC __einfo_error ( EINFO_EIO_VENDOR_SPECIFIC ) +#define EINFO_EIO_VENDOR_SPECIFIC \ + __einfo_uniqify ( EINFO_EIO, 0x09, "Vendor specific" ) +#define EIO_COPY_ABORTED __einfo_error ( EINFO_EIO_COPY_ABORTED ) +#define EINFO_EIO_COPY_ABORTED \ + __einfo_uniqify ( EINFO_EIO, 0x0a, "Copy aborted" ) +#define EIO_ABORTED_COMMAND __einfo_error ( EINFO_EIO_ABORTED_COMMAND ) +#define EINFO_EIO_ABORTED_COMMAND \ + __einfo_uniqify ( EINFO_EIO, 0x0b, "Aborted command" ) +#define EIO_RESERVED __einfo_error ( EINFO_EIO_RESERVED ) +#define EINFO_EIO_RESERVED \ + __einfo_uniqify ( EINFO_EIO, 0x0c, "Reserved" ) +#define EIO_VOLUME_OVERFLOW __einfo_error ( EINFO_EIO_VOLUME_OVERFLOW ) +#define EINFO_EIO_VOLUME_OVERFLOW \ + __einfo_uniqify ( EINFO_EIO, 0x0d, "Volume overflow" ) +#define EIO_MISCOMPARE __einfo_error ( EINFO_EIO_MISCOMPARE ) +#define EINFO_EIO_MISCOMPARE \ + __einfo_uniqify ( EINFO_EIO, 0x0e, "Miscompare" ) +#define EIO_COMPLETED __einfo_error ( EINFO_EIO_COMPLETED ) +#define EINFO_EIO_COMPLETED \ + __einfo_uniqify ( EINFO_EIO, 0x0f, "Completed" ) +#define EIO_SENSE( key ) \ + EUNIQ ( EINFO_EIO, (key), EIO_NO_SENSE, EIO_RECOVERED_ERROR, \ + EIO_NOT_READY, EIO_MEDIUM_ERROR, EIO_HARDWARE_ERROR, \ + EIO_ILLEGAL_REQUEST, EIO_UNIT_ATTENTION, \ + EIO_DATA_PROTECT, EIO_BLANK_CHECK, EIO_VENDOR_SPECIFIC, \ + EIO_COPY_ABORTED, EIO_ABORTED_COMMAND, EIO_RESERVED, \ + EIO_VOLUME_OVERFLOW, EIO_MISCOMPARE, EIO_COMPLETED ) + +/****************************************************************************** + * + * Utility functions + * + ****************************************************************************** + */ + +/** + * Parse SCSI LUN + * + * @v lun_string LUN string representation + * @v lun LUN to fill in + * @ret rc Return status code + */ +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; +} + +/** + * Parse SCSI sense data + * + * @v data Raw sense data + * @v len Length of raw sense data + * @v sense Descriptor-format sense data to fill in + */ +void scsi_parse_sense ( const void *data, size_t len, + struct scsi_sns_descriptor *sense ) { + const union scsi_sns *sns = data; + + /* Avoid returning uninitialised data */ + memset ( sense, 0, sizeof ( *sense ) ); + + /* Copy, assuming descriptor-format data */ + if ( len < sizeof ( sns->desc ) ) + return; + memcpy ( sense, &sns->desc, sizeof ( *sense ) ); + + /* Convert fixed-format to descriptor-format, if applicable */ + if ( len < sizeof ( sns->fixed ) ) + return; + if ( ! SCSI_SENSE_FIXED ( sns->code ) ) + return; + sense->additional = sns->fixed.additional; +} + +/****************************************************************************** + * + * Interface methods + * + ****************************************************************************** + */ + +/** + * Issue SCSI command + * + * @v control SCSI control interface + * @v data SCSI data interface + * @v command SCSI command + * @ret tag Command tag, or negative error + */ +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; + } + + intf_put ( dest ); + return tap; +} + +/** + * 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 */ + } + + intf_put ( dest ); +} + +/****************************************************************************** + * + * SCSI devices and commands + * + ****************************************************************************** + */ + +/** A SCSI device */ +struct scsi_device { + /** Reference count */ + struct refcnt refcnt; + /** Block control interface */ + struct interface block; + /** SCSI control interface */ + struct interface scsi; + + /** SCSI LUN */ + struct scsi_lun lun; + /** Flags */ + unsigned int flags; + + /** TEST UNIT READY interface */ + struct interface ready; + /** TEST UNIT READY process */ + struct process process; + + /** List of commands */ + struct list_head cmds; +}; + +/** SCSI device flags */ +enum scsi_device_flags { + /** TEST UNIT READY has been issued */ + SCSIDEV_UNIT_TESTED = 0x0001, + /** TEST UNIT READY has completed successfully */ + SCSIDEV_UNIT_READY = 0x0002, +}; + +/** 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; +} + +/** + * Drop reference to SCSI device + * + * @v scsidev SCSI device + */ +static inline __attribute__ (( always_inline )) void +scsidev_put ( struct scsi_device *scsidev ) { + ref_put ( &scsidev->refcnt ); +} + +/** + * 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; +} + +/** + * Drop reference to SCSI command + * + * @v scsicmd SCSI command + */ +static inline __attribute__ (( always_inline )) void +scsicmd_put ( struct scsi_command *scsicmd ) { + ref_put ( &scsicmd->refcnt ); +} + +/** + * 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; +} + +/** + * Free SCSI command + * + * @v refcnt Reference count + */ +static void scsicmd_free ( struct refcnt *refcnt ) { + struct scsi_command *scsicmd = + container_of ( refcnt, struct scsi_command, refcnt ); + + /* Remove from list of commands */ + list_del ( &scsicmd->list ); + scsidev_put ( scsicmd->scsidev ); + + /* Free command */ + free ( scsicmd ); +} + +/** + * Close SCSI command + * + * @v scsicmd SCSI command + * @v rc Reason for close + */ +static void scsicmd_close ( struct scsi_command *scsicmd, int rc ) { + struct scsi_device *scsidev = scsicmd->scsidev; + + 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 ); +} + +/** + * Construct and issue SCSI command + * + * @ret rc Return status code + */ +static int scsicmd_command ( struct scsi_command *scsicmd ) { + struct scsi_device *scsidev = scsicmd->scsidev; + struct scsi_cmd command; + int tag; + int rc; + + /* Construct command */ + memset ( &command, 0, sizeof ( command ) ); + 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; + } + + /* 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; +} + +/** + * Handle SCSI command completion + * + * @v scsicmd SCSI command + * @v rc Reason for close + */ +static void scsicmd_done ( struct scsi_command *scsicmd, int rc ) { + struct scsi_device *scsidev = scsicmd->scsidev; + + /* Restart SCSI interface */ + intf_restart ( &scsicmd->scsi, 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; + } + + /* If we didn't (successfully) reissue the command, hand over + * to the command completion handler. + */ + scsicmd->type->done ( scsicmd, rc ); +} + +/** + * 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; + int rc; + + 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 key %02x additional %04x\n", + ( response->sense.code & SCSI_SENSE_CODE_MASK ), + ( response->sense.key & SCSI_SENSE_KEY_MASK ), + ntohs ( response->sense.additional ) ); + + /* Construct error number from sense data */ + rc = -EIO_SENSE ( response->sense.key & SCSI_SENSE_KEY_MASK ); + scsicmd_done ( scsicmd, rc ); + } +} + +/** + * 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, +}; + +/** + * Construct SCSI WRITE command + * + * @v scsicmd SCSI command + * @v command SCSI command IU + */ +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; +} + +/** 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 ); + } +} + +/** + * 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; + } + + /* 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, +}; + +/** + * Construct SCSI TEST UNIT READY command + * + * @v scsicmd SCSI command + * @v command SCSI command IU + */ +static void scsicmd_test_unit_ready_cmd ( struct scsi_command *scsicmd __unused, + struct scsi_cmd *command ) { + struct scsi_cdb_test_unit_ready *testready = &command->cdb.testready; + + testready->opcode = SCSI_OPCODE_TEST_UNIT_READY; +} + +/** SCSI TEST UNIT READY command type */ +static struct scsi_command_type scsicmd_test_unit_ready = { + .name = "TEST UNIT READY", + .cmd = scsicmd_test_unit_ready_cmd, + .done = scsicmd_close, +}; + +/** 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 ), +}; + +/** 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; +} + +/** + * Issue SCSI block read + * + * @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_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 ); +} + +/** + * 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 ); +} + +/** + * Test to see if SCSI device is ready + * + * @v scsidev SCSI device + * @v block Block data interface + * @ret rc Return status code + */ +static int scsidev_test_unit_ready ( struct scsi_device *scsidev, + struct interface *block ) { + return scsidev_command ( scsidev, block, &scsicmd_test_unit_ready, + 0, 0, UNULL, 0 ); +} + +/** + * Check SCSI device flow-control window + * + * @v scsidev SCSI device + * @ret len Length of window + */ +static size_t scsidev_window ( struct scsi_device *scsidev ) { + + /* Refuse commands until unit is confirmed ready */ + if ( ! ( scsidev->flags & SCSIDEV_UNIT_READY ) ) + return 0; + + return xfer_window ( &scsidev->scsi ); +} + +/** + * 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; + + /* Stop process */ + process_del ( &scsidev->process ); + + /* Shut down interfaces */ + intf_shutdown ( &scsidev->block, rc ); + intf_shutdown ( &scsidev->scsi, rc ); + intf_shutdown ( &scsidev->ready, 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 ( xfer_window, struct scsi_device *, scsidev_window ), + 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 ); + +/** + * Handle SCSI TEST UNIT READY response + * + * @v scsidev SCSI device + * @v rc Reason for close + */ +static void scsidev_ready ( struct scsi_device *scsidev, int rc ) { + + /* Shut down interface */ + intf_shutdown ( &scsidev->ready, rc ); + + /* Close device on failure */ + if ( rc != 0 ) { + DBGC ( scsidev, "SCSI %p not ready: %s\n", + scsidev, strerror ( rc ) ); + scsidev_close ( scsidev, rc ); + return; + } + + /* Mark device as ready */ + scsidev->flags |= SCSIDEV_UNIT_READY; + xfer_window_changed ( &scsidev->block ); + DBGC ( scsidev, "SCSI %p unit is ready\n", scsidev ); +} + +/** SCSI device TEST UNIT READY interface operations */ +static struct interface_operation scsidev_ready_op[] = { + INTF_OP ( intf_close, struct scsi_device *, scsidev_ready ), +}; + +/** SCSI device TEST UNIT READY interface descriptor */ +static struct interface_descriptor scsidev_ready_desc = + INTF_DESC ( struct scsi_device, ready, scsidev_ready_op ); + +/** + * SCSI TEST UNIT READY process + * + * @v scsidev SCSI device + */ +static void scsidev_step ( struct scsi_device *scsidev ) { + int rc; + + /* Do nothing if we have already issued TEST UNIT READY */ + if ( scsidev->flags & SCSIDEV_UNIT_TESTED ) + return; + + /* Wait until underlying SCSI device is ready */ + if ( xfer_window ( &scsidev->scsi ) == 0 ) + return; + + DBGC ( scsidev, "SCSI %p waiting for unit to become ready\n", + scsidev ); + + /* Mark TEST UNIT READY as sent */ + scsidev->flags |= SCSIDEV_UNIT_TESTED; + + /* Issue TEST UNIT READY command */ + if ( ( rc = scsidev_test_unit_ready ( scsidev, &scsidev->ready )) !=0){ + scsidev_close ( scsidev, rc ); + return; + } +} + +/** SCSI device SCSI interface operations */ +static struct interface_operation scsidev_scsi_op[] = { + INTF_OP ( xfer_window_changed, struct scsi_device *, scsidev_step ), + 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 ); + +/** SCSI device process descriptor */ +static struct process_descriptor scsidev_process_desc = + PROC_DESC_ONCE ( struct scsi_device, process, scsidev_step ); + +/** + * 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 ); + intf_init ( &scsidev->ready, &scsidev_ready_desc, &scsidev->refcnt ); + process_init ( &scsidev->process, &scsidev_process_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 interfaces, mortalise self, and return */ + intf_plug_plug ( &scsidev->scsi, scsi ); + intf_plug_plug ( &scsidev->block, block ); + ref_put ( &scsidev->refcnt ); + return 0; +} |