diff options
Diffstat (limited to 'src/drivers')
| -rw-r--r-- | src/drivers/block/ata.c | 683 | ||||
| -rw-r--r-- | src/drivers/block/ibft.c | 471 | ||||
| -rw-r--r-- | src/drivers/block/ramdisk.c | 97 | ||||
| -rw-r--r-- | src/drivers/block/scsi.c | 878 | ||||
| -rw-r--r-- | src/drivers/block/srp.c | 830 |
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; -} |
