diff options
Diffstat (limited to 'contrib/syslinux-4.02/gpxe/src/net/aoe.c')
-rw-r--r-- | contrib/syslinux-4.02/gpxe/src/net/aoe.c | 471 |
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; +} |