/* * Copyright (C) 2006 Michael Brown . * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** @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 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; /** ACPI descriptor */ struct acpi_descriptor desc; }; /** 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 ), }; /** 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.". */ 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, };