Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / ipxe / src / net / aoe.c
diff --git a/qemu/roms/ipxe/src/net/aoe.c b/qemu/roms/ipxe/src/net/aoe.c
new file mode 100644 (file)
index 0000000..a6d7b3e
--- /dev/null
@@ -0,0 +1,1057 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <byteswap.h>
+#include <ipxe/list.h>
+#include <ipxe/if_ether.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/uaccess.h>
+#include <ipxe/netdevice.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/device.h>
+#include <ipxe/aoe.h>
+
+/** @file
+ *
+ * AoE protocol
+ *
+ */
+
+FEATURE ( FEATURE_PROTOCOL, "AoE", DHCP_EB_FEATURE_AOE, 1 );
+
+struct net_protocol aoe_protocol __net_protocol;
+
+/******************************************************************************
+ *
+ * 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];
+
+       /** 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;
+}
+
+/**
+ * Drop reference to AoE device
+ *
+ * @v aoedev           AoE device
+ */
+static inline __attribute__ (( always_inline )) void
+aoedev_put ( struct aoe_device *aoedev ) {
+       ref_put ( &aoedev->refcnt );
+}
+
+/**
+ * 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 );
+       }
+
+       /* 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 net_device *netdev = aoedev->netdev;
+       struct io_buffer *iobuf;
+       struct aoehdr *aoehdr;
+       size_t cmd_len;
+       int rc;
+
+       /* Sanity check */
+       assert ( 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 ( &aoecmd->timer );
+
+       /* Create outgoing I/O buffer */
+       cmd_len = aoecmd->type->cmd_len ( aoecmd );
+       iobuf = alloc_iob ( MAX_LL_HEADER_LEN + cmd_len );
+       if ( ! iobuf )
+               return -ENOMEM;
+       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 ( aoedev->major );
+       aoehdr->minor = aoedev->minor;
+       aoehdr->tag = htonl ( aoecmd->tag );
+       aoecmd->type->cmd ( aoecmd, iobuf->data, iob_len ( iobuf ) );
+
+       /* Send packet */
+       if ( ( rc = net_tx ( iobuf, netdev, &aoe_protocol, aoedev->target,
+                            netdev->ll_addr ) ) != 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;
+}
+
+/**
+ * Handle AoE retry timer expiry
+ *
+ * @v timer            AoE retry timer
+ * @v fail             Failure indicator
+ */
+static void aoecmd_expired ( struct retry_timer *timer, int fail ) {
+       struct aoe_command *aoecmd =
+               container_of ( timer, struct aoe_command, timer );
+
+       if ( fail ) {
+               aoecmd_close ( aoecmd, -ETIMEDOUT );
+       } else {
+               aoecmd_tx ( aoecmd );
+       }
+}
+
+/**
+ * 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 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_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;
+
+       /* 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;
+       }
+
+       /* 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 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;
+       }
+       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 net_device *netdev = aoedev->netdev;
+       struct aoe_command *aoecmd;
+
+       /* Fail immediately if net device is closed */
+       if ( ! netdev_is_open ( netdev ) ) {
+               DBGC ( aoedev, "AoE %s cannot issue command while net device "
+                      "is closed\n", aoedev_name ( aoedev ) );
+               return -EWOULDBLOCK;
+       }
+
+       /* 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 );
+}
+
+/**
+ * Identify device underlying AoE device
+ *
+ * @v aoedev           AoE device
+ * @ret device         Underlying device
+ */
+static struct device * aoedev_identify_device ( struct aoe_device *aoedev ) {
+       return aoedev->netdev->dev;
+}
+
+/**
+ * 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 ),
+       INTF_OP ( identify_device, struct aoe_device *,
+                 aoedev_identify_device ),
+};
+
+/** 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
+ *
+ * @v iobuf            I/O buffer
+ * @v netdev           Network device
+ * @v ll_dest          Link-layer destination address
+ * @v ll_source                Link-layer source address
+ * @v flags            Packet flags
+ * @ret rc             Return status code
+ */
+static int aoe_rx ( struct io_buffer *iobuf,
+                   struct net_device *netdev __unused,
+                   const void *ll_dest __unused,
+                   const void *ll_source,
+                   unsigned int flags __unused ) {
+       struct aoehdr *aoehdr = iobuf->data;
+       struct aoe_command *aoecmd;
+       int rc;
+
+       /* Sanity check */
+       if ( iob_len ( iobuf ) < sizeof ( *aoehdr ) ) {
+               DBG ( "AoE received underlength packet (%zd bytes)\n",
+                     iob_len ( iobuf ) );
+               rc = -EINVAL;
+               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 err_sanity;
+       }
+       if ( ! ( aoehdr->ver_flags & AOE_FL_RESPONSE ) ) {
+               DBG ( "AoE received request packet\n" );
+               rc = -EOPNOTSUPP;
+               goto err_sanity;
+       }
+
+       /* 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;
+}
+
+/** AoE protocol */
+struct net_protocol aoe_protocol __net_protocol = {
+       .name = "AoE",
+       .net_proto = htons ( ETH_P_AOE ),
+       .rx = aoe_rx,
+};
+
+/******************************************************************************
+ *
+ * AoE URIs
+ *
+ ******************************************************************************
+ */
+
+/**
+ * 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<major>.<minor>".
+ */
+static int aoe_parse_uri ( struct uri *uri, unsigned int *major,
+                          unsigned int *minor ) {
+       const char *ptr;
+       char *end;
+
+       /* 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;
+}
+
+/**
+ * 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;
+
+       /* 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;
+       }
+
+       /* Parse URI */
+       if ( ( rc = aoe_parse_uri ( uri, &major, &minor ) ) != 0 ) {
+               DBG ( "AoE cannot parse URI\n" );
+               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,
+};