[block] Replace gPXE block-device API with an iPXE asynchronous interface

The block device interface used in gPXE predates the invention of even
the old gPXE data-transfer interface, let alone the current iPXE
generic asynchronous interface mechanism.  Bring this old code up to
date, with the following benefits:

 o  Block device commands can be cancelled by the requestor.  The INT 13
    layer uses this to provide a global timeout on all INT 13 calls,
    with the result that an unexpected passive failure mode (such as
    an iSCSI target ACKing the request but never sending a response)
    will lead to a timeout that gets reported back to the INT 13 user,
    rather than simply freezing the system.

 o  INT 13,00 (reset drive) is now able to reset the underlying block
    device.  INT 13 users, such as DOS, that use INT 13,00 as a method
    for error recovery now have a chance of recovering.

 o  All block device commands are tagged, with a numerical tag that
    will show up in debugging output and in packet captures; this will
    allow easier interpretation of bug reports that include both
    sources of information.

 o  The extremely ugly hacks used to generate the boot firmware tables
    have been eradicated and replaced with a generic acpi_describe()
    method (exploiting the ability of iPXE interfaces to pass through
    methods to an underlying interface).  The ACPI tables are now
    built in a shared data block within .bss16, rather than each
    requiring dedicated space in .data16.

 o  The architecture-independent concept of a SAN device has been
    exposed to the iPXE core through the sanboot API, which provides
    calls to hook, unhook, boot, and describe SAN devices.  This
    allows for much more flexible usage patterns (such as hooking an
    empty SAN device and then running an OS installer via TFTP).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2010-09-03 16:11:51 +01:00
parent 46c7f99c66
commit 220495f8bf
47 changed files with 4904 additions and 3127 deletions

View File

