aboutsummaryrefslogtreecommitdiffstats
path: root/src/drivers/net/ice.c
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2022-08-08 15:02:44 +0100
committerMichael Brown <mcb30@ipxe.org>2022-08-12 16:15:17 +0100
commitcad1cc6b449b63415ffdad8e12f13df4256106fb (patch)
tree3eee6a3efda0cb709e632f209073cffb19cc2192 /src/drivers/net/ice.c
parent6871a7de705b6f6a4046f0d19da9bcd689c3bc8e (diff)
downloadipxe-cad1cc6b449b63415ffdad8e12f13df4256106fb.tar.gz
[intelxl] Add driver for Intel 100 Gigabit Ethernet NICs
Add a driver for the E810 family of 100 Gigabit Ethernet NICs. The core datapath is identical to that of the 40 Gigabit XL710, and this part of the code is shared between both drivers. The admin queue mechanism is sufficiently similar to make it worth reusing substantial portions of the code, with separate implementations for several commands to handle the (unnecessarily) breaking changes in data structure layouts. The major differences are in the mechanisms for programming queue contexts (where the E810 abandons TX/RX symmetry) and for configuring the transmit scheduler and receive filters: these portions are sufficiently different to justify a separate driver. Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/drivers/net/ice.c')
-rw-r--r--src/drivers/net/ice.c986
1 files changed, 986 insertions, 0 deletions
diff --git a/src/drivers/net/ice.c b/src/drivers/net/ice.c
new file mode 100644
index 000000000..b5d66f1bb
--- /dev/null
+++ b/src/drivers/net/ice.c
@@ -0,0 +1,986 @@
+/*
+ * Copyright (C) 2022 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) 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 <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/ethernet.h>
+#include <ipxe/if_ether.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/pci.h>
+#include "ice.h"
+
+/** @file
+ *
+ * Intel 100 Gigabit Ethernet network card driver
+ *
+ */
+
+/**
+ * Magic MAC address
+ *
+ * Used as the source address and promiscuous unicast destination
+ * address in the "add switch rules" command.
+ */
+static uint8_t ice_magic_mac[ETH_HLEN] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/******************************************************************************
+ *
+ * Admin queue
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Get firmware version
+ *
+ * @v intelxl Intel device
+ * @ret rc Return status code
+ */
+static int ice_admin_version ( struct intelxl_nic *intelxl ) {
+ struct ice_admin_descriptor *cmd;
+ struct ice_admin_version_params *version;
+ unsigned int api;
+ int rc;
+
+ /* Populate descriptor */
+ cmd = ice_admin_command_descriptor ( intelxl );
+ cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_VERSION );
+ version = &cmd->params.version;
+
+ /* Issue command */
+ if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+ return rc;
+ api = version->api.major;
+ DBGC ( intelxl, "ICE %p firmware v%d/%d.%d.%d API v%d/%d.%d.%d\n",
+ intelxl, version->firmware.branch, version->firmware.major,
+ version->firmware.minor, version->firmware.patch,
+ version->api.branch, version->api.major, version->api.minor,
+ version->api.patch );
+
+ /* Check for API compatibility */
+ if ( api > INTELXL_ADMIN_API_MAJOR ) {
+ DBGC ( intelxl, "ICE %p unsupported API v%d\n", intelxl, api );
+ return -ENOTSUP;
+ }
+
+ return 0;
+}
+
+/**
+ * Get MAC address
+ *
+ * @v netdev Network device
+ * @ret rc Return status code
+ */
+static int ice_admin_mac_read ( struct net_device *netdev ) {
+ struct intelxl_nic *intelxl = netdev->priv;
+ struct ice_admin_descriptor *cmd;
+ struct ice_admin_mac_read_params *read;
+ struct ice_admin_mac_read_address *mac;
+ union ice_admin_buffer *buf;
+ unsigned int i;
+ int rc;
+
+ /* Populate descriptor */
+ cmd = ice_admin_command_descriptor ( intelxl );
+ cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_MAC_READ );
+ cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_BUF );
+ cmd->len = cpu_to_le16 ( sizeof ( buf->mac_read ) );
+ read = &cmd->params.mac_read;
+ buf = ice_admin_command_buffer ( intelxl );
+
+ /* Issue command */
+ if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+ return rc;
+
+ /* Check that MAC address is present in response */
+ if ( ! ( read->valid & INTELXL_ADMIN_MAC_READ_VALID_LAN ) ) {
+ DBGC ( intelxl, "ICE %p has no MAC address\n", intelxl );
+ return -ENOENT;
+ }
+
+ /* Identify MAC address */
+ for ( i = 0 ; i < read->count ; i++ ) {
+
+ /* Check for a LAN MAC address */
+ mac = &buf->mac_read.mac[i];
+ if ( mac->type != ICE_ADMIN_MAC_READ_TYPE_LAN )
+ continue;
+
+ /* Check that address is valid */
+ if ( ! is_valid_ether_addr ( mac->mac ) ) {
+ DBGC ( intelxl, "ICE %p has invalid MAC address "
+ "(%s)\n", intelxl, eth_ntoa ( mac->mac ) );
+ return -EINVAL;
+ }
+
+ /* Copy MAC address */
+ DBGC ( intelxl, "ICE %p has MAC address %s\n",
+ intelxl, eth_ntoa ( mac->mac ) );
+ memcpy ( netdev->hw_addr, mac->mac, ETH_ALEN );
+
+ return 0;
+ }
+
+ /* Missing LAN MAC address */
+ DBGC ( intelxl, "ICE %p has no LAN MAC address\n",
+ intelxl );
+ return -ENOENT;
+}
+
+/**
+ * Set MAC address
+ *
+ * @v netdev Network device
+ * @ret rc Return status code
+ */
+static int ice_admin_mac_write ( struct net_device *netdev ) {
+ struct intelxl_nic *intelxl = netdev->priv;
+ struct ice_admin_descriptor *cmd;
+ struct ice_admin_mac_write_params *write;
+ int rc;
+
+ /* Populate descriptor */
+ cmd = ice_admin_command_descriptor ( intelxl );
+ cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_MAC_WRITE );
+ write = &cmd->params.mac_write;
+ memcpy ( write->mac, netdev->ll_addr, ETH_ALEN );
+
+ /* Issue command */
+ if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
+ * Get switch configuration
+ *
+ * @v intelxl Intel device
+ * @ret rc Return status code
+ */
+static int ice_admin_switch ( struct intelxl_nic *intelxl ) {
+ struct ice_admin_descriptor *cmd;
+ struct ice_admin_switch_params *sw;
+ union ice_admin_buffer *buf;
+ uint16_t next = 0;
+ uint16_t seid;
+ uint16_t type;
+ int rc;
+
+ /* Get each configuration in turn */
+ do {
+ /* Populate descriptor */
+ cmd = ice_admin_command_descriptor ( intelxl );
+ cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_SWITCH );
+ cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_BUF );
+ cmd->len = cpu_to_le16 ( sizeof ( buf->sw ) );
+ sw = &cmd->params.sw;
+ sw->next = next;
+ buf = ice_admin_command_buffer ( intelxl );
+
+ /* Issue command */
+ if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+ return rc;
+ seid = le16_to_cpu ( buf->sw.cfg[0].seid );
+
+ /* Dump raw configuration */
+ DBGC2 ( intelxl, "ICE %p SEID %#04x:\n", intelxl, seid );
+ DBGC2_HDA ( intelxl, 0, &buf->sw.cfg[0],
+ sizeof ( buf->sw.cfg[0] ) );
+
+ /* Parse response */
+ type = ( seid & ICE_ADMIN_SWITCH_TYPE_MASK );
+ if ( type == ICE_ADMIN_SWITCH_TYPE_VSI ) {
+ intelxl->vsi = ( seid & ~ICE_ADMIN_SWITCH_TYPE_MASK );
+ DBGC ( intelxl, "ICE %p VSI %#04x uplink %#04x func "
+ "%#04x\n", intelxl, intelxl->vsi,
+ le16_to_cpu ( buf->sw.cfg[0].uplink ),
+ le16_to_cpu ( buf->sw.cfg[0].func ) );
+ }
+
+ } while ( ( next = sw->next ) );
+
+ /* Check that we found a VSI */
+ if ( ! intelxl->vsi ) {
+ DBGC ( intelxl, "ICE %p has no VSI\n", intelxl );
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+/**
+ * Add switch rules
+ *
+ * @v intelxl Intel device
+ * @v mac MAC address
+ * @ret rc Return status code
+ */
+static int ice_admin_rules ( struct intelxl_nic *intelxl, uint8_t *mac ) {
+ struct ice_admin_descriptor *cmd;
+ struct ice_admin_rules_params *rules;
+ union ice_admin_buffer *buf;
+ int rc;
+
+ /* Populate descriptor */
+ cmd = ice_admin_command_descriptor ( intelxl );
+ cmd->opcode = cpu_to_le16 ( ICE_ADMIN_ADD_RULES );
+ cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_BUF | INTELXL_ADMIN_FL_RD );
+ cmd->len = cpu_to_le16 ( sizeof ( buf->rules ) );
+ rules = &cmd->params.rules;
+ rules->count = cpu_to_le16 ( 1 );
+ buf = ice_admin_command_buffer ( intelxl );
+ buf->rules.recipe = cpu_to_le16 ( ICE_ADMIN_RULES_RECIPE_PROMISC );
+ buf->rules.port = cpu_to_le16 ( intelxl->port );
+ buf->rules.action =
+ cpu_to_le32 ( ICE_ADMIN_RULES_ACTION_VALID |
+ ICE_ADMIN_RULES_ACTION_VSI ( intelxl->vsi ) );
+ buf->rules.len = cpu_to_le16 ( sizeof ( buf->rules.hdr ) );
+ memcpy ( buf->rules.hdr.eth.h_dest, mac, ETH_ALEN );
+ memcpy ( buf->rules.hdr.eth.h_source, ice_magic_mac, ETH_ALEN );
+ buf->rules.hdr.eth.h_protocol = htons ( ETH_P_8021Q );
+
+ /* Issue command */
+ if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
+ * Check if scheduler node is a parent (i.e. non-leaf) node
+ *
+ * @v branch Scheduler topology branch
+ * @v node Scheduler topology node
+ * @ret child Any child node, or NULL if not found
+ */
+static struct ice_admin_schedule_node *
+ice_admin_schedule_is_parent ( struct ice_admin_schedule_branch *branch,
+ struct ice_admin_schedule_node *node ) {
+ unsigned int count = le16_to_cpu ( branch->count );
+ struct ice_admin_schedule_node *child;
+ unsigned int i;
+
+ /* Find a child element, if any */
+ for ( i = 0 ; i < count ; i++ ) {
+ child = &branch->node[i];
+ if ( child->parent == node->teid )
+ return child;
+ }
+
+ return NULL;
+}
+
+/**
+ * Query default scheduling tree topology
+ *
+ * @v intelxl Intel device
+ * @ret rc Return status code
+ */
+static int ice_admin_schedule ( struct intelxl_nic *intelxl ) {
+ struct ice_admin_descriptor *cmd;
+ struct ice_admin_schedule_params *sched;
+ struct ice_admin_schedule_branch *branch;
+ struct ice_admin_schedule_node *node;
+ union ice_admin_buffer *buf;
+ int i;
+ int rc;
+
+ /* Populate descriptor */
+ cmd = ice_admin_command_descriptor ( intelxl );
+ cmd->opcode = cpu_to_le16 ( ICE_ADMIN_SCHEDULE );
+ cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_BUF );
+ cmd->len = cpu_to_le16 ( sizeof ( buf->sched ) );
+ sched = &cmd->params.sched;
+ buf = ice_admin_command_buffer ( intelxl );
+
+ /* Issue command */
+ if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+ return rc;
+
+ /* Sanity checks */
+ if ( ! sched->branches ) {
+ DBGC ( intelxl, "ICE %p topology has no branches\n", intelxl );
+ return -EINVAL;
+ }
+ branch = buf->sched.branch;
+
+ /* Identify leaf node */
+ for ( i = ( le16_to_cpu ( branch->count ) - 1 ) ; i >= 0 ; i-- ) {
+ node = &branch->node[i];
+ if ( ! ice_admin_schedule_is_parent ( branch, node ) ) {
+ intelxl->teid = le32_to_cpu ( node->teid );
+ DBGC2 ( intelxl, "ICE %p TEID %#08x type %d\n",
+ intelxl, intelxl->teid, node->config.type );
+ break;
+ }
+ }
+ if ( ! intelxl->teid ) {
+ DBGC ( intelxl, "ICE %p found no leaf TEID\n", intelxl );
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * Restart autonegotiation
+ *
+ * @v intelxl Intel device
+ * @ret rc Return status code
+ */
+static int ice_admin_autoneg ( struct intelxl_nic *intelxl ) {
+ struct ice_admin_descriptor *cmd;
+ struct ice_admin_autoneg_params *autoneg;
+ int rc;
+
+ /* Populate descriptor */
+ cmd = ice_admin_command_descriptor ( intelxl );
+ cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_AUTONEG );
+ autoneg = &cmd->params.autoneg;
+ autoneg->flags = ( INTELXL_ADMIN_AUTONEG_FL_RESTART |
+ INTELXL_ADMIN_AUTONEG_FL_ENABLE );
+
+ /* Issue command */
+ if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
+ * Get link status
+ *
+ * @v netdev Network device
+ * @ret rc Return status code
+ */
+static int ice_admin_link ( struct net_device *netdev ) {
+ struct intelxl_nic *intelxl = netdev->priv;
+ struct ice_admin_descriptor *cmd;
+ struct ice_admin_link_params *link;
+ union ice_admin_buffer *buf;
+ int rc;
+
+ /* Populate descriptor */
+ cmd = ice_admin_command_descriptor ( intelxl );
+ cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_LINK );
+ cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_BUF );
+ cmd->len = cpu_to_le16 ( sizeof ( buf->link ) );
+ link = &cmd->params.link;
+ link->notify = INTELXL_ADMIN_LINK_NOTIFY;
+ buf = ice_admin_command_buffer ( intelxl );
+
+ /* Issue command */
+ if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+ return rc;
+ DBGC ( intelxl, "ICE %p speed %#02x status %#02x\n",
+ intelxl, le16_to_cpu ( buf->link.speed ), buf->link.status );
+
+ /* Update network device */
+ if ( buf->link.status & INTELXL_ADMIN_LINK_UP ) {
+ netdev_link_up ( netdev );
+ } else {
+ netdev_link_down ( netdev );
+ }
+
+ return 0;
+}
+
+/**
+ * Handle admin event
+ *
+ * @v netdev Network device
+ * @v xlevt Event descriptor
+ * @v xlbuf Data buffer
+ */
+static void ice_admin_event ( struct net_device *netdev,
+ struct intelxl_admin_descriptor *xlevt,
+ union intelxl_admin_buffer *xlbuf __unused ) {
+ struct intelxl_nic *intelxl = netdev->priv;
+ struct ice_admin_descriptor *evt =
+ container_of ( xlevt, struct ice_admin_descriptor, xl );
+
+ /* Ignore unrecognised events */
+ if ( evt->opcode != cpu_to_le16 ( INTELXL_ADMIN_LINK ) ) {
+ DBGC ( intelxl, "INTELXL %p unrecognised event opcode "
+ "%#04x\n", intelxl, le16_to_cpu ( evt->opcode ) );
+ return;
+ }
+
+ /* Update link status */
+ ice_admin_link ( netdev );
+}
+
+/**
+ * Add transmit queue
+ *
+ * @v intelxl Intel device
+ * @v ring Descriptor ring
+ * @ret rc Return status code
+ */
+static int ice_admin_add_txq ( struct intelxl_nic *intelxl,
+ struct intelxl_ring *ring ) {
+ struct ice_admin_descriptor *cmd;
+ struct ice_admin_add_txq_params *add_txq;
+ union ice_admin_buffer *buf;
+ struct ice_context_tx *ctx;
+ struct ice_schedule_tx *sched;
+ physaddr_t address;
+ int rc;
+
+ /* Populate descriptor */
+ cmd = ice_admin_command_descriptor ( intelxl );
+ cmd->opcode = cpu_to_le16 ( ICE_ADMIN_ADD_TXQ );
+ cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF );
+ cmd->len = cpu_to_le16 ( sizeof ( buf->add_txq ) );
+ add_txq = &cmd->params.add_txq;
+ add_txq->count = 1;
+ buf = ice_admin_command_buffer ( intelxl );
+ buf->add_txq.parent = cpu_to_le32 ( intelxl->teid );
+ buf->add_txq.count = 1;
+ ctx = &buf->add_txq.ctx;
+ address = dma ( &ring->map, ring->desc.raw );
+ ctx->base_port =
+ cpu_to_le64 ( ICE_TXQ_BASE_PORT ( address, intelxl->port ) );
+ ctx->pf_type = cpu_to_le16 ( ICE_TXQ_PF_TYPE ( intelxl->pf ) );
+ ctx->vsi = cpu_to_le16 ( intelxl->vsi );
+ ctx->len = cpu_to_le16 ( ICE_TXQ_LEN ( INTELXL_TX_NUM_DESC ) );
+ ctx->flags = cpu_to_le16 ( ICE_TXQ_FL_TSO | ICE_TXQ_FL_LEGACY );
+ sched = &buf->add_txq.sched;
+ sched->sections = ( ICE_SCHEDULE_GENERIC | ICE_SCHEDULE_COMMIT |
+ ICE_SCHEDULE_EXCESS );
+ sched->commit_weight = cpu_to_le16 ( ICE_SCHEDULE_WEIGHT );
+ sched->excess_weight = cpu_to_le16 ( ICE_SCHEDULE_WEIGHT );
+
+ /* Issue command */
+ if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+ return rc;
+ DBGC ( intelxl, "ICE %p added TEID %#04x\n",
+ intelxl, le32_to_cpu ( buf->add_txq.teid ) );
+
+ return 0;
+}
+
+/**
+ * Disable transmit queue
+ *
+ * @v intelxl Intel device
+ * @v ring Descriptor ring
+ * @ret rc Return status code
+ */
+static int ice_admin_disable_txq ( struct intelxl_nic *intelxl ) {
+ struct ice_admin_descriptor *cmd;
+ struct ice_admin_disable_txq_params *disable_txq;
+ union ice_admin_buffer *buf;
+ int rc;
+
+ /* Populate descriptor */
+ cmd = ice_admin_command_descriptor ( intelxl );
+ cmd->opcode = cpu_to_le16 ( ICE_ADMIN_DISABLE_TXQ );
+ cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF );
+ cmd->len = cpu_to_le16 ( sizeof ( buf->disable_txq ) );
+ disable_txq = &cmd->params.disable_txq;
+ disable_txq->flags = ICE_TXQ_FL_FLUSH;
+ disable_txq->count = 1;
+ disable_txq->timeout = ICE_TXQ_TIMEOUT;
+ buf = ice_admin_command_buffer ( intelxl );
+ buf->disable_txq.parent = cpu_to_le32 ( intelxl->teid );
+ buf->disable_txq.count = 1;
+
+ /* Issue command */
+ if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/******************************************************************************
+ *
+ * Network device interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Dump transmit queue context (for debugging)
+ *
+ * @v intelxl Intel device
+ */
+static void ice_dump_tx ( struct intelxl_nic *intelxl ) {
+ uint32_t ctx[ sizeof ( struct ice_context_tx ) / sizeof ( uint32_t ) ];
+ uint32_t stat;
+ unsigned int i;
+
+ /* Do nothing unless debug output is enabled */
+ if ( ! DBG_EXTRA )
+ return;
+
+ /* Trigger reading of transmit context */
+ writel ( ( ICE_GLCOMM_QTX_CNTX_CTL_CMD_READ |
+ ICE_GLCOMM_QTX_CNTX_CTL_EXEC ),
+ intelxl->regs + ICE_GLCOMM_QTX_CNTX_CTL );
+
+ /* Wait for operation to complete */
+ for ( i = 0 ; i < INTELXL_CTX_MAX_WAIT_MS ; i++ ) {
+
+ /* Check if operation is complete */
+ stat = readl ( intelxl->regs + ICE_GLCOMM_QTX_CNTX_STAT );
+ if ( ! ( stat & ICE_GLCOMM_QTX_CNTX_BUSY ) )
+ break;
+
+ /* Delay */
+ mdelay ( 1 );
+ }
+
+ /* Read context registers */
+ for ( i = 0 ; i < ( sizeof ( ctx ) / sizeof ( ctx[0] ) ) ; i++ ) {
+ ctx[i] = cpu_to_le32 ( readl ( intelxl->regs +
+ ICE_GLCOMM_QTX_CNTX_DATA ( i )));
+ }
+
+ /* Dump context */
+ DBGC2 ( intelxl, "ICE %p TX context:\n", intelxl );
+ DBGC2_HDA ( intelxl, 0, ctx, sizeof ( ctx ) );
+}
+
+/**
+ * Dump receive queue context (for debugging)
+ *
+ * @v intelxl Intel device
+ */
+static void ice_dump_rx ( struct intelxl_nic *intelxl ) {
+ uint32_t ctx[ sizeof ( struct intelxl_context_rx ) /
+ sizeof ( uint32_t ) ];
+ unsigned int i;
+
+ /* Do nothing unless debug output is enabled */
+ if ( ! DBG_EXTRA )
+ return;
+
+ /* Read context registers */
+ for ( i = 0 ; i < ( sizeof ( ctx ) / sizeof ( ctx[0] ) ) ; i++ ) {
+ ctx[i] = cpu_to_le32 ( readl ( intelxl->regs +
+ ICE_QRX_CONTEXT ( i ) ) );
+ }
+
+ /* Dump context */
+ DBGC2 ( intelxl, "ICE %p RX context:\n", intelxl );
+ DBGC2_HDA ( intelxl, 0, ctx, sizeof ( ctx ) );
+}
+
+/**
+ * Create transmit queue
+ *
+ * @v intelxl Intel device
+ * @v ring Descriptor ring
+ * @ret rc Return status code
+ */
+static int ice_create_tx ( struct intelxl_nic *intelxl,
+ struct intelxl_ring *ring ) {
+ int rc;
+
+ /* Allocate descriptor ring */
+ if ( ( rc = intelxl_alloc_ring ( intelxl, ring ) ) != 0 )
+ goto err_alloc;
+
+ /* Add transmit queue */
+ if ( ( rc = ice_admin_add_txq ( intelxl, ring ) ) != 0 )
+ goto err_add_txq;
+
+ return 0;
+
+ err_add_txq:
+ intelxl_free_ring ( intelxl, ring );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Destroy transmit queue
+ *
+ * @v intelxl Intel device
+ * @v ring Descriptor ring
+ * @ret rc Return status code
+ */
+static void ice_destroy_tx ( struct intelxl_nic *intelxl,
+ struct intelxl_ring *ring ) {
+ int rc;
+
+ /* Disable transmit queue */
+ if ( ( rc = ice_admin_disable_txq ( intelxl ) ) != 0 ) {
+ /* Leak memory; there's nothing else we can do */
+ return;
+ }
+
+ /* Free descriptor ring */
+ intelxl_free_ring ( intelxl, ring );
+}
+
+/**
+ * Program receive queue context
+ *
+ * @v intelxl Intel device
+ * @v address Descriptor ring base address
+ * @ret rc Return status code
+ */
+static int ice_context_rx ( struct intelxl_nic *intelxl,
+ physaddr_t address ) {
+ union {
+ struct intelxl_context_rx rx;
+ uint32_t raw[ sizeof ( struct intelxl_context_rx ) /
+ sizeof ( uint32_t ) ];
+ } ctx;
+ uint64_t base_count;
+ unsigned int i;
+
+ /* Initialise context */
+ memset ( &ctx, 0, sizeof ( ctx ) );
+ base_count = INTELXL_CTX_RX_BASE_COUNT ( address, INTELXL_RX_NUM_DESC );
+ ctx.rx.base_count = cpu_to_le64 ( base_count );
+ ctx.rx.len = cpu_to_le16 ( INTELXL_CTX_RX_LEN ( intelxl->mfs ) );
+ ctx.rx.flags = ( INTELXL_CTX_RX_FL_DSIZE | INTELXL_CTX_RX_FL_CRCSTRIP );
+ ctx.rx.mfs = cpu_to_le16 ( INTELXL_CTX_RX_MFS ( intelxl->mfs ) );
+
+ /* Write context registers */
+ for ( i = 0 ; i < ( sizeof ( ctx ) / sizeof ( ctx.raw[0] ) ) ; i++ ) {
+ writel ( le32_to_cpu ( ctx.raw[i] ),
+ ( intelxl->regs + ICE_QRX_CONTEXT ( i ) ) );
+ }
+
+ return 0;
+}
+
+/**
+ * Open network device
+ *
+ * @v netdev Network device
+ * @ret rc Return status code
+ */
+static int ice_open ( struct net_device *netdev ) {
+ struct intelxl_nic *intelxl = netdev->priv;
+ int rc;
+
+ /* Calculate maximum frame size */
+ intelxl->mfs = ( ( ETH_HLEN + netdev->mtu + 4 /* CRC */ +
+ INTELXL_ALIGN - 1 ) & ~( INTELXL_ALIGN - 1 ) );
+
+ /* Set MAC address */
+ if ( ( rc = ice_admin_mac_write ( netdev ) ) != 0 )
+ goto err_mac_write;
+
+ /* Set maximum frame size */
+ if ( ( rc = intelxl_admin_mac_config ( intelxl ) ) != 0 )
+ goto err_mac_config;
+
+ /* Create receive descriptor ring */
+ if ( ( rc = intelxl_create_ring ( intelxl, &intelxl->rx ) ) != 0 )
+ goto err_create_rx;
+
+ /* Create transmit descriptor ring */
+ if ( ( rc = ice_create_tx ( intelxl, &intelxl->tx ) ) != 0 )
+ goto err_create_tx;
+
+ /* Restart autonegotiation */
+ ice_admin_autoneg ( intelxl );
+
+ /* Update link state */
+ ice_admin_link ( netdev );
+
+ return 0;
+
+ ice_destroy_tx ( intelxl, &intelxl->tx );
+ err_create_tx:
+ intelxl_destroy_ring ( intelxl, &intelxl->rx );
+ err_create_rx:
+ err_mac_config:
+ err_mac_write:
+ return rc;
+}
+
+/**
+ * Close network device
+ *
+ * @v netdev Network device
+ */
+static void ice_close ( struct net_device *netdev ) {
+ struct intelxl_nic *intelxl = netdev->priv;
+
+ /* Dump contexts (for debugging) */
+ ice_dump_tx ( intelxl );
+ ice_dump_rx ( intelxl );
+
+ /* Destroy transmit descriptor ring */
+ ice_destroy_tx ( intelxl, &intelxl->tx );
+
+ /* Destroy receive descriptor ring */
+ intelxl_destroy_ring ( intelxl, &intelxl->rx );
+
+ /* Discard any unused receive buffers */
+ intelxl_empty_rx ( intelxl );
+}
+
+/** Network device operations */
+static struct net_device_operations ice_operations = {
+ .open = ice_open,
+ .close = ice_close,
+ .transmit = intelxl_transmit,
+ .poll = intelxl_poll,
+};
+
+/******************************************************************************
+ *
+ * PCI interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Probe PCI device
+ *
+ * @v pci PCI device
+ * @ret rc Return status code
+ */
+static int ice_probe ( struct pci_device *pci ) {
+ struct net_device *netdev;
+ struct intelxl_nic *intelxl;
+ uint32_t pffunc_rid;
+ uint32_t pfgen_portnum;
+ int rc;
+
+ /* Allocate and initialise net device */
+ netdev = alloc_etherdev ( sizeof ( *intelxl ) );
+ if ( ! netdev ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ netdev_init ( netdev, &ice_operations );
+ netdev->max_pkt_len = INTELXL_MAX_PKT_LEN;
+ intelxl = netdev->priv;
+ pci_set_drvdata ( pci, netdev );
+ netdev->dev = &pci->dev;
+ memset ( intelxl, 0, sizeof ( *intelxl ) );
+ intelxl->intr = ICE_GLINT_DYN_CTL;
+ intelxl->handle = ice_admin_event;
+ intelxl_init_admin ( &intelxl->command, INTELXL_ADMIN_CMD,
+ &intelxl_admin_offsets );
+ intelxl_init_admin ( &intelxl->event, INTELXL_ADMIN_EVT,
+ &intelxl_admin_offsets );
+ intelxl_init_ring ( &intelxl->tx, INTELXL_TX_NUM_DESC,
+ sizeof ( intelxl->tx.desc.tx[0] ), NULL );
+ intelxl_init_ring ( &intelxl->rx, INTELXL_RX_NUM_DESC,
+ sizeof ( intelxl->rx.desc.rx[0] ),
+ ice_context_rx );
+
+ /* Fix up PCI device */
+ adjust_pci_device ( pci );
+
+ /* Map registers */
+ intelxl->regs = pci_ioremap ( pci, pci->membase, ICE_BAR_SIZE );
+ if ( ! intelxl->regs ) {
+ rc = -ENODEV;
+ goto err_ioremap;
+ }
+
+ /* Configure DMA */
+ intelxl->dma = &pci->dma;
+ dma_set_mask_64bit ( intelxl->dma );
+ netdev->dma = intelxl->dma;
+
+ /* Locate PCI Express capability */
+ intelxl->exp = pci_find_capability ( pci, PCI_CAP_ID_EXP );
+ if ( ! intelxl->exp ) {
+ DBGC ( intelxl, "ICE %p missing PCIe capability\n",
+ intelxl );
+ rc = -ENXIO;
+ goto err_exp;
+ }
+
+ /* Reset the function via PCIe FLR */
+ pci_reset ( pci, intelxl->exp );
+
+ /* Get function and port number */
+ pffunc_rid = readl ( intelxl->regs + ICE_PFFUNC_RID );
+ intelxl->pf = ICE_PFFUNC_RID_FUNC_NUM ( pffunc_rid );
+ pfgen_portnum = readl ( intelxl->regs + ICE_PFGEN_PORTNUM );
+ intelxl->port = ICE_PFGEN_PORTNUM_PORT_NUM ( pfgen_portnum );
+ DBGC ( intelxl, "ICE %p PF %d using port %d\n",
+ intelxl, intelxl->pf, intelxl->port );
+
+ /* Enable MSI-X dummy interrupt */
+ if ( ( rc = intelxl_msix_enable ( intelxl, pci,
+ INTELXL_MSIX_VECTOR ) ) != 0 )
+ goto err_msix;
+
+ /* Open admin queues */
+ if ( ( rc = intelxl_open_admin ( intelxl ) ) != 0 )
+ goto err_open_admin;
+
+ /* Get firmware version */
+ if ( ( rc = ice_admin_version ( intelxl ) ) != 0 )
+ goto err_admin_version;
+
+ /* Clear PXE mode */
+ if ( ( rc = intelxl_admin_clear_pxe ( intelxl ) ) != 0 )
+ goto err_admin_clear_pxe;
+
+ /* Get switch configuration */
+ if ( ( rc = ice_admin_switch ( intelxl ) ) != 0 )
+ goto err_admin_switch;
+
+ /* Add broadcast address */
+ if ( ( rc = ice_admin_rules ( intelxl, eth_broadcast ) ) != 0 )
+ goto err_admin_rules_broadcast;
+
+ /* Add promiscuous unicast address */
+ if ( ( rc = ice_admin_rules ( intelxl, ice_magic_mac ) ) != 0 )
+ goto err_admin_rules_magic;
+
+ /* Query scheduler topology */
+ if ( ( rc = ice_admin_schedule ( intelxl ) ) != 0 )
+ goto err_admin_schedule;
+
+ /* Get MAC address */
+ if ( ( rc = ice_admin_mac_read ( netdev ) ) != 0 )
+ goto err_admin_mac_read;
+
+ /* Configure queue register addresses */
+ intelxl->tx.tail = ICE_QTX_COMM_DBELL;
+ intelxl->rx.reg = ICE_QRX_CTRL;
+ intelxl->rx.tail = ICE_QRX_TAIL;
+
+ /* Configure interrupt causes */
+ writel ( ( ICE_QINT_TQCTL_ITR_INDX_NONE | ICE_QINT_TQCTL_CAUSE_ENA ),
+ intelxl->regs + ICE_QINT_TQCTL );
+ writel ( ( ICE_QINT_RQCTL_ITR_INDX_NONE | ICE_QINT_RQCTL_CAUSE_ENA ),
+ intelxl->regs + ICE_QINT_RQCTL );
+
+ /* Set a default value for the queue context flex extension,
+ * since this register erroneously retains its value across at
+ * least a PCIe FLR.
+ */
+ writel ( ( ICE_QRX_FLXP_CNTXT_RXDID_IDX_LEGACY_32 |
+ ICE_QRX_FLXP_CNTXT_RXDID_PRIO_MAX ),
+ intelxl->regs + ICE_QRX_FLXP_CNTXT );
+
+ /* Register network device */
+ if ( ( rc = register_netdev ( netdev ) ) != 0 )
+ goto err_register_netdev;
+
+ /* Set initial link state */
+ ice_admin_link ( netdev );
+
+ return 0;
+
+ unregister_netdev ( netdev );
+ err_register_netdev:
+ err_admin_mac_read:
+ err_admin_schedule:
+ err_admin_rules_magic:
+ err_admin_rules_broadcast:
+ err_admin_switch:
+ err_admin_clear_pxe:
+ err_admin_version:
+ intelxl_close_admin ( intelxl );
+ err_open_admin:
+ intelxl_msix_disable ( intelxl, pci, INTELXL_MSIX_VECTOR );
+ err_msix:
+ pci_reset ( pci, intelxl->exp );
+ err_exp:
+ iounmap ( intelxl->regs );
+ err_ioremap:
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Remove PCI device
+ *
+ * @v pci PCI device
+ */
+static void ice_remove ( struct pci_device *pci ) {
+ struct net_device *netdev = pci_get_drvdata ( pci );
+ struct intelxl_nic *intelxl = netdev->priv;
+
+ /* Unregister network device */
+ unregister_netdev ( netdev );
+
+ /* Close admin queues */
+ intelxl_close_admin ( intelxl );
+
+ /* Disable MSI-X dummy interrupt */
+ intelxl_msix_disable ( intelxl, pci, INTELXL_MSIX_VECTOR );
+
+ /* Reset the NIC */
+ pci_reset ( pci, intelxl->exp );
+
+ /* Free network device */
+ iounmap ( intelxl->regs );
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+}
+
+/** PCI device IDs */
+static struct pci_device_id ice_nics[] = {
+ PCI_ROM ( 0x8086, 0x124c, "e823l-bp", "E823-L backplane", 0 ),
+ PCI_ROM ( 0x8086, 0x124d, "e823l-sfp", "E823-L SFP", 0 ),
+ PCI_ROM ( 0x8086, 0x124e, "e823l-10gt", "E823-L 10GBASE-T", 0 ),
+ PCI_ROM ( 0x8086, 0x124f, "e823l-1g", "E823-L 1GbE", 0 ),
+ PCI_ROM ( 0x8086, 0x151d, "e823l-qsfp", "E823-L QSFP", 0 ),
+ PCI_ROM ( 0x8086, 0x1591, "e810c-bp", "E810-C backplane", 0 ),
+ PCI_ROM ( 0x8086, 0x1592, "e810c-qsfp", "E810-C QSFP", 0 ),
+ PCI_ROM ( 0x8086, 0x1593, "e810c-sfp", "E810-C SFP", 0 ),
+ PCI_ROM ( 0x8086, 0x1599, "e810-xxv-bp", "E810-XXV backplane", 0 ),
+ PCI_ROM ( 0x8086, 0x159a, "e810-xxv-qsfp", "E810-XXV QSFP", 0 ),
+ PCI_ROM ( 0x8086, 0x159b, "e810-xxv-sfp", "E810-XXV SFP", 0 ),
+ PCI_ROM ( 0x8086, 0x188a, "e823c-bp", "E823-C backplane", 0 ),
+ PCI_ROM ( 0x8086, 0x188b, "e823c-qsfp", "E823-C QSFP", 0 ),
+ PCI_ROM ( 0x8086, 0x188c, "e823c-sfp", "E823-C SFP", 0 ),
+ PCI_ROM ( 0x8086, 0x188d, "e823c-10gt", "E823-C 10GBASE-T", 0 ),
+ PCI_ROM ( 0x8086, 0x188e, "e823c-1g", "E823-C 1GbE", 0 ),
+ PCI_ROM ( 0x8086, 0x1890, "e822c-bp", "E822-C backplane", 0 ),
+ PCI_ROM ( 0x8086, 0x1891, "e822c-qsfp", "E822-C QSFP", 0 ),
+ PCI_ROM ( 0x8086, 0x1892, "e822c-sfp", "E822-C SFP", 0 ),
+ PCI_ROM ( 0x8086, 0x1893, "e822c-10gt", "E822-C 10GBASE-T", 0 ),
+ PCI_ROM ( 0x8086, 0x1894, "e822c-1g", "E822-C 1GbE", 0 ),
+ PCI_ROM ( 0x8086, 0x1897, "e822l-bp", "E822-L backplane", 0 ),
+ PCI_ROM ( 0x8086, 0x1898, "e822l-sfp", "E822-L SFP", 0 ),
+ PCI_ROM ( 0x8086, 0x1899, "e822l-10gt", "E822-L 10GBASE-T", 0 ),
+ PCI_ROM ( 0x8086, 0x189a, "e822l-1g", "E822-L 1GbE", 0 ),
+};
+
+/** PCI driver */
+struct pci_driver ice_driver __pci_driver = {
+ .ids = ice_nics,
+ .id_count = ( sizeof ( ice_nics ) / sizeof ( ice_nics[0] ) ),
+ .probe = ice_probe,
+ .remove = ice_remove,
+};