| /* | |
| * Copyright (C) 2011 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 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 ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <assert.h> | |
| #include <byteswap.h> | |
| #include <ipxe/pci.h> | |
| #include <ipxe/io.h> | |
| #include <ipxe/malloc.h> | |
| #include <ipxe/profile.h> | |
| #include <ipxe/iobuf.h> | |
| #include <ipxe/netdevice.h> | |
| #include <ipxe/if_ether.h> | |
| #include <ipxe/ethernet.h> | |
| #include "vmxnet3.h" | |
| /** | |
| * @file | |
| * | |
| * VMware vmxnet3 virtual NIC driver | |
| * | |
| */ | |
| /** VM command profiler */ | |
| static struct profiler vmxnet3_vm_command_profiler __profiler = | |
| { .name = "vmxnet3.vm_command" }; | |
| /** VM transmit profiler */ | |
| static struct profiler vmxnet3_vm_tx_profiler __profiler = | |
| { .name = "vmxnet3.vm_tx" }; | |
| /** VM receive refill profiler */ | |
| static struct profiler vmxnet3_vm_refill_profiler __profiler = | |
| { .name = "vmxnet3.vm_refill" }; | |
| /** VM event profiler */ | |
| static struct profiler vmxnet3_vm_event_profiler __profiler = | |
| { .name = "vmxnet3.vm_event" }; | |
| /** | |
| * Issue command | |
| * | |
| * @v vmxnet vmxnet3 NIC | |
| * @v command Command to issue | |
| * @ret result Command result | |
| */ | |
| static inline uint32_t vmxnet3_command ( struct vmxnet3_nic *vmxnet, | |
| uint32_t command ) { | |
| uint32_t result; | |
| /* Issue command */ | |
| profile_start ( &vmxnet3_vm_command_profiler ); | |
| writel ( command, ( vmxnet->vd + VMXNET3_VD_CMD ) ); | |
| result = readl ( vmxnet->vd + VMXNET3_VD_CMD ); | |
| profile_stop ( &vmxnet3_vm_command_profiler ); | |
| profile_exclude ( &vmxnet3_vm_command_profiler ); | |
| return result; | |
| } | |
| /** | |
| * Transmit packet | |
| * | |
| * @v netdev Network device | |
| * @v iobuf I/O buffer | |
| * @ret rc Return status code | |
| */ | |
| static int vmxnet3_transmit ( struct net_device *netdev, | |
| struct io_buffer *iobuf ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| struct vmxnet3_tx_desc *tx_desc; | |
| unsigned int fill; | |
| unsigned int desc_idx; | |
| unsigned int generation; | |
| /* Check that we have a free transmit descriptor */ | |
| fill = ( vmxnet->count.tx_prod - vmxnet->count.tx_cons ); | |
| if ( fill >= VMXNET3_TX_FILL ) { | |
| DBGC ( vmxnet, "VMXNET3 %p out of transmit descriptors\n", | |
| vmxnet ); | |
| return -ENOBUFS; | |
| } | |
| /* Locate transmit descriptor */ | |
| desc_idx = ( vmxnet->count.tx_prod % VMXNET3_NUM_TX_DESC ); | |
| generation = ( ( vmxnet->count.tx_prod & VMXNET3_NUM_TX_DESC ) ? | |
| 0 : cpu_to_le32 ( VMXNET3_TXF_GEN ) ); | |
| assert ( vmxnet->tx_iobuf[desc_idx] == NULL ); | |
| /* Increment producer counter */ | |
| vmxnet->count.tx_prod++; | |
| /* Store I/O buffer for later completion */ | |
| vmxnet->tx_iobuf[desc_idx] = iobuf; | |
| /* Populate transmit descriptor */ | |
| tx_desc = &vmxnet->dma->tx_desc[desc_idx]; | |
| tx_desc->address = cpu_to_le64 ( virt_to_bus ( iobuf->data ) ); | |
| tx_desc->flags[0] = ( generation | cpu_to_le32 ( iob_len ( iobuf ) ) ); | |
| tx_desc->flags[1] = cpu_to_le32 ( VMXNET3_TXF_CQ | VMXNET3_TXF_EOP ); | |
| /* Hand over descriptor to NIC */ | |
| wmb(); | |
| profile_start ( &vmxnet3_vm_tx_profiler ); | |
| writel ( ( vmxnet->count.tx_prod % VMXNET3_NUM_TX_DESC ), | |
| ( vmxnet->pt + VMXNET3_PT_TXPROD ) ); | |
| profile_stop ( &vmxnet3_vm_tx_profiler ); | |
| profile_exclude ( &vmxnet3_vm_tx_profiler ); | |
| return 0; | |
| } | |
| /** | |
| * Poll for completed transmissions | |
| * | |
| * @v netdev Network device | |
| */ | |
| static void vmxnet3_poll_tx ( struct net_device *netdev ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| struct vmxnet3_tx_comp *tx_comp; | |
| struct io_buffer *iobuf; | |
| unsigned int comp_idx; | |
| unsigned int desc_idx; | |
| unsigned int generation; | |
| while ( 1 ) { | |
| /* Look for completed descriptors */ | |
| comp_idx = ( vmxnet->count.tx_cons % VMXNET3_NUM_TX_COMP ); | |
| generation = ( ( vmxnet->count.tx_cons & VMXNET3_NUM_TX_COMP ) ? | |
| 0 : cpu_to_le32 ( VMXNET3_TXCF_GEN ) ); | |
| tx_comp = &vmxnet->dma->tx_comp[comp_idx]; | |
| if ( generation != ( tx_comp->flags & | |
| cpu_to_le32 ( VMXNET3_TXCF_GEN ) ) ) { | |
| break; | |
| } | |
| /* Increment consumer counter */ | |
| vmxnet->count.tx_cons++; | |
| /* Locate corresponding transmit descriptor */ | |
| desc_idx = ( le32_to_cpu ( tx_comp->index ) % | |
| VMXNET3_NUM_TX_DESC ); | |
| iobuf = vmxnet->tx_iobuf[desc_idx]; | |
| if ( ! iobuf ) { | |
| DBGC ( vmxnet, "VMXNET3 %p completed on empty transmit " | |
| "buffer %#x/%#x\n", vmxnet, comp_idx, desc_idx ); | |
| netdev_tx_err ( netdev, NULL, -ENOTTY ); | |
| continue; | |
| } | |
| /* Remove I/O buffer from transmit queue */ | |
| vmxnet->tx_iobuf[desc_idx] = NULL; | |
| /* Report transmission completion to network layer */ | |
| DBGC2 ( vmxnet, "VMXNET3 %p completed TX %#x/%#x (len %#zx)\n", | |
| vmxnet, comp_idx, desc_idx, iob_len ( iobuf ) ); | |
| netdev_tx_complete ( netdev, iobuf ); | |
| } | |
| } | |
| /** | |
| * Flush any uncompleted transmit buffers | |
| * | |
| * @v netdev Network device | |
| */ | |
| static void vmxnet3_flush_tx ( struct net_device *netdev ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| unsigned int i; | |
| for ( i = 0 ; i < VMXNET3_NUM_TX_DESC ; i++ ) { | |
| if ( vmxnet->tx_iobuf[i] ) { | |
| netdev_tx_complete_err ( netdev, vmxnet->tx_iobuf[i], | |
| -ECANCELED ); | |
| vmxnet->tx_iobuf[i] = NULL; | |
| } | |
| } | |
| } | |
| /** | |
| * Refill receive ring | |
| * | |
| * @v netdev Network device | |
| */ | |
| static void vmxnet3_refill_rx ( struct net_device *netdev ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| struct vmxnet3_rx_desc *rx_desc; | |
| struct io_buffer *iobuf; | |
| unsigned int orig_rx_prod = vmxnet->count.rx_prod; | |
| unsigned int desc_idx; | |
| unsigned int generation; | |
| /* Fill receive ring to specified fill level */ | |
| while ( vmxnet->count.rx_fill < VMXNET3_RX_FILL ) { | |
| /* Locate receive descriptor */ | |
| desc_idx = ( vmxnet->count.rx_prod % VMXNET3_NUM_RX_DESC ); | |
| generation = ( ( vmxnet->count.rx_prod & VMXNET3_NUM_RX_DESC ) ? | |
| 0 : cpu_to_le32 ( VMXNET3_RXF_GEN ) ); | |
| assert ( vmxnet->rx_iobuf[desc_idx] == NULL ); | |
| /* Allocate I/O buffer */ | |
| iobuf = alloc_iob ( VMXNET3_MTU + NET_IP_ALIGN ); | |
| if ( ! iobuf ) { | |
| /* Non-fatal low memory condition */ | |
| break; | |
| } | |
| iob_reserve ( iobuf, NET_IP_ALIGN ); | |
| /* Increment producer counter and fill level */ | |
| vmxnet->count.rx_prod++; | |
| vmxnet->count.rx_fill++; | |
| /* Store I/O buffer for later completion */ | |
| vmxnet->rx_iobuf[desc_idx] = iobuf; | |
| /* Populate receive descriptor */ | |
| rx_desc = &vmxnet->dma->rx_desc[desc_idx]; | |
| rx_desc->address = cpu_to_le64 ( virt_to_bus ( iobuf->data ) ); | |
| rx_desc->flags = ( generation | cpu_to_le32 ( VMXNET3_MTU ) ); | |
| } | |
| /* Hand over any new descriptors to NIC */ | |
| if ( vmxnet->count.rx_prod != orig_rx_prod ) { | |
| wmb(); | |
| profile_start ( &vmxnet3_vm_refill_profiler ); | |
| writel ( ( vmxnet->count.rx_prod % VMXNET3_NUM_RX_DESC ), | |
| ( vmxnet->pt + VMXNET3_PT_RXPROD ) ); | |
| profile_stop ( &vmxnet3_vm_refill_profiler ); | |
| profile_exclude ( &vmxnet3_vm_refill_profiler ); | |
| } | |
| } | |
| /** | |
| * Poll for received packets | |
| * | |
| * @v netdev Network device | |
| */ | |
| static void vmxnet3_poll_rx ( struct net_device *netdev ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| struct vmxnet3_rx_comp *rx_comp; | |
| struct io_buffer *iobuf; | |
| unsigned int comp_idx; | |
| unsigned int desc_idx; | |
| unsigned int generation; | |
| size_t len; | |
| while ( 1 ) { | |
| /* Look for completed descriptors */ | |
| comp_idx = ( vmxnet->count.rx_cons % VMXNET3_NUM_RX_COMP ); | |
| generation = ( ( vmxnet->count.rx_cons & VMXNET3_NUM_RX_COMP ) ? | |
| 0 : cpu_to_le32 ( VMXNET3_RXCF_GEN ) ); | |
| rx_comp = &vmxnet->dma->rx_comp[comp_idx]; | |
| if ( generation != ( rx_comp->flags & | |
| cpu_to_le32 ( VMXNET3_RXCF_GEN ) ) ) { | |
| break; | |
| } | |
| /* Increment consumer counter */ | |
| vmxnet->count.rx_cons++; | |
| /* Locate corresponding receive descriptor */ | |
| desc_idx = ( le32_to_cpu ( rx_comp->index ) % | |
| VMXNET3_NUM_RX_DESC ); | |
| iobuf = vmxnet->rx_iobuf[desc_idx]; | |
| if ( ! iobuf ) { | |
| DBGC ( vmxnet, "VMXNET3 %p completed on empty receive " | |
| "buffer %#x/%#x\n", vmxnet, comp_idx, desc_idx ); | |
| netdev_rx_err ( netdev, NULL, -ENOTTY ); | |
| continue; | |
| } | |
| /* Remove I/O buffer from receive queue */ | |
| vmxnet->rx_iobuf[desc_idx] = NULL; | |
| vmxnet->count.rx_fill--; | |
| /* Deliver packet to network layer */ | |
| len = ( le32_to_cpu ( rx_comp->len ) & | |
| ( VMXNET3_MAX_PACKET_LEN - 1 ) ); | |
| DBGC2 ( vmxnet, "VMXNET3 %p completed RX %#x/%#x (len %#zx)\n", | |
| vmxnet, comp_idx, desc_idx, len ); | |
| iob_put ( iobuf, len ); | |
| netdev_rx ( netdev, iobuf ); | |
| } | |
| } | |
| /** | |
| * Flush any uncompleted receive buffers | |
| * | |
| * @v netdev Network device | |
| */ | |
| static void vmxnet3_flush_rx ( struct net_device *netdev ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| struct io_buffer *iobuf; | |
| unsigned int i; | |
| for ( i = 0 ; i < VMXNET3_NUM_RX_DESC ; i++ ) { | |
| if ( ( iobuf = vmxnet->rx_iobuf[i] ) != NULL ) { | |
| netdev_rx_err ( netdev, iobuf, -ECANCELED ); | |
| vmxnet->rx_iobuf[i] = NULL; | |
| } | |
| } | |
| } | |
| /** | |
| * Check link state | |
| * | |
| * @v netdev Network device | |
| */ | |
| static void vmxnet3_check_link ( struct net_device *netdev ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| uint32_t state; | |
| int link_up; | |
| unsigned int link_speed; | |
| /* Get link state */ | |
| state = vmxnet3_command ( vmxnet, VMXNET3_CMD_GET_LINK ); | |
| link_up = ( state & 1 ); | |
| link_speed = ( state >> 16 ); | |
| /* Report link state to network device */ | |
| if ( link_up ) { | |
| DBGC ( vmxnet, "VMXNET3 %p link is up at %d Mbps\n", | |
| vmxnet, link_speed ); | |
| netdev_link_up ( netdev ); | |
| } else { | |
| DBGC ( vmxnet, "VMXNET3 %p link is down\n", vmxnet ); | |
| netdev_link_down ( netdev ); | |
| } | |
| } | |
| /** | |
| * Poll for events | |
| * | |
| * @v netdev Network device | |
| */ | |
| static void vmxnet3_poll_events ( struct net_device *netdev ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| uint32_t events; | |
| /* Do nothing unless there are events to process */ | |
| if ( ! vmxnet->dma->shared.ecr ) | |
| return; | |
| events = le32_to_cpu ( vmxnet->dma->shared.ecr ); | |
| /* Acknowledge these events */ | |
| profile_start ( &vmxnet3_vm_event_profiler ); | |
| writel ( events, ( vmxnet->vd + VMXNET3_VD_ECR ) ); | |
| profile_stop ( &vmxnet3_vm_event_profiler ); | |
| profile_exclude ( &vmxnet3_vm_event_profiler ); | |
| /* Check for link state change */ | |
| if ( events & VMXNET3_ECR_LINK ) { | |
| vmxnet3_check_link ( netdev ); | |
| events &= ~VMXNET3_ECR_LINK; | |
| } | |
| /* Check for queue errors */ | |
| if ( events & ( VMXNET3_ECR_TQERR | VMXNET3_ECR_RQERR ) ) { | |
| vmxnet3_command ( vmxnet, VMXNET3_CMD_GET_QUEUE_STATUS ); | |
| DBGC ( vmxnet, "VMXNET3 %p queue error status (TX %08x, RX " | |
| "%08x)\n", vmxnet, | |
| le32_to_cpu ( vmxnet->dma->queues.tx.status.error ), | |
| le32_to_cpu ( vmxnet->dma->queues.rx.status.error ) ); | |
| /* Report errors to allow for visibility via "ifstat" */ | |
| if ( events & VMXNET3_ECR_TQERR ) | |
| netdev_tx_err ( netdev, NULL, -EPIPE ); | |
| if ( events & VMXNET3_ECR_RQERR ) | |
| netdev_rx_err ( netdev, NULL, -EPIPE ); | |
| events &= ~( VMXNET3_ECR_TQERR | VMXNET3_ECR_RQERR ); | |
| } | |
| /* Check for unknown events */ | |
| if ( events ) { | |
| DBGC ( vmxnet, "VMXNET3 %p unknown events %08x\n", | |
| vmxnet, events ); | |
| /* Report error to allow for visibility via "ifstat" */ | |
| netdev_rx_err ( netdev, NULL, -ENODEV ); | |
| } | |
| } | |
| /** | |
| * Poll network device | |
| * | |
| * @v netdev Network device | |
| */ | |
| static void vmxnet3_poll ( struct net_device *netdev ) { | |
| vmxnet3_poll_events ( netdev ); | |
| vmxnet3_poll_tx ( netdev ); | |
| vmxnet3_poll_rx ( netdev ); | |
| vmxnet3_refill_rx ( netdev ); | |
| } | |
| /** | |
| * Enable/disable interrupts | |
| * | |
| * @v netdev Network device | |
| * @v enable Interrupts should be enabled | |
| */ | |
| static void vmxnet3_irq ( struct net_device *netdev, int enable ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| DBGC ( vmxnet, "VMXNET3 %p %s IRQ not implemented\n", | |
| vmxnet, ( enable ? "enable" : "disable" ) ); | |
| } | |
| /** | |
| * Set MAC address | |
| * | |
| * @v vmxnet vmxnet3 NIC | |
| * @v ll_addr Link-layer address to set | |
| */ | |
| static void vmxnet3_set_ll_addr ( struct vmxnet3_nic *vmxnet, | |
| const void *ll_addr ) { | |
| struct { | |
| uint32_t low; | |
| uint32_t high; | |
| } __attribute__ (( packed )) mac; | |
| memset ( &mac, 0, sizeof ( mac ) ); | |
| memcpy ( &mac, ll_addr, ETH_ALEN ); | |
| writel ( cpu_to_le32 ( mac.low ), ( vmxnet->vd + VMXNET3_VD_MACL ) ); | |
| writel ( cpu_to_le32 ( mac.high ), ( vmxnet->vd + VMXNET3_VD_MACH ) ); | |
| } | |
| /** | |
| * Open NIC | |
| * | |
| * @v netdev Network device | |
| * @ret rc Return status code | |
| */ | |
| static int vmxnet3_open ( struct net_device *netdev ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| struct vmxnet3_shared *shared; | |
| struct vmxnet3_queues *queues; | |
| uint64_t shared_bus; | |
| uint64_t queues_bus; | |
| uint32_t status; | |
| int rc; | |
| /* Allocate DMA areas */ | |
| vmxnet->dma = malloc_phys ( sizeof ( *vmxnet->dma ), | |
| VMXNET3_DMA_ALIGN ); | |
| if ( ! vmxnet->dma ) { | |
| DBGC ( vmxnet, "VMXNET3 %p could not allocate DMA area\n", | |
| vmxnet ); | |
| rc = -ENOMEM; | |
| goto err_alloc_dma; | |
| } | |
| memset ( vmxnet->dma, 0, sizeof ( *vmxnet->dma ) ); | |
| /* Populate queue descriptors */ | |
| queues = &vmxnet->dma->queues; | |
| queues->tx.cfg.desc_address = | |
| cpu_to_le64 ( virt_to_bus ( &vmxnet->dma->tx_desc ) ); | |
| queues->tx.cfg.comp_address = | |
| cpu_to_le64 ( virt_to_bus ( &vmxnet->dma->tx_comp ) ); | |
| queues->tx.cfg.num_desc = cpu_to_le32 ( VMXNET3_NUM_TX_DESC ); | |
| queues->tx.cfg.num_comp = cpu_to_le32 ( VMXNET3_NUM_TX_COMP ); | |
| queues->rx.cfg.desc_address[0] = | |
| cpu_to_le64 ( virt_to_bus ( &vmxnet->dma->rx_desc ) ); | |
| queues->rx.cfg.comp_address = | |
| cpu_to_le64 ( virt_to_bus ( &vmxnet->dma->rx_comp ) ); | |
| queues->rx.cfg.num_desc[0] = cpu_to_le32 ( VMXNET3_NUM_RX_DESC ); | |
| queues->rx.cfg.num_comp = cpu_to_le32 ( VMXNET3_NUM_RX_COMP ); | |
| queues_bus = virt_to_bus ( queues ); | |
| DBGC ( vmxnet, "VMXNET3 %p queue descriptors at %08llx+%zx\n", | |
| vmxnet, queues_bus, sizeof ( *queues ) ); | |
| /* Populate shared area */ | |
| shared = &vmxnet->dma->shared; | |
| shared->magic = cpu_to_le32 ( VMXNET3_SHARED_MAGIC ); | |
| shared->misc.version = cpu_to_le32 ( VMXNET3_VERSION_MAGIC ); | |
| shared->misc.version_support = cpu_to_le32 ( VMXNET3_VERSION_SELECT ); | |
| shared->misc.upt_version_support = | |
| cpu_to_le32 ( VMXNET3_UPT_VERSION_SELECT ); | |
| shared->misc.queue_desc_address = cpu_to_le64 ( queues_bus ); | |
| shared->misc.queue_desc_len = cpu_to_le32 ( sizeof ( *queues ) ); | |
| shared->misc.mtu = cpu_to_le32 ( VMXNET3_MTU ); | |
| shared->misc.num_tx_queues = 1; | |
| shared->misc.num_rx_queues = 1; | |
| shared->interrupt.num_intrs = 1; | |
| shared->interrupt.control = cpu_to_le32 ( VMXNET3_IC_DISABLE_ALL ); | |
| shared->rx_filter.mode = cpu_to_le32 ( VMXNET3_RXM_UCAST | | |
| VMXNET3_RXM_BCAST | | |
| VMXNET3_RXM_ALL_MULTI ); | |
| shared_bus = virt_to_bus ( shared ); | |
| DBGC ( vmxnet, "VMXNET3 %p shared area at %08llx+%zx\n", | |
| vmxnet, shared_bus, sizeof ( *shared ) ); | |
| /* Zero counters */ | |
| memset ( &vmxnet->count, 0, sizeof ( vmxnet->count ) ); | |
| /* Set MAC address */ | |
| vmxnet3_set_ll_addr ( vmxnet, &netdev->ll_addr ); | |
| /* Pass shared area to device */ | |
| writel ( ( shared_bus >> 0 ), ( vmxnet->vd + VMXNET3_VD_DSAL ) ); | |
| writel ( ( shared_bus >> 32 ), ( vmxnet->vd + VMXNET3_VD_DSAH ) ); | |
| /* Activate device */ | |
| if ( ( status = vmxnet3_command ( vmxnet, | |
| VMXNET3_CMD_ACTIVATE_DEV ) ) != 0 ) { | |
| DBGC ( vmxnet, "VMXNET3 %p could not activate (status %#x)\n", | |
| vmxnet, status ); | |
| rc = -EIO; | |
| goto err_activate; | |
| } | |
| /* Fill receive ring */ | |
| vmxnet3_refill_rx ( netdev ); | |
| return 0; | |
| vmxnet3_command ( vmxnet, VMXNET3_CMD_QUIESCE_DEV ); | |
| vmxnet3_command ( vmxnet, VMXNET3_CMD_RESET_DEV ); | |
| err_activate: | |
| vmxnet3_flush_tx ( netdev ); | |
| vmxnet3_flush_rx ( netdev ); | |
| free_phys ( vmxnet->dma, sizeof ( *vmxnet->dma ) ); | |
| err_alloc_dma: | |
| return rc; | |
| } | |
| /** | |
| * Close NIC | |
| * | |
| * @v netdev Network device | |
| */ | |
| static void vmxnet3_close ( struct net_device *netdev ) { | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| vmxnet3_command ( vmxnet, VMXNET3_CMD_QUIESCE_DEV ); | |
| vmxnet3_command ( vmxnet, VMXNET3_CMD_RESET_DEV ); | |
| vmxnet3_flush_tx ( netdev ); | |
| vmxnet3_flush_rx ( netdev ); | |
| free_phys ( vmxnet->dma, sizeof ( *vmxnet->dma ) ); | |
| } | |
| /** vmxnet3 net device operations */ | |
| static struct net_device_operations vmxnet3_operations = { | |
| .open = vmxnet3_open, | |
| .close = vmxnet3_close, | |
| .transmit = vmxnet3_transmit, | |
| .poll = vmxnet3_poll, | |
| .irq = vmxnet3_irq, | |
| }; | |
| /** | |
| * Check version | |
| * | |
| * @v vmxnet vmxnet3 NIC | |
| * @ret rc Return status code | |
| */ | |
| static int vmxnet3_check_version ( struct vmxnet3_nic *vmxnet ) { | |
| uint32_t version; | |
| uint32_t upt_version; | |
| /* Read version */ | |
| version = readl ( vmxnet->vd + VMXNET3_VD_VRRS ); | |
| upt_version = readl ( vmxnet->vd + VMXNET3_VD_UVRS ); | |
| DBGC ( vmxnet, "VMXNET3 %p is version %d (UPT version %d)\n", | |
| vmxnet, version, upt_version ); | |
| /* Inform NIC of driver version */ | |
| writel ( VMXNET3_VERSION_SELECT, ( vmxnet->vd + VMXNET3_VD_VRRS ) ); | |
| writel ( VMXNET3_UPT_VERSION_SELECT, ( vmxnet->vd + VMXNET3_VD_UVRS ) ); | |
| return 0; | |
| } | |
| /** | |
| * Get permanent MAC address | |
| * | |
| * @v vmxnet vmxnet3 NIC | |
| * @v hw_addr Hardware address to fill in | |
| */ | |
| static void vmxnet3_get_hw_addr ( struct vmxnet3_nic *vmxnet, void *hw_addr ) { | |
| struct { | |
| uint32_t low; | |
| uint32_t high; | |
| } __attribute__ (( packed )) mac; | |
| mac.low = le32_to_cpu ( vmxnet3_command ( vmxnet, | |
| VMXNET3_CMD_GET_PERM_MAC_LO ) ); | |
| mac.high = le32_to_cpu ( vmxnet3_command ( vmxnet, | |
| VMXNET3_CMD_GET_PERM_MAC_HI ) ); | |
| memcpy ( hw_addr, &mac, ETH_ALEN ); | |
| } | |
| /** | |
| * Probe PCI device | |
| * | |
| * @v pci PCI device | |
| * @v id PCI ID | |
| * @ret rc Return status code | |
| */ | |
| static int vmxnet3_probe ( struct pci_device *pci ) { | |
| struct net_device *netdev; | |
| struct vmxnet3_nic *vmxnet; | |
| int rc; | |
| /* Allocate network device */ | |
| netdev = alloc_etherdev ( sizeof ( *vmxnet ) ); | |
| if ( ! netdev ) { | |
| rc = -ENOMEM; | |
| goto err_alloc_etherdev; | |
| } | |
| netdev_init ( netdev, &vmxnet3_operations ); | |
| vmxnet = netdev->priv; | |
| pci_set_drvdata ( pci, netdev ); | |
| netdev->dev = &pci->dev; | |
| memset ( vmxnet, 0, sizeof ( *vmxnet ) ); | |
| /* Fix up PCI device */ | |
| adjust_pci_device ( pci ); | |
| /* Map PCI BARs */ | |
| vmxnet->pt = pci_ioremap ( pci, pci_bar_start ( pci, VMXNET3_PT_BAR ), | |
| VMXNET3_PT_LEN ); | |
| if ( ! vmxnet->pt ) { | |
| rc = -ENODEV; | |
| goto err_ioremap_pt; | |
| } | |
| vmxnet->vd = pci_ioremap ( pci, pci_bar_start ( pci, VMXNET3_VD_BAR ), | |
| VMXNET3_VD_LEN ); | |
| if ( ! vmxnet->vd ) { | |
| rc = -ENODEV; | |
| goto err_ioremap_vd; | |
| } | |
| /* Version check */ | |
| if ( ( rc = vmxnet3_check_version ( vmxnet ) ) != 0 ) | |
| goto err_check_version; | |
| /* Reset device */ | |
| if ( ( rc = vmxnet3_command ( vmxnet, VMXNET3_CMD_RESET_DEV ) ) != 0 ) | |
| goto err_reset; | |
| /* Read initial MAC address */ | |
| vmxnet3_get_hw_addr ( vmxnet, &netdev->hw_addr ); | |
| /* Register network device */ | |
| if ( ( rc = register_netdev ( netdev ) ) != 0 ) { | |
| DBGC ( vmxnet, "VMXNET3 %p could not register net device: " | |
| "%s\n", vmxnet, strerror ( rc ) ); | |
| goto err_register_netdev; | |
| } | |
| /* Get initial link state */ | |
| vmxnet3_check_link ( netdev ); | |
| return 0; | |
| unregister_netdev ( netdev ); | |
| err_register_netdev: | |
| err_reset: | |
| err_check_version: | |
| iounmap ( vmxnet->vd ); | |
| err_ioremap_vd: | |
| iounmap ( vmxnet->pt ); | |
| err_ioremap_pt: | |
| netdev_nullify ( netdev ); | |
| netdev_put ( netdev ); | |
| err_alloc_etherdev: | |
| return rc; | |
| } | |
| /** | |
| * Remove PCI device | |
| * | |
| * @v pci PCI device | |
| */ | |
| static void vmxnet3_remove ( struct pci_device *pci ) { | |
| struct net_device *netdev = pci_get_drvdata ( pci ); | |
| struct vmxnet3_nic *vmxnet = netdev->priv; | |
| unregister_netdev ( netdev ); | |
| iounmap ( vmxnet->vd ); | |
| iounmap ( vmxnet->pt ); | |
| netdev_nullify ( netdev ); | |
| netdev_put ( netdev ); | |
| } | |
| /** vmxnet3 PCI IDs */ | |
| static struct pci_device_id vmxnet3_nics[] = { | |
| PCI_ROM ( 0x15ad, 0x07b0, "vmxnet3", "vmxnet3 virtual NIC", 0 ), | |
| }; | |
| /** vmxnet3 PCI driver */ | |
| struct pci_driver vmxnet3_driver __pci_driver = { | |
| .ids = vmxnet3_nics, | |
| .id_count = ( sizeof ( vmxnet3_nics ) / sizeof ( vmxnet3_nics[0] ) ), | |
| .probe = vmxnet3_probe, | |
| .remove = vmxnet3_remove, | |
| }; |