david/ipxe
Archived
1
0
This repository has been archived on 2020-12-06. You can view files and clone it, but cannot push or open issues or pull requests.
ipxe/src/net/aoe.c
Michael Brown 246ddf5ee4 [aoe] Use an AoE config query to identify the target MAC address
The AoE spec does not specify that the source MAC address of a
received packet actually matches the MAC address of the AoE target.
In principle an AoE server can respond to an AoE request on any
interface available to it, which may not be an address configured to
accept AoE requests.

This issue is resolved by implementing AoE device discovery.  The
purpose of AoE discovery is to find out which addresses an AoE target
can use for requests.  An AoE configuration command is sent when the
AoE attach is attempted.  The AoE target must respond to that
configuration query from an interface that can accept requests.

Based on a patch from Ryan Thomas <ryan@coraid.com>
2008-11-19 21:42:33 +00:00

477 lines
11 KiB
C

/*
* 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.
*/
#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 = 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 );
int rc;
aoe->command = command;
aoe->status = 0;
aoe->command_offset = 0;
aoe->aoe_cmd_type = AOE_CMD_ATA;
aoe_send_command ( aoe );
aoe->rc = -EINPROGRESS;
while ( aoe->rc == -EINPROGRESS )
step();
rc = aoe->rc;
return rc;
}
/**
* 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, ethernet_protocol.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;
}