| /* |
| * 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, |
| }; |