From 220495f8bf2222e1dc1aa7db554d23997b545546 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 3 Sep 2010 16:11:51 +0100 Subject: [PATCH] [block] Replace gPXE block-device API with an iPXE asynchronous interface The block device interface used in gPXE predates the invention of even the old gPXE data-transfer interface, let alone the current iPXE generic asynchronous interface mechanism. Bring this old code up to date, with the following benefits: o Block device commands can be cancelled by the requestor. The INT 13 layer uses this to provide a global timeout on all INT 13 calls, with the result that an unexpected passive failure mode (such as an iSCSI target ACKing the request but never sending a response) will lead to a timeout that gets reported back to the INT 13 user, rather than simply freezing the system. o INT 13,00 (reset drive) is now able to reset the underlying block device. INT 13 users, such as DOS, that use INT 13,00 as a method for error recovery now have a chance of recovering. o All block device commands are tagged, with a numerical tag that will show up in debugging output and in packet captures; this will allow easier interpretation of bug reports that include both sources of information. o The extremely ugly hacks used to generate the boot firmware tables have been eradicated and replaced with a generic acpi_describe() method (exploiting the ability of iPXE interfaces to pass through methods to an underlying interface). The ACPI tables are now built in a shared data block within .bss16, rather than each requiring dedicated space in .data16. o The architecture-independent concept of a SAN device has been exposed to the iPXE core through the sanboot API, which provides calls to hook, unhook, boot, and describe SAN devices. This allows for much more flexible usage patterns (such as hooking an empty SAN device and then running an OS installer via TFTP). Signed-off-by: Michael Brown --- src/arch/i386/image/eltorito.c | 336 ----- src/arch/i386/include/bits/eltorito.h | 3 - src/arch/i386/include/bits/sanboot.h | 14 + src/arch/i386/include/int13.h | 61 +- src/arch/i386/include/ipxe/abft.h | 37 - src/arch/i386/include/ipxe/bios_sanboot.h | 18 + src/arch/i386/include/ipxe/sbft.h | 125 -- src/arch/i386/interface/pcbios/abft.c | 62 - src/arch/i386/interface/pcbios/aoeboot.c | 78 -- src/arch/i386/interface/pcbios/ib_srpboot.c | 73 -- src/arch/i386/interface/pcbios/int13.c | 892 ++++++++++--- src/arch/i386/interface/pcbios/iscsiboot.c | 75 -- src/arch/i386/interface/pcbios/keepsan.c | 26 - src/arch/i386/interface/pcbios/sbft.c | 105 -- src/arch/x86_64/include/bits/sanboot.h | 12 + src/config/config.c | 2 +- src/config/config_ethernet.c | 2 +- src/config/config_infiniband.c | 2 +- src/config/defaults/efi.h | 1 + src/config/defaults/linux.h | 1 + src/config/defaults/pcbios.h | 1 + src/config/sanboot.h | 16 + src/core/acpi.c | 43 + src/core/blockdev.c | 138 ++ src/core/null_sanboot.c | 44 + src/drivers/block/ata.c | 745 +++++++++-- .../interface/pcbios => drivers/block}/ibft.c | 258 ++-- src/drivers/block/ramdisk.c | 97 -- src/drivers/block/scsi.c | 1006 +++++++++----- src/drivers/block/srp.c | 860 +++++++----- src/include/ipxe/acpi.h | 22 +- src/include/ipxe/aoe.h | 67 +- src/include/ipxe/ata.h | 85 +- src/include/ipxe/blockdev.h | 70 +- src/include/ipxe/errfile.h | 3 + src/include/ipxe/ib_srp.h | 75 +- src/{arch/i386 => }/include/ipxe/ibft.h | 47 +- src/include/ipxe/iscsi.h | 71 +- src/include/ipxe/null_sanboot.h | 18 + src/include/ipxe/ramdisk.h | 24 - src/include/ipxe/sanboot.h | 91 +- src/include/ipxe/scsi.h | 102 +- src/include/ipxe/srp.h | 201 ++- src/net/aoe.c | 1162 ++++++++++++----- src/net/infiniband/ib_srp.c | 345 +++-- src/net/tcp/iscsi.c | 445 ++++--- src/usr/autoboot.c | 70 +- 47 files changed, 4904 insertions(+), 3127 deletions(-) delete mode 100644 src/arch/i386/image/eltorito.c delete mode 100644 src/arch/i386/include/bits/eltorito.h create mode 100644 src/arch/i386/include/bits/sanboot.h delete mode 100644 src/arch/i386/include/ipxe/abft.h create mode 100644 src/arch/i386/include/ipxe/bios_sanboot.h delete mode 100644 src/arch/i386/include/ipxe/sbft.h delete mode 100644 src/arch/i386/interface/pcbios/abft.c delete mode 100644 src/arch/i386/interface/pcbios/aoeboot.c delete mode 100644 src/arch/i386/interface/pcbios/ib_srpboot.c delete mode 100644 src/arch/i386/interface/pcbios/iscsiboot.c delete mode 100644 src/arch/i386/interface/pcbios/keepsan.c delete mode 100644 src/arch/i386/interface/pcbios/sbft.c create mode 100644 src/arch/x86_64/include/bits/sanboot.h create mode 100644 src/config/sanboot.h create mode 100644 src/core/blockdev.c create mode 100644 src/core/null_sanboot.c rename src/{arch/i386/interface/pcbios => drivers/block}/ibft.c (63%) delete mode 100644 src/drivers/block/ramdisk.c rename src/{arch/i386 => }/include/ipxe/ibft.h (86%) create mode 100644 src/include/ipxe/null_sanboot.h delete mode 100644 src/include/ipxe/ramdisk.h diff --git a/src/arch/i386/image/eltorito.c b/src/arch/i386/image/eltorito.c deleted file mode 100644 index a416d15b..00000000 --- a/src/arch/i386/image/eltorito.c +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (C) 2007 Michael Brown . - * - * 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 ); - -/** - * @file - * - * El Torito bootable ISO image format - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ISO9660_BLKSIZE 2048 -#define ELTORITO_VOL_DESC_OFFSET ( 17 * ISO9660_BLKSIZE ) - -/** An El Torito Boot Record Volume Descriptor */ -struct eltorito_vol_desc { - /** Boot record indicator; must be 0 */ - uint8_t record_indicator; - /** ISO-9660 identifier; must be "CD001" */ - uint8_t iso9660_id[5]; - /** Version, must be 1 */ - uint8_t version; - /** Boot system indicator; must be "EL TORITO SPECIFICATION" */ - uint8_t system_indicator[32]; - /** Unused */ - uint8_t unused[32]; - /** Boot catalog sector */ - uint32_t sector; -} __attribute__ (( packed )); - -/** An El Torito Boot Catalog Validation Entry */ -struct eltorito_validation_entry { - /** Header ID; must be 1 */ - uint8_t header_id; - /** Platform ID - * - * 0 = 80x86 - * 1 = PowerPC - * 2 = Mac - */ - uint8_t platform_id; - /** Reserved */ - uint16_t reserved; - /** ID string */ - uint8_t id_string[24]; - /** Checksum word */ - uint16_t checksum; - /** Signature; must be 0xaa55 */ - uint16_t signature; -} __attribute__ (( packed )); - -/** A bootable entry in the El Torito Boot Catalog */ -struct eltorito_boot_entry { - /** Boot indicator - * - * Must be @c ELTORITO_BOOTABLE for a bootable ISO image - */ - uint8_t indicator; - /** Media type - * - */ - uint8_t media_type; - /** Load segment */ - uint16_t load_segment; - /** System type */ - uint8_t filesystem; - /** Unused */ - uint8_t reserved_a; - /** Sector count */ - uint16_t length; - /** Starting sector */ - uint32_t start; - /** Unused */ - uint8_t reserved_b[20]; -} __attribute__ (( packed )); - -/** Boot indicator for a bootable ISO image */ -#define ELTORITO_BOOTABLE 0x88 - -/** El Torito media types */ -enum eltorito_media_type { - /** No emulation */ - ELTORITO_NO_EMULATION = 0, -}; - -struct image_type eltorito_image_type __image_type ( PROBE_NORMAL ); - -/** - * Calculate 16-bit word checksum - * - * @v data Data to checksum - * @v len Length (in bytes, must be even) - * @ret sum Checksum - */ -static unsigned int word_checksum ( void *data, size_t len ) { - uint16_t *words; - uint16_t sum = 0; - - for ( words = data ; len ; words++, len -= 2 ) { - sum += *words; - } - return sum; -} - -/** - * Execute El Torito image - * - * @v image El Torito image - * @ret rc Return status code - */ -static int eltorito_exec ( struct image *image ) { - struct ramdisk ramdisk; - struct int13_drive int13_drive; - unsigned int load_segment = image->priv.ul; - unsigned int load_offset = ( load_segment ? 0 : 0x7c00 ); - int rc; - - memset ( &ramdisk, 0, sizeof ( ramdisk ) ); - init_ramdisk ( &ramdisk, image->data, image->len, ISO9660_BLKSIZE ); - - memset ( &int13_drive, 0, sizeof ( int13_drive ) ); - int13_drive.blockdev = &ramdisk.blockdev; - register_int13_drive ( &int13_drive ); - - if ( ( rc = call_bootsector ( load_segment, load_offset, - int13_drive.drive ) ) != 0 ) { - DBGC ( image, "ElTorito %p boot failed: %s\n", - image, strerror ( rc ) ); - goto err; - } - - rc = -ECANCELED; /* -EIMPOSSIBLE */ - err: - unregister_int13_drive ( &int13_drive ); - return rc; -} - -/** - * Read and verify El Torito Boot Record Volume Descriptor - * - * @v image El Torito file - * @ret catalog_offset Offset of Boot Catalog - * @ret rc Return status code - */ -static int eltorito_read_voldesc ( struct image *image, - unsigned long *catalog_offset ) { - static const struct eltorito_vol_desc vol_desc_signature = { - .record_indicator = 0, - .iso9660_id = "CD001", - .version = 1, - .system_indicator = "EL TORITO SPECIFICATION", - }; - struct eltorito_vol_desc vol_desc; - - /* Sanity check */ - if ( image->len < ( ELTORITO_VOL_DESC_OFFSET + ISO9660_BLKSIZE ) ) { - DBGC ( image, "ElTorito %p too short\n", image ); - return -ENOEXEC; - } - - /* Read and verify Boot Record Volume Descriptor */ - copy_from_user ( &vol_desc, image->data, ELTORITO_VOL_DESC_OFFSET, - sizeof ( vol_desc ) ); - if ( memcmp ( &vol_desc, &vol_desc_signature, - offsetof ( typeof ( vol_desc ), sector ) ) != 0 ) { - DBGC ( image, "ElTorito %p invalid Boot Record Volume " - "Descriptor\n", image ); - return -ENOEXEC; - } - *catalog_offset = ( vol_desc.sector * ISO9660_BLKSIZE ); - - DBGC ( image, "ElTorito %p boot catalog at offset %#lx\n", - image, *catalog_offset ); - - return 0; -} - -/** - * Read and verify El Torito Boot Catalog - * - * @v image El Torito file - * @v catalog_offset Offset of Boot Catalog - * @ret boot_entry El Torito boot entry - * @ret rc Return status code - */ -static int eltorito_read_catalog ( struct image *image, - unsigned long catalog_offset, - struct eltorito_boot_entry *boot_entry ) { - struct eltorito_validation_entry validation_entry; - - /* Sanity check */ - if ( image->len < ( catalog_offset + ISO9660_BLKSIZE ) ) { - DBGC ( image, "ElTorito %p bad boot catalog offset %#lx\n", - image, catalog_offset ); - return -ENOEXEC; - } - - /* Read and verify the Validation Entry of the Boot Catalog */ - copy_from_user ( &validation_entry, image->data, catalog_offset, - sizeof ( validation_entry ) ); - if ( word_checksum ( &validation_entry, - sizeof ( validation_entry ) ) != 0 ) { - DBGC ( image, "ElTorito %p bad Validation Entry checksum\n", - image ); - return -ENOEXEC; - } - - /* Read and verify the Initial/Default entry */ - copy_from_user ( boot_entry, image->data, - ( catalog_offset + sizeof ( validation_entry ) ), - sizeof ( *boot_entry ) ); - if ( boot_entry->indicator != ELTORITO_BOOTABLE ) { - DBGC ( image, "ElTorito %p not bootable\n", image ); - return -ENOEXEC; - } - if ( boot_entry->media_type != ELTORITO_NO_EMULATION ) { - DBGC ( image, "ElTorito %p cannot support media type %d\n", - image, boot_entry->media_type ); - return -ENOTSUP; - } - - DBGC ( image, "ElTorito %p media type %d segment %04x\n", - image, boot_entry->media_type, boot_entry->load_segment ); - - return 0; -} - -/** - * Load El Torito virtual disk image into memory - * - * @v image El Torito file - * @v boot_entry El Torito boot entry - * @ret rc Return status code - */ -static int eltorito_load_disk ( struct image *image, - struct eltorito_boot_entry *boot_entry ) { - unsigned long start = ( boot_entry->start * ISO9660_BLKSIZE ); - unsigned long length = ( boot_entry->length * ISO9660_BLKSIZE ); - unsigned int load_segment; - userptr_t buffer; - int rc; - - /* Sanity check */ - if ( image->len < ( start + length ) ) { - DBGC ( image, "ElTorito %p virtual disk lies outside image\n", - image ); - return -ENOEXEC; - } - DBGC ( image, "ElTorito %p virtual disk at %#lx+%#lx\n", - image, start, length ); - - /* Calculate load address */ - load_segment = boot_entry->load_segment; - buffer = real_to_user ( load_segment, ( load_segment ? 0 : 0x7c00 ) ); - - /* Verify and prepare segment */ - if ( ( rc = prep_segment ( buffer, length, length ) ) != 0 ) { - DBGC ( image, "ElTorito %p could not prepare segment: %s\n", - image, strerror ( rc ) ); - return rc; - } - - /* Copy image to segment */ - memcpy_user ( buffer, 0, image->data, start, length ); - - return 0; -} - -/** - * Load El Torito image into memory - * - * @v image El Torito file - * @ret rc Return status code - */ -static int eltorito_load ( struct image *image ) { - struct eltorito_boot_entry boot_entry; - unsigned long bootcat_offset; - int rc; - - /* Read Boot Record Volume Descriptor, if present */ - if ( ( rc = eltorito_read_voldesc ( image, &bootcat_offset ) ) != 0 ) - return rc; - - /* This is an El Torito image, valid or otherwise */ - if ( ! image->type ) - image->type = &eltorito_image_type; - - /* Read Boot Catalog */ - if ( ( rc = eltorito_read_catalog ( image, bootcat_offset, - &boot_entry ) ) != 0 ) - return rc; - - /* Load Virtual Disk image */ - if ( ( rc = eltorito_load_disk ( image, &boot_entry ) ) != 0 ) - return rc; - - /* Record load segment in image private data field */ - image->priv.ul = boot_entry.load_segment; - - return 0; -} - -/** El Torito image type */ -struct image_type eltorito_image_type __image_type ( PROBE_NORMAL ) = { - .name = "El Torito", - .load = eltorito_load, - .exec = eltorito_exec, -}; diff --git a/src/arch/i386/include/bits/eltorito.h b/src/arch/i386/include/bits/eltorito.h deleted file mode 100644 index d43e9aac..00000000 --- a/src/arch/i386/include/bits/eltorito.h +++ /dev/null @@ -1,3 +0,0 @@ -#ifndef ELTORITO_PLATFORM -#define ELTORITO_PLATFORM ELTORITO_PLATFORM_X86 -#endif /* ELTORITO_PLATFORM */ diff --git a/src/arch/i386/include/bits/sanboot.h b/src/arch/i386/include/bits/sanboot.h new file mode 100644 index 00000000..9c77a4d4 --- /dev/null +++ b/src/arch/i386/include/bits/sanboot.h @@ -0,0 +1,14 @@ +#ifndef _BITS_SANBOOT_H +#define _BITS_SANBOOT_H + +/** @file + * + * i386-specific sanboot API implementations + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include + +#endif /* _BITS_SANBOOT_H */ diff --git a/src/arch/i386/include/int13.h b/src/arch/i386/include/int13.h index ed085d57..2a0f19cf 100644 --- a/src/arch/i386/include/int13.h +++ b/src/arch/i386/include/int13.h @@ -13,8 +13,6 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include -struct block_device; - /** * @defgroup int13ops INT 13 operation codes * @{ @@ -56,6 +54,8 @@ struct block_device; #define INT13_STATUS_INVALID 0x01 /** Read error */ #define INT13_STATUS_READ_ERROR 0x04 +/** Reset failed */ +#define INT13_STATUS_RESET_FAILED 0x05 /** Write error */ #define INT13_STATUS_WRITE_ERROR 0xcc @@ -64,57 +64,6 @@ struct block_device; /** Block size for non-extended INT 13 calls */ #define INT13_BLKSIZE 512 -/** An INT 13 emulated drive */ -struct int13_drive { - /** List of all registered drives */ - struct list_head list; - - /** Underlying block device */ - struct block_device *blockdev; - - /** BIOS in-use drive number (0x80-0xff) */ - unsigned int drive; - /** BIOS natural drive number (0x80-0xff) - * - * This is the drive number that would have been assigned by - * 'naturally' appending the drive to the end of the BIOS - * drive list. - * - * If the emulated drive replaces a preexisting drive, this is - * the drive number that the preexisting drive gets remapped - * to. - */ - unsigned int natural_drive; - - /** Number of cylinders - * - * The cylinder number field in an INT 13 call is ten bits - * wide, giving a maximum of 1024 cylinders. Conventionally, - * when the 7.8GB limit of a CHS address is exceeded, it is - * the number of cylinders that is increased beyond the - * addressable limit. - */ - unsigned int cylinders; - /** Number of heads - * - * The head number field in an INT 13 call is eight bits wide, - * giving a maximum of 256 heads. However, apparently all - * versions of MS-DOS up to and including Win95 fail with 256 - * heads, so the maximum encountered in practice is 255. - */ - unsigned int heads; - /** Number of sectors per track - * - * The sector number field in an INT 13 call is six bits wide, - * giving a maximum of 63 sectors, since sector numbering - * (unlike head and cylinder numbering) starts at 1, not 0. - */ - unsigned int sectors_per_track; - - /** Status of last operation */ - int last_status; -}; - /** An INT 13 disk address packet */ struct int13_disk_address { /** Size of the packet, in bytes */ @@ -147,7 +96,6 @@ struct int13_disk_parameters { uint64_t sectors; /** Bytes per sector */ uint16_t sector_size; - } __attribute__ (( packed )); /** @@ -285,8 +233,7 @@ struct master_boot_record { uint16_t signature; } __attribute__ (( packed )); -extern void register_int13_drive ( struct int13_drive *drive ); -extern void unregister_int13_drive ( struct int13_drive *drive ); -extern int int13_boot ( unsigned int drive ); +/** Use natural BIOS drive number */ +#define INT13_USE_NATURAL_DRIVE 0xff #endif /* INT13_H */ diff --git a/src/arch/i386/include/ipxe/abft.h b/src/arch/i386/include/ipxe/abft.h deleted file mode 100644 index ca963020..00000000 --- a/src/arch/i386/include/ipxe/abft.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef _IPXE_ABFT_H -#define _IPXE_ABFT_H - -/** @file - * - * AoE boot firmware table - * - */ - -FILE_LICENCE ( GPL2_OR_LATER ); - -#include -#include -#include - -/** AoE boot firmware table signature */ -#define ABFT_SIG "aBFT" - -/** - * AoE Boot Firmware Table (aBFT) - */ -struct abft_table { - /** ACPI header */ - struct acpi_description_header acpi; - /** AoE shelf */ - uint16_t shelf; - /** AoE slot */ - uint8_t slot; - /** Reserved */ - uint8_t reserved_a; - /** MAC address */ - uint8_t mac[ETH_ALEN]; -} __attribute__ (( packed )); - -extern void abft_fill_data ( struct aoe_session *aoe ); - -#endif /* _IPXE_ABFT_H */ diff --git a/src/arch/i386/include/ipxe/bios_sanboot.h b/src/arch/i386/include/ipxe/bios_sanboot.h new file mode 100644 index 00000000..09794548 --- /dev/null +++ b/src/arch/i386/include/ipxe/bios_sanboot.h @@ -0,0 +1,18 @@ +#ifndef _IPXE_BIOS_SANBOOT_H +#define _IPXE_BIOS_SANBOOT_H + +/** @file + * + * Standard PC-BIOS sanboot interface + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#ifdef SANBOOT_PCBIOS +#define SANBOOT_PREFIX_pcbios +#else +#define SANBOOT_PREFIX_pcbios __pcbios_ +#endif + +#endif /* _IPXE_BIOS_SANBOOT_H */ diff --git a/src/arch/i386/include/ipxe/sbft.h b/src/arch/i386/include/ipxe/sbft.h deleted file mode 100644 index f9910624..00000000 --- a/src/arch/i386/include/ipxe/sbft.h +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef _IPXE_SBFT_H -#define _IPXE_SBFT_H - -/* - * Copyright (C) 2009 Fen Systems Ltd . - * 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 ); - -/** @file - * - * SRP boot firmware table - * - * The working draft specification for the SRP boot firmware table can - * be found at - * - * http://ipxe.org/wiki/srp/sbft - * - */ - -#include -#include -#include -#include -#include - -/** SRP Boot Firmware Table signature */ -#define SBFT_SIG "sBFT" - -/** An offset from the start of the sBFT */ -typedef uint16_t sbft_off_t; - -/** - * SRP Boot Firmware Table - */ -struct sbft_table { - /** ACPI header */ - struct acpi_description_header acpi; - /** Offset to SCSI subtable */ - sbft_off_t scsi_offset; - /** Offset to SRP subtable */ - sbft_off_t srp_offset; - /** Offset to IB subtable, if present */ - sbft_off_t ib_offset; - /** Reserved */ - uint8_t reserved[6]; -} __attribute__ (( packed )); - -/** - * sBFT SCSI subtable - */ -struct sbft_scsi_subtable { - /** LUN */ - struct scsi_lun lun; -} __attribute__ (( packed )); - -/** - * sBFT SRP subtable - */ -struct sbft_srp_subtable { - /** Initiator and target ports */ - struct srp_port_ids port_ids; -} __attribute__ (( packed )); - -/** - * sBFT IB subtable - */ -struct sbft_ib_subtable { - /** Source GID */ - struct ib_gid sgid; - /** Destination GID */ - struct ib_gid dgid; - /** Service ID */ - struct ib_gid_half service_id; - /** Partition key */ - uint16_t pkey; - /** Reserved */ - uint8_t reserved[6]; -} __attribute__ (( packed )); - -/** - * An 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; - /** The IB subtable */ - struct sbft_ib_subtable ib; -} __attribute__ (( packed, aligned ( 16 ) )); - -struct srp_device; - -extern int sbft_fill_data ( struct srp_device *srp ); - -#endif /* _IPXE_SBFT_H */ diff --git a/src/arch/i386/interface/pcbios/abft.c b/src/arch/i386/interface/pcbios/abft.c deleted file mode 100644 index 9aa2f098..00000000 --- a/src/arch/i386/interface/pcbios/abft.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2007 Michael Brown . - * - * 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 -#include -#include -#include - -/** @file - * - * AoE Boot Firmware Table - * - */ - -#define abftab __use_data16 ( abftab ) -/** The aBFT used by iPXE */ -struct abft_table __data16 ( abftab ) __attribute__ (( aligned ( 16 ) )) = { - /* ACPI header */ - .acpi = { - .signature = ABFT_SIG, - .length = sizeof ( abftab ), - .revision = 1, - .oem_id = "FENSYS", - .oem_table_id = "iPXE", - }, -}; - -/** - * Fill in all variable portions of aBFT - * - * @v aoe AoE session - */ -void abft_fill_data ( struct aoe_session *aoe ) { - - /* Fill in boot parameters */ - abftab.shelf = aoe->major; - abftab.slot = aoe->minor; - memcpy ( abftab.mac, aoe->netdev->ll_addr, sizeof ( abftab.mac ) ); - - /* Update checksum */ - acpi_fix_checksum ( &abftab.acpi ); - - DBG ( "AoE boot firmware table:\n" ); - DBG_HD ( &abftab, sizeof ( abftab ) ); -} diff --git a/src/arch/i386/interface/pcbios/aoeboot.c b/src/arch/i386/interface/pcbios/aoeboot.c deleted file mode 100644 index 388cd140..00000000 --- a/src/arch/i386/interface/pcbios/aoeboot.c +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -FILE_LICENCE ( GPL2_OR_LATER ); - -static int aoeboot ( const char *root_path ) { - struct ata_device *ata; - struct int13_drive *drive; - int rc; - - ata = zalloc ( sizeof ( *ata ) ); - if ( ! ata ) { - rc = -ENOMEM; - goto err_alloc_ata; - } - drive = zalloc ( sizeof ( *drive ) ); - if ( ! drive ) { - rc = -ENOMEM; - goto err_alloc_drive; - } - - /* FIXME: ugly, ugly hack */ - struct net_device *netdev = last_opened_netdev(); - - if ( ( rc = aoe_attach ( ata, netdev, root_path ) ) != 0 ) { - printf ( "Could not attach AoE device: %s\n", - strerror ( rc ) ); - goto err_attach; - } - if ( ( rc = init_atadev ( ata ) ) != 0 ) { - printf ( "Could not initialise AoE device: %s\n", - strerror ( rc ) ); - goto err_init; - } - - /* FIXME: ugly, ugly hack */ - struct aoe_session *aoe = - container_of ( ata->backend, struct aoe_session, refcnt ); - abft_fill_data ( aoe ); - - drive->blockdev = &ata->blockdev; - - register_int13_drive ( drive ); - printf ( "Registered as BIOS drive %#02x\n", drive->drive ); - printf ( "Booting from BIOS drive %#02x\n", drive->drive ); - rc = int13_boot ( drive->drive ); - printf ( "Boot failed\n" ); - - /* Leave drive registered, if instructed to do so */ - if ( keep_san() ) - return rc; - - printf ( "Unregistering BIOS drive %#02x\n", drive->drive ); - unregister_int13_drive ( drive ); - - err_init: - aoe_detach ( ata ); - err_attach: - free ( drive ); - err_alloc_drive: - free ( ata ); - err_alloc_ata: - return rc; -} - -struct sanboot_protocol aoe_sanboot_protocol __sanboot_protocol = { - .prefix = "aoe:", - .boot = aoeboot, -}; diff --git a/src/arch/i386/interface/pcbios/ib_srpboot.c b/src/arch/i386/interface/pcbios/ib_srpboot.c deleted file mode 100644 index 711c1e93..00000000 --- a/src/arch/i386/interface/pcbios/ib_srpboot.c +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -FILE_LICENCE ( GPL2_OR_LATER ); - -static int ib_srpboot ( const char *root_path ) { - struct scsi_device *scsi; - struct int13_drive *drive; - int rc; - - scsi = zalloc ( sizeof ( *scsi ) ); - if ( ! scsi ) { - rc = -ENOMEM; - goto err_alloc_scsi; - } - drive = zalloc ( sizeof ( *drive ) ); - if ( ! drive ) { - rc = -ENOMEM; - goto err_alloc_drive; - } - - if ( ( rc = srp_attach ( scsi, root_path ) ) != 0 ) { - printf ( "Could not attach IB_SRP device: %s\n", - strerror ( rc ) ); - goto err_attach; - } - if ( ( rc = init_scsidev ( scsi ) ) != 0 ) { - printf ( "Could not initialise IB_SRP device: %s\n", - strerror ( rc ) ); - goto err_init; - } - - drive->blockdev = &scsi->blockdev; - - /* FIXME: ugly, ugly hack */ - struct srp_device *srp = - container_of ( scsi->backend, struct srp_device, refcnt ); - sbft_fill_data ( srp ); - - register_int13_drive ( drive ); - printf ( "Registered as BIOS drive %#02x\n", drive->drive ); - printf ( "Booting from BIOS drive %#02x\n", drive->drive ); - rc = int13_boot ( drive->drive ); - printf ( "Boot failed\n" ); - - /* Leave drive registered, if instructed to do so */ - if ( keep_san() ) - return rc; - - printf ( "Unregistering BIOS drive %#02x\n", drive->drive ); - unregister_int13_drive ( drive ); - - err_init: - srp_detach ( scsi ); - err_attach: - free ( drive ); - err_alloc_drive: - free ( scsi ); - err_alloc_scsi: - return rc; -} - -struct sanboot_protocol ib_srp_sanboot_protocol __sanboot_protocol = { - .prefix = "ib_srp:", - .boot = ib_srpboot, -}; diff --git a/src/arch/i386/interface/pcbios/int13.c b/src/arch/i386/interface/pcbios/int13.c index f72dc5fa..97110fde 100644 --- a/src/arch/i386/interface/pcbios/int13.c +++ b/src/arch/i386/interface/pcbios/int13.c @@ -19,6 +19,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include +#include #include #include #include @@ -26,6 +27,14 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -41,6 +50,77 @@ FILE_LICENCE ( GPL2_OR_LATER ); * */ +/** + * Overall timeout for INT 13 commands (independent of underlying device + * + * Underlying devices should ideally never become totally stuck. + * However, if they do, then the INT 13 mechanism provides no means + * for the caller to cancel the operation, and the machine appears to + * hang. Use an overall timeout for all commands to avoid this + * problem and bounce timeout failures to the caller. + */ +#define INT13_COMMAND_TIMEOUT ( 15 * TICKS_PER_SEC ) + +/** An INT 13 emulated drive */ +struct int13_drive { + /** Reference count */ + struct refcnt refcnt; + /** List of all registered drives */ + struct list_head list; + + /** Block device URI */ + struct uri *uri; + /** Underlying block device interface */ + struct interface block; + + /** BIOS in-use drive number (0x80-0xff) */ + unsigned int drive; + /** BIOS natural drive number (0x80-0xff) + * + * This is the drive number that would have been assigned by + * 'naturally' appending the drive to the end of the BIOS + * drive list. + * + * If the emulated drive replaces a preexisting drive, this is + * the drive number that the preexisting drive gets remapped + * to. + */ + unsigned int natural_drive; + + /** Block device capacity */ + struct block_device_capacity capacity; + + /** Number of cylinders + * + * The cylinder number field in an INT 13 call is ten bits + * wide, giving a maximum of 1024 cylinders. Conventionally, + * when the 7.8GB limit of a CHS address is exceeded, it is + * the number of cylinders that is increased beyond the + * addressable limit. + */ + unsigned int cylinders; + /** Number of heads + * + * The head number field in an INT 13 call is eight bits wide, + * giving a maximum of 256 heads. However, apparently all + * versions of MS-DOS up to and including Win95 fail with 256 + * heads, so the maximum encountered in practice is 255. + */ + unsigned int heads; + /** Number of sectors per track + * + * The sector number field in an INT 13 call is six bits wide, + * giving a maximum of 63 sectors, since sector numbering + * (unlike head and cylinder numbering) starts at 1, not 0. + */ + unsigned int sectors_per_track; + + /** Underlying device status, if in error */ + int block_rc; + /** Status of last operation */ + int last_status; +}; + /** Vector for chaining to other INT 13 handlers */ static struct segoff __text16 ( int13_vector ); #define int13_vector __use_text16 ( int13_vector ) @@ -49,7 +129,7 @@ static struct segoff __text16 ( int13_vector ); extern void int13_wrapper ( void ); /** List of registered emulated drives */ -static LIST_HEAD ( drives ); +static LIST_HEAD ( int13s ); /** * Number of BIOS drives @@ -60,19 +140,309 @@ static LIST_HEAD ( drives ); */ static uint8_t num_drives; +/** An INT 13 command */ +struct int13_command { + /** Status */ + int rc; + /** INT 13 drive */ + struct int13_drive *int13; + /** Underlying block device interface */ + struct interface block; + /** Command timeout timer */ + struct retry_timer timer; +}; + +/** + * Record INT 13 drive capacity + * + * @v command INT 13 command + * @v capacity Block device capacity + */ +static void int13_command_capacity ( struct int13_command *command, + struct block_device_capacity *capacity ) { + memcpy ( &command->int13->capacity, capacity, + sizeof ( command->int13->capacity ) ); +} + +/** + * Close INT 13 command + * + * @v command INT 13 command + * @v rc Reason for close + */ +static void int13_command_close ( struct int13_command *command, int rc ) { + intf_restart ( &command->block, rc ); + stop_timer ( &command->timer ); + command->rc = rc; +} + +/** + * Handle INT 13 command timer expiry + * + * @v timer Timer + */ +static void int13_command_expired ( struct retry_timer *timer, + int over __unused ) { + struct int13_command *command = + container_of ( timer, struct int13_command, timer ); + + int13_command_close ( command, -ETIMEDOUT ); +} + +/** INT 13 command interface operations */ +static struct interface_operation int13_command_op[] = { + INTF_OP ( intf_close, struct int13_command *, int13_command_close ), + INTF_OP ( block_capacity, struct int13_command *, + int13_command_capacity ), +}; + +/** INT 13 command interface descriptor */ +static struct interface_descriptor int13_command_desc = + INTF_DESC ( struct int13_command, block, int13_command_op ); + +/** + * Prepare to issue INT 13 command + * + * @v command INT 13 command + * @v int13 Emulated drive + * @ret rc Return status code + */ +static int int13_command_start ( struct int13_command *command, + struct int13_drive *int13 ) { + + /* Sanity check */ + assert ( command->int13 == NULL ); + assert ( ! timer_running ( &command->timer ) ); + + /* Initialise command */ + command->rc = -EINPROGRESS; + command->int13 = int13; + start_timer_fixed ( &command->timer, INT13_COMMAND_TIMEOUT ); + + /* Wait for block control interface to become ready */ + while ( ( command->rc == -EINPROGRESS ) && + ( xfer_window ( &int13->block ) == 0 ) ) { + step(); + } + + return ( ( command->rc == -EINPROGRESS ) ? + int13->block_rc : command->rc ); +} + +/** + * Wait for INT 13 command to complete + * + * @v command INT 13 command + * @ret rc Return status code + */ +static int int13_command_wait ( struct int13_command *command ) { + + /* Sanity check */ + assert ( timer_running ( &command->timer ) ); + + /* Wait for command to complete */ + while ( command->rc == -EINPROGRESS ) + step(); + + assert ( ! timer_running ( &command->timer ) ); + return command->rc; +} + +/** + * Terminate INT 13 command + * + * @v command INT 13 command + */ +static void int13_command_stop ( struct int13_command *command ) { + stop_timer ( &command->timer ); + command->int13 = NULL; +} + +/** The single active INT 13 command */ +static struct int13_command int13_command = { + .block = INTF_INIT ( int13_command_desc ), + .timer = TIMER_INIT ( int13_command_expired ), +}; + +/** + * Read from or write to INT 13 drive + * + * @v int13 Emulated drive + * @v lba Starting logical block address + * @v count Number of logical blocks + * @v buffer Data buffer + * @v block_rw Block read/write method + * @ret rc Return status code + */ +static int int13_rw ( struct int13_drive *int13, uint64_t lba, + unsigned int count, userptr_t buffer, + int ( * block_rw ) ( struct interface *control, + struct interface *data, + uint64_t lba, unsigned int count, + userptr_t buffer, size_t len ) ) { + struct int13_command *command = &int13_command; + unsigned int frag_count; + size_t frag_len; + int rc; + + while ( count ) { + + /* Determine fragment length */ + frag_count = count; + if ( frag_count > int13->capacity.max_count ) + frag_count = int13->capacity.max_count; + frag_len = ( int13->capacity.blksize * frag_count ); + + /* Issue command */ + if ( ( ( rc = int13_command_start ( command, int13 ) ) != 0 ) || + ( ( rc = block_rw ( &int13->block, &command->block, lba, + frag_count, buffer, + frag_len ) ) != 0 ) || + ( ( rc = int13_command_wait ( command ) ) != 0 ) ) { + int13_command_stop ( command ); + return rc; + } + int13_command_stop ( command ); + + /* Move to next fragment */ + lba += frag_count; + count -= frag_count; + buffer = userptr_add ( buffer, frag_len ); + } + + return 0; +} + +/** + * Read INT 13 drive capacity + * + * @v int13 Emulated drive + * @ret rc Return status code + */ +static int int13_read_capacity ( struct int13_drive *int13 ) { + struct int13_command *command = &int13_command; + int rc; + + /* Issue command */ + if ( ( ( rc = int13_command_start ( command, int13 ) ) != 0 ) || + ( ( rc = block_read_capacity ( &int13->block, + &command->block ) ) != 0 ) || + ( ( rc = int13_command_wait ( command ) ) != 0 ) ) { + int13_command_stop ( command ); + return rc; + } + + int13_command_stop ( command ); + return 0; +} + +/** + * Guess INT 13 drive geometry + * + * @v int13 Emulated drive + * @ret rc Return status code + * + * Guesses the drive geometry by inspecting the partition table. + */ +static int int13_guess_geometry ( struct int13_drive *int13 ) { + struct master_boot_record mbr; + struct partition_table_entry *partition; + unsigned int guessed_heads = 255; + unsigned int guessed_sectors_per_track = 63; + unsigned long blocks; + unsigned long blocks_per_cyl; + unsigned int i; + int rc; + + /* Don't even try when the blksize is invalid for C/H/S access */ + if ( int13->capacity.blksize != INT13_BLKSIZE ) + return 0; + + /* Read partition table */ + if ( ( rc = int13_rw ( int13, 0, 1, virt_to_user ( &mbr ), + block_read ) ) != 0 ) { + DBGC ( int13, "INT13 drive %02x could not read partition " + "table to guess geometry: %s\n", + int13->drive, strerror ( rc ) ); + return rc; + } + + /* Scan through partition table and modify guesses for heads + * and sectors_per_track if we find any used partitions. + */ + for ( i = 0 ; i < 4 ; i++ ) { + partition = &mbr.partitions[i]; + if ( ! partition->type ) + continue; + guessed_heads = ( PART_HEAD ( partition->chs_end ) + 1 ); + guessed_sectors_per_track = PART_SECTOR ( partition->chs_end ); + DBGC ( int13, "INT13 drive %02x guessing C/H/S xx/%d/%d based " + "on partition %d\n", int13->drive, guessed_heads, + guessed_sectors_per_track, ( i + 1 ) ); + } + + /* Apply guesses if no geometry already specified */ + if ( ! int13->heads ) + int13->heads = guessed_heads; + if ( ! int13->sectors_per_track ) + int13->sectors_per_track = guessed_sectors_per_track; + if ( ! int13->cylinders ) { + /* Avoid attempting a 64-bit divide on a 32-bit system */ + blocks = ( ( int13->capacity.blocks <= ULONG_MAX ) ? + int13->capacity.blocks : ULONG_MAX ); + blocks_per_cyl = ( int13->heads * int13->sectors_per_track ); + assert ( blocks_per_cyl != 0 ); + int13->cylinders = ( blocks / blocks_per_cyl ); + if ( int13->cylinders > 1024 ) + int13->cylinders = 1024; + } + + return 0; +} + +/** + * Open (or reopen) INT 13 emulated drive underlying block device + * + * @v int13 Emulated drive + * @ret rc Return status code + */ +static int int13_reopen_block ( struct int13_drive *int13 ) { + int rc; + + /* Close any existing block device */ + intf_restart ( &int13->block, -ECONNRESET ); + + /* Open block device */ + if ( ( rc = xfer_open_uri ( &int13->block, int13->uri ) ) != 0 ) { + DBGC ( int13, "INT13 drive %02x could not reopen block " + "device: %s\n", int13->drive, strerror ( rc ) ); + int13->block_rc = rc; + return rc; + } + + /* Clear block device error status */ + int13->block_rc = 0; + + /* Read device capacity */ + if ( ( rc = int13_read_capacity ( int13 ) ) != 0 ) + return rc; + + return 0; +} + /** * Update BIOS drive count */ static void int13_set_num_drives ( void ) { - struct int13_drive *drive; + struct int13_drive *int13; /* Get current drive count */ get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES ); /* Ensure count is large enough to cover all of our emulated drives */ - list_for_each_entry ( drive, &drives, list ) { - if ( num_drives <= ( drive->drive & 0x7f ) ) - num_drives = ( ( drive->drive & 0x7f ) + 1 ); + list_for_each_entry ( int13, &int13s, list ) { + if ( num_drives <= ( int13->drive & 0x7f ) ) + num_drives = ( ( int13->drive & 0x7f ) + 1 ); } /* Update current drive count */ @@ -96,48 +466,56 @@ static void int13_check_num_drives ( void ) { /** * INT 13, 00 - Reset disk system * - * @v drive Emulated drive + * @v int13 Emulated drive * @ret status Status code */ -static int int13_reset ( struct int13_drive *drive __unused, +static int int13_reset ( struct int13_drive *int13, struct i386_all_regs *ix86 __unused ) { - DBG ( "Reset drive\n" ); + int rc; + + DBGC2 ( int13, "Reset drive\n" ); + + /* Reopen underlying block device */ + if ( ( rc = int13_reopen_block ( int13 ) ) != 0 ) + return -INT13_STATUS_RESET_FAILED; + return 0; } /** * INT 13, 01 - Get status of last operation * - * @v drive Emulated drive + * @v int13 Emulated drive * @ret status Status code */ -static int int13_get_last_status ( struct int13_drive *drive, +static int int13_get_last_status ( struct int13_drive *int13, struct i386_all_regs *ix86 __unused ) { - DBG ( "Get status of last operation\n" ); - return drive->last_status; + DBGC2 ( int13, "Get status of last operation\n" ); + return int13->last_status; } /** * Read / write sectors * - * @v drive Emulated drive + * @v int13 Emulated drive * @v al Number of sectors to read or write (must be nonzero) * @v ch Low bits of cylinder number * @v cl (bits 7:6) High bits of cylinder number * @v cl (bits 5:0) Sector number * @v dh Head number * @v es:bx Data buffer - * @v io Read / write method + * @v block_rw Block read/write method * @ret status Status code * @ret al Number of sectors read or written */ -static int int13_rw_sectors ( struct int13_drive *drive, +static int int13_rw_sectors ( struct int13_drive *int13, struct i386_all_regs *ix86, - int ( * io ) ( struct block_device *blockdev, - uint64_t block, - unsigned long count, - userptr_t buffer ) ) { - struct block_device *blockdev = drive->blockdev; + int ( * block_rw ) ( struct interface *control, + struct interface *data, + uint64_t lba, + unsigned int count, + userptr_t buffer, + size_t len ) ) { unsigned int cylinder, head, sector; unsigned long lba; unsigned int count; @@ -145,30 +523,33 @@ static int int13_rw_sectors ( struct int13_drive *drive, int rc; /* Validate blocksize */ - if ( blockdev->blksize != INT13_BLKSIZE ) { - DBG ( "Invalid blocksize (%zd) for non-extended read/write\n", - blockdev->blksize ); + if ( int13->capacity.blksize != INT13_BLKSIZE ) { + DBGC ( int13, "\nINT 13 drive %02x invalid blocksize (%zd) " + "for non-extended read/write\n", + int13->drive, int13->capacity.blksize ); return -INT13_STATUS_INVALID; } /* Calculate parameters */ cylinder = ( ( ( ix86->regs.cl & 0xc0 ) << 2 ) | ix86->regs.ch ); - assert ( cylinder < drive->cylinders ); + assert ( cylinder < int13->cylinders ); head = ix86->regs.dh; - assert ( head < drive->heads ); + assert ( head < int13->heads ); sector = ( ix86->regs.cl & 0x3f ); - assert ( ( sector >= 1 ) && ( sector <= drive->sectors_per_track ) ); - lba = ( ( ( ( cylinder * drive->heads ) + head ) - * drive->sectors_per_track ) + sector - 1 ); + assert ( ( sector >= 1 ) && ( sector <= int13->sectors_per_track ) ); + lba = ( ( ( ( cylinder * int13->heads ) + head ) + * int13->sectors_per_track ) + sector - 1 ); count = ix86->regs.al; buffer = real_to_user ( ix86->segs.es, ix86->regs.bx ); - DBG ( "C/H/S %d/%d/%d = LBA %#lx <-> %04x:%04x (count %d)\n", cylinder, - head, sector, lba, ix86->segs.es, ix86->regs.bx, count ); + DBGC2 ( int13, "C/H/S %d/%d/%d = LBA %08lx <-> %04x:%04x (count %d)\n", + cylinder, head, sector, lba, ix86->segs.es, ix86->regs.bx, + count ); /* Read from / write to block device */ - if ( ( rc = io ( blockdev, lba, count, buffer ) ) != 0 ) { - DBG ( "INT 13 failed: %s\n", strerror ( rc ) ); + if ( ( rc = int13_rw ( int13, lba, count, buffer, block_rw ) ) != 0 ) { + DBGC ( int13, "INT13 drive %02x I/O failed: %s\n", + int13->drive, strerror ( rc ) ); return -INT13_STATUS_READ_ERROR; } @@ -178,7 +559,7 @@ static int int13_rw_sectors ( struct int13_drive *drive, /** * INT 13, 02 - Read sectors * - * @v drive Emulated drive + * @v int13 Emulated drive * @v al Number of sectors to read (must be nonzero) * @v ch Low bits of cylinder number * @v cl (bits 7:6) High bits of cylinder number @@ -188,16 +569,16 @@ static int int13_rw_sectors ( struct int13_drive *drive, * @ret status Status code * @ret al Number of sectors read */ -static int int13_read_sectors ( struct int13_drive *drive, +static int int13_read_sectors ( struct int13_drive *int13, struct i386_all_regs *ix86 ) { - DBG ( "Read: " ); - return int13_rw_sectors ( drive, ix86, drive->blockdev->op->read ); + DBGC2 ( int13, "Read: " ); + return int13_rw_sectors ( int13, ix86, block_read ); } /** * INT 13, 03 - Write sectors * - * @v drive Emulated drive + * @v int13 Emulated drive * @v al Number of sectors to write (must be nonzero) * @v ch Low bits of cylinder number * @v cl (bits 7:6) High bits of cylinder number @@ -207,16 +588,16 @@ static int int13_read_sectors ( struct int13_drive *drive, * @ret status Status code * @ret al Number of sectors written */ -static int int13_write_sectors ( struct int13_drive *drive, +static int int13_write_sectors ( struct int13_drive *int13, struct i386_all_regs *ix86 ) { - DBG ( "Write: " ); - return int13_rw_sectors ( drive, ix86, drive->blockdev->op->write ); + DBGC2 ( int13, "Write: " ); + return int13_rw_sectors ( int13, ix86, block_write ); } /** * INT 13, 08 - Get drive parameters * - * @v drive Emulated drive + * @v int13 Emulated drive * @ret status Status code * @ret ch Low bits of maximum cylinder number * @ret cl (bits 7:6) High bits of maximum cylinder number @@ -224,13 +605,13 @@ static int int13_write_sectors ( struct int13_drive *drive, * @ret dh Maximum head number * @ret dl Number of drives */ -static int int13_get_parameters ( struct int13_drive *drive, +static int int13_get_parameters ( struct int13_drive *int13, struct i386_all_regs *ix86 ) { - unsigned int max_cylinder = drive->cylinders - 1; - unsigned int max_head = drive->heads - 1; - unsigned int max_sector = drive->sectors_per_track; /* sic */ + unsigned int max_cylinder = int13->cylinders - 1; + unsigned int max_head = int13->heads - 1; + unsigned int max_sector = int13->sectors_per_track; /* sic */ - DBG ( "Get drive parameters\n" ); + DBGC2 ( int13, "Get drive parameters\n" ); ix86->regs.ch = ( max_cylinder & 0xff ); ix86->regs.cl = ( ( ( max_cylinder >> 8 ) << 6 ) | max_sector ); @@ -242,18 +623,18 @@ static int int13_get_parameters ( struct int13_drive *drive, /** * INT 13, 15 - Get disk type * - * @v drive Emulated drive + * @v int13 Emulated drive * @ret ah Type code * @ret cx:dx Sector count * @ret status Status code / disk type */ -static int int13_get_disk_type ( struct int13_drive *drive, +static int int13_get_disk_type ( struct int13_drive *int13, struct i386_all_regs *ix86 ) { uint32_t blocks; - DBG ( "Get disk type\n" ); - blocks = ( ( drive->blockdev->blocks <= 0xffffffffUL ) ? - drive->blockdev->blocks : 0xffffffffUL ); + DBGC2 ( int13, "Get disk type\n" ); + blocks = ( ( int13->capacity.blocks <= 0xffffffffUL ) ? + int13->capacity.blocks : 0xffffffffUL ); ix86->regs.cx = ( blocks >> 16 ); ix86->regs.dx = ( blocks & 0xffff ); return INT13_DISK_TYPE_HDD; @@ -262,16 +643,16 @@ static int int13_get_disk_type ( struct int13_drive *drive, /** * INT 13, 41 - Extensions installation check * - * @v drive Emulated drive + * @v int13 Emulated drive * @v bx 0x55aa * @ret bx 0xaa55 * @ret cx Extensions API support bitmap * @ret status Status code / API version */ -static int int13_extension_check ( struct int13_drive *drive __unused, +static int int13_extension_check ( struct int13_drive *int13 __unused, struct i386_all_regs *ix86 ) { if ( ix86->regs.bx == 0x55aa ) { - DBG ( "INT 13 extensions installation check\n" ); + DBGC2 ( int13, "INT13 extensions installation check\n" ); ix86->regs.bx = 0xaa55; ix86->regs.cx = INT13_EXTENSION_LINEAR; return INT13_EXTENSION_VER_1_X; @@ -283,18 +664,19 @@ static int int13_extension_check ( struct int13_drive *drive __unused, /** * Extended read / write * - * @v drive Emulated drive + * @v int13 Emulated drive * @v ds:si Disk address packet - * @v io Read / write method + * @v block_rw Block read/write method * @ret status Status code */ -static int int13_extended_rw ( struct int13_drive *drive, +static int int13_extended_rw ( struct int13_drive *int13, struct i386_all_regs *ix86, - int ( * io ) ( struct block_device *blockdev, - uint64_t block, - unsigned long count, - userptr_t buffer ) ) { - struct block_device *blockdev = drive->blockdev; + int ( * block_rw ) ( struct interface *control, + struct interface *data, + uint64_t lba, + unsigned int count, + userptr_t buffer, + size_t len ) ) { struct int13_disk_address addr; uint64_t lba; unsigned long count; @@ -307,12 +689,14 @@ static int int13_extended_rw ( struct int13_drive *drive, count = addr.count; buffer = real_to_user ( addr.buffer.segment, addr.buffer.offset ); - DBG ( "LBA %#llx <-> %04x:%04x (count %ld)\n", (unsigned long long)lba, - addr.buffer.segment, addr.buffer.offset, count ); + DBGC2 ( int13, "LBA %08llx <-> %04x:%04x (count %ld)\n", + ( ( unsigned long long ) lba ), addr.buffer.segment, + addr.buffer.offset, count ); /* Read from / write to block device */ - if ( ( rc = io ( blockdev, lba, count, buffer ) ) != 0 ) { - DBG ( "INT 13 failed: %s\n", strerror ( rc ) ); + if ( ( rc = int13_rw ( int13, lba, count, buffer, block_rw ) ) != 0 ) { + DBGC ( int13, "INT13 drive %02x extended I/O failed: %s\n", + int13->drive, strerror ( rc ) ); return -INT13_STATUS_READ_ERROR; } @@ -322,50 +706,50 @@ static int int13_extended_rw ( struct int13_drive *drive, /** * INT 13, 42 - Extended read * - * @v drive Emulated drive + * @v int13 Emulated drive * @v ds:si Disk address packet * @ret status Status code */ -static int int13_extended_read ( struct int13_drive *drive, +static int int13_extended_read ( struct int13_drive *int13, struct i386_all_regs *ix86 ) { - DBG ( "Extended read: " ); - return int13_extended_rw ( drive, ix86, drive->blockdev->op->read ); + DBGC2 ( int13, "Extended read: " ); + return int13_extended_rw ( int13, ix86, block_read ); } /** * INT 13, 43 - Extended write * - * @v drive Emulated drive + * @v int13 Emulated drive * @v ds:si Disk address packet * @ret status Status code */ -static int int13_extended_write ( struct int13_drive *drive, +static int int13_extended_write ( struct int13_drive *int13, struct i386_all_regs *ix86 ) { - DBG ( "Extended write: " ); - return int13_extended_rw ( drive, ix86, drive->blockdev->op->write ); + DBGC2 ( int13, "Extended write: " ); + return int13_extended_rw ( int13, ix86, block_write ); } /** * INT 13, 48 - Get extended parameters * - * @v drive Emulated drive + * @v int13 Emulated drive * @v ds:si Drive parameter table * @ret status Status code */ -static int int13_get_extended_parameters ( struct int13_drive *drive, +static int int13_get_extended_parameters ( struct int13_drive *int13, struct i386_all_regs *ix86 ) { struct int13_disk_parameters params = { .bufsize = sizeof ( params ), .flags = INT13_FL_DMA_TRANSPARENT, - .cylinders = drive->cylinders, - .heads = drive->heads, - .sectors_per_track = drive->sectors_per_track, - .sectors = drive->blockdev->blocks, - .sector_size = drive->blockdev->blksize, + .cylinders = int13->cylinders, + .heads = int13->heads, + .sectors_per_track = int13->sectors_per_track, + .sectors = int13->capacity.blocks, + .sector_size = int13->capacity.blksize, }; - DBG ( "Get extended drive parameters to %04x:%04x\n", - ix86->segs.ds, ix86->regs.si ); + DBGC2 ( int13, "Get extended drive parameters to %04x:%04x\n", + ix86->segs.ds, ix86->regs.si ); copy_to_real ( ix86->segs.ds, ix86->regs.si, ¶ms, sizeof ( params ) ); @@ -379,72 +763,74 @@ static int int13_get_extended_parameters ( struct int13_drive *drive, static __asmcall void int13 ( struct i386_all_regs *ix86 ) { int command = ix86->regs.ah; unsigned int bios_drive = ix86->regs.dl; - struct int13_drive *drive; + struct int13_drive *int13; int status; /* Check BIOS hasn't killed off our drive */ int13_check_num_drives(); - list_for_each_entry ( drive, &drives, list ) { + list_for_each_entry ( int13, &int13s, list ) { - if ( bios_drive != drive->drive ) { + if ( bios_drive != int13->drive ) { /* Remap any accesses to this drive's natural number */ - if ( bios_drive == drive->natural_drive ) { - DBG ( "INT 13,%04x (%02x) remapped to " - "(%02x)\n", ix86->regs.ax, - bios_drive, drive->drive ); - ix86->regs.dl = drive->drive; + if ( bios_drive == int13->natural_drive ) { + DBGC2 ( int13, "INT13,%02x (%02x) remapped to " + "(%02x)\n", ix86->regs.ah, + bios_drive, int13->drive ); + ix86->regs.dl = int13->drive; return; } continue; } - DBG ( "INT 13,%04x (%02x): ", ix86->regs.ax, drive->drive ); + DBGC2 ( int13, "INT13,%02x (%02x): ", + ix86->regs.ah, int13->drive ); switch ( command ) { case INT13_RESET: - status = int13_reset ( drive, ix86 ); + status = int13_reset ( int13, ix86 ); break; case INT13_GET_LAST_STATUS: - status = int13_get_last_status ( drive, ix86 ); + status = int13_get_last_status ( int13, ix86 ); break; case INT13_READ_SECTORS: - status = int13_read_sectors ( drive, ix86 ); + status = int13_read_sectors ( int13, ix86 ); break; case INT13_WRITE_SECTORS: - status = int13_write_sectors ( drive, ix86 ); + status = int13_write_sectors ( int13, ix86 ); break; case INT13_GET_PARAMETERS: - status = int13_get_parameters ( drive, ix86 ); + status = int13_get_parameters ( int13, ix86 ); break; case INT13_GET_DISK_TYPE: - status = int13_get_disk_type ( drive, ix86 ); + status = int13_get_disk_type ( int13, ix86 ); break; case INT13_EXTENSION_CHECK: - status = int13_extension_check ( drive, ix86 ); + status = int13_extension_check ( int13, ix86 ); break; case INT13_EXTENDED_READ: - status = int13_extended_read ( drive, ix86 ); + status = int13_extended_read ( int13, ix86 ); break; case INT13_EXTENDED_WRITE: - status = int13_extended_write ( drive, ix86 ); + status = int13_extended_write ( int13, ix86 ); break; case INT13_GET_EXTENDED_PARAMETERS: - status = int13_get_extended_parameters ( drive, ix86 ); + status = int13_get_extended_parameters ( int13, ix86 ); break; default: - DBG ( "*** Unrecognised INT 13 ***\n" ); + DBGC2 ( int13, "*** Unrecognised INT13 ***\n" ); status = -INT13_STATUS_INVALID; break; } /* Store status for INT 13,01 */ - drive->last_status = status; + int13->last_status = status; /* Negative status indicates an error */ if ( status < 0 ) { status = -status; - DBG ( "INT 13 returning failure status %x\n", status ); + DBGC ( int13, "INT13,%02x (%02x) failed with status " + "%02x\n", ix86->regs.ah, int13->drive, status ); } else { ix86->flags &= ~CF; } @@ -461,7 +847,7 @@ static __asmcall void int13 ( struct i386_all_regs *ix86 ) { * Hook INT 13 handler * */ -static void hook_int13 ( void ) { +static void int13_hook_vector ( void ) { /* Assembly wrapper to call int13(). int13() sets OF if we * should not chain to the previous handler. (The wrapper * clears CF and OF before calling int13()). @@ -517,136 +903,188 @@ static void hook_int13 ( void ) { /** * Unhook INT 13 handler */ -static void unhook_int13 ( void ) { +static void int13_unhook_vector ( void ) { unhook_bios_interrupt ( 0x13, ( unsigned int ) int13_wrapper, &int13_vector ); } /** - * Guess INT 13 drive geometry + * Handle INT 13 emulated drive underlying block device closing * - * @v drive Emulated drive - * - * Guesses the drive geometry by inspecting the partition table. + * @v int13 Emulated drive + * @v rc Reason for close */ -static void guess_int13_geometry ( struct int13_drive *drive ) { - struct master_boot_record mbr; - struct partition_table_entry *partition; - unsigned int guessed_heads = 255; - unsigned int guessed_sectors_per_track = 63; - unsigned long blocks; - unsigned long blocks_per_cyl; - unsigned int i; +static void int13_block_close ( struct int13_drive *int13, int rc ) { - /* Don't even try when the blksize is invalid for C/H/S access */ - if ( drive->blockdev->blksize != INT13_BLKSIZE ) - return; + /* Any closing is an error from our point of view */ + if ( rc == 0 ) + rc = -ENOTCONN; - /* Scan through partition table and modify guesses for heads - * and sectors_per_track if we find any used partitions. + DBGC ( int13, "INT13 drive %02x went away: %s\n", + int13->drive, strerror ( rc ) ); + + /* Record block device error code */ + int13->block_rc = rc; + + /* Shut down interfaces */ + intf_restart ( &int13->block, rc ); + + /* Further INT 13 calls will fail immediately. The caller may + * use INT 13,00 to reset the drive. */ - if ( drive->blockdev->op->read ( drive->blockdev, 0, 1, - virt_to_user ( &mbr ) ) == 0 ) { - for ( i = 0 ; i < 4 ; i++ ) { - partition = &mbr.partitions[i]; - if ( ! partition->type ) - continue; - guessed_heads = - ( PART_HEAD ( partition->chs_end ) + 1 ); - guessed_sectors_per_track = - PART_SECTOR ( partition->chs_end ); - DBG ( "Guessing C/H/S xx/%d/%d based on partition " - "%d\n", guessed_heads, - guessed_sectors_per_track, ( i + 1 ) ); - } - } else { - DBG ( "Could not read partition table to guess geometry\n" ); - } +} - /* Apply guesses if no geometry already specified */ - if ( ! drive->heads ) - drive->heads = guessed_heads; - if ( ! drive->sectors_per_track ) - drive->sectors_per_track = guessed_sectors_per_track; - if ( ! drive->cylinders ) { - /* Avoid attempting a 64-bit divide on a 32-bit system */ - blocks = ( ( drive->blockdev->blocks <= ULONG_MAX ) ? - drive->blockdev->blocks : ULONG_MAX ); - blocks_per_cyl = ( drive->heads * drive->sectors_per_track ); - assert ( blocks_per_cyl != 0 ); - drive->cylinders = ( blocks / blocks_per_cyl ); - if ( drive->cylinders > 1024 ) - drive->cylinders = 1024; - } +/** INT 13 drive interface operations */ +static struct interface_operation int13_block_op[] = { + INTF_OP ( intf_close, struct int13_drive *, int13_block_close ), +}; + +/** INT 13 drive interface descriptor */ +static struct interface_descriptor int13_block_desc = + INTF_DESC ( struct int13_drive, block, int13_block_op ); + +/** + * Free INT 13 emulated drive + * + * @v refcnt Reference count + */ +static void int13_free ( struct refcnt *refcnt ) { + struct int13_drive *int13 = + container_of ( refcnt, struct int13_drive, refcnt ); + + uri_put ( int13->uri ); + free ( int13 ); } /** - * Register INT 13 emulated drive + * Hook INT 13 emulated drive * - * @v drive Emulated drive + * @v uri URI + * @v drive Requested drive number + * @ret drive Assigned drive number, or negative error * * Registers the drive with the INT 13 emulation subsystem, and hooks * the INT 13 interrupt vector (if not already hooked). - * - * The underlying block device must be valid. A drive number and - * geometry will be assigned if left blank. */ -void register_int13_drive ( struct int13_drive *drive ) { +static int int13_hook ( struct uri *uri, unsigned int drive ) { + struct int13_drive *int13; uint8_t num_drives; + unsigned int natural_drive; + int rc; - /* Give drive a default geometry if none specified */ - guess_int13_geometry ( drive ); - - /* Assign natural drive number */ + /* Calculate drive number */ get_real ( num_drives, BDA_SEG, BDA_NUM_DRIVES ); - drive->natural_drive = ( num_drives | 0x80 ); + natural_drive = ( num_drives | 0x80 ); + if ( drive == INT13_USE_NATURAL_DRIVE ) + drive = natural_drive; + drive |= 0x80; - /* Assign drive number */ - if ( ( drive->drive & 0xff ) == 0xff ) { - /* Drive number == -1 => use natural drive number */ - drive->drive = drive->natural_drive; - } else { - /* Use specified drive number (+0x80 if necessary) */ - drive->drive |= 0x80; + /* Check that drive number is not in use */ + list_for_each_entry ( int13, &int13s, list ) { + if ( int13->drive == drive ) { + rc = -EADDRINUSE; + goto err_in_use; + } } - DBG ( "Registered INT13 drive %02x (naturally %02x) with C/H/S " - "geometry %d/%d/%d\n", drive->drive, drive->natural_drive, - drive->cylinders, drive->heads, drive->sectors_per_track ); + /* Allocate and initialise structure */ + int13 = zalloc ( sizeof ( *int13 ) ); + if ( ! int13 ) { + rc = -ENOMEM; + goto err_zalloc; + } + ref_init ( &int13->refcnt, int13_free ); + intf_init ( &int13->block, &int13_block_desc, &int13->refcnt ); + int13->uri = uri_get ( uri ); + int13->drive = drive; + int13->natural_drive = natural_drive; + + /* Open block device interface */ + if ( ( rc = int13_reopen_block ( int13 ) ) != 0 ) + goto err_reopen_block; + + /* Give drive a default geometry */ + if ( ( rc = int13_guess_geometry ( int13 ) ) != 0 ) + goto err_guess_geometry; + + DBGC ( int13, "INT13 drive %02x (naturally %02x) registered with C/H/S " + "geometry %d/%d/%d\n", int13->drive, int13->natural_drive, + int13->cylinders, int13->heads, int13->sectors_per_track ); /* Hook INT 13 vector if not already hooked */ - if ( list_empty ( &drives ) ) - hook_int13(); + if ( list_empty ( &int13s ) ) + int13_hook_vector(); /* Add to list of emulated drives */ - list_add ( &drive->list, &drives ); + list_add ( &int13->list, &int13s ); /* Update BIOS drive count */ int13_set_num_drives(); + + return int13->drive; + + err_guess_geometry: + err_reopen_block: + intf_shutdown ( &int13->block, rc ); + ref_put ( &int13->refcnt ); + err_zalloc: + err_in_use: + return rc; } /** - * Unregister INT 13 emulated drive + * Find INT 13 emulated drive by drive number * - * @v drive Emulated drive + * @v drive Drive number + * @ret int13 Emulated drive, or NULL + */ +static struct int13_drive * int13_find ( unsigned int drive ) { + struct int13_drive *int13; + + list_for_each_entry ( int13, &int13s, list ) { + if ( int13->drive == drive ) + return int13; + } + return NULL; +} + +/** + * Unhook INT 13 emulated drive + * + * @v drive Drive number * * Unregisters the drive from the INT 13 emulation subsystem. If this * is the last emulated drive, the INT 13 vector is unhooked (if * possible). */ -void unregister_int13_drive ( struct int13_drive *drive ) { - /* Remove from list of emulated drives */ - list_del ( &drive->list ); +static void int13_unhook ( unsigned int drive ) { + struct int13_drive *int13; - /* Should adjust BIOS drive count, but it's difficult to do so - * reliably. + /* Find drive */ + int13 = int13_find ( drive ); + if ( ! int13 ) { + DBG ( "INT13 cannot find emulated drive %02x\n", drive ); + return; + } + + /* Shut down interfaces */ + intf_shutdown ( &int13->block, 0 ); + + /* Remove from list of emulated drives */ + list_del ( &int13->list ); + + /* Should adjust BIOS drive count, but it's difficult + * to do so reliably. */ - DBG ( "Unregistered INT13 drive %02x\n", drive->drive ); + DBGC ( int13, "INT13 drive %02x unregsitered\n", int13->drive ); /* Unhook INT 13 vector if no more drives */ - if ( list_empty ( &drives ) ) - unhook_int13(); + if ( list_empty ( &int13s ) ) + int13_unhook_vector(); + + /* Drop list's reference to drive */ + ref_put ( &int13->refcnt ); } /** @@ -662,13 +1100,13 @@ void unregister_int13_drive ( struct int13_drive *drive ) { * * Note that this function can never return success, by definition. */ -int int13_boot ( unsigned int drive ) { +static int int13_boot ( unsigned int drive ) { struct memory_map memmap; int status, signature; int discard_c, discard_d; int rc; - DBG ( "Booting from INT 13 drive %02x\n", drive ); + DBG ( "INT13 drive %02x booting\n", drive ); /* Use INT 13 to read the boot sector */ __asm__ __volatile__ ( REAL_CODE ( "pushw %%es\n\t" @@ -692,8 +1130,8 @@ int int13_boot ( unsigned int drive ) { /* Check signature is correct */ if ( signature != be16_to_cpu ( 0x55aa ) ) { - DBG ( "Invalid disk signature %#04x (should be 0x55aa)\n", - cpu_to_be16 ( signature ) ); + DBG ( "INT13 drive %02x invalid disk signature %#04x (should " + "be 0x55aa)\n", drive, cpu_to_be16 ( signature ) ); return -ENOEXEC; } @@ -706,10 +1144,74 @@ int int13_boot ( unsigned int drive ) { /* Jump to boot sector */ if ( ( rc = call_bootsector ( 0x0, 0x7c00, drive ) ) != 0 ) { - DBG ( "INT 13 drive %02x boot returned: %s\n", + DBG ( "INT13 drive %02x boot returned: %s\n", drive, strerror ( rc ) ); return rc; } return -ECANCELED; /* -EIMPOSSIBLE */ } + +/** A boot firmware table generated by iPXE */ +union xbft_table { + /** ACPI header */ + struct acpi_description_header acpi; + /** Padding */ + char pad[768]; +}; + +/** The boot firmware table generated by iPXE */ +static union xbft_table __bss16 ( xbftab ) __attribute__ (( aligned ( 16 ) )); +#define xbftab __use_data16 ( xbftab ) + +/** + * Describe INT 13 emulated drive for SAN-booted operating system + * + * @v drive Drive number + * @ret rc Return status code + */ +static int int13_describe ( unsigned int drive ) { + struct int13_drive *int13; + struct segoff xbft_address; + int rc; + + /* Find drive */ + int13 = int13_find ( drive ); + if ( ! int13 ) { + DBG ( "INT13 cannot find emulated drive %02x\n", drive ); + return -ENODEV; + } + + /* Clear table */ + memset ( &xbftab, 0, sizeof ( xbftab ) ); + + /* Fill in common parameters */ + strncpy ( xbftab.acpi.oem_id, "FENSYS", + sizeof ( xbftab.acpi.oem_id ) ); + strncpy ( xbftab.acpi.oem_table_id, "iPXE", + sizeof ( xbftab.acpi.oem_table_id ) ); + + /* Fill in remaining parameters */ + if ( ( rc = acpi_describe ( &int13->block, &xbftab.acpi, + sizeof ( xbftab ) ) ) != 0 ) { + DBGC ( int13, "INT13 drive %02x could not create ACPI " + "description: %s\n", int13->drive, strerror ( rc ) ); + return rc; + } + + /* Fix up ACPI checksum */ + acpi_fix_checksum ( &xbftab.acpi ); + xbft_address.segment = rm_ds; + xbft_address.offset = __from_data16 ( &xbftab ); + DBGC ( int13, "INT13 drive %02x described using boot firmware " + "table:\n", int13->drive ); + DBGC_HDA ( int13, xbft_address, &xbftab, + le32_to_cpu ( xbftab.acpi.length ) ); + + return 0; +} + +PROVIDE_SANBOOT ( pcbios, san_hook, int13_hook ); +PROVIDE_SANBOOT ( pcbios, san_unhook, int13_unhook ); +PROVIDE_SANBOOT ( pcbios, san_boot, int13_boot ); +PROVIDE_SANBOOT ( pcbios, san_describe, int13_describe ); diff --git a/src/arch/i386/interface/pcbios/iscsiboot.c b/src/arch/i386/interface/pcbios/iscsiboot.c deleted file mode 100644 index 12c5566d..00000000 --- a/src/arch/i386/interface/pcbios/iscsiboot.c +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -FILE_LICENCE ( GPL2_OR_LATER ); - -static int iscsiboot ( const char *root_path ) { - struct scsi_device *scsi; - struct int13_drive *drive; - int rc; - - scsi = zalloc ( sizeof ( *scsi ) ); - if ( ! scsi ) { - rc = -ENOMEM; - goto err_alloc_scsi; - } - drive = zalloc ( sizeof ( *drive ) ); - if ( ! drive ) { - rc = -ENOMEM; - goto err_alloc_drive; - } - - if ( ( rc = iscsi_attach ( scsi, root_path ) ) != 0 ) { - printf ( "Could not attach iSCSI device: %s\n", - strerror ( rc ) ); - goto err_attach; - } - if ( ( rc = init_scsidev ( scsi ) ) != 0 ) { - printf ( "Could not initialise iSCSI device: %s\n", - strerror ( rc ) ); - goto err_init; - } - - drive->blockdev = &scsi->blockdev; - - /* FIXME: ugly, ugly hack */ - struct net_device *netdev = last_opened_netdev(); - struct iscsi_session *iscsi = - container_of ( scsi->backend, struct iscsi_session, refcnt ); - ibft_fill_data ( netdev, iscsi ); - - register_int13_drive ( drive ); - printf ( "Registered as BIOS drive %#02x\n", drive->drive ); - printf ( "Booting from BIOS drive %#02x\n", drive->drive ); - rc = int13_boot ( drive->drive ); - printf ( "Boot failed\n" ); - - /* Leave drive registered, if instructed to do so */ - if ( keep_san() ) - return rc; - - printf ( "Unregistering BIOS drive %#02x\n", drive->drive ); - unregister_int13_drive ( drive ); - - err_init: - iscsi_detach ( scsi ); - err_attach: - free ( drive ); - err_alloc_drive: - free ( scsi ); - err_alloc_scsi: - return rc; -} - -struct sanboot_protocol iscsi_sanboot_protocol __sanboot_protocol = { - .prefix = "iscsi:", - .boot = iscsiboot, -}; diff --git a/src/arch/i386/interface/pcbios/keepsan.c b/src/arch/i386/interface/pcbios/keepsan.c deleted file mode 100644 index b2a93177..00000000 --- a/src/arch/i386/interface/pcbios/keepsan.c +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -struct setting keep_san_setting __setting = { - .name = "keep-san", - .description = "Preserve SAN connection", - .tag = DHCP_EB_KEEP_SAN, - .type = &setting_type_int8, -}; - -int keep_san ( void ) { - int keep_san; - - keep_san = fetch_intz_setting ( NULL, &keep_san_setting ); - if ( ! keep_san ) - return 0; - - printf ( "Preserving connection to SAN disk\n" ); - shutdown_exit_flags |= SHUTDOWN_KEEP_DEVICES; - return 1; -} diff --git a/src/arch/i386/interface/pcbios/sbft.c b/src/arch/i386/interface/pcbios/sbft.c deleted file mode 100644 index 6d51020a..00000000 --- a/src/arch/i386/interface/pcbios/sbft.c +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2009 Fen Systems Ltd . - * 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 ); - -/** @file - * - * SRP boot firmware table - * - */ - -#include -#include -#include -#include -#include -#include - -#define sbftab __use_data16 ( sbftab ) -/** The sBFT used by iPXE */ -struct ipxe_sbft __data16 ( sbftab ) = { - /* Table header */ - .table = { - /* ACPI header */ - .acpi = { - .signature = SBFT_SIG, - .length = sizeof ( sbftab ), - .revision = 1, - .oem_id = "FENSYS", - .oem_table_id = "iPXE", - }, - .scsi_offset = offsetof ( typeof ( sbftab ), scsi ), - .srp_offset = offsetof ( typeof ( sbftab ), srp ), - .ib_offset = offsetof ( typeof ( sbftab ), ib ), - }, -}; - -/** - * Fill in all variable portions of sBFT - * - * @v srp SRP device - * @ret rc Return status code - */ -int sbft_fill_data ( struct srp_device *srp ) { - struct sbft_scsi_subtable *sbft_scsi = &sbftab.scsi; - struct sbft_srp_subtable *sbft_srp = &sbftab.srp; - struct sbft_ib_subtable *sbft_ib = &sbftab.ib; - struct ib_srp_parameters *ib_params; - struct segoff rm_sbftab = { - .segment = rm_ds, - .offset = __from_data16 ( &sbftab ), - }; - - /* Fill in the SCSI subtable */ - memcpy ( &sbft_scsi->lun, &srp->lun, sizeof ( sbft_scsi->lun ) ); - - /* Fill in the SRP subtable */ - memcpy ( &sbft_srp->port_ids, &srp->port_ids, - sizeof ( sbft_srp->port_ids ) ); - - /* Fill in the IB subtable */ - assert ( srp->transport == &ib_srp_transport ); - ib_params = ib_srp_params ( srp ); - memcpy ( &sbft_ib->sgid, &ib_params->sgid, sizeof ( sbft_ib->sgid ) ); - memcpy ( &sbft_ib->dgid, &ib_params->dgid, sizeof ( sbft_ib->dgid ) ); - memcpy ( &sbft_ib->service_id, &ib_params->service_id, - sizeof ( sbft_ib->service_id ) ); - sbft_ib->pkey = ib_params->pkey; - - /* Update checksum */ - acpi_fix_checksum ( &sbftab.table.acpi ); - - DBGC ( &sbftab, "SRP Boot Firmware Table at %04x:%04x:\n", - rm_sbftab.segment, rm_sbftab.offset ); - DBGC_HDA ( &sbftab, rm_sbftab, &sbftab, sizeof ( sbftab ) ); - - return 0; -} diff --git a/src/arch/x86_64/include/bits/sanboot.h b/src/arch/x86_64/include/bits/sanboot.h new file mode 100644 index 00000000..d33d03cb --- /dev/null +++ b/src/arch/x86_64/include/bits/sanboot.h @@ -0,0 +1,12 @@ +#ifndef _BITS_SANBOOT_H +#define _BITS_SANBOOT_H + +/** @file + * + * x86_64-specific sanboot API implementations + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#endif /* _BITS_SANBOOT_H */ diff --git a/src/config/config.c b/src/config/config.c index fcf27f04..46df6bd2 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -133,7 +133,7 @@ REQUIRE_OBJECT ( slam ); * */ #ifdef SANBOOT_PROTO_ISCSI -REQUIRE_OBJECT ( iscsiboot ); +REQUIRE_OBJECT ( iscsi ); #endif /* diff --git a/src/config/config_ethernet.c b/src/config/config_ethernet.c index 51c197d7..f6cb87bc 100644 --- a/src/config/config_ethernet.c +++ b/src/config/config_ethernet.c @@ -19,5 +19,5 @@ FILE_LICENCE ( GPL2_OR_LATER ); * Drag in Ethernet-specific protocols */ #ifdef SANBOOT_PROTO_AOE -REQUIRE_OBJECT ( aoeboot ); +REQUIRE_OBJECT ( aoe ); #endif diff --git a/src/config/config_infiniband.c b/src/config/config_infiniband.c index 1b8c53a9..432e621d 100644 --- a/src/config/config_infiniband.c +++ b/src/config/config_infiniband.c @@ -19,5 +19,5 @@ FILE_LICENCE ( GPL2_OR_LATER ); * Drag in Infiniband-specific protocols */ #ifdef SANBOOT_PROTO_IB_SRP -REQUIRE_OBJECT ( ib_srpboot ); +REQUIRE_OBJECT ( ib_srp ); #endif diff --git a/src/config/defaults/efi.h b/src/config/defaults/efi.h index 35e5fde9..9c3f3ff3 100644 --- a/src/config/defaults/efi.h +++ b/src/config/defaults/efi.h @@ -15,6 +15,7 @@ #define NAP_EFIX86 #define UMALLOC_EFI #define SMBIOS_EFI +#define SANBOOT_NULL #define IMAGE_EFI /* EFI image support */ #define IMAGE_SCRIPT /* iPXE script image support */ diff --git a/src/config/defaults/linux.h b/src/config/defaults/linux.h index 901097fd..647dc0a5 100644 --- a/src/config/defaults/linux.h +++ b/src/config/defaults/linux.h @@ -13,6 +13,7 @@ #define UMALLOC_LINUX #define NAP_LINUX #define SMBIOS_LINUX +#define SANBOOT_NULL #define DRIVERS_LINUX diff --git a/src/config/defaults/pcbios.h b/src/config/defaults/pcbios.h index 246a8cd9..d2ffc3d2 100644 --- a/src/config/defaults/pcbios.h +++ b/src/config/defaults/pcbios.h @@ -17,6 +17,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define NAP_PCBIOS #define UMALLOC_MEMTOP #define SMBIOS_PCBIOS +#define SANBOOT_PCBIOS #define IMAGE_ELF /* ELF image support */ #define IMAGE_MULTIBOOT /* MultiBoot image support */ diff --git a/src/config/sanboot.h b/src/config/sanboot.h new file mode 100644 index 00000000..1d7f5f17 --- /dev/null +++ b/src/config/sanboot.h @@ -0,0 +1,16 @@ +#ifndef CONFIG_SANBOOT_H +#define CONFIG_SANBOOT_H + +/** @file + * + * sanboot API configuration + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include + +#include + +#endif /* CONFIG_SANBOOT_H */ diff --git a/src/core/acpi.c b/src/core/acpi.c index d573d2e9..223765f7 100644 --- a/src/core/acpi.c +++ b/src/core/acpi.c @@ -18,7 +18,9 @@ FILE_LICENCE ( GPL2_OR_LATER ); +#include #include +#include /** @file * @@ -26,6 +28,13 @@ FILE_LICENCE ( GPL2_OR_LATER ); * */ +/****************************************************************************** + * + * Utility functions + * + ****************************************************************************** + */ + /** * Fix up ACPI table checksum * @@ -40,3 +49,37 @@ void acpi_fix_checksum ( struct acpi_description_header *acpi ) { } acpi->checksum -= sum; } + +/****************************************************************************** + * + * Interface methods + * + ****************************************************************************** + */ + +/** + * Describe object in an ACPI table + * + * @v intf Interface + * @v acpi ACPI table + * @v len Length of ACPI table + * @ret rc Return status code + */ +int acpi_describe ( struct interface *intf, + struct acpi_description_header *acpi, size_t len ) { + struct interface *dest; + acpi_describe_TYPE ( void * ) *op = + intf_get_dest_op ( intf, acpi_describe, &dest ); + void *object = intf_object ( dest ); + int rc; + + if ( op ) { + rc = op ( object, acpi, len ); + } else { + /* Default is to fail to describe */ + rc = -EOPNOTSUPP; + } + + intf_put ( dest ); + return rc; +} diff --git a/src/core/blockdev.c b/src/core/blockdev.c new file mode 100644 index 00000000..182765e3 --- /dev/null +++ b/src/core/blockdev.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2010 Michael Brown . + * + * 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 +#include +#include + +/** @file + * + * Block devices + * + */ + +/** + * Read from block device + * + * @v control Control interface + * @v data Data interface + * @v lba Starting logical block address + * @v count Number of logical blocks + * @v buffer Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int block_read ( struct interface *control, struct interface *data, + uint64_t lba, unsigned int count, + userptr_t buffer, size_t len ) { + struct interface *dest; + block_read_TYPE ( void * ) *op = + intf_get_dest_op ( control, block_read, &dest ); + void *object = intf_object ( dest ); + int rc; + + if ( op ) { + rc = op ( object, data, lba, count, buffer, len ); + } else { + /* Default is to fail to issue the command */ + rc = -EOPNOTSUPP; + } + + intf_put ( dest ); + return rc; +} + +/** + * Write to block device + * + * @v control Control interface + * @v data Data interface + * @v lba Starting logical block address + * @v count Number of logical blocks + * @v buffer Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int block_write ( struct interface *control, struct interface *data, + uint64_t lba, unsigned int count, + userptr_t buffer, size_t len ) { + struct interface *dest; + block_write_TYPE ( void * ) *op = + intf_get_dest_op ( control, block_write, &dest ); + void *object = intf_object ( dest ); + int rc; + + if ( op ) { + rc = op ( object, data, lba, count, buffer, len ); + } else { + /* Default is to fail to issue the command */ + rc = -EOPNOTSUPP; + } + + intf_put ( dest ); + return rc; +} + +/** + * Read block device capacity + * + * @v control Control interface + * @v data Data interface + * @ret rc Return status code + */ +int block_read_capacity ( struct interface *control, struct interface *data ) { + struct interface *dest; + block_read_capacity_TYPE ( void * ) *op = + intf_get_dest_op ( control, block_read_capacity, &dest ); + void *object = intf_object ( dest ); + int rc; + + if ( op ) { + rc = op ( object, data ); + } else { + /* Default is to fail to issue the command */ + rc = -EOPNOTSUPP; + } + + intf_put ( dest ); + return rc; +} + +/** + * Report block device capacity + * + * @v intf Interface + * @v capacity Block device capacity + */ +void block_capacity ( struct interface *intf, + struct block_device_capacity *capacity ) { + struct interface *dest; + block_capacity_TYPE ( void * ) *op = + intf_get_dest_op ( intf, block_capacity, &dest ); + void *object = intf_object ( dest ); + + if ( op ) { + op ( object, capacity ); + } else { + /* Default is to do nothing */ + } + + intf_put ( dest ); +} diff --git a/src/core/null_sanboot.c b/src/core/null_sanboot.c new file mode 100644 index 00000000..9cdb1629 --- /dev/null +++ b/src/core/null_sanboot.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010 Michael Brown . + * + * 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 +#include + +static int null_san_hook ( struct uri *uri __unused, + unsigned int drive __unused ) { + return -EOPNOTSUPP; +} + +static void null_san_unhook ( unsigned int drive __unused ) { + /* Do nothing */ +} + +static int null_san_boot ( unsigned int drive __unused ) { + return -EOPNOTSUPP; +} + +static int null_san_describe ( unsigned int drive __unused ) { + return -EOPNOTSUPP; +} + +PROVIDE_SANBOOT ( null, san_hook, null_san_hook ); +PROVIDE_SANBOOT ( null, san_unhook, null_san_unhook ); +PROVIDE_SANBOOT ( null, san_boot, null_san_boot ); +PROVIDE_SANBOOT ( null, san_describe, null_san_describe ); diff --git a/src/drivers/block/ata.c b/src/drivers/block/ata.c index c59e788a..0197483b 100644 --- a/src/drivers/block/ata.c +++ b/src/drivers/block/ata.c @@ -19,12 +19,14 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include +#include #include #include #include #include +#include +#include #include -#include #include /** @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; - 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 ); - - /* Flag command as in-progress */ - command->rc = -EINPROGRESS; - - /* 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; - } - - /* 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; - } - - return 0; -} - -/** - * Read block from ATA device - * - * @v blockdev Block device - * @v block LBA block number - * @v count Block count - * @v buffer 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; - - 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_READ_EXT : ATA_CMD_READ ); - command.data_in = buffer; - return ata_command ( ata, &command ); -} - -/** - * Write block to ATA device - * - * @v blockdev Block device - * @v block LBA block number - * @v count Block count - * @v buffer 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 ); -} - -/** - * Identify ATA device - * - * @v blockdev Block device - * @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; - - /* 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 ); + if ( op ) { + tag = op ( object, data, command ); } else { - blockdev->blocks = le32_to_cpu ( identity.lba_sectors ); + /* Default is to fail to issue the command */ + tag = -EOPNOTSUPP; } - return 0; + + intf_put ( dest ); + return tag; } -static struct block_device_operations ata_operations = { - .read = ata_read, - .write = ata_write +/****************************************************************************** + * + * 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 ); }; /** - * Initialise ATA device + * Get reference to ATA device * - * @v ata ATA device - * @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. + * @v atadev ATA device + * @ret atadev ATA device */ -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 ); +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 ); + } +} + +/** 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 ); + +/** + * 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/src/arch/i386/interface/pcbios/ibft.c b/src/drivers/block/ibft.c similarity index 63% rename from src/arch/i386/interface/pcbios/ibft.c rename to src/drivers/block/ibft.c index 63dba65e..adf1d7d5 100644 --- a/src/arch/i386/interface/pcbios/ibft.c +++ b/src/drivers/block/ibft.c @@ -32,7 +32,6 @@ FILE_LICENCE ( BSD2 ); #include #include #include -#include #include #include #include @@ -53,62 +52,36 @@ FILE_LICENCE ( BSD2 ); * */ -#define ibftab __use_data16 ( ibftab ) -/** The iBFT used by iPXE */ -struct ipxe_ibft __data16 ( ibftab ) = { - /* Table header */ - .table = { - /* ACPI header */ - .acpi = { - .signature = IBFT_SIG, - .length = sizeof ( ibftab ), - .revision = 1, - .oem_id = "FENSYS", - .oem_table_id = "iPXE", - }, - /* Control block */ - .control = { - .header = { - .structure_id = IBFT_STRUCTURE_ID_CONTROL, - .version = 1, - .length = sizeof ( ibftab.table.control ), - .flags = 0, - }, - .initiator = offsetof ( typeof ( ibftab ), initiator ), - .nic_0 = offsetof ( typeof ( ibftab ), nic ), - .target_0 = offsetof ( typeof ( ibftab ), target ), - }, - }, - /* iSCSI initiator information */ - .initiator = { - .header = { - .structure_id = IBFT_STRUCTURE_ID_INITIATOR, - .version = 1, - .length = sizeof ( ibftab.initiator ), - .flags = ( IBFT_FL_INITIATOR_BLOCK_VALID | - IBFT_FL_INITIATOR_FIRMWARE_BOOT_SELECTED ), - }, - }, - /* NIC information */ - .nic = { - .header = { - .structure_id = IBFT_STRUCTURE_ID_NIC, - .version = 1, - .length = sizeof ( ibftab.nic ), - .flags = ( IBFT_FL_NIC_BLOCK_VALID | - IBFT_FL_NIC_FIRMWARE_BOOT_SELECTED ), - }, - }, - /* iSCSI target information */ - .target = { - .header = { - .structure_id = IBFT_STRUCTURE_ID_TARGET, - .version = 1, - .length = sizeof ( ibftab.target ), - .flags = ( IBFT_FL_TARGET_BLOCK_VALID | - IBFT_FL_TARGET_FIRMWARE_BOOT_SELECTED ), - }, - }, +/** + * 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; }; /** @@ -132,9 +105,9 @@ static void ibft_set_ipaddr ( struct ibft_ipaddr *ipaddr, struct in_addr in ) { * @v setting Configuration setting * @v tag DHCP option tag */ -static void ibft_set_ipaddr_option ( struct ibft_ipaddr *ipaddr, - struct setting *setting ) { - struct in_addr in = { 0 }; +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 ); } @@ -156,22 +129,19 @@ static const char * ibft_ipaddr ( struct ibft_ipaddr *ipaddr ) { * @v strings iBFT string block descriptor * @v string String field to fill in * @v len Length of string to allocate (excluding NUL) - * @ret rc Return status code + * @ret dest String destination, or NULL */ -static int ibft_alloc_string ( struct ibft_string_block *strings, - struct ibft_string *string, size_t len ) { - char *dest; - unsigned int remaining; +static char * ibft_alloc_string ( struct ibft_strings *strings, + struct ibft_string *string, size_t len ) { - dest = ( ( ( char * ) strings->table ) + strings->offset ); - remaining = ( strings->table->acpi.length - strings->offset ); - if ( len >= remaining ) - return -ENOMEM; + if ( ( strings->offset + len ) >= strings->len ) + return NULL; - string->offset = strings->offset; - string->length = len; + string->offset = cpu_to_le16 ( strings->offset ); + string->len = cpu_to_le16 ( len ); strings->offset += ( len + 1 ); - return 0; + + return ( ( ( char * ) strings->table ) + string->offset ); } /** @@ -182,18 +152,16 @@ static int ibft_alloc_string ( struct ibft_string_block *strings, * @v data String to fill in, or NULL * @ret rc Return status code */ -static int ibft_set_string ( struct ibft_string_block *strings, +static int ibft_set_string ( struct ibft_strings *strings, struct ibft_string *string, const char *data ) { char *dest; - int rc; if ( ! data ) return 0; - if ( ( rc = ibft_alloc_string ( strings, string, - strlen ( data ) ) ) != 0 ) - return rc; - dest = ( ( ( char * ) strings->table ) + string->offset ); + dest = ibft_alloc_string ( strings, string, strlen ( data ) ); + if ( ! dest ) + return -ENOBUFS; strcpy ( dest, data ); return 0; @@ -207,24 +175,24 @@ static int ibft_set_string ( struct ibft_string_block *strings, * @v setting Configuration setting * @ret rc Return status code */ -static int ibft_set_string_option ( struct ibft_string_block *strings, - struct ibft_string *string, - struct setting *setting ) { +static int ibft_set_string_setting ( struct ibft_strings *strings, + struct ibft_string *string, + struct setting *setting ) { int len; char *dest; - int rc; len = fetch_setting_len ( NULL, setting ); if ( len < 0 ) { string->offset = 0; - string->length = 0; + string->len = 0; return 0; } - if ( ( rc = ibft_alloc_string ( strings, string, len ) ) != 0 ) - return rc; - dest = ( ( ( char * ) strings->table ) + string->offset ); + dest = ibft_alloc_string ( strings, string, len ); + if ( ! dest ) + return -ENOBUFS; fetch_string_setting ( NULL, setting, dest, ( len + 1 ) ); + return 0; } @@ -235,7 +203,7 @@ static int ibft_set_string_option ( struct ibft_string_block *strings, * @v string String field * @ret data String content (or "") */ -static const char * ibft_string ( struct ibft_string_block *strings, +static const char * ibft_string ( struct ibft_strings *strings, struct ibft_string *string ) { return ( string->offset ? ( ( ( char * ) strings->table ) + string->offset ) : NULL ); @@ -250,22 +218,29 @@ static const char * ibft_string ( struct ibft_string_block *strings, * @ret rc Return status code */ static int ibft_fill_nic ( struct ibft_nic *nic, - struct ibft_string_block *strings, + 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; - /* Extract values from DHCP configuration */ - ibft_set_ipaddr_option ( &nic->ip_address, &ip_setting ); + /* 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_option ( &nic->gateway, &gateway_setting ); + ibft_set_ipaddr_setting ( &nic->gateway, &gateway_setting ); DBG ( "iBFT NIC gateway = %s\n", ibft_ipaddr ( &nic->gateway ) ); - ibft_set_ipaddr_option ( &nic->dns[0], &dns_setting ); + 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_option ( strings, &nic->hostname, - &hostname_setting ) ) != 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 ) ); @@ -287,8 +262,8 @@ static int ibft_fill_nic ( struct ibft_nic *nic, return rc; } DBG ( "iBFT NIC MAC = %s\n", eth_ntoa ( nic->mac_address ) ); - nic->pci_bus_dev_func = netdev->dev->desc.location; - DBG ( "iBFT NIC PCI = %04x\n", nic->pci_bus_dev_func ); + 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; } @@ -301,10 +276,18 @@ static int ibft_fill_nic ( struct ibft_nic *nic, * @ret rc Return status code */ static int ibft_fill_initiator ( struct ibft_initiator *initiator, - struct ibft_string_block *strings ) { + 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; @@ -323,7 +306,7 @@ static int ibft_fill_initiator ( struct ibft_initiator *initiator, * @ret rc Return status code */ static int ibft_fill_target_chap ( struct ibft_target *target, - struct ibft_string_block *strings, + struct ibft_strings *strings, struct iscsi_session *iscsi ) { int rc; @@ -356,7 +339,7 @@ static int ibft_fill_target_chap ( struct ibft_target *target, * @ret rc Return status code */ static int ibft_fill_target_reverse_chap ( struct ibft_target *target, - struct ibft_string_block *strings, + struct ibft_strings *strings, struct iscsi_session *iscsi ) { int rc; @@ -391,17 +374,27 @@ static int ibft_fill_target_reverse_chap ( struct ibft_target *target, * @ret rc Return status code */ static int ibft_fill_target ( struct ibft_target *target, - struct ibft_string_block *strings, + 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 = ntohs ( sin_target->sin_port ); + 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; @@ -417,35 +410,62 @@ static int ibft_fill_target ( struct ibft_target *target, } /** - * Fill in all variable portions of iBFT + * Fill in iBFT * - * @v netdev Network device - * @v initiator_iqn Initiator IQN - * @v st_target Target socket address - * @v target_iqn Target IQN + * @v iscsi iSCSI session + * @v acpi ACPI table + * @v len Length of ACPI table * @ret rc Return status code - * */ -int ibft_fill_data ( struct net_device *netdev, - struct iscsi_session *iscsi ) { - struct ibft_string_block strings = { - .table = &ibftab.table, - .offset = offsetof ( typeof ( ibftab ), strings ), +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; - /* Fill in NIC, Initiator and Target portions */ - if ( ( rc = ibft_fill_nic ( &ibftab.nic, &strings, netdev ) ) != 0 ) + /* 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 ( &ibftab.initiator, + if ( ( rc = ibft_fill_initiator ( &ibft->initiator, &strings ) ) != 0 ) return rc; - if ( ( rc = ibft_fill_target ( &ibftab.target, &strings, + if ( ( rc = ibft_fill_target ( &ibft->target, &strings, iscsi ) ) != 0 ) return rc; - /* Update checksum */ - acpi_fix_checksum ( &ibftab.table.acpi ); - return 0; } diff --git a/src/drivers/block/ramdisk.c b/src/drivers/block/ramdisk.c deleted file mode 100644 index 55c0d2d5..00000000 --- a/src/drivers/block/ramdisk.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2007 Michael Brown . - * - * 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 -#include - -/** - * @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 94e283b6..d7f7b4db 100644 --- a/src/drivers/block/scsi.c +++ b/src/drivers/block/scsi.c @@ -23,8 +23,8 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include -#include #include /** @file @@ -33,308 +33,15 @@ FILE_LICENCE ( GPL2_OR_LATER ); * */ -/** Maximum number of dummy "read capacity (10)" operations +/** Maximum number of command retries */ +#define SCSICMD_MAX_RETRIES 10 + +/****************************************************************************** * - * These are issued at connection setup to draw out various useless - * power-on messages. + * Utility functions + * + ****************************************************************************** */ -#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 - * - * @v scsi SCSI device - * @v command SCSI command - * @ret rc Return status code - */ -int scsi_detached_command ( struct scsi_device *scsi __unused, - struct scsi_command *command __unused ) { - return -ENODEV; -} - -/** - * Issue SCSI command - * - * @v scsi SCSI device - * @v command SCSI command - * @ret rc Return status code - */ -static int scsi_command ( struct scsi_device *scsi, - struct scsi_command *command ) { - int rc; - - DBGC2 ( scsi, "SCSI %p " SCSI_CDB_FORMAT "\n", - scsi, SCSI_CDB_DATA ( command->cdb ) ); - - /* Clear sense response code before issuing command */ - command->sense_response = 0; - - /* Flag command as in-progress */ - command->rc = -EINPROGRESS; - - /* 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; - } - - /* 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; - } - - /* 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; - } - - return 0; -} - -/** - * Read block from SCSI device using READ (10) - * - * @v blockdev Block device - * @v block LBA block number - * @v count Block count - * @v buffer Data buffer - * @ret rc Return status code - */ -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; - - /* 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 ); -} - -/** - * Read block from SCSI device using READ (16) - * - * @v blockdev Block device - * @v block LBA block number - * @v count Block count - * @v buffer Data buffer - * @ret rc Return status code - */ -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; - - /* 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 ); -} - -/** - * Write block to SCSI device using WRITE (10) - * - * @v blockdev Block device - * @v block LBA block number - * @v count Block count - * @v buffer Data buffer - * @ret rc Return status code - */ -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; - - /* 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 ); -} - -/** - * Write block to SCSI device using WRITE (16) - * - * @v blockdev Block device - * @v block LBA block number - * @v count Block count - * @v buffer Data buffer - * @ret rc Return status code - */ -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; - - /* 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 ); -} - -/** - * Read capacity of SCSI device via READ CAPACITY (10) - * - * @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; - int rc; - - /* Issue READ CAPACITY (10) */ - 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 ) - return rc; - - /* Fill in block device fields */ - blockdev->blksize = be32_to_cpu ( capacity.blksize ); - blockdev->blocks = ( be32_to_cpu ( capacity.lba ) + 1 ); - - return 0; -} - -/** - * Read capacity of SCSI device via READ CAPACITY (16) - * - * @v blockdev Block device - * @ret rc Return status code - */ -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; - - /* 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 ); - - if ( ( rc = scsi_command ( scsi, &command ) ) != 0 ) - return rc; - - /* Fill in block device fields */ - blockdev->blksize = be32_to_cpu ( capacity.blksize ); - blockdev->blocks = ( be64_to_cpu ( capacity.lba ) + 1 ); - return 0; -} - -static struct block_device_operations scsi_operations_16 = { - .read = scsi_read_16, - .write = scsi_write_16, -}; - -static struct block_device_operations scsi_operations_10 = { - .read = scsi_read_10, - .write = scsi_write_10, -}; - -/** - * Initialise SCSI device - * - * @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. - */ -int init_scsidev ( struct scsi_device *scsi ) { - unsigned int i; - int rc; - - /* 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 ) ); - } - - /* 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; - } - - /* 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; - } - } - - 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 ); - - return 0; -} /** * Parse SCSI LUN @@ -364,3 +71,700 @@ int scsi_parse_lun ( const char *lun_string, struct scsi_lun *lun ) { return 0; } + +/****************************************************************************** + * + * 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; + + /** 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; +} + +/** + * 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; + + 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 ); + } +} + +/** + * 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, +}; + +/** 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 ); +} + +/** + * 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 a8deab15..4b592e95 100644 --- a/src/drivers/block/srp.c +++ b/src/drivers/block/srp.c @@ -36,7 +36,6 @@ FILE_LICENCE ( BSD2 ); #include #include #include -#include #include /** @@ -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 -static void srp_login ( struct srp_device *srp ); -static void srp_cmd ( struct srp_device *srp ); +/** 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; +}; /** - * Mark SRP SCSI command as complete + * Get reference to SRP device * - * @v srp SRP device - * @v rc Status code + * @v srpdev SRP device + * @ret srpdev SRP device */ -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 )) struct srp_device * +srpdev_get ( struct srp_device *srpdev ) { + ref_get ( &srpdev->refcnt ); + return srpdev; } /** - * Handle SRP session failure + * Drop reference to SRP device * - * @v srp SRP device - * @v rc Reason for failure + * @v srpdev SRP device */ -static void srp_fail ( struct srp_device *srp, int rc ) { +static inline __attribute__ (( always_inline )) void +srpdev_put ( struct srp_device *srpdev ) { + ref_put ( &srpdev->refcnt ); +} - /* Close underlying socket */ - intf_restart ( &srp->socket, rc ); +/** + * 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; +} - /* Clear session state */ - srp->state = 0; +/** + * 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 ); +} - /* If we have reached the retry limit, report the failure */ - if ( srp->retry_count >= SRP_MAX_RETRIES ) { - srp_scsi_done ( srp, rc ); - return; +/** + * 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 ) ); } - /* 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 ) { +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; - assert ( ! ( srp->state & SRP_STATE_SOCKET_OPEN ) ); - - /* 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; - } - 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; - } + 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; + } + 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; } - /* 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 ) ); - } - } - 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 ) ); - } - srp->command->status = rsp->status; + /* Hold command reference for remainder of function */ + srpcmd_get ( srpcmd ); - /* Mark SCSI command as complete */ - srp_scsi_done ( srp, 0 ); + /* 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); + } + sense = srp_rsp_sense_data ( rsp ); + if ( sense ) + memcpy ( &response.sense, sense, sizeof ( response.sense ) ); - rc = 0; - out: - free_iob ( iobuf ); + /* 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; } /** - * Handle SRP unrecognised response + * Receive data from SRP socket * - * @v srp SRP device - * @v iobuf I/O buffer - * @ret rc Returns status code - */ -static int srp_unrecognised ( struct srp_device *srp, - struct io_buffer *iobuf ) { - struct srp_common *common = iobuf->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 ); - - free_iob ( iobuf ); - return -ENOTSUP; -} - -/** - * Receive data from underlying 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 ), -}; - -/** SRP data transfer interface descriptor */ -static struct interface_descriptor srp_xfer_desc = - INTF_DESC ( struct srp_device, socket, srp_xfer_operations ); +/** + * 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 ); +} /** - * Issue SCSI command via SRP + * 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 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 */ - } - - return 0; -} - -/** - * Attach SRP device - * - * @v scsi SCSI device - * @v root_path Root path - */ -int srp_attach ( struct scsi_device *scsi, const char *root_path ) { - struct srp_transport_type *transport; - struct srp_device *srp; +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; - /* Hard-code an IB SRP back-end for now */ - transport = &ib_srp_transport; + /* Sanity check */ + if ( len < sizeof ( *sbft ) ) + return -ENOBUFS; - /* Allocate and initialise structure */ - srp = zalloc ( sizeof ( *srp ) + transport->priv_len ); - if ( ! srp ) { - rc = -ENOMEM; - goto err_alloc; - } - 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 ); + /* 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 ) ); - /* 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; + /* 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; } - /* Attach parent interface, mortalise self, and return */ - scsi->backend = ref_get ( &srp->refcnt ); - scsi->command = srp_command; - ref_put ( &srp->refcnt ); return 0; - - err_parse_root_path: - ref_put ( &srp->refcnt ); - err_alloc: - return rc; } +/** 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 ); + /** - * Detach SRP device + * Open SRP device * - * @v scsi SCSI 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 */ -void srp_detach ( struct scsi_device *scsi ) { - struct srp_device *srp = - container_of ( scsi->backend, struct srp_device, refcnt ); +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; - /* Close socket */ - intf_shutdown ( &srp->socket, 0 ); - scsi->command = scsi_detached_command; - ref_put ( scsi->backend ); - scsi->backend = NULL; + /* 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; } diff --git a/src/include/ipxe/acpi.h b/src/include/ipxe/acpi.h index eabbec39..282b6d92 100644 --- a/src/include/ipxe/acpi.h +++ b/src/include/ipxe/acpi.h @@ -10,6 +10,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include +#include /** * An ACPI description header @@ -19,7 +20,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); */ struct acpi_description_header { /** ACPI signature (4 ASCII characters) */ - char signature[4]; + uint32_t signature; /** Length of table, in bytes, including header */ uint32_t length; /** ACPI Specification minor version number */ @@ -38,6 +39,25 @@ struct acpi_description_header { uint32_t asl_compiler_revision; } __attribute__ (( packed )); +/** + * Build ACPI signature + * + * @v a First character of ACPI signature + * @v b Second character of ACPI signature + * @v c Third character of ACPI signature + * @v d Fourth character of ACPI signature + * @ret signature ACPI signature + */ +#define ACPI_SIGNATURE( a, b, c, d ) \ + ( ( (a) << 0 ) | ( (b) << 8 ) | ( (c) << 16 ) | ( (d) << 24 ) ) + +extern int acpi_describe ( struct interface *interface, + struct acpi_description_header *acpi, size_t len ); +#define acpi_describe_TYPE( object_type ) \ + typeof ( int ( object_type, \ + struct acpi_description_header *acpi, \ + size_t len ) ) + extern void acpi_fix_checksum ( struct acpi_description_header *acpi ); #endif /* _IPXE_ACPI_H */ diff --git a/src/include/ipxe/aoe.h b/src/include/ipxe/aoe.h index d2a12858..60f3bd95 100644 --- a/src/include/ipxe/aoe.h +++ b/src/include/ipxe/aoe.h @@ -14,10 +14,11 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include /** An AoE config command */ struct aoecfg { - /** AoE Queue depth */ + /** AoE queue depth */ uint16_t bufcnt; /** ATA target firmware version */ uint16_t fwver; @@ -78,7 +79,7 @@ struct aoehdr { /** Tag, in network byte order */ uint32_t tag; /** Payload */ - union aoecmd cmd[0]; + union aoecmd payload[0]; } __attribute__ (( packed )); #define AOE_VERSION 0x10 /**< Version 1 */ @@ -93,58 +94,38 @@ struct aoehdr { #define AOE_CMD_ATA 0x00 /**< Issue ATA command */ #define AOE_CMD_CONFIG 0x01 /**< Query Config Information */ -#define AOE_TAG_MAGIC 0xebeb0000 - #define AOE_ERR_BAD_COMMAND 1 /**< Unrecognised command code */ #define AOE_ERR_BAD_PARAMETER 2 /**< Bad argument parameter */ #define AOE_ERR_UNAVAILABLE 3 /**< Device unavailable */ #define AOE_ERR_CONFIG_EXISTS 4 /**< Config string present */ #define AOE_ERR_BAD_VERSION 5 /**< Unsupported version */ -/** An AoE session */ -struct aoe_session { - /** Reference counter */ - struct refcnt refcnt; - - /** List of all AoE sessions */ - struct list_head list; - - /** Network device */ - struct net_device *netdev; - - /** Major number */ - uint16_t major; - /** Minor number */ - uint8_t minor; - /** Target MAC address */ - uint8_t target[ETH_ALEN]; - - /** Tag for current AoE command */ - uint32_t tag; - - /** Current AOE command */ - uint8_t aoe_cmd_type; - /** Current ATA command */ - struct ata_command *command; - /** Overall status of current ATA command */ - unsigned int status; - /** Byte offset within command's data buffer */ - unsigned int command_offset; - /** Return status code for command */ - int rc; - - /** Retransmission timer */ - struct retry_timer timer; -}; - #define AOE_STATUS_ERR_MASK 0x0f /**< Error portion of status code */ #define AOE_STATUS_PENDING 0x80 /**< Command pending */ +/** AoE tag magic marker */ +#define AOE_TAG_MAGIC 0x18ae0000 + /** Maximum number of sectors per packet */ #define AOE_MAX_COUNT 2 -extern void aoe_detach ( struct ata_device *ata ); -extern int aoe_attach ( struct ata_device *ata, struct net_device *netdev, - const char *root_path ); +/** AoE boot firmware table signature */ +#define ABFT_SIG ACPI_SIGNATURE ( 'a', 'B', 'F', 'T' ) + +/** + * AoE Boot Firmware Table (aBFT) + */ +struct abft_table { + /** ACPI header */ + struct acpi_description_header acpi; + /** AoE shelf */ + uint16_t shelf; + /** AoE slot */ + uint8_t slot; + /** Reserved */ + uint8_t reserved_a; + /** MAC address */ + uint8_t mac[ETH_ALEN]; +} __attribute__ (( packed )); #endif /* _IPXE_AOE_H */ diff --git a/src/include/ipxe/ata.h b/src/include/ipxe/ata.h index 043e3705..b7f02d65 100644 --- a/src/include/ipxe/ata.h +++ b/src/include/ipxe/ata.h @@ -2,9 +2,8 @@ #define _IPXE_ATA_H #include -#include #include -#include +#include /** @file * @@ -104,7 +103,7 @@ struct ata_cb { uint8_t device; /** Command/status register */ uint8_t cmd_stat; - /** LBA48 addressing flag */ + /** Use LBA48 extended addressing */ int lba48; }; @@ -138,25 +137,8 @@ struct ata_cb { /** "Identify" command */ #define ATA_CMD_IDENTIFY 0xec -/** An ATA command */ -struct ata_command { - /** ATA command block */ - struct ata_cb cb; - /** Data-out buffer (may be NULL) - * - * If non-NULL, this buffer must be ata_command::cb::count - * sectors in size. - */ - userptr_t data_out; - /** Data-in buffer (may be NULL) - * - * If non-NULL, this buffer must be ata_command::cb::count - * sectors in size. - */ - userptr_t data_in; - /** Command status code */ - int rc; -}; +/** Command completed in error */ +#define ATA_STAT_ERR 0x01 /** * Structure returned by ATA IDENTIFY command @@ -165,13 +147,15 @@ struct ata_command { * so we implement only a few fields. */ struct ata_identity { - uint16_t ignore_a[60]; /* words 0-59 */ + uint16_t ignore_a[27]; /* words 0-26 */ + uint16_t model[20]; /* words 27-46 */ + uint16_t ignore_b[13]; /* words 47-59 */ uint32_t lba_sectors; /* words 60-61 */ - uint16_t ignore_b[21]; /* words 62-82 */ + uint16_t ignore_c[21]; /* words 62-82 */ uint16_t supports_lba48; /* word 83 */ - uint16_t ignore_c[16]; /* words 84-99 */ + uint16_t ignore_d[16]; /* words 84-99 */ uint64_t lba48_sectors; /* words 100-103 */ - uint16_t ignore_d[152]; /* words 104-255 */ + uint16_t ignore_e[152]; /* words 104-255 */ }; /** Supports LBA48 flag */ @@ -180,30 +164,41 @@ struct ata_identity { /** ATA sector size */ #define ATA_SECTOR_SIZE 512 -/** An ATA device */ -struct ata_device { - /** Block device interface */ - struct block_device blockdev; - /** Device number +/** An ATA command information unit */ +struct ata_cmd { + /** ATA command block */ + struct ata_cb cb; + /** Data-out buffer (may be NULL) * - * Must be ATA_DEV_MASTER or ATA_DEV_SLAVE. + * If non-NULL, this buffer must be ata_command::cb::count + * sectors in size. */ - int device; - /** LBA48 extended addressing */ - int lba48; - /** - * Issue ATA command + userptr_t data_out; + /** Data-out buffer length * - * @v ata ATA device - * @v command ATA command - * @ret rc Return status code + * Must be zero if @c data_out is NULL */ - int ( * command ) ( struct ata_device *ata, - struct ata_command *command ); - /** Backing device */ - struct refcnt *backend; + size_t data_out_len; + /** Data-in buffer (may be NULL) + * + * If non-NULL, this buffer must be ata_command::cb::count + * sectors in size. + */ + userptr_t data_in; + /** Data-in buffer length + * + * Must be zero if @c data_in is NULL + */ + size_t data_in_len; }; -extern int init_atadev ( struct ata_device *ata ); +extern int ata_command ( struct interface *control, struct interface *data, + struct ata_cmd *command ); +#define ata_command_TYPE( object_type ) \ + typeof ( int ( object_type, struct interface *data, \ + struct ata_cmd *command ) ) + +extern int ata_open ( struct interface *block, struct interface *ata, + unsigned int device, unsigned int max_count ); #endif /* _IPXE_ATA_H */ diff --git a/src/include/ipxe/blockdev.h b/src/include/ipxe/blockdev.h index 5c1ab136..9f0a9f78 100644 --- a/src/include/ipxe/blockdev.h +++ b/src/include/ipxe/blockdev.h @@ -10,44 +10,46 @@ FILE_LICENCE ( GPL2_OR_LATER ); +#include #include +#include -struct block_device; - -/** Block device operations */ -struct block_device_operations { - /** - * Read block - * - * @v blockdev Block device - * @v block Block number - * @v count Block count - * @v buffer Data buffer - * @ret rc Return status code - */ - int ( * read ) ( struct block_device *blockdev, uint64_t block, - unsigned long count, userptr_t buffer ); - /** - * Write block - * - * @v blockdev Block device - * @v block Block number - * @v count Block count - * @v buffer Data buffer - * @ret rc Return status code - */ - int ( * write ) ( struct block_device *blockdev, uint64_t block, - unsigned long count, userptr_t buffer ); -}; - -/** A block device */ -struct block_device { - /** Block device operations */ - struct block_device_operations *op; - /** Block size */ - size_t blksize; +/** Block device capacity */ +struct block_device_capacity { /** Total number of blocks */ uint64_t blocks; + /** Block size */ + size_t blksize; + /** Maximum number of blocks per single transfer */ + unsigned int max_count; }; +extern int block_read ( struct interface *control, struct interface *data, + uint64_t lba, unsigned int count, + userptr_t buffer, size_t len ); +#define block_read_TYPE( object_type ) \ + typeof ( int ( object_type, struct interface *data, \ + uint64_t lba, unsigned int count, \ + userptr_t buffer, size_t len ) ) + +extern int block_write ( struct interface *control, struct interface *data, + uint64_t lba, unsigned int count, + userptr_t buffer, size_t len ); +#define block_write_TYPE( object_type ) \ + typeof ( int ( object_type, struct interface *data, \ + uint64_t lba, unsigned int count, \ + userptr_t buffer, size_t len ) ) + +extern int block_read_capacity ( struct interface *control, + struct interface *data ); +#define block_read_capacity_TYPE( object_type ) \ + typeof ( int ( object_type, struct interface *data ) ) + +extern void block_capacity ( struct interface *intf, + struct block_device_capacity *capacity ); +#define block_capacity_TYPE( object_type ) \ + typeof ( void ( object_type, \ + struct block_device_capacity *capacity ) ) + + #endif /* _IPXE_BLOCKDEV_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index ad32ed1b..d78f2822 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -55,6 +55,9 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define ERRFILE_bitmap ( ERRFILE_CORE | 0x000f0000 ) #define ERRFILE_base64 ( ERRFILE_CORE | 0x00100000 ) #define ERRFILE_base16 ( ERRFILE_CORE | 0x00110000 ) +#define ERRFILE_blockdev ( ERRFILE_CORE | 0x00120000 ) +#define ERRFILE_acpi ( ERRFILE_CORE | 0x00130000 ) +#define ERRFILE_null_sanboot ( ERRFILE_CORE | 0x00140000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) diff --git a/src/include/ipxe/ib_srp.h b/src/include/ipxe/ib_srp.h index b773f964..902bef56 100644 --- a/src/include/ipxe/ib_srp.h +++ b/src/include/ipxe/ib_srp.h @@ -14,45 +14,35 @@ FILE_LICENCE ( BSD2 ); #include /** SRP initiator port identifier for Infiniband */ -struct ib_srp_initiator_port_id { - /** Identifier extension */ - struct ib_gid_half id_ext; - /** IB channel adapter GUID */ - struct ib_gid_half hca_guid; -} __attribute__ (( packed )); +union ib_srp_initiator_port_id { + /** SRP version of port identifier */ + union srp_port_id srp; + /** Infiniband version of port identifier */ + struct { + /** Identifier extension */ + struct ib_gid_half id_ext; + /** IB channel adapter GUID */ + struct ib_gid_half hca_guid; + } __attribute__ (( packed )) ib; +}; /** SRP target port identifier for Infiniband */ -struct ib_srp_target_port_id { - /** Identifier extension */ - struct ib_gid_half id_ext; - /** I/O controller GUID */ - struct ib_gid_half ioc_guid; -} __attribute__ (( packed )); +union ib_srp_target_port_id { + /** SRP version of port identifier */ + union srp_port_id srp; + /** Infiniband version of port identifier */ + struct { + /** Identifier extension */ + struct ib_gid_half id_ext; + /** I/O controller GUID */ + struct ib_gid_half ioc_guid; + } __attribute__ (( packed )) ib; +}; /** - * Get Infiniband-specific initiator port ID - * - * @v port_ids SRP port IDs - * @ret initiator_port_id Infiniband-specific initiator port ID + * sBFT Infiniband subtable */ -static inline __always_inline struct ib_srp_initiator_port_id * -ib_srp_initiator_port_id ( struct srp_port_ids *port_ids ) { - return ( ( struct ib_srp_initiator_port_id * ) &port_ids->initiator ); -} - -/** - * Get Infiniband-specific target port ID - * - * @v port_ids SRP port IDs - * @ret target_port_id Infiniband-specific target port ID - */ -static inline __always_inline struct ib_srp_target_port_id * -ib_srp_target_port_id ( struct srp_port_ids *port_ids ) { - return ( ( struct ib_srp_target_port_id * ) &port_ids->target ); -} - -/** Infiniband-specific SRP parameters */ -struct ib_srp_parameters { +struct sbft_ib_subtable { /** Source GID */ struct ib_gid sgid; /** Destination GID */ @@ -61,19 +51,8 @@ struct ib_srp_parameters { struct ib_gid_half service_id; /** Partition key */ uint16_t pkey; -}; - -/** - * Get Infiniband-specific transport parameters - * - * @v srp SRP device - * @ret ib_params Infiniband-specific transport parameters - */ -static inline __always_inline struct ib_srp_parameters * -ib_srp_params ( struct srp_device *srp ) { - return srp_transport_priv ( srp ); -} - -extern struct srp_transport_type ib_srp_transport; + /** Reserved */ + uint8_t reserved[6]; +} __attribute__ (( packed )); #endif /* _IPXE_IB_SRP_H */ diff --git a/src/arch/i386/include/ipxe/ibft.h b/src/include/ipxe/ibft.h similarity index 86% rename from src/arch/i386/include/ipxe/ibft.h rename to src/include/ipxe/ibft.h index 8525fe81..73372122 100644 --- a/src/arch/i386/include/ipxe/ibft.h +++ b/src/include/ipxe/ibft.h @@ -43,10 +43,11 @@ FILE_LICENCE ( BSD2 ); #include #include +#include #include /** iSCSI Boot Firmware Table signature */ -#define IBFT_SIG "iBFT" +#define IBFT_SIG ACPI_SIGNATURE ( 'i', 'B', 'F', 'T' ) /** An offset from the start of the iBFT */ typedef uint16_t ibft_off_t; @@ -57,7 +58,7 @@ typedef uint16_t ibft_size_t; /** A string within the iBFT */ struct ibft_string { /** Length of string */ - ibft_size_t length; + ibft_size_t len; /** Offset to string */ ibft_off_t offset; } __attribute__ (( packed )); @@ -208,7 +209,7 @@ struct ibft_target { /** TCP port */ uint16_t socket; /** Boot LUN */ - uint64_t boot_lun; + struct scsi_lun boot_lun; /** CHAP type * * This is an IBFT_CHAP_XXX constant. @@ -260,43 +261,11 @@ struct ibft_table { struct ibft_control control; } __attribute__ (( packed )); -/** - * iSCSI string block descriptor - * - * This is an internal structure that we use to keep track of the - * allocation of string data. - */ -struct ibft_string_block { - /** The iBFT containing these strings */ - struct ibft_table *table; - /** Offset of first free byte within iBFT */ - unsigned int offset; -}; - -/** Amount of space reserved for strings in a iPXE iBFT */ -#define IBFT_STRINGS_SIZE 384 - -/** - * 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[IBFT_STRINGS_SIZE]; -} __attribute__ (( packed, aligned ( 16 ) )); - -struct net_device; struct iscsi_session; +struct net_device; -extern int ibft_fill_data ( struct net_device *netdev, - struct iscsi_session *iscsi ); +extern int ibft_describe ( struct iscsi_session *iscsi, + struct acpi_description_header *acpi, + size_t len ); #endif /* _IPXE_IBFT_H */ diff --git a/src/include/ipxe/iscsi.h b/src/include/ipxe/iscsi.h index a604e1e4..e711459c 100644 --- a/src/include/ipxe/iscsi.h +++ b/src/include/ipxe/iscsi.h @@ -90,6 +90,9 @@ struct iscsi_bhs_common { /** Final PDU of a sequence */ #define ISCSI_FLAG_FINAL 0x80 +/** iSCSI tag magic marker */ +#define ISCSI_TAG_MAGIC 0x18ae0000 + /** * iSCSI basic header segment common request fields * @@ -307,8 +310,10 @@ struct iscsi_bhs_scsi_response { uint32_t maxcmdsn; /** Expected data sequence number */ uint32_t expdatasn; - /** Reserved */ - uint8_t reserved_b[8]; + /** Bidirectional read residual count */ + uint32_t bidi_residual_count; + /** Residual count */ + uint32_t residual_count; }; /** SCSI response opcode */ @@ -320,13 +325,11 @@ struct iscsi_bhs_scsi_response { /** SCSI target failure */ #define ISCSI_RESPONSE_TARGET_FAILURE 0x01 -/** SCSI sense response code offset - * - * The SCSI response may contain unsolicited sense data in the data - * segment. If it does, this is the offset to the sense response code - * byte, which is the only byte we care about. - */ -#define ISCSI_SENSE_RESPONSE_CODE_OFFSET 2 +/** Data overflow occurred */ +#define ISCSI_RESPONSE_FLAG_OVERFLOW 0x20 + +/** Data underflow occurred */ +#define ISCSI_RESPONSE_FLAG_UNDERFLOW 0x40 /** * iSCSI data-in basic header segment @@ -497,6 +500,10 @@ struct iscsi_session { /** Reference counter */ struct refcnt refcnt; + /** SCSI command-issuing interface */ + struct interface control; + /** SCSI command interface */ + struct interface data; /** Transport-layer socket */ struct interface socket; @@ -506,10 +513,6 @@ struct iscsi_session { unsigned int target_port; /** Target IQN */ char *target_iqn; - /** Logical Unit Number (LUN) */ - struct scsi_lun lun; - /** Target socket address (recorded only for iBFT) */ - struct sockaddr target_sockaddr; /** Session status * @@ -517,12 +520,6 @@ struct iscsi_session { * constants. */ int status; - /** Retry count - * - * Number of times that the connection has been retried. - * Reset upon a successful connection. - */ - int retry_count; /** Initiator username (if any) */ char *initiator_username; @@ -542,12 +539,6 @@ struct iscsi_session { /** CHAP response (used for both initiator and target auth) */ struct chap_response chap; - /** Target session identifying handle - * - * This is assigned by the target when we first log in, and - * must be reused on subsequent login attempts. - */ - uint16_t tsih; /** Initiator task tag * * This is the tag of the current command. It is incremented @@ -560,15 +551,13 @@ struct iscsi_session { * response to an R2T. */ uint32_t ttt; - /** - * Transfer offset + /** Transfer offset * * This is the offset for an in-progress sequence of data-out * PDUs in response to an R2T. */ uint32_t transfer_offset; - /** - * Transfer length + /** Transfer length * * This is the length for an in-progress sequence of data-out * PDUs in response to an R2T. @@ -609,18 +598,13 @@ struct iscsi_session { /** Buffer for received data (not always used) */ void *rx_buffer; - /** Current SCSI command - * - * Set to NULL when command is complete. - */ - struct scsi_command *command; - /** Instant return code - * - * Set to a non-zero value if all requests should return - * immediately. This can be used to e.g. avoid retrying - * logins that are doomed to fail authentication. - */ - int instant_rc; + /** Current SCSI command, if any */ + struct scsi_cmd *command; + + /** Target socket address (for boot firmware table) */ + struct sockaddr target_sockaddr; + /** SCSI LUN (for boot firmware table) */ + struct scsi_lun lun; }; /** iSCSI session is currently in the security negotiation phase */ @@ -668,11 +652,6 @@ struct iscsi_session { /** Target authenticated itself correctly */ #define ISCSI_STATUS_AUTH_REVERSE_OK 0x00040000 -/** Maximum number of retries at connecting */ -#define ISCSI_MAX_RETRIES 2 - -extern int iscsi_attach ( struct scsi_device *scsi, const char *root_path ); -extern void iscsi_detach ( struct scsi_device *scsi ); extern const char * iscsi_initiator_iqn ( void ); #endif /* _IPXE_ISCSI_H */ diff --git a/src/include/ipxe/null_sanboot.h b/src/include/ipxe/null_sanboot.h new file mode 100644 index 00000000..341a9a1d --- /dev/null +++ b/src/include/ipxe/null_sanboot.h @@ -0,0 +1,18 @@ +#ifndef _IPXE_NULL_SANBOOT_H +#define _IPXE_NULL_SANBOOT_H + +/** @file + * + * Standard do-nothing sanboot interface + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#ifdef SANBOOT_NULL +#define SANBOOT_PREFIX_null +#else +#define SANBOOT_PREFIX_null __null_ +#endif + +#endif /* _IPXE_NULL_SANBOOT_H */ diff --git a/src/include/ipxe/ramdisk.h b/src/include/ipxe/ramdisk.h deleted file mode 100644 index 012ac736..00000000 --- a/src/include/ipxe/ramdisk.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef _IPXE_RAMDISK_H -#define _IPXE_RAMDISK_H - -/** - * @file - * - * RAM disks - * - */ - -FILE_LICENCE ( GPL2_OR_LATER ); - -#include -#include - -struct ramdisk { - struct block_device blockdev; - userptr_t data; -}; - -extern int init_ramdisk ( struct ramdisk *ramdisk, userptr_t data, size_t len, - unsigned int blksize ); - -#endif /* _IPXE_RAMDISK_H */ diff --git a/src/include/ipxe/sanboot.h b/src/include/ipxe/sanboot.h index d27452cb..913282eb 100644 --- a/src/include/ipxe/sanboot.h +++ b/src/include/ipxe/sanboot.h @@ -1,20 +1,93 @@ #ifndef _IPXE_SANBOOT_H #define _IPXE_SANBOOT_H +/** @file + * + * iPXE sanboot API + * + * The sanboot API provides methods for hooking, unhooking, + * describing, and booting from SAN devices. + * + * The standard methods (readl()/writel() etc.) do not strictly check + * the type of the address parameter; this is because traditional + * usage does not necessarily provide the correct pointer type. For + * example, code written for ISA devices at fixed I/O addresses (such + * as the keyboard controller) tend to use plain integer constants for + * the address parameter. + */ + FILE_LICENCE ( GPL2_OR_LATER ); -#include +#include +#include -struct sanboot_protocol { - const char *prefix; - int ( * boot ) ( const char *root_path ); -}; +struct uri; -#define SANBOOT_PROTOCOLS \ - __table ( struct sanboot_protocol, "sanboot_protocols" ) +/** + * Calculate static inline sanboot API function name + * + * @v _prefix Subsystem prefix + * @v _api_func API function + * @ret _subsys_func Subsystem API function + */ +#define SANBOOT_INLINE( _subsys, _api_func ) \ + SINGLE_API_INLINE ( SANBOOT_PREFIX_ ## _subsys, _api_func ) -#define __sanboot_protocol __table_entry ( SANBOOT_PROTOCOLS, 01 ) +/** + * Provide a sanboot API implementation + * + * @v _prefix Subsystem prefix + * @v _api_func API function + * @v _func Implementing function + */ +#define PROVIDE_SANBOOT( _subsys, _api_func, _func ) \ + PROVIDE_SINGLE_API ( SANBOOT_PREFIX_ ## _subsys, _api_func, _func ) -extern int keep_san ( void ); +/** + * Provide a static inline sanboot API implementation + * + * @v _prefix Subsystem prefix + * @v _api_func API function + */ +#define PROVIDE_SANBOOT_INLINE( _subsys, _api_func ) \ + PROVIDE_SINGLE_API_INLINE ( SANBOOT_PREFIX_ ## _subsys, _api_func ) + +/* Include all architecture-independent sanboot API headers */ +#include + +/* Include all architecture-dependent sanboot API headers */ +#include + +/** + * Hook SAN device + * + * @v uri URI + * @v drive Requested drive number + * @ret drive Assigned drive number, or negative error + */ +int san_hook ( struct uri *uri, unsigned int drive ); + +/** + * Unhook SAN device + * + * @v drive Drive number + */ +void san_unhook ( unsigned int drive ); + +/** + * Attempt to boot from a SAN device + * + * @v drive Drive number + * @ret rc Return status code + */ +int san_boot ( unsigned int drive ); + +/** + * Describe SAN device for SAN-booted operating system + * + * @v drive Drive number + * @ret rc Return status code + */ +int san_describe ( unsigned int drive ); #endif /* _IPXE_SANBOOT_H */ diff --git a/src/include/ipxe/scsi.h b/src/include/ipxe/scsi.h index b56ab757..b90aa3aa 100644 --- a/src/include/ipxe/scsi.h +++ b/src/include/ipxe/scsi.h @@ -2,9 +2,8 @@ #define _IPXE_SCSI_H #include -#include #include -#include +#include /** @file * @@ -14,6 +13,9 @@ FILE_LICENCE ( GPL2_OR_LATER ); +/** Maximum block for READ/WRITE (10) commands */ +#define SCSI_MAX_BLOCK_10 0xffffffffULL + /** * @defgroup scsiops SCSI operation codes * @{ @@ -214,8 +216,27 @@ union scsi_cdb { /** @} */ -/** A SCSI command */ -struct scsi_command { +/** A SCSI LUN + * + * This is a four-level LUN as specified by SAM-2, in big-endian + * order. + */ +struct scsi_lun { + uint16_t u16[4]; +} __attribute__ (( packed )); + +/** printf() format for dumping a scsi_lun */ +#define SCSI_LUN_FORMAT "%04x-%04x-%04x-%04x" + +/** printf() parameters for dumping a scsi_lun */ +#define SCSI_LUN_DATA(lun) \ + ntohs ( (lun).u16[0] ), ntohs ( (lun).u16[1] ), \ + ntohs ( (lun).u16[2] ), ntohs ( (lun).u16[3] ) + +/** A SCSI command information unit */ +struct scsi_cmd { + /** LUN */ + struct scsi_lun lun; /** CDB for this command */ union scsi_cdb cdb; /** Data-out buffer (may be NULL) */ @@ -232,50 +253,43 @@ struct scsi_command { * Must be zero if @c data_in is NULL */ size_t data_in_len; +}; + +/** SCSI sense data */ +struct scsi_sns { + /** Response code */ + uint8_t code; + /** Reserved */ + uint8_t reserved; + /** Sense key */ + uint8_t key; + /** Information */ + uint32_t info; +}; + +/** A SCSI response information unit */ +struct scsi_rsp { /** SCSI status code */ uint8_t status; - /** SCSI sense response code */ - uint8_t sense_response; - /** Command status code */ - int rc; + /** Data overrun (or negative underrun) */ + ssize_t overrun; + /** Autosense data (if any) */ + struct scsi_sns sense; }; -/** A SCSI LUN - * - * This is a four-level LUN as specified by SAM-2, in big-endian - * order. - */ -struct scsi_lun { - uint16_t u16[4]; -} __attribute__ (( packed )); - -/** A SCSI device */ -struct scsi_device { - /** Block device interface */ - struct block_device blockdev; - /** - * Issue SCSI command - * - * @v scsi SCSI device - * @v command SCSI command - * @ret rc Return status code - * - * Note that a successful return status code indicates only - * that the SCSI command was issued. The caller must check - * the status field in the command structure to see when the - * command completes and whether, for example, the device - * returned CHECK CONDITION or some other non-success status - * code. - */ - int ( * command ) ( struct scsi_device *scsi, - struct scsi_command *command ); - /** Backing device */ - struct refcnt *backend; -}; - -extern int scsi_detached_command ( struct scsi_device *scsi, - struct scsi_command *command ); -extern int init_scsidev ( struct scsi_device *scsi ); extern int scsi_parse_lun ( const char *lun_string, struct scsi_lun *lun ); +extern int scsi_command ( struct interface *control, struct interface *data, + struct scsi_cmd *command ); +#define scsi_command_TYPE( object_type ) \ + typeof ( int ( object_type, struct interface *data, \ + struct scsi_cmd *command ) ) + +extern void scsi_response ( struct interface *intf, struct scsi_rsp *response ); +#define scsi_response_TYPE( object_type ) \ + typeof ( void ( object_type, struct scsi_rsp *response ) ) + +extern int scsi_open ( struct interface *block, struct interface *scsi, + struct scsi_lun *lun ); + #endif /* _IPXE_SCSI_H */ diff --git a/src/include/ipxe/srp.h b/src/include/ipxe/srp.h index e519838a..acde7c94 100644 --- a/src/include/ipxe/srp.h +++ b/src/include/ipxe/srp.h @@ -14,6 +14,7 @@ FILE_LICENCE ( BSD2 ); #include #include #include +#include /***************************************************************************** * @@ -23,21 +24,18 @@ FILE_LICENCE ( BSD2 ); */ /** An SRP information unit tag */ -struct srp_tag { +union srp_tag { + uint8_t bytes[8]; uint32_t dwords[2]; } __attribute__ (( packed )); -/** An SRP port ID */ -struct srp_port_id { - uint8_t bytes[16]; -} __attribute__ (( packed )); +/** SRP tag magic marker */ +#define SRP_TAG_MAGIC 0x69505845 -/** An SRP port ID pair */ -struct srp_port_ids { - /** Initiator port ID */ - struct srp_port_id initiator; - /** Target port ID */ - struct srp_port_id target; +/** An SRP port ID */ +union srp_port_id { + uint8_t bytes[16]; + uint32_t dwords[4]; } __attribute__ (( packed )); /** SRP information unit common fields */ @@ -47,7 +45,7 @@ struct srp_common { /** Reserved */ uint8_t reserved0[7]; /** Tag */ - struct srp_tag tag; + union srp_tag tag; } __attribute__ (( packed )); /***************************************************************************** @@ -67,7 +65,7 @@ struct srp_login_req { /** Reserved */ uint8_t reserved0[7]; /** Tag */ - struct srp_tag tag; + union srp_tag tag; /** Requested maximum initiator to target IU length */ uint32_t max_i_t_iu_len; /** Reserved */ @@ -87,8 +85,10 @@ struct srp_login_req { uint8_t flags; /** Reserved */ uint8_t reserved2[5]; - /** Initiator and target port identifiers */ - struct srp_port_ids port_ids; + /** Initiator port identifier */ + union srp_port_id initiator; + /** Target port identifier */ + union srp_port_id target; } __attribute__ (( packed )); /** Type of an SRP login request */ @@ -137,7 +137,7 @@ struct srp_login_rsp { /** Request limit delta */ uint32_t request_limit_delta; /** Tag */ - struct srp_tag tag; + union srp_tag tag; /** Maximum initiator to target IU length */ uint32_t max_i_t_iu_len; /** Maximum target to initiator IU length */ @@ -205,7 +205,7 @@ struct srp_login_rej { */ uint32_t reason; /** Tag */ - struct srp_tag tag; + union srp_tag tag; /** Reserved */ uint8_t reserved1[8]; /** Supported buffer formats @@ -265,7 +265,7 @@ struct srp_i_logout { /** Reserved */ uint8_t reserved0[7]; /** Tag */ - struct srp_tag tag; + union srp_tag tag; } __attribute__ (( packed )); /** Type of an SRP initiator logout request */ @@ -299,7 +299,7 @@ struct srp_t_logout { */ uint32_t reason; /** Tag */ - struct srp_tag tag; + union srp_tag tag; } __attribute__ (( packed )); /** Type of an SRP target logout request */ @@ -355,7 +355,7 @@ struct srp_tsk_mgmt { /** Reserved */ uint8_t reserved0[6]; /** Tag */ - struct srp_tag tag; + union srp_tag tag; /** Reserved */ uint8_t reserved1[4]; /** Logical unit number */ @@ -370,7 +370,7 @@ struct srp_tsk_mgmt { /** Reserved */ uint8_t reserved3[1]; /** Tag of task to be managed */ - struct srp_tag managed_tag; + union srp_tag managed_tag; /** Reserved */ uint8_t reserved4[8]; } __attribute__ (( packed )); @@ -432,7 +432,7 @@ struct srp_cmd { /** Data-in buffer descriptor count */ uint8_t data_in_buffer_count; /** Tag */ - struct srp_tag tag; + union srp_tag tag; /** Reserved */ uint8_t reserved1[4]; /** Logical unit number */ @@ -526,7 +526,7 @@ struct srp_rsp { /** Request limit delta */ uint32_t request_limit_delta; /** Tag */ - struct srp_tag tag; + union srp_tag tag; /** Reserved */ uint8_t reserved1[2]; /** Valid fields @@ -580,9 +580,9 @@ struct srp_rsp { * @v rsp SCSI response * @ret response_data Response data, or NULL if not present */ -static inline void * srp_rsp_response_data ( struct srp_rsp *rsp ) { +static inline const void * srp_rsp_response_data ( const struct srp_rsp *rsp ) { return ( ( rsp->valid & SRP_RSP_VALID_RSPVALID ) ? - ( ( ( void * ) rsp ) + sizeof ( *rsp ) ) : NULL ); + ( ( ( const void * ) rsp ) + sizeof ( *rsp ) ) : NULL ); } /** @@ -591,7 +591,7 @@ static inline void * srp_rsp_response_data ( struct srp_rsp *rsp ) { * @v rsp SCSI response * @ret response_data_len Response data length */ -static inline size_t srp_rsp_response_data_len ( struct srp_rsp *rsp ) { +static inline size_t srp_rsp_response_data_len ( const struct srp_rsp *rsp ) { return ( ( rsp->valid & SRP_RSP_VALID_RSPVALID ) ? ntohl ( rsp->response_data_len ) : 0 ); } @@ -602,9 +602,9 @@ static inline size_t srp_rsp_response_data_len ( struct srp_rsp *rsp ) { * @v rsp SCSI response * @ret sense_data Sense data, or NULL if not present */ -static inline void * srp_rsp_sense_data ( struct srp_rsp *rsp ) { +static inline const void * srp_rsp_sense_data ( const struct srp_rsp *rsp ) { return ( ( rsp->valid & SRP_RSP_VALID_SNSVALID ) ? - ( ( ( void * ) rsp ) + sizeof ( *rsp ) + + ( ( ( const void * ) rsp ) + sizeof ( *rsp ) + srp_rsp_response_data_len ( rsp ) ) : NULL ); } @@ -614,7 +614,7 @@ static inline void * srp_rsp_sense_data ( struct srp_rsp *rsp ) { * @v rsp SCSI response * @ret sense_data_len Sense data length */ -static inline size_t srp_rsp_sense_data_len ( struct srp_rsp *rsp ) { +static inline size_t srp_rsp_sense_data_len ( const struct srp_rsp *rsp ) { return ( ( rsp->valid & SRP_RSP_VALID_SNSVALID ) ? ntohl ( rsp->sense_data_len ) : 0 ); } @@ -644,7 +644,7 @@ struct srp_cred_req { /** Request limit delta */ uint32_t request_limit_delta; /** Tag */ - struct srp_tag tag; + union srp_tag tag; } __attribute__ (( packed )); /** Type of an SRP credit request */ @@ -670,7 +670,7 @@ struct srp_cred_rsp { /** Reserved */ uint8_t reserved0[7]; /** Tag */ - struct srp_tag tag; + union srp_tag tag; } __attribute__ (( packed )); /** Type of an SRP credit response */ @@ -701,7 +701,7 @@ struct srp_aer_req { /** Request limit delta */ uint32_t request_limit_delta; /** Tag */ - struct srp_tag tag; + union srp_tag tag; /** Reserved */ uint8_t reserved1[4]; /** Logical unit number */ @@ -757,7 +757,7 @@ struct srp_aer_rsp { /** Reserved */ uint8_t reserved0[7]; /** Tag */ - struct srp_tag tag; + union srp_tag tag; } __attribute__ (( packed )); /** Type of an SRP asynchronous event response */ @@ -765,104 +765,65 @@ struct srp_aer_rsp { /***************************************************************************** * - * Information units + * SRP boot firmware table + * + * The working draft specification for the SRP boot firmware table can + * be found at + * + * http://ipxe.org/wiki/srp/sbft * ***************************************************************************** */ -/** 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. +/** SRP Boot Firmware Table signature */ +#define SBFT_SIG ACPI_SIGNATURE ( 's', 'B', 'F', 'T' ) + +/** An offset from the start of the sBFT */ +typedef uint16_t sbft_off_t; + +/** + * SRP Boot Firmware Table */ -#define SRP_MAX_I_T_IU_LEN 80 +struct sbft_table { + /** ACPI header */ + struct acpi_description_header acpi; + /** Offset to SCSI subtable */ + sbft_off_t scsi_offset; + /** Offset to SRP subtable */ + sbft_off_t srp_offset; + /** Offset to IB subtable, if present */ + sbft_off_t ib_offset; + /** Reserved */ + uint8_t reserved[6]; +} __attribute__ (( packed )); + +/** + * sBFT SCSI subtable + */ +struct sbft_scsi_subtable { + /** LUN */ + struct scsi_lun lun; +} __attribute__ (( packed )); + +/** + * sBFT SRP subtable + */ +struct sbft_srp_subtable { + /** Initiator port identifier */ + union srp_port_id initiator; + /** Target port identifier */ + union srp_port_id target; +} __attribute__ (( packed )); /***************************************************************************** * - * SRP device + * SRP devices * ***************************************************************************** */ -struct srp_device; - -/** An SRP transport type */ -struct srp_transport_type { - /** Length of transport private data */ - size_t priv_len; - /** Parse root path - * - * @v srp SRP device - * @v root_path Root path - * @ret Return status code - */ - int ( * parse_root_path ) ( struct srp_device *srp, - const char *root_path ); - /** Connect SRP session - * - * @v srp SRP device - * @ret rc Return status code - * - * This method should open the underlying socket. - */ - int ( * connect ) ( struct srp_device *srp ); -}; - -/** An SRP device */ -struct srp_device { - /** Reference count */ - struct refcnt refcnt; - - /** Initiator and target port IDs */ - struct srp_port_ids port_ids; - /** Logical unit number */ - struct scsi_lun lun; - /** Memory handle */ - uint32_t memory_handle; - - /** Current state - * - * This is the bitwise-OR of zero or more @c SRP_STATE_XXX - * flags. - */ - unsigned int state; - /** Retry counter */ - unsigned int retry_count; - /** Current SCSI command */ - struct scsi_command *command; - - /** Underlying data transfer interface */ - struct interface socket; - - /** Transport type */ - struct srp_transport_type *transport; - /** Transport private data */ - char transport_priv[0]; -}; - -/** - * Get SRP transport private data - * - * @v srp SRP device - * @ret priv SRP transport private data - */ -static inline __always_inline void * -srp_transport_priv ( struct srp_device *srp ) { - return ( ( void * ) srp->transport_priv ); -} - -/** SRP state flags */ -enum srp_state { - /** Underlying socket is open */ - SRP_STATE_SOCKET_OPEN = 0x0001, - /** Session is logged in */ - SRP_STATE_LOGGED_IN = 0x0002, -}; - -/** Maximum number of SRP retry attempts */ -#define SRP_MAX_RETRIES 3 - -extern int srp_attach ( struct scsi_device *scsi, const char *root_path ); -extern void srp_detach ( struct scsi_device *scsi ); +extern 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 ); #endif /* _IPXE_SRP_H */ diff --git a/src/net/aoe.c b/src/net/aoe.c index 5e1a3b5b..fec8d33f 100644 --- a/src/net/aoe.c +++ b/src/net/aoe.c @@ -27,13 +27,15 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include -#include #include #include -#include #include -#include #include +#include +#include +#include +#include +#include #include /** @file @@ -46,140 +48,302 @@ FEATURE ( FEATURE_PROTOCOL, "AoE", DHCP_EB_FEATURE_AOE, 1 ); struct net_protocol aoe_protocol __net_protocol; -/** List of all AoE sessions */ -static LIST_HEAD ( aoe_sessions ); +/****************************************************************************** + * + * AoE devices and commands + * + ****************************************************************************** + */ -static void aoe_free ( struct refcnt *refcnt ) { - struct aoe_session *aoe = - container_of ( refcnt, struct aoe_session, refcnt ); +/** List of all AoE devices */ +static LIST_HEAD ( aoe_devices ); - netdev_put ( aoe->netdev ); - free ( aoe ); +/** List of active AoE commands */ +static LIST_HEAD ( aoe_commands ); + +/** An AoE device */ +struct aoe_device { + /** Reference counter */ + struct refcnt refcnt; + + /** Network device */ + struct net_device *netdev; + /** ATA command issuing interface */ + struct interface ata; + + /** Major number */ + uint16_t major; + /** Minor number */ + uint8_t minor; + /** Target MAC address */ + uint8_t target[MAX_LL_ADDR_LEN]; + + /** Saved timeout value */ + unsigned long timeout; + + /** Configuration command interface */ + struct interface config; + /** Device is configued */ + int configured; +}; + +/** An AoE command */ +struct aoe_command { + /** Reference count */ + struct refcnt refcnt; + /** AOE device */ + struct aoe_device *aoedev; + /** List of active commands */ + struct list_head list; + + /** ATA command interface */ + struct interface ata; + + /** ATA command */ + struct ata_cmd command; + /** Command type */ + struct aoe_command_type *type; + /** Command tag */ + uint32_t tag; + + /** Retransmission timer */ + struct retry_timer timer; +}; + +/** An AoE command type */ +struct aoe_command_type { + /** + * Calculate length of AoE command IU + * + * @v aoecmd AoE command + * @ret len Length of command IU + */ + size_t ( * cmd_len ) ( struct aoe_command *aoecmd ); + /** + * Build AoE command IU + * + * @v aoecmd AoE command + * @v data Command IU + * @v len Length of command IU + */ + void ( * cmd ) ( struct aoe_command *aoecmd, void *data, size_t len ); + /** + * Handle AoE response IU + * + * @v aoecmd AoE command + * @v data Response IU + * @v len Length of response IU + * @v ll_source Link-layer source address + * @ret rc Return status code + */ + int ( * rsp ) ( struct aoe_command *aoecmd, const void *data, + size_t len, const void *ll_source ); +}; + +/** + * Get reference to AoE device + * + * @v aoedev AoE device + * @ret aoedev AoE device + */ +static inline __attribute__ (( always_inline )) struct aoe_device * +aoedev_get ( struct aoe_device *aoedev ) { + ref_get ( &aoedev->refcnt ); + return aoedev; } /** - * Mark current AoE command complete + * Drop reference to AoE device * - * @v aoe AoE session - * @v rc Return status code + * @v aoedev AoE device */ -static void aoe_done ( struct aoe_session *aoe, int rc ) { +static inline __attribute__ (( always_inline )) void +aoedev_put ( struct aoe_device *aoedev ) { + ref_put ( &aoedev->refcnt ); +} - /* Record overall command status */ - if ( aoe->command ) { - aoe->command->cb.cmd_stat = aoe->status; - aoe->command->rc = rc; - aoe->command = NULL; +/** + * Get reference to AoE command + * + * @v aoecmd AoE command + * @ret aoecmd AoE command + */ +static inline __attribute__ (( always_inline )) struct aoe_command * +aoecmd_get ( struct aoe_command *aoecmd ) { + ref_get ( &aoecmd->refcnt ); + return aoecmd; +} + +/** + * Drop reference to AoE command + * + * @v aoecmd AoE command + */ +static inline __attribute__ (( always_inline )) void +aoecmd_put ( struct aoe_command *aoecmd ) { + ref_put ( &aoecmd->refcnt ); +} + +/** + * Name AoE device + * + * @v aoedev AoE device + * @ret name AoE device name + */ +static const char * aoedev_name ( struct aoe_device *aoedev ) { + static char buf[16]; + + snprintf ( buf, sizeof ( buf ), "%s/e%d.%d", aoedev->netdev->name, + aoedev->major, aoedev->minor ); + return buf; +} + +/** + * Free AoE command + * + * @v refcnt Reference counter + */ +static void aoecmd_free ( struct refcnt *refcnt ) { + struct aoe_command *aoecmd = + container_of ( refcnt, struct aoe_command, refcnt ); + + assert ( ! timer_running ( &aoecmd->timer ) ); + assert ( list_empty ( &aoecmd->list ) ); + + aoedev_put ( aoecmd->aoedev ); + free ( aoecmd ); +} + +/** + * Close AoE command + * + * @v aoecmd AoE command + * @v rc Reason for close + */ +static void aoecmd_close ( struct aoe_command *aoecmd, int rc ) { + struct aoe_device *aoedev = aoecmd->aoedev; + + /* Stop timer */ + stop_timer ( &aoecmd->timer ); + + /* Preserve the timeout value for subsequent commands */ + aoedev->timeout = aoecmd->timer.timeout; + + /* Remove from list of commands */ + if ( ! list_empty ( &aoecmd->list ) ) { + list_del ( &aoecmd->list ); + INIT_LIST_HEAD ( &aoecmd->list ); + aoecmd_put ( aoecmd ); } - /* Stop retransmission timer */ - stop_timer ( &aoe->timer ); - - /* Mark operation as complete */ - aoe->rc = rc; + /* Shut down interfaces */ + intf_shutdown ( &aoecmd->ata, rc ); } /** - * Send AoE command + * Transmit AoE command request * - * @v aoe AoE session + * @v aoecmd AoE command * @ret rc Return status code - * - * This transmits an AoE command packet. It does not wait for a - * response. */ -static int aoe_send_command ( struct aoe_session *aoe ) { - struct ata_command *command = aoe->command; +static int aoecmd_tx ( struct aoe_command *aoecmd ) { + struct aoe_device *aoedev = aoecmd->aoedev; struct io_buffer *iobuf; struct aoehdr *aoehdr; - union aoecmd *aoecmd; - struct aoeata *aoeata; - unsigned int count; - unsigned int data_out_len; - unsigned int aoecmdlen; + size_t cmd_len; + int rc; - /* Fail immediately if we have no netdev to send on */ - if ( ! aoe->netdev ) { - aoe_done ( aoe, -ENETUNREACH ); - return -ENETUNREACH; - } + /* Sanity check */ + assert ( aoedev->netdev != NULL ); /* If we are transmitting anything that requires a response, * start the retransmission timer. Do this before attempting * to allocate the I/O buffer, in case allocation itself * fails. */ - start_timer ( &aoe->timer ); - - /* Calculate count and data_out_len for this subcommand */ - switch ( aoe->aoe_cmd_type ) { - case AOE_CMD_ATA: - count = command->cb.count.native; - if ( count > AOE_MAX_COUNT ) - count = AOE_MAX_COUNT; - data_out_len = ( command->data_out ? - ( count * ATA_SECTOR_SIZE ) : 0 ); - aoecmdlen = sizeof ( aoecmd->ata ); - break; - case AOE_CMD_CONFIG: - count = 0; - data_out_len = 0; - aoecmdlen = sizeof ( aoecmd->cfg ); - break; - default: - return -ENOTSUP; - } + start_timer ( &aoecmd->timer ); /* Create outgoing I/O buffer */ - iobuf = alloc_iob ( ETH_HLEN + sizeof ( *aoehdr ) + - aoecmdlen + data_out_len ); - + cmd_len = aoecmd->type->cmd_len ( aoecmd ); + iobuf = alloc_iob ( MAX_LL_HEADER_LEN + cmd_len ); if ( ! iobuf ) return -ENOMEM; - iob_reserve ( iobuf, ETH_HLEN ); - aoehdr = iob_put ( iobuf, sizeof ( *aoehdr ) ); - aoecmd = iob_put ( iobuf, aoecmdlen ); - memset ( aoehdr, 0, ( sizeof ( *aoehdr ) + aoecmdlen ) ); + iob_reserve ( iobuf, MAX_LL_HEADER_LEN ); + aoehdr = iob_put ( iobuf, cmd_len ); /* Fill AoE header */ + memset ( aoehdr, 0, sizeof ( *aoehdr ) ); aoehdr->ver_flags = AOE_VERSION; - aoehdr->major = htons ( aoe->major ); - aoehdr->minor = aoe->minor; - aoehdr->command = aoe->aoe_cmd_type; - aoehdr->tag = htonl ( ++aoe->tag ); - - /* Fill AoE payload */ - switch ( aoe->aoe_cmd_type ) { - case AOE_CMD_ATA: - /* Fill AoE command */ - aoeata = &aoecmd->ata; - linker_assert ( AOE_FL_DEV_HEAD == ATA_DEV_SLAVE, - __fix_ata_h__ ); - aoeata->aflags = ( ( command->cb.lba48 ? AOE_FL_EXTENDED : 0 )| - ( command->cb.device & ATA_DEV_SLAVE ) | - ( data_out_len ? AOE_FL_WRITE : 0 ) ); - aoeata->err_feat = command->cb.err_feat.bytes.cur; - aoeata->count = count; - aoeata->cmd_stat = command->cb.cmd_stat; - aoeata->lba.u64 = cpu_to_le64 ( command->cb.lba.native ); - if ( ! command->cb.lba48 ) - aoeata->lba.bytes[3] |= - ( command->cb.device & ATA_DEV_MASK ); - - /* Fill data payload */ - copy_from_user ( iob_put ( iobuf, data_out_len ), - command->data_out, aoe->command_offset, - data_out_len ); - break; - case AOE_CMD_CONFIG: - /* Nothing to do */ - break; - default: - assert ( 0 ); - } + aoehdr->major = htons ( aoedev->major ); + aoehdr->minor = aoedev->minor; + aoehdr->tag = htonl ( aoecmd->tag ); + aoecmd->type->cmd ( aoecmd, iobuf->data, iob_len ( iobuf ) ); /* Send packet */ - return net_tx ( iobuf, aoe->netdev, &aoe_protocol, aoe->target ); + if ( ( rc = net_tx ( iobuf, aoedev->netdev, &aoe_protocol, + aoedev->target ) ) != 0 ) { + DBGC ( aoedev, "AoE %s/%08x could not transmit: %s\n", + aoedev_name ( aoedev ), aoecmd->tag, + strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Receive AoE command response + * + * @v aoecmd AoE command + * @v iobuf I/O buffer + * @v ll_source Link-layer source address + * @ret rc Return status code + */ +static int aoecmd_rx ( struct aoe_command *aoecmd, struct io_buffer *iobuf, + const void *ll_source ) { + struct aoe_device *aoedev = aoecmd->aoedev; + struct aoehdr *aoehdr = iobuf->data; + int rc; + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *aoehdr ) ) { + DBGC ( aoedev, "AoE %s/%08x received underlength response " + "(%zd bytes)\n", aoedev_name ( aoedev ), + aoecmd->tag, iob_len ( iobuf ) ); + rc = -EINVAL; + goto done; + } + if ( ( ntohs ( aoehdr->major ) != aoedev->major ) || + ( aoehdr->minor != aoedev->minor ) ) { + DBGC ( aoedev, "AoE %s/%08x received response for incorrect " + "device e%d.%d\n", aoedev_name ( aoedev ), aoecmd->tag, + ntohs ( aoehdr->major ), aoehdr->minor ); + rc = -EINVAL; + goto done; + } + + /* Catch command failures */ + if ( aoehdr->ver_flags & AOE_FL_ERROR ) { + DBGC ( aoedev, "AoE %s/%08x terminated in error\n", + aoedev_name ( aoedev ), aoecmd->tag ); + aoecmd_close ( aoecmd, -EIO ); + rc = -EIO; + goto done; + } + + /* Hand off to command completion handler */ + if ( ( rc = aoecmd->type->rsp ( aoecmd, iobuf->data, iob_len ( iobuf ), + ll_source ) ) != 0 ) + goto done; + + done: + /* Free I/O buffer */ + free_iob ( iobuf ); + + /* Terminate command */ + aoecmd_close ( aoecmd, rc ); + + return rc; } /** @@ -188,94 +352,531 @@ static int aoe_send_command ( struct aoe_session *aoe ) { * @v timer AoE retry timer * @v fail Failure indicator */ -static void aoe_timer_expired ( struct retry_timer *timer, int fail ) { - struct aoe_session *aoe = - container_of ( timer, struct aoe_session, timer ); +static void aoecmd_expired ( struct retry_timer *timer, int fail ) { + struct aoe_command *aoecmd = + container_of ( timer, struct aoe_command, timer ); if ( fail ) { - aoe_done ( aoe, -ETIMEDOUT ); + aoecmd_close ( aoecmd, -ETIMEDOUT ); } else { - aoe_send_command ( aoe ); + aoecmd_tx ( aoecmd ); } } /** - * Handle AoE configuration command response + * Calculate length of AoE ATA command IU * - * @v aoe AoE session + * @v aoecmd AoE command + * @ret len Length of command IU + */ +static size_t aoecmd_ata_cmd_len ( struct aoe_command *aoecmd ) { + struct ata_cmd *command = &aoecmd->command; + + return ( sizeof ( struct aoehdr ) + sizeof ( struct aoeata ) + + command->data_out_len ); +} + +/** + * Build AoE ATA command IU + * + * @v aoecmd AoE command + * @v data Command IU + * @v len Length of command IU + */ +static void aoecmd_ata_cmd ( struct aoe_command *aoecmd, + void *data, size_t len ) { + struct aoe_device *aoedev = aoecmd->aoedev; + struct ata_cmd *command = &aoecmd->command; + struct aoehdr *aoehdr = data; + struct aoeata *aoeata = &aoehdr->payload[0].ata; + + /* Sanity check */ + linker_assert ( AOE_FL_DEV_HEAD == ATA_DEV_SLAVE, __fix_ata_h__ ); + assert ( len == ( sizeof ( *aoehdr ) + sizeof ( *aoeata ) + + command->data_out_len ) ); + + /* Build IU */ + aoehdr->command = AOE_CMD_ATA; + memset ( aoeata, 0, sizeof ( *aoeata ) ); + aoeata->aflags = ( ( command->cb.lba48 ? AOE_FL_EXTENDED : 0 ) | + ( command->cb.device & ATA_DEV_SLAVE ) | + ( command->data_out_len ? AOE_FL_WRITE : 0 ) ); + aoeata->err_feat = command->cb.err_feat.bytes.cur; + aoeata->count = command->cb.count.native; + aoeata->cmd_stat = command->cb.cmd_stat; + aoeata->lba.u64 = cpu_to_le64 ( command->cb.lba.native ); + if ( ! command->cb.lba48 ) + aoeata->lba.bytes[3] |= + ( command->cb.device & ATA_DEV_MASK ); + copy_from_user ( aoeata->data, command->data_out, 0, + command->data_out_len ); + + DBGC2 ( aoedev, "AoE %s/%08x ATA cmd %02x:%02x:%02x:%02x:%08llx", + aoedev_name ( aoedev ), aoecmd->tag, aoeata->aflags, + aoeata->err_feat, aoeata->count, aoeata->cmd_stat, + aoeata->lba.u64 ); + if ( command->data_out_len ) + DBGC2 ( aoedev, " out %04zx", command->data_out_len ); + if ( command->data_in_len ) + DBGC2 ( aoedev, " in %04zx", command->data_in_len ); + DBGC2 ( aoedev, "\n" ); +} + +/** + * Handle AoE ATA response IU + * + * @v aoecmd AoE command + * @v data Response IU + * @v len Length of response IU * @v ll_source Link-layer source address * @ret rc Return status code */ -static int aoe_rx_cfg ( struct aoe_session *aoe, const void *ll_source ) { +static int aoecmd_ata_rsp ( struct aoe_command *aoecmd, const void *data, + size_t len, const void *ll_source __unused ) { + struct aoe_device *aoedev = aoecmd->aoedev; + struct ata_cmd *command = &aoecmd->command; + const struct aoehdr *aoehdr = data; + const struct aoeata *aoeata = &aoehdr->payload[0].ata; + size_t data_len; - /* Record target MAC address */ - memcpy ( aoe->target, ll_source, sizeof ( aoe->target ) ); - DBGC ( aoe, "AoE %p target MAC address %s\n", - aoe, eth_ntoa ( aoe->target ) ); + /* Sanity check */ + if ( len < ( sizeof ( *aoehdr ) + sizeof ( *aoeata ) ) ) { + DBGC ( aoedev, "AoE %s/%08x received underlength ATA response " + "(%zd bytes)\n", aoedev_name ( aoedev ), + aoecmd->tag, len ); + return -EINVAL; + } + data_len = ( len - ( sizeof ( *aoehdr ) + sizeof ( *aoeata ) ) ); + DBGC2 ( aoedev, "AoE %s/%08x ATA rsp %02x in %04zx\n", + aoedev_name ( aoedev ), aoecmd->tag, aoeata->cmd_stat, + data_len ); - /* Mark config request as complete */ - aoe_done ( aoe, 0 ); + /* Check for command failure */ + if ( aoeata->cmd_stat & ATA_STAT_ERR ) { + DBGC ( aoedev, "AoE %s/%08x status %02x\n", + aoedev_name ( aoedev ), aoecmd->tag, aoeata->cmd_stat ); + return -EIO; + } + + /* Check data-in length is sufficient. (There may be trailing + * garbage due to Ethernet minimum-frame-size padding.) + */ + if ( data_len < command->data_in_len ) { + DBGC ( aoedev, "AoE %s/%08x data-in underrun (received %zd, " + "expected %zd)\n", aoedev_name ( aoedev ), aoecmd->tag, + data_len, command->data_in_len ); + return -ERANGE; + } + + /* Copy out data payload */ + copy_to_user ( command->data_in, 0, aoeata->data, + command->data_in_len ); return 0; } +/** AoE ATA command */ +static struct aoe_command_type aoecmd_ata = { + .cmd_len = aoecmd_ata_cmd_len, + .cmd = aoecmd_ata_cmd, + .rsp = aoecmd_ata_rsp, +}; + +/** + * Calculate length of AoE configuration command IU + * + * @v aoecmd AoE command + * @ret len Length of command IU + */ +static size_t aoecmd_cfg_cmd_len ( struct aoe_command *aoecmd __unused ) { + return ( sizeof ( struct aoehdr ) + sizeof ( struct aoecfg ) ); +} + /** - * Handle AoE ATA command response + * Build AoE configuration command IU * - * @v aoe AoE session - * @v aoeata AoE ATA command - * @v len Length of AoE ATA command - * @ret rc Return status code + * @v aoecmd AoE command + * @v data Command IU + * @v len Length of command IU */ -static int aoe_rx_ata ( struct aoe_session *aoe, struct aoeata *aoeata, - size_t len ) { - struct ata_command *command = aoe->command; - unsigned int rx_data_len; - unsigned int count; - unsigned int data_len; +static void aoecmd_cfg_cmd ( struct aoe_command *aoecmd, + void *data, size_t len ) { + struct aoe_device *aoedev = aoecmd->aoedev; + struct aoehdr *aoehdr = data; + struct aoecfg *aoecfg = &aoehdr->payload[0].cfg; /* Sanity check */ - if ( len < sizeof ( *aoeata ) ) { - /* Ignore packet; allow timer to trigger retransmit */ + assert ( len == ( sizeof ( *aoehdr ) + sizeof ( *aoecfg ) ) ); + + /* Build IU */ + aoehdr->command = AOE_CMD_CONFIG; + memset ( aoecfg, 0, sizeof ( *aoecfg ) ); + + DBGC ( aoedev, "AoE %s/%08x CONFIG cmd\n", + aoedev_name ( aoedev ), aoecmd->tag ); +} + +/** + * Handle AoE configuration response IU + * + * @v aoecmd AoE command + * @v data Response IU + * @v len Length of response IU + * @v ll_source Link-layer source address + * @ret rc Return status code + */ +static int aoecmd_cfg_rsp ( struct aoe_command *aoecmd, const void *data, + size_t len, const void *ll_source ) { + struct aoe_device *aoedev = aoecmd->aoedev; + struct ll_protocol *ll_protocol = aoedev->netdev->ll_protocol; + const struct aoehdr *aoehdr = data; + const struct aoecfg *aoecfg = &aoehdr->payload[0].cfg; + + /* Sanity check */ + if ( len < ( sizeof ( *aoehdr ) + sizeof ( *aoecfg ) ) ) { + DBGC ( aoedev, "AoE %s/%08x received underlength " + "configuration response (%zd bytes)\n", + aoedev_name ( aoedev ), aoecmd->tag, len ); return -EINVAL; } - rx_data_len = ( len - sizeof ( *aoeata ) ); + DBGC ( aoedev, "AoE %s/%08x CONFIG rsp buf %04x fw %04x scnt %02x\n", + aoedev_name ( aoedev ), aoecmd->tag, ntohs ( aoecfg->bufcnt ), + aoecfg->fwver, aoecfg->scnt ); - /* Calculate count and data_len for this subcommand */ - count = command->cb.count.native; - if ( count > AOE_MAX_COUNT ) - count = AOE_MAX_COUNT; - data_len = count * ATA_SECTOR_SIZE; - - /* Merge into overall ATA status */ - aoe->status |= aoeata->cmd_stat; - - /* Copy data payload */ - if ( command->data_in ) { - if ( rx_data_len > data_len ) - rx_data_len = data_len; - copy_to_user ( command->data_in, aoe->command_offset, - aoeata->data, rx_data_len ); - } - - /* Update ATA command and offset */ - aoe->command_offset += data_len; - command->cb.lba.native += count; - command->cb.count.native -= count; - - /* Check for operation complete */ - if ( ! command->cb.count.native ) { - aoe_done ( aoe, 0 ); - return 0; - } - - /* Transmit next portion of request */ - stop_timer ( &aoe->timer ); - aoe_send_command ( aoe ); + /* Record target MAC address */ + memcpy ( aoedev->target, ll_source, ll_protocol->ll_addr_len ); + DBGC ( aoedev, "AoE %s has MAC address %s\n", + aoedev_name ( aoedev ), ll_protocol->ntoa ( aoedev->target ) ); return 0; } +/** AoE configuration command */ +static struct aoe_command_type aoecmd_cfg = { + .cmd_len = aoecmd_cfg_cmd_len, + .cmd = aoecmd_cfg_cmd, + .rsp = aoecmd_cfg_rsp, +}; + +/** AoE command ATA interface operations */ +static struct interface_operation aoecmd_ata_op[] = { + INTF_OP ( intf_close, struct aoe_command *, aoecmd_close ), +}; + +/** AoE command ATA interface descriptor */ +static struct interface_descriptor aoecmd_ata_desc = + INTF_DESC ( struct aoe_command, ata, aoecmd_ata_op ); + +/** + * Identify AoE command by tag + * + * @v tag Command tag + * @ret aoecmd AoE command, or NULL + */ +static struct aoe_command * aoecmd_find_tag ( uint32_t tag ) { + struct aoe_command *aoecmd; + + list_for_each_entry ( aoecmd, &aoe_commands, list ) { + if ( aoecmd->tag == tag ) + return aoecmd; + } + return NULL; +} + +/** + * Choose an AoE command tag + * + * @ret tag New tag, or negative error + */ +static int aoecmd_new_tag ( void ) { + static uint16_t tag_idx; + unsigned int i; + + for ( i = 0 ; i < 65536 ; i++ ) { + tag_idx++; + if ( aoecmd_find_tag ( tag_idx ) == NULL ) + return ( AOE_TAG_MAGIC | tag_idx ); + } + return -EADDRINUSE; +} + +/** + * Create AoE command + * + * @v aoedev AoE device + * @v type AoE command type + * @ret aoecmd AoE command + */ +static struct aoe_command * aoecmd_create ( struct aoe_device *aoedev, + struct aoe_command_type *type ) { + struct aoe_command *aoecmd; + int tag; + + /* Allocate command tag */ + tag = aoecmd_new_tag(); + if ( tag < 0 ) + return NULL; + + /* Allocate and initialise structure */ + aoecmd = zalloc ( sizeof ( *aoecmd ) ); + if ( ! aoecmd ) + return NULL; + ref_init ( &aoecmd->refcnt, aoecmd_free ); + list_add ( &aoecmd->list, &aoe_commands ); + intf_init ( &aoecmd->ata, &aoecmd_ata_desc, &aoecmd->refcnt ); + timer_init ( &aoecmd->timer, aoecmd_expired, &aoecmd->refcnt ); + aoecmd->aoedev = aoedev_get ( aoedev ); + aoecmd->type = type; + aoecmd->tag = tag; + + /* Preserve timeout from last completed command */ + aoecmd->timer.timeout = aoedev->timeout; + + /* Return already mortalised. (Reference is held by command list.) */ + return aoecmd; +} + +/** + * Issue AoE ATA command + * + * @v aoedev AoE device + * @v parent Parent interface + * @v command ATA command + * @ret tag Command tag, or negative error + */ +static int aoedev_ata_command ( struct aoe_device *aoedev, + struct interface *parent, + struct ata_cmd *command ) { + struct aoe_command *aoecmd; + + /* Create command */ + aoecmd = aoecmd_create ( aoedev, &aoecmd_ata ); + if ( ! aoecmd ) + return -ENOMEM; + memcpy ( &aoecmd->command, command, sizeof ( aoecmd->command ) ); + + /* Attempt to send command. Allow failures to be handled by + * the retry timer. + */ + aoecmd_tx ( aoecmd ); + + /* Attach to parent interface, leave reference with command + * list, and return. + */ + intf_plug_plug ( &aoecmd->ata, parent ); + return aoecmd->tag; +} + +/** + * Issue AoE configuration command + * + * @v aoedev AoE device + * @v parent Parent interface + * @ret tag Command tag, or negative error + */ +static int aoedev_cfg_command ( struct aoe_device *aoedev, + struct interface *parent ) { + struct aoe_command *aoecmd; + + /* Create command */ + aoecmd = aoecmd_create ( aoedev, &aoecmd_cfg ); + if ( ! aoecmd ) + return -ENOMEM; + + /* Attempt to send command. Allow failures to be handled by + * the retry timer. + */ + aoecmd_tx ( aoecmd ); + + /* Attach to parent interface, leave reference with command + * list, and return. + */ + intf_plug_plug ( &aoecmd->ata, parent ); + return aoecmd->tag; +} + +/** + * Free AoE device + * + * @v refcnt Reference count + */ +static void aoedev_free ( struct refcnt *refcnt ) { + struct aoe_device *aoedev = + container_of ( refcnt, struct aoe_device, refcnt ); + + netdev_put ( aoedev->netdev ); + free ( aoedev ); +} + +/** + * Close AoE device + * + * @v aoedev AoE device + * @v rc Reason for close + */ +static void aoedev_close ( struct aoe_device *aoedev, int rc ) { + struct aoe_command *aoecmd; + struct aoe_command *tmp; + + /* Shut down interfaces */ + intf_shutdown ( &aoedev->ata, rc ); + intf_shutdown ( &aoedev->config, rc ); + + /* Shut down any active commands */ + list_for_each_entry_safe ( aoecmd, tmp, &aoe_commands, list ) { + if ( aoecmd->aoedev != aoedev ) + continue; + aoecmd_get ( aoecmd ); + aoecmd_close ( aoecmd, rc ); + aoecmd_put ( aoecmd ); + } +} + +/** + * Check AoE device flow-control window + * + * @v aoedev AoE device + * @ret len Length of window + */ +static size_t aoedev_window ( struct aoe_device *aoedev ) { + return ( aoedev->configured ? ~( ( size_t ) 0 ) : 0 ); +} + +/** + * Handle AoE device configuration completion + * + * @v aoedev AoE device + * @v rc Reason for completion + */ +static void aoedev_config_done ( struct aoe_device *aoedev, int rc ) { + + /* Shut down interface */ + intf_shutdown ( &aoedev->config, rc ); + + /* Close device on failure */ + if ( rc != 0 ) { + aoedev_close ( aoedev, rc ); + return; + } + + /* Mark device as configured */ + aoedev->configured = 1; + xfer_window_changed ( &aoedev->ata ); +} + +/** + * Describe AoE device in an ACPI table + * + * @v aoedev AoE device + * @v acpi ACPI table + * @v len Length of ACPI table + * @ret rc Return status code + */ +static int aoedev_describe ( struct aoe_device *aoedev, + struct acpi_description_header *acpi, + size_t len ) { + struct abft_table *abft = + container_of ( acpi, struct abft_table, acpi ); + + /* Sanity check */ + if ( len < sizeof ( *abft ) ) + return -ENOBUFS; + + /* Populate table */ + abft->acpi.signature = cpu_to_le32 ( ABFT_SIG ); + abft->acpi.length = cpu_to_le32 ( sizeof ( *abft ) ); + abft->acpi.revision = 1; + abft->shelf = cpu_to_le16 ( aoedev->major ); + abft->slot = aoedev->minor; + memcpy ( abft->mac, aoedev->netdev->ll_addr, sizeof ( abft->mac ) ); + + return 0; +} + +/** AoE device ATA interface operations */ +static struct interface_operation aoedev_ata_op[] = { + INTF_OP ( ata_command, struct aoe_device *, aoedev_ata_command ), + INTF_OP ( xfer_window, struct aoe_device *, aoedev_window ), + INTF_OP ( intf_close, struct aoe_device *, aoedev_close ), + INTF_OP ( acpi_describe, struct aoe_device *, aoedev_describe ), +}; + +/** AoE device ATA interface descriptor */ +static struct interface_descriptor aoedev_ata_desc = + INTF_DESC ( struct aoe_device, ata, aoedev_ata_op ); + +/** AoE device configuration interface operations */ +static struct interface_operation aoedev_config_op[] = { + INTF_OP ( intf_close, struct aoe_device *, aoedev_config_done ), +}; + +/** AoE device configuration interface descriptor */ +static struct interface_descriptor aoedev_config_desc = + INTF_DESC ( struct aoe_device, config, aoedev_config_op ); + +/** + * Open AoE device + * + * @v parent Parent interface + * @v netdev Network device + * @v major Device major number + * @v minor Device minor number + * @ret rc Return status code + */ +static int aoedev_open ( struct interface *parent, struct net_device *netdev, + unsigned int major, unsigned int minor ) { + struct aoe_device *aoedev; + int rc; + + /* Allocate and initialise structure */ + aoedev = zalloc ( sizeof ( *aoedev ) ); + if ( ! aoedev ) { + rc = -ENOMEM; + goto err_zalloc; + } + ref_init ( &aoedev->refcnt, aoedev_free ); + intf_init ( &aoedev->ata, &aoedev_ata_desc, &aoedev->refcnt ); + intf_init ( &aoedev->config, &aoedev_config_desc, &aoedev->refcnt ); + aoedev->netdev = netdev_get ( netdev ); + aoedev->major = major; + aoedev->minor = minor; + memcpy ( aoedev->target, netdev->ll_broadcast, + netdev->ll_protocol->ll_addr_len ); + + /* Initiate configuration */ + if ( ( rc = aoedev_cfg_command ( aoedev, &aoedev->config ) ) < 0 ) { + DBGC ( aoedev, "AoE %s could not initiate configuration: %s\n", + aoedev_name ( aoedev ), strerror ( rc ) ); + goto err_config; + } + + /* Attach ATA device to parent interface */ + if ( ( rc = ata_open ( parent, &aoedev->ata, ATA_DEV_MASTER, + AOE_MAX_COUNT ) ) != 0 ) { + DBGC ( aoedev, "AoE %s could not create ATA device: %s\n", + aoedev_name ( aoedev ), strerror ( rc ) ); + goto err_ata_open; + } + + /* Mortalise self and return */ + ref_put ( &aoedev->refcnt ); + return 0; + + err_ata_open: + err_config: + aoedev_close ( aoedev, rc ); + ref_put ( &aoedev->refcnt ); + err_zalloc: + return rc; +} + +/****************************************************************************** + * + * AoE network protocol + * + ****************************************************************************** + */ + /** * Process incoming AoE packets * @@ -289,52 +890,47 @@ static int aoe_rx ( struct io_buffer *iobuf, struct net_device *netdev __unused, const void *ll_source ) { struct aoehdr *aoehdr = iobuf->data; - struct aoe_session *aoe; - int rc = 0; + struct aoe_command *aoecmd; + int rc; - /* Sanity checks */ + /* Sanity check */ if ( iob_len ( iobuf ) < sizeof ( *aoehdr ) ) { + DBG ( "AoE received underlength packet (%zd bytes)\n", + iob_len ( iobuf ) ); rc = -EINVAL; - goto done; + goto err_sanity; } if ( ( aoehdr->ver_flags & AOE_VERSION_MASK ) != AOE_VERSION ) { + DBG ( "AoE received packet for unsupported protocol version " + "%02x\n", ( aoehdr->ver_flags & AOE_VERSION_MASK ) ); rc = -EPROTONOSUPPORT; - goto done; + goto err_sanity; } if ( ! ( aoehdr->ver_flags & AOE_FL_RESPONSE ) ) { - /* Ignore AoE requests that we happen to see */ - goto done; - } - iob_pull ( iobuf, sizeof ( *aoehdr ) ); - - /* Demultiplex amongst active AoE sessions */ - list_for_each_entry ( aoe, &aoe_sessions, list ) { - if ( ntohs ( aoehdr->major ) != aoe->major ) - continue; - if ( aoehdr->minor != aoe->minor ) - continue; - if ( ntohl ( aoehdr->tag ) != aoe->tag ) - continue; - if ( aoehdr->ver_flags & AOE_FL_ERROR ) { - aoe_done ( aoe, -EIO ); - break; - } - switch ( aoehdr->command ) { - case AOE_CMD_ATA: - rc = aoe_rx_ata ( aoe, iobuf->data, iob_len ( iobuf )); - break; - case AOE_CMD_CONFIG: - rc = aoe_rx_cfg ( aoe, ll_source ); - break; - default: - DBGC ( aoe, "AoE %p ignoring command %02x\n", - aoe, aoehdr->command ); - break; - } - break; + DBG ( "AoE received request packet\n" ); + rc = -EOPNOTSUPP; + goto err_sanity; } - done: + /* Demultiplex amongst active AoE commands */ + aoecmd = aoecmd_find_tag ( ntohl ( aoehdr->tag ) ); + if ( ! aoecmd ) { + DBG ( "AoE received packet for unused tag %08x\n", + ntohl ( aoehdr->tag ) ); + rc = -ENOENT; + goto err_demux; + } + + /* Pass received frame to command */ + aoecmd_get ( aoecmd ); + if ( ( rc = aoecmd_rx ( aoecmd, iob_disown ( iobuf ), + ll_source ) ) != 0 ) + goto err_rx; + + err_rx: + aoecmd_put ( aoecmd ); + err_demux: + err_sanity: free_iob ( iobuf ); return rc; } @@ -346,126 +942,90 @@ struct net_protocol aoe_protocol __net_protocol = { .rx = aoe_rx, }; -/** - * Issue ATA command via an open AoE session +/****************************************************************************** * - * @v ata ATA device - * @v command ATA command - * @ret rc Return status code + * AoE URIs + * + ****************************************************************************** */ -static int aoe_command ( struct ata_device *ata, - struct ata_command *command ) { - struct aoe_session *aoe = - container_of ( ata->backend, struct aoe_session, refcnt ); - aoe->command = command; - aoe->status = 0; - aoe->command_offset = 0; - aoe->aoe_cmd_type = AOE_CMD_ATA; +/** + * Parse AoE URI + * + * @v uri URI + * @ret major Major device number + * @ret minor Minor device number + * @ret rc Return status code + * + * An AoE URI has the form "aoe:e.". + */ +static int aoe_parse_uri ( struct uri *uri, unsigned int *major, + unsigned int *minor ) { + const char *ptr; + char *end; - aoe_send_command ( aoe ); + /* Check for URI with opaque portion */ + if ( ! uri->opaque ) + return -EINVAL; + ptr = uri->opaque; + + /* Check for initial 'e' */ + if ( *ptr != 'e' ) + return -EINVAL; + ptr++; + + /* Parse major device number */ + *major = strtoul ( ptr, &end, 10 ); + if ( *end != '.' ) + return -EINVAL; + ptr = ( end + 1 ); + + /* Parse minor device number */ + *minor = strtoul ( ptr, &end, 10 ); + if ( *end ) + return -EINVAL; return 0; } /** - * Issue AoE config query for AoE target discovery + * Open AoE URI * - * @v aoe AoE session + * @v parent Parent interface + * @v uri URI * @ret rc Return status code */ -static int aoe_discover ( struct aoe_session *aoe ) { +static int aoe_open ( struct interface *parent, struct uri *uri ) { + struct net_device *netdev; + unsigned int major; + unsigned int minor; int rc; - aoe->status = 0; - aoe->aoe_cmd_type = AOE_CMD_CONFIG; - aoe->command = NULL; + /* Identify network device. This is something of a hack, but + * the AoE URI scheme that has been in use for some time now + * provides no way to specify a particular device. + */ + netdev = last_opened_netdev(); + if ( ! netdev ) { + DBG ( "AoE cannot identify network device\n" ); + return -ENODEV; + } - aoe_send_command ( aoe ); + /* Parse URI */ + if ( ( rc = aoe_parse_uri ( uri, &major, &minor ) ) != 0 ) { + DBG ( "AoE cannot parse URI\n" ); + return rc; + } - aoe->rc = -EINPROGRESS; - while ( aoe->rc == -EINPROGRESS ) - step(); - rc = aoe->rc; - - return rc; -} - -static int aoe_detached_command ( struct ata_device *ata __unused, - struct ata_command *command __unused ) { - return -ENODEV; -} - -void aoe_detach ( struct ata_device *ata ) { - struct aoe_session *aoe = - container_of ( ata->backend, struct aoe_session, refcnt ); - - stop_timer ( &aoe->timer ); - ata->command = aoe_detached_command; - list_del ( &aoe->list ); - ref_put ( ata->backend ); - ata->backend = NULL; -} - -static int aoe_parse_root_path ( struct aoe_session *aoe, - const char *root_path ) { - char *ptr; - - if ( strncmp ( root_path, "aoe:", 4 ) != 0 ) - return -EINVAL; - ptr = ( ( char * ) root_path + 4 ); - - if ( *ptr++ != 'e' ) - return -EINVAL; - - aoe->major = strtoul ( ptr, &ptr, 10 ); - if ( *ptr++ != '.' ) - return -EINVAL; - - aoe->minor = strtoul ( ptr, &ptr, 10 ); - if ( *ptr ) - return -EINVAL; + /* Open AoE device */ + if ( ( rc = aoedev_open ( parent, netdev, major, minor ) ) != 0 ) + return rc; return 0; } -int aoe_attach ( struct ata_device *ata, struct net_device *netdev, - const char *root_path ) { - struct aoe_session *aoe; - int rc; - - /* Allocate and initialise structure */ - aoe = zalloc ( sizeof ( *aoe ) ); - if ( ! aoe ) - return -ENOMEM; - ref_init ( &aoe->refcnt, aoe_free ); - timer_init ( &aoe->timer, aoe_timer_expired, &aoe->refcnt ); - aoe->netdev = netdev_get ( netdev ); - memcpy ( aoe->target, netdev->ll_broadcast, sizeof ( aoe->target ) ); - aoe->tag = AOE_TAG_MAGIC; - - /* Parse root path */ - if ( ( rc = aoe_parse_root_path ( aoe, root_path ) ) != 0 ) - goto err; - - /* Attach parent interface, transfer reference to connection - * list, and return - */ - ata->backend = ref_get ( &aoe->refcnt ); - ata->command = aoe_command; - list_add ( &aoe->list, &aoe_sessions ); - - /* Send discovery packet to find the target MAC address. - * Ideally, this ought to be done asynchronously, but the - * block device interface does not yet support asynchronous - * operation. - */ - if ( ( rc = aoe_discover( aoe ) ) != 0 ) - goto err; - - return 0; - - err: - ref_put ( &aoe->refcnt ); - return rc; -} +/** AoE URI opener */ +struct uri_opener aoe_uri_opener __uri_opener = { + .scheme = "aoe", + .open = aoe_open, +}; diff --git a/src/net/infiniband/ib_srp.c b/src/net/infiniband/ib_srp.c index ef0078d4..41416a20 100644 --- a/src/net/infiniband/ib_srp.c +++ b/src/net/infiniband/ib_srp.c @@ -32,7 +32,11 @@ FILE_LICENCE ( BSD2 ); #include #include +#include +#include +#include #include +#include #include #include #include @@ -56,6 +60,192 @@ FILE_LICENCE ( BSD2 ); #define EINFO_EINVAL_RP_TOO_SHORT __einfo_uniqify \ ( EINFO_EINVAL, 0x04, "Root path too short" ) +/****************************************************************************** + * + * IB SRP devices + * + ****************************************************************************** + */ + +/** An Infiniband SRP device */ +struct ib_srp_device { + /** Reference count */ + struct refcnt refcnt; + + /** SRP transport interface */ + struct interface srp; + /** CMRC interface */ + struct interface cmrc; + + /** Infiniband device */ + struct ib_device *ibdev; + + /** Destination GID (for boot firmware table) */ + struct ib_gid dgid; + /** Service ID (for boot firmware table) */ + struct ib_gid_half service_id; +}; + +/** + * Free IB SRP device + * + * @v refcnt Reference count + */ +static void ib_srp_free ( struct refcnt *refcnt ) { + struct ib_srp_device *ib_srp = + container_of ( refcnt, struct ib_srp_device, refcnt ); + + ibdev_put ( ib_srp->ibdev ); + free ( ib_srp ); +} + +/** + * Close IB SRP device + * + * @v ib_srp IB SRP device + * @v rc Reason for close + */ +static void ib_srp_close ( struct ib_srp_device *ib_srp, int rc ) { + + /* Shut down interfaces */ + intf_shutdown ( &ib_srp->cmrc, rc ); + intf_shutdown ( &ib_srp->srp, rc ); +} + +/** + * Describe IB 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 ib_srp_describe ( struct ib_srp_device *ib_srp, + struct acpi_description_header *acpi, + size_t len ) { + struct ib_device *ibdev = ib_srp->ibdev; + struct sbft_table *sbft = + container_of ( acpi, struct sbft_table, acpi ); + struct sbft_ib_subtable *ib_sbft; + size_t used; + + /* Sanity check */ + if ( acpi->signature != SBFT_SIG ) + return -EINVAL; + + /* Append IB subtable to existing table */ + used = le32_to_cpu ( sbft->acpi.length ); + sbft->ib_offset = cpu_to_le16 ( used ); + ib_sbft = ( ( ( void * ) sbft ) + used ); + used += sizeof ( *ib_sbft ); + if ( used > len ) + return -ENOBUFS; + sbft->acpi.length = cpu_to_le32 ( used ); + + /* Populate subtable */ + memcpy ( &ib_sbft->sgid, &ibdev->gid, sizeof ( ib_sbft->sgid ) ); + memcpy ( &ib_sbft->dgid, &ib_srp->dgid, sizeof ( ib_sbft->dgid ) ); + memcpy ( &ib_sbft->service_id, &ib_srp->service_id, + sizeof ( ib_sbft->service_id ) ); + ib_sbft->pkey = cpu_to_le16 ( ibdev->pkey ); + + return 0; +} + +/** IB SRP CMRC interface operations */ +static struct interface_operation ib_srp_cmrc_op[] = { + INTF_OP ( intf_close, struct ib_srp_device *, ib_srp_close ), +}; + +/** IB SRP CMRC interface descriptor */ +static struct interface_descriptor ib_srp_cmrc_desc = + INTF_DESC_PASSTHRU ( struct ib_srp_device, cmrc, ib_srp_cmrc_op, srp ); + +/** IB SRP SRP interface operations */ +static struct interface_operation ib_srp_srp_op[] = { + INTF_OP ( acpi_describe, struct ib_srp_device *, ib_srp_describe ), + INTF_OP ( intf_close, struct ib_srp_device *, ib_srp_close ), +}; + +/** IB SRP SRP interface descriptor */ +static struct interface_descriptor ib_srp_srp_desc = + INTF_DESC_PASSTHRU ( struct ib_srp_device, srp, ib_srp_srp_op, cmrc ); + +/** + * Open IB SRP device + * + * @v block Block control interface + * @v ibdev Infiniband device + * @v dgid Destination GID + * @v service_id Service ID + * @v initiator Initiator port ID + * @v target Target port ID + * @v lun SCSI LUN + * @ret rc Return status code + */ +static int ib_srp_open ( struct interface *block, struct ib_device *ibdev, + struct ib_gid *dgid, struct ib_gid_half *service_id, + union srp_port_id *initiator, + union srp_port_id *target, struct scsi_lun *lun ) { + struct ib_srp_device *ib_srp; + int rc; + + /* Allocate and initialise structure */ + ib_srp = zalloc ( sizeof ( *ib_srp ) ); + if ( ! ib_srp ) { + rc = -ENOMEM; + goto err_zalloc; + } + ref_init ( &ib_srp->refcnt, ib_srp_free ); + intf_init ( &ib_srp->srp, &ib_srp_srp_desc, &ib_srp->refcnt ); + intf_init ( &ib_srp->cmrc, &ib_srp_cmrc_desc, &ib_srp->refcnt ); + ib_srp->ibdev = ibdev_get ( ibdev ); + DBGC ( ib_srp, "IBSRP %p created for %08x%08x%08x%08x:%08x%08x\n", + ib_srp, ntohl ( dgid->u.dwords[0] ), + ntohl ( dgid->u.dwords[1] ), ntohl ( dgid->u.dwords[2] ), + ntohl ( dgid->u.dwords[3] ), ntohl ( service_id->u.dwords[0] ), + ntohl ( service_id->u.dwords[1] ) ); + + /* Preserve parameters required for boot firmware table */ + memcpy ( &ib_srp->dgid, dgid, sizeof ( ib_srp->dgid ) ); + memcpy ( &ib_srp->service_id, service_id, + sizeof ( ib_srp->service_id ) ); + + /* Open CMRC socket */ + if ( ( rc = ib_cmrc_open ( &ib_srp->cmrc, ibdev, dgid, + service_id ) ) != 0 ) { + DBGC ( ib_srp, "IBSRP %p could not open CMRC socket: %s\n", + ib_srp, strerror ( rc ) ); + goto err_cmrc_open; + } + + /* Attach SRP device to parent interface */ + if ( ( rc = srp_open ( block, &ib_srp->srp, initiator, target, + ibdev->rdma_key, lun ) ) != 0 ) { + DBGC ( ib_srp, "IBSRP %p could not create SRP device: %s\n", + ib_srp, strerror ( rc ) ); + goto err_srp_open; + } + + /* Mortalise self and return */ + ref_put ( &ib_srp->refcnt ); + return 0; + + err_srp_open: + err_cmrc_open: + ib_srp_close ( ib_srp, rc ); + ref_put ( &ib_srp->refcnt ); + err_zalloc: + return rc; +} + +/****************************************************************************** + * + * IB SRP URIs + * + ****************************************************************************** + */ + /** IB SRP parse flags */ enum ib_srp_parse_flags { IB_SRP_PARSE_REQUIRED = 0x0000, @@ -65,12 +255,20 @@ enum ib_srp_parse_flags { /** IB SRP root path parameters */ struct ib_srp_root_path { + /** Source GID */ + struct ib_gid sgid; + /** Initiator port ID */ + union ib_srp_initiator_port_id initiator; + /** Destination GID */ + struct ib_gid dgid; + /** Partition key */ + uint16_t pkey; + /** Service ID */ + struct ib_gid_half service_id; /** SCSI LUN */ - struct scsi_lun *lun; - /** SRP port IDs */ - struct srp_port_ids *port_ids; - /** IB SRP parameters */ - struct ib_srp_parameters *ib; + struct scsi_lun lun; + /** Target port ID */ + union ib_srp_target_port_id target; }; /** @@ -99,7 +297,6 @@ static int ib_srp_parse_byte_string ( const char *rp_comp, uint8_t *bytes, decoded_size = base16_decode ( rp_comp, bytes ); if ( decoded_size < 0 ) return decoded_size; - assert ( decoded_size == size ); return 0; } @@ -125,19 +322,6 @@ static int ib_srp_parse_integer ( const char *rp_comp, int default_value ) { return value; } -/** - * Parse IB SRP root path literal component - * - * @v rp_comp Root path component string - * @v rp IB SRP root path - * @ret rc Return status code - */ -static int ib_srp_parse_literal ( const char *rp_comp __unused, - struct ib_srp_root_path *rp __unused ) { - /* Ignore */ - return 0; -} - /** * Parse IB SRP root path source GID * @@ -151,10 +335,10 @@ static int ib_srp_parse_sgid ( const char *rp_comp, /* Default to the GID of the last opened Infiniband device */ if ( ( ibdev = last_opened_ibdev() ) != NULL ) - memcpy ( &rp->ib->sgid, &ibdev->gid, sizeof ( rp->ib->sgid ) ); + memcpy ( &rp->sgid, &ibdev->gid, sizeof ( rp->sgid ) ); - return ib_srp_parse_byte_string ( rp_comp, rp->ib->sgid.u.bytes, - ( sizeof ( rp->ib->sgid ) | + return ib_srp_parse_byte_string ( rp_comp, rp->sgid.u.bytes, + ( sizeof ( rp->sgid ) | IB_SRP_PARSE_OPTIONAL ) ); } @@ -167,11 +351,10 @@ static int ib_srp_parse_sgid ( const char *rp_comp, */ static int ib_srp_parse_initiator_id_ext ( const char *rp_comp, struct ib_srp_root_path *rp ) { - struct ib_srp_initiator_port_id *port_id = - ib_srp_initiator_port_id ( rp->port_ids ); + union ib_srp_initiator_port_id *port_id = &rp->initiator; - return ib_srp_parse_byte_string ( rp_comp, port_id->id_ext.u.bytes, - ( sizeof ( port_id->id_ext ) | + return ib_srp_parse_byte_string ( rp_comp, port_id->ib.id_ext.u.bytes, + ( sizeof ( port_id->ib.id_ext ) | IB_SRP_PARSE_OPTIONAL ) ); } @@ -184,15 +367,14 @@ static int ib_srp_parse_initiator_id_ext ( const char *rp_comp, */ static int ib_srp_parse_initiator_hca_guid ( const char *rp_comp, struct ib_srp_root_path *rp ) { - struct ib_srp_initiator_port_id *port_id = - ib_srp_initiator_port_id ( rp->port_ids ); + union ib_srp_initiator_port_id *port_id = &rp->initiator; /* Default to the GUID portion of the source GID */ - memcpy ( &port_id->hca_guid, &rp->ib->sgid.u.half[1], - sizeof ( port_id->hca_guid ) ); + memcpy ( &port_id->ib.hca_guid, &rp->sgid.u.half[1], + sizeof ( port_id->ib.hca_guid ) ); - return ib_srp_parse_byte_string ( rp_comp, port_id->hca_guid.u.bytes, - ( sizeof ( port_id->hca_guid ) | + return ib_srp_parse_byte_string ( rp_comp, port_id->ib.hca_guid.u.bytes, + ( sizeof ( port_id->ib.hca_guid ) | IB_SRP_PARSE_OPTIONAL ) ); } @@ -205,8 +387,8 @@ static int ib_srp_parse_initiator_hca_guid ( const char *rp_comp, */ static int ib_srp_parse_dgid ( const char *rp_comp, struct ib_srp_root_path *rp ) { - return ib_srp_parse_byte_string ( rp_comp, rp->ib->dgid.u.bytes, - ( sizeof ( rp->ib->dgid ) | + return ib_srp_parse_byte_string ( rp_comp, rp->dgid.u.bytes, + ( sizeof ( rp->dgid ) | IB_SRP_PARSE_REQUIRED ) ); } @@ -223,7 +405,7 @@ static int ib_srp_parse_pkey ( const char *rp_comp, if ( ( pkey = ib_srp_parse_integer ( rp_comp, IB_PKEY_DEFAULT ) ) < 0 ) return pkey; - rp->ib->pkey = pkey; + rp->pkey = pkey; return 0; } @@ -236,8 +418,8 @@ static int ib_srp_parse_pkey ( const char *rp_comp, */ static int ib_srp_parse_service_id ( const char *rp_comp, struct ib_srp_root_path *rp ) { - return ib_srp_parse_byte_string ( rp_comp, rp->ib->service_id.u.bytes, - ( sizeof ( rp->ib->service_id ) | + return ib_srp_parse_byte_string ( rp_comp, rp->service_id.u.bytes, + ( sizeof ( rp->service_id ) | IB_SRP_PARSE_REQUIRED ) ); } @@ -250,7 +432,7 @@ static int ib_srp_parse_service_id ( const char *rp_comp, */ static int ib_srp_parse_lun ( const char *rp_comp, struct ib_srp_root_path *rp ) { - return scsi_parse_lun ( rp_comp, rp->lun ); + return scsi_parse_lun ( rp_comp, &rp->lun ); } /** @@ -262,11 +444,10 @@ static int ib_srp_parse_lun ( const char *rp_comp, */ static int ib_srp_parse_target_id_ext ( const char *rp_comp, struct ib_srp_root_path *rp ) { - struct ib_srp_target_port_id *port_id = - ib_srp_target_port_id ( rp->port_ids ); + union ib_srp_target_port_id *port_id = &rp->target; - return ib_srp_parse_byte_string ( rp_comp, port_id->id_ext.u.bytes, - ( sizeof ( port_id->id_ext ) | + return ib_srp_parse_byte_string ( rp_comp, port_id->ib.id_ext.u.bytes, + ( sizeof ( port_id->ib.id_ext ) | IB_SRP_PARSE_REQUIRED ) ); } @@ -279,11 +460,10 @@ static int ib_srp_parse_target_id_ext ( const char *rp_comp, */ static int ib_srp_parse_target_ioc_guid ( const char *rp_comp, struct ib_srp_root_path *rp ) { - struct ib_srp_target_port_id *port_id = - ib_srp_target_port_id ( rp->port_ids ); + union ib_srp_target_port_id *port_id = &rp->target; - return ib_srp_parse_byte_string ( rp_comp, port_id->ioc_guid.u.bytes, - ( sizeof ( port_id->ioc_guid ) | + return ib_srp_parse_byte_string ( rp_comp, port_id->ib.ioc_guid.u.bytes, + ( sizeof ( port_id->ib.ioc_guid ) | IB_SRP_PARSE_REQUIRED ) ); } @@ -301,7 +481,6 @@ struct ib_srp_root_path_parser { /** IB SRP root path components */ static struct ib_srp_root_path_parser ib_srp_rp_parser[] = { - { ib_srp_parse_literal }, { ib_srp_parse_sgid }, { ib_srp_parse_initiator_id_ext }, { ib_srp_parse_initiator_hca_guid }, @@ -320,18 +499,13 @@ static struct ib_srp_root_path_parser ib_srp_rp_parser[] = { /** * Parse IB SRP root path * - * @v srp SRP device - * @v rp_string Root path + * @v rp_string Root path string + * @v rp IB SRP root path * @ret rc Return status code */ -static int ib_srp_parse_root_path ( struct srp_device *srp, - const char *rp_string ) { - struct ib_srp_parameters *ib_params = ib_srp_params ( srp ); - struct ib_srp_root_path rp = { - .lun = &srp->lun, - .port_ids = &srp->port_ids, - .ib = ib_params, - }; +static int ib_srp_parse_root_path ( const char *rp_string, + struct ib_srp_root_path *rp ) { + struct ib_srp_root_path_parser *parser; char rp_string_copy[ strlen ( rp_string ) + 1 ]; char *rp_comp[IB_SRP_NUM_RP_COMPONENTS]; char *rp_string_tmp = rp_string_copy; @@ -346,8 +520,8 @@ static int ib_srp_parse_root_path ( struct srp_device *srp, break; for ( ; *rp_string_tmp != ':' ; rp_string_tmp++ ) { if ( ! *rp_string_tmp ) { - DBGC ( srp, "SRP %p root path \"%s\" too " - "short\n", srp, rp_string ); + DBG ( "IBSRP root path \"%s\" too short\n", + rp_string ); return -EINVAL_RP_TOO_SHORT; } } @@ -356,11 +530,11 @@ static int ib_srp_parse_root_path ( struct srp_device *srp, /* Parse root path components */ for ( i = 0 ; i < IB_SRP_NUM_RP_COMPONENTS ; i++ ) { - if ( ( rc = ib_srp_rp_parser[i].parse ( rp_comp[i], - &rp ) ) != 0 ) { - DBGC ( srp, "SRP %p could not parse \"%s\" in root " - "path \"%s\": %s\n", srp, rp_comp[i], - rp_string, strerror ( rc ) ); + parser = &ib_srp_rp_parser[i]; + if ( ( rc = parser->parse ( rp_comp[i], rp ) ) != 0 ) { + DBG ( "IBSRP could not parse \"%s\" in root path " + "\"%s\": %s\n", rp_comp[i], rp_string, + strerror ( rc ) ); return rc; } } @@ -369,41 +543,42 @@ static int ib_srp_parse_root_path ( struct srp_device *srp, } /** - * Connect IB SRP session + * Open IB SRP URI * - * @v srp SRP device + * @v parent Parent interface + * @v uri URI * @ret rc Return status code */ -static int ib_srp_connect ( struct srp_device *srp ) { - struct ib_srp_parameters *ib_params = ib_srp_params ( srp ); +static int ib_srp_open_uri ( struct interface *parent, struct uri *uri ) { + struct ib_srp_root_path rp; struct ib_device *ibdev; int rc; + /* Parse URI */ + if ( ! uri->opaque ) + return -EINVAL; + memset ( &rp, 0, sizeof ( rp ) ); + if ( ( rc = ib_srp_parse_root_path ( uri->opaque, &rp ) ) != 0 ) + return rc; + /* Identify Infiniband device */ - ibdev = find_ibdev ( &ib_params->sgid ); + ibdev = find_ibdev ( &rp.sgid ); if ( ! ibdev ) { - DBGC ( srp, "SRP %p could not identify Infiniband device\n", - srp ); + DBG ( "IBSRP could not identify Infiniband device\n" ); return -ENODEV; } - /* Configure remaining SRP parameters */ - srp->memory_handle = ibdev->rdma_key; - - /* Open CMRC socket */ - if ( ( rc = ib_cmrc_open ( &srp->socket, ibdev, &ib_params->dgid, - &ib_params->service_id ) ) != 0 ) { - DBGC ( srp, "SRP %p could not open CMRC socket: %s\n", - srp, strerror ( rc ) ); + /* Open IB SRP device */ + if ( ( rc = ib_srp_open ( parent, ibdev, &rp.dgid, &rp.service_id, + &rp.initiator.srp, &rp.target.srp, + &rp.lun ) ) != 0 ) return rc; - } return 0; } -/** IB SRP transport type */ -struct srp_transport_type ib_srp_transport = { - .priv_len = sizeof ( struct ib_srp_parameters ), - .parse_root_path = ib_srp_parse_root_path, - .connect = ib_srp_connect, +/** IB SRP URI opener */ +struct uri_opener ib_srp_uri_opener __uri_opener = { + .scheme = "ib_srp", + .open = ib_srp_open_uri, }; diff --git a/src/net/tcp/iscsi.c b/src/net/tcp/iscsi.c index 80c63a6b..e19f4d82 100644 --- a/src/net/tcp/iscsi.c +++ b/src/net/tcp/iscsi.c @@ -28,6 +28,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include /** @file @@ -127,6 +129,36 @@ static void iscsi_rx_buffered_data_done ( struct iscsi_session *iscsi ) { iscsi->rx_buffer = NULL; } +/** + * Receive PDU data into buffer + * + * @v iscsi iSCSI session + * @v data Data to receive + * @v len Length of data + * @ret rc Return status code + * + * This can be used when the RX PDU type handler wishes to buffer up + * all received data and process the PDU as a single unit. The caller + * is repsonsible for calling iscsi_rx_buffered_data_done() after + * processing the data. + */ +static int iscsi_rx_buffered_data ( struct iscsi_session *iscsi, + const void *data, size_t len ) { + + /* Allocate buffer on first call */ + if ( ! iscsi->rx_buffer ) { + iscsi->rx_buffer = malloc ( iscsi->rx_len ); + if ( ! iscsi->rx_buffer ) + return -ENOMEM; + } + + /* Copy data to buffer */ + assert ( ( iscsi->rx_offset + len ) <= iscsi->rx_len ); + memcpy ( ( iscsi->rx_buffer + iscsi->rx_offset ), data, len ); + + return 0; +} + /** * Free iSCSI session * @@ -144,9 +176,44 @@ static void iscsi_free ( struct refcnt *refcnt ) { free ( iscsi->target_password ); chap_finish ( &iscsi->chap ); iscsi_rx_buffered_data_done ( iscsi ); + free ( iscsi->command ); free ( iscsi ); } +/** + * Shut down iSCSI interface + * + * @v iscsi iSCSI session + * @v rc Reason for close + */ +static void iscsi_close ( struct iscsi_session *iscsi, int rc ) { + + /* A TCP graceful close is still an error from our point of view */ + if ( rc == 0 ) + rc = -ECONNRESET; + + DBGC ( iscsi, "iSCSI %p closed: %s\n", iscsi, strerror ( rc ) ); + + /* Stop transmission process */ + process_del ( &iscsi->process ); + + /* Shut down interfaces */ + intf_shutdown ( &iscsi->socket, rc ); + intf_shutdown ( &iscsi->control, rc ); + intf_shutdown ( &iscsi->data, rc ); +} + +/** + * Assign new iSCSI initiator task tag + * + * @v iscsi iSCSI session + */ +static void iscsi_new_itt ( struct iscsi_session *iscsi ) { + static uint16_t itt_idx; + + iscsi->itt = ( ISCSI_TAG_MAGIC | (++itt_idx) ); +} + /** * Open iSCSI transport-layer connection * @@ -180,7 +247,7 @@ static int iscsi_open_connection ( struct iscsi_session *iscsi ) { iscsi->status |= ISCSI_STATUS_AUTH_REVERSE_REQUIRED; /* Assign fresh initiator task tag */ - iscsi->itt++; + iscsi_new_itt ( iscsi ); /* Initiate login */ iscsi_start_login ( iscsi ); @@ -220,21 +287,34 @@ static void iscsi_close_connection ( struct iscsi_session *iscsi, int rc ) { * * @v iscsi iSCSI session * @v rc Return status code + * @v rsp SCSI response, if any * * Note that iscsi_scsi_done() will not close the connection, and must * therefore be called only when the internal state machines are in an * appropriate state, otherwise bad things may happen on the next call - * to iscsi_issue(). The general rule is to call iscsi_scsi_done() - * only at the end of receiving a PDU; at this point the TX and RX - * engines should both be idle. + * to iscsi_scsi_command(). The general rule is to call + * iscsi_scsi_done() only at the end of receiving a PDU; at this point + * the TX and RX engines should both be idle. */ -static void iscsi_scsi_done ( struct iscsi_session *iscsi, int rc ) { +static void iscsi_scsi_done ( struct iscsi_session *iscsi, int rc, + struct scsi_rsp *rsp ) { + uint32_t itt = iscsi->itt; assert ( iscsi->tx_state == ISCSI_TX_IDLE ); - assert ( iscsi->command != NULL ); - iscsi->command->rc = rc; + /* Clear command */ + free ( iscsi->command ); iscsi->command = NULL; + + /* Send SCSI response, if any */ + scsi_response ( &iscsi->data, rsp ); + + /* Close SCSI command, if this is still the same command. (It + * is possible that the command interface has already been + * closed as a result of the SCSI response we sent.) + */ + if ( iscsi->itt == itt ) + intf_restart ( &iscsi->data, rc ); } /**************************************************************************** @@ -268,8 +348,9 @@ static void iscsi_start_command ( struct iscsi_session *iscsi ) { if ( iscsi->command->data_out ) command->flags |= ISCSI_COMMAND_FLAG_WRITE; /* lengths left as zero */ - command->lun = iscsi->lun; - command->itt = htonl ( ++iscsi->itt ); + memcpy ( &command->lun, &iscsi->command->lun, + sizeof ( command->lun ) ); + command->itt = htonl ( iscsi->itt ); command->exp_len = htonl ( iscsi->command->data_in_len | iscsi->command->data_out_len ); command->cmdsn = htonl ( iscsi->cmdsn ); @@ -297,28 +378,39 @@ static int iscsi_rx_scsi_response ( struct iscsi_session *iscsi, size_t remaining ) { struct iscsi_bhs_scsi_response *response = &iscsi->rx_bhs.scsi_response; - int sense_offset; + struct scsi_rsp rsp; + uint32_t residual_count; + int rc; - /* Capture the sense response code as it floats past, if present */ - sense_offset = ISCSI_SENSE_RESPONSE_CODE_OFFSET - iscsi->rx_offset; - if ( ( sense_offset >= 0 ) && len ) { - iscsi->command->sense_response = - * ( ( char * ) data + sense_offset ); + /* Buffer up the PDU data */ + if ( ( rc = iscsi_rx_buffered_data ( iscsi, data, len ) ) != 0 ) { + DBGC ( iscsi, "iSCSI %p could not buffer login response: %s\n", + iscsi, strerror ( rc ) ); + return rc; } - - /* Wait for whole SCSI response to arrive */ if ( remaining ) return 0; - - /* Record SCSI status code */ - iscsi->command->status = response->status; + + /* Parse SCSI response and discard buffer */ + memset ( &rsp, 0, sizeof ( rsp ) ); + rsp.status = response->status; + residual_count = ntohl ( response->residual_count ); + if ( response->flags & ISCSI_DATA_FLAG_OVERFLOW ) { + rsp.overrun = residual_count; + } else if ( response->flags & ISCSI_DATA_FLAG_UNDERFLOW ) { + rsp.overrun = -(residual_count); + } + if ( ISCSI_DATA_LEN ( response->lengths ) ) + memcpy ( &rsp.sense, ( iscsi->rx_buffer + 2 ), + sizeof ( rsp.sense ) ); + iscsi_rx_buffered_data_done ( iscsi ); /* Check for errors */ if ( response->response != ISCSI_RESPONSE_COMMAND_COMPLETE ) return -EIO; /* Mark as completed */ - iscsi_scsi_done ( iscsi, 0 ); + iscsi_scsi_done ( iscsi, 0, &rsp ); return 0; } @@ -352,9 +444,8 @@ static int iscsi_rx_data_in ( struct iscsi_session *iscsi, if ( data_in->flags & ISCSI_DATA_FLAG_STATUS ) { assert ( ( offset + len ) == iscsi->command->data_in_len ); assert ( data_in->flags & ISCSI_FLAG_FINAL ); - iscsi->command->status = data_in->status; /* iSCSI cannot return an error status via a data-in */ - iscsi_scsi_done ( iscsi, 0 ); + iscsi_scsi_done ( iscsi, 0, NULL ); } return 0; @@ -412,7 +503,7 @@ static void iscsi_start_data_out ( struct iscsi_session *iscsi, if ( len == remaining ) data_out->flags = ( ISCSI_FLAG_FINAL ); ISCSI_SET_LENGTHS ( data_out->lengths, 0, len ); - data_out->lun = iscsi->lun; + data_out->lun = iscsi->command->lun; data_out->itt = htonl ( iscsi->itt ); data_out->ttt = htonl ( iscsi->ttt ); data_out->expstatsn = htonl ( iscsi->statsn + 1 ); @@ -592,6 +683,19 @@ static void iscsi_start_login ( struct iscsi_session *iscsi ) { struct iscsi_bhs_login_request *request = &iscsi->tx_bhs.login_request; int len; + switch ( iscsi->status & ISCSI_LOGIN_CSG_MASK ) { + case ISCSI_LOGIN_CSG_SECURITY_NEGOTIATION: + DBGC ( iscsi, "iSCSI %p entering security negotiation\n", + iscsi ); + break; + case ISCSI_LOGIN_CSG_OPERATIONAL_NEGOTIATION: + DBGC ( iscsi, "iSCSI %p entering operational negotiation\n", + iscsi ); + break; + default: + assert ( 0 ); + } + /* Construct BHS and initiate transmission */ iscsi_start_tx ( iscsi ); request->opcode = ( ISCSI_OPCODE_LOGIN_REQUEST | @@ -604,7 +708,7 @@ static void iscsi_start_login ( struct iscsi_session *iscsi ) { request->isid_iana_en = htonl ( ISCSI_ISID_IANA | IANA_EN_FEN_SYSTEMS ); /* isid_iana_qual left as zero */ - request->tsih = htons ( iscsi->tsih ); + /* tsih left as zero */ request->itt = htonl ( iscsi->itt ); /* cid left as zero */ request->cmdsn = htonl ( iscsi->cmdsn ); @@ -1011,36 +1115,6 @@ static int iscsi_handle_strings ( struct iscsi_session *iscsi, return 0; } -/** - * Receive PDU data into buffer - * - * @v iscsi iSCSI session - * @v data Data to receive - * @v len Length of data - * @ret rc Return status code - * - * This can be used when the RX PDU type handler wishes to buffer up - * all received data and process the PDU as a single unit. The caller - * is repsonsible for calling iscsi_rx_buffered_data_done() after - * processing the data. - */ -static int iscsi_rx_buffered_data ( struct iscsi_session *iscsi, - const void *data, size_t len ) { - - /* Allocate buffer on first call */ - if ( ! iscsi->rx_buffer ) { - iscsi->rx_buffer = malloc ( iscsi->rx_len ); - if ( ! iscsi->rx_buffer ) - return -ENOMEM; - } - - /* Copy data to buffer */ - assert ( ( iscsi->rx_offset + len ) <= iscsi->rx_len ); - memcpy ( ( iscsi->rx_buffer + iscsi->rx_offset ), data, len ); - - return 0; -} - /** * Convert iSCSI response status to return status code * @@ -1119,7 +1193,6 @@ static int iscsi_rx_login_response ( struct iscsi_session *iscsi, response->status_class, response->status_detail ); rc = iscsi_status_to_rc ( response->status_class, response->status_detail ); - iscsi->instant_rc = rc; return rc; } @@ -1160,14 +1233,9 @@ static int iscsi_rx_login_response ( struct iscsi_session *iscsi, return -EPROTO; } - /* Reset retry count */ - iscsi->retry_count = 0; - - /* Record TSIH for future reference */ - iscsi->tsih = ntohl ( response->tsih ); - - /* Send the actual SCSI command */ - iscsi_start_command ( iscsi ); + /* Notify SCSI layer of window change */ + DBGC ( iscsi, "iSCSI %p entering full feature phase\n", iscsi ); + xfer_window_changed ( &iscsi->control ); return 0; } @@ -1187,13 +1255,18 @@ static int iscsi_rx_login_response ( struct iscsi_session *iscsi, * be in transit at any one time. */ static void iscsi_start_tx ( struct iscsi_session *iscsi ) { + assert ( iscsi->tx_state == ISCSI_TX_IDLE ); + assert ( ! process_running ( &iscsi->process ) ); /* Initialise TX BHS */ memset ( &iscsi->tx_bhs, 0, sizeof ( iscsi->tx_bhs ) ); /* Flag TX engine to start transmitting */ iscsi->tx_state = ISCSI_TX_BHS; + + /* Start transmission process */ + process_add ( &iscsi->process ); } /** @@ -1273,6 +1346,9 @@ static int iscsi_tx_data_padding ( struct iscsi_session *iscsi ) { static void iscsi_tx_done ( struct iscsi_session *iscsi ) { struct iscsi_bhs_common *common = &iscsi->tx_bhs.common; + /* Stop transmission process */ + process_del ( &iscsi->process ); + switch ( common->opcode & ISCSI_OPCODE_MASK ) { case ISCSI_OPCODE_DATA_OUT: iscsi_data_out_done ( iscsi ); @@ -1305,9 +1381,6 @@ static void iscsi_tx_step ( struct process *process ) { /* Select fragment to transmit */ while ( 1 ) { switch ( iscsi->tx_state ) { - case ISCSI_TX_IDLE: - /* Stop processing */ - return; case ISCSI_TX_BHS: tx = iscsi_tx_bhs; tx_len = sizeof ( iscsi->tx_bhs ); @@ -1328,6 +1401,10 @@ static void iscsi_tx_step ( struct process *process ) { tx_len = ISCSI_DATA_PAD_LEN ( common->lengths ); next_state = ISCSI_TX_IDLE; break; + case ISCSI_TX_IDLE: + /* Stop processing */ + iscsi_tx_done ( iscsi ); + return; default: assert ( 0 ); return; @@ -1343,13 +1420,13 @@ static void iscsi_tx_step ( struct process *process ) { if ( ( rc = tx ( iscsi ) ) != 0 ) { DBGC ( iscsi, "iSCSI %p could not transmit: %s\n", iscsi, strerror ( rc ) ); + /* Transmission errors are fatal */ + iscsi_close ( iscsi, rc ); return; } /* Move to next state */ iscsi->tx_state = next_state; - if ( next_state == ISCSI_TX_IDLE ) - iscsi_tx_done ( iscsi ); } } @@ -1495,8 +1572,6 @@ static int iscsi_socket_deliver ( struct iscsi_session *iscsi, remaining ) ) != 0 ) { DBGC ( iscsi, "iSCSI %p could not process received " "data: %s\n", iscsi, strerror ( rc ) ); - iscsi_close_connection ( iscsi, rc ); - iscsi_scsi_done ( iscsi, rc ); goto done; } @@ -1518,41 +1593,14 @@ static int iscsi_socket_deliver ( struct iscsi_session *iscsi, done: /* Free I/O buffer */ free_iob ( iobuf ); + + /* Destroy session on error */ + if ( rc != 0 ) + iscsi_close ( iscsi, rc ); + return rc; } -/** - * Handle stream connection closure - * - * @v iscsi iSCSI session - * @v rc Reason for close - * - */ -static void iscsi_socket_close ( struct iscsi_session *iscsi, int rc ) { - - /* Even a graceful close counts as an error for iSCSI */ - if ( ! rc ) - rc = -ECONNRESET; - - /* Close session cleanly */ - iscsi_close_connection ( iscsi, rc ); - - /* Retry connection if within the retry limit, otherwise fail */ - if ( ++iscsi->retry_count <= ISCSI_MAX_RETRIES ) { - DBGC ( iscsi, "iSCSI %p retrying connection (retry #%d)\n", - iscsi, iscsi->retry_count ); - if ( ( rc = iscsi_open_connection ( iscsi ) ) != 0 ) { - DBGC ( iscsi, "iSCSI %p could not reconnect: %s\n", - iscsi, strerror ( rc ) ); - iscsi_scsi_done ( iscsi, rc ); - } - } else { - DBGC ( iscsi, "iSCSI %p retry count exceeded\n", iscsi ); - iscsi->instant_rc = rc; - iscsi_scsi_done ( iscsi, rc ); - } -} - /** * Handle redirection event * @@ -1582,13 +1630,12 @@ static int iscsi_vredirect ( struct iscsi_session *iscsi, int type, return xfer_vreopen ( &iscsi->socket, type, args ); } - /** iSCSI socket interface operations */ static struct interface_operation iscsi_socket_operations[] = { INTF_OP ( xfer_deliver, struct iscsi_session *, iscsi_socket_deliver ), INTF_OP ( xfer_vredirect, struct iscsi_session *, iscsi_vredirect ), - INTF_OP ( intf_close, struct iscsi_session *, iscsi_socket_close ), + INTF_OP ( intf_close, struct iscsi_session *, iscsi_close ), }; /** iSCSI socket interface descriptor */ @@ -1602,54 +1649,100 @@ static struct interface_descriptor iscsi_socket_desc = */ /** - * Issue SCSI command + * Check iSCSI flow-control window * - * @v scsi SCSI device - * @v command SCSI command - * @ret rc Return status code + * @v iscsi iSCSI session + * @ret len Length of window */ -static int iscsi_command ( struct scsi_device *scsi, - struct scsi_command *command ) { - struct iscsi_session *iscsi = - container_of ( scsi->backend, struct iscsi_session, refcnt ); - int rc; +static size_t iscsi_scsi_window ( struct iscsi_session *iscsi ) { - /* Abort immediately if we have a recorded permanent failure */ - if ( iscsi->instant_rc ) - return iscsi->instant_rc; - - /* Record SCSI command */ - iscsi->command = command; - - /* Issue command or open connection as appropriate */ - if ( iscsi->status ) { - iscsi_start_command ( iscsi ); + if ( ( ( iscsi->status & ISCSI_STATUS_PHASE_MASK ) == + ISCSI_STATUS_FULL_FEATURE_PHASE ) && + ( iscsi->command == NULL ) ) { + /* We cannot handle concurrent commands */ + return 1; } else { - if ( ( rc = iscsi_open_connection ( iscsi ) ) != 0 ) { - iscsi->command = NULL; - return rc; - } + return 0; } - - return 0; } /** - * Shut down iSCSI interface + * Issue iSCSI SCSI command * - * @v scsi SCSI device + * @v iscsi iSCSI session + * @v parent Parent interface + * @v command SCSI command + * @ret tag Command tag, or negative error */ -void iscsi_detach ( struct scsi_device *scsi ) { - struct iscsi_session *iscsi = - container_of ( scsi->backend, struct iscsi_session, refcnt ); +static int iscsi_scsi_command ( struct iscsi_session *iscsi, + struct interface *parent, + struct scsi_cmd *command ) { - iscsi_close_connection ( iscsi, 0 ); - process_del ( &iscsi->process ); - scsi->command = scsi_detached_command; - ref_put ( scsi->backend ); - scsi->backend = NULL; + /* This iSCSI implementation cannot handle multiple concurrent + * commands or commands arriving before login is complete. + */ + if ( iscsi_scsi_window ( iscsi ) == 0 ) { + DBGC ( iscsi, "iSCSI %p cannot handle concurrent commands\n", + iscsi ); + return -EOPNOTSUPP; + } + + /* Store command */ + iscsi->command = malloc ( sizeof ( *command ) ); + if ( ! iscsi->command ) + return -ENOMEM; + memcpy ( iscsi->command, command, sizeof ( *command ) ); + + /* Assign new ITT */ + iscsi_new_itt ( iscsi ); + + /* Start sending command */ + iscsi_start_command ( iscsi ); + + /* Attach to parent interface and return */ + intf_plug_plug ( &iscsi->data, parent ); + return iscsi->itt; } +/** iSCSI SCSI command-issuing interface operations */ +static struct interface_operation iscsi_control_op[] = { + INTF_OP ( scsi_command, struct iscsi_session *, iscsi_scsi_command ), + INTF_OP ( xfer_window, struct iscsi_session *, iscsi_scsi_window ), + INTF_OP ( intf_close, struct iscsi_session *, iscsi_close ), + INTF_OP ( acpi_describe, struct iscsi_session *, ibft_describe ), +}; + +/** iSCSI SCSI command-issuing interface descriptor */ +static struct interface_descriptor iscsi_control_desc = + INTF_DESC ( struct iscsi_session, control, iscsi_control_op ); + +/** + * Close iSCSI command + * + * @v iscsi iSCSI session + * @v rc Reason for close + */ +static void iscsi_command_close ( struct iscsi_session *iscsi, int rc ) { + + /* Restart interface */ + intf_restart ( &iscsi->data, rc ); + + /* Treat unsolicited command closures mid-command as fatal, + * because we have no code to handle partially-completed PDUs. + */ + if ( iscsi->command != NULL ) + iscsi_close ( iscsi, ( ( rc == 0 ) ? -ECANCELED : rc ) ); +} + +/** iSCSI SCSI command interface operations */ +static struct interface_operation iscsi_data_op[] = { + INTF_OP ( intf_close, struct iscsi_session *, iscsi_command_close ), +}; + +/** iSCSI SCSI command interface descriptor */ +static struct interface_descriptor iscsi_data_desc = + INTF_DESC ( struct iscsi_session, data, iscsi_data_op ); + /**************************************************************************** * * Instantiator @@ -1658,8 +1751,7 @@ void iscsi_detach ( struct scsi_device *scsi ) { /** iSCSI root path components (as per RFC4173) */ enum iscsi_root_path_component { - RP_LITERAL = 0, - RP_SERVERNAME, + RP_SERVERNAME = 0, RP_PROTOCOL, RP_PORT, RP_LUN, @@ -1779,60 +1871,95 @@ static int iscsi_set_auth ( struct iscsi_session *iscsi, } /** - * Attach iSCSI interface + * Open iSCSI URI * - * @v scsi SCSI device - * @v root_path iSCSI root path (as per RFC4173) + * @v parent Parent interface + * @v uri URI * @ret rc Return status code */ -int iscsi_attach ( struct scsi_device *scsi, const char *root_path ) { +static int iscsi_open ( struct interface *parent, struct uri *uri ) { struct iscsi_session *iscsi; int rc; + /* Sanity check */ + if ( ! uri->opaque ) { + rc = -EINVAL; + goto err_sanity_uri; + } + /* Allocate and initialise structure */ iscsi = zalloc ( sizeof ( *iscsi ) ); - if ( ! iscsi ) - return -ENOMEM; + if ( ! iscsi ) { + rc = -ENOMEM; + goto err_zalloc; + } ref_init ( &iscsi->refcnt, iscsi_free ); + intf_init ( &iscsi->control, &iscsi_control_desc, &iscsi->refcnt ); + intf_init ( &iscsi->data, &iscsi_data_desc, &iscsi->refcnt ); intf_init ( &iscsi->socket, &iscsi_socket_desc, &iscsi->refcnt ); - process_init ( &iscsi->process, iscsi_tx_step, &iscsi->refcnt ); + process_init_stopped ( &iscsi->process, iscsi_tx_step, + &iscsi->refcnt ); /* Parse root path */ - if ( ( rc = iscsi_parse_root_path ( iscsi, root_path ) ) != 0 ) - goto err; + if ( ( rc = iscsi_parse_root_path ( iscsi, uri->opaque ) ) != 0 ) + goto err_parse_root_path; /* Set fields not specified by root path */ if ( ( rc = iscsi_set_auth ( iscsi, iscsi_initiator_username, iscsi_initiator_password, iscsi_target_username, iscsi_target_password ) ) != 0 ) - goto err; + goto err_set_auth; /* Sanity checks */ if ( ! iscsi->target_address ) { DBGC ( iscsi, "iSCSI %p does not yet support discovery\n", iscsi ); rc = -ENOTSUP_DISCOVERY; - goto err; + goto err_sanity_address; } if ( ! iscsi->target_iqn ) { DBGC ( iscsi, "iSCSI %p no target address supplied in %s\n", - iscsi, root_path ); + iscsi, uri->opaque ); rc = -EINVAL; - goto err; + goto err_sanity_iqn; } - /* Attach parent interface, mortalise self, and return */ - scsi->backend = ref_get ( &iscsi->refcnt ); - scsi->command = iscsi_command; + /* Open socket */ + if ( ( rc = iscsi_open_connection ( iscsi ) ) != 0 ) + goto err_open_connection; + + /* Attach SCSI device to parent interface */ + if ( ( rc = scsi_open ( parent, &iscsi->control, + &iscsi->lun ) ) != 0 ) { + DBGC ( iscsi, "iSCSI %p could not create SCSI device: %s\n", + iscsi, strerror ( rc ) ); + goto err_scsi_open; + } + + /* Mortalise self, and return */ ref_put ( &iscsi->refcnt ); return 0; - err: + err_scsi_open: + err_open_connection: + err_sanity_iqn: + err_sanity_address: + err_set_auth: + err_parse_root_path: + iscsi_close ( iscsi, rc ); ref_put ( &iscsi->refcnt ); + err_zalloc: + err_sanity_uri: return rc; } +/** iSCSI URI opener */ +struct uri_opener iscsi_uri_opener __uri_opener = { + .scheme = "iscsi", + .open = iscsi_open, +}; + /**************************************************************************** * * Settings diff --git a/src/usr/autoboot.c b/src/usr/autoboot.c index 48d1fa60..0421dc43 100644 --- a/src/usr/autoboot.c +++ b/src/usr/autoboot.c @@ -27,6 +27,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include #include @@ -109,6 +110,14 @@ int boot_next_server_and_filename ( struct in_addr next_server, return rc; } +/** The "keep-san" setting */ +struct setting keep_san_setting __setting = { + .name = "keep-san", + .description = "Preserve SAN connection", + .tag = DHCP_EB_KEEP_SAN, + .type = &setting_type_int8, +}; + /** * Boot using root path * @@ -116,17 +125,62 @@ int boot_next_server_and_filename ( struct in_addr next_server, * @ret rc Return status code */ int boot_root_path ( const char *root_path ) { - struct sanboot_protocol *sanboot; + struct uri *uri; + int drive; + int rc; - /* Quick hack */ - for_each_table_entry ( sanboot, SANBOOT_PROTOCOLS ) { - if ( strncmp ( root_path, sanboot->prefix, - strlen ( sanboot->prefix ) ) == 0 ) { - return sanboot->boot ( root_path ); - } + /* Parse URI */ + uri = parse_uri ( root_path ); + if ( ! uri ) { + printf ( "Could not parse \"%s\"\n", root_path ); + rc = -ENOMEM; + goto err_parse_uri; } - return -ENOTSUP; + /* Hook SAN device */ + if ( ( drive = san_hook ( uri, 0 ) ) < 0 ) { + rc = drive; + printf ( "Could not open SAN device: %s\n", + strerror ( rc ) ); + goto err_open; + } + printf ( "Registered as SAN device %#02x\n", drive ); + + /* Describe SAN device */ + if ( ( rc = san_describe ( drive ) ) != 0 ) { + printf ( "Could not describe SAN device %#02x: %s\n", + drive, strerror ( rc ) ); + goto err_describe; + } + + printf ( "Booting from SAN device %#02x\n", drive ); + rc = san_boot ( drive ); + printf ( "Boot from SAN device %#02x failed: %s\n", + drive, strerror ( rc ) ); + + /* Leave drive registered, if instructed to do so */ + if ( fetch_intz_setting ( NULL, &keep_san_setting ) != 0 ) { + printf ( "Preserving connection to SAN device %#02x\n", + drive ); + shutdown_exit_flags |= SHUTDOWN_KEEP_DEVICES; + goto err_keep_san; + } + + /* Unhook SAN deivce */ + printf ( "Unregistering SAN device %#02x\n", drive ); + san_unhook ( drive ); + + /* Drop URI reference */ + uri_put ( uri ); + + return 0; + + err_keep_san: + err_describe: + err_open: + uri_put ( uri ); + err_parse_uri: + return rc; } /**