diff --git a/src/config/config_infiniband.c b/src/config/config_infiniband.c index 9bac3b7c..4da8fe21 100644 --- a/src/config/config_infiniband.c +++ b/src/config/config_infiniband.c @@ -44,6 +44,9 @@ REQUIRE_OBJECT ( ib_srp ); #ifdef VNIC_IPOIB REQUIRE_OBJECT ( ipoib ); #endif +#ifdef VNIC_XSIGO +REQUIRE_OBJECT ( xsigo ); +#endif /* * Drag in Infiniband-specific commands diff --git a/src/config/general.h b/src/config/general.h index 9c433039..e9b781fb 100644 --- a/src/config/general.h +++ b/src/config/general.h @@ -158,6 +158,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ #define VNIC_IPOIB /* Infiniband IPoIB virtual NICs */ +//#define VNIC_XSIGO /* Infiniband Xsigo virtual NICs */ /* * Error message tables to include diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index e64ccc39..4129861a 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -259,6 +259,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_peerdisc ( ERRFILE_NET | 0x00450000 ) #define ERRFILE_peerblk ( ERRFILE_NET | 0x00460000 ) #define ERRFILE_peermux ( ERRFILE_NET | 0x00470000 ) +#define ERRFILE_xsigo ( ERRFILE_NET | 0x00480000 ) #define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 ) #define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 ) diff --git a/src/include/ipxe/xsigo.h b/src/include/ipxe/xsigo.h new file mode 100644 index 00000000..f4f14c48 --- /dev/null +++ b/src/include/ipxe/xsigo.h @@ -0,0 +1,406 @@ +#ifndef _IPXE_XSIGO_H +#define _IPXE_XSIGO_H + +/** @file + * + * Xsigo virtual Ethernet devices + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include + +/** Xsigo directory service record name */ +#define XDS_SERVICE_NAME "XSIGOXDS" + +/** Xsigo configuration manager service ID */ +#define XCM_SERVICE_ID { 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x97, 0x01 } + +/** Xsigo management class */ +#define XSIGO_MGMT_CLASS 0x0b + +/** Xsigo management class version */ +#define XSIGO_MGMT_CLASS_VERSION 2 + +/** Xsigo configuration manager request MAD */ +#define XSIGO_ATTR_XCM_REQUEST 0xb002 + +/** Generic operating system type */ +#define XSIGO_OS_TYPE_GENERIC 0x40 + +/** Xsigo virtual Ethernet broadcast GID prefix */ +#define XVE_PREFIX 0xff15101cUL + +/** Xsigo resource types */ +enum xsigo_resource_type { + /** Virtual Ethernet resource type */ + XSIGO_RESOURCE_XVE = ( 1 << 6 ), + /** Absence-of-high-availability "resource" type */ + XSIGO_RESOURCE_NO_HA = ( 1 << 4 ), +}; + +/** A Xsigo server identifier */ +struct xsigo_server_id { + /** Virtual machine ID */ + uint32_t vm; + /** Port GUID */ + union ib_guid guid; +} __attribute__ (( packed )); + +/** A Xsigo configuration manager identifier */ +struct xsigo_manager_id { + /** Port GUID */ + union ib_guid guid; + /** LID */ + uint16_t lid; + /** Reserved */ + uint8_t reserved[10]; +} __attribute__ (( packed )); + +/** A Xsigo configuration manager request MAD */ +struct xsigo_managers_request { + /** MAD header */ + struct ib_mad_hdr mad_hdr; + /** Reserved */ + uint8_t reserved0[32]; + /** Server ID */ + struct xsigo_server_id server; + /** Hostname */ + char hostname[ 65 /* Seriously, guys? */ ]; + /** OS version */ + char os_version[32]; + /** CPU architecture */ + char arch[16]; + /** OS type */ + uint8_t os_type; + /** Reserved */ + uint8_t reserved1[3]; + /** Firmware version */ + uint64_t firmware_version; + /** Hardware version */ + uint32_t hardware_version; + /** Driver version */ + uint32_t driver_version; + /** System ID */ + union ib_gid system_id; + /** Resource types */ + uint16_t resources; + /** Reserved */ + uint8_t reserved2[2]; + /** Build version */ + char build[16]; + /** Reserved */ + uint8_t reserved3[19]; +} __attribute__ (( packed )); + +/** Resource types are present */ +#define XSIGO_RESOURCES_PRESENT 0x8000 + +/** A Xsigo configuration manager reply MAD */ +struct xsigo_managers_reply { + /** MAD header */ + struct ib_mad_hdr mad_hdr; + /** Reserved */ + uint8_t reserved0[32]; + /** Server ID */ + struct xsigo_server_id server; + /** Number of XCM records */ + uint8_t count; + /** Version */ + uint8_t version; + /** Reserved */ + uint8_t reserved1[2]; + /** Managers */ + struct xsigo_manager_id manager[8]; + /** Reserved */ + uint8_t reserved2[24]; +} __attribute__ (( packed )); + +/** A Xsigo MAD */ +union xsigo_mad { + /** Generic MAD */ + union ib_mad mad; + /** Configuration manager request */ + struct xsigo_managers_request request; + /** Configuration manager reply */ + struct xsigo_managers_reply reply; +} __attribute__ (( packed )); + +/** An XSMP node identifier */ +struct xsmp_node_id { + /** Auxiliary ID (never used) */ + uint32_t aux; + /** Port GUID */ + union ib_guid guid; +} __attribute__ (( packed )); + +/** An XSMP message header */ +struct xsmp_message_header { + /** Message type */ + uint8_t type; + /** Reason code */ + uint8_t code; + /** Length */ + uint16_t len; + /** Sequence number */ + uint32_t seq; + /** Source node ID */ + struct xsmp_node_id src; + /** Destination node ID */ + struct xsmp_node_id dst; +} __attribute__ (( packed )); + +/** XSMP message types */ +enum xsmp_message_type { + /** Session message type */ + XSMP_TYPE_SESSION = 1, + /** Virtual Ethernet message type */ + XSMP_TYPE_XVE = 6, +}; + +/** An XSMP session message */ +struct xsmp_session_message { + /** Message header */ + struct xsmp_message_header hdr; + /** Message type */ + uint8_t type; + /** Reason code */ + uint8_t code; + /** Length (excluding message header) */ + uint16_t len; + /** Operating system type */ + uint8_t os_type; + /** Reserved */ + uint8_t reserved0; + /** Resource types */ + uint16_t resources; + /** Driver version */ + uint32_t driver_version; + /** Required chassis version */ + uint32_t chassis_version; + /** Boot flags */ + uint32_t boot; + /** Firmware version */ + uint64_t firmware_version; + /** Hardware version */ + uint32_t hardware_version; + /** Vendor part ID */ + uint32_t vendor; + /** Protocol version */ + uint32_t xsmp_version; + /** Chassis name */ + char chassis[32]; + /** Session name */ + char session[32]; + /** Reserved */ + uint8_t reserved1[120]; +} __attribute__ (( packed )); + +/** XSMP session message types */ +enum xsmp_session_type { + /** Keepalive message */ + XSMP_SESSION_TYPE_HELLO = 1, + /** Initial registration message */ + XSMP_SESSION_TYPE_REGISTER = 2, + /** Registration confirmation message */ + XSMP_SESSION_TYPE_CONFIRM = 3, + /** Registration rejection message */ + XSMP_SESSION_TYPE_REJECT = 4, + /** Shutdown message */ + XSMP_SESSION_TYPE_SHUTDOWN = 5, +}; + +/** XSMP boot flags */ +enum xsmp_session_boot { + /** PXE boot */ + XSMP_BOOT_PXE = ( 1 << 0 ), +}; + +/** XSMP virtual Ethernet channel adapter parameters */ +struct xsmp_xve_ca { + /** Subnet prefix (little-endian) */ + union ib_guid prefix_le; + /** Control queue pair number */ + uint32_t ctrl; + /** Data queue pair number */ + uint32_t data; + /** Partition key */ + uint16_t pkey; + /** Queue key */ + uint16_t qkey; +} __attribute__ (( packed )); + +/** XSMP virtual Ethernet MAC address */ +struct xsmp_xve_mac { + /** High 16 bits */ + uint16_t high; + /** Low 32 bits */ + uint32_t low; +} __attribute__ (( packed )); + +/** An XSMP virtual Ethernet message */ +struct xsmp_xve_message { + /** Message header */ + struct xsmp_message_header hdr; + /** Message type */ + uint8_t type; + /** Reason code */ + uint8_t code; + /** Length (excluding message header) */ + uint16_t len; + /** Update bitmask */ + uint32_t update; + /** Resource identifier */ + union ib_guid resource; + /** TCA GUID (little-endian) */ + union ib_guid guid_le; + /** TCA LID */ + uint16_t lid; + /** MAC address (little-endian) */ + struct xsmp_xve_mac mac_le; + /** Rate */ + uint16_t rate; + /** Administrative state (non-zero = "up") */ + uint16_t state; + /** Encapsulation (apparently obsolete and unused) */ + uint16_t encap; + /** MTU */ + uint16_t mtu; + /** Installation flags (apparently obsolete and unused) */ + uint32_t install; + /** Interface name */ + char name[16]; + /** Service level */ + uint16_t sl; + /** Flow control enabled (apparently obsolete and unused) */ + uint16_t flow; + /** Committed rate (in Mbps) */ + uint16_t committed_mbps; + /** Peak rate (in Mbps) */ + uint16_t peak_mbps; + /** Committed burst size (in bytes) */ + uint32_t committed_burst; + /** Peak burst size (in bytes) */ + uint32_t peak_burst; + /** VMware index */ + uint8_t vmware; + /** Reserved */ + uint8_t reserved0; + /** Multipath flags */ + uint16_t multipath; + /** Multipath group name */ + char group[48]; + /** Link aggregation flag */ + uint8_t agg; + /** Link aggregation policy */ + uint8_t policy; + /** Network ID */ + uint32_t network; + /** Mode */ + uint8_t mode; + /** Uplink type */ + uint8_t uplink; + /** Target channel adapter parameters */ + struct xsmp_xve_ca tca; + /** Host channel adapter parameters */ + struct xsmp_xve_ca hca; + /** Reserved */ + uint8_t reserved1[336]; +} __attribute__ (( packed )); + +/** XSMP virtual Ethernet message types */ +enum xsmp_xve_type { + /** Install virtual NIC */ + XSMP_XVE_TYPE_INSTALL = 1, + /** Delete virtual NIC */ + XSMP_XVE_TYPE_DELETE = 2, + /** Update virtual NIC */ + XSMP_XVE_TYPE_UPDATE = 3, + /** Set operational state up */ + XSMP_XVE_TYPE_OPER_UP = 6, + /** Set operational state down */ + XSMP_XVE_TYPE_OPER_DOWN = 7, + /** Get operational state */ + XSMP_XVE_TYPE_OPER_REQ = 15, + /** Virtual NIC is ready */ + XSMP_XVE_TYPE_READY = 20, +}; + +/** XSMP virtual Ethernet message codes */ +enum xsmp_xve_code { + /* Something went wrong */ + XSMP_XVE_CODE_ERROR = 0x84, +}; + +/** XSMP virtual Ethernet update bitmask */ +enum xsmp_xve_update { + /** Update MTU */ + XSMP_XVE_UPDATE_MTU = ( 1 << 2 ), + /** Update administrative state */ + XSMP_XVE_UPDATE_STATE = ( 1 << 6 ), + /** Update gateway to mark as down */ + XSMP_XVE_UPDATE_GW_DOWN = ( 1 << 30 ), + /** Update gateway information */ + XSMP_XVE_UPDATE_GW_CHANGE = ( 1 << 31 ), +}; + +/** XSMP virtual Ethernet modes */ +enum xsmp_xve_mode { + /** Reliable Connected */ + XSMP_XVE_MODE_RC = 1, + /** Unreliable Datagram */ + XSMP_XVE_MODE_UD = 2, +}; + +/** XSMP virtual Ethernet uplink types */ +enum xsmp_xve_uplink { + /** No uplink */ + XSMP_XVE_NO_UPLINK = 1, + /** Has uplink */ + XSMP_XVE_UPLINK = 2, +}; + +/** An XSMP message */ +union xsmp_message { + /** Message header */ + struct xsmp_message_header hdr; + /** Session message */ + struct xsmp_session_message sess; + /** Virtual Ethernet message */ + struct xsmp_xve_message xve; +}; + +/** Delay between attempts to open the Infiniband device + * + * This is a policy decision. + */ +#define XSIGO_OPEN_RETRY_DELAY ( 2 * TICKS_PER_SEC ) + +/** Delay between unsuccessful discovery attempts + * + * This is a policy decision. + */ +#define XSIGO_DISCOVERY_FAILURE_DELAY ( 10 * TICKS_PER_SEC ) + +/** Delay between successful discovery attempts + * + * This is a policy decision. + */ +#define XSIGO_DISCOVERY_SUCCESS_DELAY ( 20 * TICKS_PER_SEC ) + +/** Delay between keepalive requests + * + * This is a policy decision. + */ +#define XSIGO_KEEPALIVE_INTERVAL ( 10 * TICKS_PER_SEC ) + +/** Maximum time to wait for a keepalive response + * + * This is a policy decision. + */ +#define XSIGO_KEEPALIVE_MAX_WAIT ( 2 * TICKS_PER_SEC ) + +#endif /* _IPXE_XSIGO_H */ diff --git a/src/net/infiniband/xsigo.c b/src/net/infiniband/xsigo.c new file mode 100644 index 00000000..91b7b71f --- /dev/null +++ b/src/net/infiniband/xsigo.c @@ -0,0 +1,1858 @@ +/* + * Copyright (C) 2016 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 + +/** @file + * + * Xsigo virtual Ethernet devices + * + */ + +/** A Xsigo device */ +struct xsigo_device { + /** Reference count */ + struct refcnt refcnt; + /** Underlying Infiniband device */ + struct ib_device *ibdev; + /** List of Xsigo devices */ + struct list_head list; + /** Device name */ + const char *name; + + /** Link opener timer */ + struct retry_timer opener; + + /** Discovery timer */ + struct retry_timer discovery; + /** Discovery management transaction (if any) */ + struct ib_mad_transaction *madx; + + /** List of configuration managers */ + struct list_head managers; +}; + +/** A Xsigo configuration manager */ +struct xsigo_manager { + /** Reference count */ + struct refcnt refcnt; + /** Xsigo device */ + struct xsigo_device *xdev; + /** List of managers */ + struct list_head list; + /** Device name */ + char name[16]; + /** Manager ID */ + struct xsigo_manager_id id; + + /** Data transfer interface */ + struct interface xfer; + /** Connection timer */ + struct retry_timer reopen; + /** Keepalive timer */ + struct retry_timer keepalive; + /** Transmission process */ + struct process process; + /** Pending transmissions */ + unsigned int pending; + /** Transmit sequence number */ + uint32_t seq; + + /** List of virtual Ethernet devices */ + struct list_head nics; +}; + +/** Configuration manager pending transmissions */ +enum xsigo_manager_pending { + /** Send connection request */ + XCM_TX_CONNECT = 0x0001, + /** Send registration message */ + XCM_TX_REGISTER = 0x0002, +}; + +/** A Xsigo virtual Ethernet device */ +struct xsigo_nic { + /** Configuration manager */ + struct xsigo_manager *xcm; + /** List of virtual Ethernet devices */ + struct list_head list; + /** Device name */ + char name[16]; + + /** Resource identifier */ + union ib_guid resource; + /** MAC address */ + uint8_t mac[ETH_ALEN]; + /** Network ID */ + unsigned long network; +}; + +/** Configuration manager service ID */ +static union ib_guid xcm_service_id = { + .bytes = XCM_SERVICE_ID, +}; + +/** List of all Xsigo devices */ +static LIST_HEAD ( xsigo_devices ); + +/** + * Free Xsigo device + * + * @v refcnt Reference count + */ +static void xsigo_free ( struct refcnt *refcnt ) { + struct xsigo_device *xdev = + container_of ( refcnt, struct xsigo_device, refcnt ); + + /* Sanity checks */ + assert ( ! timer_running ( &xdev->opener ) ); + assert ( ! timer_running ( &xdev->discovery ) ); + assert ( xdev->madx == NULL ); + assert ( list_empty ( &xdev->managers ) ); + + /* Drop reference to Infiniband device */ + ibdev_put ( xdev->ibdev ); + + /* Free device */ + free ( xdev ); +} + +/** + * Free configuration manager + * + * @v refcnt Reference count + */ +static void xcm_free ( struct refcnt *refcnt ) { + struct xsigo_manager *xcm = + container_of ( refcnt, struct xsigo_manager, refcnt ); + + /* Sanity checks */ + assert ( ! timer_running ( &xcm->reopen ) ); + assert ( ! timer_running ( &xcm->keepalive ) ); + assert ( ! process_running ( &xcm->process ) ); + assert ( list_empty ( &xcm->nics ) ); + + /* Drop reference to Xsigo device */ + ref_put ( &xcm->xdev->refcnt ); + + /* Free manager */ + free ( xcm ); +} + +/**************************************************************************** + * + * Virtual Ethernet (XVE) devices + * + **************************************************************************** + */ + +/** + * Create virtual Ethernet device + * + * @v xcm Configuration manager + * @v resource Resource identifier + * @v mac Ethernet MAC + * @v network Network identifier + * @v name Device name + * @ret rc Return status code + */ +static int xve_create ( struct xsigo_manager *xcm, union ib_guid *resource, + const uint8_t *mac, unsigned long network, + unsigned long qkey, const char *name ) { + struct xsigo_device *xdev = xcm->xdev; + struct ib_device *ibdev = xdev->ibdev; + struct xsigo_nic *xve; + struct ib_address_vector broadcast; + int rc; + + /* Allocate and initialise structure */ + xve = zalloc ( sizeof ( *xve ) ); + if ( ! xve ) { + rc = -ENOMEM; + goto err_alloc; + } + xve->xcm = xcm; + snprintf ( xve->name, sizeof ( xve->name ), "%s", name ); + memcpy ( &xve->resource, resource, sizeof ( xve->resource ) ); + memcpy ( xve->mac, mac, ETH_ALEN ); + xve->network = network; + DBGC ( xve, "XVE %s created for %s " IB_GUID_FMT "\n", + xve->name, xcm->name, IB_GUID_ARGS ( resource ) ); + DBGC ( xve, "XVE %s is MAC %s on network %ld\n", + xve->name, eth_ntoa ( mac ), network ); + + /* Construct broadcast address vector */ + memset ( &broadcast, 0, sizeof ( broadcast ) ); + broadcast.qpn = IB_QPN_BROADCAST; + broadcast.qkey = qkey; + broadcast.gid_present = 1; + broadcast.gid.dwords[0] = htonl ( XVE_PREFIX ); + broadcast.gid.words[2] = htons ( ibdev->pkey ); + broadcast.gid.dwords[3] = htonl ( network ); + + /* Create EoIB device */ + if ( ( rc = eoib_create ( ibdev, xve->mac, &broadcast, + xve->name ) ) != 0 ) { + DBGC ( xve, "XVE %s could not create EoIB device: %s\n", + xve->name, strerror ( rc ) ); + goto err_create; + } + + /* Add to list of virtual Ethernet devices. Do this only + * after creating the EoIB device, so that our net device + * notifier won't attempt to send an operational state update + * before we have acknowledged the installation. + */ + list_add ( &xve->list, &xcm->nics ); + + return 0; + + list_del ( &xve->list ); + err_create: + free ( xve ); + err_alloc: + return rc; +} + +/** + * Find virtual Ethernet device + * + * @v xcm Configuration manager + * @v resource Resource identifier + * @ret xve Virtual Ethernet device, or NULL + */ +static struct xsigo_nic * xve_find ( struct xsigo_manager *xcm, + union ib_guid *resource ) { + struct xsigo_nic *xve; + + list_for_each_entry ( xve, &xcm->nics, list ) { + if ( memcmp ( resource, &xve->resource, + sizeof ( *resource ) ) == 0 ) + return xve; + } + return NULL; +} + +/** + * Destroy virtual Ethernet device + * + * @v xve Virtual Ethernet device + */ +static void xve_destroy ( struct xsigo_nic *xve ) { + struct xsigo_manager *xcm = xve->xcm; + struct xsigo_device *xdev = xcm->xdev; + struct ib_device *ibdev = xdev->ibdev; + struct eoib_device *eoib; + + /* Destroy corresponding EoIB device, if any */ + if ( ( eoib = eoib_find ( ibdev, xve->mac ) ) ) + eoib_destroy ( eoib ); + + /* Remove from list of virtual Ethernet devices */ + list_del ( &xve->list ); + + /* Free virtual Ethernet device */ + DBGC ( xve, "XVE %s destroyed\n", xve->name ); + free ( xve ); +} + +/** + * Update virtual Ethernet device MTU + * + * @v xve Virtual Ethernet device + * @v eoib EoIB device + * @v mtu New MTU (excluding Ethernet and EoIB headers) + * @ret rc Return status code + */ +static int xve_update_mtu ( struct xsigo_nic *xve, struct eoib_device *eoib, + size_t mtu ) { + struct net_device *netdev = eoib->netdev; + size_t max; + + /* Check that we can support this MTU */ + max = ( IB_MAX_PAYLOAD_SIZE - ( sizeof ( struct ethhdr ) + + sizeof ( struct eoib_header ) ) ); + if ( mtu > max ) { + DBGC ( xve, "XVE %s cannot support MTU %zd (max %zd)\n", + xve->name, mtu, max ); + return -ERANGE; + } + + /* Update MTU. No need to close/reopen the network device, + * since our Infiniband stack uses a fixed MTU anyway. Note + * that the network device sees the Ethernet frame header but + * not the EoIB header. + */ + netdev->max_pkt_len = ( mtu + sizeof ( struct ethhdr ) ); + DBGC ( xve, "XVE %s has MTU %zd\n", xve->name, mtu ); + + return 0; +} + +/** + * Open virtual Ethernet device + * + * @v xve Virtual Ethernet device + * @v eoib EoIB device + * @v open New administrative state + * @ret rc Return status code + */ +static int xve_open ( struct xsigo_nic *xve, struct eoib_device *eoib ) { + struct net_device *netdev = eoib->netdev; + int rc; + + /* Do nothing if network device is already open */ + if ( netdev_is_open ( netdev ) ) + return 0; + DBGC ( xve, "XVE %s opening network device\n", xve->name ); + + /* Open network device */ + if ( ( rc = netdev_open ( netdev ) ) != 0 ) { + DBGC ( xve, "XVE %s could not open: %s\n", + xve->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Close virtual Ethernet device + * + * @v xve Virtual Ethernet device + * @v eoib EoIB device + */ +static void xve_close ( struct xsigo_nic *xve, struct eoib_device *eoib ) { + struct net_device *netdev = eoib->netdev; + + /* Do nothing if network device is already closed */ + if ( ! netdev_is_open ( netdev ) ) + return; + + /* Close network device */ + netdev_close ( netdev ); + DBGC ( xve, "XVE %s closed network device\n", xve->name ); +} + +/** + * Update virtual Ethernet device administrative state + * + * @v xve Virtual Ethernet device + * @v eoib EoIB device + * @v open New administrative state + * @ret rc Return status code + */ +static int xve_update_state ( struct xsigo_nic *xve, struct eoib_device *eoib, + int open ) { + + /* Open or close device, as applicable */ + if ( open ) { + return xve_open ( xve, eoib ); + } else { + xve_close ( xve, eoib ); + return 0; + } +} + +/** + * Update gateway (TCA) + * + * @v xve Virtual Ethernet device + * @v eoib EoIB device + * @v av Address vector, or NULL if no gateway + * @ret rc Return status code + */ +static int xve_update_tca ( struct xsigo_nic *xve, struct eoib_device *eoib, + struct ib_address_vector *av ) { + + /* Update gateway address */ + eoib_set_gateway ( eoib, av ); + if ( av ) { + DBGC ( xve, "XVE %s has TCA " IB_GID_FMT " data %#lx qkey " + "%#lx\n", xve->name, IB_GID_ARGS ( &av->gid ), av->qpn, + av->qkey ); + } else { + DBGC ( xve, "XVE %s has no TCA\n", xve->name ); + } + + /* The Linux driver will modify the local device's link state + * to reflect the EoIB-to-Ethernet gateway's link state, but + * this seems philosophically incorrect since communication + * within the EoIB broadcast domain still works regardless of + * the state of the gateway. + */ + + return 0; +} + +/**************************************************************************** + * + * Server management protocol (XSMP) session messages + * + **************************************************************************** + */ + +/** + * Get session message name (for debugging) + * + * @v type Message type + * @ret name Message name + */ +static const char * xsmp_session_type ( unsigned int type ) { + static char buf[16]; + + switch ( type ) { + case XSMP_SESSION_TYPE_HELLO: return "HELLO"; + case XSMP_SESSION_TYPE_REGISTER: return "REGISTER"; + case XSMP_SESSION_TYPE_CONFIRM: return "CONFIRM"; + case XSMP_SESSION_TYPE_REJECT: return "REJECT"; + case XSMP_SESSION_TYPE_SHUTDOWN: return "SHUTDOWN"; + default: + snprintf ( buf, sizeof ( buf ), "UNKNOWN<%d>", type ); + return buf; + } +} + +/** + * Extract chassis name (for debugging) + * + * @v msg Session message + * @ret chassis Chassis name + */ +static const char * xsmp_chassis_name ( struct xsmp_session_message *msg ) { + static char chassis[ sizeof ( msg->chassis ) + 1 /* NUL */ ]; + + memcpy ( chassis, msg->chassis, sizeof ( msg->chassis ) ); + return chassis; +} + +/** + * Extract session name (for debugging) + * + * @v msg Session message + * @ret session Session name + */ +static const char * xsmp_session_name ( struct xsmp_session_message *msg ) { + static char session[ sizeof ( msg->session ) + 1 /* NUL */ ]; + + memcpy ( session, msg->session, sizeof ( msg->session ) ); + return session; +} + +/** + * Send session message + * + * @v xcm Configuration manager + * @v type Message type + * @ret rc Return status code + */ +static int xsmp_tx_session ( struct xsigo_manager *xcm, unsigned int type ) { + struct xsigo_device *xdev = xcm->xdev; + struct ib_device *ibdev = xdev->ibdev; + struct xsmp_session_message msg; + int rc; + + /* Construct session message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.hdr.type = XSMP_TYPE_SESSION; + msg.hdr.len = htons ( sizeof ( msg ) ); + msg.hdr.seq = htonl ( ++xcm->seq ); + memcpy ( &msg.hdr.src.guid, &ibdev->gid.s.guid, + sizeof ( msg.hdr.src.guid ) ); + memcpy ( &msg.hdr.dst.guid, &xcm->id.guid, + sizeof ( msg.hdr.dst.guid ) ); + msg.type = type; + msg.len = htons ( sizeof ( msg ) - sizeof ( msg.hdr ) ); + msg.os_type = XSIGO_OS_TYPE_GENERIC; + msg.resources = htons ( XSIGO_RESOURCE_XVE | + XSIGO_RESOURCE_NO_HA ); + msg.boot = htonl ( XSMP_BOOT_PXE ); + DBGCP ( xcm, "XCM %s TX[%d] session %s\n", xcm->name, + ntohl ( msg.hdr.seq ), xsmp_session_type ( msg.type ) ); + DBGCP_HDA ( xcm, 0, &msg, sizeof ( msg ) ); + + /* Send session message */ + if ( ( rc = xfer_deliver_raw ( &xcm->xfer, &msg, + sizeof ( msg ) ) ) != 0 ) { + DBGC ( xcm, "XCM %s TX session %s failed: %s\n", xcm->name, + xsmp_session_type ( msg.type ), strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Send registration message + * + * @v xcm Configuration manager + * @ret rc Return status code + */ +static inline int xsmp_tx_session_register ( struct xsigo_manager *xcm ) { + + DBGC ( xcm, "XCM %s registering with " IB_GUID_FMT "\n", + xcm->name, IB_GUID_ARGS ( &xcm->id.guid ) ); + + /* Send registration message */ + return xsmp_tx_session ( xcm, XSMP_SESSION_TYPE_REGISTER ); +} + +/** + * Send keepalive message + * + * @v xcm Configuration manager + * @ret rc Return status code + */ +static int xsmp_tx_session_hello ( struct xsigo_manager *xcm ) { + + /* Send keepalive message */ + return xsmp_tx_session ( xcm, XSMP_SESSION_TYPE_HELLO ); +} + +/** + * Handle received keepalive message + * + * @v xcm Configuration manager + * @v msg Keepalive message + * @ret rc Return status code + */ +static int xsmp_rx_session_hello ( struct xsigo_manager *xcm, + struct xsmp_session_message *msg __unused ) { + + /* Respond to keepalive message. Note that the XCM doesn't + * seem to actually ever send these. + */ + return xsmp_tx_session_hello ( xcm ); +} + +/** + * Handle received registration confirmation message + * + * @v xcm Configuration manager + * @v msg Registration confirmation message + * @ret rc Return status code + */ +static int xsmp_rx_session_confirm ( struct xsigo_manager *xcm, + struct xsmp_session_message *msg ) { + + DBGC ( xcm, "XCM %s registered with \"%s\" as \"%s\"\n", xcm->name, + xsmp_chassis_name ( msg ), xsmp_session_name ( msg ) ); + + return 0; +} + +/** + * Handle received registration rejection message + * + * @v xcm Configuration manager + * @v msg Registration confirmation message + * @ret rc Return status code + */ +static int xsmp_rx_session_reject ( struct xsigo_manager *xcm, + struct xsmp_session_message *msg ) { + + DBGC ( xcm, "XCM %s rejected by \"%s\":\n", + xcm->name, xsmp_chassis_name ( msg ) ); + DBGC_HDA ( xcm, 0, msg, sizeof ( *msg ) ); + + return -EPERM; +} + +/** + * Handle received shutdown message + * + * @v xcm Configuration manager + * @v msg Registration confirmation message + * @ret rc Return status code + */ +static int xsmp_rx_session_shutdown ( struct xsigo_manager *xcm, + struct xsmp_session_message *msg ) { + + DBGC ( xcm, "XCM %s shut down by \"%s\":\n", + xcm->name, xsmp_chassis_name ( msg ) ); + DBGC_HDA ( xcm, 0, msg, sizeof ( *msg ) ); + + return -ENOTCONN; +} + +/** + * Handle received session message + * + * @v xcm Configuration manager + * @v msg Session message + * @ret rc Return status code + */ +static int xsmp_rx_session ( struct xsigo_manager *xcm, + struct xsmp_session_message *msg ) { + + DBGCP ( xcm, "XCM %s RX[%d] session %s\n", xcm->name, + ntohl ( msg->hdr.seq ), xsmp_session_type ( msg->type ) ); + DBGCP_HDA ( xcm, 0, msg, sizeof ( *msg ) ); + + /* Handle message according to type */ + switch ( msg->type ) { + case XSMP_SESSION_TYPE_HELLO: + return xsmp_rx_session_hello ( xcm, msg ); + case XSMP_SESSION_TYPE_CONFIRM: + return xsmp_rx_session_confirm ( xcm, msg ); + case XSMP_SESSION_TYPE_REJECT: + return xsmp_rx_session_reject ( xcm, msg ); + case XSMP_SESSION_TYPE_SHUTDOWN: + return xsmp_rx_session_shutdown ( xcm, msg ); + default: + DBGC ( xcm, "XCM %s RX[%d] session unexpected %s:\n", xcm->name, + ntohl ( msg->hdr.seq ), xsmp_session_type ( msg->type )); + DBGC_HDA ( xcm, 0, msg, sizeof ( *msg ) ); + return -EPROTO; + } +} + +/**************************************************************************** + * + * Server management protocol (XSMP) virtual Ethernet (XVE) messages + * + **************************************************************************** + */ + +/** + * Get virtual Ethernet message name (for debugging) + * + * @v type Message type + * @ret name Message name + */ +static const char * xsmp_xve_type ( unsigned int type ) { + static char buf[16]; + + switch ( type ) { + case XSMP_XVE_TYPE_INSTALL: return "INSTALL"; + case XSMP_XVE_TYPE_DELETE: return "DELETE"; + case XSMP_XVE_TYPE_UPDATE: return "UPDATE"; + case XSMP_XVE_TYPE_OPER_UP: return "OPER_UP"; + case XSMP_XVE_TYPE_OPER_DOWN: return "OPER_DOWN"; + case XSMP_XVE_TYPE_OPER_REQ: return "OPER_REQ"; + case XSMP_XVE_TYPE_READY: return "READY"; + default: + snprintf ( buf, sizeof ( buf ), "UNKNOWN<%d>", type ); + return buf; + } +} + +/** + * Send virtual Ethernet message + * + * @v xcm Configuration manager + * @v msg Partial message + * @ret rc Return status code + */ +static int xsmp_tx_xve ( struct xsigo_manager *xcm, + struct xsmp_xve_message *msg ) { + struct xsigo_device *xdev = xcm->xdev; + struct ib_device *ibdev = xdev->ibdev; + int rc; + + /* Fill in common header fields */ + msg->hdr.type = XSMP_TYPE_XVE; + msg->hdr.len = htons ( sizeof ( *msg ) ); + msg->hdr.seq = htonl ( ++xcm->seq ); + memcpy ( &msg->hdr.src.guid, &ibdev->gid.s.guid, + sizeof ( msg->hdr.src.guid ) ); + memcpy ( &msg->hdr.dst.guid, &xcm->id.guid, + sizeof ( msg->hdr.dst.guid ) ); + msg->len = htons ( sizeof ( *msg ) - sizeof ( msg->hdr ) ); + DBGCP ( xcm, "XCM %s TX[%d] xve %s code %#02x\n", xcm->name, + ntohl ( msg->hdr.seq ), xsmp_xve_type ( msg->type ), + msg->code ); + DBGCP_HDA ( xcm, 0, msg, sizeof ( *msg ) ); + + /* Send virtual Ethernet message */ + if ( ( rc = xfer_deliver_raw ( &xcm->xfer, msg, + sizeof ( *msg ) ) ) != 0 ) { + DBGC ( xcm, "XCM %s TX xve %s failed: %s\n", xcm->name, + xsmp_xve_type ( msg->type ), strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Send virtual Ethernet message including current device parameters + * + * @v xcm Configuration manager + * @v msg Partial virtual Ethernet message + * @v xve Virtual Ethernet device + * @v eoib EoIB device + * @ret rc Return status code + */ +static int xsmp_tx_xve_params ( struct xsigo_manager *xcm, + struct xsmp_xve_message *msg, + struct xsigo_nic *xve, + struct eoib_device *eoib ) { + struct xsigo_device *xdev = xcm->xdev; + struct ib_device *ibdev = xdev->ibdev; + struct net_device *netdev = eoib->netdev; + + /* Set successful response code */ + msg->code = 0; + + /* Include network identifier, MTU, and current HCA parameters */ + msg->network = htonl ( xve->network ); + msg->mtu = htons ( netdev->max_pkt_len - sizeof ( struct ethhdr ) ); + msg->hca.prefix_le.qword = bswap_64 ( ibdev->gid.s.prefix.qword ); + msg->hca.pkey = htons ( ibdev->pkey ); + msg->hca.qkey = msg->tca.qkey; + if ( eoib->qp ) { + msg->hca.data = htonl ( eoib->qp->ext_qpn ); + msg->hca.qkey = htons ( eoib->qp->qkey ); + } + + /* The message type field is (ab)used to return the current + * operational status. + */ + if ( msg->type == XSMP_XVE_TYPE_OPER_REQ ) { + msg->type = ( netdev_is_open ( netdev ) ? + XSMP_XVE_TYPE_OPER_UP : XSMP_XVE_TYPE_OPER_DOWN ); + } + + /* Send message */ + DBGC ( xve, "XVE %s network %d MTU %d ctrl %#x data %#x qkey %#04x " + "%s\n", xve->name, ntohl ( msg->network ), ntohs ( msg->mtu ), + ntohl ( msg->hca.ctrl ), ntohl ( msg->hca.data ), + ntohs ( msg->hca.qkey ), xsmp_xve_type ( msg->type ) ); + + return xsmp_tx_xve ( xcm, msg ); +} + +/** + * Send virtual Ethernet error response + * + * @v xcm Configuration manager + * @v msg Partial virtual Ethernet message + * @ret rc Return status code + */ +static inline int xsmp_tx_xve_nack ( struct xsigo_manager *xcm, + struct xsmp_xve_message *msg ) { + + /* Set error response code. (There aren't any meaningful + * detailed response codes defined by the wire protocol.) + */ + msg->code = XSMP_XVE_CODE_ERROR; + + /* Send message */ + return xsmp_tx_xve ( xcm, msg ); +} + +/** + * Send virtual Ethernet notification + * + * @v xcm Configuration manager + * @v type Message type + * @v xve Virtual Ethernet device + * @v eoib EoIB device + * @ret rc Return status code + */ +static int xsmp_tx_xve_notify ( struct xsigo_manager *xcm, + unsigned int type, + struct xsigo_nic *xve, + struct eoib_device *eoib ) { + struct xsmp_xve_message msg; + + /* Construct message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.type = type; + memcpy ( &msg.resource, &xve->resource, sizeof ( msg.resource ) ); + + /* Send message */ + return xsmp_tx_xve_params ( xcm, &msg, xve, eoib ); +} + +/** + * Send virtual Ethernet current operational state + * + * @v xcm Configuration manager + * @v xve Virtual Ethernet device + * @v eoib EoIB device + * @ret rc Return status code + */ +static inline int xsmp_tx_xve_oper ( struct xsigo_manager *xcm, + struct xsigo_nic *xve, + struct eoib_device *eoib ) { + + /* Send notification */ + return xsmp_tx_xve_notify ( xcm, XSMP_XVE_TYPE_OPER_REQ, xve, eoib ); +} + +/** + * Handle received virtual Ethernet modification message + * + * @v xcm Configuration manager + * @v msg Virtual Ethernet message + * @v update Update bitmask + * @ret rc Return status code + */ +static int xsmp_rx_xve_modify ( struct xsigo_manager *xcm, + struct xsmp_xve_message *msg, + unsigned int update ) { + struct xsigo_device *xdev = xcm->xdev; + struct ib_device *ibdev = xdev->ibdev; + struct xsigo_nic *xve; + struct eoib_device *eoib; + struct ib_address_vector tca; + size_t mtu; + int rc; + + /* Avoid returning uninitialised HCA parameters in response */ + memset ( &msg->hca, 0, sizeof ( msg->hca ) ); + + /* Find virtual Ethernet device */ + xve = xve_find ( xcm, &msg->resource ); + if ( ! xve ) { + DBGC ( xcm, "XCM %s unrecognised resource " IB_GUID_FMT "\n", + xcm->name, IB_GUID_ARGS ( &msg->resource ) ); + rc = -ENOENT; + goto err_no_xve; + } + + /* Find corresponding EoIB device */ + eoib = eoib_find ( ibdev, xve->mac ); + if ( ! eoib ) { + DBGC ( xve, "XVE %s has no EoIB device\n", xve->name ); + rc = -EPIPE; + goto err_no_eoib; + } + + /* The Xsigo management software fails to create the EoIB + * multicast group. This is a fundamental design flaw. + */ + eoib_force_group_creation ( eoib ); + + /* Extract modifiable parameters. Note that the TCA GID is + * erroneously transmitted as little-endian. + */ + mtu = ntohs ( msg->mtu ); + tca.qpn = ntohl ( msg->tca.data ); + tca.qkey = ntohs ( msg->tca.qkey ); + tca.gid_present = 1; + tca.gid.s.prefix.qword = bswap_64 ( msg->tca.prefix_le.qword ); + tca.gid.s.guid.qword = bswap_64 ( msg->guid_le.qword ); + + /* Update MTU, if applicable */ + if ( ( update & XSMP_XVE_UPDATE_MTU ) && + ( ( rc = xve_update_mtu ( xve, eoib, mtu ) ) != 0 ) ) + goto err_mtu; + update &= ~XSMP_XVE_UPDATE_MTU; + + /* Update admin state, if applicable */ + if ( ( update & XSMP_XVE_UPDATE_STATE ) && + ( ( rc = xve_update_state ( xve, eoib, msg->state ) ) != 0 ) ) + goto err_state; + update &= ~XSMP_XVE_UPDATE_STATE; + + /* Remove gateway, if applicable */ + if ( ( update & XSMP_XVE_UPDATE_GW_DOWN ) && + ( ( rc = xve_update_tca ( xve, eoib, NULL ) ) != 0 ) ) + goto err_gw_down; + update &= ~XSMP_XVE_UPDATE_GW_DOWN; + + /* Update gateway, if applicable */ + if ( ( update & XSMP_XVE_UPDATE_GW_CHANGE ) && + ( ( rc = xve_update_tca ( xve, eoib, &tca ) ) != 0 ) ) + goto err_gw_change; + update &= ~XSMP_XVE_UPDATE_GW_CHANGE; + + /* Warn about unexpected updates */ + if ( update ) { + DBGC ( xve, "XVE %s unrecognised update(s) %#08x\n", + xve->name, update ); + } + + xsmp_tx_xve_params ( xcm, msg, xve, eoib ); + return 0; + + err_gw_change: + err_gw_down: + err_state: + err_mtu: + err_no_eoib: + err_no_xve: + /* Send NACK */ + xsmp_tx_xve_nack ( xcm, msg ); + return rc; +} + +/** + * Handle received virtual Ethernet installation message + * + * @v xcm Configuration manager + * @v msg Virtual Ethernet message + * @ret rc Return status code + */ +static int xsmp_rx_xve_install ( struct xsigo_manager *xcm, + struct xsmp_xve_message *msg ) { + union { + struct xsmp_xve_mac msg; + uint8_t raw[ETH_ALEN]; + } mac; + char name[ sizeof ( msg->name ) + 1 /* NUL */ ]; + unsigned long network; + unsigned long qkey; + unsigned int update; + int rc; + + /* Demangle MAC address (which is erroneously transmitted as + * little-endian). + */ + mac.msg.high = bswap_16 ( msg->mac_le.high ); + mac.msg.low = bswap_32 ( msg->mac_le.low ); + + /* Extract interface name (which may not be NUL-terminated) */ + memcpy ( name, msg->name, ( sizeof ( name ) - 1 /* NUL */ ) ); + name[ sizeof ( name ) - 1 /* NUL */ ] = '\0'; + + /* Extract remaining message parameters */ + network = ntohl ( msg->network ); + qkey = ntohs ( msg->tca.qkey ); + DBGC2 ( xcm, "XCM %s " IB_GUID_FMT " install \"%s\" %s net %ld qkey " + "%#lx\n", xcm->name, IB_GUID_ARGS ( &msg->resource ), name, + eth_ntoa ( mac.raw ), network, qkey ); + + /* Create virtual Ethernet device, if applicable */ + if ( ( xve_find ( xcm, &msg->resource ) == NULL ) && + ( ( rc = xve_create ( xcm, &msg->resource, mac.raw, network, + qkey, name ) ) != 0 ) ) + goto err_create; + + /* Handle remaining parameters as for a modification message */ + update = XSMP_XVE_UPDATE_MTU; + if ( msg->uplink == XSMP_XVE_UPLINK ) + update |= XSMP_XVE_UPDATE_GW_CHANGE; + return xsmp_rx_xve_modify ( xcm, msg, update ); + + err_create: + /* Send NACK */ + xsmp_tx_xve_nack ( xcm, msg ); + return rc; +} + +/** + * Handle received virtual Ethernet deletion message + * + * @v xcm Configuration manager + * @v msg Virtual Ethernet message + * @ret rc Return status code + */ +static int xsmp_rx_xve_delete ( struct xsigo_manager *xcm, + struct xsmp_xve_message *msg ) { + struct xsigo_nic *xve; + + DBGC2 ( xcm, "XCM %s " IB_GUID_FMT " delete\n", + xcm->name, IB_GUID_ARGS ( &msg->resource ) ); + + /* Destroy virtual Ethernet device (if any) */ + if ( ( xve = xve_find ( xcm, &msg->resource ) ) ) + xve_destroy ( xve ); + + /* Send ACK */ + msg->code = 0; + xsmp_tx_xve ( xcm, msg ); + + return 0; +} + +/** + * Handle received virtual Ethernet update message + * + * @v xcm Configuration manager + * @v msg Virtual Ethernet message + * @ret rc Return status code + */ +static int xsmp_rx_xve_update ( struct xsigo_manager *xcm, + struct xsmp_xve_message *msg ) { + unsigned int update = ntohl ( msg->update ); + + DBGC2 ( xcm, "XCM %s " IB_GUID_FMT " update (%08x)\n", + xcm->name, IB_GUID_ARGS ( &msg->resource ), update ); + + /* Handle as a modification message */ + return xsmp_rx_xve_modify ( xcm, msg, update ); +} + +/** + * Handle received virtual Ethernet operational request message + * + * @v xcm Configuration manager + * @v msg Virtual Ethernet message + * @ret rc Return status code + */ +static int xsmp_rx_xve_oper_req ( struct xsigo_manager *xcm, + struct xsmp_xve_message *msg ) { + + DBGC2 ( xcm, "XCM %s " IB_GUID_FMT " operational request\n", + xcm->name, IB_GUID_ARGS ( &msg->resource ) ); + + /* Handle as a nullipotent modification message */ + return xsmp_rx_xve_modify ( xcm, msg, 0 ); +} + +/** + * Handle received virtual Ethernet readiness message + * + * @v xcm Configuration manager + * @v msg Virtual Ethernet message + * @ret rc Return status code + */ +static int xsmp_rx_xve_ready ( struct xsigo_manager *xcm, + struct xsmp_xve_message *msg ) { + int rc; + + DBGC2 ( xcm, "XCM %s " IB_GUID_FMT " ready\n", + xcm->name, IB_GUID_ARGS ( &msg->resource ) ); + + /* Handle as a nullipotent modification message */ + if ( ( rc = xsmp_rx_xve_modify ( xcm, msg, 0 ) ) != 0 ) + return rc; + + /* Send an unsolicited operational state update, since there + * is no other way to convey the current operational state. + */ + msg->type = XSMP_XVE_TYPE_OPER_REQ; + if ( ( rc = xsmp_rx_xve_modify ( xcm, msg, 0 ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Handle received virtual Ethernet message + * + * @v xcm Configuration manager + * @v msg Virtual Ethernet message + * @ret rc Return status code + */ +static int xsmp_rx_xve ( struct xsigo_manager *xcm, + struct xsmp_xve_message *msg ) { + + DBGCP ( xcm, "XCM %s RX[%d] xve %s\n", xcm->name, + ntohl ( msg->hdr.seq ), xsmp_xve_type ( msg->type ) ); + DBGCP_HDA ( xcm, 0, msg, sizeof ( *msg ) ); + + /* Handle message according to type */ + switch ( msg->type ) { + case XSMP_XVE_TYPE_INSTALL: + return xsmp_rx_xve_install ( xcm, msg ); + case XSMP_XVE_TYPE_DELETE: + return xsmp_rx_xve_delete ( xcm, msg ); + case XSMP_XVE_TYPE_UPDATE: + return xsmp_rx_xve_update ( xcm, msg ); + case XSMP_XVE_TYPE_OPER_REQ: + return xsmp_rx_xve_oper_req ( xcm, msg ); + case XSMP_XVE_TYPE_READY: + return xsmp_rx_xve_ready ( xcm, msg ); + default: + DBGC ( xcm, "XCM %s RX[%d] xve unexpected %s:\n", xcm->name, + ntohl ( msg->hdr.seq ), xsmp_xve_type ( msg->type ) ); + DBGC_HDA ( xcm, 0, msg, sizeof ( *msg ) ); + return -EPROTO; + } +} + +/**************************************************************************** + * + * Configuration managers (XCM) + * + **************************************************************************** + */ + +/** + * Close configuration manager connection + * + * @v xcm Configuration manager + * @v rc Reason for close + */ +static void xcm_close ( struct xsigo_manager *xcm, int rc ) { + + DBGC ( xcm, "XCM %s closed: %s\n", xcm->name, strerror ( rc ) ); + + /* Stop transmission process */ + process_del ( &xcm->process ); + + /* Stop keepalive timer */ + stop_timer ( &xcm->keepalive ); + + /* Restart data transfer interface */ + intf_restart ( &xcm->xfer, rc ); + + /* Schedule reconnection attempt */ + start_timer ( &xcm->reopen ); +} + +/** + * Send data to configuration manager + * + * @v xcm Configuration manager + */ +static void xcm_step ( struct xsigo_manager *xcm ) { + int rc; + + /* Do nothing unless we have something to send */ + if ( ! xcm->pending ) + return; + + /* Send (empty) connection request, if applicable */ + if ( xcm->pending & XCM_TX_CONNECT ) { + if ( ( rc = xfer_deliver_raw ( &xcm->xfer, NULL, 0 ) ) != 0 ) { + DBGC ( xcm, "XCM %s could not send connection request: " + "%s\n", xcm->name, strerror ( rc ) ); + goto err; + } + xcm->pending &= ~XCM_TX_CONNECT; + return; + } + + /* Wait until data transfer interface is connected */ + if ( ! xfer_window ( &xcm->xfer ) ) + return; + + /* Send registration message, if applicable */ + if ( xcm->pending & XCM_TX_REGISTER ) { + if ( ( rc = xsmp_tx_session_register ( xcm ) ) != 0 ) + goto err; + xcm->pending &= ~XCM_TX_REGISTER; + return; + } + + return; + + err: + xcm_close ( xcm, rc ); +} + +/** + * Receive data from configuration manager + * + * @v xcm Configuration manager + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int xcm_deliver ( struct xsigo_manager *xcm, struct io_buffer *iobuf, + struct xfer_metadata *meta __unused ) { + union xsmp_message *msg; + size_t len = iob_len ( iobuf ); + int rc; + + /* Sanity check */ + if ( len < sizeof ( msg->hdr ) ) { + DBGC ( xcm, "XCM %s underlength message:\n", xcm->name ); + DBGC_HDA ( xcm, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EPROTO; + goto out; + } + msg = iobuf->data; + + /* Handle message according to type */ + if ( ! msg->hdr.type ) { + + /* Ignore unused communication manager private data blocks */ + rc = 0; + + } else if ( ( msg->hdr.type == XSMP_TYPE_SESSION ) && + ( len >= sizeof ( msg->sess ) ) ) { + + /* Session message */ + rc = xsmp_rx_session ( xcm, &msg->sess ); + + } else if ( ( msg->hdr.type == XSMP_TYPE_XVE ) && + ( len >= sizeof ( msg->xve ) ) ) { + + /* Virtual Ethernet message */ + xsmp_rx_xve ( xcm, &msg->xve ); + + /* Virtual Ethernet message errors are non-fatal */ + rc = 0; + + } else { + + /* Unknown message */ + DBGC ( xcm, "XCM %s unexpected message type %d:\n", + xcm->name, msg->hdr.type ); + DBGC_HDA ( xcm, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EPROTO; + } + + out: + free_iob ( iobuf ); + if ( rc != 0 ) + xcm_close ( xcm, rc ); + return rc; +} + +/** Configuration manager data transfer interface operations */ +static struct interface_operation xcm_xfer_op[] = { + INTF_OP ( xfer_deliver, struct xsigo_manager *, xcm_deliver ), + INTF_OP ( xfer_window_changed, struct xsigo_manager *, xcm_step ), + INTF_OP ( intf_close, struct xsigo_manager *, xcm_close ), +}; + +/** Configuration manager data transfer interface descriptor */ +static struct interface_descriptor xcm_xfer_desc = + INTF_DESC ( struct xsigo_manager, xfer, xcm_xfer_op ); + +/** Configuration manager process descriptor */ +static struct process_descriptor xcm_process_desc = + PROC_DESC_ONCE ( struct xsigo_manager, process, xcm_step ); + +/** + * Handle configuration manager connection timer expiry + * + * @v timer Connection timer + * @v fail Failure indicator + */ +static void xcm_reopen ( struct retry_timer *timer, int fail __unused ) { + struct xsigo_manager *xcm = + container_of ( timer, struct xsigo_manager, reopen ); + struct xsigo_device *xdev = xcm->xdev; + struct ib_device *ibdev = xdev->ibdev; + union ib_gid gid; + int rc; + + /* Stop transmission process */ + process_del ( &xcm->process ); + + /* Stop keepalive timer */ + stop_timer ( &xcm->keepalive ); + + /* Restart data transfer interface */ + intf_restart ( &xcm->xfer, -ECANCELED ); + + /* Reset sequence number */ + xcm->seq = 0; + + /* Construct GID */ + memcpy ( &gid.s.prefix, &ibdev->gid.s.prefix, sizeof ( gid.s.prefix ) ); + memcpy ( &gid.s.guid, &xcm->id.guid, sizeof ( gid.s.guid ) ); + DBGC ( xcm, "XCM %s connecting to " IB_GID_FMT "\n", + xcm->name, IB_GID_ARGS ( &gid ) ); + + /* Open CMRC connection */ + if ( ( rc = ib_cmrc_open ( &xcm->xfer, ibdev, &gid, + &xcm_service_id, xcm->name ) ) != 0 ) { + DBGC ( xcm, "XCM %s could not open CMRC connection: %s\n", + xcm->name, strerror ( rc ) ); + start_timer ( &xcm->reopen ); + return; + } + + /* Schedule transmissions */ + xcm->pending |= ( XCM_TX_CONNECT | XCM_TX_REGISTER ); + process_add ( &xcm->process ); + + /* Start keepalive timer */ + start_timer_fixed ( &xcm->keepalive, XSIGO_KEEPALIVE_INTERVAL ); + + return; +} + +/** + * Handle configuration manager keepalive timer expiry + * + * @v timer Connection timer + * @v fail Failure indicator + */ +static void xcm_keepalive ( struct retry_timer *timer, int fail __unused ) { + struct xsigo_manager *xcm = + container_of ( timer, struct xsigo_manager, keepalive ); + int rc; + + /* Send keepalive message. The server won't actually respond + * to these, but it gives the RC queue pair a chance to + * complain if it doesn't ever at least get an ACK. + */ + if ( ( rc = xsmp_tx_session_hello ( xcm ) ) != 0 ) { + xcm_close ( xcm, rc ); + return; + } + + /* Restart keepalive timer */ + start_timer_fixed ( &xcm->keepalive, XSIGO_KEEPALIVE_INTERVAL ); +} + +/** + * Create configuration manager + * + * @v xsigo Xsigo device + * @v id Configuration manager ID + * @ret rc Return status code + */ +static int xcm_create ( struct xsigo_device *xdev, + struct xsigo_manager_id *id ) { + struct xsigo_manager *xcm; + + /* Allocate and initialise structure */ + xcm = zalloc ( sizeof ( *xcm ) ); + if ( ! xcm ) + return -ENOMEM; + ref_init ( &xcm->refcnt, xcm_free ); + xcm->xdev = xdev; + ref_get ( &xcm->xdev->refcnt ); + snprintf ( xcm->name, sizeof ( xcm->name ), "%s:xcm-%d", + xdev->name, ntohs ( id->lid ) ); + memcpy ( &xcm->id, id, sizeof ( xcm->id ) ); + intf_init ( &xcm->xfer, &xcm_xfer_desc, &xcm->refcnt ); + timer_init ( &xcm->keepalive, xcm_keepalive, &xcm->refcnt ); + timer_init ( &xcm->reopen, xcm_reopen, &xcm->refcnt ); + process_init_stopped ( &xcm->process, &xcm_process_desc, &xcm->refcnt ); + INIT_LIST_HEAD ( &xcm->nics ); + + /* Start timer to open connection */ + start_timer_nodelay ( &xcm->reopen ); + + /* Add to list of managers and transfer reference to list */ + list_add ( &xcm->list, &xdev->managers ); + DBGC ( xcm, "XCM %s created for " IB_GUID_FMT " (LID %d)\n", xcm->name, + IB_GUID_ARGS ( &xcm->id.guid ), ntohs ( id->lid ) ); + return 0; +} + +/** + * Find configuration manager + * + * @v xsigo Xsigo device + * @v id Configuration manager ID + * @ret xcm Configuration manager, or NULL + */ +static struct xsigo_manager * xcm_find ( struct xsigo_device *xdev, + struct xsigo_manager_id *id ) { + struct xsigo_manager *xcm; + union ib_guid *guid = &id->guid; + + /* Find configuration manager */ + list_for_each_entry ( xcm, &xdev->managers, list ) { + if ( memcmp ( guid, &xcm->id.guid, sizeof ( *guid ) ) == 0 ) + return xcm; + } + return NULL; +} + +/** + * Destroy configuration manager + * + * @v xcm Configuration manager + */ +static void xcm_destroy ( struct xsigo_manager *xcm ) { + struct xsigo_nic *xve; + + /* Remove all EoIB NICs */ + while ( ( xve = list_first_entry ( &xcm->nics, struct xsigo_nic, + list ) ) ) { + xve_destroy ( xve ); + } + + /* Stop transmission process */ + process_del ( &xcm->process ); + + /* Stop timers */ + stop_timer ( &xcm->keepalive ); + stop_timer ( &xcm->reopen ); + + /* Shut down data transfer interface */ + intf_shutdown ( &xcm->xfer, 0 ); + + /* Remove from list of managers and drop list's reference */ + DBGC ( xcm, "XCM %s destroyed\n", xcm->name ); + list_del ( &xcm->list ); + ref_put ( &xcm->refcnt ); +} + +/** + * Synchronise list of configuration managers + * + * @v xdev Xsigo device + * @v ids List of manager IDs + * @v count Number of manager IDs + * @ret rc Return status code + */ +static int xcm_list ( struct xsigo_device *xdev, struct xsigo_manager_id *ids, + unsigned int count ) { + struct xsigo_manager_id *id; + struct xsigo_manager *xcm; + struct xsigo_manager *tmp; + struct list_head list; + unsigned int i; + int rc; + + /* Create list of managers to be retained */ + INIT_LIST_HEAD ( &list ); + for ( i = 0, id = ids ; i < count ; i++, id++ ) { + if ( ( xcm = xcm_find ( xdev, id ) ) ) { + list_del ( &xcm->list ); + list_add_tail ( &xcm->list, &list ); + } + } + + /* Destroy any managers not in the list */ + list_for_each_entry_safe ( xcm, tmp, &xdev->managers, list ) + xcm_destroy ( xcm ); + list_splice ( &list, &xdev->managers ); + + /* Create any new managers in the list, and force reconnection + * for any changed LIDs. + */ + for ( i = 0, id = ids ; i < count ; i++, id++ ) { + if ( ( xcm = xcm_find ( xdev, id ) ) ) { + if ( xcm->id.lid != id->lid ) + start_timer_nodelay ( &xcm->reopen ); + continue; + } + if ( ( rc = xcm_create ( xdev, id ) ) != 0 ) { + DBGC ( xdev, "XDEV %s could not create manager: %s\n", + xdev->name, strerror ( rc ) ); + return rc; + } + } + + return 0; +} + +/**************************************************************************** + * + * Configuration manager discovery + * + **************************************************************************** + */ + +/** A stage of discovery */ +struct xsigo_discovery { + /** Name */ + const char *name; + /** Management transaction operations */ + struct ib_mad_transaction_operations op; +}; + +/** + * Handle configuration manager lookup completion + * + * @v ibdev Infiniband device + * @v mi Management interface + * @v madx Management transaction + * @v rc Status code + * @v mad Received MAD (or NULL on error) + * @v av Source address vector (or NULL on error) + */ +static void xsigo_xcm_complete ( struct ib_device *ibdev, + struct ib_mad_interface *mi __unused, + struct ib_mad_transaction *madx, + int rc, union ib_mad *mad, + struct ib_address_vector *av __unused ) { + struct xsigo_device *xdev = ib_madx_get_ownerdata ( madx ); + union xsigo_mad *xsmad = container_of ( mad, union xsigo_mad, mad ); + struct xsigo_managers_reply *reply = &xsmad->reply; + + /* Check for failures */ + if ( ( rc == 0 ) && ( mad->hdr.status != htons ( IB_MGMT_STATUS_OK ) ) ) + rc = -ENODEV; + if ( rc != 0 ) { + DBGC ( xdev, "XDEV %s manager lookup failed: %s\n", + xdev->name, strerror ( rc ) ); + goto out; + } + + /* Sanity checks */ + if ( reply->count > ( sizeof ( reply->manager ) / + sizeof ( reply->manager[0] ) ) ) { + DBGC ( xdev, "XDEV %s has too many managers (%d)\n", + xdev->name, reply->count ); + goto out; + } + + /* Synchronise list of managers */ + if ( ( rc = xcm_list ( xdev, reply->manager, reply->count ) ) != 0 ) + goto out; + + /* Report an empty list of managers */ + if ( reply->count == 0 ) + DBGC ( xdev, "XDEV %s has no managers\n", xdev->name ); + + /* Delay next discovery attempt */ + start_timer_fixed ( &xdev->discovery, XSIGO_DISCOVERY_SUCCESS_DELAY ); + +out: + /* Destroy the completed transaction */ + ib_destroy_madx ( ibdev, ibdev->gsi, madx ); + xdev->madx = NULL; +} + +/** Configuration manager lookup discovery stage */ +static struct xsigo_discovery xsigo_xcm_discovery = { + .name = "manager", + .op = { + .complete = xsigo_xcm_complete, + }, +}; + +/** + * Handle directory service lookup completion + * + * @v ibdev Infiniband device + * @v mi Management interface + * @v madx Management transaction + * @v rc Status code + * @v mad Received MAD (or NULL on error) + * @v av Source address vector (or NULL on error) + */ +static void xsigo_xds_complete ( struct ib_device *ibdev, + struct ib_mad_interface *mi __unused, + struct ib_mad_transaction *madx, + int rc, union ib_mad *mad, + struct ib_address_vector *av __unused ) { + struct xsigo_device *xdev = ib_madx_get_ownerdata ( madx ); + union xsigo_mad *xsmad = container_of ( mad, union xsigo_mad, mad ); + struct xsigo_managers_request *request = &xsmad->request; + struct ib_service_record *svc; + struct ib_address_vector dest; + union ib_guid *guid; + + /* Allow for reuse of transaction pointer */ + xdev->madx = NULL; + + /* Check for failures */ + if ( ( rc == 0 ) && ( mad->hdr.status != htons ( IB_MGMT_STATUS_OK ) ) ) + rc = -ENODEV; + if ( rc != 0 ) { + DBGC ( xdev, "XDEV %s directory lookup failed: %s\n", + xdev->name, strerror ( rc ) ); + goto out; + } + + /* Construct address vector */ + memset ( &dest, 0, sizeof ( dest ) ); + svc = &mad->sa.sa_data.service_record; + dest.lid = ntohs ( svc->data16[0] ); + dest.sl = ibdev->sm_sl; + dest.qpn = IB_QPN_GSI; + dest.qkey = IB_QKEY_GSI; + guid = ( ( union ib_guid * ) &svc->data64[0] ); + DBGC2 ( xdev, "XDEV %s found directory at LID %d GUID " IB_GUID_FMT + "\n", xdev->name, dest.lid, IB_GUID_ARGS ( guid ) ); + + /* Construct request (reusing MAD buffer) */ + memset ( request, 0, sizeof ( *request ) ); + request->mad_hdr.mgmt_class = XSIGO_MGMT_CLASS; + request->mad_hdr.class_version = XSIGO_MGMT_CLASS_VERSION; + request->mad_hdr.method = IB_MGMT_METHOD_GET; + request->mad_hdr.attr_id = htons ( XSIGO_ATTR_XCM_REQUEST ); + memcpy ( &request->server.guid, &ibdev->gid.s.guid, + sizeof ( request->server.guid ) ); + snprintf ( request->os_version, sizeof ( request->os_version ), + "%s %s", product_short_name, product_version ); + snprintf ( request->arch, sizeof ( request->arch ), _S2 ( ARCH ) ); + request->os_type = XSIGO_OS_TYPE_GENERIC; + request->resources = htons ( XSIGO_RESOURCES_PRESENT | + XSIGO_RESOURCE_XVE | + XSIGO_RESOURCE_NO_HA ); + + /* The handling of this request on the server side is a + * textbook example of how not to design a wire protocol. The + * server uses the _driver_ version number to determine which + * fields are present. + */ + request->driver_version = htonl ( 0x2a2a2a ); + + /* The build version field is ignored unless it happens to + * contain the substring "xg-". + */ + snprintf ( request->build, sizeof ( request->build ), + "not-xg-%08lx", build_id ); + + /* The server side user interface occasionally has no way to + * refer to an entry with an empty hostname. + */ + fetch_string_setting ( NULL, &hostname_setting, request->hostname, + sizeof ( request->hostname ) ); + if ( ! request->hostname[0] ) { + snprintf ( request->hostname, sizeof ( request->hostname ), + "%s-" IB_GUID_FMT, product_short_name, + IB_GUID_ARGS ( &ibdev->gid.s.guid ) ); + } + + /* Start configuration manager lookup */ + xdev->madx = ib_create_madx ( ibdev, ibdev->gsi, mad, &dest, + &xsigo_xcm_discovery.op ); + if ( ! xdev->madx ) { + DBGC ( xdev, "XDEV %s could not start manager lookup\n", + xdev->name ); + goto out; + } + ib_madx_set_ownerdata ( xdev->madx, xdev ); + +out: + /* Destroy the completed transaction */ + ib_destroy_madx ( ibdev, ibdev->gsi, madx ); +} + +/** Directory service lookup discovery stage */ +static struct xsigo_discovery xsigo_xds_discovery = { + .name = "directory", + .op = { + .complete = xsigo_xds_complete, + }, +}; + +/** + * Discover configuration managers + * + * @v timer Retry timer + * @v over Failure indicator + */ +static void xsigo_discover ( struct retry_timer *timer, int over __unused ) { + struct xsigo_device *xdev = + container_of ( timer, struct xsigo_device, discovery ); + struct ib_device *ibdev = xdev->ibdev; + struct xsigo_discovery *discovery; + + /* Restart timer */ + start_timer_fixed ( &xdev->discovery, XSIGO_DISCOVERY_FAILURE_DELAY ); + + /* Cancel any pending discovery transaction */ + if ( xdev->madx ) { + discovery = container_of ( xdev->madx->op, + struct xsigo_discovery, op ); + DBGC ( xdev, "XDEV %s timed out waiting for %s lookup\n", + xdev->name, discovery->name ); + ib_destroy_madx ( ibdev, ibdev->gsi, xdev->madx ); + xdev->madx = NULL; + } + + /* Start directory service lookup */ + xdev->madx = ib_create_service_madx ( ibdev, ibdev->gsi, + XDS_SERVICE_NAME, + &xsigo_xds_discovery.op ); + if ( ! xdev->madx ) { + DBGC ( xdev, "XDEV %s could not start directory lookup\n", + xdev->name ); + return; + } + ib_madx_set_ownerdata ( xdev->madx, xdev ); +} + +/**************************************************************************** + * + * Infiniband device driver + * + **************************************************************************** + */ + +/** + * Open link and start discovery + * + * @v opener Link opener + * @v over Failure indicator + */ +static void xsigo_ib_open ( struct retry_timer *opener, int over __unused ) { + struct xsigo_device *xdev = + container_of ( opener, struct xsigo_device, opener ); + struct ib_device *ibdev = xdev->ibdev; + int rc; + + /* Open Infiniband device */ + if ( ( rc = ib_open ( ibdev ) ) != 0 ) { + DBGC ( xdev, "XDEV %s could not open: %s\n", + xdev->name, strerror ( rc ) ); + /* Delay and try again */ + start_timer_fixed ( &xdev->opener, XSIGO_OPEN_RETRY_DELAY ); + return; + } + + /* If link is already up, then start discovery */ + if ( ib_link_ok ( ibdev ) ) + start_timer_nodelay ( &xdev->discovery ); +} + +/** + * Probe Xsigo device + * + * @v ibdev Infiniband device + * @ret rc Return status code + */ +static int xsigo_ib_probe ( struct ib_device *ibdev ) { + struct xsigo_device *xdev; + + /* Allocate and initialise structure */ + xdev = zalloc ( sizeof ( *xdev ) ); + if ( ! xdev ) + return -ENOMEM; + ref_init ( &xdev->refcnt, xsigo_free ); + xdev->ibdev = ibdev_get ( ibdev ); + xdev->name = ibdev->name; + timer_init ( &xdev->opener, xsigo_ib_open, &xdev->refcnt ); + timer_init ( &xdev->discovery, xsigo_discover, &xdev->refcnt ); + INIT_LIST_HEAD ( &xdev->managers ); + + /* Start timer to open Infiniband device. (We are currently + * within the Infiniband device probe callback list; opening + * the device here would have interesting side-effects.) + */ + start_timer_nodelay ( &xdev->opener ); + + /* Add to list of devices and transfer reference to list */ + list_add_tail ( &xdev->list, &xsigo_devices ); + DBGC ( xdev, "XDEV %s created for " IB_GUID_FMT "\n", + xdev->name, IB_GUID_ARGS ( &ibdev->gid.s.guid ) ); + return 0; +} + +/** + * Handle device or link status change + * + * @v ibdev Infiniband device + */ +static void xsigo_ib_notify ( struct ib_device *ibdev ) { + struct xsigo_device *xdev; + + /* Stop/restart discovery on any attached devices */ + list_for_each_entry ( xdev, &xsigo_devices, list ) { + + /* Skip non-attached devices */ + if ( xdev->ibdev != ibdev ) + continue; + + /* Stop any ongoing discovery */ + if ( xdev->madx ) { + ib_destroy_madx ( ibdev, ibdev->gsi, xdev->madx ); + xdev->madx = NULL; + } + stop_timer ( &xdev->discovery ); + + /* If link is up, then start discovery */ + if ( ib_link_ok ( ibdev ) ) + start_timer_nodelay ( &xdev->discovery ); + } +} + +/** + * Remove Xsigo device + * + * @v ibdev Infiniband device + */ +static void xsigo_ib_remove ( struct ib_device *ibdev ) { + struct xsigo_device *xdev; + struct xsigo_device *tmp; + + /* Remove any attached Xsigo devices */ + list_for_each_entry_safe ( xdev, tmp, &xsigo_devices, list ) { + + /* Skip non-attached devices */ + if ( xdev->ibdev != ibdev ) + continue; + + /* Stop any ongoing discovery */ + if ( xdev->madx ) { + ib_destroy_madx ( ibdev, ibdev->gsi, xdev->madx ); + xdev->madx = NULL; + } + stop_timer ( &xdev->discovery ); + + /* Destroy all configuration managers */ + xcm_list ( xdev, NULL, 0 ); + + /* Close Infiniband device, if applicable */ + if ( ! timer_running ( &xdev->opener ) ) + ib_close ( xdev->ibdev ); + + /* Stop link opener */ + stop_timer ( &xdev->opener ); + + /* Remove from list of devices and drop list's reference */ + DBGC ( xdev, "XDEV %s destroyed\n", xdev->name ); + list_del ( &xdev->list ); + ref_put ( &xdev->refcnt ); + } +} + +/** Xsigo Infiniband driver */ +struct ib_driver xsigo_ib_driver __ib_driver = { + .name = "Xsigo", + .probe = xsigo_ib_probe, + .notify = xsigo_ib_notify, + .remove = xsigo_ib_remove, +}; + +/**************************************************************************** + * + * Network device driver + * + **************************************************************************** + */ + +/** + * Handle device or link status change + * + * @v netdev Network device + */ +static void xsigo_net_notify ( struct net_device *netdev ) { + struct xsigo_device *xdev; + struct ib_device *ibdev; + struct xsigo_manager *xcm; + struct xsigo_nic *xve; + struct eoib_device *eoib; + + /* Send current operational state to XCM, if applicable */ + list_for_each_entry ( xdev, &xsigo_devices, list ) { + ibdev = xdev->ibdev; + list_for_each_entry ( xcm, &xdev->managers, list ) { + list_for_each_entry ( xve, &xcm->nics, list ) { + eoib = eoib_find ( ibdev, xve->mac ); + if ( ! eoib ) + continue; + if ( eoib->netdev != netdev ) + continue; + xsmp_tx_xve_oper ( xcm, xve, eoib ); + } + } + } +} + +/** Xsigo network driver */ +struct net_driver xsigo_net_driver __net_driver = { + .name = "Xsigo", + .notify = xsigo_net_notify, +};