| /* | |
| * 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 ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #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/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 | |
| * @ret rc Return status code | |
| */ | |
| int ( * 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 ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| 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 ); | |
| if ( ( rc = aoecmd->type->cmd ( aoecmd, iobuf->data, | |
| iob_len ( iobuf ) ) ) != 0 ) { | |
| goto err_cmd; | |
| } | |
| /* Send packet */ | |
| if ( ( rc = net_tx ( iob_disown ( 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 ) ); | |
| goto err_tx; | |
| } | |
| return 0; | |
| err_tx: | |
| err_cmd: | |
| free_iob ( iobuf ); | |
| err_alloc: | |
| return rc; | |
| } | |
| /** | |
| * 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; | |
| 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; | |
| } | |
| aoehdr = iobuf->data; | |
| 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 | |
| * @ret rc Return status code | |
| */ | |
| static int 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; | |
| int rc; | |
| /* Sanity check */ | |
| static_assert ( AOE_FL_DEV_HEAD == ATA_DEV_SLAVE ); | |
| 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 ); | |
| 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" ); | |
| /* Copy data-out (if any) to command IU */ | |
| if ( ( rc = xferbuf_read ( &command->data_out, 0, aoeata->data, | |
| command->data_out.len ) ) != 0 ) { | |
| DBGC ( aoedev, "AoE %s/%08x could not read data-out: %s\n", | |
| aoedev_name ( aoedev ), aoecmd->tag, strerror ( rc ) ); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * 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; | |
| int rc; | |
| /* 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 */ | |
| if ( ( rc = xferbuf_write ( &command->data_in, 0, aoeata->data, | |
| command->data_in.len ) ) != 0 ) { | |
| DBGC ( aoedev, "AoE %s/%08x could not write data-in: %s\n", | |
| aoedev_name ( aoedev ), aoecmd->tag, strerror ( rc ) ); | |
| return rc; | |
| } | |
| 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 | |
| * @ret rc Return status code | |
| */ | |
| static int 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 ); | |
| return 0; | |
| } | |
| /** | |
| * 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; | |
| 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; | |
| } | |
| aoehdr = iobuf->data; | |
| 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, | |
| }; |