diff options
Diffstat (limited to 'qemu/roms/ipxe/src/net/aoe.c')
-rw-r--r-- | qemu/roms/ipxe/src/net/aoe.c | 1057 |
1 files changed, 1057 insertions, 0 deletions
diff --git a/qemu/roms/ipxe/src/net/aoe.c b/qemu/roms/ipxe/src/net/aoe.c new file mode 100644 index 000000000..a6d7b3e7b --- /dev/null +++ b/qemu/roms/ipxe/src/net/aoe.c @@ -0,0 +1,1057 @@ +/* + * 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. + */ + +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 <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/aoe.h> + +/** @file + * + * AoE protocol + * + */ + +FEATURE ( FEATURE_PROTOCOL, "AoE", DHCP_EB_FEATURE_AOE, 1 ); + +struct net_protocol aoe_protocol __net_protocol; + +/****************************************************************************** + * + * 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 device */ +struct aoe_device { + /** Reference counter */ + struct refcnt refcnt; + + /** Network device */ + struct net_device *netdev; + /** ATA command issuing interface */ + struct interface ata; + + /** Major number */ + uint16_t major; + /** Minor number */ + uint8_t minor; + /** Target MAC address */ + uint8_t target[MAX_LL_ADDR_LEN]; + + /** Saved timeout value */ + unsigned long timeout; + + /** Configuration command interface */ + struct interface config; + /** Device is configued */ + int configured; +}; + +/** 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; +} + +/** + * Describe AoE device in an ACPI table + * + * @v aoedev AoE device + * @v acpi ACPI table + * @v len Length of ACPI table + * @ret rc Return status code + */ +static int aoedev_describe ( struct aoe_device *aoedev, + struct acpi_description_header *acpi, + size_t len ) { + struct abft_table *abft = + container_of ( acpi, struct abft_table, acpi ); + + /* Sanity check */ + if ( len < sizeof ( *abft ) ) + return -ENOBUFS; + + /* Populate table */ + 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 ) ); + + return 0; +} + +/** 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 ), +}; + +/** 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 ); + + /* 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, +}; |