Files
ipxe/src/net/aoe.c
Michael Brown 04cb17de50 [aoe] Allow AoE device to be described using an EFI device path
There is no standard defined for AoE device paths in the UEFI
specification, and it seems unlikely that any standard will be adopted
in future.

Choose to construct an AoE device path using a concatenation of the
network device path and a SATA device path, treating the AoE major and
minor numbers as the HBA port number and port multiplier port number
respectively.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
2020-10-19 14:45:49 +01:00

1079 lines
27 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., 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,
};