blob: e785e8979afcc6249d097a35f3d7b7d703e3d117 [file] [log] [blame]
/*
* 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.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#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/efi/efi_path.h>
#include <ipxe/aoe.h>
/** @file
*
* AoE protocol
*
*/
FEATURE ( FEATURE_PROTOCOL, "AoE", DHCP_EB_FEATURE_AOE, 1 );
struct net_protocol aoe_protocol __net_protocol;
struct acpi_model abft_model __acpi_model;
/******************************************************************************
*
* 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 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;
}
/**
* Get AoE ACPI descriptor
*
* @v aoedev AoE device
* @ret desc ACPI descriptor
*/
static struct acpi_descriptor * aoedev_describe ( struct aoe_device *aoedev ) {
return &aoedev->desc;
}
/** 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 ),
EFI_INTF_OP ( efi_describe, struct aoe_device *, efi_aoe_path ),
};
/** 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 );
acpi_init ( &aoedev->desc, &abft_model, &aoedev->refcnt );
/* 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,
};
/******************************************************************************
*
* AoE boot firmware table (aBFT)
*
******************************************************************************
*/
/**
* Check if AoE boot firmware table descriptor is complete
*
* @v desc ACPI descriptor
* @ret rc Return status code
*/
static int abft_complete ( struct acpi_descriptor *desc __unused ) {
return 0;
}
/**
* Install AoE boot firmware table(s)
*
* @v install Installation method
* @ret rc Return status code
*/
static int abft_install ( int ( * install ) ( struct acpi_header *acpi ) ) {
struct aoe_device *aoedev;
struct abft_table abft;
int rc;
list_for_each_entry ( aoedev, &abft_model.descs, desc.list ) {
/* Populate table */
memset ( &abft, 0, sizeof ( abft ) );
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 ) );
/* Install table */
if ( ( rc = install ( &abft.acpi ) ) != 0 ) {
DBGC ( aoedev, "AoE %s could not install aBFT: %s\n",
aoedev_name ( aoedev ), strerror ( rc ) );
return rc;
}
}
return 0;
}
/** aBFT model */
struct acpi_model abft_model __acpi_model = {
.descs = LIST_HEAD_INIT ( abft_model.descs ),
.complete = abft_complete,
.install = abft_install,
};