summaryrefslogtreecommitdiffstats
path: root/src/net/aoe.c
diff options
context:
space:
mode:
authorMichael Brown2010-09-03 17:11:51 +0200
committerMichael Brown2010-09-14 21:37:15 +0200
commit220495f8bf2222e1dc1aa7db554d23997b545546 (patch)
treef04aa0d123a0b643fc41e178db81cfe63ba913bf /src/net/aoe.c
parent[hermon] Use correct alignment for doorbell records (diff)
downloadipxe-220495f8bf2222e1dc1aa7db554d23997b545546.tar.gz
ipxe-220495f8bf2222e1dc1aa7db554d23997b545546.tar.xz
ipxe-220495f8bf2222e1dc1aa7db554d23997b545546.zip
[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>
Diffstat (limited to 'src/net/aoe.c')
-rw-r--r--src/net/aoe.c1140
1 files changed, 850 insertions, 290 deletions
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 <byteswap.h>
#include <ipxe/list.h>
#include <ipxe/if_ether.h>
-#include <ipxe/ethernet.h>
#include <ipxe/iobuf.h>
#include <ipxe/uaccess.h>
-#include <ipxe/ata.h>
#include <ipxe/netdevice.h>
-#include <ipxe/process.h>
#include <ipxe/features.h>
+#include <ipxe/interface.h>
+#include <ipxe/xfer.h>
+#include <ipxe/uri.h>
+#include <ipxe/open.h>
+#include <ipxe/ata.h>
#include <ipxe/aoe.h>
/** @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
+ *
+ ******************************************************************************
+ */
+
+/** List of all AoE devices */
+static LIST_HEAD ( aoe_devices );
+
+/** 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];
-static void aoe_free ( struct refcnt *refcnt ) {
- struct aoe_session *aoe =
- container_of ( refcnt, struct aoe_session, refcnt );
+ /** 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 );
+};
- netdev_put ( aoe->netdev );
- free ( aoe );
+/**
+ * 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;
+}
- /* Stop retransmission timer */
- stop_timer ( &aoe->timer );
+/**
+ * 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 );
+}
- /* Mark operation as complete */
- aoe->rc = rc;
+/**
+ * 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;
}
/**
- * Send AoE command
+ * Free AoE command
*
- * @v aoe AoE session
- * @ret rc Return status code
+ * @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
*
- * This transmits an AoE command packet. It does not wait for a
- * response.
+ * @v aoecmd AoE command
+ * @v rc Reason for close
*/
-static int aoe_send_command ( struct aoe_session *aoe ) {
- struct ata_command *command = aoe->command;
+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 );
+ }
+
+ /* Shut down interfaces */
+ intf_shutdown ( &aoecmd->ata, rc );
+}
+
+/**
+ * Transmit AoE command request
+ *
+ * @v aoecmd AoE command
+ * @ret rc Return status code
+ */
+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;
-
- /* Fail immediately if we have no netdev to send on */
- if ( ! aoe->netdev ) {
- aoe_done ( aoe, -ENETUNREACH );
- return -ENETUNREACH;
- }
+ size_t cmd_len;
+ int rc;
+
+ /* 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 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 aoe AoE session
+ * @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 );
+
+ /* 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;
+ }
- /* Mark config request as complete */
- aoe_done ( aoe, 0 );
+ /* 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 ) );
+}
+
+/**
+ * Build AoE configuration command IU
+ *
+ * @v aoecmd AoE command
+ * @v data Command IU
+ * @v len Length of command IU
+ */
+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 */
+ 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 ATA command response
+ * Handle AoE configuration response IU
*
- * @v aoe AoE session
- * @v aoeata AoE ATA command
- * @v len Length of AoE ATA command
+ * @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_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 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 ( *aoeata ) ) {
- /* Ignore packet; allow timer to trigger retransmit */
+ 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 ) );
-
- /* 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 );
+ 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 );
+
+ /* 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;
- /* Update ATA command and offset */
- aoe->command_offset += data_len;
- command->cb.lba.native += count;
- command->cb.count.native -= count;
+ /* 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 ) );
- /* Check for operation complete */
- if ( ! command->cb.count.native ) {
- aoe_done ( aoe, 0 );
- return 0;
+ 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;
}
- /* Transmit next portion of request */
- stop_timer ( &aoe->timer );
- aoe_send_command ( aoe );
+ /* 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;
+ DBG ( "AoE received request packet\n" );
+ rc = -EOPNOTSUPP;
+ goto err_sanity;
}
- 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;
+ /* 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;
}
- done:
+ /* 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;
-
- aoe_send_command ( aoe );
-
- return 0;
-}
/**
- * Issue AoE config query for AoE target discovery
+ * Parse AoE URI
*
- * @v aoe AoE session
+ * @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<major>.<minor>".
*/
-static int aoe_discover ( struct aoe_session *aoe ) {
- int rc;
-
- aoe->status = 0;
- aoe->aoe_cmd_type = AOE_CMD_CONFIG;
- aoe->command = NULL;
-
- aoe_send_command ( aoe );
-
- 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 );
+static int aoe_parse_uri ( struct uri *uri, unsigned int *major,
+ unsigned int *minor ) {
+ const char *ptr;
+ char *end;
- 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 )
+ /* Check for URI with opaque portion */
+ if ( ! uri->opaque )
return -EINVAL;
- ptr = ( ( char * ) root_path + 4 );
+ ptr = uri->opaque;
- if ( *ptr++ != 'e' )
+ /* Check for initial 'e' */
+ if ( *ptr != 'e' )
return -EINVAL;
+ ptr++;
- aoe->major = strtoul ( ptr, &ptr, 10 );
- if ( *ptr++ != '.' )
+ /* Parse major device number */
+ *major = strtoul ( ptr, &end, 10 );
+ if ( *end != '.' )
return -EINVAL;
+ ptr = ( end + 1 );
- aoe->minor = strtoul ( ptr, &ptr, 10 );
- if ( *ptr )
+ /* Parse minor device number */
+ *minor = strtoul ( ptr, &end, 10 );
+ if ( *end )
return -EINVAL;
return 0;
}
-int aoe_attach ( struct ata_device *ata, struct net_device *netdev,
- const char *root_path ) {
- struct aoe_session *aoe;
+/**
+ * Open AoE URI
+ *
+ * @v parent Parent interface
+ * @v uri URI
+ * @ret rc Return status code
+ */
+static int aoe_open ( struct interface *parent, struct uri *uri ) {
+ struct net_device *netdev;
+ unsigned int major;
+ unsigned int minor;
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
+ /* 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.
*/
- 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;
+ netdev = last_opened_netdev();
+ if ( ! netdev ) {
+ DBG ( "AoE cannot identify network device\n" );
+ return -ENODEV;
+ }
- return 0;
+ /* Parse URI */
+ if ( ( rc = aoe_parse_uri ( uri, &major, &minor ) ) != 0 ) {
+ DBG ( "AoE cannot parse URI\n" );
+ return rc;
+ }
- err:
- ref_put ( &aoe->refcnt );
- return rc;
+ /* Open AoE device */
+ if ( ( rc = aoedev_open ( parent, netdev, major, minor ) ) != 0 )
+ return rc;
+
+ return 0;
}
+
+/** AoE URI opener */
+struct uri_opener aoe_uri_opener __uri_opener = {
+ .scheme = "aoe",
+ .open = aoe_open,
+};