summaryrefslogtreecommitdiffstats
path: root/contrib/syslinux-4.02/gpxe/src/net/aoe.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/syslinux-4.02/gpxe/src/net/aoe.c')
-rw-r--r--contrib/syslinux-4.02/gpxe/src/net/aoe.c471
1 files changed, 471 insertions, 0 deletions
diff --git a/contrib/syslinux-4.02/gpxe/src/net/aoe.c b/contrib/syslinux-4.02/gpxe/src/net/aoe.c
new file mode 100644
index 0000000..839a875
--- /dev/null
+++ b/contrib/syslinux-4.02/gpxe/src/net/aoe.c
@@ -0,0 +1,471 @@
+/*
+ * 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., 675 Mass Ave, Cambridge, MA 02139, 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 <gpxe/list.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/ethernet.h>
+#include <gpxe/iobuf.h>
+#include <gpxe/uaccess.h>
+#include <gpxe/ata.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/process.h>
+#include <gpxe/features.h>
+#include <gpxe/aoe.h>
+
+/** @file
+ *
+ * AoE protocol
+ *
+ */
+
+FEATURE ( FEATURE_PROTOCOL, "AoE", DHCP_EB_FEATURE_AOE, 1 );
+
+struct net_protocol aoe_protocol;
+
+/** List of all AoE sessions */
+static LIST_HEAD ( aoe_sessions );
+
+static void aoe_free ( struct refcnt *refcnt ) {
+ struct aoe_session *aoe =
+ container_of ( refcnt, struct aoe_session, refcnt );
+
+ netdev_put ( aoe->netdev );
+ free ( aoe );
+}
+
+/**
+ * Mark current AoE command complete
+ *
+ * @v aoe AoE session
+ * @v rc Return status code
+ */
+static void aoe_done ( struct aoe_session *aoe, int rc ) {
+
+ /* Record overall command status */
+ if ( aoe->command ) {
+ aoe->command->cb.cmd_stat = aoe->status;
+ aoe->command->rc = rc;
+ aoe->command = NULL;
+ }
+
+ /* Stop retransmission timer */
+ stop_timer ( &aoe->timer );
+
+ /* Mark operation as complete */
+ aoe->rc = rc;
+}
+
+/**
+ * Send AoE command
+ *
+ * @v aoe AoE session
+ * @ret rc Return status code
+ *
+ * This transmits an AoE command packet. It does not wait for a
+ * response.
+ */
+static int aoe_send_command ( struct aoe_session *aoe ) {
+ struct ata_command *command = aoe->command;
+ 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;
+ }
+
+ /* 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;
+ }
+
+ /* Create outgoing I/O buffer */
+ iobuf = alloc_iob ( ETH_HLEN + sizeof ( *aoehdr ) +
+ aoecmdlen + data_out_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 ) );
+
+ /* Fill AoE header */
+ 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 );
+ }
+
+ /* Send packet */
+ return net_tx ( iobuf, aoe->netdev, &aoe_protocol, aoe->target );
+}
+
+/**
+ * Handle AoE retry timer expiry
+ *
+ * @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 );
+
+ if ( fail ) {
+ aoe_done ( aoe, -ETIMEDOUT );
+ } else {
+ aoe_send_command ( aoe );
+ }
+}
+
+/**
+ * Handle AoE configuration command response
+ *
+ * @v aoe AoE session
+ * @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 ) {
+
+ /* 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 ) );
+
+ /* Mark config request as complete */
+ aoe_done ( aoe, 0 );
+
+ return 0;
+}
+
+/**
+ * Handle AoE ATA command response
+ *
+ * @v aoe AoE session
+ * @v aoeata AoE ATA command
+ * @v len Length of AoE ATA command
+ * @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;
+
+ /* Sanity check */
+ if ( len < sizeof ( *aoeata ) ) {
+ /* Ignore packet; allow timer to trigger retransmit */
+ 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 );
+ }
+
+ /* Update ATA command and offset */
+ aoe->command_offset += data_len;
+ command->cb.lba.native += count;
+ command->cb.count.native -= count;
+
+ /* Check for operation complete */
+ if ( ! command->cb.count.native ) {
+ aoe_done ( aoe, 0 );
+ return 0;
+ }
+
+ /* Transmit next portion of request */
+ stop_timer ( &aoe->timer );
+ aoe_send_command ( aoe );
+
+ return 0;
+}
+
+/**
+ * Process incoming AoE packets
+ *
+ * @v iobuf I/O buffer
+ * @v netdev Network device
+ * @v ll_source Link-layer source address
+ * @ret rc Return status code
+ *
+ */
+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;
+
+ /* Sanity checks */
+ if ( iob_len ( iobuf ) < sizeof ( *aoehdr ) ) {
+ rc = -EINVAL;
+ goto done;
+ }
+ if ( ( aoehdr->ver_flags & AOE_VERSION_MASK ) != AOE_VERSION ) {
+ rc = -EPROTONOSUPPORT;
+ goto done;
+ }
+ if ( ! ( aoehdr->ver_flags & AOE_FL_RESPONSE ) ) {
+ /* Ignore AoE requests that we happen to see */
+ goto done;
+ }
+ iob_pull ( iobuf, sizeof ( *aoehdr ) );
+
+ /* Demultiplex amongst active AoE sessions */
+ list_for_each_entry ( aoe, &aoe_sessions, list ) {
+ if ( ntohs ( aoehdr->major ) != aoe->major )
+ continue;
+ if ( aoehdr->minor != aoe->minor )
+ continue;
+ if ( ntohl ( aoehdr->tag ) != aoe->tag )
+ continue;
+ if ( aoehdr->ver_flags & AOE_FL_ERROR ) {
+ aoe_done ( aoe, -EIO );
+ break;
+ }
+ switch ( aoehdr->command ) {
+ case AOE_CMD_ATA:
+ rc = aoe_rx_ata ( aoe, iobuf->data, iob_len ( iobuf ));
+ break;
+ case AOE_CMD_CONFIG:
+ rc = aoe_rx_cfg ( aoe, ll_source );
+ break;
+ default:
+ DBGC ( aoe, "AoE %p ignoring command %02x\n",
+ aoe, aoehdr->command );
+ break;
+ }
+ break;
+ }
+
+ done:
+ 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,
+};
+
+/**
+ * Issue ATA command via an open AoE session
+ *
+ * @v ata ATA device
+ * @v command ATA command
+ * @ret rc Return status code
+ */
+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
+ *
+ * @v aoe AoE session
+ * @ret rc Return status code
+ */
+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 );
+
+ stop_timer ( &aoe->timer );
+ ata->command = aoe_detached_command;
+ list_del ( &aoe->list );
+ ref_put ( ata->backend );
+ ata->backend = NULL;
+}
+
+static int aoe_parse_root_path ( struct aoe_session *aoe,
+ const char *root_path ) {
+ char *ptr;
+
+ if ( strncmp ( root_path, "aoe:", 4 ) != 0 )
+ return -EINVAL;
+ ptr = ( ( char * ) root_path + 4 );
+
+ if ( *ptr++ != 'e' )
+ return -EINVAL;
+
+ aoe->major = strtoul ( ptr, &ptr, 10 );
+ if ( *ptr++ != '.' )
+ return -EINVAL;
+
+ aoe->minor = strtoul ( ptr, &ptr, 10 );
+ if ( *ptr )
+ return -EINVAL;
+
+ return 0;
+}
+
+int aoe_attach ( struct ata_device *ata, struct net_device *netdev,
+ const char *root_path ) {
+ struct aoe_session *aoe;
+ int rc;
+
+ /* Allocate and initialise structure */
+ aoe = zalloc ( sizeof ( *aoe ) );
+ if ( ! aoe )
+ return -ENOMEM;
+ aoe->refcnt.free = aoe_free;
+ aoe->netdev = netdev_get ( netdev );
+ memcpy ( aoe->target, netdev->ll_broadcast, sizeof ( aoe->target ) );
+ aoe->tag = AOE_TAG_MAGIC;
+ aoe->timer.expired = aoe_timer_expired;
+
+ /* Parse root path */
+ if ( ( rc = aoe_parse_root_path ( aoe, root_path ) ) != 0 )
+ goto err;
+
+ /* Attach parent interface, transfer reference to connection
+ * list, and return
+ */
+ ata->backend = ref_get ( &aoe->refcnt );
+ ata->command = aoe_command;
+ list_add ( &aoe->list, &aoe_sessions );
+
+ /* Send discovery packet to find the target MAC address.
+ * Ideally, this ought to be done asynchronously, but the
+ * block device interface does not yet support asynchronous
+ * operation.
+ */
+ if ( ( rc = aoe_discover( aoe ) ) != 0 )
+ goto err;
+
+ return 0;
+
+ err:
+ ref_put ( &aoe->refcnt );
+ return rc;
+}