@@ -19,12 +19,14 @@
FILE_LICENCE ( GPL2_OR_LATER );
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/list.h>
#include <ipxe/interface.h>
#include <ipxe/blockdev.h>
#include <ipxe/process.h>
#include <ipxe/ata.h>
/** @file
@@ -33,156 +35,625 @@ FILE_LICENCE ( GPL2_OR_LATER );
*
*/
static inline __attribute__ (( always_inline )) struct ata_device *
block_to_ata ( struct block_device *blockdev ) {
return container_of ( blockdev, struct ata_device, blockdev );
}
/******************************************************************************
*
* Interface methods
*
******************************************************************************
*/
/**
* Issue ATA command
*
* @v ata ATA device
* @v control ATA control interface
* @v data ATA data interface
* @v command ATA command
* @ret rc Return status code
* @ret tag Command tag, or negative error
*/
static inline __attribute__ (( always_inline )) int
ata_command ( struct ata_device *ata, struct ata_command *command ) {
int rc;
int ata_command ( struct interface *control, struct interface *data,
struct ata_cmd *command ) {
struct interface *dest;
ata_command_TYPE ( void * ) *op =
intf_get_dest_op ( control, ata_command, &dest );
void *object = intf_object ( dest );
int tag;
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;
}

471
src/drivers/block/ibft.c Normal file
View File

@@ -0,0 +1,471 @@
/*
* Copyright Fen Systems Ltd. 2007. Portions of this code are derived
* from IBM Corporation Sample Programs. Copyright IBM Corporation
* 2004, 2007. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
FILE_LICENCE ( BSD2 );
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/pci.h>
#include <ipxe/acpi.h>
#include <ipxe/in.h>
#include <ipxe/netdevice.h>
#include <ipxe/ethernet.h>
#include <ipxe/dhcp.h>
#include <ipxe/iscsi.h>
#include <ipxe/ibft.h>
/** @file
*
* iSCSI boot firmware table
*
* The information in this file is derived from the document "iSCSI
* Boot Firmware Table (iBFT)" as published by IBM at
*
* ftp://ftp.software.ibm.com/systems/support/system_x_pdf/ibm_iscsi_boot_firmware_table_v1.02.pdf
*
*/
/**
* An iBFT created by iPXE
*
*/
struct ipxe_ibft {
/** The fixed section */
struct ibft_table table;
/** The Initiator section */
struct ibft_initiator initiator __attribute__ (( aligned ( 16 ) ));
/** The NIC section */
struct ibft_nic nic __attribute__ (( aligned ( 16 ) ));
/** The Target section */
struct ibft_target target __attribute__ (( aligned ( 16 ) ));
/** Strings block */
char strings[0];
} __attribute__ (( packed, aligned ( 16 ) ));
/**
* iSCSI string block descriptor
*
* This is an internal structure that we use to keep track of the
* allocation of string data.
*/
struct ibft_strings {
/** The iBFT containing these strings */
struct ibft_table *table;
/** Offset of first free byte within iBFT */
size_t offset;
/** Total length of the iBFT */
size_t len;
};
/**
* Fill in an IP address field within iBFT
*
* @v ipaddr IP address field
* @v in IPv4 address
*/
static void ibft_set_ipaddr ( struct ibft_ipaddr *ipaddr, struct in_addr in ) {
memset ( ipaddr, 0, sizeof ( ipaddr ) );
if ( in.s_addr ) {
ipaddr->in = in;
ipaddr->ones = 0xffff;
}
}
/**
* Fill in an IP address within iBFT from configuration setting
*
* @v ipaddr IP address field
* @v setting Configuration setting
* @v tag DHCP option tag
*/
static void ibft_set_ipaddr_setting ( struct ibft_ipaddr *ipaddr,
struct setting *setting ) {
struct in_addr in;
fetch_ipv4_setting ( NULL, setting, &in );
ibft_set_ipaddr ( ipaddr, in );
}
/**
* Read IP address from iBFT (for debugging)
*
* @v strings iBFT string block descriptor
* @v string String field
* @ret ipaddr IP address string
*/
static const char * ibft_ipaddr ( struct ibft_ipaddr *ipaddr ) {
return inet_ntoa ( ipaddr->in );
}
/**
* Allocate a string within iBFT
*
* @v strings iBFT string block descriptor
* @v string String field to fill in
* @v len Length of string to allocate (excluding NUL)
* @ret dest String destination, or NULL
*/
static char * ibft_alloc_string ( struct ibft_strings *strings,
struct ibft_string *string, size_t len ) {
if ( ( strings->offset + len ) >= strings->len )
return NULL;
string->offset = cpu_to_le16 ( strings->offset );
string->len = cpu_to_le16 ( len );
strings->offset += ( len + 1 );
return ( ( ( char * ) strings->table ) + string->offset );
}
/**
* Fill in a string field within iBFT
*
* @v strings iBFT string block descriptor
* @v string String field
* @v data String to fill in, or NULL
* @ret rc Return status code
*/
static int ibft_set_string ( struct ibft_strings *strings,
struct ibft_string *string, const char *data ) {
char *dest;
if ( ! data )
return 0;
dest = ibft_alloc_string ( strings, string, strlen ( data ) );
if ( ! dest )
return -ENOBUFS;
strcpy ( dest, data );
return 0;
}
/**
* Fill in a string field within iBFT from configuration setting
*
* @v strings iBFT string block descriptor
* @v string String field
* @v setting Configuration setting
* @ret rc Return status code
*/
static int ibft_set_string_setting ( struct ibft_strings *strings,
struct ibft_string *string,
struct setting *setting ) {
int len;
char *dest;
len = fetch_setting_len ( NULL, setting );
if ( len < 0 ) {
string->offset = 0;
string->len = 0;
return 0;
}
dest = ibft_alloc_string ( strings, string, len );
if ( ! dest )
return -ENOBUFS;
fetch_string_setting ( NULL, setting, dest, ( len + 1 ) );
return 0;
}
/**
* Read string from iBFT (for debugging)
*
* @v strings iBFT string block descriptor
* @v string String field
* @ret data String content (or "<empty>")
*/
static const char * ibft_string ( struct ibft_strings *strings,
struct ibft_string *string ) {
return ( string->offset ?
( ( ( char * ) strings->table ) + string->offset ) : NULL );
}
/**
* Fill in NIC portion of iBFT
*
* @v nic NIC portion of iBFT
* @v strings iBFT string block descriptor
* @v netdev Network device
* @ret rc Return status code
*/
static int ibft_fill_nic ( struct ibft_nic *nic,
struct ibft_strings *strings,
struct net_device *netdev ) {
struct ll_protocol *ll_protocol = netdev->ll_protocol;
struct in_addr netmask_addr = { 0 };
unsigned int netmask_count = 0;
int rc;
/* Fill in common header */
nic->header.structure_id = IBFT_STRUCTURE_ID_NIC;
nic->header.version = 1;
nic->header.length = cpu_to_le16 ( sizeof ( *nic ) );
nic->header.flags = ( IBFT_FL_NIC_BLOCK_VALID |
IBFT_FL_NIC_FIRMWARE_BOOT_SELECTED );
/* Extract values from configuration settings */
ibft_set_ipaddr_setting ( &nic->ip_address, &ip_setting );
DBG ( "iBFT NIC IP = %s\n", ibft_ipaddr ( &nic->ip_address ) );
ibft_set_ipaddr_setting ( &nic->gateway, &gateway_setting );
DBG ( "iBFT NIC gateway = %s\n", ibft_ipaddr ( &nic->gateway ) );
ibft_set_ipaddr_setting ( &nic->dns[0], &dns_setting );
DBG ( "iBFT NIC DNS = %s\n", ibft_ipaddr ( &nic->dns[0] ) );
if ( ( rc = ibft_set_string_setting ( strings, &nic->hostname,
&hostname_setting ) ) != 0 )
return rc;
DBG ( "iBFT NIC hostname = %s\n",
ibft_string ( strings, &nic->hostname ) );
/* Derive subnet mask prefix from subnet mask */
fetch_ipv4_setting ( NULL, &netmask_setting, &netmask_addr );
while ( netmask_addr.s_addr ) {
if ( netmask_addr.s_addr & 0x1 )
netmask_count++;
netmask_addr.s_addr >>= 1;
}
nic->subnet_mask_prefix = netmask_count;
DBG ( "iBFT NIC subnet = /%d\n", nic->subnet_mask_prefix );
/* Extract values from net-device configuration */
if ( ( rc = ll_protocol->eth_addr ( netdev->ll_addr,
nic->mac_address ) ) != 0 ) {
DBG ( "Could not determine iBFT MAC: %s\n", strerror ( rc ) );
return rc;
}
DBG ( "iBFT NIC MAC = %s\n", eth_ntoa ( nic->mac_address ) );
nic->pci_bus_dev_func = cpu_to_le16 ( netdev->dev->desc.location );
DBG ( "iBFT NIC PCI = %04x\n", le16_to_cpu ( nic->pci_bus_dev_func ) );
return 0;
}
/**
* Fill in Initiator portion of iBFT
*
* @v initiator Initiator portion of iBFT
* @v strings iBFT string block descriptor
* @ret rc Return status code
*/
static int ibft_fill_initiator ( struct ibft_initiator *initiator,
struct ibft_strings *strings ) {
const char *initiator_iqn = iscsi_initiator_iqn();
int rc;
/* Fill in common header */
initiator->header.structure_id = IBFT_STRUCTURE_ID_INITIATOR;
initiator->header.version = 1;
initiator->header.length = cpu_to_le16 ( sizeof ( *initiator ) );
initiator->header.flags = ( IBFT_FL_INITIATOR_BLOCK_VALID |
IBFT_FL_INITIATOR_FIRMWARE_BOOT_SELECTED );
/* Fill in hostname */
if ( ( rc = ibft_set_string ( strings, &initiator->initiator_name,
initiator_iqn ) ) != 0 )
return rc;
DBG ( "iBFT initiator hostname = %s\n",
ibft_string ( strings, &initiator->initiator_name ) );
return 0;
}
/**
* Fill in Target CHAP portion of iBFT
*
* @v target Target portion of iBFT
* @v strings iBFT string block descriptor
* @v iscsi iSCSI session
* @ret rc Return status code
*/
static int ibft_fill_target_chap ( struct ibft_target *target,
struct ibft_strings *strings,
struct iscsi_session *iscsi ) {
int rc;
if ( ! ( iscsi->status & ISCSI_STATUS_AUTH_FORWARD_REQUIRED ) )
return 0;
assert ( iscsi->initiator_username );
assert ( iscsi->initiator_password );
target->chap_type = IBFT_CHAP_ONE_WAY;
if ( ( rc = ibft_set_string ( strings, &target->chap_name,
iscsi->initiator_username ) ) != 0 )
return rc;
DBG ( "iBFT target username = %s\n",
ibft_string ( strings, &target->chap_name ) );
if ( ( rc = ibft_set_string ( strings, &target->chap_secret,
iscsi->initiator_password ) ) != 0 )
return rc;
DBG ( "iBFT target password = <redacted>\n" );
return 0;
}
/**
* Fill in Target Reverse CHAP portion of iBFT
*
* @v target Target portion of iBFT
* @v strings iBFT string block descriptor
* @v iscsi iSCSI session
* @ret rc Return status code
*/
static int ibft_fill_target_reverse_chap ( struct ibft_target *target,
struct ibft_strings *strings,
struct iscsi_session *iscsi ) {
int rc;
if ( ! ( iscsi->status & ISCSI_STATUS_AUTH_REVERSE_REQUIRED ) )
return 0;
assert ( iscsi->initiator_username );
assert ( iscsi->initiator_password );
assert ( iscsi->target_username );
assert ( iscsi->target_password );
target->chap_type = IBFT_CHAP_MUTUAL;
if ( ( rc = ibft_set_string ( strings, &target->reverse_chap_name,
iscsi->target_username ) ) != 0 )
return rc;
DBG ( "iBFT target reverse username = %s\n",
ibft_string ( strings, &target->chap_name ) );
if ( ( rc = ibft_set_string ( strings, &target->reverse_chap_secret,
iscsi->target_password ) ) != 0 )
return rc;
DBG ( "iBFT target reverse password = <redacted>\n" );
return 0;
}
/**
* Fill in Target portion of iBFT
*
* @v target Target portion of iBFT
* @v strings iBFT string block descriptor
* @v iscsi iSCSI session
* @ret rc Return status code
*/
static int ibft_fill_target ( struct ibft_target *target,
struct ibft_strings *strings,
struct iscsi_session *iscsi ) {
struct sockaddr_in *sin_target =
( struct sockaddr_in * ) &iscsi->target_sockaddr;
int rc;
/* Fill in common header */
target->header.structure_id = IBFT_STRUCTURE_ID_TARGET;
target->header.version = 1;
target->header.length = cpu_to_le16 ( sizeof ( *target ) );
target->header.flags = ( IBFT_FL_TARGET_BLOCK_VALID |
IBFT_FL_TARGET_FIRMWARE_BOOT_SELECTED );
/* Fill in Target values */
ibft_set_ipaddr ( &target->ip_address, sin_target->sin_addr );
DBG ( "iBFT target IP = %s\n", ibft_ipaddr ( &target->ip_address ) );
target->socket = cpu_to_le16 ( ntohs ( sin_target->sin_port ) );
DBG ( "iBFT target port = %d\n", target->socket );
memcpy ( &target->boot_lun, &iscsi->lun, sizeof ( target->boot_lun ) );
DBG ( "iBFT target boot LUN = " SCSI_LUN_FORMAT "\n",
SCSI_LUN_DATA ( target->boot_lun ) );
if ( ( rc = ibft_set_string ( strings, &target->target_name,
iscsi->target_iqn ) ) != 0 )
return rc;
DBG ( "iBFT target name = %s\n",
ibft_string ( strings, &target->target_name ) );
if ( ( rc = ibft_fill_target_chap ( target, strings, iscsi ) ) != 0 )
return rc;
if ( ( rc = ibft_fill_target_reverse_chap ( target, strings,
iscsi ) ) != 0 )
return rc;
return 0;
}
/**
* Fill in iBFT
*
* @v iscsi iSCSI session
* @v acpi ACPI table
* @v len Length of ACPI table
* @ret rc Return status code
*/
int ibft_describe ( struct iscsi_session *iscsi,
struct acpi_description_header *acpi,
size_t len ) {
struct ipxe_ibft *ibft =
container_of ( acpi, struct ipxe_ibft, table.acpi );
struct ibft_strings strings = {
.table = &ibft->table,
.offset = offsetof ( typeof ( *ibft ), strings ),
.len = len,
};
struct net_device *netdev;
int rc;
/* Ugly hack. Now that we have a generic interface mechanism
* that can support ioctls, we can potentially eliminate this.
*/
netdev = last_opened_netdev();
if ( ! netdev ) {
DBGC ( iscsi, "iSCSI %p cannot guess network device\n",
iscsi );
return -ENODEV;
}
/* Fill in ACPI header */
ibft->table.acpi.signature = cpu_to_le32 ( IBFT_SIG );
ibft->table.acpi.length = cpu_to_le32 ( len );
ibft->table.acpi.revision = 1;
/* Fill in Control block */
ibft->table.control.header.structure_id = IBFT_STRUCTURE_ID_CONTROL;
ibft->table.control.header.version = 1;
ibft->table.control.header.length =
cpu_to_le16 ( sizeof ( ibft->table.control ) );
ibft->table.control.initiator =
cpu_to_le16 ( offsetof ( typeof ( *ibft ), initiator ) );
ibft->table.control.nic_0 =
cpu_to_le16 ( offsetof ( typeof ( *ibft ), nic ) );
ibft->table.control.target_0 =
cpu_to_le16 ( offsetof ( typeof ( *ibft ), target ) );
/* Fill in NIC, Initiator and Target blocks */
if ( ( rc = ibft_fill_nic ( &ibft->nic, &strings, netdev ) ) != 0 )
return rc;
if ( ( rc = ibft_fill_initiator ( &ibft->initiator,
&strings ) ) != 0 )
return rc;
if ( ( rc = ibft_fill_target ( &ibft->target, &strings,
iscsi ) ) != 0 )
return rc;
return 0;
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/blockdev.h>
#include <ipxe/ramdisk.h>
/**
* @file
*
* RAM disks
*
*/
static inline __attribute__ (( always_inline )) struct ramdisk *
block_to_ramdisk ( struct block_device *blockdev ) {
return container_of ( blockdev, struct ramdisk, blockdev );
}
/**
* Read block
*
* @v blockdev Block device
* @v block Block number
* @v count Block count
* @v buffer Data buffer
* @ret rc Return status code
*/
static int ramdisk_read ( struct block_device *blockdev, uint64_t block,
unsigned long count, userptr_t buffer ) {
struct ramdisk *ramdisk = block_to_ramdisk ( blockdev );
unsigned long offset = ( block * blockdev->blksize );
unsigned long length = ( count * blockdev->blksize );
DBGC ( ramdisk, "RAMDISK %p reading [%lx,%lx)\n",
ramdisk, offset, length );
memcpy_user ( buffer, 0, ramdisk->data, offset, length );
return 0;
}
/**
* Write block
*
* @v blockdev Block device
* @v block Block number
* @v count Block count
* @v buffer Data buffer
* @ret rc Return status code
*/
static int ramdisk_write ( struct block_device *blockdev, uint64_t block,
unsigned long count, userptr_t buffer ) {
struct ramdisk *ramdisk = block_to_ramdisk ( blockdev );
unsigned long offset = ( block * blockdev->blksize );
unsigned long length = ( count * blockdev->blksize );
DBGC ( ramdisk, "RAMDISK %p writing [%lx,%lx)\n",
ramdisk, offset, length );
memcpy_user ( ramdisk->data, offset, buffer, 0, length );
return 0;
}
static struct block_device_operations ramdisk_operations = {
.read = ramdisk_read,
.write = ramdisk_write
};
int init_ramdisk ( struct ramdisk *ramdisk, userptr_t data, size_t len,
unsigned int blksize ) {
if ( ! blksize )
blksize = 512;
ramdisk->data = data;
ramdisk->blockdev.op = &ramdisk_operations;
ramdisk->blockdev.blksize = blksize;
ramdisk->blockdev.blocks = ( len / blksize );
return 0;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff