]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[intelxl] Add driver for Intel 40 Gigabit Ethernet NIC virtual functions
authorMichael Brown <mcb30@ipxe.org>
Wed, 24 Apr 2019 16:15:49 +0000 (17:15 +0100)
committerMichael Brown <mcb30@ipxe.org>
Sat, 27 Apr 2019 19:26:18 +0000 (20:26 +0100)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/drivers/net/intelxl.h
src/drivers/net/intelxlvf.c [new file with mode: 0644]
src/drivers/net/intelxlvf.h [new file with mode: 0644]
src/include/ipxe/errfile.h

index 4144774349635286534dd08ae7755c9912a6a985..80586cef05f576033d49b7ba9a5df1def348fc96 100644 (file)
@@ -20,9 +20,9 @@ struct intelxl_nic;
 
 /** Alignment
  *
- * No data structure requires greater than 128 byte alignment.
+ * No data structure requires greater than 256 byte alignment.
  */
-#define INTELXL_ALIGN 128
+#define INTELXL_ALIGN 256
 
 /******************************************************************************
  *
@@ -310,6 +310,166 @@ struct intelxl_admin_link_params {
 /** Admin queue Send Message to VF command */
 #define INTELXL_ADMIN_SEND_TO_VF 0x0802
 
+/** Admin Queue VF Reset opcode */
+#define INTELXL_ADMIN_VF_RESET 0x00000002
+
+/** Admin Queue VF Get Resources opcode */
+#define INTELXL_ADMIN_VF_GET_RESOURCES 0x00000003
+
+/** Admin Queue VF Get Resources data buffer */
+struct intelxl_admin_vf_get_resources_buffer {
+       /** Reserved */
+       uint8_t reserved_a[20];
+       /** VSI switching element ID */
+       uint16_t vsi;
+       /** Reserved */
+       uint8_t reserved_b[8];
+       /** MAC address */
+       uint8_t mac[ETH_ALEN];
+} __attribute__ (( packed ));
+
+/** Admin Queue VF Status Change Event opcode */
+#define INTELXL_ADMIN_VF_STATUS 0x00000011
+
+/** Link status change event type */
+#define INTELXL_ADMIN_VF_STATUS_LINK 0x00000001
+
+/** Link status change event data */
+struct intelxl_admin_vf_status_link {
+       /** Link speed */
+       uint32_t speed;
+       /** Link status */
+       uint8_t status;
+       /** Reserved */
+       uint8_t reserved[3];
+} __attribute__ (( packed ));
+
+/** Admin Queue VF Status Change Event data buffer */
+struct intelxl_admin_vf_status_buffer {
+       /** Event type */
+       uint32_t event;
+       /** Event data */
+       union {
+               /** Link change event data */
+               struct intelxl_admin_vf_status_link link;
+       } data;
+       /** Reserved */
+       uint8_t reserved[4];
+} __attribute__ (( packed ));
+
+/** Admin Queue VF Configure Queues opcode */
+#define INTELXL_ADMIN_VF_CONFIGURE 0x00000006
+
+/** Admin Queue VF Configure Queues data buffer */
+struct intelxl_admin_vf_configure_buffer {
+       /** VSI switching element ID */
+       uint16_t vsi;
+       /** Number of queue pairs */
+       uint16_t count;
+       /** Reserved */
+       uint8_t reserved_a[4];
+       /** Transmit queue */
+       struct {
+               /** VSI switching element ID */
+               uint16_t vsi;
+               /** Queue ID */
+               uint16_t id;
+               /** Queue count */
+               uint16_t count;
+               /** Reserved */
+               uint8_t reserved_a[2];
+               /** Base address */
+               uint64_t base;
+               /** Reserved */
+               uint8_t reserved_b[8];
+       } __attribute__ (( packed )) tx;
+       /** Receive queue */
+       struct {
+               /** VSI switching element ID */
+               uint16_t vsi;
+               /** Queue ID */
+               uint16_t id;
+               /** Queue count */
+               uint32_t count;
+               /** Reserved */
+               uint8_t reserved_a[4];
+               /** Data buffer length */
+               uint32_t len;
+               /** Maximum frame size */
+               uint32_t mfs;
+               /** Reserved */
+               uint8_t reserved_b[4];
+               /** Base address */
+               uint64_t base;
+               /** Reserved */
+               uint8_t reserved_c[8];
+       } __attribute__ (( packed )) rx;
+       /** Reserved
+        *
+        * This field exists only due to a bug in the PF driver's
+        * message validation logic, which causes it to miscalculate
+        * the expected message length.
+        */
+       uint8_t reserved_b[64];
+} __attribute__ (( packed ));
+
+/** Admin Queue VF IRQ Map opcode */
+#define INTELXL_ADMIN_VF_IRQ_MAP 0x00000007
+
+/** Admin Queue VF IRQ Map data buffer */
+struct intelxl_admin_vf_irq_map_buffer {
+       /** Number of interrupt vectors */
+       uint16_t count;
+       /** VSI switching element ID */
+       uint16_t vsi;
+       /** Interrupt vector ID */
+       uint16_t vec;
+       /** Receive queue bitmap */
+       uint16_t rxmap;
+       /** Transmit queue bitmap */
+       uint16_t txmap;
+       /** Receive interrupt throttling index */
+       uint16_t rxitr;
+       /** Transmit interrupt throttling index */
+       uint16_t txitr;
+       /** Reserved
+        *
+        * This field exists only due to a bug in the PF driver's
+        * message validation logic, which causes it to miscalculate
+        * the expected message length.
+        */
+       uint8_t reserved[12];
+} __attribute__ (( packed ));
+
+/** Admin Queue VF Enable Queues opcode */
+#define INTELXL_ADMIN_VF_ENABLE 0x00000008
+
+/** Admin Queue VF Disable Queues opcode */
+#define INTELXL_ADMIN_VF_DISABLE 0x00000009
+
+/** Admin Queue VF Enable/Disable Queues data buffer */
+struct intelxl_admin_vf_queues_buffer {
+       /** VSI switching element ID */
+       uint16_t vsi;
+       /** Reserved */
+       uint8_t reserved[2];
+       /** Receive queue bitmask */
+       uint32_t rx;
+       /** Transmit queue bitmask */
+       uint32_t tx;
+} __attribute__ (( packed ));
+
+/** Admin Queue VF Configure Promiscuous Mode opcode */
+#define INTELXL_ADMIN_VF_PROMISC 0x0000000e
+
+/** Admin Queue VF Configure Promiscuous Mode data buffer */
+struct intelxl_admin_vf_promisc_buffer {
+       /** VSI switching element ID */
+       uint16_t vsi;
+       /** Flags */
+       uint16_t flags;
+} __attribute__ (( packed ));
+
 /** Admin queue command parameters */
 union intelxl_admin_params {
        /** Additional data buffer command parameters */
@@ -342,6 +502,18 @@ union intelxl_admin_buffer {
        struct intelxl_admin_switch_buffer sw;
        /** Get VSI Parameters data buffer */
        struct intelxl_admin_vsi_buffer vsi;
+       /** VF Get Resources data buffer */
+       struct intelxl_admin_vf_get_resources_buffer res;
+       /** VF Status Change Event data buffer */
+       struct intelxl_admin_vf_status_buffer stat;
+       /** VF Configure Queues data buffer */
+       struct intelxl_admin_vf_configure_buffer cfg;
+       /** VF Enable/Disable Queues data buffer */
+       struct intelxl_admin_vf_queues_buffer queues;
+       /** VF Configure Promiscuous Mode data buffer */
+       struct intelxl_admin_vf_promisc_buffer promisc;
+       /*** VF IRQ Map data buffer */
+       struct intelxl_admin_vf_irq_map_buffer irq;
        /** Alignment padding */
        uint8_t pad[INTELXL_ALIGN];
 } __attribute__ (( packed ));
@@ -867,12 +1039,21 @@ struct intelxl_nic {
        struct pci_msix msix;
        /** MSI-X dummy interrupt target */
        uint32_t msg;
+       /** PCI Express capability offset */
+       unsigned int exp;
 
        /** Admin command queue */
        struct intelxl_admin command;
        /** Admin event queue */
        struct intelxl_admin event;
 
+       /** Current VF opcode */
+       unsigned int vopcode;
+       /** Current VF return value */
+       int vret;
+       /** Current VF event data buffer */
+       union intelxl_admin_buffer vbuf;
+
        /** Transmit descriptor ring */
        struct intelxl_ring tx;
        /** Receive descriptor ring */
diff --git a/src/drivers/net/intelxlvf.c b/src/drivers/net/intelxlvf.c
new file mode 100644 (file)
index 0000000..8f76daf
--- /dev/null
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2019 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 <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <ipxe/pci.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/ethernet.h>
+#include "intelxlvf.h"
+
+/** @file
+ *
+ * Intel 40 Gigabit Ethernet virtual function network card driver
+ *
+ */
+
+/******************************************************************************
+ *
+ * Device reset
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Reset hardware via PCIe function-level reset
+ *
+ * @v intelxl          Intel device
+ */
+static void intelxlvf_reset_flr ( struct intelxl_nic *intelxl,
+                                 struct pci_device *pci ) {
+       uint16_t control;
+
+       /* Perform a PCIe function-level reset */
+       pci_read_config_word ( pci, ( intelxl->exp + PCI_EXP_DEVCTL ),
+                              &control );
+       pci_write_config_word ( pci, ( intelxl->exp + PCI_EXP_DEVCTL ),
+                               ( control | PCI_EXP_DEVCTL_FLR ) );
+       mdelay ( INTELXL_RESET_DELAY_MS );
+}
+
+/**
+ * Wait for admin event queue to be torn down
+ *
+ * @v intelxl          Intel device
+ * @ret rc             Return status code
+ */
+static int intelxlvf_reset_wait_teardown ( struct intelxl_nic *intelxl ) {
+       uint32_t admin_evt_len;
+       unsigned int i;
+
+       /* Wait for admin event queue to be torn down */
+       for ( i = 0 ; i < INTELXLVF_RESET_MAX_WAIT_MS ; i++ ) {
+
+               /* Check admin event queue length register */
+               admin_evt_len = readl ( intelxl->regs + INTELXLVF_ADMIN +
+                                       INTELXLVF_ADMIN_EVT_LEN );
+               if ( ! ( admin_evt_len & INTELXL_ADMIN_LEN_ENABLE ) )
+                       return 0;
+
+               /* Delay */
+               mdelay ( 1 );
+       }
+
+       DBGC ( intelxl, "INTELXL %p timed out waiting for teardown (%#08x)\n",
+              intelxl, admin_evt_len );
+       return -ETIMEDOUT;
+}
+
+/**
+ * Wait for virtual function to be marked as active
+ *
+ * @v intelxl          Intel device
+ * @ret rc             Return status code
+ */
+static int intelxlvf_reset_wait_active ( struct intelxl_nic *intelxl ) {
+       uint32_t vfgen_rstat;
+       unsigned int vfr_state;
+       unsigned int i;
+
+       /* Wait for virtual function to be marked as active */
+       for ( i = 0 ; i < INTELXLVF_RESET_MAX_WAIT_MS ; i++ ) {
+
+               /* Check status as written by physical function driver */
+               vfgen_rstat = readl ( intelxl->regs + INTELXLVF_VFGEN_RSTAT );
+               vfr_state = INTELXLVF_VFGEN_RSTAT_VFR_STATE ( vfgen_rstat );
+               if ( vfr_state == INTELXLVF_VFGEN_RSTAT_VFR_STATE_ACTIVE )
+                       return 0;
+
+               /* Delay */
+               mdelay ( 1 );
+       }
+
+       DBGC ( intelxl, "INTELXL %p timed out waiting for activation "
+              "(%#08x)\n", intelxl, vfgen_rstat );
+       return -ETIMEDOUT;
+}
+
+/**
+ * Reset hardware via admin queue
+ *
+ * @v intelxl          Intel device
+ * @ret rc             Return status code
+ */
+static int intelxlvf_reset_admin ( struct intelxl_nic *intelxl ) {
+       struct intelxl_admin_descriptor *cmd;
+       int rc;
+
+       /* Populate descriptor */
+       cmd = intelxl_admin_command_descriptor ( intelxl );
+       cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_SEND_TO_PF );
+       cmd->vopcode = cpu_to_le32 ( INTELXL_ADMIN_VF_RESET );
+
+       /* Issue command */
+       if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+               goto err_command;
+
+       /* Wait for minimum reset time */
+       mdelay ( INTELXL_RESET_DELAY_MS );
+
+       /* Wait for reset to take effect */
+       if ( ( rc = intelxlvf_reset_wait_teardown ( intelxl ) ) != 0 )
+               goto err_teardown;
+
+       /* Wait for virtual function to become active */
+       if ( ( rc = intelxlvf_reset_wait_active ( intelxl ) ) != 0 )
+               goto err_active;
+
+ err_active:
+ err_teardown:
+       intelxl_reopen_admin ( intelxl );
+ err_command:
+       return rc;
+}
+
+/******************************************************************************
+ *
+ * Admin queue
+ *
+ ******************************************************************************
+ */
+
+/** Admin command queue register offsets */
+static const struct intelxl_admin_offsets intelxlvf_admin_command_offsets = {
+       .bal = INTELXLVF_ADMIN_CMD_BAL,
+       .bah = INTELXLVF_ADMIN_CMD_BAH,
+       .len = INTELXLVF_ADMIN_CMD_LEN,
+       .head = INTELXLVF_ADMIN_CMD_HEAD,
+       .tail = INTELXLVF_ADMIN_CMD_TAIL,
+};
+
+/** Admin event queue register offsets */
+static const struct intelxl_admin_offsets intelxlvf_admin_event_offsets = {
+       .bal = INTELXLVF_ADMIN_EVT_BAL,
+       .bah = INTELXLVF_ADMIN_EVT_BAH,
+       .len = INTELXLVF_ADMIN_EVT_LEN,
+       .head = INTELXLVF_ADMIN_EVT_HEAD,
+       .tail = INTELXLVF_ADMIN_EVT_TAIL,
+};
+
+/**
+ * Issue admin queue virtual function command
+ *
+ * @v netdev           Network device
+ * @ret rc             Return status code
+ */
+static int intelxlvf_admin_command ( struct net_device *netdev ) {
+       struct intelxl_nic *intelxl = netdev->priv;
+       struct intelxl_admin *admin = &intelxl->command;
+       struct intelxl_admin_descriptor *cmd;
+       unsigned int i;
+       int rc;
+
+       /* Populate descriptor */
+       cmd = &admin->desc[ admin->index % INTELXL_ADMIN_NUM_DESC ];
+       cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_SEND_TO_PF );
+
+       /* Record opcode */
+       intelxl->vopcode = le32_to_cpu ( cmd->vopcode );
+
+       /* Issue command */
+       if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 )
+               goto err_command;
+
+       /* Wait for response */
+       for ( i = 0 ; i < INTELXLVF_ADMIN_MAX_WAIT_MS ; i++ ) {
+
+               /* Poll admin event queue */
+               intelxl_poll_admin ( netdev );
+
+               /* If response has not arrived, delay 1ms and retry */
+               if ( intelxl->vopcode ) {
+                       mdelay ( 1 );
+                       continue;
+               }
+
+               /* Check for errors */
+               if ( intelxl->vret != 0 )
+                       return -EIO;
+
+               return 0;
+       }
+
+       rc = -ETIMEDOUT;
+       DBGC ( intelxl, "INTELXL %p timed out waiting for admin VF command "
+              "%#x\n", intelxl, intelxl->vopcode );
+ err_command:
+       intelxl->vopcode = 0;
+       return rc;
+}
+
+/**
+ * Handle link status event
+ *
+ * @v netdev           Network device
+ * @v link             Link status
+ */
+static void intelxlvf_admin_link ( struct net_device *netdev,
+                                  struct intelxl_admin_vf_status_link *link ) {
+       struct intelxl_nic *intelxl = netdev->priv;
+
+       DBGC ( intelxl, "INTELXL %p link %#02x speed %#02x\n", intelxl,
+              link->status, link->speed );
+
+       /* Update network device */
+       if ( link->status ) {
+               netdev_link_up ( netdev );
+       } else {
+               netdev_link_down ( netdev );
+       }
+}
+
+/**
+ * Handle status change event
+ *
+ * @v netdev           Network device
+ * @v stat             Status change event
+ */
+static void
+intelxlvf_admin_status ( struct net_device *netdev,
+                        struct intelxl_admin_vf_status_buffer *stat ) {
+       struct intelxl_nic *intelxl = netdev->priv;
+
+       /* Handle event */
+       switch ( stat->event ) {
+       case cpu_to_le32 ( INTELXL_ADMIN_VF_STATUS_LINK ):
+               intelxlvf_admin_link ( netdev, &stat->data.link );
+               break;
+       default:
+               DBGC ( intelxl, "INTELXL %p unrecognised status change "
+                      "event %#x:\n", intelxl, le32_to_cpu ( stat->event ) );
+               DBGC_HDA ( intelxl, 0, stat, sizeof ( *stat ) );
+               break;
+       }
+}
+
+/**
+ * Handle virtual function event
+ *
+ * @v netdev           Network device
+ * @v evt              Admin queue event descriptor
+ * @v buf              Admin queue event data buffer
+ */
+void intelxlvf_admin_event ( struct net_device *netdev,
+                            struct intelxl_admin_descriptor *evt,
+                            union intelxl_admin_buffer *buf ) {
+       struct intelxl_nic *intelxl = netdev->priv;
+       unsigned int vopcode = le32_to_cpu ( evt->vopcode );
+
+       /* Record command response if applicable */
+       if ( vopcode == intelxl->vopcode ) {
+               memcpy ( &intelxl->vbuf, buf, sizeof ( intelxl->vbuf ) );
+               intelxl->vopcode = 0;
+               intelxl->vret = le32_to_cpu ( evt->vret );
+               if ( intelxl->vret != 0 ) {
+                       DBGC ( intelxl, "INTELXL %p admin VF command %#x "
+                              "error %d\n", intelxl, vopcode, intelxl->vret );
+                       DBGC_HDA ( intelxl, virt_to_bus ( evt ), evt,
+                                  sizeof ( *evt ) );
+                       DBGC_HDA ( intelxl, virt_to_bus ( buf ), buf,
+                                  le16_to_cpu ( evt->len ) );
+               }
+               return;
+       }
+
+       /* Handle unsolicited events */
+       switch ( vopcode ) {
+       case INTELXL_ADMIN_VF_STATUS:
+               intelxlvf_admin_status ( netdev, &buf->stat );
+               break;
+       default:
+               DBGC ( intelxl, "INTELXL %p unrecognised VF event %#x:\n",
+                      intelxl, vopcode );
+               DBGC_HDA ( intelxl, 0, evt, sizeof ( *evt ) );
+               DBGC_HDA ( intelxl, 0, buf, le16_to_cpu ( evt->len ) );
+               break;
+       }
+}
+
+/**
+ * Get resources
+ *
+ * @v netdev           Network device
+ * @ret rc             Return status code
+ */
+static int intelxlvf_admin_get_resources ( struct net_device *netdev ) {
+       struct intelxl_nic *intelxl = netdev->priv;
+       struct intelxl_admin_descriptor *cmd;
+       struct intelxl_admin_vf_get_resources_buffer *res;
+       int rc;
+
+       /* Populate descriptor */
+       cmd = intelxl_admin_command_descriptor ( intelxl );
+       cmd->vopcode = cpu_to_le32 ( INTELXL_ADMIN_VF_GET_RESOURCES );
+
+       /* Issue command */
+       if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 )
+               return rc;
+
+       /* Parse response */
+       res = &intelxl->vbuf.res;
+       intelxl->vsi = le16_to_cpu ( res->vsi );
+       memcpy ( netdev->hw_addr, res->mac, ETH_ALEN );
+       DBGC ( intelxl, "INTELXL %p VSI %#04x\n", intelxl, intelxl->vsi );
+
+       return 0;
+}
+
+/******************************************************************************
+ *
+ * Network device interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Configure queues
+ *
+ * @v netdev           Network device
+ * @ret rc             Return status code
+ */
+static int intelxlvf_admin_configure ( struct net_device *netdev ) {
+       struct intelxl_nic *intelxl = netdev->priv;
+       struct intelxl_admin_descriptor *cmd;
+       union intelxl_admin_buffer *buf;
+       int rc;
+
+       /* Populate descriptor */
+       cmd = intelxl_admin_command_descriptor ( intelxl );
+       cmd->vopcode = cpu_to_le32 ( INTELXL_ADMIN_VF_CONFIGURE );
+       cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF );
+       cmd->len = cpu_to_le16 ( sizeof ( buf->cfg ) );
+       buf = intelxl_admin_command_buffer ( intelxl );
+       buf->cfg.vsi = cpu_to_le16 ( intelxl->vsi );
+       buf->cfg.count = cpu_to_le16 ( 1 );
+       buf->cfg.tx.vsi = cpu_to_le16 ( intelxl->vsi );
+       buf->cfg.tx.count = cpu_to_le16 ( INTELXL_TX_NUM_DESC );
+       buf->cfg.tx.base = cpu_to_le64 ( virt_to_bus ( intelxl->tx.desc.raw ) );
+       buf->cfg.rx.vsi = cpu_to_le16 ( intelxl->vsi );
+       buf->cfg.rx.count = cpu_to_le32 ( INTELXL_RX_NUM_DESC );
+       buf->cfg.rx.len = cpu_to_le32 ( intelxl->mfs );
+       buf->cfg.rx.mfs = cpu_to_le32 ( intelxl->mfs );
+       buf->cfg.rx.base = cpu_to_le64 ( virt_to_bus ( intelxl->rx.desc.raw ) );
+
+       /* Issue command */
+       if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Configure IRQ mapping
+ *
+ * @v netdev           Network device
+ * @ret rc             Return status code
+ */
+static int intelxlvf_admin_irq_map ( struct net_device *netdev ) {
+       struct intelxl_nic *intelxl = netdev->priv;
+       struct intelxl_admin_descriptor *cmd;
+       union intelxl_admin_buffer *buf;
+       int rc;
+
+       /* Populate descriptor */
+       cmd = intelxl_admin_command_descriptor ( intelxl );
+       cmd->vopcode = cpu_to_le32 ( INTELXL_ADMIN_VF_IRQ_MAP );
+       cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF );
+       cmd->len = cpu_to_le16 ( sizeof ( buf->irq ) );
+       buf = intelxl_admin_command_buffer ( intelxl );
+       buf->irq.count = cpu_to_le16 ( 1 );
+       buf->irq.vsi = cpu_to_le16 ( intelxl->vsi );
+       buf->irq.rxmap = cpu_to_le16 ( 0x0001 );
+       buf->irq.txmap = cpu_to_le16 ( 0x0001 );
+
+       /* Issue command */
+       if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Enable/disable queues
+ *
+ * @v netdev           Network device
+ * @v enable           Enable queues
+ * @ret rc             Return status code
+ */
+static int intelxlvf_admin_queues ( struct net_device *netdev, int enable ) {
+       struct intelxl_nic *intelxl = netdev->priv;
+       struct intelxl_admin_descriptor *cmd;
+       union intelxl_admin_buffer *buf;
+       int rc;
+
+       /* Populate descriptor */
+       cmd = intelxl_admin_command_descriptor ( intelxl );
+       cmd->vopcode = ( enable ? cpu_to_le32 ( INTELXL_ADMIN_VF_ENABLE ) :
+                        cpu_to_le32 ( INTELXL_ADMIN_VF_DISABLE ) );
+       cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF );
+       cmd->len = cpu_to_le16 ( sizeof ( buf->queues ) );
+       buf = intelxl_admin_command_buffer ( intelxl );
+       buf->queues.vsi = cpu_to_le16 ( intelxl->vsi );
+       buf->queues.rx = cpu_to_le32 ( 1 );
+       buf->queues.tx = cpu_to_le32 ( 1 );
+
+       /* Issue command */
+       if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Configure promiscuous mode
+ *
+ * @v netdev           Network device
+ * @ret rc             Return status code
+ */
+static int intelxlvf_admin_promisc ( struct net_device *netdev ) {
+       struct intelxl_nic *intelxl = netdev->priv;
+       struct intelxl_admin_descriptor *cmd;
+       union intelxl_admin_buffer *buf;
+       int rc;
+
+       /* Populate descriptor */
+       cmd = intelxl_admin_command_descriptor ( intelxl );
+       cmd->vopcode = cpu_to_le32 ( INTELXL_ADMIN_VF_PROMISC );
+       cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF );
+       cmd->len = cpu_to_le16 ( sizeof ( buf->promisc ) );
+       buf = intelxl_admin_command_buffer ( intelxl );
+       buf->promisc.vsi = cpu_to_le16 ( intelxl->vsi );
+       buf->promisc.flags = cpu_to_le16 ( INTELXL_ADMIN_PROMISC_FL_UNICAST |
+                                          INTELXL_ADMIN_PROMISC_FL_MULTICAST );
+
+       /* Issue command */
+       if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Open network device
+ *
+ * @v netdev           Network device
+ * @ret rc             Return status code
+ */
+static int intelxlvf_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 ) );
+
+       /* Allocate transmit descriptor ring */
+       if ( ( rc = intelxl_alloc_ring ( intelxl, &intelxl->tx ) ) != 0 )
+               goto err_alloc_tx;
+
+       /* Allocate receive descriptor ring */
+       if ( ( rc = intelxl_alloc_ring ( intelxl, &intelxl->rx ) ) != 0 )
+               goto err_alloc_rx;
+
+       /* Configure queues */
+       if ( ( rc = intelxlvf_admin_configure ( netdev ) ) != 0 )
+               goto err_configure;
+
+       /* Configure IRQ map */
+       if ( ( rc = intelxlvf_admin_irq_map ( netdev ) ) != 0 )
+               goto err_irq_map;
+
+       /* Enable queues */
+       if ( ( rc = intelxlvf_admin_queues ( netdev, 1 ) ) != 0 )
+               goto err_enable;
+
+       /* Configure promiscuous mode */
+       if ( ( rc = intelxlvf_admin_promisc ( netdev ) ) != 0 )
+               goto err_promisc;
+
+       return 0;
+
+ err_promisc:
+       intelxlvf_admin_queues ( netdev, INTELXL_ADMIN_VF_DISABLE );
+ err_enable:
+ err_irq_map:
+ err_configure:
+       intelxl_free_ring ( intelxl, &intelxl->rx );
+ err_alloc_rx:
+       intelxl_free_ring ( intelxl, &intelxl->tx );
+ err_alloc_tx:
+       return rc;
+}
+
+/**
+ * Close network device
+ *
+ * @v netdev           Network device
+ */
+static void intelxlvf_close ( struct net_device *netdev ) {
+       struct intelxl_nic *intelxl = netdev->priv;
+       int rc;
+
+       /* Disable queues */
+       if ( ( rc = intelxlvf_admin_queues ( netdev, 0 ) ) != 0 ) {
+               /* Leak memory; there's nothing else we can do */
+               return;
+       }
+
+       /* Free receive descriptor ring */
+       intelxl_free_ring ( intelxl, &intelxl->rx );
+
+       /* Free transmit descriptor ring */
+       intelxl_free_ring ( intelxl, &intelxl->tx );
+
+       /* Discard any unused receive buffers */
+       intelxl_empty_rx ( intelxl );
+}
+
+/** Network device operations */
+static struct net_device_operations intelxlvf_operations = {
+       .open           = intelxlvf_open,
+       .close          = intelxlvf_close,
+       .transmit       = intelxl_transmit,
+       .poll           = intelxl_poll,
+};
+
+/******************************************************************************
+ *
+ * PCI interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Probe PCI device
+ *
+ * @v pci              PCI device
+ * @ret rc             Return status code
+ */
+static int intelxlvf_probe ( struct pci_device *pci ) {
+       struct net_device *netdev;
+       struct intelxl_nic *intelxl;
+       int rc;
+
+       /* Allocate and initialise net device */
+       netdev = alloc_etherdev ( sizeof ( *intelxl ) );
+       if ( ! netdev ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       netdev_init ( netdev, &intelxlvf_operations );
+       intelxl = netdev->priv;
+       pci_set_drvdata ( pci, netdev );
+       netdev->dev = &pci->dev;
+       memset ( intelxl, 0, sizeof ( *intelxl ) );
+       intelxl->intr = INTELXLVF_VFINT_DYN_CTL0;
+       intelxl_init_admin ( &intelxl->command, INTELXLVF_ADMIN,
+                            &intelxlvf_admin_command_offsets );
+       intelxl_init_admin ( &intelxl->event, INTELXLVF_ADMIN,
+                            &intelxlvf_admin_event_offsets );
+       intelxlvf_init_ring ( &intelxl->tx, INTELXL_TX_NUM_DESC,
+                             sizeof ( intelxl->tx.desc.tx[0] ),
+                             INTELXLVF_QTX_TAIL );
+       intelxlvf_init_ring ( &intelxl->rx, INTELXL_RX_NUM_DESC,
+                             sizeof ( intelxl->rx.desc.rx[0] ),
+                             INTELXLVF_QRX_TAIL );
+
+       /* Fix up PCI device */
+       adjust_pci_device ( pci );
+
+       /* Map registers */
+       intelxl->regs = ioremap ( pci->membase, INTELXLVF_BAR_SIZE );
+       if ( ! intelxl->regs ) {
+               rc = -ENODEV;
+               goto err_ioremap;
+       }
+
+       /* Locate PCI Express capability */
+       intelxl->exp = pci_find_capability ( pci, PCI_CAP_ID_EXP );
+       if ( ! intelxl->exp ) {
+               DBGC ( intelxl, "INTELXL %p missing PCIe capability\n",
+                      intelxl );
+               rc = -ENXIO;
+               goto err_exp;
+       }
+
+       /* Reset the function via PCIe FLR */
+       intelxlvf_reset_flr ( intelxl, pci );
+
+       /* Enable MSI-X dummy interrupt */
+       if ( ( rc = intelxl_msix_enable ( intelxl, pci ) ) != 0 )
+               goto err_msix;
+
+       /* Open admin queues */
+       if ( ( rc = intelxl_open_admin ( intelxl ) ) != 0 )
+               goto err_open_admin;
+
+       /* Reset the function via admin queue */
+       if ( ( rc = intelxlvf_reset_admin ( intelxl ) ) != 0 )
+               goto err_reset_admin;
+
+       /* Get MAC address */
+       if ( ( rc = intelxlvf_admin_get_resources ( netdev ) ) != 0 )
+               goto err_get_resources;
+
+       /* Register network device */
+       if ( ( rc = register_netdev ( netdev ) ) != 0 )
+               goto err_register_netdev;
+
+       return 0;
+
+       unregister_netdev ( netdev );
+ err_register_netdev:
+ err_get_resources:
+ err_reset_admin:
+       intelxl_close_admin ( intelxl );
+ err_open_admin:
+       intelxl_msix_disable ( intelxl, pci );
+ err_msix:
+       intelxlvf_reset_flr ( intelxl, pci );
+ 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 intelxlvf_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 );
+
+       /* Reset the function via admin queue */
+       intelxlvf_reset_admin ( intelxl );
+
+       /* Close admin queues */
+       intelxl_close_admin ( intelxl );
+
+       /* Disable MSI-X dummy interrupt */
+       intelxl_msix_disable ( intelxl, pci );
+
+       /* Reset the function via PCIe FLR */
+       intelxlvf_reset_flr ( intelxl, pci );
+
+       /* Free network device */
+       iounmap ( intelxl->regs );
+       netdev_nullify ( netdev );
+       netdev_put ( netdev );
+}
+
+/** PCI device IDs */
+static struct pci_device_id intelxlvf_nics[] = {
+       PCI_ROM ( 0x8086, 0x154c, "xl710-vf", "XL710 VF", 0 ),
+       PCI_ROM ( 0x8086, 0x1571, "xl710-vf-hv", "XL710 VF (Hyper-V)", 0 ),
+       PCI_ROM ( 0x8086, 0x1889, "xl710-vf-ad", "XL710 VF (adaptive)", 0 ),
+       PCI_ROM ( 0x8086, 0x37cd, "x722-vf", "X722 VF", 0 ),
+       PCI_ROM ( 0x8086, 0x37d9, "x722-vf-hv", "X722 VF (Hyper-V)", 0 ),
+};
+
+/** PCI driver */
+struct pci_driver intelxlvf_driver __pci_driver = {
+       .ids = intelxlvf_nics,
+       .id_count = ( sizeof ( intelxlvf_nics ) /
+                     sizeof ( intelxlvf_nics[0] ) ),
+       .probe = intelxlvf_probe,
+       .remove = intelxlvf_remove,
+};
diff --git a/src/drivers/net/intelxlvf.h b/src/drivers/net/intelxlvf.h
new file mode 100644 (file)
index 0000000..ffcae56
--- /dev/null
@@ -0,0 +1,86 @@
+#ifndef _INTELXLVF_H
+#define _INTELXLVF_H
+
+/** @file
+ *
+ * Intel 40 Gigabit Ethernet virtual function network card driver
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include "intelxl.h"
+
+/** BAR size */
+#define INTELXLVF_BAR_SIZE 0x10000
+
+/** Transmit Queue Tail Register */
+#define INTELXLVF_QTX_TAIL 0x00000
+
+/** Receive Queue Tail Register */
+#define INTELXLVF_QRX_TAIL 0x02000
+
+/** VF Interrupt Zero Dynamic Control Register */
+#define INTELXLVF_VFINT_DYN_CTL0 0x5c00
+
+/** VF Admin Queue register block */
+#define INTELXLVF_ADMIN 0x6000
+
+/** Admin Command Queue Base Address Low Register (offset) */
+#define INTELXLVF_ADMIN_CMD_BAL 0x1c00
+
+/** Admin Command Queue Base Address High Register (offset) */
+#define INTELXLVF_ADMIN_CMD_BAH 0x1800
+
+/** Admin Command Queue Length Register (offset) */
+#define INTELXLVF_ADMIN_CMD_LEN 0x0800
+
+/** Admin Command Queue Head Register (offset) */
+#define INTELXLVF_ADMIN_CMD_HEAD 0x0400
+
+/** Admin Command Queue Tail Register (offset) */
+#define INTELXLVF_ADMIN_CMD_TAIL 0x2400
+
+/** Admin Event Queue Base Address Low Register (offset) */
+#define INTELXLVF_ADMIN_EVT_BAL 0x0c00
+
+/** Admin Event Queue Base Address High Register (offset) */
+#define INTELXLVF_ADMIN_EVT_BAH 0x0000
+
+/** Admin Event Queue Length Register (offset) */
+#define INTELXLVF_ADMIN_EVT_LEN 0x2000
+
+/** Admin Event Queue Head Register (offset) */
+#define INTELXLVF_ADMIN_EVT_HEAD 0x1400
+
+/** Admin Event Queue Tail Register (offset) */
+#define INTELXLVF_ADMIN_EVT_TAIL 0x1000
+
+/** Maximum time to wait for a VF admin request to complete */
+#define INTELXLVF_ADMIN_MAX_WAIT_MS 2000
+
+/** VF Reset Status Register */
+#define INTELXLVF_VFGEN_RSTAT 0x8800
+#define INTELXLVF_VFGEN_RSTAT_VFR_STATE(x) ( (x) & 0x3 )
+#define INTELXLVF_VFGEN_RSTAT_VFR_STATE_ACTIVE 0x2
+
+/** Maximum time to wait for reset to complete */
+#define INTELXLVF_RESET_MAX_WAIT_MS 1000
+
+/**
+ * Initialise descriptor ring
+ *
+ * @v ring             Descriptor ring
+ * @v count            Number of descriptors
+ * @v len              Length of a single descriptor
+ * @v tail             Tail register offset
+ */
+static inline __attribute__ (( always_inline)) void
+intelxlvf_init_ring ( struct intelxl_ring *ring, unsigned int count,
+                     size_t len, unsigned int tail ) {
+
+       ring->len = ( count * len );
+       ring->tail = tail;
+}
+
+#endif /* _INTELXLVF_H */
index 02e13d11b050aa1865fd66b9d853922d72140d4c..1a92b6ce2a8a93df5998009d42ad4d992354679d 100644 (file)
@@ -206,6 +206,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define ERRFILE_icplus              ( ERRFILE_DRIVER | 0x00ca0000 )
 #define ERRFILE_intelxl                     ( ERRFILE_DRIVER | 0x00cb0000 )
 #define ERRFILE_pcimsix                     ( ERRFILE_DRIVER | 0x00cc0000 )
+#define ERRFILE_intelxlvf           ( ERRFILE_DRIVER | 0x00cd0000 )
 
 #define ERRFILE_aoe                    ( ERRFILE_NET | 0x00000000 )
 #define ERRFILE_arp                    ( ERRFILE_NET | 0x00010000 )