| /* |
| * Copyright (C) 2006 Michael Brown <[email protected]>. |
| * |
| * 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., 675 Mass Ave, Cambridge, MA 02139, 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 <gpxe/list.h> |
| #include <gpxe/if_ether.h> |
| #include <gpxe/ethernet.h> |
| #include <gpxe/iobuf.h> |
| #include <gpxe/uaccess.h> |
| #include <gpxe/ata.h> |
| #include <gpxe/netdevice.h> |
| #include <gpxe/process.h> |
| #include <gpxe/features.h> |
| #include <gpxe/aoe.h> |
| |
| /** @file |
| * |
| * AoE protocol |
| * |
| */ |
| |
| FEATURE ( FEATURE_PROTOCOL, "AoE", DHCP_EB_FEATURE_AOE, 1 ); |
| |
| struct net_protocol aoe_protocol; |
| |
| /** List of all AoE sessions */ |
| static LIST_HEAD ( aoe_sessions ); |
| |
| static void aoe_free ( struct refcnt *refcnt ) { |
| struct aoe_session *aoe = |
| container_of ( refcnt, struct aoe_session, refcnt ); |
| |
| netdev_put ( aoe->netdev ); |
| free ( aoe ); |
| } |
| |
| /** |
| * Mark current AoE command complete |
| * |
| * @v aoe AoE session |
| * @v rc Return status code |
| */ |
| static void aoe_done ( struct aoe_session *aoe, int rc ) { |
| |
| /* Record overall command status */ |
| if ( aoe->command ) { |
| aoe->command->cb.cmd_stat = aoe->status; |
| aoe->command->rc = rc; |
| aoe->command = NULL; |
| } |
| |
| /* Stop retransmission timer */ |
| stop_timer ( &aoe->timer ); |
| |
| /* Mark operation as complete */ |
| aoe->rc = rc; |
| } |
| |
| /** |
| * Send AoE command |
| * |
| * @v aoe AoE session |
| * @ret rc Return status code |
| * |
| * This transmits an AoE command packet. It does not wait for a |
| * response. |
| */ |
| static int aoe_send_command ( struct aoe_session *aoe ) { |
| struct ata_command *command = aoe->command; |
| struct io_buffer *iobuf; |
| struct aoehdr *aoehdr; |
| union aoecmd *aoecmd; |
| struct aoeata *aoeata; |
| unsigned int count; |
| unsigned int data_out_len; |
| unsigned int aoecmdlen; |
| |
| /* Fail immediately if we have no netdev to send on */ |
| if ( ! aoe->netdev ) { |
| aoe_done ( aoe, -ENETUNREACH ); |
| return -ENETUNREACH; |
| } |
| |
| /* 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 ( &aoe->timer ); |
| |
| /* Calculate count and data_out_len for this subcommand */ |
| switch ( aoe->aoe_cmd_type ) { |
| case AOE_CMD_ATA: |
| count = command->cb.count.native; |
| if ( count > AOE_MAX_COUNT ) |
| count = AOE_MAX_COUNT; |
| data_out_len = ( command->data_out ? |
| ( count * ATA_SECTOR_SIZE ) : 0 ); |
| aoecmdlen = sizeof ( aoecmd->ata ); |
| break; |
| case AOE_CMD_CONFIG: |
| count = 0; |
| data_out_len = 0; |
| aoecmdlen = sizeof ( aoecmd->cfg ); |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| /* Create outgoing I/O buffer */ |
| iobuf = alloc_iob ( ETH_HLEN + sizeof ( *aoehdr ) + |
| aoecmdlen + data_out_len ); |
| |
| if ( ! iobuf ) |
| return -ENOMEM; |
| iob_reserve ( iobuf, ETH_HLEN ); |
| aoehdr = iob_put ( iobuf, sizeof ( *aoehdr ) ); |
| aoecmd = iob_put ( iobuf, aoecmdlen ); |
| memset ( aoehdr, 0, ( sizeof ( *aoehdr ) + aoecmdlen ) ); |
| |
| /* Fill AoE header */ |
| aoehdr->ver_flags = AOE_VERSION; |
| aoehdr->major = htons ( aoe->major ); |
| aoehdr->minor = aoe->minor; |
| aoehdr->command = aoe->aoe_cmd_type; |
| aoehdr->tag = htonl ( ++aoe->tag ); |
| |
| /* Fill AoE payload */ |
| switch ( aoe->aoe_cmd_type ) { |
| case AOE_CMD_ATA: |
| /* Fill AoE command */ |
| aoeata = &aoecmd->ata; |
| linker_assert ( AOE_FL_DEV_HEAD == ATA_DEV_SLAVE, |
| __fix_ata_h__ ); |
| aoeata->aflags = ( ( command->cb.lba48 ? AOE_FL_EXTENDED : 0 )| |
| ( command->cb.device & ATA_DEV_SLAVE ) | |
| ( data_out_len ? AOE_FL_WRITE : 0 ) ); |
| aoeata->err_feat = command->cb.err_feat.bytes.cur; |
| aoeata->count = count; |
| 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 ); |
| |
| /* Fill data payload */ |
| copy_from_user ( iob_put ( iobuf, data_out_len ), |
| command->data_out, aoe->command_offset, |
| data_out_len ); |
| break; |
| case AOE_CMD_CONFIG: |
| /* Nothing to do */ |
| break; |
| default: |
| assert ( 0 ); |
| } |
| |
| /* Send packet */ |
| return net_tx ( iobuf, aoe->netdev, &aoe_protocol, aoe->target ); |
| } |
| |
| /** |
| * Handle AoE retry timer expiry |
| * |
| * @v timer AoE retry timer |
| * @v fail Failure indicator |
| */ |
| static void aoe_timer_expired ( struct retry_timer *timer, int fail ) { |
| struct aoe_session *aoe = |
| container_of ( timer, struct aoe_session, timer ); |
| |
| if ( fail ) { |
| aoe_done ( aoe, -ETIMEDOUT ); |
| } else { |
| aoe_send_command ( aoe ); |
| } |
| } |
| |
| /** |
| * Handle AoE configuration command response |
| * |
| * @v aoe AoE session |
| * @v ll_source Link-layer source address |
| * @ret rc Return status code |
| */ |
| static int aoe_rx_cfg ( struct aoe_session *aoe, const void *ll_source ) { |
| |
| /* Record target MAC address */ |
| memcpy ( aoe->target, ll_source, sizeof ( aoe->target ) ); |
| DBGC ( aoe, "AoE %p target MAC address %s\n", |
| aoe, eth_ntoa ( aoe->target ) ); |
| |
| /* Mark config request as complete */ |
| aoe_done ( aoe, 0 ); |
| |
| return 0; |
| } |
| |
| /** |
| * Handle AoE ATA command response |
| * |
| * @v aoe AoE session |
| * @v aoeata AoE ATA command |
| * @v len Length of AoE ATA command |
| * @ret rc Return status code |
| */ |
| static int aoe_rx_ata ( struct aoe_session *aoe, struct aoeata *aoeata, |
| size_t len ) { |
| struct ata_command *command = aoe->command; |
| unsigned int rx_data_len; |
| unsigned int count; |
| unsigned int data_len; |
| |
| /* Sanity check */ |
| if ( len < sizeof ( *aoeata ) ) { |
| /* Ignore packet; allow timer to trigger retransmit */ |
| return -EINVAL; |
| } |
| rx_data_len = ( len - sizeof ( *aoeata ) ); |
| |
| /* Calculate count and data_len for this subcommand */ |
| count = command->cb.count.native; |
| if ( count > AOE_MAX_COUNT ) |
| count = AOE_MAX_COUNT; |
| data_len = count * ATA_SECTOR_SIZE; |
| |
| /* Merge into overall ATA status */ |
| aoe->status |= aoeata->cmd_stat; |
| |
| /* Copy data payload */ |
| if ( command->data_in ) { |
| if ( rx_data_len > data_len ) |
| rx_data_len = data_len; |
| copy_to_user ( command->data_in, aoe->command_offset, |
| aoeata->data, rx_data_len ); |
| } |
| |
| /* Update ATA command and offset */ |
| aoe->command_offset += data_len; |
| command->cb.lba.native += count; |
| command->cb.count.native -= count; |
| |
| /* Check for operation complete */ |
| if ( ! command->cb.count.native ) { |
| aoe_done ( aoe, 0 ); |
| return 0; |
| } |
| |
| /* Transmit next portion of request */ |
| stop_timer ( &aoe->timer ); |
| aoe_send_command ( aoe ); |
| |
| return 0; |
| } |
| |
| /** |
| * Process incoming AoE packets |
| * |
| * @v iobuf I/O buffer |
| * @v netdev Network device |
| * @v ll_source Link-layer source address |
| * @ret rc Return status code |
| * |
| */ |
| static int aoe_rx ( struct io_buffer *iobuf, |
| struct net_device *netdev __unused, |
| const void *ll_source ) { |
| struct aoehdr *aoehdr = iobuf->data; |
| struct aoe_session *aoe; |
| int rc = 0; |
| |
| /* Sanity checks */ |
| if ( iob_len ( iobuf ) < sizeof ( *aoehdr ) ) { |
| rc = -EINVAL; |
| goto done; |
| } |
| if ( ( aoehdr->ver_flags & AOE_VERSION_MASK ) != AOE_VERSION ) { |
| rc = -EPROTONOSUPPORT; |
| goto done; |
| } |
| if ( ! ( aoehdr->ver_flags & AOE_FL_RESPONSE ) ) { |
| /* Ignore AoE requests that we happen to see */ |
| goto done; |
| } |
| iob_pull ( iobuf, sizeof ( *aoehdr ) ); |
| |
| /* Demultiplex amongst active AoE sessions */ |
| list_for_each_entry ( aoe, &aoe_sessions, list ) { |
| if ( ntohs ( aoehdr->major ) != aoe->major ) |
| continue; |
| if ( aoehdr->minor != aoe->minor ) |
| continue; |
| if ( ntohl ( aoehdr->tag ) != aoe->tag ) |
| continue; |
| if ( aoehdr->ver_flags & AOE_FL_ERROR ) { |
| aoe_done ( aoe, -EIO ); |
| break; |
| } |
| switch ( aoehdr->command ) { |
| case AOE_CMD_ATA: |
| rc = aoe_rx_ata ( aoe, iobuf->data, iob_len ( iobuf )); |
| break; |
| case AOE_CMD_CONFIG: |
| rc = aoe_rx_cfg ( aoe, ll_source ); |
| break; |
| default: |
| DBGC ( aoe, "AoE %p ignoring command %02x\n", |
| aoe, aoehdr->command ); |
| break; |
| } |
| break; |
| } |
| |
| done: |
| 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, |
| }; |
| |
| /** |
| * Issue ATA command via an open AoE session |
| * |
| * @v ata ATA device |
| * @v command ATA command |
| * @ret rc Return status code |
| */ |
| static int aoe_command ( struct ata_device *ata, |
| struct ata_command *command ) { |
| struct aoe_session *aoe = |
| container_of ( ata->backend, struct aoe_session, refcnt ); |
| |
| aoe->command = command; |
| aoe->status = 0; |
| aoe->command_offset = 0; |
| aoe->aoe_cmd_type = AOE_CMD_ATA; |
| |
| aoe_send_command ( aoe ); |
| |
| return 0; |
| } |
| |
| /** |
| * Issue AoE config query for AoE target discovery |
| * |
| * @v aoe AoE session |
| * @ret rc Return status code |
| */ |
| static int aoe_discover ( struct aoe_session *aoe ) { |
| int rc; |
| |
| aoe->status = 0; |
| aoe->aoe_cmd_type = AOE_CMD_CONFIG; |
| aoe->command = NULL; |
| |
| aoe_send_command ( aoe ); |
| |
| aoe->rc = -EINPROGRESS; |
| while ( aoe->rc == -EINPROGRESS ) |
| step(); |
| rc = aoe->rc; |
| |
| return rc; |
| } |
| |
| static int aoe_detached_command ( struct ata_device *ata __unused, |
| struct ata_command *command __unused ) { |
| return -ENODEV; |
| } |
| |
| void aoe_detach ( struct ata_device *ata ) { |
| struct aoe_session *aoe = |
| container_of ( ata->backend, struct aoe_session, refcnt ); |
| |
| stop_timer ( &aoe->timer ); |
| ata->command = aoe_detached_command; |
| list_del ( &aoe->list ); |
| ref_put ( ata->backend ); |
| ata->backend = NULL; |
| } |
| |
| static int aoe_parse_root_path ( struct aoe_session *aoe, |
| const char *root_path ) { |
| char *ptr; |
| |
| if ( strncmp ( root_path, "aoe:", 4 ) != 0 ) |
| return -EINVAL; |
| ptr = ( ( char * ) root_path + 4 ); |
| |
| if ( *ptr++ != 'e' ) |
| return -EINVAL; |
| |
| aoe->major = strtoul ( ptr, &ptr, 10 ); |
| if ( *ptr++ != '.' ) |
| return -EINVAL; |
| |
| aoe->minor = strtoul ( ptr, &ptr, 10 ); |
| if ( *ptr ) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| int aoe_attach ( struct ata_device *ata, struct net_device *netdev, |
| const char *root_path ) { |
| struct aoe_session *aoe; |
| int rc; |
| |
| /* Allocate and initialise structure */ |
| aoe = zalloc ( sizeof ( *aoe ) ); |
| if ( ! aoe ) |
| return -ENOMEM; |
| aoe->refcnt.free = aoe_free; |
| aoe->netdev = netdev_get ( netdev ); |
| memcpy ( aoe->target, netdev->ll_broadcast, sizeof ( aoe->target ) ); |
| aoe->tag = AOE_TAG_MAGIC; |
| aoe->timer.expired = aoe_timer_expired; |
| |
| /* Parse root path */ |
| if ( ( rc = aoe_parse_root_path ( aoe, root_path ) ) != 0 ) |
| goto err; |
| |
| /* Attach parent interface, transfer reference to connection |
| * list, and return |
| */ |
| ata->backend = ref_get ( &aoe->refcnt ); |
| ata->command = aoe_command; |
| list_add ( &aoe->list, &aoe_sessions ); |
| |
| /* Send discovery packet to find the target MAC address. |
| * Ideally, this ought to be done asynchronously, but the |
| * block device interface does not yet support asynchronous |
| * operation. |
| */ |
| if ( ( rc = aoe_discover( aoe ) ) != 0 ) |
| goto err; |
| |
| return 0; |
| |
| err: |
| ref_put ( &aoe->refcnt ); |
| return rc; |
| } |