| /* |
| * 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 |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * Wait for reset to complete |
| * |
| * @v intelxl Intel device |
| * @ret rc Return status code |
| */ |
| static int intelxlvf_reset_wait ( struct intelxl_nic *intelxl ) { |
| int rc; |
| |
| /* Wait for minimum reset time */ |
| mdelay ( INTELXLVF_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 ); |
| return rc; |
| } |
| |
| /** |
| * Reset hardware via admin queue |
| * |
| * @v intelxl Intel device |
| * @ret rc Return status code |
| */ |
| static int intelxlvf_reset_admin ( struct intelxl_nic *intelxl ) { |
| struct intelxlvf_admin_descriptor *cmd; |
| int rc; |
| |
| /* Populate descriptor */ |
| cmd = intelxlvf_admin_command_descriptor ( intelxl ); |
| cmd->opcode = cpu_to_le16 ( INTELXLVF_ADMIN_SEND_TO_PF ); |
| cmd->vopcode = cpu_to_le32 ( INTELXLVF_ADMIN_RESET ); |
| |
| /* Issue command */ |
| if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) |
| return rc; |
| |
| /* Wait for reset to complete */ |
| if ( ( rc = intelxlvf_reset_wait ( intelxl ) ) != 0 ) |
| return rc; |
| |
| return 0; |
| } |
| |
| /****************************************************************************** |
| * |
| * 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 *xlcmd; |
| struct intelxlvf_admin_descriptor *cmd; |
| unsigned int i; |
| int rc; |
| |
| /* Populate descriptor */ |
| xlcmd = &admin->desc[ admin->index % INTELXL_ADMIN_NUM_DESC ]; |
| cmd = container_of ( xlcmd, struct intelxlvf_admin_descriptor, xl ); |
| cmd->opcode = cpu_to_le16 ( INTELXLVF_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 ( cmd->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 intelxlvf_admin_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 intelxlvf_admin_status_buffer *stat ) { |
| struct intelxl_nic *intelxl = netdev->priv; |
| |
| /* Handle event */ |
| switch ( stat->event ) { |
| case cpu_to_le32 ( INTELXLVF_ADMIN_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 admin event |
| * |
| * @v netdev Network device |
| * @v xlevt Admin queue event descriptor |
| * @v xlbuf Admin queue event data buffer |
| */ |
| static void intelxlvf_admin_event ( struct net_device *netdev, |
| struct intelxl_admin_descriptor *xlevt, |
| union intelxl_admin_buffer *xlbuf ) { |
| struct intelxl_nic *intelxl = netdev->priv; |
| struct intelxl_admin *admin = &intelxl->command; |
| struct intelxlvf_admin_descriptor *evt = |
| container_of ( xlevt, struct intelxlvf_admin_descriptor, xl ); |
| union intelxlvf_admin_buffer *buf = |
| container_of ( xlbuf, union intelxlvf_admin_buffer, xl ); |
| unsigned int vopcode; |
| unsigned int index; |
| |
| /* Ignore unrecognised events */ |
| if ( evt->opcode != cpu_to_le16 ( INTELXLVF_ADMIN_SEND_TO_VF ) ) { |
| DBGC ( intelxl, "INTELXL %p unrecognised event opcode " |
| "%#04x\n", intelxl, le16_to_cpu ( evt->opcode ) ); |
| return; |
| } |
| |
| /* Record command response if applicable */ |
| vopcode = le32_to_cpu ( evt->vopcode ); |
| if ( vopcode == intelxl->vopcode ) { |
| index = ( ( admin->index - 1 ) % INTELXL_ADMIN_NUM_DESC ); |
| memcpy ( &admin->desc[index], evt, sizeof ( *evt ) ); |
| memcpy ( &admin->buf[index], buf, sizeof ( *buf ) ); |
| intelxl->vopcode = 0; |
| if ( evt->vret != 0 ) { |
| DBGC ( intelxl, "INTELXL %p admin VF command %#x " |
| "error %d\n", intelxl, vopcode, |
| le32_to_cpu ( evt->vret ) ); |
| DBGC_HDA ( intelxl, virt_to_phys ( evt ), evt, |
| sizeof ( *evt ) ); |
| DBGC_HDA ( intelxl, virt_to_phys ( buf ), buf, |
| le16_to_cpu ( evt->len ) ); |
| } |
| return; |
| } |
| |
| /* Handle unsolicited events */ |
| switch ( vopcode ) { |
| case INTELXLVF_ADMIN_STATUS: |
| intelxlvf_admin_status ( netdev, &buf->stat ); |
| break; |
| default: |
| DBGC ( intelxl, "INTELXL %p unrecognised VF event %#x:\n", |
| intelxl, vopcode ); |
| DBGC_HDA ( intelxl, virt_to_phys ( evt ), evt, |
| sizeof ( *evt ) ); |
| DBGC_HDA ( intelxl, virt_to_phys ( buf ), buf, |
| le16_to_cpu ( evt->len ) ); |
| break; |
| } |
| } |
| |
| /** |
| * Negotiate API version |
| * |
| * @v netdev Network device |
| * @ret rc Return status code |
| */ |
| static int intelxlvf_admin_version ( struct net_device *netdev ) { |
| struct intelxl_nic *intelxl = netdev->priv; |
| struct intelxlvf_admin_descriptor *cmd; |
| union intelxlvf_admin_buffer *buf; |
| unsigned int api; |
| int rc; |
| |
| /* Populate descriptor */ |
| cmd = intelxlvf_admin_command_descriptor ( intelxl ); |
| cmd->vopcode = cpu_to_le32 ( INTELXLVF_ADMIN_VERSION ); |
| cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); |
| cmd->len = cpu_to_le16 ( sizeof ( buf->ver ) ); |
| buf = intelxlvf_admin_command_buffer ( intelxl ); |
| buf->ver.major = cpu_to_le32 ( INTELXLVF_ADMIN_API_MAJOR ); |
| buf->ver.minor = cpu_to_le32 ( INTELXLVF_ADMIN_API_MINOR ); |
| |
| /* Issue command */ |
| if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 ) |
| return rc; |
| api = le32_to_cpu ( buf->ver.major ); |
| DBGC ( intelxl, "INTELXL %p API v%d.%d\n", |
| intelxl, api, le32_to_cpu ( buf->ver.minor ) ); |
| |
| /* Check for API compatibility */ |
| if ( api > INTELXLVF_ADMIN_API_MAJOR ) { |
| DBGC ( intelxl, "INTELXL %p unsupported API v%d\n", |
| intelxl, api ); |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * 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 intelxlvf_admin_descriptor *cmd; |
| union intelxlvf_admin_buffer *buf; |
| int rc; |
| |
| /* Populate descriptor */ |
| cmd = intelxlvf_admin_command_descriptor ( intelxl ); |
| cmd->vopcode = cpu_to_le32 ( INTELXLVF_ADMIN_GET_RESOURCES ); |
| cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); |
| cmd->len = cpu_to_le16 ( sizeof ( buf->caps ) ); |
| buf = intelxlvf_admin_command_buffer ( intelxl ); |
| buf->caps.caps = cpu_to_le32 ( INTELXLVF_ADMIN_CAP_L2 | |
| INTELXLVF_ADMIN_CAP_RQPS ); |
| |
| /* Issue command */ |
| if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 ) |
| return rc; |
| |
| /* Parse response */ |
| intelxl->caps = le32_to_cpu ( buf->res.caps ); |
| intelxl->vsi = le16_to_cpu ( buf->res.vsi ); |
| memcpy ( netdev->hw_addr, buf->res.mac, ETH_ALEN ); |
| DBGC ( intelxl, "INTELXL %p capabilities %#08x VSI %#04x\n", |
| intelxl, intelxl->caps, intelxl->vsi ); |
| |
| return 0; |
| } |
| |
| /** |
| * Get statistics (for debugging) |
| * |
| * @v netdev Network device |
| * @ret rc Return status code |
| */ |
| static int intelxlvf_admin_stats ( struct net_device *netdev ) { |
| struct intelxl_nic *intelxl = netdev->priv; |
| struct intelxlvf_admin_descriptor *cmd; |
| union intelxlvf_admin_buffer *buf; |
| struct intelxlvf_admin_stats *tx; |
| struct intelxlvf_admin_stats *rx; |
| int rc; |
| |
| /* Populate descriptor */ |
| cmd = intelxlvf_admin_command_descriptor ( intelxl ); |
| cmd->vopcode = cpu_to_le32 ( INTELXLVF_ADMIN_GET_STATS ); |
| cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); |
| cmd->len = cpu_to_le16 ( sizeof ( buf->queues ) ); |
| buf = intelxlvf_admin_command_buffer ( intelxl ); |
| buf->queues.vsi = cpu_to_le16 ( intelxl->vsi ); |
| tx = &buf->stats.tx; |
| rx = &buf->stats.rx; |
| |
| /* Issue command */ |
| if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 ) |
| return rc; |
| DBGC ( intelxl, "INTELXL %p TX bytes %#llx discards %#llx errors " |
| "%#llx\n", intelxl, |
| ( ( unsigned long long ) le64_to_cpu ( tx->bytes ) ), |
| ( ( unsigned long long ) le64_to_cpu ( tx->discards ) ), |
| ( ( unsigned long long ) le64_to_cpu ( tx->errors ) ) ); |
| DBGC ( intelxl, "INTELXL %p TX unicasts %#llx multicasts %#llx " |
| "broadcasts %#llx\n", intelxl, |
| ( ( unsigned long long ) le64_to_cpu ( tx->unicasts ) ), |
| ( ( unsigned long long ) le64_to_cpu ( tx->multicasts ) ), |
| ( ( unsigned long long ) le64_to_cpu ( tx->broadcasts ) ) ); |
| DBGC ( intelxl, "INTELXL %p RX bytes %#llx discards %#llx errors " |
| "%#llx\n", intelxl, |
| ( ( unsigned long long ) le64_to_cpu ( rx->bytes ) ), |
| ( ( unsigned long long ) le64_to_cpu ( rx->discards ) ), |
| ( ( unsigned long long ) le64_to_cpu ( rx->errors ) ) ); |
| DBGC ( intelxl, "INTELXL %p RX unicasts %#llx multicasts %#llx " |
| "broadcasts %#llx\n", intelxl, |
| ( ( unsigned long long ) le64_to_cpu ( rx->unicasts ) ), |
| ( ( unsigned long long ) le64_to_cpu ( rx->multicasts ) ), |
| ( ( unsigned long long ) le64_to_cpu ( rx->broadcasts ) ) ); |
| |
| return 0; |
| } |
| |
| /** |
| * Configure number of queue pairs |
| * |
| * @v netdev Network device |
| * @ret rc Return status code |
| */ |
| static int intelxlvf_admin_request_qps ( struct net_device *netdev ) { |
| struct intelxl_nic *intelxl = netdev->priv; |
| struct intelxlvf_admin_descriptor *cmd; |
| union intelxlvf_admin_buffer *buf; |
| int rc; |
| |
| /* Populate descriptor */ |
| cmd = intelxlvf_admin_command_descriptor ( intelxl ); |
| cmd->opcode = cpu_to_le16 ( INTELXLVF_ADMIN_SEND_TO_PF ); |
| cmd->vopcode = cpu_to_le32 ( INTELXLVF_ADMIN_REQUEST_QPS ); |
| cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); |
| cmd->len = cpu_to_le16 ( sizeof ( buf->rqps ) ); |
| buf = intelxlvf_admin_command_buffer ( intelxl ); |
| buf->rqps.count = cpu_to_le16 ( 1 ); |
| |
| /* Issue command (which will trigger a reset) */ |
| if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) |
| return rc; |
| |
| /* Wait for reset to complete */ |
| if ( ( rc = intelxlvf_reset_wait ( intelxl ) ) != 0 ) |
| return rc; |
| |
| /* Reestablish capabilities to reactivate VF after reset */ |
| if ( ( rc = intelxlvf_admin_get_resources ( netdev ) ) != 0 ) |
| return rc; |
| |
| 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 intelxlvf_admin_descriptor *cmd; |
| union intelxlvf_admin_buffer *buf; |
| int rc; |
| |
| /* Populate descriptor */ |
| cmd = intelxlvf_admin_command_descriptor ( intelxl ); |
| cmd->vopcode = cpu_to_le32 ( INTELXLVF_ADMIN_CONFIGURE ); |
| cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); |
| cmd->len = cpu_to_le16 ( sizeof ( buf->cfg ) ); |
| buf = intelxlvf_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 ( dma ( &intelxl->tx.map, |
| 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 ( dma ( &intelxl->rx.map, |
| 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 intelxlvf_admin_descriptor *cmd; |
| union intelxlvf_admin_buffer *buf; |
| int rc; |
| |
| /* Populate descriptor */ |
| cmd = intelxlvf_admin_command_descriptor ( intelxl ); |
| cmd->vopcode = cpu_to_le32 ( INTELXLVF_ADMIN_IRQ_MAP ); |
| cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); |
| cmd->len = cpu_to_le16 ( sizeof ( buf->irq ) ); |
| buf = intelxlvf_admin_command_buffer ( intelxl ); |
| buf->irq.count = cpu_to_le16 ( 1 ); |
| buf->irq.vsi = cpu_to_le16 ( intelxl->vsi ); |
| buf->irq.vec = cpu_to_le16 ( INTELXLVF_MSIX_VECTOR ); |
| 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 intelxlvf_admin_descriptor *cmd; |
| union intelxlvf_admin_buffer *buf; |
| int rc; |
| |
| /* Populate descriptor */ |
| cmd = intelxlvf_admin_command_descriptor ( intelxl ); |
| cmd->vopcode = ( enable ? cpu_to_le32 ( INTELXLVF_ADMIN_ENABLE ) : |
| cpu_to_le32 ( INTELXLVF_ADMIN_DISABLE ) ); |
| cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); |
| cmd->len = cpu_to_le16 ( sizeof ( buf->queues ) ); |
| buf = intelxlvf_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 intelxlvf_admin_descriptor *cmd; |
| union intelxlvf_admin_buffer *buf; |
| int rc; |
| |
| /* Populate descriptor */ |
| cmd = intelxlvf_admin_command_descriptor ( intelxl ); |
| cmd->vopcode = cpu_to_le32 ( INTELXLVF_ADMIN_PROMISC ); |
| cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); |
| cmd->len = cpu_to_le16 ( sizeof ( buf->promisc ) ); |
| buf = intelxlvf_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; |
| |
| /* Reset statistics counters (if debugging) */ |
| if ( DBG_LOG ) |
| intelxlvf_admin_stats ( netdev ); |
| |
| return 0; |
| |
| err_promisc: |
| intelxlvf_admin_queues ( netdev, 0 ); |
| 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; |
| |
| /* Show statistics (if debugging) */ |
| if ( DBG_LOG ) |
| intelxlvf_admin_stats ( netdev ); |
| |
| /* 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_CTLN ( INTELXLVF_MSIX_VECTOR ); |
| intelxl->handle = intelxlvf_admin_event; |
| 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 = pci_ioremap ( pci, pci->membase, INTELXLVF_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, "INTELXL %p missing PCIe capability\n", |
| intelxl ); |
| rc = -ENXIO; |
| goto err_exp; |
| } |
| |
| /* Reset the function via PCIe FLR */ |
| pci_reset ( pci, intelxl->exp ); |
| |
| /* Enable MSI-X dummy interrupt */ |
| if ( ( rc = intelxl_msix_enable ( intelxl, pci, |
| INTELXLVF_MSIX_VECTOR ) ) != 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; |
| |
| /* Negotiate API version */ |
| if ( ( rc = intelxlvf_admin_version ( netdev ) ) != 0 ) |
| goto err_version; |
| |
| /* Get MAC address */ |
| if ( ( rc = intelxlvf_admin_get_resources ( netdev ) ) != 0 ) |
| goto err_get_resources; |
| |
| /* Configure number of queue pairs, if applicable */ |
| if ( ( intelxl->caps & INTELXLVF_ADMIN_CAP_RQPS ) && |
| ( ( rc = intelxlvf_admin_request_qps ( netdev ) ) != 0 ) ) |
| goto err_rqps; |
| |
| /* Register network device */ |
| if ( ( rc = register_netdev ( netdev ) ) != 0 ) |
| goto err_register_netdev; |
| |
| return 0; |
| |
| unregister_netdev ( netdev ); |
| err_register_netdev: |
| err_rqps: |
| err_get_resources: |
| err_version: |
| err_reset_admin: |
| intelxl_close_admin ( intelxl ); |
| err_open_admin: |
| intelxl_msix_disable ( intelxl, pci, INTELXLVF_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 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, INTELXLVF_MSIX_VECTOR ); |
| |
| /* Reset the function via PCIe FLR */ |
| 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 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, "iavf", "Intel adaptive VF", 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, |
| }; |