diff options
Diffstat (limited to 'qemu/roms/ipxe/src/drivers/block')
-rw-r--r-- | qemu/roms/ipxe/src/drivers/block/ata.c | 679 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/block/ibft.c | 497 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/block/scsi.c | 999 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/block/srp.c | 829 |
4 files changed, 3004 insertions, 0 deletions
diff --git a/qemu/roms/ipxe/src/drivers/block/ata.c b/qemu/roms/ipxe/src/drivers/block/ata.c new file mode 100644 index 000000000..c9b87c20c --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/block/ata.c @@ -0,0 +1,679 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/list.h> +#include <ipxe/interface.h> +#include <ipxe/blockdev.h> +#include <ipxe/edd.h> +#include <ipxe/ata.h> + +/** @file + * + * ATA block device + * + */ + +/****************************************************************************** + * + * Interface methods + * + ****************************************************************************** + */ + +/** + * Issue ATA command + * + * @v control ATA control interface + * @v data ATA data interface + * @v command ATA command + * @ret tag Command tag, or negative error + */ +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; +} + +/** + * 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; + + for ( i = 0 ; i < ( sizeof ( identity->model ) / 2 ) ; i++ ) + buf.words[i] = bswap_16 ( identity->model[i] ); + + 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; + } + + /* 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 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 ); + +/** + * Create ATA command + * + * @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 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 = lba; + command.cb.count.native = count; + 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 = + ( 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; +} + +/** + * Issue ATA block read + * + * @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 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 ); +} + +/** + * Issue ATA block write + * + * @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 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 ); +} + +/** + * 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 ); + } +} + +/** + * Describe ATA device using EDD + * + * @v atadev ATA device + * @v type EDD interface type + * @v path EDD device path + * @ret rc Return status code + */ +static int atadev_edd_describe ( struct ata_device *atadev, + struct edd_interface_type *type, + union edd_device_path *path ) { + + type->type = cpu_to_le64 ( EDD_INTF_TYPE_ATA ); + path->ata.slave = ( ( atadev->device == ATA_DEV_SLAVE ) ? 0x01 : 0x00 ); + return 0; +} + +/** 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 ), + INTF_OP ( edd_describe, struct ata_device *, atadev_edd_describe ), +}; + +/** 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 ); + +/** + * Open 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 + */ +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/qemu/roms/ipxe/src/drivers/block/ibft.c b/qemu/roms/ipxe/src/drivers/block/ibft.c new file mode 100644 index 000000000..6aabd766a --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/block/ibft.c @@ -0,0 +1,497 @@ +/* + * 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/vlan.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 settings Parent settings block, or NULL + * @v ipaddr IP address field + * @v setting Configuration setting + * @v count Maximum number of IP addresses + */ +static void ibft_set_ipaddr_setting ( struct settings *settings, + struct ibft_ipaddr *ipaddr, + const struct setting *setting, + unsigned int count ) { + struct in_addr in[count]; + unsigned int i; + + fetch_ipv4_array_setting ( settings, setting, in, count ); + for ( i = 0 ; i < count ; i++ ) { + ibft_set_ipaddr ( &ipaddr[i], in[i] ); + } +} + +/** + * 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 settings Parent settings block, or NULL + * @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 settings *settings, + struct ibft_strings *strings, + struct ibft_string *string, + const struct setting *setting ) { + struct settings *origin; + struct setting fetched; + int len; + char *dest; + + len = fetch_setting ( settings, setting, &origin, &fetched, NULL, 0 ); + 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 ( origin, &fetched, 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; + struct settings *parent = netdev_settings ( netdev ); + struct settings *origin; + 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 ); + + /* Determine origin of IP address */ + fetch_setting ( parent, &ip_setting, &origin, NULL, NULL, 0 ); + nic->origin = ( ( origin == parent ) ? + IBFT_NIC_ORIGIN_MANUAL : IBFT_NIC_ORIGIN_DHCP ); + DBG ( "iBFT NIC origin = %d\n", nic->origin ); + + /* Extract values from configuration settings */ + ibft_set_ipaddr_setting ( parent, &nic->ip_address, &ip_setting, 1 ); + DBG ( "iBFT NIC IP = %s\n", ibft_ipaddr ( &nic->ip_address ) ); + ibft_set_ipaddr_setting ( parent, &nic->gateway, &gateway_setting, 1 ); + DBG ( "iBFT NIC gateway = %s\n", ibft_ipaddr ( &nic->gateway ) ); + ibft_set_ipaddr_setting ( NULL, &nic->dns[0], &dns_setting, + ( sizeof ( nic->dns ) / + sizeof ( nic->dns[0] ) ) ); + DBG ( "iBFT NIC DNS = %s", ibft_ipaddr ( &nic->dns[0] ) ); + DBG ( ", %s\n", ibft_ipaddr ( &nic->dns[1] ) ); + if ( ( rc = ibft_set_string_setting ( NULL, 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 ( parent, &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 */ + nic->vlan = cpu_to_le16 ( vlan_tag ( netdev ) ); + DBG ( "iBFT NIC VLAN = %02x\n", le16_to_cpu ( nic->vlan ) ); + 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 + * @v iscsi iSCSI session + * @ret rc Return status code + */ +static int ibft_fill_initiator ( struct ibft_initiator *initiator, + struct ibft_strings *strings, + struct iscsi_session *iscsi ) { + 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, + iscsi->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, + iscsi ) ) != 0 ) + return rc; + if ( ( rc = ibft_fill_target ( &ibft->target, &strings, + iscsi ) ) != 0 ) + return rc; + + return 0; +} diff --git a/qemu/roms/ipxe/src/drivers/block/scsi.c b/qemu/roms/ipxe/src/drivers/block/scsi.c new file mode 100644 index 000000000..64d692986 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/block/scsi.c @@ -0,0 +1,999 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <byteswap.h> +#include <errno.h> +#include <ipxe/list.h> +#include <ipxe/process.h> +#include <ipxe/xfer.h> +#include <ipxe/blockdev.h> +#include <ipxe/scsi.h> + +/** @file + * + * SCSI block device + * + */ + +/** Maximum number of command retries */ +#define SCSICMD_MAX_RETRIES 10 + +/* Error numbers generated by SCSI sense data */ +#define EIO_NO_SENSE __einfo_error ( EINFO_EIO_NO_SENSE ) +#define EINFO_EIO_NO_SENSE \ + __einfo_uniqify ( EINFO_EIO, 0x00, "No sense" ) +#define EIO_RECOVERED_ERROR __einfo_error ( EINFO_EIO_RECOVERED_ERROR ) +#define EINFO_EIO_RECOVERED_ERROR \ + __einfo_uniqify ( EINFO_EIO, 0x01, "Recovered error" ) +#define EIO_NOT_READY __einfo_error ( EINFO_EIO_NOT_READY ) +#define EINFO_EIO_NOT_READY \ + __einfo_uniqify ( EINFO_EIO, 0x02, "Not ready" ) +#define EIO_MEDIUM_ERROR __einfo_error ( EINFO_EIO_MEDIUM_ERROR ) +#define EINFO_EIO_MEDIUM_ERROR \ + __einfo_uniqify ( EINFO_EIO, 0x03, "Medium error" ) +#define EIO_HARDWARE_ERROR __einfo_error ( EINFO_EIO_HARDWARE_ERROR ) +#define EINFO_EIO_HARDWARE_ERROR \ + __einfo_uniqify ( EINFO_EIO, 0x04, "Hardware error" ) +#define EIO_ILLEGAL_REQUEST __einfo_error ( EINFO_EIO_ILLEGAL_REQUEST ) +#define EINFO_EIO_ILLEGAL_REQUEST \ + __einfo_uniqify ( EINFO_EIO, 0x05, "Illegal request" ) +#define EIO_UNIT_ATTENTION __einfo_error ( EINFO_EIO_UNIT_ATTENTION ) +#define EINFO_EIO_UNIT_ATTENTION \ + __einfo_uniqify ( EINFO_EIO, 0x06, "Unit attention" ) +#define EIO_DATA_PROTECT __einfo_error ( EINFO_EIO_DATA_PROTECT ) +#define EINFO_EIO_DATA_PROTECT \ + __einfo_uniqify ( EINFO_EIO, 0x07, "Data protect" ) +#define EIO_BLANK_CHECK __einfo_error ( EINFO_EIO_BLANK_CHECK ) +#define EINFO_EIO_BLANK_CHECK \ + __einfo_uniqify ( EINFO_EIO, 0x08, "Blank check" ) +#define EIO_VENDOR_SPECIFIC __einfo_error ( EINFO_EIO_VENDOR_SPECIFIC ) +#define EINFO_EIO_VENDOR_SPECIFIC \ + __einfo_uniqify ( EINFO_EIO, 0x09, "Vendor specific" ) +#define EIO_COPY_ABORTED __einfo_error ( EINFO_EIO_COPY_ABORTED ) +#define EINFO_EIO_COPY_ABORTED \ + __einfo_uniqify ( EINFO_EIO, 0x0a, "Copy aborted" ) +#define EIO_ABORTED_COMMAND __einfo_error ( EINFO_EIO_ABORTED_COMMAND ) +#define EINFO_EIO_ABORTED_COMMAND \ + __einfo_uniqify ( EINFO_EIO, 0x0b, "Aborted command" ) +#define EIO_RESERVED __einfo_error ( EINFO_EIO_RESERVED ) +#define EINFO_EIO_RESERVED \ + __einfo_uniqify ( EINFO_EIO, 0x0c, "Reserved" ) +#define EIO_VOLUME_OVERFLOW __einfo_error ( EINFO_EIO_VOLUME_OVERFLOW ) +#define EINFO_EIO_VOLUME_OVERFLOW \ + __einfo_uniqify ( EINFO_EIO, 0x0d, "Volume overflow" ) +#define EIO_MISCOMPARE __einfo_error ( EINFO_EIO_MISCOMPARE ) +#define EINFO_EIO_MISCOMPARE \ + __einfo_uniqify ( EINFO_EIO, 0x0e, "Miscompare" ) +#define EIO_COMPLETED __einfo_error ( EINFO_EIO_COMPLETED ) +#define EINFO_EIO_COMPLETED \ + __einfo_uniqify ( EINFO_EIO, 0x0f, "Completed" ) +#define EIO_SENSE( key ) \ + EUNIQ ( EINFO_EIO, (key), EIO_NO_SENSE, EIO_RECOVERED_ERROR, \ + EIO_NOT_READY, EIO_MEDIUM_ERROR, EIO_HARDWARE_ERROR, \ + EIO_ILLEGAL_REQUEST, EIO_UNIT_ATTENTION, \ + EIO_DATA_PROTECT, EIO_BLANK_CHECK, EIO_VENDOR_SPECIFIC, \ + EIO_COPY_ABORTED, EIO_ABORTED_COMMAND, EIO_RESERVED, \ + EIO_VOLUME_OVERFLOW, EIO_MISCOMPARE, EIO_COMPLETED ) + +/****************************************************************************** + * + * Utility functions + * + ****************************************************************************** + */ + +/** + * Parse SCSI LUN + * + * @v lun_string LUN string representation + * @v lun LUN to fill in + * @ret rc Return status code + */ +int scsi_parse_lun ( const char *lun_string, struct scsi_lun *lun ) { + char *p; + int i; + + memset ( lun, 0, sizeof ( *lun ) ); + if ( lun_string ) { + p = ( char * ) lun_string; + for ( i = 0 ; i < 4 ; i++ ) { + lun->u16[i] = htons ( strtoul ( p, &p, 16 ) ); + if ( *p == '\0' ) + break; + if ( *p != '-' ) + return -EINVAL; + p++; + } + if ( *p ) + return -EINVAL; + } + + return 0; +} + +/** + * Parse SCSI sense data + * + * @v data Raw sense data + * @v len Length of raw sense data + * @v sense Descriptor-format sense data to fill in + */ +void scsi_parse_sense ( const void *data, size_t len, + struct scsi_sns_descriptor *sense ) { + const union scsi_sns *sns = data; + + /* Avoid returning uninitialised data */ + memset ( sense, 0, sizeof ( *sense ) ); + + /* Copy, assuming descriptor-format data */ + if ( len < sizeof ( sns->desc ) ) + return; + memcpy ( sense, &sns->desc, sizeof ( *sense ) ); + + /* Convert fixed-format to descriptor-format, if applicable */ + if ( len < sizeof ( sns->fixed ) ) + return; + if ( ! SCSI_SENSE_FIXED ( sns->code ) ) + return; + sense->additional = sns->fixed.additional; +} + +/****************************************************************************** + * + * Interface methods + * + ****************************************************************************** + */ + +/** + * Issue SCSI command + * + * @v control SCSI control interface + * @v data SCSI data interface + * @v command SCSI command + * @ret tag Command tag, or negative error + */ +int scsi_command ( struct interface *control, struct interface *data, + struct scsi_cmd *command ) { + struct interface *dest; + scsi_command_TYPE ( void * ) *op = + intf_get_dest_op ( control, scsi_command, &dest ); + void *object = intf_object ( dest ); + int tap; + + if ( op ) { + tap = op ( object, data, command ); + } else { + /* Default is to fail to issue the command */ + tap = -EOPNOTSUPP; + } + + intf_put ( dest ); + return tap; +} + +/** + * Report SCSI response + * + * @v interface SCSI command interface + * @v response SCSI response + */ +void scsi_response ( struct interface *intf, struct scsi_rsp *response ) { + struct interface *dest; + scsi_response_TYPE ( void * ) *op = + intf_get_dest_op ( intf, scsi_response, &dest ); + void *object = intf_object ( dest ); + + if ( op ) { + op ( object, response ); + } else { + /* Default is to ignore the response */ + } + + intf_put ( dest ); +} + +/****************************************************************************** + * + * SCSI devices and commands + * + ****************************************************************************** + */ + +/** A SCSI device */ +struct scsi_device { + /** Reference count */ + struct refcnt refcnt; + /** Block control interface */ + struct interface block; + /** SCSI control interface */ + struct interface scsi; + + /** SCSI LUN */ + struct scsi_lun lun; + /** Flags */ + unsigned int flags; + + /** TEST UNIT READY interface */ + struct interface ready; + /** TEST UNIT READY process */ + struct process process; + + /** List of commands */ + struct list_head cmds; +}; + +/** SCSI device flags */ +enum scsi_device_flags { + /** TEST UNIT READY has been issued */ + SCSIDEV_UNIT_TESTED = 0x0001, + /** TEST UNIT READY has completed successfully */ + SCSIDEV_UNIT_READY = 0x0002, +}; + +/** A SCSI command */ +struct scsi_command { + /** Reference count */ + struct refcnt refcnt; + /** SCSI device */ + struct scsi_device *scsidev; + /** List of SCSI commands */ + struct list_head list; + + /** Block data interface */ + struct interface block; + /** SCSI data interface */ + struct interface scsi; + + /** Command type */ + struct scsi_command_type *type; + /** Starting logical block address */ + uint64_t lba; + /** Number of blocks */ + unsigned int count; + /** Data buffer */ + userptr_t buffer; + /** Length of data buffer */ + size_t len; + /** Command tag */ + uint32_t tag; + + /** Retry count */ + unsigned int retries; + + /** Private data */ + uint8_t priv[0]; +}; + +/** A SCSI command type */ +struct scsi_command_type { + /** Name */ + const char *name; + /** Additional working space */ + size_t priv_len; + /** + * Construct SCSI command IU + * + * @v scsicmd SCSI command + * @v command SCSI command IU + */ + void ( * cmd ) ( struct scsi_command *scsicmd, + struct scsi_cmd *command ); + /** + * Handle SCSI command completion + * + * @v scsicmd SCSI command + * @v rc Reason for completion + */ + void ( * done ) ( struct scsi_command *scsicmd, int rc ); +}; + +/** + * Get reference to SCSI device + * + * @v scsidev SCSI device + * @ret scsidev SCSI device + */ +static inline __attribute__ (( always_inline )) struct scsi_device * +scsidev_get ( struct scsi_device *scsidev ) { + ref_get ( &scsidev->refcnt ); + return scsidev; +} + +/** + * Drop reference to SCSI device + * + * @v scsidev SCSI device + */ +static inline __attribute__ (( always_inline )) void +scsidev_put ( struct scsi_device *scsidev ) { + ref_put ( &scsidev->refcnt ); +} + +/** + * Get reference to SCSI command + * + * @v scsicmd SCSI command + * @ret scsicmd SCSI command + */ +static inline __attribute__ (( always_inline )) struct scsi_command * +scsicmd_get ( struct scsi_command *scsicmd ) { + ref_get ( &scsicmd->refcnt ); + return scsicmd; +} + +/** + * Drop reference to SCSI command + * + * @v scsicmd SCSI command + */ +static inline __attribute__ (( always_inline )) void +scsicmd_put ( struct scsi_command *scsicmd ) { + ref_put ( &scsicmd->refcnt ); +} + +/** + * Get SCSI command private data + * + * @v scsicmd SCSI command + * @ret priv Private data + */ +static inline __attribute__ (( always_inline )) void * +scsicmd_priv ( struct scsi_command *scsicmd ) { + return scsicmd->priv; +} + +/** + * Free SCSI command + * + * @v refcnt Reference count + */ +static void scsicmd_free ( struct refcnt *refcnt ) { + struct scsi_command *scsicmd = + container_of ( refcnt, struct scsi_command, refcnt ); + + /* Remove from list of commands */ + list_del ( &scsicmd->list ); + scsidev_put ( scsicmd->scsidev ); + + /* Free command */ + free ( scsicmd ); +} + +/** + * Close SCSI command + * + * @v scsicmd SCSI command + * @v rc Reason for close + */ +static void scsicmd_close ( struct scsi_command *scsicmd, int rc ) { + struct scsi_device *scsidev = scsicmd->scsidev; + + if ( rc != 0 ) { + DBGC ( scsidev, "SCSI %p tag %08x closed: %s\n", + scsidev, scsicmd->tag, strerror ( rc ) ); + } + + /* Shut down interfaces */ + intf_shutdown ( &scsicmd->scsi, rc ); + intf_shutdown ( &scsicmd->block, rc ); +} + +/** + * Construct and issue SCSI command + * + * @ret rc Return status code + */ +static int scsicmd_command ( struct scsi_command *scsicmd ) { + struct scsi_device *scsidev = scsicmd->scsidev; + struct scsi_cmd command; + int tag; + int rc; + + /* Construct command */ + memset ( &command, 0, sizeof ( command ) ); + memcpy ( &command.lun, &scsidev->lun, sizeof ( command.lun ) ); + scsicmd->type->cmd ( scsicmd, &command ); + + /* Issue command */ + if ( ( tag = scsi_command ( &scsidev->scsi, &scsicmd->scsi, + &command ) ) < 0 ) { + rc = tag; + DBGC ( scsidev, "SCSI %p could not issue command: %s\n", + scsidev, strerror ( rc ) ); + return rc; + } + + /* Record tag */ + if ( scsicmd->tag ) { + DBGC ( scsidev, "SCSI %p tag %08x is now tag %08x\n", + scsidev, scsicmd->tag, tag ); + } + scsicmd->tag = tag; + DBGC2 ( scsidev, "SCSI %p tag %08x %s " SCSI_CDB_FORMAT "\n", + scsidev, scsicmd->tag, scsicmd->type->name, + SCSI_CDB_DATA ( command.cdb ) ); + + return 0; +} + +/** + * Handle SCSI command completion + * + * @v scsicmd SCSI command + * @v rc Reason for close + */ +static void scsicmd_done ( struct scsi_command *scsicmd, int rc ) { + struct scsi_device *scsidev = scsicmd->scsidev; + + /* Restart SCSI interface */ + intf_restart ( &scsicmd->scsi, rc ); + + /* SCSI targets have an annoying habit of returning occasional + * pointless "error" messages such as "power-on occurred", so + * we have to be prepared to retry commands. + */ + if ( ( rc != 0 ) && ( scsicmd->retries++ < SCSICMD_MAX_RETRIES ) ) { + /* Retry command */ + DBGC ( scsidev, "SCSI %p tag %08x failed: %s\n", + scsidev, scsicmd->tag, strerror ( rc ) ); + DBGC ( scsidev, "SCSI %p tag %08x retrying (retry %d)\n", + scsidev, scsicmd->tag, scsicmd->retries ); + if ( ( rc = scsicmd_command ( scsicmd ) ) == 0 ) + return; + } + + /* If we didn't (successfully) reissue the command, hand over + * to the command completion handler. + */ + scsicmd->type->done ( scsicmd, rc ); +} + +/** + * Handle SCSI response + * + * @v scsicmd SCSI command + * @v response SCSI response + */ +static void scsicmd_response ( struct scsi_command *scsicmd, + struct scsi_rsp *response ) { + struct scsi_device *scsidev = scsicmd->scsidev; + size_t overrun; + size_t underrun; + int rc; + + if ( response->status == 0 ) { + scsicmd_done ( scsicmd, 0 ); + } else { + DBGC ( scsidev, "SCSI %p tag %08x status %02x", + scsidev, scsicmd->tag, response->status ); + if ( response->overrun > 0 ) { + overrun = response->overrun; + DBGC ( scsidev, " overrun +%zd", overrun ); + } else if ( response->overrun < 0 ) { + underrun = -(response->overrun); + DBGC ( scsidev, " underrun -%zd", underrun ); + } + DBGC ( scsidev, " sense %02x key %02x additional %04x\n", + ( response->sense.code & SCSI_SENSE_CODE_MASK ), + ( response->sense.key & SCSI_SENSE_KEY_MASK ), + ntohs ( response->sense.additional ) ); + + /* Construct error number from sense data */ + rc = -EIO_SENSE ( response->sense.key & SCSI_SENSE_KEY_MASK ); + scsicmd_done ( scsicmd, rc ); + } +} + +/** + * Construct SCSI READ command + * + * @v scsicmd SCSI command + * @v command SCSI command IU + */ +static void scsicmd_read_cmd ( struct scsi_command *scsicmd, + struct scsi_cmd *command ) { + + if ( ( scsicmd->lba + scsicmd->count ) > SCSI_MAX_BLOCK_10 ) { + /* Use READ (16) */ + command->cdb.read16.opcode = SCSI_OPCODE_READ_16; + command->cdb.read16.lba = cpu_to_be64 ( scsicmd->lba ); + command->cdb.read16.len = cpu_to_be32 ( scsicmd->count ); + } else { + /* Use READ (10) */ + command->cdb.read10.opcode = SCSI_OPCODE_READ_10; + command->cdb.read10.lba = cpu_to_be32 ( scsicmd->lba ); + command->cdb.read10.len = cpu_to_be16 ( scsicmd->count ); + } + command->data_in = scsicmd->buffer; + command->data_in_len = scsicmd->len; +} + +/** SCSI READ command type */ +static struct scsi_command_type scsicmd_read = { + .name = "READ", + .cmd = scsicmd_read_cmd, + .done = scsicmd_close, +}; + +/** + * Construct SCSI WRITE command + * + * @v scsicmd SCSI command + * @v command SCSI command IU + */ +static void scsicmd_write_cmd ( struct scsi_command *scsicmd, + struct scsi_cmd *command ) { + + if ( ( scsicmd->lba + scsicmd->count ) > SCSI_MAX_BLOCK_10 ) { + /* Use WRITE (16) */ + command->cdb.write16.opcode = SCSI_OPCODE_WRITE_16; + command->cdb.write16.lba = cpu_to_be64 ( scsicmd->lba ); + command->cdb.write16.len = cpu_to_be32 ( scsicmd->count ); + } else { + /* Use WRITE (10) */ + command->cdb.write10.opcode = SCSI_OPCODE_WRITE_10; + command->cdb.write10.lba = cpu_to_be32 ( scsicmd->lba ); + command->cdb.write10.len = cpu_to_be16 ( scsicmd->count ); + } + command->data_out = scsicmd->buffer; + command->data_out_len = scsicmd->len; +} + +/** SCSI WRITE command type */ +static struct scsi_command_type scsicmd_write = { + .name = "WRITE", + .cmd = scsicmd_write_cmd, + .done = scsicmd_close, +}; + +/** SCSI READ CAPACITY private data */ +struct scsi_read_capacity_private { + /** Use READ CAPACITY (16) */ + int use16; + /** Data buffer for READ CAPACITY commands */ + union { + /** Data buffer for READ CAPACITY (10) */ + struct scsi_capacity_10 capacity10; + /** Data buffer for READ CAPACITY (16) */ + struct scsi_capacity_16 capacity16; + } capacity; +}; + +/** + * Construct SCSI READ CAPACITY command + * + * @v scsicmd SCSI command + * @v command SCSI command IU + */ +static void scsicmd_read_capacity_cmd ( struct scsi_command *scsicmd, + struct scsi_cmd *command ) { + struct scsi_read_capacity_private *priv = scsicmd_priv ( scsicmd ); + struct scsi_cdb_read_capacity_16 *readcap16 = &command->cdb.readcap16; + struct scsi_cdb_read_capacity_10 *readcap10 = &command->cdb.readcap10; + struct scsi_capacity_16 *capacity16 = &priv->capacity.capacity16; + struct scsi_capacity_10 *capacity10 = &priv->capacity.capacity10; + + if ( priv->use16 ) { + /* Use READ CAPACITY (16) */ + readcap16->opcode = SCSI_OPCODE_SERVICE_ACTION_IN; + readcap16->service_action = + SCSI_SERVICE_ACTION_READ_CAPACITY_16; + readcap16->len = cpu_to_be32 ( sizeof ( *capacity16 ) ); + command->data_in = virt_to_user ( capacity16 ); + command->data_in_len = sizeof ( *capacity16 ); + } else { + /* Use READ CAPACITY (10) */ + readcap10->opcode = SCSI_OPCODE_READ_CAPACITY_10; + command->data_in = virt_to_user ( capacity10 ); + command->data_in_len = sizeof ( *capacity10 ); + } +} + +/** + * Handle SCSI READ CAPACITY command completion + * + * @v scsicmd SCSI command + * @v rc Reason for completion + */ +static void scsicmd_read_capacity_done ( struct scsi_command *scsicmd, + int rc ) { + struct scsi_read_capacity_private *priv = scsicmd_priv ( scsicmd ); + struct scsi_capacity_16 *capacity16 = &priv->capacity.capacity16; + struct scsi_capacity_10 *capacity10 = &priv->capacity.capacity10; + struct block_device_capacity capacity; + + /* Close if command failed */ + if ( rc != 0 ) { + scsicmd_close ( scsicmd, rc ); + return; + } + + /* Extract capacity */ + if ( priv->use16 ) { + capacity.blocks = ( be64_to_cpu ( capacity16->lba ) + 1 ); + capacity.blksize = be32_to_cpu ( capacity16->blksize ); + } else { + capacity.blocks = ( be32_to_cpu ( capacity10->lba ) + 1 ); + capacity.blksize = be32_to_cpu ( capacity10->blksize ); + + /* If capacity range was exceeded (i.e. capacity.lba + * was 0xffffffff, meaning that blockdev->blocks is + * now zero), use READ CAPACITY (16) instead. READ + * CAPACITY (16) is not mandatory, so we can't just + * use it straight off. + */ + if ( capacity.blocks == 0 ) { + priv->use16 = 1; + if ( ( rc = scsicmd_command ( scsicmd ) ) != 0 ) { + scsicmd_close ( scsicmd, rc ); + return; + } + return; + } + } + capacity.max_count = -1U; + + /* Return capacity to caller */ + block_capacity ( &scsicmd->block, &capacity ); + + /* Close command */ + scsicmd_close ( scsicmd, 0 ); +} + +/** SCSI READ CAPACITY command type */ +static struct scsi_command_type scsicmd_read_capacity = { + .name = "READ CAPACITY", + .priv_len = sizeof ( struct scsi_read_capacity_private ), + .cmd = scsicmd_read_capacity_cmd, + .done = scsicmd_read_capacity_done, +}; + +/** + * Construct SCSI TEST UNIT READY command + * + * @v scsicmd SCSI command + * @v command SCSI command IU + */ +static void scsicmd_test_unit_ready_cmd ( struct scsi_command *scsicmd __unused, + struct scsi_cmd *command ) { + struct scsi_cdb_test_unit_ready *testready = &command->cdb.testready; + + testready->opcode = SCSI_OPCODE_TEST_UNIT_READY; +} + +/** SCSI TEST UNIT READY command type */ +static struct scsi_command_type scsicmd_test_unit_ready = { + .name = "TEST UNIT READY", + .cmd = scsicmd_test_unit_ready_cmd, + .done = scsicmd_close, +}; + +/** SCSI command block interface operations */ +static struct interface_operation scsicmd_block_op[] = { + INTF_OP ( intf_close, struct scsi_command *, scsicmd_close ), +}; + +/** SCSI command block interface descriptor */ +static struct interface_descriptor scsicmd_block_desc = + INTF_DESC_PASSTHRU ( struct scsi_command, block, + scsicmd_block_op, scsi ); + +/** SCSI command SCSI interface operations */ +static struct interface_operation scsicmd_scsi_op[] = { + INTF_OP ( intf_close, struct scsi_command *, scsicmd_done ), + INTF_OP ( scsi_response, struct scsi_command *, scsicmd_response ), +}; + +/** SCSI command SCSI interface descriptor */ +static struct interface_descriptor scsicmd_scsi_desc = + INTF_DESC_PASSTHRU ( struct scsi_command, scsi, + scsicmd_scsi_op, block ); + +/** + * Create SCSI command + * + * @v scsidev SCSI device + * @v block Block data interface + * @v type SCSI command type + * @v lba Starting logical block address + * @v count Number of blocks to transfer + * @v buffer Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +static int scsidev_command ( struct scsi_device *scsidev, + struct interface *block, + struct scsi_command_type *type, + uint64_t lba, unsigned int count, + userptr_t buffer, size_t len ) { + struct scsi_command *scsicmd; + int rc; + + /* Allocate and initialise structure */ + scsicmd = zalloc ( sizeof ( *scsicmd ) + type->priv_len ); + if ( ! scsicmd ) { + rc = -ENOMEM; + goto err_zalloc; + } + ref_init ( &scsicmd->refcnt, scsicmd_free ); + intf_init ( &scsicmd->block, &scsicmd_block_desc, &scsicmd->refcnt ); + intf_init ( &scsicmd->scsi, &scsicmd_scsi_desc, + &scsicmd->refcnt ); + scsicmd->scsidev = scsidev_get ( scsidev ); + list_add ( &scsicmd->list, &scsidev->cmds ); + scsicmd->type = type; + scsicmd->lba = lba; + scsicmd->count = count; + scsicmd->buffer = buffer; + scsicmd->len = len; + + /* Issue SCSI command */ + if ( ( rc = scsicmd_command ( scsicmd ) ) != 0 ) + goto err_command; + + /* Attach to parent interface, mortalise self, and return */ + intf_plug_plug ( &scsicmd->block, block ); + ref_put ( &scsicmd->refcnt ); + return 0; + + err_command: + scsicmd_close ( scsicmd, rc ); + ref_put ( &scsicmd->refcnt ); + err_zalloc: + return rc; +} + +/** + * Issue SCSI block read + * + * @v scsidev SCSI device + * @v block Block data interface + * @v lba Starting logical block address + * @v count Number of blocks to transfer + * @v buffer Data buffer + * @v len Length of data buffer + * @ret rc Return status code + + */ +static int scsidev_read ( struct scsi_device *scsidev, + struct interface *block, + uint64_t lba, unsigned int count, + userptr_t buffer, size_t len ) { + return scsidev_command ( scsidev, block, &scsicmd_read, + lba, count, buffer, len ); +} + +/** + * Issue SCSI block write + * + * @v scsidev SCSI device + * @v block Block data interface + * @v lba Starting logical block address + * @v count Number of blocks to transfer + * @v buffer Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +static int scsidev_write ( struct scsi_device *scsidev, + struct interface *block, + uint64_t lba, unsigned int count, + userptr_t buffer, size_t len ) { + return scsidev_command ( scsidev, block, &scsicmd_write, + lba, count, buffer, len ); +} + +/** + * Read SCSI device capacity + * + * @v scsidev SCSI device + * @v block Block data interface + * @ret rc Return status code + */ +static int scsidev_read_capacity ( struct scsi_device *scsidev, + struct interface *block ) { + return scsidev_command ( scsidev, block, &scsicmd_read_capacity, + 0, 0, UNULL, 0 ); +} + +/** + * Test to see if SCSI device is ready + * + * @v scsidev SCSI device + * @v block Block data interface + * @ret rc Return status code + */ +static int scsidev_test_unit_ready ( struct scsi_device *scsidev, + struct interface *block ) { + return scsidev_command ( scsidev, block, &scsicmd_test_unit_ready, + 0, 0, UNULL, 0 ); +} + +/** + * Check SCSI device flow-control window + * + * @v scsidev SCSI device + * @ret len Length of window + */ +static size_t scsidev_window ( struct scsi_device *scsidev ) { + + /* Refuse commands until unit is confirmed ready */ + if ( ! ( scsidev->flags & SCSIDEV_UNIT_READY ) ) + return 0; + + return xfer_window ( &scsidev->scsi ); +} + +/** + * Close SCSI device + * + * @v scsidev SCSI device + * @v rc Reason for close + */ +static void scsidev_close ( struct scsi_device *scsidev, int rc ) { + struct scsi_command *scsicmd; + struct scsi_command *tmp; + + /* Stop process */ + process_del ( &scsidev->process ); + + /* Shut down interfaces */ + intf_shutdown ( &scsidev->block, rc ); + intf_shutdown ( &scsidev->scsi, rc ); + intf_shutdown ( &scsidev->ready, rc ); + + /* Shut down any remaining commands */ + list_for_each_entry_safe ( scsicmd, tmp, &scsidev->cmds, list ) { + scsicmd_get ( scsicmd ); + scsicmd_close ( scsicmd, rc ); + scsicmd_put ( scsicmd ); + } +} + +/** SCSI device block interface operations */ +static struct interface_operation scsidev_block_op[] = { + INTF_OP ( xfer_window, struct scsi_device *, scsidev_window ), + INTF_OP ( block_read, struct scsi_device *, scsidev_read ), + INTF_OP ( block_write, struct scsi_device *, scsidev_write ), + INTF_OP ( block_read_capacity, struct scsi_device *, + scsidev_read_capacity ), + INTF_OP ( intf_close, struct scsi_device *, scsidev_close ), +}; + +/** SCSI device block interface descriptor */ +static struct interface_descriptor scsidev_block_desc = + INTF_DESC_PASSTHRU ( struct scsi_device, block, + scsidev_block_op, scsi ); + +/** + * Handle SCSI TEST UNIT READY response + * + * @v scsidev SCSI device + * @v rc Reason for close + */ +static void scsidev_ready ( struct scsi_device *scsidev, int rc ) { + + /* Shut down interface */ + intf_shutdown ( &scsidev->ready, rc ); + + /* Close device on failure */ + if ( rc != 0 ) { + DBGC ( scsidev, "SCSI %p not ready: %s\n", + scsidev, strerror ( rc ) ); + scsidev_close ( scsidev, rc ); + return; + } + + /* Mark device as ready */ + scsidev->flags |= SCSIDEV_UNIT_READY; + xfer_window_changed ( &scsidev->block ); + DBGC ( scsidev, "SCSI %p unit is ready\n", scsidev ); +} + +/** SCSI device TEST UNIT READY interface operations */ +static struct interface_operation scsidev_ready_op[] = { + INTF_OP ( intf_close, struct scsi_device *, scsidev_ready ), +}; + +/** SCSI device TEST UNIT READY interface descriptor */ +static struct interface_descriptor scsidev_ready_desc = + INTF_DESC ( struct scsi_device, ready, scsidev_ready_op ); + +/** + * SCSI TEST UNIT READY process + * + * @v scsidev SCSI device + */ +static void scsidev_step ( struct scsi_device *scsidev ) { + int rc; + + /* Do nothing if we have already issued TEST UNIT READY */ + if ( scsidev->flags & SCSIDEV_UNIT_TESTED ) + return; + + /* Wait until underlying SCSI device is ready */ + if ( xfer_window ( &scsidev->scsi ) == 0 ) + return; + + DBGC ( scsidev, "SCSI %p waiting for unit to become ready\n", + scsidev ); + + /* Mark TEST UNIT READY as sent */ + scsidev->flags |= SCSIDEV_UNIT_TESTED; + + /* Issue TEST UNIT READY command */ + if ( ( rc = scsidev_test_unit_ready ( scsidev, &scsidev->ready )) !=0){ + scsidev_close ( scsidev, rc ); + return; + } +} + +/** SCSI device SCSI interface operations */ +static struct interface_operation scsidev_scsi_op[] = { + INTF_OP ( xfer_window_changed, struct scsi_device *, scsidev_step ), + INTF_OP ( intf_close, struct scsi_device *, scsidev_close ), +}; + +/** SCSI device SCSI interface descriptor */ +static struct interface_descriptor scsidev_scsi_desc = + INTF_DESC_PASSTHRU ( struct scsi_device, scsi, + scsidev_scsi_op, block ); + +/** SCSI device process descriptor */ +static struct process_descriptor scsidev_process_desc = + PROC_DESC_ONCE ( struct scsi_device, process, scsidev_step ); + +/** + * Open SCSI device + * + * @v block Block control interface + * @v scsi SCSI control interface + * @v lun SCSI LUN + * @ret rc Return status code + */ +int scsi_open ( struct interface *block, struct interface *scsi, + struct scsi_lun *lun ) { + struct scsi_device *scsidev; + + /* Allocate and initialise structure */ + scsidev = zalloc ( sizeof ( *scsidev ) ); + if ( ! scsidev ) + return -ENOMEM; + ref_init ( &scsidev->refcnt, NULL ); + intf_init ( &scsidev->block, &scsidev_block_desc, &scsidev->refcnt ); + intf_init ( &scsidev->scsi, &scsidev_scsi_desc, &scsidev->refcnt ); + intf_init ( &scsidev->ready, &scsidev_ready_desc, &scsidev->refcnt ); + process_init ( &scsidev->process, &scsidev_process_desc, + &scsidev->refcnt ); + INIT_LIST_HEAD ( &scsidev->cmds ); + memcpy ( &scsidev->lun, lun, sizeof ( scsidev->lun ) ); + DBGC ( scsidev, "SCSI %p created for LUN " SCSI_LUN_FORMAT "\n", + scsidev, SCSI_LUN_DATA ( scsidev->lun ) ); + + /* Attach to SCSI and parent interfaces, mortalise self, and return */ + intf_plug_plug ( &scsidev->scsi, scsi ); + intf_plug_plug ( &scsidev->block, block ); + ref_put ( &scsidev->refcnt ); + return 0; +} diff --git a/qemu/roms/ipxe/src/drivers/block/srp.c b/qemu/roms/ipxe/src/drivers/block/srp.c new file mode 100644 index 000000000..7edf69ace --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/block/srp.c @@ -0,0 +1,829 @@ +/* + * Copyright (C) 2009 Fen Systems Ltd <mbrown@fensystems.co.uk>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +FILE_LICENCE ( BSD2 ); + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ipxe/scsi.h> +#include <ipxe/xfer.h> +#include <ipxe/features.h> +#include <ipxe/srp.h> + +/** + * @file + * + * SCSI RDMA Protocol + * + */ + +FEATURE ( FEATURE_PROTOCOL, "SRP", DHCP_EB_FEATURE_SRP, 1 ); + +/** 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 + +/* Error numbers generated by SRP login rejection */ +#define EINFO_SRP_LOGIN_REJ( reason, desc ) \ + __einfo_uniqify ( EINFO_EPERM, ( (reason) & 0x0f ), desc ) +#define EPERM_UNKNOWN \ + __einfo_error ( EINFO_EPERM_UNKNOWN ) +#define EINFO_EPERM_UNKNOWN EINFO_SRP_LOGIN_REJ ( \ + SRP_LOGIN_REJ_REASON_UNKNOWN, \ + "Unable to establish RDMA channel, no reason specified" ) +#define EPERM_INSUFFICIENT_RESOURCES \ + __einfo_error ( EINFO_EPERM_INSUFFICIENT_RESOURCES ) +#define EINFO_EPERM_INSUFFICIENT_RESOURCES EINFO_SRP_LOGIN_REJ ( \ + SRP_LOGIN_REJ_REASON_INSUFFICIENT_RESOURCES, \ + "Insufficient RDMA channel resources" ) +#define EPERM_BAD_MAX_I_T_IU_LEN \ + __einfo_error ( EINFO_EPERM_BAD_MAX_I_T_IU_LEN ) +#define EINFO_EPERM_BAD_MAX_I_T_IU_LEN EINFO_SRP_LOGIN_REJ ( \ + SRP_LOGIN_REJ_REASON_BAD_MAX_I_T_IU_LEN, \ + "Requested maximum initiator to target IU length value too large" ) +#define EPERM_CANNOT_ASSOCIATE \ + __einfo_error ( EINFO_EPERM_CANNOT_ASSOCIATE ) +#define EINFO_EPERM_CANNOT_ASSOCIATE EINFO_SRP_LOGIN_REJ ( \ + SRP_LOGIN_REJ_REASON_CANNOT_ASSOCIATE, \ + "Unable to associate RDMA channel with specified I_T nexus" ) +#define EPERM_UNSUPPORTED_BUFFER_FORMAT \ + __einfo_error ( EINFO_EPERM_UNSUPPORTED_BUFFER_FORMAT ) +#define EINFO_EPERM_UNSUPPORTED_BUFFER_FORMAT EINFO_SRP_LOGIN_REJ ( \ + SRP_LOGIN_REJ_REASON_UNSUPPORTED_BUFFER_FORMAT, \ + "One or more requested data buffer descriptor formats not supported" ) +#define EPERM_NO_MULTIPLE_CHANNELS \ + __einfo_error ( EINFO_EPERM_NO_MULTIPLE_CHANNELS ) +#define EINFO_EPERM_NO_MULTIPLE_CHANNELS EINFO_SRP_LOGIN_REJ ( \ + SRP_LOGIN_REJ_REASON_NO_MULTIPLE_CHANNELS, \ + "SRP target does not support multiple RDMA channels per I_T nexus" ) +#define EPERM_NO_MORE_CHANNELS \ + __einfo_error ( EINFO_EPERM_NO_MORE_CHANNELS ) +#define EINFO_EPERM_NO_MORE_CHANNELS EINFO_SRP_LOGIN_REJ ( \ + SRP_LOGIN_REJ_REASON_NO_MORE_CHANNELS, \ + "RDMA channel limit reached for this initiator" ) +#define EPERM_LOGIN_REJ( reason_nibble ) \ + EUNIQ ( EINFO_EPERM, (reason_nibble), EPERM_UNKNOWN, \ + EPERM_INSUFFICIENT_RESOURCES, EPERM_BAD_MAX_I_T_IU_LEN, \ + EPERM_CANNOT_ASSOCIATE, EPERM_UNSUPPORTED_BUFFER_FORMAT, \ + EPERM_NO_MULTIPLE_CHANNELS, EPERM_NO_MORE_CHANNELS ) + +/** 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; +} + +/** + * 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; +} + +/** + * Drop reference to SRP command + * + * @v srpcmd SRP command + */ +static inline __attribute__ (( always_inline )) void +srpcmd_put ( struct srp_command *srpcmd ) { + ref_put ( &srpcmd->refcnt ); +} + +/** + * Free SRP command + * + * @v refcnt Reference count + */ +static void srpcmd_free ( struct refcnt *refcnt ) { + struct srp_command *srpcmd = + container_of ( refcnt, struct srp_command, refcnt ); + + assert ( list_empty ( &srpcmd->list ) ); + + srpdev_put ( srpcmd->srpdev ); + free ( srpcmd ); +} + +/** + * 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 ) ); + } + + /* 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 ); +} + +/** + * Close SRP device + * + * @v srpdev SRP device + * @v rc Reason for close + */ +static void srpdev_close ( struct srp_device *srpdev, int rc ) { + struct srp_command *srpcmd; + struct srp_command *tmp; + + if ( rc != 0 ) { + DBGC ( srpdev, "SRP %p closed: %s\n", + srpdev, strerror ( rc ) ); + } + + /* 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 ); + } +} + +/** + * 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[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->initiator, initiator, + sizeof ( login_req->initiator ) ); + memcpy ( &login_req->target, target, sizeof ( login_req->target ) ); + + 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 ( &srpdev->socket, iobuf ) ) != 0 ) { + DBGC ( srpdev, "SRP %p tag %08x could not send LOGIN_REQ: " + "%s\n", srpdev, tag, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Receive SRP login response + * + * @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 *srpdev, + const void *data, size_t len ) { + const struct srp_login_rsp *login_rsp = data; + + /* Sanity check */ + if ( len < sizeof ( *login_rsp ) ) { + DBGC ( srpdev, "SRP %p LOGIN_RSP too short (%zd bytes)\n", + srpdev, len ); + return -EINVAL; + } + 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 */ + srpdev->logged_in = 1; + DBGC ( srpdev, "SRP %p logged in\n", srpdev ); + + /* Notify of window change */ + xfer_window_changed ( &srpdev->scsi ); + + return 0; +} + +/** + * Receive SRP login rejection + * + * @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 *srpdev, + const void *data, size_t len ) { + const struct srp_login_rej *login_rej = data; + uint32_t reason; + + /* Sanity check */ + if ( len < sizeof ( *login_rej ) ) { + DBGC ( srpdev, "SRP %p LOGIN_REJ too short (%zd bytes)\n", + srpdev, len ); + return -EINVAL; + } + reason = ntohl ( login_rej->reason ); + DBGC ( srpdev, "SRP %p tag %08x LOGIN_REJ reason %08x:\n", + srpdev, ntohl ( login_rej->tag.dwords[1] ), reason ); + DBGC_HDA ( srpdev, 0, data, len ); + + /* Login rejection always indicates an error */ + return ( SRP_LOGIN_REJ_REASON_DEFINED ( reason ) ? + -EPERM_LOGIN_REJ ( reason ) : -EACCES ); +} + +/** + * Transmit SRP SCSI command + * + * @v srpdev SRP device + * @v command SCSI command + * @v tag Command tag + * @ret rc Return status code + */ +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; + + /* 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 ( &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[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 ( 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 ( 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 ( 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 ( command->data_in, 0 ) ); + data_in->handle = ntohl ( srpdev->memory_handle ); + data_in->len = ntohl ( command->data_in_len ); + } + + 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 ( &srpdev->socket, iobuf ) ) != 0 ) { + DBGC ( srpdev, "SRP %p tag %08x could not send CMD: %s\n", + srpdev, tag, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Receive SRP SCSI response + * + * @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 *srpdev, + const void *data, size_t len ) { + const struct srp_rsp *rsp = data; + struct srp_command *srpcmd; + struct scsi_rsp response; + ssize_t data_out_residual_count; + ssize_t data_in_residual_count; + + /* Sanity check */ + if ( ( len < sizeof ( *rsp ) ) || + ( len < ( sizeof ( *rsp ) + + srp_rsp_response_data_len ( rsp ) + + srp_rsp_sense_data_len ( rsp ) ) ) ) { + DBGC ( srpdev, "SRP %p RSP too short (%zd bytes)\n", + srpdev, len ); + return -EINVAL; + } + 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; + } + + /* 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); + } + scsi_parse_sense ( srp_rsp_sense_data ( rsp ), + srp_rsp_sense_data_len ( rsp ), &response.sense ); + + /* Report SCSI response */ + scsi_response ( &srpcmd->scsi, &response ); + + /* Close SCSI command */ + srpcmd_close ( srpcmd, 0 ); + + /* Drop temporary command reference */ + srpcmd_put ( srpcmd ); + + return 0; +} + +/** + * Receive SRP unrecognised response IU + * + * @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 *srpdev, + const void *data, size_t len ) { + const struct srp_common *common = data; + + 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 ); + + 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 SRP socket + * + * @v srpdev SRP device + * @v iobuf Datagram I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +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, 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: + type = srp_login_rsp; + break; + case SRP_LOGIN_REJ: + type = srp_login_rej; + break; + case SRP_RSP: + type = srp_rsp; + break; + default: + type = srp_unrecognised; + break; + } + + /* Handle IU */ + if ( ( rc = type ( srpdev, iobuf->data, iob_len ( iobuf ) ) ) != 0 ) + goto err; + + free_iob ( iobuf ); + return 0; + + err: + 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; +} + +/** + * 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 ); +} + +/** + * 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 ) )); + +/** + * Describe SRP device in an ACPI table + * + * @v srpdev SRP device + * @v acpi ACPI table + * @v len Length of ACPI table + * @ret rc Return status code + */ +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_PASSTHRU ( struct srp_device, socket, srpdev_socket_op, + scsi ); + +/** 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_PASSTHRU ( struct srp_device, scsi, srpdev_scsi_op, socket ); + +/** + * Open SRP device + * + * @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_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; + + /* Allocate and initialise structure */ + srpdev = zalloc ( sizeof ( *srpdev ) ); + if ( ! srpdev ) { + rc = -ENOMEM; + goto err_zalloc; + } + 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; + } + + /* Mortalise self and return */ + ref_put ( &srpdev->refcnt ); + return 0; + + err_scsi_open: + err_login: + srpdev_close ( srpdev, rc ); + ref_put ( &srpdev->refcnt ); + err_zalloc: + return rc; +} |