d7c5a620e2
Run on LLNW canaries and tested by pho@ gallatin: Using a 14-core, 28-HTT single socket E5-2697 v3 with a 40GbE MLX5 based ConnectX 4-LX NIC, I see an almost 12% improvement in received packet rate, and a larger improvement in bytes delivered all the way to userspace. When the host receiving 64 streams of netperf -H $DUT -t UDP_STREAM -- -m 1, I see, using nstat -I mce0 1 before the patch: InMpps OMpps InGbs OGbs err TCP Est %CPU syscalls csw irq GBfree 4.98 0.00 4.42 0.00 4235592 33 83.80 4720653 2149771 1235 247.32 4.73 0.00 4.20 0.00 4025260 33 82.99 4724900 2139833 1204 247.32 4.72 0.00 4.20 0.00 4035252 33 82.14 4719162 2132023 1264 247.32 4.71 0.00 4.21 0.00 4073206 33 83.68 4744973 2123317 1347 247.32 4.72 0.00 4.21 0.00 4061118 33 80.82 4713615 2188091 1490 247.32 4.72 0.00 4.21 0.00 4051675 33 85.29 4727399 2109011 1205 247.32 4.73 0.00 4.21 0.00 4039056 33 84.65 4724735 2102603 1053 247.32 After the patch InMpps OMpps InGbs OGbs err TCP Est %CPU syscalls csw irq GBfree 5.43 0.00 4.20 0.00 3313143 33 84.96 5434214 1900162 2656 245.51 5.43 0.00 4.20 0.00 3308527 33 85.24 5439695 1809382 2521 245.51 5.42 0.00 4.19 0.00 3316778 33 87.54 5416028 1805835 2256 245.51 5.42 0.00 4.19 0.00 3317673 33 90.44 5426044 1763056 2332 245.51 5.42 0.00 4.19 0.00 3314839 33 88.11 5435732 1792218 2499 245.52 5.44 0.00 4.19 0.00 3293228 33 91.84 5426301 1668597 2121 245.52 Similarly, netperf reports 230Mb/s before the patch, and 270Mb/s after the patch Reviewed by: gallatin Sponsored by: Limelight Networks Differential Revision: https://reviews.freebsd.org/D15366
5023 lines
125 KiB
C
5023 lines
125 KiB
C
/******************************************************************************
|
|
SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
|
|
Copyright (c) 2006-2013, Myricom Inc.
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
|
|
2. Neither the name of the Myricom Inc, nor the names of its
|
|
contributors may be used to endorse or promote products derived from
|
|
this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
***************************************************************************/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/linker.h>
|
|
#include <sys/firmware.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/kdb.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/module.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/sx.h>
|
|
#include <sys/taskqueue.h>
|
|
#include <sys/zlib.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_var.h>
|
|
#include <net/if_arp.h>
|
|
#include <net/ethernet.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_media.h>
|
|
|
|
#include <net/bpf.h>
|
|
|
|
#include <net/if_types.h>
|
|
#include <net/if_vlan_var.h>
|
|
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/ip6.h>
|
|
#include <netinet/tcp.h>
|
|
#include <netinet/tcp_lro.h>
|
|
#include <netinet6/ip6_var.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/in_cksum.h>
|
|
#include <machine/resource.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/smp.h>
|
|
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcivar.h>
|
|
#include <dev/pci/pci_private.h> /* XXX for pci_cfg_restore */
|
|
|
|
#include <vm/vm.h> /* for pmap_mapdev() */
|
|
#include <vm/pmap.h>
|
|
|
|
#if defined(__i386) || defined(__amd64)
|
|
#include <machine/specialreg.h>
|
|
#endif
|
|
|
|
#include <dev/mxge/mxge_mcp.h>
|
|
#include <dev/mxge/mcp_gen_header.h>
|
|
/*#define MXGE_FAKE_IFP*/
|
|
#include <dev/mxge/if_mxge_var.h>
|
|
#ifdef IFNET_BUF_RING
|
|
#include <sys/buf_ring.h>
|
|
#endif
|
|
|
|
#include "opt_inet.h"
|
|
#include "opt_inet6.h"
|
|
|
|
/* tunable params */
|
|
static int mxge_nvidia_ecrc_enable = 1;
|
|
static int mxge_force_firmware = 0;
|
|
static int mxge_intr_coal_delay = 30;
|
|
static int mxge_deassert_wait = 1;
|
|
static int mxge_flow_control = 1;
|
|
static int mxge_verbose = 0;
|
|
static int mxge_ticks;
|
|
static int mxge_max_slices = 1;
|
|
static int mxge_rss_hash_type = MXGEFW_RSS_HASH_TYPE_SRC_DST_PORT;
|
|
static int mxge_always_promisc = 0;
|
|
static int mxge_initial_mtu = ETHERMTU_JUMBO;
|
|
static int mxge_throttle = 0;
|
|
static char *mxge_fw_unaligned = "mxge_ethp_z8e";
|
|
static char *mxge_fw_aligned = "mxge_eth_z8e";
|
|
static char *mxge_fw_rss_aligned = "mxge_rss_eth_z8e";
|
|
static char *mxge_fw_rss_unaligned = "mxge_rss_ethp_z8e";
|
|
|
|
static int mxge_probe(device_t dev);
|
|
static int mxge_attach(device_t dev);
|
|
static int mxge_detach(device_t dev);
|
|
static int mxge_shutdown(device_t dev);
|
|
static void mxge_intr(void *arg);
|
|
|
|
static device_method_t mxge_methods[] =
|
|
{
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, mxge_probe),
|
|
DEVMETHOD(device_attach, mxge_attach),
|
|
DEVMETHOD(device_detach, mxge_detach),
|
|
DEVMETHOD(device_shutdown, mxge_shutdown),
|
|
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
static driver_t mxge_driver =
|
|
{
|
|
"mxge",
|
|
mxge_methods,
|
|
sizeof(mxge_softc_t),
|
|
};
|
|
|
|
static devclass_t mxge_devclass;
|
|
|
|
/* Declare ourselves to be a child of the PCI bus.*/
|
|
DRIVER_MODULE(mxge, pci, mxge_driver, mxge_devclass, 0, 0);
|
|
MODULE_DEPEND(mxge, firmware, 1, 1, 1);
|
|
MODULE_DEPEND(mxge, zlib, 1, 1, 1);
|
|
|
|
static int mxge_load_firmware(mxge_softc_t *sc, int adopt);
|
|
static int mxge_send_cmd(mxge_softc_t *sc, uint32_t cmd, mxge_cmd_t *data);
|
|
static int mxge_close(mxge_softc_t *sc, int down);
|
|
static int mxge_open(mxge_softc_t *sc);
|
|
static void mxge_tick(void *arg);
|
|
|
|
static int
|
|
mxge_probe(device_t dev)
|
|
{
|
|
int rev;
|
|
|
|
|
|
if ((pci_get_vendor(dev) == MXGE_PCI_VENDOR_MYRICOM) &&
|
|
((pci_get_device(dev) == MXGE_PCI_DEVICE_Z8E) ||
|
|
(pci_get_device(dev) == MXGE_PCI_DEVICE_Z8E_9))) {
|
|
rev = pci_get_revid(dev);
|
|
switch (rev) {
|
|
case MXGE_PCI_REV_Z8E:
|
|
device_set_desc(dev, "Myri10G-PCIE-8A");
|
|
break;
|
|
case MXGE_PCI_REV_Z8ES:
|
|
device_set_desc(dev, "Myri10G-PCIE-8B");
|
|
break;
|
|
default:
|
|
device_set_desc(dev, "Myri10G-PCIE-8??");
|
|
device_printf(dev, "Unrecognized rev %d NIC\n",
|
|
rev);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
return ENXIO;
|
|
}
|
|
|
|
static void
|
|
mxge_enable_wc(mxge_softc_t *sc)
|
|
{
|
|
#if defined(__i386) || defined(__amd64)
|
|
vm_offset_t len;
|
|
int err;
|
|
|
|
sc->wc = 1;
|
|
len = rman_get_size(sc->mem_res);
|
|
err = pmap_change_attr((vm_offset_t) sc->sram,
|
|
len, PAT_WRITE_COMBINING);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "pmap_change_attr failed, %d\n",
|
|
err);
|
|
sc->wc = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/* callback to get our DMA address */
|
|
static void
|
|
mxge_dmamap_callback(void *arg, bus_dma_segment_t *segs, int nsegs,
|
|
int error)
|
|
{
|
|
if (error == 0) {
|
|
*(bus_addr_t *) arg = segs->ds_addr;
|
|
}
|
|
}
|
|
|
|
static int
|
|
mxge_dma_alloc(mxge_softc_t *sc, mxge_dma_t *dma, size_t bytes,
|
|
bus_size_t alignment)
|
|
{
|
|
int err;
|
|
device_t dev = sc->dev;
|
|
bus_size_t boundary, maxsegsize;
|
|
|
|
if (bytes > 4096 && alignment == 4096) {
|
|
boundary = 0;
|
|
maxsegsize = bytes;
|
|
} else {
|
|
boundary = 4096;
|
|
maxsegsize = 4096;
|
|
}
|
|
|
|
/* allocate DMAable memory tags */
|
|
err = bus_dma_tag_create(sc->parent_dmat, /* parent */
|
|
alignment, /* alignment */
|
|
boundary, /* boundary */
|
|
BUS_SPACE_MAXADDR, /* low */
|
|
BUS_SPACE_MAXADDR, /* high */
|
|
NULL, NULL, /* filter */
|
|
bytes, /* maxsize */
|
|
1, /* num segs */
|
|
maxsegsize, /* maxsegsize */
|
|
BUS_DMA_COHERENT, /* flags */
|
|
NULL, NULL, /* lock */
|
|
&dma->dmat); /* tag */
|
|
if (err != 0) {
|
|
device_printf(dev, "couldn't alloc tag (err = %d)\n", err);
|
|
return err;
|
|
}
|
|
|
|
/* allocate DMAable memory & map */
|
|
err = bus_dmamem_alloc(dma->dmat, &dma->addr,
|
|
(BUS_DMA_WAITOK | BUS_DMA_COHERENT
|
|
| BUS_DMA_ZERO), &dma->map);
|
|
if (err != 0) {
|
|
device_printf(dev, "couldn't alloc mem (err = %d)\n", err);
|
|
goto abort_with_dmat;
|
|
}
|
|
|
|
/* load the memory */
|
|
err = bus_dmamap_load(dma->dmat, dma->map, dma->addr, bytes,
|
|
mxge_dmamap_callback,
|
|
(void *)&dma->bus_addr, 0);
|
|
if (err != 0) {
|
|
device_printf(dev, "couldn't load map (err = %d)\n", err);
|
|
goto abort_with_mem;
|
|
}
|
|
return 0;
|
|
|
|
abort_with_mem:
|
|
bus_dmamem_free(dma->dmat, dma->addr, dma->map);
|
|
abort_with_dmat:
|
|
(void)bus_dma_tag_destroy(dma->dmat);
|
|
return err;
|
|
}
|
|
|
|
|
|
static void
|
|
mxge_dma_free(mxge_dma_t *dma)
|
|
{
|
|
bus_dmamap_unload(dma->dmat, dma->map);
|
|
bus_dmamem_free(dma->dmat, dma->addr, dma->map);
|
|
(void)bus_dma_tag_destroy(dma->dmat);
|
|
}
|
|
|
|
/*
|
|
* The eeprom strings on the lanaiX have the format
|
|
* SN=x\0
|
|
* MAC=x:x:x:x:x:x\0
|
|
* PC=text\0
|
|
*/
|
|
|
|
static int
|
|
mxge_parse_strings(mxge_softc_t *sc)
|
|
{
|
|
char *ptr;
|
|
int i, found_mac, found_sn2;
|
|
char *endptr;
|
|
|
|
ptr = sc->eeprom_strings;
|
|
found_mac = 0;
|
|
found_sn2 = 0;
|
|
while (*ptr != '\0') {
|
|
if (strncmp(ptr, "MAC=", 4) == 0) {
|
|
ptr += 4;
|
|
for (i = 0;;) {
|
|
sc->mac_addr[i] = strtoul(ptr, &endptr, 16);
|
|
if (endptr - ptr != 2)
|
|
goto abort;
|
|
ptr = endptr;
|
|
if (++i == 6)
|
|
break;
|
|
if (*ptr++ != ':')
|
|
goto abort;
|
|
}
|
|
found_mac = 1;
|
|
} else if (strncmp(ptr, "PC=", 3) == 0) {
|
|
ptr += 3;
|
|
strlcpy(sc->product_code_string, ptr,
|
|
sizeof(sc->product_code_string));
|
|
} else if (!found_sn2 && (strncmp(ptr, "SN=", 3) == 0)) {
|
|
ptr += 3;
|
|
strlcpy(sc->serial_number_string, ptr,
|
|
sizeof(sc->serial_number_string));
|
|
} else if (strncmp(ptr, "SN2=", 4) == 0) {
|
|
/* SN2 takes precedence over SN */
|
|
ptr += 4;
|
|
found_sn2 = 1;
|
|
strlcpy(sc->serial_number_string, ptr,
|
|
sizeof(sc->serial_number_string));
|
|
}
|
|
while (*ptr++ != '\0') {}
|
|
}
|
|
|
|
if (found_mac)
|
|
return 0;
|
|
|
|
abort:
|
|
device_printf(sc->dev, "failed to parse eeprom_strings\n");
|
|
|
|
return ENXIO;
|
|
}
|
|
|
|
#if defined __i386 || defined i386 || defined __i386__ || defined __x86_64__
|
|
static void
|
|
mxge_enable_nvidia_ecrc(mxge_softc_t *sc)
|
|
{
|
|
uint32_t val;
|
|
unsigned long base, off;
|
|
char *va, *cfgptr;
|
|
device_t pdev, mcp55;
|
|
uint16_t vendor_id, device_id, word;
|
|
uintptr_t bus, slot, func, ivend, idev;
|
|
uint32_t *ptr32;
|
|
|
|
|
|
if (!mxge_nvidia_ecrc_enable)
|
|
return;
|
|
|
|
pdev = device_get_parent(device_get_parent(sc->dev));
|
|
if (pdev == NULL) {
|
|
device_printf(sc->dev, "could not find parent?\n");
|
|
return;
|
|
}
|
|
vendor_id = pci_read_config(pdev, PCIR_VENDOR, 2);
|
|
device_id = pci_read_config(pdev, PCIR_DEVICE, 2);
|
|
|
|
if (vendor_id != 0x10de)
|
|
return;
|
|
|
|
base = 0;
|
|
|
|
if (device_id == 0x005d) {
|
|
/* ck804, base address is magic */
|
|
base = 0xe0000000UL;
|
|
} else if (device_id >= 0x0374 && device_id <= 0x378) {
|
|
/* mcp55, base address stored in chipset */
|
|
mcp55 = pci_find_bsf(0, 0, 0);
|
|
if (mcp55 &&
|
|
0x10de == pci_read_config(mcp55, PCIR_VENDOR, 2) &&
|
|
0x0369 == pci_read_config(mcp55, PCIR_DEVICE, 2)) {
|
|
word = pci_read_config(mcp55, 0x90, 2);
|
|
base = ((unsigned long)word & 0x7ffeU) << 25;
|
|
}
|
|
}
|
|
if (!base)
|
|
return;
|
|
|
|
/* XXXX
|
|
Test below is commented because it is believed that doing
|
|
config read/write beyond 0xff will access the config space
|
|
for the next larger function. Uncomment this and remove
|
|
the hacky pmap_mapdev() way of accessing config space when
|
|
FreeBSD grows support for extended pcie config space access
|
|
*/
|
|
#if 0
|
|
/* See if we can, by some miracle, access the extended
|
|
config space */
|
|
val = pci_read_config(pdev, 0x178, 4);
|
|
if (val != 0xffffffff) {
|
|
val |= 0x40;
|
|
pci_write_config(pdev, 0x178, val, 4);
|
|
return;
|
|
}
|
|
#endif
|
|
/* Rather than using normal pci config space writes, we must
|
|
* map the Nvidia config space ourselves. This is because on
|
|
* opteron/nvidia class machine the 0xe000000 mapping is
|
|
* handled by the nvidia chipset, that means the internal PCI
|
|
* device (the on-chip northbridge), or the amd-8131 bridge
|
|
* and things behind them are not visible by this method.
|
|
*/
|
|
|
|
BUS_READ_IVAR(device_get_parent(pdev), pdev,
|
|
PCI_IVAR_BUS, &bus);
|
|
BUS_READ_IVAR(device_get_parent(pdev), pdev,
|
|
PCI_IVAR_SLOT, &slot);
|
|
BUS_READ_IVAR(device_get_parent(pdev), pdev,
|
|
PCI_IVAR_FUNCTION, &func);
|
|
BUS_READ_IVAR(device_get_parent(pdev), pdev,
|
|
PCI_IVAR_VENDOR, &ivend);
|
|
BUS_READ_IVAR(device_get_parent(pdev), pdev,
|
|
PCI_IVAR_DEVICE, &idev);
|
|
|
|
off = base
|
|
+ 0x00100000UL * (unsigned long)bus
|
|
+ 0x00001000UL * (unsigned long)(func
|
|
+ 8 * slot);
|
|
|
|
/* map it into the kernel */
|
|
va = pmap_mapdev(trunc_page((vm_paddr_t)off), PAGE_SIZE);
|
|
|
|
|
|
if (va == NULL) {
|
|
device_printf(sc->dev, "pmap_kenter_temporary didn't\n");
|
|
return;
|
|
}
|
|
/* get a pointer to the config space mapped into the kernel */
|
|
cfgptr = va + (off & PAGE_MASK);
|
|
|
|
/* make sure that we can really access it */
|
|
vendor_id = *(uint16_t *)(cfgptr + PCIR_VENDOR);
|
|
device_id = *(uint16_t *)(cfgptr + PCIR_DEVICE);
|
|
if (! (vendor_id == ivend && device_id == idev)) {
|
|
device_printf(sc->dev, "mapping failed: 0x%x:0x%x\n",
|
|
vendor_id, device_id);
|
|
pmap_unmapdev((vm_offset_t)va, PAGE_SIZE);
|
|
return;
|
|
}
|
|
|
|
ptr32 = (uint32_t*)(cfgptr + 0x178);
|
|
val = *ptr32;
|
|
|
|
if (val == 0xffffffff) {
|
|
device_printf(sc->dev, "extended mapping failed\n");
|
|
pmap_unmapdev((vm_offset_t)va, PAGE_SIZE);
|
|
return;
|
|
}
|
|
*ptr32 = val | 0x40;
|
|
pmap_unmapdev((vm_offset_t)va, PAGE_SIZE);
|
|
if (mxge_verbose)
|
|
device_printf(sc->dev,
|
|
"Enabled ECRC on upstream Nvidia bridge "
|
|
"at %d:%d:%d\n",
|
|
(int)bus, (int)slot, (int)func);
|
|
return;
|
|
}
|
|
#else
|
|
static void
|
|
mxge_enable_nvidia_ecrc(mxge_softc_t *sc)
|
|
{
|
|
device_printf(sc->dev,
|
|
"Nforce 4 chipset on non-x86/amd64!?!?!\n");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
|
|
static int
|
|
mxge_dma_test(mxge_softc_t *sc, int test_type)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
bus_addr_t dmatest_bus = sc->dmabench_dma.bus_addr;
|
|
int status;
|
|
uint32_t len;
|
|
char *test = " ";
|
|
|
|
|
|
/* Run a small DMA test.
|
|
* The magic multipliers to the length tell the firmware
|
|
* to do DMA read, write, or read+write tests. The
|
|
* results are returned in cmd.data0. The upper 16
|
|
* bits of the return is the number of transfers completed.
|
|
* The lower 16 bits is the time in 0.5us ticks that the
|
|
* transfers took to complete.
|
|
*/
|
|
|
|
len = sc->tx_boundary;
|
|
|
|
cmd.data0 = MXGE_LOWPART_TO_U32(dmatest_bus);
|
|
cmd.data1 = MXGE_HIGHPART_TO_U32(dmatest_bus);
|
|
cmd.data2 = len * 0x10000;
|
|
status = mxge_send_cmd(sc, test_type, &cmd);
|
|
if (status != 0) {
|
|
test = "read";
|
|
goto abort;
|
|
}
|
|
sc->read_dma = ((cmd.data0>>16) * len * 2) /
|
|
(cmd.data0 & 0xffff);
|
|
cmd.data0 = MXGE_LOWPART_TO_U32(dmatest_bus);
|
|
cmd.data1 = MXGE_HIGHPART_TO_U32(dmatest_bus);
|
|
cmd.data2 = len * 0x1;
|
|
status = mxge_send_cmd(sc, test_type, &cmd);
|
|
if (status != 0) {
|
|
test = "write";
|
|
goto abort;
|
|
}
|
|
sc->write_dma = ((cmd.data0>>16) * len * 2) /
|
|
(cmd.data0 & 0xffff);
|
|
|
|
cmd.data0 = MXGE_LOWPART_TO_U32(dmatest_bus);
|
|
cmd.data1 = MXGE_HIGHPART_TO_U32(dmatest_bus);
|
|
cmd.data2 = len * 0x10001;
|
|
status = mxge_send_cmd(sc, test_type, &cmd);
|
|
if (status != 0) {
|
|
test = "read/write";
|
|
goto abort;
|
|
}
|
|
sc->read_write_dma = ((cmd.data0>>16) * len * 2 * 2) /
|
|
(cmd.data0 & 0xffff);
|
|
|
|
abort:
|
|
if (status != 0 && test_type != MXGEFW_CMD_UNALIGNED_TEST)
|
|
device_printf(sc->dev, "DMA %s benchmark failed: %d\n",
|
|
test, status);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* The Lanai Z8E PCI-E interface achieves higher Read-DMA throughput
|
|
* when the PCI-E Completion packets are aligned on an 8-byte
|
|
* boundary. Some PCI-E chip sets always align Completion packets; on
|
|
* the ones that do not, the alignment can be enforced by enabling
|
|
* ECRC generation (if supported).
|
|
*
|
|
* When PCI-E Completion packets are not aligned, it is actually more
|
|
* efficient to limit Read-DMA transactions to 2KB, rather than 4KB.
|
|
*
|
|
* If the driver can neither enable ECRC nor verify that it has
|
|
* already been enabled, then it must use a firmware image which works
|
|
* around unaligned completion packets (ethp_z8e.dat), and it should
|
|
* also ensure that it never gives the device a Read-DMA which is
|
|
* larger than 2KB by setting the tx_boundary to 2KB. If ECRC is
|
|
* enabled, then the driver should use the aligned (eth_z8e.dat)
|
|
* firmware image, and set tx_boundary to 4KB.
|
|
*/
|
|
|
|
static int
|
|
mxge_firmware_probe(mxge_softc_t *sc)
|
|
{
|
|
device_t dev = sc->dev;
|
|
int reg, status;
|
|
uint16_t pectl;
|
|
|
|
sc->tx_boundary = 4096;
|
|
/*
|
|
* Verify the max read request size was set to 4KB
|
|
* before trying the test with 4KB.
|
|
*/
|
|
if (pci_find_cap(dev, PCIY_EXPRESS, ®) == 0) {
|
|
pectl = pci_read_config(dev, reg + 0x8, 2);
|
|
if ((pectl & (5 << 12)) != (5 << 12)) {
|
|
device_printf(dev, "Max Read Req. size != 4k (0x%x\n",
|
|
pectl);
|
|
sc->tx_boundary = 2048;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* load the optimized firmware (which assumes aligned PCIe
|
|
* completions) in order to see if it works on this host.
|
|
*/
|
|
sc->fw_name = mxge_fw_aligned;
|
|
status = mxge_load_firmware(sc, 1);
|
|
if (status != 0) {
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Enable ECRC if possible
|
|
*/
|
|
mxge_enable_nvidia_ecrc(sc);
|
|
|
|
/*
|
|
* Run a DMA test which watches for unaligned completions and
|
|
* aborts on the first one seen. Not required on Z8ES or newer.
|
|
*/
|
|
if (pci_get_revid(sc->dev) >= MXGE_PCI_REV_Z8ES)
|
|
return 0;
|
|
status = mxge_dma_test(sc, MXGEFW_CMD_UNALIGNED_TEST);
|
|
if (status == 0)
|
|
return 0; /* keep the aligned firmware */
|
|
|
|
if (status != E2BIG)
|
|
device_printf(dev, "DMA test failed: %d\n", status);
|
|
if (status == ENOSYS)
|
|
device_printf(dev, "Falling back to ethp! "
|
|
"Please install up to date fw\n");
|
|
return status;
|
|
}
|
|
|
|
static int
|
|
mxge_select_firmware(mxge_softc_t *sc)
|
|
{
|
|
int aligned = 0;
|
|
int force_firmware = mxge_force_firmware;
|
|
|
|
if (sc->throttle)
|
|
force_firmware = sc->throttle;
|
|
|
|
if (force_firmware != 0) {
|
|
if (force_firmware == 1)
|
|
aligned = 1;
|
|
else
|
|
aligned = 0;
|
|
if (mxge_verbose)
|
|
device_printf(sc->dev,
|
|
"Assuming %s completions (forced)\n",
|
|
aligned ? "aligned" : "unaligned");
|
|
goto abort;
|
|
}
|
|
|
|
/* if the PCIe link width is 4 or less, we can use the aligned
|
|
firmware and skip any checks */
|
|
if (sc->link_width != 0 && sc->link_width <= 4) {
|
|
device_printf(sc->dev,
|
|
"PCIe x%d Link, expect reduced performance\n",
|
|
sc->link_width);
|
|
aligned = 1;
|
|
goto abort;
|
|
}
|
|
|
|
if (0 == mxge_firmware_probe(sc))
|
|
return 0;
|
|
|
|
abort:
|
|
if (aligned) {
|
|
sc->fw_name = mxge_fw_aligned;
|
|
sc->tx_boundary = 4096;
|
|
} else {
|
|
sc->fw_name = mxge_fw_unaligned;
|
|
sc->tx_boundary = 2048;
|
|
}
|
|
return (mxge_load_firmware(sc, 0));
|
|
}
|
|
|
|
static int
|
|
mxge_validate_firmware(mxge_softc_t *sc, const mcp_gen_header_t *hdr)
|
|
{
|
|
|
|
|
|
if (be32toh(hdr->mcp_type) != MCP_TYPE_ETH) {
|
|
device_printf(sc->dev, "Bad firmware type: 0x%x\n",
|
|
be32toh(hdr->mcp_type));
|
|
return EIO;
|
|
}
|
|
|
|
/* save firmware version for sysctl */
|
|
strlcpy(sc->fw_version, hdr->version, sizeof(sc->fw_version));
|
|
if (mxge_verbose)
|
|
device_printf(sc->dev, "firmware id: %s\n", hdr->version);
|
|
|
|
sscanf(sc->fw_version, "%d.%d.%d", &sc->fw_ver_major,
|
|
&sc->fw_ver_minor, &sc->fw_ver_tiny);
|
|
|
|
if (!(sc->fw_ver_major == MXGEFW_VERSION_MAJOR
|
|
&& sc->fw_ver_minor == MXGEFW_VERSION_MINOR)) {
|
|
device_printf(sc->dev, "Found firmware version %s\n",
|
|
sc->fw_version);
|
|
device_printf(sc->dev, "Driver needs %d.%d\n",
|
|
MXGEFW_VERSION_MAJOR, MXGEFW_VERSION_MINOR);
|
|
return EINVAL;
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
static void *
|
|
z_alloc(void *nil, u_int items, u_int size)
|
|
{
|
|
void *ptr;
|
|
|
|
ptr = malloc(items * size, M_TEMP, M_NOWAIT);
|
|
return ptr;
|
|
}
|
|
|
|
static void
|
|
z_free(void *nil, void *ptr)
|
|
{
|
|
free(ptr, M_TEMP);
|
|
}
|
|
|
|
|
|
static int
|
|
mxge_load_firmware_helper(mxge_softc_t *sc, uint32_t *limit)
|
|
{
|
|
z_stream zs;
|
|
char *inflate_buffer;
|
|
const struct firmware *fw;
|
|
const mcp_gen_header_t *hdr;
|
|
unsigned hdr_offset;
|
|
int status;
|
|
unsigned int i;
|
|
char dummy;
|
|
size_t fw_len;
|
|
|
|
fw = firmware_get(sc->fw_name);
|
|
if (fw == NULL) {
|
|
device_printf(sc->dev, "Could not find firmware image %s\n",
|
|
sc->fw_name);
|
|
return ENOENT;
|
|
}
|
|
|
|
|
|
|
|
/* setup zlib and decompress f/w */
|
|
bzero(&zs, sizeof (zs));
|
|
zs.zalloc = z_alloc;
|
|
zs.zfree = z_free;
|
|
status = inflateInit(&zs);
|
|
if (status != Z_OK) {
|
|
status = EIO;
|
|
goto abort_with_fw;
|
|
}
|
|
|
|
/* the uncompressed size is stored as the firmware version,
|
|
which would otherwise go unused */
|
|
fw_len = (size_t) fw->version;
|
|
inflate_buffer = malloc(fw_len, M_TEMP, M_NOWAIT);
|
|
if (inflate_buffer == NULL)
|
|
goto abort_with_zs;
|
|
zs.avail_in = fw->datasize;
|
|
zs.next_in = __DECONST(char *, fw->data);
|
|
zs.avail_out = fw_len;
|
|
zs.next_out = inflate_buffer;
|
|
status = inflate(&zs, Z_FINISH);
|
|
if (status != Z_STREAM_END) {
|
|
device_printf(sc->dev, "zlib %d\n", status);
|
|
status = EIO;
|
|
goto abort_with_buffer;
|
|
}
|
|
|
|
/* check id */
|
|
hdr_offset = htobe32(*(const uint32_t *)
|
|
(inflate_buffer + MCP_HEADER_PTR_OFFSET));
|
|
if ((hdr_offset & 3) || hdr_offset + sizeof(*hdr) > fw_len) {
|
|
device_printf(sc->dev, "Bad firmware file");
|
|
status = EIO;
|
|
goto abort_with_buffer;
|
|
}
|
|
hdr = (const void*)(inflate_buffer + hdr_offset);
|
|
|
|
status = mxge_validate_firmware(sc, hdr);
|
|
if (status != 0)
|
|
goto abort_with_buffer;
|
|
|
|
/* Copy the inflated firmware to NIC SRAM. */
|
|
for (i = 0; i < fw_len; i += 256) {
|
|
mxge_pio_copy(sc->sram + MXGE_FW_OFFSET + i,
|
|
inflate_buffer + i,
|
|
min(256U, (unsigned)(fw_len - i)));
|
|
wmb();
|
|
dummy = *sc->sram;
|
|
wmb();
|
|
}
|
|
|
|
*limit = fw_len;
|
|
status = 0;
|
|
abort_with_buffer:
|
|
free(inflate_buffer, M_TEMP);
|
|
abort_with_zs:
|
|
inflateEnd(&zs);
|
|
abort_with_fw:
|
|
firmware_put(fw, FIRMWARE_UNLOAD);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Enable or disable periodic RDMAs from the host to make certain
|
|
* chipsets resend dropped PCIe messages
|
|
*/
|
|
|
|
static void
|
|
mxge_dummy_rdma(mxge_softc_t *sc, int enable)
|
|
{
|
|
char buf_bytes[72];
|
|
volatile uint32_t *confirm;
|
|
volatile char *submit;
|
|
uint32_t *buf, dma_low, dma_high;
|
|
int i;
|
|
|
|
buf = (uint32_t *)((unsigned long)(buf_bytes + 7) & ~7UL);
|
|
|
|
/* clear confirmation addr */
|
|
confirm = (volatile uint32_t *)sc->cmd;
|
|
*confirm = 0;
|
|
wmb();
|
|
|
|
/* send an rdma command to the PCIe engine, and wait for the
|
|
response in the confirmation address. The firmware should
|
|
write a -1 there to indicate it is alive and well
|
|
*/
|
|
|
|
dma_low = MXGE_LOWPART_TO_U32(sc->cmd_dma.bus_addr);
|
|
dma_high = MXGE_HIGHPART_TO_U32(sc->cmd_dma.bus_addr);
|
|
buf[0] = htobe32(dma_high); /* confirm addr MSW */
|
|
buf[1] = htobe32(dma_low); /* confirm addr LSW */
|
|
buf[2] = htobe32(0xffffffff); /* confirm data */
|
|
dma_low = MXGE_LOWPART_TO_U32(sc->zeropad_dma.bus_addr);
|
|
dma_high = MXGE_HIGHPART_TO_U32(sc->zeropad_dma.bus_addr);
|
|
buf[3] = htobe32(dma_high); /* dummy addr MSW */
|
|
buf[4] = htobe32(dma_low); /* dummy addr LSW */
|
|
buf[5] = htobe32(enable); /* enable? */
|
|
|
|
|
|
submit = (volatile char *)(sc->sram + MXGEFW_BOOT_DUMMY_RDMA);
|
|
|
|
mxge_pio_copy(submit, buf, 64);
|
|
wmb();
|
|
DELAY(1000);
|
|
wmb();
|
|
i = 0;
|
|
while (*confirm != 0xffffffff && i < 20) {
|
|
DELAY(1000);
|
|
i++;
|
|
}
|
|
if (*confirm != 0xffffffff) {
|
|
device_printf(sc->dev, "dummy rdma %s failed (%p = 0x%x)",
|
|
(enable ? "enable" : "disable"), confirm,
|
|
*confirm);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static int
|
|
mxge_send_cmd(mxge_softc_t *sc, uint32_t cmd, mxge_cmd_t *data)
|
|
{
|
|
mcp_cmd_t *buf;
|
|
char buf_bytes[sizeof(*buf) + 8];
|
|
volatile mcp_cmd_response_t *response = sc->cmd;
|
|
volatile char *cmd_addr = sc->sram + MXGEFW_ETH_CMD;
|
|
uint32_t dma_low, dma_high;
|
|
int err, sleep_total = 0;
|
|
|
|
/* ensure buf is aligned to 8 bytes */
|
|
buf = (mcp_cmd_t *)((unsigned long)(buf_bytes + 7) & ~7UL);
|
|
|
|
buf->data0 = htobe32(data->data0);
|
|
buf->data1 = htobe32(data->data1);
|
|
buf->data2 = htobe32(data->data2);
|
|
buf->cmd = htobe32(cmd);
|
|
dma_low = MXGE_LOWPART_TO_U32(sc->cmd_dma.bus_addr);
|
|
dma_high = MXGE_HIGHPART_TO_U32(sc->cmd_dma.bus_addr);
|
|
|
|
buf->response_addr.low = htobe32(dma_low);
|
|
buf->response_addr.high = htobe32(dma_high);
|
|
mtx_lock(&sc->cmd_mtx);
|
|
response->result = 0xffffffff;
|
|
wmb();
|
|
mxge_pio_copy((volatile void *)cmd_addr, buf, sizeof (*buf));
|
|
|
|
/* wait up to 20ms */
|
|
err = EAGAIN;
|
|
for (sleep_total = 0; sleep_total < 20; sleep_total++) {
|
|
bus_dmamap_sync(sc->cmd_dma.dmat,
|
|
sc->cmd_dma.map, BUS_DMASYNC_POSTREAD);
|
|
wmb();
|
|
switch (be32toh(response->result)) {
|
|
case 0:
|
|
data->data0 = be32toh(response->data);
|
|
err = 0;
|
|
break;
|
|
case 0xffffffff:
|
|
DELAY(1000);
|
|
break;
|
|
case MXGEFW_CMD_UNKNOWN:
|
|
err = ENOSYS;
|
|
break;
|
|
case MXGEFW_CMD_ERROR_UNALIGNED:
|
|
err = E2BIG;
|
|
break;
|
|
case MXGEFW_CMD_ERROR_BUSY:
|
|
err = EBUSY;
|
|
break;
|
|
case MXGEFW_CMD_ERROR_I2C_ABSENT:
|
|
err = ENXIO;
|
|
break;
|
|
default:
|
|
device_printf(sc->dev,
|
|
"mxge: command %d "
|
|
"failed, result = %d\n",
|
|
cmd, be32toh(response->result));
|
|
err = ENXIO;
|
|
break;
|
|
}
|
|
if (err != EAGAIN)
|
|
break;
|
|
}
|
|
if (err == EAGAIN)
|
|
device_printf(sc->dev, "mxge: command %d timed out"
|
|
"result = %d\n",
|
|
cmd, be32toh(response->result));
|
|
mtx_unlock(&sc->cmd_mtx);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
mxge_adopt_running_firmware(mxge_softc_t *sc)
|
|
{
|
|
struct mcp_gen_header *hdr;
|
|
const size_t bytes = sizeof (struct mcp_gen_header);
|
|
size_t hdr_offset;
|
|
int status;
|
|
|
|
/* find running firmware header */
|
|
hdr_offset = htobe32(*(volatile uint32_t *)
|
|
(sc->sram + MCP_HEADER_PTR_OFFSET));
|
|
|
|
if ((hdr_offset & 3) || hdr_offset + sizeof(*hdr) > sc->sram_size) {
|
|
device_printf(sc->dev,
|
|
"Running firmware has bad header offset (%d)\n",
|
|
(int)hdr_offset);
|
|
return EIO;
|
|
}
|
|
|
|
/* copy header of running firmware from SRAM to host memory to
|
|
* validate firmware */
|
|
hdr = malloc(bytes, M_DEVBUF, M_NOWAIT);
|
|
if (hdr == NULL) {
|
|
device_printf(sc->dev, "could not malloc firmware hdr\n");
|
|
return ENOMEM;
|
|
}
|
|
bus_space_read_region_1(rman_get_bustag(sc->mem_res),
|
|
rman_get_bushandle(sc->mem_res),
|
|
hdr_offset, (char *)hdr, bytes);
|
|
status = mxge_validate_firmware(sc, hdr);
|
|
free(hdr, M_DEVBUF);
|
|
|
|
/*
|
|
* check to see if adopted firmware has bug where adopting
|
|
* it will cause broadcasts to be filtered unless the NIC
|
|
* is kept in ALLMULTI mode
|
|
*/
|
|
if (sc->fw_ver_major == 1 && sc->fw_ver_minor == 4 &&
|
|
sc->fw_ver_tiny >= 4 && sc->fw_ver_tiny <= 11) {
|
|
sc->adopted_rx_filter_bug = 1;
|
|
device_printf(sc->dev, "Adopting fw %d.%d.%d: "
|
|
"working around rx filter bug\n",
|
|
sc->fw_ver_major, sc->fw_ver_minor,
|
|
sc->fw_ver_tiny);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
static int
|
|
mxge_load_firmware(mxge_softc_t *sc, int adopt)
|
|
{
|
|
volatile uint32_t *confirm;
|
|
volatile char *submit;
|
|
char buf_bytes[72];
|
|
uint32_t *buf, size, dma_low, dma_high;
|
|
int status, i;
|
|
|
|
buf = (uint32_t *)((unsigned long)(buf_bytes + 7) & ~7UL);
|
|
|
|
size = sc->sram_size;
|
|
status = mxge_load_firmware_helper(sc, &size);
|
|
if (status) {
|
|
if (!adopt)
|
|
return status;
|
|
/* Try to use the currently running firmware, if
|
|
it is new enough */
|
|
status = mxge_adopt_running_firmware(sc);
|
|
if (status) {
|
|
device_printf(sc->dev,
|
|
"failed to adopt running firmware\n");
|
|
return status;
|
|
}
|
|
device_printf(sc->dev,
|
|
"Successfully adopted running firmware\n");
|
|
if (sc->tx_boundary == 4096) {
|
|
device_printf(sc->dev,
|
|
"Using firmware currently running on NIC"
|
|
". For optimal\n");
|
|
device_printf(sc->dev,
|
|
"performance consider loading optimized "
|
|
"firmware\n");
|
|
}
|
|
sc->fw_name = mxge_fw_unaligned;
|
|
sc->tx_boundary = 2048;
|
|
return 0;
|
|
}
|
|
/* clear confirmation addr */
|
|
confirm = (volatile uint32_t *)sc->cmd;
|
|
*confirm = 0;
|
|
wmb();
|
|
/* send a reload command to the bootstrap MCP, and wait for the
|
|
response in the confirmation address. The firmware should
|
|
write a -1 there to indicate it is alive and well
|
|
*/
|
|
|
|
dma_low = MXGE_LOWPART_TO_U32(sc->cmd_dma.bus_addr);
|
|
dma_high = MXGE_HIGHPART_TO_U32(sc->cmd_dma.bus_addr);
|
|
|
|
buf[0] = htobe32(dma_high); /* confirm addr MSW */
|
|
buf[1] = htobe32(dma_low); /* confirm addr LSW */
|
|
buf[2] = htobe32(0xffffffff); /* confirm data */
|
|
|
|
/* FIX: All newest firmware should un-protect the bottom of
|
|
the sram before handoff. However, the very first interfaces
|
|
do not. Therefore the handoff copy must skip the first 8 bytes
|
|
*/
|
|
/* where the code starts*/
|
|
buf[3] = htobe32(MXGE_FW_OFFSET + 8);
|
|
buf[4] = htobe32(size - 8); /* length of code */
|
|
buf[5] = htobe32(8); /* where to copy to */
|
|
buf[6] = htobe32(0); /* where to jump to */
|
|
|
|
submit = (volatile char *)(sc->sram + MXGEFW_BOOT_HANDOFF);
|
|
mxge_pio_copy(submit, buf, 64);
|
|
wmb();
|
|
DELAY(1000);
|
|
wmb();
|
|
i = 0;
|
|
while (*confirm != 0xffffffff && i < 20) {
|
|
DELAY(1000*10);
|
|
i++;
|
|
bus_dmamap_sync(sc->cmd_dma.dmat,
|
|
sc->cmd_dma.map, BUS_DMASYNC_POSTREAD);
|
|
}
|
|
if (*confirm != 0xffffffff) {
|
|
device_printf(sc->dev,"handoff failed (%p = 0x%x)",
|
|
confirm, *confirm);
|
|
|
|
return ENXIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mxge_update_mac_address(mxge_softc_t *sc)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
uint8_t *addr = sc->mac_addr;
|
|
int status;
|
|
|
|
|
|
cmd.data0 = ((addr[0] << 24) | (addr[1] << 16)
|
|
| (addr[2] << 8) | addr[3]);
|
|
|
|
cmd.data1 = ((addr[4] << 8) | (addr[5]));
|
|
|
|
status = mxge_send_cmd(sc, MXGEFW_SET_MAC_ADDRESS, &cmd);
|
|
return status;
|
|
}
|
|
|
|
static int
|
|
mxge_change_pause(mxge_softc_t *sc, int pause)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
int status;
|
|
|
|
if (pause)
|
|
status = mxge_send_cmd(sc, MXGEFW_ENABLE_FLOW_CONTROL,
|
|
&cmd);
|
|
else
|
|
status = mxge_send_cmd(sc, MXGEFW_DISABLE_FLOW_CONTROL,
|
|
&cmd);
|
|
|
|
if (status) {
|
|
device_printf(sc->dev, "Failed to set flow control mode\n");
|
|
return ENXIO;
|
|
}
|
|
sc->pause = pause;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
mxge_change_promisc(mxge_softc_t *sc, int promisc)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
int status;
|
|
|
|
if (mxge_always_promisc)
|
|
promisc = 1;
|
|
|
|
if (promisc)
|
|
status = mxge_send_cmd(sc, MXGEFW_ENABLE_PROMISC,
|
|
&cmd);
|
|
else
|
|
status = mxge_send_cmd(sc, MXGEFW_DISABLE_PROMISC,
|
|
&cmd);
|
|
|
|
if (status) {
|
|
device_printf(sc->dev, "Failed to set promisc mode\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
mxge_set_multicast_list(mxge_softc_t *sc)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
struct ifmultiaddr *ifma;
|
|
struct ifnet *ifp = sc->ifp;
|
|
int err;
|
|
|
|
/* This firmware is known to not support multicast */
|
|
if (!sc->fw_multicast_support)
|
|
return;
|
|
|
|
/* Disable multicast filtering while we play with the lists*/
|
|
err = mxge_send_cmd(sc, MXGEFW_ENABLE_ALLMULTI, &cmd);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Failed MXGEFW_ENABLE_ALLMULTI,"
|
|
" error status: %d\n", err);
|
|
return;
|
|
}
|
|
|
|
if (sc->adopted_rx_filter_bug)
|
|
return;
|
|
|
|
if (ifp->if_flags & IFF_ALLMULTI)
|
|
/* request to disable multicast filtering, so quit here */
|
|
return;
|
|
|
|
/* Flush all the filters */
|
|
|
|
err = mxge_send_cmd(sc, MXGEFW_LEAVE_ALL_MULTICAST_GROUPS, &cmd);
|
|
if (err != 0) {
|
|
device_printf(sc->dev,
|
|
"Failed MXGEFW_LEAVE_ALL_MULTICAST_GROUPS"
|
|
", error status: %d\n", err);
|
|
return;
|
|
}
|
|
|
|
/* Walk the multicast list, and add each address */
|
|
|
|
if_maddr_rlock(ifp);
|
|
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
|
if (ifma->ifma_addr->sa_family != AF_LINK)
|
|
continue;
|
|
bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr),
|
|
&cmd.data0, 4);
|
|
bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr) + 4,
|
|
&cmd.data1, 2);
|
|
cmd.data0 = htonl(cmd.data0);
|
|
cmd.data1 = htonl(cmd.data1);
|
|
err = mxge_send_cmd(sc, MXGEFW_JOIN_MULTICAST_GROUP, &cmd);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Failed "
|
|
"MXGEFW_JOIN_MULTICAST_GROUP, error status:"
|
|
"%d\t", err);
|
|
/* abort, leaving multicast filtering off */
|
|
if_maddr_runlock(ifp);
|
|
return;
|
|
}
|
|
}
|
|
if_maddr_runlock(ifp);
|
|
/* Enable multicast filtering */
|
|
err = mxge_send_cmd(sc, MXGEFW_DISABLE_ALLMULTI, &cmd);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Failed MXGEFW_DISABLE_ALLMULTI"
|
|
", error status: %d\n", err);
|
|
}
|
|
}
|
|
|
|
static int
|
|
mxge_max_mtu(mxge_softc_t *sc)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
int status;
|
|
|
|
if (MJUMPAGESIZE - MXGEFW_PAD > MXGEFW_MAX_MTU)
|
|
return MXGEFW_MAX_MTU - MXGEFW_PAD;
|
|
|
|
/* try to set nbufs to see if it we can
|
|
use virtually contiguous jumbos */
|
|
cmd.data0 = 0;
|
|
status = mxge_send_cmd(sc, MXGEFW_CMD_ALWAYS_USE_N_BIG_BUFFERS,
|
|
&cmd);
|
|
if (status == 0)
|
|
return MXGEFW_MAX_MTU - MXGEFW_PAD;
|
|
|
|
/* otherwise, we're limited to MJUMPAGESIZE */
|
|
return MJUMPAGESIZE - MXGEFW_PAD;
|
|
}
|
|
|
|
static int
|
|
mxge_reset(mxge_softc_t *sc, int interrupts_setup)
|
|
{
|
|
struct mxge_slice_state *ss;
|
|
mxge_rx_done_t *rx_done;
|
|
volatile uint32_t *irq_claim;
|
|
mxge_cmd_t cmd;
|
|
int slice, status;
|
|
|
|
/* try to send a reset command to the card to see if it
|
|
is alive */
|
|
memset(&cmd, 0, sizeof (cmd));
|
|
status = mxge_send_cmd(sc, MXGEFW_CMD_RESET, &cmd);
|
|
if (status != 0) {
|
|
device_printf(sc->dev, "failed reset\n");
|
|
return ENXIO;
|
|
}
|
|
|
|
mxge_dummy_rdma(sc, 1);
|
|
|
|
|
|
/* set the intrq size */
|
|
cmd.data0 = sc->rx_ring_size;
|
|
status = mxge_send_cmd(sc, MXGEFW_CMD_SET_INTRQ_SIZE, &cmd);
|
|
|
|
/*
|
|
* Even though we already know how many slices are supported
|
|
* via mxge_slice_probe(), MXGEFW_CMD_GET_MAX_RSS_QUEUES
|
|
* has magic side effects, and must be called after a reset.
|
|
* It must be called prior to calling any RSS related cmds,
|
|
* including assigning an interrupt queue for anything but
|
|
* slice 0. It must also be called *after*
|
|
* MXGEFW_CMD_SET_INTRQ_SIZE, since the intrq size is used by
|
|
* the firmware to compute offsets.
|
|
*/
|
|
|
|
if (sc->num_slices > 1) {
|
|
/* ask the maximum number of slices it supports */
|
|
status = mxge_send_cmd(sc, MXGEFW_CMD_GET_MAX_RSS_QUEUES,
|
|
&cmd);
|
|
if (status != 0) {
|
|
device_printf(sc->dev,
|
|
"failed to get number of slices\n");
|
|
return status;
|
|
}
|
|
/*
|
|
* MXGEFW_CMD_ENABLE_RSS_QUEUES must be called prior
|
|
* to setting up the interrupt queue DMA
|
|
*/
|
|
cmd.data0 = sc->num_slices;
|
|
cmd.data1 = MXGEFW_SLICE_INTR_MODE_ONE_PER_SLICE;
|
|
#ifdef IFNET_BUF_RING
|
|
cmd.data1 |= MXGEFW_SLICE_ENABLE_MULTIPLE_TX_QUEUES;
|
|
#endif
|
|
status = mxge_send_cmd(sc, MXGEFW_CMD_ENABLE_RSS_QUEUES,
|
|
&cmd);
|
|
if (status != 0) {
|
|
device_printf(sc->dev,
|
|
"failed to set number of slices\n");
|
|
return status;
|
|
}
|
|
}
|
|
|
|
|
|
if (interrupts_setup) {
|
|
/* Now exchange information about interrupts */
|
|
for (slice = 0; slice < sc->num_slices; slice++) {
|
|
rx_done = &sc->ss[slice].rx_done;
|
|
memset(rx_done->entry, 0, sc->rx_ring_size);
|
|
cmd.data0 = MXGE_LOWPART_TO_U32(rx_done->dma.bus_addr);
|
|
cmd.data1 = MXGE_HIGHPART_TO_U32(rx_done->dma.bus_addr);
|
|
cmd.data2 = slice;
|
|
status |= mxge_send_cmd(sc,
|
|
MXGEFW_CMD_SET_INTRQ_DMA,
|
|
&cmd);
|
|
}
|
|
}
|
|
|
|
status |= mxge_send_cmd(sc,
|
|
MXGEFW_CMD_GET_INTR_COAL_DELAY_OFFSET, &cmd);
|
|
|
|
|
|
sc->intr_coal_delay_ptr = (volatile uint32_t *)(sc->sram + cmd.data0);
|
|
|
|
status |= mxge_send_cmd(sc, MXGEFW_CMD_GET_IRQ_ACK_OFFSET, &cmd);
|
|
irq_claim = (volatile uint32_t *)(sc->sram + cmd.data0);
|
|
|
|
|
|
status |= mxge_send_cmd(sc, MXGEFW_CMD_GET_IRQ_DEASSERT_OFFSET,
|
|
&cmd);
|
|
sc->irq_deassert = (volatile uint32_t *)(sc->sram + cmd.data0);
|
|
if (status != 0) {
|
|
device_printf(sc->dev, "failed set interrupt parameters\n");
|
|
return status;
|
|
}
|
|
|
|
|
|
*sc->intr_coal_delay_ptr = htobe32(sc->intr_coal_delay);
|
|
|
|
|
|
/* run a DMA benchmark */
|
|
(void) mxge_dma_test(sc, MXGEFW_DMA_TEST);
|
|
|
|
for (slice = 0; slice < sc->num_slices; slice++) {
|
|
ss = &sc->ss[slice];
|
|
|
|
ss->irq_claim = irq_claim + (2 * slice);
|
|
/* reset mcp/driver shared state back to 0 */
|
|
ss->rx_done.idx = 0;
|
|
ss->rx_done.cnt = 0;
|
|
ss->tx.req = 0;
|
|
ss->tx.done = 0;
|
|
ss->tx.pkt_done = 0;
|
|
ss->tx.queue_active = 0;
|
|
ss->tx.activate = 0;
|
|
ss->tx.deactivate = 0;
|
|
ss->tx.wake = 0;
|
|
ss->tx.defrag = 0;
|
|
ss->tx.stall = 0;
|
|
ss->rx_big.cnt = 0;
|
|
ss->rx_small.cnt = 0;
|
|
ss->lc.lro_bad_csum = 0;
|
|
ss->lc.lro_queued = 0;
|
|
ss->lc.lro_flushed = 0;
|
|
if (ss->fw_stats != NULL) {
|
|
bzero(ss->fw_stats, sizeof *ss->fw_stats);
|
|
}
|
|
}
|
|
sc->rdma_tags_available = 15;
|
|
status = mxge_update_mac_address(sc);
|
|
mxge_change_promisc(sc, sc->ifp->if_flags & IFF_PROMISC);
|
|
mxge_change_pause(sc, sc->pause);
|
|
mxge_set_multicast_list(sc);
|
|
if (sc->throttle) {
|
|
cmd.data0 = sc->throttle;
|
|
if (mxge_send_cmd(sc, MXGEFW_CMD_SET_THROTTLE_FACTOR,
|
|
&cmd)) {
|
|
device_printf(sc->dev,
|
|
"can't enable throttle\n");
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static int
|
|
mxge_change_throttle(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
mxge_softc_t *sc;
|
|
int err;
|
|
unsigned int throttle;
|
|
|
|
sc = arg1;
|
|
throttle = sc->throttle;
|
|
err = sysctl_handle_int(oidp, &throttle, arg2, req);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
if (throttle == sc->throttle)
|
|
return 0;
|
|
|
|
if (throttle < MXGE_MIN_THROTTLE || throttle > MXGE_MAX_THROTTLE)
|
|
return EINVAL;
|
|
|
|
mtx_lock(&sc->driver_mtx);
|
|
cmd.data0 = throttle;
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_SET_THROTTLE_FACTOR, &cmd);
|
|
if (err == 0)
|
|
sc->throttle = throttle;
|
|
mtx_unlock(&sc->driver_mtx);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
mxge_change_intr_coal(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
mxge_softc_t *sc;
|
|
unsigned int intr_coal_delay;
|
|
int err;
|
|
|
|
sc = arg1;
|
|
intr_coal_delay = sc->intr_coal_delay;
|
|
err = sysctl_handle_int(oidp, &intr_coal_delay, arg2, req);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
if (intr_coal_delay == sc->intr_coal_delay)
|
|
return 0;
|
|
|
|
if (intr_coal_delay == 0 || intr_coal_delay > 1000*1000)
|
|
return EINVAL;
|
|
|
|
mtx_lock(&sc->driver_mtx);
|
|
*sc->intr_coal_delay_ptr = htobe32(intr_coal_delay);
|
|
sc->intr_coal_delay = intr_coal_delay;
|
|
|
|
mtx_unlock(&sc->driver_mtx);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
mxge_change_flow_control(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
mxge_softc_t *sc;
|
|
unsigned int enabled;
|
|
int err;
|
|
|
|
sc = arg1;
|
|
enabled = sc->pause;
|
|
err = sysctl_handle_int(oidp, &enabled, arg2, req);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
if (enabled == sc->pause)
|
|
return 0;
|
|
|
|
mtx_lock(&sc->driver_mtx);
|
|
err = mxge_change_pause(sc, enabled);
|
|
mtx_unlock(&sc->driver_mtx);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
mxge_handle_be32(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
int err;
|
|
|
|
if (arg1 == NULL)
|
|
return EFAULT;
|
|
arg2 = be32toh(*(int *)arg1);
|
|
arg1 = NULL;
|
|
err = sysctl_handle_int(oidp, arg1, arg2, req);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
mxge_rem_sysctls(mxge_softc_t *sc)
|
|
{
|
|
struct mxge_slice_state *ss;
|
|
int slice;
|
|
|
|
if (sc->slice_sysctl_tree == NULL)
|
|
return;
|
|
|
|
for (slice = 0; slice < sc->num_slices; slice++) {
|
|
ss = &sc->ss[slice];
|
|
if (ss == NULL || ss->sysctl_tree == NULL)
|
|
continue;
|
|
sysctl_ctx_free(&ss->sysctl_ctx);
|
|
ss->sysctl_tree = NULL;
|
|
}
|
|
sysctl_ctx_free(&sc->slice_sysctl_ctx);
|
|
sc->slice_sysctl_tree = NULL;
|
|
}
|
|
|
|
static void
|
|
mxge_add_sysctls(mxge_softc_t *sc)
|
|
{
|
|
struct sysctl_ctx_list *ctx;
|
|
struct sysctl_oid_list *children;
|
|
mcp_irq_data_t *fw;
|
|
struct mxge_slice_state *ss;
|
|
int slice;
|
|
char slice_num[8];
|
|
|
|
ctx = device_get_sysctl_ctx(sc->dev);
|
|
children = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev));
|
|
fw = sc->ss[0].fw_stats;
|
|
|
|
/* random information */
|
|
SYSCTL_ADD_STRING(ctx, children, OID_AUTO,
|
|
"firmware_version",
|
|
CTLFLAG_RD, sc->fw_version,
|
|
0, "firmware version");
|
|
SYSCTL_ADD_STRING(ctx, children, OID_AUTO,
|
|
"serial_number",
|
|
CTLFLAG_RD, sc->serial_number_string,
|
|
0, "serial number");
|
|
SYSCTL_ADD_STRING(ctx, children, OID_AUTO,
|
|
"product_code",
|
|
CTLFLAG_RD, sc->product_code_string,
|
|
0, "product_code");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"pcie_link_width",
|
|
CTLFLAG_RD, &sc->link_width,
|
|
0, "tx_boundary");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"tx_boundary",
|
|
CTLFLAG_RD, &sc->tx_boundary,
|
|
0, "tx_boundary");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"write_combine",
|
|
CTLFLAG_RD, &sc->wc,
|
|
0, "write combining PIO?");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"read_dma_MBs",
|
|
CTLFLAG_RD, &sc->read_dma,
|
|
0, "DMA Read speed in MB/s");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"write_dma_MBs",
|
|
CTLFLAG_RD, &sc->write_dma,
|
|
0, "DMA Write speed in MB/s");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"read_write_dma_MBs",
|
|
CTLFLAG_RD, &sc->read_write_dma,
|
|
0, "DMA concurrent Read/Write speed in MB/s");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"watchdog_resets",
|
|
CTLFLAG_RD, &sc->watchdog_resets,
|
|
0, "Number of times NIC was reset");
|
|
|
|
|
|
/* performance related tunables */
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"intr_coal_delay",
|
|
CTLTYPE_INT|CTLFLAG_RW, sc,
|
|
0, mxge_change_intr_coal,
|
|
"I", "interrupt coalescing delay in usecs");
|
|
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"throttle",
|
|
CTLTYPE_INT|CTLFLAG_RW, sc,
|
|
0, mxge_change_throttle,
|
|
"I", "transmit throttling");
|
|
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"flow_control_enabled",
|
|
CTLTYPE_INT|CTLFLAG_RW, sc,
|
|
0, mxge_change_flow_control,
|
|
"I", "interrupt coalescing delay in usecs");
|
|
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"deassert_wait",
|
|
CTLFLAG_RW, &mxge_deassert_wait,
|
|
0, "Wait for IRQ line to go low in ihandler");
|
|
|
|
/* stats block from firmware is in network byte order.
|
|
Need to swap it */
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"link_up",
|
|
CTLTYPE_INT|CTLFLAG_RD, &fw->link_up,
|
|
0, mxge_handle_be32,
|
|
"I", "link up");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"rdma_tags_available",
|
|
CTLTYPE_INT|CTLFLAG_RD, &fw->rdma_tags_available,
|
|
0, mxge_handle_be32,
|
|
"I", "rdma_tags_available");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_bad_crc32",
|
|
CTLTYPE_INT|CTLFLAG_RD,
|
|
&fw->dropped_bad_crc32,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_bad_crc32");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_bad_phy",
|
|
CTLTYPE_INT|CTLFLAG_RD,
|
|
&fw->dropped_bad_phy,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_bad_phy");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_link_error_or_filtered",
|
|
CTLTYPE_INT|CTLFLAG_RD,
|
|
&fw->dropped_link_error_or_filtered,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_link_error_or_filtered");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_link_overflow",
|
|
CTLTYPE_INT|CTLFLAG_RD, &fw->dropped_link_overflow,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_link_overflow");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_multicast_filtered",
|
|
CTLTYPE_INT|CTLFLAG_RD,
|
|
&fw->dropped_multicast_filtered,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_multicast_filtered");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_no_big_buffer",
|
|
CTLTYPE_INT|CTLFLAG_RD, &fw->dropped_no_big_buffer,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_no_big_buffer");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_no_small_buffer",
|
|
CTLTYPE_INT|CTLFLAG_RD,
|
|
&fw->dropped_no_small_buffer,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_no_small_buffer");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_overrun",
|
|
CTLTYPE_INT|CTLFLAG_RD, &fw->dropped_overrun,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_overrun");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_pause",
|
|
CTLTYPE_INT|CTLFLAG_RD,
|
|
&fw->dropped_pause,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_pause");
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_runt",
|
|
CTLTYPE_INT|CTLFLAG_RD, &fw->dropped_runt,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_runt");
|
|
|
|
SYSCTL_ADD_PROC(ctx, children, OID_AUTO,
|
|
"dropped_unicast_filtered",
|
|
CTLTYPE_INT|CTLFLAG_RD, &fw->dropped_unicast_filtered,
|
|
0, mxge_handle_be32,
|
|
"I", "dropped_unicast_filtered");
|
|
|
|
/* verbose printing? */
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"verbose",
|
|
CTLFLAG_RW, &mxge_verbose,
|
|
0, "verbose printing");
|
|
|
|
/* add counters exported for debugging from all slices */
|
|
sysctl_ctx_init(&sc->slice_sysctl_ctx);
|
|
sc->slice_sysctl_tree =
|
|
SYSCTL_ADD_NODE(&sc->slice_sysctl_ctx, children, OID_AUTO,
|
|
"slice", CTLFLAG_RD, 0, "");
|
|
|
|
for (slice = 0; slice < sc->num_slices; slice++) {
|
|
ss = &sc->ss[slice];
|
|
sysctl_ctx_init(&ss->sysctl_ctx);
|
|
ctx = &ss->sysctl_ctx;
|
|
children = SYSCTL_CHILDREN(sc->slice_sysctl_tree);
|
|
sprintf(slice_num, "%d", slice);
|
|
ss->sysctl_tree =
|
|
SYSCTL_ADD_NODE(ctx, children, OID_AUTO, slice_num,
|
|
CTLFLAG_RD, 0, "");
|
|
children = SYSCTL_CHILDREN(ss->sysctl_tree);
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"rx_small_cnt",
|
|
CTLFLAG_RD, &ss->rx_small.cnt,
|
|
0, "rx_small_cnt");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"rx_big_cnt",
|
|
CTLFLAG_RD, &ss->rx_big.cnt,
|
|
0, "rx_small_cnt");
|
|
SYSCTL_ADD_U64(ctx, children, OID_AUTO,
|
|
"lro_flushed", CTLFLAG_RD, &ss->lc.lro_flushed,
|
|
0, "number of lro merge queues flushed");
|
|
|
|
SYSCTL_ADD_U64(ctx, children, OID_AUTO,
|
|
"lro_bad_csum", CTLFLAG_RD, &ss->lc.lro_bad_csum,
|
|
0, "number of bad csums preventing LRO");
|
|
|
|
SYSCTL_ADD_U64(ctx, children, OID_AUTO,
|
|
"lro_queued", CTLFLAG_RD, &ss->lc.lro_queued,
|
|
0, "number of frames appended to lro merge"
|
|
"queues");
|
|
|
|
#ifndef IFNET_BUF_RING
|
|
/* only transmit from slice 0 for now */
|
|
if (slice > 0)
|
|
continue;
|
|
#endif
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"tx_req",
|
|
CTLFLAG_RD, &ss->tx.req,
|
|
0, "tx_req");
|
|
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"tx_done",
|
|
CTLFLAG_RD, &ss->tx.done,
|
|
0, "tx_done");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"tx_pkt_done",
|
|
CTLFLAG_RD, &ss->tx.pkt_done,
|
|
0, "tx_done");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"tx_stall",
|
|
CTLFLAG_RD, &ss->tx.stall,
|
|
0, "tx_stall");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"tx_wake",
|
|
CTLFLAG_RD, &ss->tx.wake,
|
|
0, "tx_wake");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"tx_defrag",
|
|
CTLFLAG_RD, &ss->tx.defrag,
|
|
0, "tx_defrag");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"tx_queue_active",
|
|
CTLFLAG_RD, &ss->tx.queue_active,
|
|
0, "tx_queue_active");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"tx_activate",
|
|
CTLFLAG_RD, &ss->tx.activate,
|
|
0, "tx_activate");
|
|
SYSCTL_ADD_INT(ctx, children, OID_AUTO,
|
|
"tx_deactivate",
|
|
CTLFLAG_RD, &ss->tx.deactivate,
|
|
0, "tx_deactivate");
|
|
}
|
|
}
|
|
|
|
/* copy an array of mcp_kreq_ether_send_t's to the mcp. Copy
|
|
backwards one at a time and handle ring wraps */
|
|
|
|
static inline void
|
|
mxge_submit_req_backwards(mxge_tx_ring_t *tx,
|
|
mcp_kreq_ether_send_t *src, int cnt)
|
|
{
|
|
int idx, starting_slot;
|
|
starting_slot = tx->req;
|
|
while (cnt > 1) {
|
|
cnt--;
|
|
idx = (starting_slot + cnt) & tx->mask;
|
|
mxge_pio_copy(&tx->lanai[idx],
|
|
&src[cnt], sizeof(*src));
|
|
wmb();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* copy an array of mcp_kreq_ether_send_t's to the mcp. Copy
|
|
* at most 32 bytes at a time, so as to avoid involving the software
|
|
* pio handler in the nic. We re-write the first segment's flags
|
|
* to mark them valid only after writing the entire chain
|
|
*/
|
|
|
|
static inline void
|
|
mxge_submit_req(mxge_tx_ring_t *tx, mcp_kreq_ether_send_t *src,
|
|
int cnt)
|
|
{
|
|
int idx, i;
|
|
uint32_t *src_ints;
|
|
volatile uint32_t *dst_ints;
|
|
mcp_kreq_ether_send_t *srcp;
|
|
volatile mcp_kreq_ether_send_t *dstp, *dst;
|
|
uint8_t last_flags;
|
|
|
|
idx = tx->req & tx->mask;
|
|
|
|
last_flags = src->flags;
|
|
src->flags = 0;
|
|
wmb();
|
|
dst = dstp = &tx->lanai[idx];
|
|
srcp = src;
|
|
|
|
if ((idx + cnt) < tx->mask) {
|
|
for (i = 0; i < (cnt - 1); i += 2) {
|
|
mxge_pio_copy(dstp, srcp, 2 * sizeof(*src));
|
|
wmb(); /* force write every 32 bytes */
|
|
srcp += 2;
|
|
dstp += 2;
|
|
}
|
|
} else {
|
|
/* submit all but the first request, and ensure
|
|
that it is submitted below */
|
|
mxge_submit_req_backwards(tx, src, cnt);
|
|
i = 0;
|
|
}
|
|
if (i < cnt) {
|
|
/* submit the first request */
|
|
mxge_pio_copy(dstp, srcp, sizeof(*src));
|
|
wmb(); /* barrier before setting valid flag */
|
|
}
|
|
|
|
/* re-write the last 32-bits with the valid flags */
|
|
src->flags = last_flags;
|
|
src_ints = (uint32_t *)src;
|
|
src_ints+=3;
|
|
dst_ints = (volatile uint32_t *)dst;
|
|
dst_ints+=3;
|
|
*dst_ints = *src_ints;
|
|
tx->req += cnt;
|
|
wmb();
|
|
}
|
|
|
|
static int
|
|
mxge_parse_tx(struct mxge_slice_state *ss, struct mbuf *m,
|
|
struct mxge_pkt_info *pi)
|
|
{
|
|
struct ether_vlan_header *eh;
|
|
uint16_t etype;
|
|
int tso = m->m_pkthdr.csum_flags & (CSUM_TSO);
|
|
#if IFCAP_TSO6 && defined(INET6)
|
|
int nxt;
|
|
#endif
|
|
|
|
eh = mtod(m, struct ether_vlan_header *);
|
|
if (eh->evl_encap_proto == htons(ETHERTYPE_VLAN)) {
|
|
etype = ntohs(eh->evl_proto);
|
|
pi->ip_off = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
|
|
} else {
|
|
etype = ntohs(eh->evl_encap_proto);
|
|
pi->ip_off = ETHER_HDR_LEN;
|
|
}
|
|
|
|
switch (etype) {
|
|
case ETHERTYPE_IP:
|
|
/*
|
|
* ensure ip header is in first mbuf, copy it to a
|
|
* scratch buffer if not
|
|
*/
|
|
pi->ip = (struct ip *)(m->m_data + pi->ip_off);
|
|
pi->ip6 = NULL;
|
|
if (__predict_false(m->m_len < pi->ip_off + sizeof(*pi->ip))) {
|
|
m_copydata(m, 0, pi->ip_off + sizeof(*pi->ip),
|
|
ss->scratch);
|
|
pi->ip = (struct ip *)(ss->scratch + pi->ip_off);
|
|
}
|
|
pi->ip_hlen = pi->ip->ip_hl << 2;
|
|
if (!tso)
|
|
return 0;
|
|
|
|
if (__predict_false(m->m_len < pi->ip_off + pi->ip_hlen +
|
|
sizeof(struct tcphdr))) {
|
|
m_copydata(m, 0, pi->ip_off + pi->ip_hlen +
|
|
sizeof(struct tcphdr), ss->scratch);
|
|
pi->ip = (struct ip *)(ss->scratch + pi->ip_off);
|
|
}
|
|
pi->tcp = (struct tcphdr *)((char *)pi->ip + pi->ip_hlen);
|
|
break;
|
|
#if IFCAP_TSO6 && defined(INET6)
|
|
case ETHERTYPE_IPV6:
|
|
pi->ip6 = (struct ip6_hdr *)(m->m_data + pi->ip_off);
|
|
if (__predict_false(m->m_len < pi->ip_off + sizeof(*pi->ip6))) {
|
|
m_copydata(m, 0, pi->ip_off + sizeof(*pi->ip6),
|
|
ss->scratch);
|
|
pi->ip6 = (struct ip6_hdr *)(ss->scratch + pi->ip_off);
|
|
}
|
|
nxt = 0;
|
|
pi->ip_hlen = ip6_lasthdr(m, pi->ip_off, IPPROTO_IPV6, &nxt);
|
|
pi->ip_hlen -= pi->ip_off;
|
|
if (nxt != IPPROTO_TCP && nxt != IPPROTO_UDP)
|
|
return EINVAL;
|
|
|
|
if (!tso)
|
|
return 0;
|
|
|
|
if (pi->ip_off + pi->ip_hlen > ss->sc->max_tso6_hlen)
|
|
return EINVAL;
|
|
|
|
if (__predict_false(m->m_len < pi->ip_off + pi->ip_hlen +
|
|
sizeof(struct tcphdr))) {
|
|
m_copydata(m, 0, pi->ip_off + pi->ip_hlen +
|
|
sizeof(struct tcphdr), ss->scratch);
|
|
pi->ip6 = (struct ip6_hdr *)(ss->scratch + pi->ip_off);
|
|
}
|
|
pi->tcp = (struct tcphdr *)((char *)pi->ip6 + pi->ip_hlen);
|
|
break;
|
|
#endif
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if IFCAP_TSO4
|
|
|
|
static void
|
|
mxge_encap_tso(struct mxge_slice_state *ss, struct mbuf *m,
|
|
int busdma_seg_cnt, struct mxge_pkt_info *pi)
|
|
{
|
|
mxge_tx_ring_t *tx;
|
|
mcp_kreq_ether_send_t *req;
|
|
bus_dma_segment_t *seg;
|
|
uint32_t low, high_swapped;
|
|
int len, seglen, cum_len, cum_len_next;
|
|
int next_is_first, chop, cnt, rdma_count, small;
|
|
uint16_t pseudo_hdr_offset, cksum_offset, mss, sum;
|
|
uint8_t flags, flags_next;
|
|
static int once;
|
|
|
|
mss = m->m_pkthdr.tso_segsz;
|
|
|
|
/* negative cum_len signifies to the
|
|
* send loop that we are still in the
|
|
* header portion of the TSO packet.
|
|
*/
|
|
|
|
cksum_offset = pi->ip_off + pi->ip_hlen;
|
|
cum_len = -(cksum_offset + (pi->tcp->th_off << 2));
|
|
|
|
/* TSO implies checksum offload on this hardware */
|
|
if (__predict_false((m->m_pkthdr.csum_flags & (CSUM_TCP|CSUM_TCP_IPV6)) == 0)) {
|
|
/*
|
|
* If packet has full TCP csum, replace it with pseudo hdr
|
|
* sum that the NIC expects, otherwise the NIC will emit
|
|
* packets with bad TCP checksums.
|
|
*/
|
|
m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum);
|
|
if (pi->ip6) {
|
|
#if (CSUM_TCP_IPV6 != 0) && defined(INET6)
|
|
m->m_pkthdr.csum_flags |= CSUM_TCP_IPV6;
|
|
sum = in6_cksum_pseudo(pi->ip6,
|
|
m->m_pkthdr.len - cksum_offset,
|
|
IPPROTO_TCP, 0);
|
|
#endif
|
|
} else {
|
|
#ifdef INET
|
|
m->m_pkthdr.csum_flags |= CSUM_TCP;
|
|
sum = in_pseudo(pi->ip->ip_src.s_addr,
|
|
pi->ip->ip_dst.s_addr,
|
|
htons(IPPROTO_TCP + (m->m_pkthdr.len -
|
|
cksum_offset)));
|
|
#endif
|
|
}
|
|
m_copyback(m, offsetof(struct tcphdr, th_sum) +
|
|
cksum_offset, sizeof(sum), (caddr_t)&sum);
|
|
}
|
|
flags = MXGEFW_FLAGS_TSO_HDR | MXGEFW_FLAGS_FIRST;
|
|
|
|
|
|
/* for TSO, pseudo_hdr_offset holds mss.
|
|
* The firmware figures out where to put
|
|
* the checksum by parsing the header. */
|
|
pseudo_hdr_offset = htobe16(mss);
|
|
|
|
if (pi->ip6) {
|
|
/*
|
|
* for IPv6 TSO, the "checksum offset" is re-purposed
|
|
* to store the TCP header len
|
|
*/
|
|
cksum_offset = (pi->tcp->th_off << 2);
|
|
}
|
|
|
|
tx = &ss->tx;
|
|
req = tx->req_list;
|
|
seg = tx->seg_list;
|
|
cnt = 0;
|
|
rdma_count = 0;
|
|
/* "rdma_count" is the number of RDMAs belonging to the
|
|
* current packet BEFORE the current send request. For
|
|
* non-TSO packets, this is equal to "count".
|
|
* For TSO packets, rdma_count needs to be reset
|
|
* to 0 after a segment cut.
|
|
*
|
|
* The rdma_count field of the send request is
|
|
* the number of RDMAs of the packet starting at
|
|
* that request. For TSO send requests with one ore more cuts
|
|
* in the middle, this is the number of RDMAs starting
|
|
* after the last cut in the request. All previous
|
|
* segments before the last cut implicitly have 1 RDMA.
|
|
*
|
|
* Since the number of RDMAs is not known beforehand,
|
|
* it must be filled-in retroactively - after each
|
|
* segmentation cut or at the end of the entire packet.
|
|
*/
|
|
|
|
while (busdma_seg_cnt) {
|
|
/* Break the busdma segment up into pieces*/
|
|
low = MXGE_LOWPART_TO_U32(seg->ds_addr);
|
|
high_swapped = htobe32(MXGE_HIGHPART_TO_U32(seg->ds_addr));
|
|
len = seg->ds_len;
|
|
|
|
while (len) {
|
|
flags_next = flags & ~MXGEFW_FLAGS_FIRST;
|
|
seglen = len;
|
|
cum_len_next = cum_len + seglen;
|
|
(req-rdma_count)->rdma_count = rdma_count + 1;
|
|
if (__predict_true(cum_len >= 0)) {
|
|
/* payload */
|
|
chop = (cum_len_next > mss);
|
|
cum_len_next = cum_len_next % mss;
|
|
next_is_first = (cum_len_next == 0);
|
|
flags |= chop * MXGEFW_FLAGS_TSO_CHOP;
|
|
flags_next |= next_is_first *
|
|
MXGEFW_FLAGS_FIRST;
|
|
rdma_count |= -(chop | next_is_first);
|
|
rdma_count += chop & !next_is_first;
|
|
} else if (cum_len_next >= 0) {
|
|
/* header ends */
|
|
rdma_count = -1;
|
|
cum_len_next = 0;
|
|
seglen = -cum_len;
|
|
small = (mss <= MXGEFW_SEND_SMALL_SIZE);
|
|
flags_next = MXGEFW_FLAGS_TSO_PLD |
|
|
MXGEFW_FLAGS_FIRST |
|
|
(small * MXGEFW_FLAGS_SMALL);
|
|
}
|
|
|
|
req->addr_high = high_swapped;
|
|
req->addr_low = htobe32(low);
|
|
req->pseudo_hdr_offset = pseudo_hdr_offset;
|
|
req->pad = 0;
|
|
req->rdma_count = 1;
|
|
req->length = htobe16(seglen);
|
|
req->cksum_offset = cksum_offset;
|
|
req->flags = flags | ((cum_len & 1) *
|
|
MXGEFW_FLAGS_ALIGN_ODD);
|
|
low += seglen;
|
|
len -= seglen;
|
|
cum_len = cum_len_next;
|
|
flags = flags_next;
|
|
req++;
|
|
cnt++;
|
|
rdma_count++;
|
|
if (cksum_offset != 0 && !pi->ip6) {
|
|
if (__predict_false(cksum_offset > seglen))
|
|
cksum_offset -= seglen;
|
|
else
|
|
cksum_offset = 0;
|
|
}
|
|
if (__predict_false(cnt > tx->max_desc))
|
|
goto drop;
|
|
}
|
|
busdma_seg_cnt--;
|
|
seg++;
|
|
}
|
|
(req-rdma_count)->rdma_count = rdma_count;
|
|
|
|
do {
|
|
req--;
|
|
req->flags |= MXGEFW_FLAGS_TSO_LAST;
|
|
} while (!(req->flags & (MXGEFW_FLAGS_TSO_CHOP | MXGEFW_FLAGS_FIRST)));
|
|
|
|
tx->info[((cnt - 1) + tx->req) & tx->mask].flag = 1;
|
|
mxge_submit_req(tx, tx->req_list, cnt);
|
|
#ifdef IFNET_BUF_RING
|
|
if ((ss->sc->num_slices > 1) && tx->queue_active == 0) {
|
|
/* tell the NIC to start polling this slice */
|
|
*tx->send_go = 1;
|
|
tx->queue_active = 1;
|
|
tx->activate++;
|
|
wmb();
|
|
}
|
|
#endif
|
|
return;
|
|
|
|
drop:
|
|
bus_dmamap_unload(tx->dmat, tx->info[tx->req & tx->mask].map);
|
|
m_freem(m);
|
|
ss->oerrors++;
|
|
if (!once) {
|
|
printf("tx->max_desc exceeded via TSO!\n");
|
|
printf("mss = %d, %ld, %d!\n", mss,
|
|
(long)seg - (long)tx->seg_list, tx->max_desc);
|
|
once = 1;
|
|
}
|
|
return;
|
|
|
|
}
|
|
|
|
#endif /* IFCAP_TSO4 */
|
|
|
|
#ifdef MXGE_NEW_VLAN_API
|
|
/*
|
|
* We reproduce the software vlan tag insertion from
|
|
* net/if_vlan.c:vlan_start() here so that we can advertise "hardware"
|
|
* vlan tag insertion. We need to advertise this in order to have the
|
|
* vlan interface respect our csum offload flags.
|
|
*/
|
|
static struct mbuf *
|
|
mxge_vlan_tag_insert(struct mbuf *m)
|
|
{
|
|
struct ether_vlan_header *evl;
|
|
|
|
M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, M_NOWAIT);
|
|
if (__predict_false(m == NULL))
|
|
return NULL;
|
|
if (m->m_len < sizeof(*evl)) {
|
|
m = m_pullup(m, sizeof(*evl));
|
|
if (__predict_false(m == NULL))
|
|
return NULL;
|
|
}
|
|
/*
|
|
* Transform the Ethernet header into an Ethernet header
|
|
* with 802.1Q encapsulation.
|
|
*/
|
|
evl = mtod(m, struct ether_vlan_header *);
|
|
bcopy((char *)evl + ETHER_VLAN_ENCAP_LEN,
|
|
(char *)evl, ETHER_HDR_LEN - ETHER_TYPE_LEN);
|
|
evl->evl_encap_proto = htons(ETHERTYPE_VLAN);
|
|
evl->evl_tag = htons(m->m_pkthdr.ether_vtag);
|
|
m->m_flags &= ~M_VLANTAG;
|
|
return m;
|
|
}
|
|
#endif /* MXGE_NEW_VLAN_API */
|
|
|
|
static void
|
|
mxge_encap(struct mxge_slice_state *ss, struct mbuf *m)
|
|
{
|
|
struct mxge_pkt_info pi = {0,0,0,0};
|
|
mxge_softc_t *sc;
|
|
mcp_kreq_ether_send_t *req;
|
|
bus_dma_segment_t *seg;
|
|
struct mbuf *m_tmp;
|
|
struct ifnet *ifp;
|
|
mxge_tx_ring_t *tx;
|
|
int cnt, cum_len, err, i, idx, odd_flag;
|
|
uint16_t pseudo_hdr_offset;
|
|
uint8_t flags, cksum_offset;
|
|
|
|
|
|
sc = ss->sc;
|
|
ifp = sc->ifp;
|
|
tx = &ss->tx;
|
|
|
|
#ifdef MXGE_NEW_VLAN_API
|
|
if (m->m_flags & M_VLANTAG) {
|
|
m = mxge_vlan_tag_insert(m);
|
|
if (__predict_false(m == NULL))
|
|
goto drop_without_m;
|
|
}
|
|
#endif
|
|
if (m->m_pkthdr.csum_flags &
|
|
(CSUM_TSO | CSUM_DELAY_DATA | CSUM_DELAY_DATA_IPV6)) {
|
|
if (mxge_parse_tx(ss, m, &pi))
|
|
goto drop;
|
|
}
|
|
|
|
/* (try to) map the frame for DMA */
|
|
idx = tx->req & tx->mask;
|
|
err = bus_dmamap_load_mbuf_sg(tx->dmat, tx->info[idx].map,
|
|
m, tx->seg_list, &cnt,
|
|
BUS_DMA_NOWAIT);
|
|
if (__predict_false(err == EFBIG)) {
|
|
/* Too many segments in the chain. Try
|
|
to defrag */
|
|
m_tmp = m_defrag(m, M_NOWAIT);
|
|
if (m_tmp == NULL) {
|
|
goto drop;
|
|
}
|
|
ss->tx.defrag++;
|
|
m = m_tmp;
|
|
err = bus_dmamap_load_mbuf_sg(tx->dmat,
|
|
tx->info[idx].map,
|
|
m, tx->seg_list, &cnt,
|
|
BUS_DMA_NOWAIT);
|
|
}
|
|
if (__predict_false(err != 0)) {
|
|
device_printf(sc->dev, "bus_dmamap_load_mbuf_sg returned %d"
|
|
" packet len = %d\n", err, m->m_pkthdr.len);
|
|
goto drop;
|
|
}
|
|
bus_dmamap_sync(tx->dmat, tx->info[idx].map,
|
|
BUS_DMASYNC_PREWRITE);
|
|
tx->info[idx].m = m;
|
|
|
|
#if IFCAP_TSO4
|
|
/* TSO is different enough, we handle it in another routine */
|
|
if (m->m_pkthdr.csum_flags & (CSUM_TSO)) {
|
|
mxge_encap_tso(ss, m, cnt, &pi);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
req = tx->req_list;
|
|
cksum_offset = 0;
|
|
pseudo_hdr_offset = 0;
|
|
flags = MXGEFW_FLAGS_NO_TSO;
|
|
|
|
/* checksum offloading? */
|
|
if (m->m_pkthdr.csum_flags &
|
|
(CSUM_DELAY_DATA | CSUM_DELAY_DATA_IPV6)) {
|
|
/* ensure ip header is in first mbuf, copy
|
|
it to a scratch buffer if not */
|
|
cksum_offset = pi.ip_off + pi.ip_hlen;
|
|
pseudo_hdr_offset = cksum_offset + m->m_pkthdr.csum_data;
|
|
pseudo_hdr_offset = htobe16(pseudo_hdr_offset);
|
|
req->cksum_offset = cksum_offset;
|
|
flags |= MXGEFW_FLAGS_CKSUM;
|
|
odd_flag = MXGEFW_FLAGS_ALIGN_ODD;
|
|
} else {
|
|
odd_flag = 0;
|
|
}
|
|
if (m->m_pkthdr.len < MXGEFW_SEND_SMALL_SIZE)
|
|
flags |= MXGEFW_FLAGS_SMALL;
|
|
|
|
/* convert segments into a request list */
|
|
cum_len = 0;
|
|
seg = tx->seg_list;
|
|
req->flags = MXGEFW_FLAGS_FIRST;
|
|
for (i = 0; i < cnt; i++) {
|
|
req->addr_low =
|
|
htobe32(MXGE_LOWPART_TO_U32(seg->ds_addr));
|
|
req->addr_high =
|
|
htobe32(MXGE_HIGHPART_TO_U32(seg->ds_addr));
|
|
req->length = htobe16(seg->ds_len);
|
|
req->cksum_offset = cksum_offset;
|
|
if (cksum_offset > seg->ds_len)
|
|
cksum_offset -= seg->ds_len;
|
|
else
|
|
cksum_offset = 0;
|
|
req->pseudo_hdr_offset = pseudo_hdr_offset;
|
|
req->pad = 0; /* complete solid 16-byte block */
|
|
req->rdma_count = 1;
|
|
req->flags |= flags | ((cum_len & 1) * odd_flag);
|
|
cum_len += seg->ds_len;
|
|
seg++;
|
|
req++;
|
|
req->flags = 0;
|
|
}
|
|
req--;
|
|
/* pad runts to 60 bytes */
|
|
if (cum_len < 60) {
|
|
req++;
|
|
req->addr_low =
|
|
htobe32(MXGE_LOWPART_TO_U32(sc->zeropad_dma.bus_addr));
|
|
req->addr_high =
|
|
htobe32(MXGE_HIGHPART_TO_U32(sc->zeropad_dma.bus_addr));
|
|
req->length = htobe16(60 - cum_len);
|
|
req->cksum_offset = 0;
|
|
req->pseudo_hdr_offset = pseudo_hdr_offset;
|
|
req->pad = 0; /* complete solid 16-byte block */
|
|
req->rdma_count = 1;
|
|
req->flags |= flags | ((cum_len & 1) * odd_flag);
|
|
cnt++;
|
|
}
|
|
|
|
tx->req_list[0].rdma_count = cnt;
|
|
#if 0
|
|
/* print what the firmware will see */
|
|
for (i = 0; i < cnt; i++) {
|
|
printf("%d: addr: 0x%x 0x%x len:%d pso%d,"
|
|
"cso:%d, flags:0x%x, rdma:%d\n",
|
|
i, (int)ntohl(tx->req_list[i].addr_high),
|
|
(int)ntohl(tx->req_list[i].addr_low),
|
|
(int)ntohs(tx->req_list[i].length),
|
|
(int)ntohs(tx->req_list[i].pseudo_hdr_offset),
|
|
tx->req_list[i].cksum_offset, tx->req_list[i].flags,
|
|
tx->req_list[i].rdma_count);
|
|
}
|
|
printf("--------------\n");
|
|
#endif
|
|
tx->info[((cnt - 1) + tx->req) & tx->mask].flag = 1;
|
|
mxge_submit_req(tx, tx->req_list, cnt);
|
|
#ifdef IFNET_BUF_RING
|
|
if ((ss->sc->num_slices > 1) && tx->queue_active == 0) {
|
|
/* tell the NIC to start polling this slice */
|
|
*tx->send_go = 1;
|
|
tx->queue_active = 1;
|
|
tx->activate++;
|
|
wmb();
|
|
}
|
|
#endif
|
|
return;
|
|
|
|
drop:
|
|
m_freem(m);
|
|
drop_without_m:
|
|
ss->oerrors++;
|
|
return;
|
|
}
|
|
|
|
#ifdef IFNET_BUF_RING
|
|
static void
|
|
mxge_qflush(struct ifnet *ifp)
|
|
{
|
|
mxge_softc_t *sc = ifp->if_softc;
|
|
mxge_tx_ring_t *tx;
|
|
struct mbuf *m;
|
|
int slice;
|
|
|
|
for (slice = 0; slice < sc->num_slices; slice++) {
|
|
tx = &sc->ss[slice].tx;
|
|
mtx_lock(&tx->mtx);
|
|
while ((m = buf_ring_dequeue_sc(tx->br)) != NULL)
|
|
m_freem(m);
|
|
mtx_unlock(&tx->mtx);
|
|
}
|
|
if_qflush(ifp);
|
|
}
|
|
|
|
static inline void
|
|
mxge_start_locked(struct mxge_slice_state *ss)
|
|
{
|
|
mxge_softc_t *sc;
|
|
struct mbuf *m;
|
|
struct ifnet *ifp;
|
|
mxge_tx_ring_t *tx;
|
|
|
|
sc = ss->sc;
|
|
ifp = sc->ifp;
|
|
tx = &ss->tx;
|
|
|
|
while ((tx->mask - (tx->req - tx->done)) > tx->max_desc) {
|
|
m = drbr_dequeue(ifp, tx->br);
|
|
if (m == NULL) {
|
|
return;
|
|
}
|
|
/* let BPF see it */
|
|
BPF_MTAP(ifp, m);
|
|
|
|
/* give it to the nic */
|
|
mxge_encap(ss, m);
|
|
}
|
|
/* ran out of transmit slots */
|
|
if (((ss->if_drv_flags & IFF_DRV_OACTIVE) == 0)
|
|
&& (!drbr_empty(ifp, tx->br))) {
|
|
ss->if_drv_flags |= IFF_DRV_OACTIVE;
|
|
tx->stall++;
|
|
}
|
|
}
|
|
|
|
static int
|
|
mxge_transmit_locked(struct mxge_slice_state *ss, struct mbuf *m)
|
|
{
|
|
mxge_softc_t *sc;
|
|
struct ifnet *ifp;
|
|
mxge_tx_ring_t *tx;
|
|
int err;
|
|
|
|
sc = ss->sc;
|
|
ifp = sc->ifp;
|
|
tx = &ss->tx;
|
|
|
|
if ((ss->if_drv_flags & (IFF_DRV_RUNNING|IFF_DRV_OACTIVE)) !=
|
|
IFF_DRV_RUNNING) {
|
|
err = drbr_enqueue(ifp, tx->br, m);
|
|
return (err);
|
|
}
|
|
|
|
if (!drbr_needs_enqueue(ifp, tx->br) &&
|
|
((tx->mask - (tx->req - tx->done)) > tx->max_desc)) {
|
|
/* let BPF see it */
|
|
BPF_MTAP(ifp, m);
|
|
/* give it to the nic */
|
|
mxge_encap(ss, m);
|
|
} else if ((err = drbr_enqueue(ifp, tx->br, m)) != 0) {
|
|
return (err);
|
|
}
|
|
if (!drbr_empty(ifp, tx->br))
|
|
mxge_start_locked(ss);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
mxge_transmit(struct ifnet *ifp, struct mbuf *m)
|
|
{
|
|
mxge_softc_t *sc = ifp->if_softc;
|
|
struct mxge_slice_state *ss;
|
|
mxge_tx_ring_t *tx;
|
|
int err = 0;
|
|
int slice;
|
|
|
|
slice = m->m_pkthdr.flowid;
|
|
slice &= (sc->num_slices - 1); /* num_slices always power of 2 */
|
|
|
|
ss = &sc->ss[slice];
|
|
tx = &ss->tx;
|
|
|
|
if (mtx_trylock(&tx->mtx)) {
|
|
err = mxge_transmit_locked(ss, m);
|
|
mtx_unlock(&tx->mtx);
|
|
} else {
|
|
err = drbr_enqueue(ifp, tx->br, m);
|
|
}
|
|
|
|
return (err);
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void
|
|
mxge_start_locked(struct mxge_slice_state *ss)
|
|
{
|
|
mxge_softc_t *sc;
|
|
struct mbuf *m;
|
|
struct ifnet *ifp;
|
|
mxge_tx_ring_t *tx;
|
|
|
|
sc = ss->sc;
|
|
ifp = sc->ifp;
|
|
tx = &ss->tx;
|
|
while ((tx->mask - (tx->req - tx->done)) > tx->max_desc) {
|
|
IFQ_DRV_DEQUEUE(&ifp->if_snd, m);
|
|
if (m == NULL) {
|
|
return;
|
|
}
|
|
/* let BPF see it */
|
|
BPF_MTAP(ifp, m);
|
|
|
|
/* give it to the nic */
|
|
mxge_encap(ss, m);
|
|
}
|
|
/* ran out of transmit slots */
|
|
if ((sc->ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0) {
|
|
sc->ifp->if_drv_flags |= IFF_DRV_OACTIVE;
|
|
tx->stall++;
|
|
}
|
|
}
|
|
#endif
|
|
static void
|
|
mxge_start(struct ifnet *ifp)
|
|
{
|
|
mxge_softc_t *sc = ifp->if_softc;
|
|
struct mxge_slice_state *ss;
|
|
|
|
/* only use the first slice for now */
|
|
ss = &sc->ss[0];
|
|
mtx_lock(&ss->tx.mtx);
|
|
mxge_start_locked(ss);
|
|
mtx_unlock(&ss->tx.mtx);
|
|
}
|
|
|
|
/*
|
|
* copy an array of mcp_kreq_ether_recv_t's to the mcp. Copy
|
|
* at most 32 bytes at a time, so as to avoid involving the software
|
|
* pio handler in the nic. We re-write the first segment's low
|
|
* DMA address to mark it valid only after we write the entire chunk
|
|
* in a burst
|
|
*/
|
|
static inline void
|
|
mxge_submit_8rx(volatile mcp_kreq_ether_recv_t *dst,
|
|
mcp_kreq_ether_recv_t *src)
|
|
{
|
|
uint32_t low;
|
|
|
|
low = src->addr_low;
|
|
src->addr_low = 0xffffffff;
|
|
mxge_pio_copy(dst, src, 4 * sizeof (*src));
|
|
wmb();
|
|
mxge_pio_copy(dst + 4, src + 4, 4 * sizeof (*src));
|
|
wmb();
|
|
src->addr_low = low;
|
|
dst->addr_low = low;
|
|
wmb();
|
|
}
|
|
|
|
static int
|
|
mxge_get_buf_small(struct mxge_slice_state *ss, bus_dmamap_t map, int idx)
|
|
{
|
|
bus_dma_segment_t seg;
|
|
struct mbuf *m;
|
|
mxge_rx_ring_t *rx = &ss->rx_small;
|
|
int cnt, err;
|
|
|
|
m = m_gethdr(M_NOWAIT, MT_DATA);
|
|
if (m == NULL) {
|
|
rx->alloc_fail++;
|
|
err = ENOBUFS;
|
|
goto done;
|
|
}
|
|
m->m_len = MHLEN;
|
|
err = bus_dmamap_load_mbuf_sg(rx->dmat, map, m,
|
|
&seg, &cnt, BUS_DMA_NOWAIT);
|
|
if (err != 0) {
|
|
m_free(m);
|
|
goto done;
|
|
}
|
|
rx->info[idx].m = m;
|
|
rx->shadow[idx].addr_low =
|
|
htobe32(MXGE_LOWPART_TO_U32(seg.ds_addr));
|
|
rx->shadow[idx].addr_high =
|
|
htobe32(MXGE_HIGHPART_TO_U32(seg.ds_addr));
|
|
|
|
done:
|
|
if ((idx & 7) == 7)
|
|
mxge_submit_8rx(&rx->lanai[idx - 7], &rx->shadow[idx - 7]);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
mxge_get_buf_big(struct mxge_slice_state *ss, bus_dmamap_t map, int idx)
|
|
{
|
|
bus_dma_segment_t seg[3];
|
|
struct mbuf *m;
|
|
mxge_rx_ring_t *rx = &ss->rx_big;
|
|
int cnt, err, i;
|
|
|
|
m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, rx->cl_size);
|
|
if (m == NULL) {
|
|
rx->alloc_fail++;
|
|
err = ENOBUFS;
|
|
goto done;
|
|
}
|
|
m->m_len = rx->mlen;
|
|
err = bus_dmamap_load_mbuf_sg(rx->dmat, map, m,
|
|
seg, &cnt, BUS_DMA_NOWAIT);
|
|
if (err != 0) {
|
|
m_free(m);
|
|
goto done;
|
|
}
|
|
rx->info[idx].m = m;
|
|
rx->shadow[idx].addr_low =
|
|
htobe32(MXGE_LOWPART_TO_U32(seg->ds_addr));
|
|
rx->shadow[idx].addr_high =
|
|
htobe32(MXGE_HIGHPART_TO_U32(seg->ds_addr));
|
|
|
|
#if MXGE_VIRT_JUMBOS
|
|
for (i = 1; i < cnt; i++) {
|
|
rx->shadow[idx + i].addr_low =
|
|
htobe32(MXGE_LOWPART_TO_U32(seg[i].ds_addr));
|
|
rx->shadow[idx + i].addr_high =
|
|
htobe32(MXGE_HIGHPART_TO_U32(seg[i].ds_addr));
|
|
}
|
|
#endif
|
|
|
|
done:
|
|
for (i = 0; i < rx->nbufs; i++) {
|
|
if ((idx & 7) == 7) {
|
|
mxge_submit_8rx(&rx->lanai[idx - 7],
|
|
&rx->shadow[idx - 7]);
|
|
}
|
|
idx++;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
#ifdef INET6
|
|
|
|
static uint16_t
|
|
mxge_csum_generic(uint16_t *raw, int len)
|
|
{
|
|
uint32_t csum;
|
|
|
|
|
|
csum = 0;
|
|
while (len > 0) {
|
|
csum += *raw;
|
|
raw++;
|
|
len -= 2;
|
|
}
|
|
csum = (csum >> 16) + (csum & 0xffff);
|
|
csum = (csum >> 16) + (csum & 0xffff);
|
|
return (uint16_t)csum;
|
|
}
|
|
|
|
static inline uint16_t
|
|
mxge_rx_csum6(void *p, struct mbuf *m, uint32_t csum)
|
|
{
|
|
uint32_t partial;
|
|
int nxt, cksum_offset;
|
|
struct ip6_hdr *ip6 = p;
|
|
uint16_t c;
|
|
|
|
nxt = ip6->ip6_nxt;
|
|
cksum_offset = sizeof (*ip6) + ETHER_HDR_LEN;
|
|
if (nxt != IPPROTO_TCP && nxt != IPPROTO_UDP) {
|
|
cksum_offset = ip6_lasthdr(m, ETHER_HDR_LEN,
|
|
IPPROTO_IPV6, &nxt);
|
|
if (nxt != IPPROTO_TCP && nxt != IPPROTO_UDP)
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* IPv6 headers do not contain a checksum, and hence
|
|
* do not checksum to zero, so they don't "fall out"
|
|
* of the partial checksum calculation like IPv4
|
|
* headers do. We need to fix the partial checksum by
|
|
* subtracting the checksum of the IPv6 header.
|
|
*/
|
|
|
|
partial = mxge_csum_generic((uint16_t *)ip6, cksum_offset -
|
|
ETHER_HDR_LEN);
|
|
csum += ~partial;
|
|
csum += (csum < ~partial);
|
|
csum = (csum >> 16) + (csum & 0xFFFF);
|
|
csum = (csum >> 16) + (csum & 0xFFFF);
|
|
c = in6_cksum_pseudo(ip6, m->m_pkthdr.len - cksum_offset, nxt,
|
|
csum);
|
|
c ^= 0xffff;
|
|
return (c);
|
|
}
|
|
#endif /* INET6 */
|
|
/*
|
|
* Myri10GE hardware checksums are not valid if the sender
|
|
* padded the frame with non-zero padding. This is because
|
|
* the firmware just does a simple 16-bit 1s complement
|
|
* checksum across the entire frame, excluding the first 14
|
|
* bytes. It is best to simply to check the checksum and
|
|
* tell the stack about it only if the checksum is good
|
|
*/
|
|
|
|
static inline uint16_t
|
|
mxge_rx_csum(struct mbuf *m, int csum)
|
|
{
|
|
struct ether_header *eh;
|
|
#ifdef INET
|
|
struct ip *ip;
|
|
#endif
|
|
#if defined(INET) || defined(INET6)
|
|
int cap = m->m_pkthdr.rcvif->if_capenable;
|
|
#endif
|
|
uint16_t c, etype;
|
|
|
|
|
|
eh = mtod(m, struct ether_header *);
|
|
etype = ntohs(eh->ether_type);
|
|
switch (etype) {
|
|
#ifdef INET
|
|
case ETHERTYPE_IP:
|
|
if ((cap & IFCAP_RXCSUM) == 0)
|
|
return (1);
|
|
ip = (struct ip *)(eh + 1);
|
|
if (ip->ip_p != IPPROTO_TCP && ip->ip_p != IPPROTO_UDP)
|
|
return (1);
|
|
c = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr,
|
|
htonl(ntohs(csum) + ntohs(ip->ip_len) -
|
|
(ip->ip_hl << 2) + ip->ip_p));
|
|
c ^= 0xffff;
|
|
break;
|
|
#endif
|
|
#ifdef INET6
|
|
case ETHERTYPE_IPV6:
|
|
if ((cap & IFCAP_RXCSUM_IPV6) == 0)
|
|
return (1);
|
|
c = mxge_rx_csum6((eh + 1), m, csum);
|
|
break;
|
|
#endif
|
|
default:
|
|
c = 1;
|
|
}
|
|
return (c);
|
|
}
|
|
|
|
static void
|
|
mxge_vlan_tag_remove(struct mbuf *m, uint32_t *csum)
|
|
{
|
|
struct ether_vlan_header *evl;
|
|
struct ether_header *eh;
|
|
uint32_t partial;
|
|
|
|
evl = mtod(m, struct ether_vlan_header *);
|
|
eh = mtod(m, struct ether_header *);
|
|
|
|
/*
|
|
* fix checksum by subtracting ETHER_VLAN_ENCAP_LEN bytes
|
|
* after what the firmware thought was the end of the ethernet
|
|
* header.
|
|
*/
|
|
|
|
/* put checksum into host byte order */
|
|
*csum = ntohs(*csum);
|
|
partial = ntohl(*(uint32_t *)(mtod(m, char *) + ETHER_HDR_LEN));
|
|
(*csum) += ~partial;
|
|
(*csum) += ((*csum) < ~partial);
|
|
(*csum) = ((*csum) >> 16) + ((*csum) & 0xFFFF);
|
|
(*csum) = ((*csum) >> 16) + ((*csum) & 0xFFFF);
|
|
|
|
/* restore checksum to network byte order;
|
|
later consumers expect this */
|
|
*csum = htons(*csum);
|
|
|
|
/* save the tag */
|
|
#ifdef MXGE_NEW_VLAN_API
|
|
m->m_pkthdr.ether_vtag = ntohs(evl->evl_tag);
|
|
#else
|
|
{
|
|
struct m_tag *mtag;
|
|
mtag = m_tag_alloc(MTAG_VLAN, MTAG_VLAN_TAG, sizeof(u_int),
|
|
M_NOWAIT);
|
|
if (mtag == NULL)
|
|
return;
|
|
VLAN_TAG_VALUE(mtag) = ntohs(evl->evl_tag);
|
|
m_tag_prepend(m, mtag);
|
|
}
|
|
|
|
#endif
|
|
m->m_flags |= M_VLANTAG;
|
|
|
|
/*
|
|
* Remove the 802.1q header by copying the Ethernet
|
|
* addresses over it and adjusting the beginning of
|
|
* the data in the mbuf. The encapsulated Ethernet
|
|
* type field is already in place.
|
|
*/
|
|
bcopy((char *)evl, (char *)evl + ETHER_VLAN_ENCAP_LEN,
|
|
ETHER_HDR_LEN - ETHER_TYPE_LEN);
|
|
m_adj(m, ETHER_VLAN_ENCAP_LEN);
|
|
}
|
|
|
|
|
|
static inline void
|
|
mxge_rx_done_big(struct mxge_slice_state *ss, uint32_t len,
|
|
uint32_t csum, int lro)
|
|
{
|
|
mxge_softc_t *sc;
|
|
struct ifnet *ifp;
|
|
struct mbuf *m;
|
|
struct ether_header *eh;
|
|
mxge_rx_ring_t *rx;
|
|
bus_dmamap_t old_map;
|
|
int idx;
|
|
|
|
sc = ss->sc;
|
|
ifp = sc->ifp;
|
|
rx = &ss->rx_big;
|
|
idx = rx->cnt & rx->mask;
|
|
rx->cnt += rx->nbufs;
|
|
/* save a pointer to the received mbuf */
|
|
m = rx->info[idx].m;
|
|
/* try to replace the received mbuf */
|
|
if (mxge_get_buf_big(ss, rx->extra_map, idx)) {
|
|
/* drop the frame -- the old mbuf is re-cycled */
|
|
if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
|
|
return;
|
|
}
|
|
|
|
/* unmap the received buffer */
|
|
old_map = rx->info[idx].map;
|
|
bus_dmamap_sync(rx->dmat, old_map, BUS_DMASYNC_POSTREAD);
|
|
bus_dmamap_unload(rx->dmat, old_map);
|
|
|
|
/* swap the bus_dmamap_t's */
|
|
rx->info[idx].map = rx->extra_map;
|
|
rx->extra_map = old_map;
|
|
|
|
/* mcp implicitly skips 1st 2 bytes so that packet is properly
|
|
* aligned */
|
|
m->m_data += MXGEFW_PAD;
|
|
|
|
m->m_pkthdr.rcvif = ifp;
|
|
m->m_len = m->m_pkthdr.len = len;
|
|
ss->ipackets++;
|
|
eh = mtod(m, struct ether_header *);
|
|
if (eh->ether_type == htons(ETHERTYPE_VLAN)) {
|
|
mxge_vlan_tag_remove(m, &csum);
|
|
}
|
|
/* flowid only valid if RSS hashing is enabled */
|
|
if (sc->num_slices > 1) {
|
|
m->m_pkthdr.flowid = (ss - sc->ss);
|
|
M_HASHTYPE_SET(m, M_HASHTYPE_OPAQUE);
|
|
}
|
|
/* if the checksum is valid, mark it in the mbuf header */
|
|
if ((ifp->if_capenable & (IFCAP_RXCSUM_IPV6 | IFCAP_RXCSUM)) &&
|
|
(0 == mxge_rx_csum(m, csum))) {
|
|
/* Tell the stack that the checksum is good */
|
|
m->m_pkthdr.csum_data = 0xffff;
|
|
m->m_pkthdr.csum_flags = CSUM_PSEUDO_HDR |
|
|
CSUM_DATA_VALID;
|
|
|
|
#if defined(INET) || defined (INET6)
|
|
if (lro && (0 == tcp_lro_rx(&ss->lc, m, 0)))
|
|
return;
|
|
#endif
|
|
}
|
|
/* pass the frame up the stack */
|
|
(*ifp->if_input)(ifp, m);
|
|
}
|
|
|
|
static inline void
|
|
mxge_rx_done_small(struct mxge_slice_state *ss, uint32_t len,
|
|
uint32_t csum, int lro)
|
|
{
|
|
mxge_softc_t *sc;
|
|
struct ifnet *ifp;
|
|
struct ether_header *eh;
|
|
struct mbuf *m;
|
|
mxge_rx_ring_t *rx;
|
|
bus_dmamap_t old_map;
|
|
int idx;
|
|
|
|
sc = ss->sc;
|
|
ifp = sc->ifp;
|
|
rx = &ss->rx_small;
|
|
idx = rx->cnt & rx->mask;
|
|
rx->cnt++;
|
|
/* save a pointer to the received mbuf */
|
|
m = rx->info[idx].m;
|
|
/* try to replace the received mbuf */
|
|
if (mxge_get_buf_small(ss, rx->extra_map, idx)) {
|
|
/* drop the frame -- the old mbuf is re-cycled */
|
|
if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
|
|
return;
|
|
}
|
|
|
|
/* unmap the received buffer */
|
|
old_map = rx->info[idx].map;
|
|
bus_dmamap_sync(rx->dmat, old_map, BUS_DMASYNC_POSTREAD);
|
|
bus_dmamap_unload(rx->dmat, old_map);
|
|
|
|
/* swap the bus_dmamap_t's */
|
|
rx->info[idx].map = rx->extra_map;
|
|
rx->extra_map = old_map;
|
|
|
|
/* mcp implicitly skips 1st 2 bytes so that packet is properly
|
|
* aligned */
|
|
m->m_data += MXGEFW_PAD;
|
|
|
|
m->m_pkthdr.rcvif = ifp;
|
|
m->m_len = m->m_pkthdr.len = len;
|
|
ss->ipackets++;
|
|
eh = mtod(m, struct ether_header *);
|
|
if (eh->ether_type == htons(ETHERTYPE_VLAN)) {
|
|
mxge_vlan_tag_remove(m, &csum);
|
|
}
|
|
/* flowid only valid if RSS hashing is enabled */
|
|
if (sc->num_slices > 1) {
|
|
m->m_pkthdr.flowid = (ss - sc->ss);
|
|
M_HASHTYPE_SET(m, M_HASHTYPE_OPAQUE);
|
|
}
|
|
/* if the checksum is valid, mark it in the mbuf header */
|
|
if ((ifp->if_capenable & (IFCAP_RXCSUM_IPV6 | IFCAP_RXCSUM)) &&
|
|
(0 == mxge_rx_csum(m, csum))) {
|
|
/* Tell the stack that the checksum is good */
|
|
m->m_pkthdr.csum_data = 0xffff;
|
|
m->m_pkthdr.csum_flags = CSUM_PSEUDO_HDR |
|
|
CSUM_DATA_VALID;
|
|
|
|
#if defined(INET) || defined (INET6)
|
|
if (lro && (0 == tcp_lro_rx(&ss->lc, m, csum)))
|
|
return;
|
|
#endif
|
|
}
|
|
/* pass the frame up the stack */
|
|
(*ifp->if_input)(ifp, m);
|
|
}
|
|
|
|
static inline void
|
|
mxge_clean_rx_done(struct mxge_slice_state *ss)
|
|
{
|
|
mxge_rx_done_t *rx_done = &ss->rx_done;
|
|
int limit = 0;
|
|
uint16_t length;
|
|
uint16_t checksum;
|
|
int lro;
|
|
|
|
lro = ss->sc->ifp->if_capenable & IFCAP_LRO;
|
|
while (rx_done->entry[rx_done->idx].length != 0) {
|
|
length = ntohs(rx_done->entry[rx_done->idx].length);
|
|
rx_done->entry[rx_done->idx].length = 0;
|
|
checksum = rx_done->entry[rx_done->idx].checksum;
|
|
if (length <= (MHLEN - MXGEFW_PAD))
|
|
mxge_rx_done_small(ss, length, checksum, lro);
|
|
else
|
|
mxge_rx_done_big(ss, length, checksum, lro);
|
|
rx_done->cnt++;
|
|
rx_done->idx = rx_done->cnt & rx_done->mask;
|
|
|
|
/* limit potential for livelock */
|
|
if (__predict_false(++limit > rx_done->mask / 2))
|
|
break;
|
|
}
|
|
#if defined(INET) || defined (INET6)
|
|
tcp_lro_flush_all(&ss->lc);
|
|
#endif
|
|
}
|
|
|
|
|
|
static inline void
|
|
mxge_tx_done(struct mxge_slice_state *ss, uint32_t mcp_idx)
|
|
{
|
|
struct ifnet *ifp;
|
|
mxge_tx_ring_t *tx;
|
|
struct mbuf *m;
|
|
bus_dmamap_t map;
|
|
int idx;
|
|
int *flags;
|
|
|
|
tx = &ss->tx;
|
|
ifp = ss->sc->ifp;
|
|
while (tx->pkt_done != mcp_idx) {
|
|
idx = tx->done & tx->mask;
|
|
tx->done++;
|
|
m = tx->info[idx].m;
|
|
/* mbuf and DMA map only attached to the first
|
|
segment per-mbuf */
|
|
if (m != NULL) {
|
|
ss->obytes += m->m_pkthdr.len;
|
|
if (m->m_flags & M_MCAST)
|
|
ss->omcasts++;
|
|
ss->opackets++;
|
|
tx->info[idx].m = NULL;
|
|
map = tx->info[idx].map;
|
|
bus_dmamap_unload(tx->dmat, map);
|
|
m_freem(m);
|
|
}
|
|
if (tx->info[idx].flag) {
|
|
tx->info[idx].flag = 0;
|
|
tx->pkt_done++;
|
|
}
|
|
}
|
|
|
|
/* If we have space, clear IFF_OACTIVE to tell the stack that
|
|
its OK to send packets */
|
|
#ifdef IFNET_BUF_RING
|
|
flags = &ss->if_drv_flags;
|
|
#else
|
|
flags = &ifp->if_drv_flags;
|
|
#endif
|
|
mtx_lock(&ss->tx.mtx);
|
|
if ((*flags) & IFF_DRV_OACTIVE &&
|
|
tx->req - tx->done < (tx->mask + 1)/4) {
|
|
*(flags) &= ~IFF_DRV_OACTIVE;
|
|
ss->tx.wake++;
|
|
mxge_start_locked(ss);
|
|
}
|
|
#ifdef IFNET_BUF_RING
|
|
if ((ss->sc->num_slices > 1) && (tx->req == tx->done)) {
|
|
/* let the NIC stop polling this queue, since there
|
|
* are no more transmits pending */
|
|
if (tx->req == tx->done) {
|
|
*tx->send_stop = 1;
|
|
tx->queue_active = 0;
|
|
tx->deactivate++;
|
|
wmb();
|
|
}
|
|
}
|
|
#endif
|
|
mtx_unlock(&ss->tx.mtx);
|
|
|
|
}
|
|
|
|
static struct mxge_media_type mxge_xfp_media_types[] =
|
|
{
|
|
{IFM_10G_CX4, 0x7f, "10GBASE-CX4 (module)"},
|
|
{IFM_10G_SR, (1 << 7), "10GBASE-SR"},
|
|
{IFM_10G_LR, (1 << 6), "10GBASE-LR"},
|
|
{0, (1 << 5), "10GBASE-ER"},
|
|
{IFM_10G_LRM, (1 << 4), "10GBASE-LRM"},
|
|
{0, (1 << 3), "10GBASE-SW"},
|
|
{0, (1 << 2), "10GBASE-LW"},
|
|
{0, (1 << 1), "10GBASE-EW"},
|
|
{0, (1 << 0), "Reserved"}
|
|
};
|
|
static struct mxge_media_type mxge_sfp_media_types[] =
|
|
{
|
|
{IFM_10G_TWINAX, 0, "10GBASE-Twinax"},
|
|
{0, (1 << 7), "Reserved"},
|
|
{IFM_10G_LRM, (1 << 6), "10GBASE-LRM"},
|
|
{IFM_10G_LR, (1 << 5), "10GBASE-LR"},
|
|
{IFM_10G_SR, (1 << 4), "10GBASE-SR"},
|
|
{IFM_10G_TWINAX,(1 << 0), "10GBASE-Twinax"}
|
|
};
|
|
|
|
static void
|
|
mxge_media_set(mxge_softc_t *sc, int media_type)
|
|
{
|
|
|
|
|
|
ifmedia_add(&sc->media, IFM_ETHER | IFM_FDX | media_type,
|
|
0, NULL);
|
|
ifmedia_set(&sc->media, IFM_ETHER | IFM_FDX | media_type);
|
|
sc->current_media = media_type;
|
|
sc->media.ifm_media = sc->media.ifm_cur->ifm_media;
|
|
}
|
|
|
|
static void
|
|
mxge_media_init(mxge_softc_t *sc)
|
|
{
|
|
char *ptr;
|
|
int i;
|
|
|
|
ifmedia_removeall(&sc->media);
|
|
mxge_media_set(sc, IFM_AUTO);
|
|
|
|
/*
|
|
* parse the product code to deterimine the interface type
|
|
* (CX4, XFP, Quad Ribbon Fiber) by looking at the character
|
|
* after the 3rd dash in the driver's cached copy of the
|
|
* EEPROM's product code string.
|
|
*/
|
|
ptr = sc->product_code_string;
|
|
if (ptr == NULL) {
|
|
device_printf(sc->dev, "Missing product code\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < 3; i++, ptr++) {
|
|
ptr = strchr(ptr, '-');
|
|
if (ptr == NULL) {
|
|
device_printf(sc->dev,
|
|
"only %d dashes in PC?!?\n", i);
|
|
return;
|
|
}
|
|
}
|
|
if (*ptr == 'C' || *(ptr +1) == 'C') {
|
|
/* -C is CX4 */
|
|
sc->connector = MXGE_CX4;
|
|
mxge_media_set(sc, IFM_10G_CX4);
|
|
} else if (*ptr == 'Q') {
|
|
/* -Q is Quad Ribbon Fiber */
|
|
sc->connector = MXGE_QRF;
|
|
device_printf(sc->dev, "Quad Ribbon Fiber Media\n");
|
|
/* FreeBSD has no media type for Quad ribbon fiber */
|
|
} else if (*ptr == 'R') {
|
|
/* -R is XFP */
|
|
sc->connector = MXGE_XFP;
|
|
} else if (*ptr == 'S' || *(ptr +1) == 'S') {
|
|
/* -S or -2S is SFP+ */
|
|
sc->connector = MXGE_SFP;
|
|
} else {
|
|
device_printf(sc->dev, "Unknown media type: %c\n", *ptr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Determine the media type for a NIC. Some XFPs will identify
|
|
* themselves only when their link is up, so this is initiated via a
|
|
* link up interrupt. However, this can potentially take up to
|
|
* several milliseconds, so it is run via the watchdog routine, rather
|
|
* than in the interrupt handler itself.
|
|
*/
|
|
static void
|
|
mxge_media_probe(mxge_softc_t *sc)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
char *cage_type;
|
|
|
|
struct mxge_media_type *mxge_media_types = NULL;
|
|
int i, err, ms, mxge_media_type_entries;
|
|
uint32_t byte;
|
|
|
|
sc->need_media_probe = 0;
|
|
|
|
if (sc->connector == MXGE_XFP) {
|
|
/* -R is XFP */
|
|
mxge_media_types = mxge_xfp_media_types;
|
|
mxge_media_type_entries =
|
|
nitems(mxge_xfp_media_types);
|
|
byte = MXGE_XFP_COMPLIANCE_BYTE;
|
|
cage_type = "XFP";
|
|
} else if (sc->connector == MXGE_SFP) {
|
|
/* -S or -2S is SFP+ */
|
|
mxge_media_types = mxge_sfp_media_types;
|
|
mxge_media_type_entries =
|
|
nitems(mxge_sfp_media_types);
|
|
cage_type = "SFP+";
|
|
byte = 3;
|
|
} else {
|
|
/* nothing to do; media type cannot change */
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* At this point we know the NIC has an XFP cage, so now we
|
|
* try to determine what is in the cage by using the
|
|
* firmware's XFP I2C commands to read the XFP 10GbE compilance
|
|
* register. We read just one byte, which may take over
|
|
* a millisecond
|
|
*/
|
|
|
|
cmd.data0 = 0; /* just fetch 1 byte, not all 256 */
|
|
cmd.data1 = byte;
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_I2C_READ, &cmd);
|
|
if (err == MXGEFW_CMD_ERROR_I2C_FAILURE) {
|
|
device_printf(sc->dev, "failed to read XFP\n");
|
|
}
|
|
if (err == MXGEFW_CMD_ERROR_I2C_ABSENT) {
|
|
device_printf(sc->dev, "Type R/S with no XFP!?!?\n");
|
|
}
|
|
if (err != MXGEFW_CMD_OK) {
|
|
return;
|
|
}
|
|
|
|
/* now we wait for the data to be cached */
|
|
cmd.data0 = byte;
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_I2C_BYTE, &cmd);
|
|
for (ms = 0; (err == EBUSY) && (ms < 50); ms++) {
|
|
DELAY(1000);
|
|
cmd.data0 = byte;
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_I2C_BYTE, &cmd);
|
|
}
|
|
if (err != MXGEFW_CMD_OK) {
|
|
device_printf(sc->dev, "failed to read %s (%d, %dms)\n",
|
|
cage_type, err, ms);
|
|
return;
|
|
}
|
|
|
|
if (cmd.data0 == mxge_media_types[0].bitmask) {
|
|
if (mxge_verbose)
|
|
device_printf(sc->dev, "%s:%s\n", cage_type,
|
|
mxge_media_types[0].name);
|
|
if (sc->current_media != mxge_media_types[0].flag) {
|
|
mxge_media_init(sc);
|
|
mxge_media_set(sc, mxge_media_types[0].flag);
|
|
}
|
|
return;
|
|
}
|
|
for (i = 1; i < mxge_media_type_entries; i++) {
|
|
if (cmd.data0 & mxge_media_types[i].bitmask) {
|
|
if (mxge_verbose)
|
|
device_printf(sc->dev, "%s:%s\n",
|
|
cage_type,
|
|
mxge_media_types[i].name);
|
|
|
|
if (sc->current_media != mxge_media_types[i].flag) {
|
|
mxge_media_init(sc);
|
|
mxge_media_set(sc, mxge_media_types[i].flag);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (mxge_verbose)
|
|
device_printf(sc->dev, "%s media 0x%x unknown\n",
|
|
cage_type, cmd.data0);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
mxge_intr(void *arg)
|
|
{
|
|
struct mxge_slice_state *ss = arg;
|
|
mxge_softc_t *sc = ss->sc;
|
|
mcp_irq_data_t *stats = ss->fw_stats;
|
|
mxge_tx_ring_t *tx = &ss->tx;
|
|
mxge_rx_done_t *rx_done = &ss->rx_done;
|
|
uint32_t send_done_count;
|
|
uint8_t valid;
|
|
|
|
|
|
#ifndef IFNET_BUF_RING
|
|
/* an interrupt on a non-zero slice is implicitly valid
|
|
since MSI-X irqs are not shared */
|
|
if (ss != sc->ss) {
|
|
mxge_clean_rx_done(ss);
|
|
*ss->irq_claim = be32toh(3);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* make sure the DMA has finished */
|
|
if (!stats->valid) {
|
|
return;
|
|
}
|
|
valid = stats->valid;
|
|
|
|
if (sc->legacy_irq) {
|
|
/* lower legacy IRQ */
|
|
*sc->irq_deassert = 0;
|
|
if (!mxge_deassert_wait)
|
|
/* don't wait for conf. that irq is low */
|
|
stats->valid = 0;
|
|
} else {
|
|
stats->valid = 0;
|
|
}
|
|
|
|
/* loop while waiting for legacy irq deassertion */
|
|
do {
|
|
/* check for transmit completes and receives */
|
|
send_done_count = be32toh(stats->send_done_count);
|
|
while ((send_done_count != tx->pkt_done) ||
|
|
(rx_done->entry[rx_done->idx].length != 0)) {
|
|
if (send_done_count != tx->pkt_done)
|
|
mxge_tx_done(ss, (int)send_done_count);
|
|
mxge_clean_rx_done(ss);
|
|
send_done_count = be32toh(stats->send_done_count);
|
|
}
|
|
if (sc->legacy_irq && mxge_deassert_wait)
|
|
wmb();
|
|
} while (*((volatile uint8_t *) &stats->valid));
|
|
|
|
/* fw link & error stats meaningful only on the first slice */
|
|
if (__predict_false((ss == sc->ss) && stats->stats_updated)) {
|
|
if (sc->link_state != stats->link_up) {
|
|
sc->link_state = stats->link_up;
|
|
if (sc->link_state) {
|
|
if_link_state_change(sc->ifp, LINK_STATE_UP);
|
|
if (mxge_verbose)
|
|
device_printf(sc->dev, "link up\n");
|
|
} else {
|
|
if_link_state_change(sc->ifp, LINK_STATE_DOWN);
|
|
if (mxge_verbose)
|
|
device_printf(sc->dev, "link down\n");
|
|
}
|
|
sc->need_media_probe = 1;
|
|
}
|
|
if (sc->rdma_tags_available !=
|
|
be32toh(stats->rdma_tags_available)) {
|
|
sc->rdma_tags_available =
|
|
be32toh(stats->rdma_tags_available);
|
|
device_printf(sc->dev, "RDMA timed out! %d tags "
|
|
"left\n", sc->rdma_tags_available);
|
|
}
|
|
|
|
if (stats->link_down) {
|
|
sc->down_cnt += stats->link_down;
|
|
sc->link_state = 0;
|
|
if_link_state_change(sc->ifp, LINK_STATE_DOWN);
|
|
}
|
|
}
|
|
|
|
/* check to see if we have rx token to pass back */
|
|
if (valid & 0x1)
|
|
*ss->irq_claim = be32toh(3);
|
|
*(ss->irq_claim + 1) = be32toh(3);
|
|
}
|
|
|
|
static void
|
|
mxge_init(void *arg)
|
|
{
|
|
mxge_softc_t *sc = arg;
|
|
struct ifnet *ifp = sc->ifp;
|
|
|
|
|
|
mtx_lock(&sc->driver_mtx);
|
|
if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
|
|
(void) mxge_open(sc);
|
|
mtx_unlock(&sc->driver_mtx);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
mxge_free_slice_mbufs(struct mxge_slice_state *ss)
|
|
{
|
|
int i;
|
|
|
|
#if defined(INET) || defined(INET6)
|
|
tcp_lro_free(&ss->lc);
|
|
#endif
|
|
for (i = 0; i <= ss->rx_big.mask; i++) {
|
|
if (ss->rx_big.info[i].m == NULL)
|
|
continue;
|
|
bus_dmamap_unload(ss->rx_big.dmat,
|
|
ss->rx_big.info[i].map);
|
|
m_freem(ss->rx_big.info[i].m);
|
|
ss->rx_big.info[i].m = NULL;
|
|
}
|
|
|
|
for (i = 0; i <= ss->rx_small.mask; i++) {
|
|
if (ss->rx_small.info[i].m == NULL)
|
|
continue;
|
|
bus_dmamap_unload(ss->rx_small.dmat,
|
|
ss->rx_small.info[i].map);
|
|
m_freem(ss->rx_small.info[i].m);
|
|
ss->rx_small.info[i].m = NULL;
|
|
}
|
|
|
|
/* transmit ring used only on the first slice */
|
|
if (ss->tx.info == NULL)
|
|
return;
|
|
|
|
for (i = 0; i <= ss->tx.mask; i++) {
|
|
ss->tx.info[i].flag = 0;
|
|
if (ss->tx.info[i].m == NULL)
|
|
continue;
|
|
bus_dmamap_unload(ss->tx.dmat,
|
|
ss->tx.info[i].map);
|
|
m_freem(ss->tx.info[i].m);
|
|
ss->tx.info[i].m = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
mxge_free_mbufs(mxge_softc_t *sc)
|
|
{
|
|
int slice;
|
|
|
|
for (slice = 0; slice < sc->num_slices; slice++)
|
|
mxge_free_slice_mbufs(&sc->ss[slice]);
|
|
}
|
|
|
|
static void
|
|
mxge_free_slice_rings(struct mxge_slice_state *ss)
|
|
{
|
|
int i;
|
|
|
|
|
|
if (ss->rx_done.entry != NULL)
|
|
mxge_dma_free(&ss->rx_done.dma);
|
|
ss->rx_done.entry = NULL;
|
|
|
|
if (ss->tx.req_bytes != NULL)
|
|
free(ss->tx.req_bytes, M_DEVBUF);
|
|
ss->tx.req_bytes = NULL;
|
|
|
|
if (ss->tx.seg_list != NULL)
|
|
free(ss->tx.seg_list, M_DEVBUF);
|
|
ss->tx.seg_list = NULL;
|
|
|
|
if (ss->rx_small.shadow != NULL)
|
|
free(ss->rx_small.shadow, M_DEVBUF);
|
|
ss->rx_small.shadow = NULL;
|
|
|
|
if (ss->rx_big.shadow != NULL)
|
|
free(ss->rx_big.shadow, M_DEVBUF);
|
|
ss->rx_big.shadow = NULL;
|
|
|
|
if (ss->tx.info != NULL) {
|
|
if (ss->tx.dmat != NULL) {
|
|
for (i = 0; i <= ss->tx.mask; i++) {
|
|
bus_dmamap_destroy(ss->tx.dmat,
|
|
ss->tx.info[i].map);
|
|
}
|
|
bus_dma_tag_destroy(ss->tx.dmat);
|
|
}
|
|
free(ss->tx.info, M_DEVBUF);
|
|
}
|
|
ss->tx.info = NULL;
|
|
|
|
if (ss->rx_small.info != NULL) {
|
|
if (ss->rx_small.dmat != NULL) {
|
|
for (i = 0; i <= ss->rx_small.mask; i++) {
|
|
bus_dmamap_destroy(ss->rx_small.dmat,
|
|
ss->rx_small.info[i].map);
|
|
}
|
|
bus_dmamap_destroy(ss->rx_small.dmat,
|
|
ss->rx_small.extra_map);
|
|
bus_dma_tag_destroy(ss->rx_small.dmat);
|
|
}
|
|
free(ss->rx_small.info, M_DEVBUF);
|
|
}
|
|
ss->rx_small.info = NULL;
|
|
|
|
if (ss->rx_big.info != NULL) {
|
|
if (ss->rx_big.dmat != NULL) {
|
|
for (i = 0; i <= ss->rx_big.mask; i++) {
|
|
bus_dmamap_destroy(ss->rx_big.dmat,
|
|
ss->rx_big.info[i].map);
|
|
}
|
|
bus_dmamap_destroy(ss->rx_big.dmat,
|
|
ss->rx_big.extra_map);
|
|
bus_dma_tag_destroy(ss->rx_big.dmat);
|
|
}
|
|
free(ss->rx_big.info, M_DEVBUF);
|
|
}
|
|
ss->rx_big.info = NULL;
|
|
}
|
|
|
|
static void
|
|
mxge_free_rings(mxge_softc_t *sc)
|
|
{
|
|
int slice;
|
|
|
|
for (slice = 0; slice < sc->num_slices; slice++)
|
|
mxge_free_slice_rings(&sc->ss[slice]);
|
|
}
|
|
|
|
static int
|
|
mxge_alloc_slice_rings(struct mxge_slice_state *ss, int rx_ring_entries,
|
|
int tx_ring_entries)
|
|
{
|
|
mxge_softc_t *sc = ss->sc;
|
|
size_t bytes;
|
|
int err, i;
|
|
|
|
/* allocate per-slice receive resources */
|
|
|
|
ss->rx_small.mask = ss->rx_big.mask = rx_ring_entries - 1;
|
|
ss->rx_done.mask = (2 * rx_ring_entries) - 1;
|
|
|
|
/* allocate the rx shadow rings */
|
|
bytes = rx_ring_entries * sizeof (*ss->rx_small.shadow);
|
|
ss->rx_small.shadow = malloc(bytes, M_DEVBUF, M_ZERO|M_WAITOK);
|
|
|
|
bytes = rx_ring_entries * sizeof (*ss->rx_big.shadow);
|
|
ss->rx_big.shadow = malloc(bytes, M_DEVBUF, M_ZERO|M_WAITOK);
|
|
|
|
/* allocate the rx host info rings */
|
|
bytes = rx_ring_entries * sizeof (*ss->rx_small.info);
|
|
ss->rx_small.info = malloc(bytes, M_DEVBUF, M_ZERO|M_WAITOK);
|
|
|
|
bytes = rx_ring_entries * sizeof (*ss->rx_big.info);
|
|
ss->rx_big.info = malloc(bytes, M_DEVBUF, M_ZERO|M_WAITOK);
|
|
|
|
/* allocate the rx busdma resources */
|
|
err = bus_dma_tag_create(sc->parent_dmat, /* parent */
|
|
1, /* alignment */
|
|
4096, /* boundary */
|
|
BUS_SPACE_MAXADDR, /* low */
|
|
BUS_SPACE_MAXADDR, /* high */
|
|
NULL, NULL, /* filter */
|
|
MHLEN, /* maxsize */
|
|
1, /* num segs */
|
|
MHLEN, /* maxsegsize */
|
|
BUS_DMA_ALLOCNOW, /* flags */
|
|
NULL, NULL, /* lock */
|
|
&ss->rx_small.dmat); /* tag */
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Err %d allocating rx_small dmat\n",
|
|
err);
|
|
return err;
|
|
}
|
|
|
|
err = bus_dma_tag_create(sc->parent_dmat, /* parent */
|
|
1, /* alignment */
|
|
#if MXGE_VIRT_JUMBOS
|
|
4096, /* boundary */
|
|
#else
|
|
0, /* boundary */
|
|
#endif
|
|
BUS_SPACE_MAXADDR, /* low */
|
|
BUS_SPACE_MAXADDR, /* high */
|
|
NULL, NULL, /* filter */
|
|
3*4096, /* maxsize */
|
|
#if MXGE_VIRT_JUMBOS
|
|
3, /* num segs */
|
|
4096, /* maxsegsize*/
|
|
#else
|
|
1, /* num segs */
|
|
MJUM9BYTES, /* maxsegsize*/
|
|
#endif
|
|
BUS_DMA_ALLOCNOW, /* flags */
|
|
NULL, NULL, /* lock */
|
|
&ss->rx_big.dmat); /* tag */
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Err %d allocating rx_big dmat\n",
|
|
err);
|
|
return err;
|
|
}
|
|
for (i = 0; i <= ss->rx_small.mask; i++) {
|
|
err = bus_dmamap_create(ss->rx_small.dmat, 0,
|
|
&ss->rx_small.info[i].map);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Err %d rx_small dmamap\n",
|
|
err);
|
|
return err;
|
|
}
|
|
}
|
|
err = bus_dmamap_create(ss->rx_small.dmat, 0,
|
|
&ss->rx_small.extra_map);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Err %d extra rx_small dmamap\n",
|
|
err);
|
|
return err;
|
|
}
|
|
|
|
for (i = 0; i <= ss->rx_big.mask; i++) {
|
|
err = bus_dmamap_create(ss->rx_big.dmat, 0,
|
|
&ss->rx_big.info[i].map);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Err %d rx_big dmamap\n",
|
|
err);
|
|
return err;
|
|
}
|
|
}
|
|
err = bus_dmamap_create(ss->rx_big.dmat, 0,
|
|
&ss->rx_big.extra_map);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Err %d extra rx_big dmamap\n",
|
|
err);
|
|
return err;
|
|
}
|
|
|
|
/* now allocate TX resources */
|
|
|
|
#ifndef IFNET_BUF_RING
|
|
/* only use a single TX ring for now */
|
|
if (ss != ss->sc->ss)
|
|
return 0;
|
|
#endif
|
|
|
|
ss->tx.mask = tx_ring_entries - 1;
|
|
ss->tx.max_desc = MIN(MXGE_MAX_SEND_DESC, tx_ring_entries / 4);
|
|
|
|
|
|
/* allocate the tx request copy block */
|
|
bytes = 8 +
|
|
sizeof (*ss->tx.req_list) * (ss->tx.max_desc + 4);
|
|
ss->tx.req_bytes = malloc(bytes, M_DEVBUF, M_WAITOK);
|
|
/* ensure req_list entries are aligned to 8 bytes */
|
|
ss->tx.req_list = (mcp_kreq_ether_send_t *)
|
|
((unsigned long)(ss->tx.req_bytes + 7) & ~7UL);
|
|
|
|
/* allocate the tx busdma segment list */
|
|
bytes = sizeof (*ss->tx.seg_list) * ss->tx.max_desc;
|
|
ss->tx.seg_list = (bus_dma_segment_t *)
|
|
malloc(bytes, M_DEVBUF, M_WAITOK);
|
|
|
|
/* allocate the tx host info ring */
|
|
bytes = tx_ring_entries * sizeof (*ss->tx.info);
|
|
ss->tx.info = malloc(bytes, M_DEVBUF, M_ZERO|M_WAITOK);
|
|
|
|
/* allocate the tx busdma resources */
|
|
err = bus_dma_tag_create(sc->parent_dmat, /* parent */
|
|
1, /* alignment */
|
|
sc->tx_boundary, /* boundary */
|
|
BUS_SPACE_MAXADDR, /* low */
|
|
BUS_SPACE_MAXADDR, /* high */
|
|
NULL, NULL, /* filter */
|
|
65536 + 256, /* maxsize */
|
|
ss->tx.max_desc - 2, /* num segs */
|
|
sc->tx_boundary, /* maxsegsz */
|
|
BUS_DMA_ALLOCNOW, /* flags */
|
|
NULL, NULL, /* lock */
|
|
&ss->tx.dmat); /* tag */
|
|
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Err %d allocating tx dmat\n",
|
|
err);
|
|
return err;
|
|
}
|
|
|
|
/* now use these tags to setup dmamaps for each slot
|
|
in the ring */
|
|
for (i = 0; i <= ss->tx.mask; i++) {
|
|
err = bus_dmamap_create(ss->tx.dmat, 0,
|
|
&ss->tx.info[i].map);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Err %d tx dmamap\n",
|
|
err);
|
|
return err;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int
|
|
mxge_alloc_rings(mxge_softc_t *sc)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
int tx_ring_size;
|
|
int tx_ring_entries, rx_ring_entries;
|
|
int err, slice;
|
|
|
|
/* get ring sizes */
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_GET_SEND_RING_SIZE, &cmd);
|
|
tx_ring_size = cmd.data0;
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Cannot determine tx ring sizes\n");
|
|
goto abort;
|
|
}
|
|
|
|
tx_ring_entries = tx_ring_size / sizeof (mcp_kreq_ether_send_t);
|
|
rx_ring_entries = sc->rx_ring_size / sizeof (mcp_dma_addr_t);
|
|
IFQ_SET_MAXLEN(&sc->ifp->if_snd, tx_ring_entries - 1);
|
|
sc->ifp->if_snd.ifq_drv_maxlen = sc->ifp->if_snd.ifq_maxlen;
|
|
IFQ_SET_READY(&sc->ifp->if_snd);
|
|
|
|
for (slice = 0; slice < sc->num_slices; slice++) {
|
|
err = mxge_alloc_slice_rings(&sc->ss[slice],
|
|
rx_ring_entries,
|
|
tx_ring_entries);
|
|
if (err != 0)
|
|
goto abort;
|
|
}
|
|
return 0;
|
|
|
|
abort:
|
|
mxge_free_rings(sc);
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
static void
|
|
mxge_choose_params(int mtu, int *big_buf_size, int *cl_size, int *nbufs)
|
|
{
|
|
int bufsize = mtu + ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN + MXGEFW_PAD;
|
|
|
|
if (bufsize < MCLBYTES) {
|
|
/* easy, everything fits in a single buffer */
|
|
*big_buf_size = MCLBYTES;
|
|
*cl_size = MCLBYTES;
|
|
*nbufs = 1;
|
|
return;
|
|
}
|
|
|
|
if (bufsize < MJUMPAGESIZE) {
|
|
/* still easy, everything still fits in a single buffer */
|
|
*big_buf_size = MJUMPAGESIZE;
|
|
*cl_size = MJUMPAGESIZE;
|
|
*nbufs = 1;
|
|
return;
|
|
}
|
|
#if MXGE_VIRT_JUMBOS
|
|
/* now we need to use virtually contiguous buffers */
|
|
*cl_size = MJUM9BYTES;
|
|
*big_buf_size = 4096;
|
|
*nbufs = mtu / 4096 + 1;
|
|
/* needs to be a power of two, so round up */
|
|
if (*nbufs == 3)
|
|
*nbufs = 4;
|
|
#else
|
|
*cl_size = MJUM9BYTES;
|
|
*big_buf_size = MJUM9BYTES;
|
|
*nbufs = 1;
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
mxge_slice_open(struct mxge_slice_state *ss, int nbufs, int cl_size)
|
|
{
|
|
mxge_softc_t *sc;
|
|
mxge_cmd_t cmd;
|
|
bus_dmamap_t map;
|
|
int err, i, slice;
|
|
|
|
|
|
sc = ss->sc;
|
|
slice = ss - sc->ss;
|
|
|
|
#if defined(INET) || defined(INET6)
|
|
(void)tcp_lro_init(&ss->lc);
|
|
#endif
|
|
ss->lc.ifp = sc->ifp;
|
|
|
|
/* get the lanai pointers to the send and receive rings */
|
|
|
|
err = 0;
|
|
#ifndef IFNET_BUF_RING
|
|
/* We currently only send from the first slice */
|
|
if (slice == 0) {
|
|
#endif
|
|
cmd.data0 = slice;
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_GET_SEND_OFFSET, &cmd);
|
|
ss->tx.lanai =
|
|
(volatile mcp_kreq_ether_send_t *)(sc->sram + cmd.data0);
|
|
ss->tx.send_go = (volatile uint32_t *)
|
|
(sc->sram + MXGEFW_ETH_SEND_GO + 64 * slice);
|
|
ss->tx.send_stop = (volatile uint32_t *)
|
|
(sc->sram + MXGEFW_ETH_SEND_STOP + 64 * slice);
|
|
#ifndef IFNET_BUF_RING
|
|
}
|
|
#endif
|
|
cmd.data0 = slice;
|
|
err |= mxge_send_cmd(sc,
|
|
MXGEFW_CMD_GET_SMALL_RX_OFFSET, &cmd);
|
|
ss->rx_small.lanai =
|
|
(volatile mcp_kreq_ether_recv_t *)(sc->sram + cmd.data0);
|
|
cmd.data0 = slice;
|
|
err |= mxge_send_cmd(sc, MXGEFW_CMD_GET_BIG_RX_OFFSET, &cmd);
|
|
ss->rx_big.lanai =
|
|
(volatile mcp_kreq_ether_recv_t *)(sc->sram + cmd.data0);
|
|
|
|
if (err != 0) {
|
|
device_printf(sc->dev,
|
|
"failed to get ring sizes or locations\n");
|
|
return EIO;
|
|
}
|
|
|
|
/* stock receive rings */
|
|
for (i = 0; i <= ss->rx_small.mask; i++) {
|
|
map = ss->rx_small.info[i].map;
|
|
err = mxge_get_buf_small(ss, map, i);
|
|
if (err) {
|
|
device_printf(sc->dev, "alloced %d/%d smalls\n",
|
|
i, ss->rx_small.mask + 1);
|
|
return ENOMEM;
|
|
}
|
|
}
|
|
for (i = 0; i <= ss->rx_big.mask; i++) {
|
|
ss->rx_big.shadow[i].addr_low = 0xffffffff;
|
|
ss->rx_big.shadow[i].addr_high = 0xffffffff;
|
|
}
|
|
ss->rx_big.nbufs = nbufs;
|
|
ss->rx_big.cl_size = cl_size;
|
|
ss->rx_big.mlen = ss->sc->ifp->if_mtu + ETHER_HDR_LEN +
|
|
ETHER_VLAN_ENCAP_LEN + MXGEFW_PAD;
|
|
for (i = 0; i <= ss->rx_big.mask; i += ss->rx_big.nbufs) {
|
|
map = ss->rx_big.info[i].map;
|
|
err = mxge_get_buf_big(ss, map, i);
|
|
if (err) {
|
|
device_printf(sc->dev, "alloced %d/%d bigs\n",
|
|
i, ss->rx_big.mask + 1);
|
|
return ENOMEM;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mxge_open(mxge_softc_t *sc)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
int err, big_bytes, nbufs, slice, cl_size, i;
|
|
bus_addr_t bus;
|
|
volatile uint8_t *itable;
|
|
struct mxge_slice_state *ss;
|
|
|
|
/* Copy the MAC address in case it was overridden */
|
|
bcopy(IF_LLADDR(sc->ifp), sc->mac_addr, ETHER_ADDR_LEN);
|
|
|
|
err = mxge_reset(sc, 1);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "failed to reset\n");
|
|
return EIO;
|
|
}
|
|
|
|
if (sc->num_slices > 1) {
|
|
/* setup the indirection table */
|
|
cmd.data0 = sc->num_slices;
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_SET_RSS_TABLE_SIZE,
|
|
&cmd);
|
|
|
|
err |= mxge_send_cmd(sc, MXGEFW_CMD_GET_RSS_TABLE_OFFSET,
|
|
&cmd);
|
|
if (err != 0) {
|
|
device_printf(sc->dev,
|
|
"failed to setup rss tables\n");
|
|
return err;
|
|
}
|
|
|
|
/* just enable an identity mapping */
|
|
itable = sc->sram + cmd.data0;
|
|
for (i = 0; i < sc->num_slices; i++)
|
|
itable[i] = (uint8_t)i;
|
|
|
|
cmd.data0 = 1;
|
|
cmd.data1 = mxge_rss_hash_type;
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_SET_RSS_ENABLE, &cmd);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "failed to enable slices\n");
|
|
return err;
|
|
}
|
|
}
|
|
|
|
|
|
mxge_choose_params(sc->ifp->if_mtu, &big_bytes, &cl_size, &nbufs);
|
|
|
|
cmd.data0 = nbufs;
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_ALWAYS_USE_N_BIG_BUFFERS,
|
|
&cmd);
|
|
/* error is only meaningful if we're trying to set
|
|
MXGEFW_CMD_ALWAYS_USE_N_BIG_BUFFERS > 1 */
|
|
if (err && nbufs > 1) {
|
|
device_printf(sc->dev,
|
|
"Failed to set alway-use-n to %d\n",
|
|
nbufs);
|
|
return EIO;
|
|
}
|
|
/* Give the firmware the mtu and the big and small buffer
|
|
sizes. The firmware wants the big buf size to be a power
|
|
of two. Luckily, FreeBSD's clusters are powers of two */
|
|
cmd.data0 = sc->ifp->if_mtu + ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_SET_MTU, &cmd);
|
|
cmd.data0 = MHLEN - MXGEFW_PAD;
|
|
err |= mxge_send_cmd(sc, MXGEFW_CMD_SET_SMALL_BUFFER_SIZE,
|
|
&cmd);
|
|
cmd.data0 = big_bytes;
|
|
err |= mxge_send_cmd(sc, MXGEFW_CMD_SET_BIG_BUFFER_SIZE, &cmd);
|
|
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "failed to setup params\n");
|
|
goto abort;
|
|
}
|
|
|
|
/* Now give him the pointer to the stats block */
|
|
for (slice = 0;
|
|
#ifdef IFNET_BUF_RING
|
|
slice < sc->num_slices;
|
|
#else
|
|
slice < 1;
|
|
#endif
|
|
slice++) {
|
|
ss = &sc->ss[slice];
|
|
cmd.data0 =
|
|
MXGE_LOWPART_TO_U32(ss->fw_stats_dma.bus_addr);
|
|
cmd.data1 =
|
|
MXGE_HIGHPART_TO_U32(ss->fw_stats_dma.bus_addr);
|
|
cmd.data2 = sizeof(struct mcp_irq_data);
|
|
cmd.data2 |= (slice << 16);
|
|
err |= mxge_send_cmd(sc, MXGEFW_CMD_SET_STATS_DMA_V2, &cmd);
|
|
}
|
|
|
|
if (err != 0) {
|
|
bus = sc->ss->fw_stats_dma.bus_addr;
|
|
bus += offsetof(struct mcp_irq_data, send_done_count);
|
|
cmd.data0 = MXGE_LOWPART_TO_U32(bus);
|
|
cmd.data1 = MXGE_HIGHPART_TO_U32(bus);
|
|
err = mxge_send_cmd(sc,
|
|
MXGEFW_CMD_SET_STATS_DMA_OBSOLETE,
|
|
&cmd);
|
|
/* Firmware cannot support multicast without STATS_DMA_V2 */
|
|
sc->fw_multicast_support = 0;
|
|
} else {
|
|
sc->fw_multicast_support = 1;
|
|
}
|
|
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "failed to setup params\n");
|
|
goto abort;
|
|
}
|
|
|
|
for (slice = 0; slice < sc->num_slices; slice++) {
|
|
err = mxge_slice_open(&sc->ss[slice], nbufs, cl_size);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "couldn't open slice %d\n",
|
|
slice);
|
|
goto abort;
|
|
}
|
|
}
|
|
|
|
/* Finally, start the firmware running */
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_ETHERNET_UP, &cmd);
|
|
if (err) {
|
|
device_printf(sc->dev, "Couldn't bring up link\n");
|
|
goto abort;
|
|
}
|
|
#ifdef IFNET_BUF_RING
|
|
for (slice = 0; slice < sc->num_slices; slice++) {
|
|
ss = &sc->ss[slice];
|
|
ss->if_drv_flags |= IFF_DRV_RUNNING;
|
|
ss->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
}
|
|
#endif
|
|
sc->ifp->if_drv_flags |= IFF_DRV_RUNNING;
|
|
sc->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
|
|
return 0;
|
|
|
|
|
|
abort:
|
|
mxge_free_mbufs(sc);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
mxge_close(mxge_softc_t *sc, int down)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
int err, old_down_cnt;
|
|
#ifdef IFNET_BUF_RING
|
|
struct mxge_slice_state *ss;
|
|
int slice;
|
|
#endif
|
|
|
|
#ifdef IFNET_BUF_RING
|
|
for (slice = 0; slice < sc->num_slices; slice++) {
|
|
ss = &sc->ss[slice];
|
|
ss->if_drv_flags &= ~IFF_DRV_RUNNING;
|
|
}
|
|
#endif
|
|
sc->ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
|
|
if (!down) {
|
|
old_down_cnt = sc->down_cnt;
|
|
wmb();
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_ETHERNET_DOWN, &cmd);
|
|
if (err) {
|
|
device_printf(sc->dev,
|
|
"Couldn't bring down link\n");
|
|
}
|
|
if (old_down_cnt == sc->down_cnt) {
|
|
/* wait for down irq */
|
|
DELAY(10 * sc->intr_coal_delay);
|
|
}
|
|
wmb();
|
|
if (old_down_cnt == sc->down_cnt) {
|
|
device_printf(sc->dev, "never got down irq\n");
|
|
}
|
|
}
|
|
mxge_free_mbufs(sc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
mxge_setup_cfg_space(mxge_softc_t *sc)
|
|
{
|
|
device_t dev = sc->dev;
|
|
int reg;
|
|
uint16_t lnk, pectl;
|
|
|
|
/* find the PCIe link width and set max read request to 4KB*/
|
|
if (pci_find_cap(dev, PCIY_EXPRESS, ®) == 0) {
|
|
lnk = pci_read_config(dev, reg + 0x12, 2);
|
|
sc->link_width = (lnk >> 4) & 0x3f;
|
|
|
|
if (sc->pectl == 0) {
|
|
pectl = pci_read_config(dev, reg + 0x8, 2);
|
|
pectl = (pectl & ~0x7000) | (5 << 12);
|
|
pci_write_config(dev, reg + 0x8, pectl, 2);
|
|
sc->pectl = pectl;
|
|
} else {
|
|
/* restore saved pectl after watchdog reset */
|
|
pci_write_config(dev, reg + 0x8, sc->pectl, 2);
|
|
}
|
|
}
|
|
|
|
/* Enable DMA and Memory space access */
|
|
pci_enable_busmaster(dev);
|
|
}
|
|
|
|
static uint32_t
|
|
mxge_read_reboot(mxge_softc_t *sc)
|
|
{
|
|
device_t dev = sc->dev;
|
|
uint32_t vs;
|
|
|
|
/* find the vendor specific offset */
|
|
if (pci_find_cap(dev, PCIY_VENDOR, &vs) != 0) {
|
|
device_printf(sc->dev,
|
|
"could not find vendor specific offset\n");
|
|
return (uint32_t)-1;
|
|
}
|
|
/* enable read32 mode */
|
|
pci_write_config(dev, vs + 0x10, 0x3, 1);
|
|
/* tell NIC which register to read */
|
|
pci_write_config(dev, vs + 0x18, 0xfffffff0, 4);
|
|
return (pci_read_config(dev, vs + 0x14, 4));
|
|
}
|
|
|
|
static void
|
|
mxge_watchdog_reset(mxge_softc_t *sc)
|
|
{
|
|
struct pci_devinfo *dinfo;
|
|
struct mxge_slice_state *ss;
|
|
int err, running, s, num_tx_slices = 1;
|
|
uint32_t reboot;
|
|
uint16_t cmd;
|
|
|
|
err = ENXIO;
|
|
|
|
device_printf(sc->dev, "Watchdog reset!\n");
|
|
|
|
/*
|
|
* check to see if the NIC rebooted. If it did, then all of
|
|
* PCI config space has been reset, and things like the
|
|
* busmaster bit will be zero. If this is the case, then we
|
|
* must restore PCI config space before the NIC can be used
|
|
* again
|
|
*/
|
|
cmd = pci_read_config(sc->dev, PCIR_COMMAND, 2);
|
|
if (cmd == 0xffff) {
|
|
/*
|
|
* maybe the watchdog caught the NIC rebooting; wait
|
|
* up to 100ms for it to finish. If it does not come
|
|
* back, then give up
|
|
*/
|
|
DELAY(1000*100);
|
|
cmd = pci_read_config(sc->dev, PCIR_COMMAND, 2);
|
|
if (cmd == 0xffff) {
|
|
device_printf(sc->dev, "NIC disappeared!\n");
|
|
}
|
|
}
|
|
if ((cmd & PCIM_CMD_BUSMASTEREN) == 0) {
|
|
/* print the reboot status */
|
|
reboot = mxge_read_reboot(sc);
|
|
device_printf(sc->dev, "NIC rebooted, status = 0x%x\n",
|
|
reboot);
|
|
running = sc->ifp->if_drv_flags & IFF_DRV_RUNNING;
|
|
if (running) {
|
|
|
|
/*
|
|
* quiesce NIC so that TX routines will not try to
|
|
* xmit after restoration of BAR
|
|
*/
|
|
|
|
/* Mark the link as down */
|
|
if (sc->link_state) {
|
|
sc->link_state = 0;
|
|
if_link_state_change(sc->ifp,
|
|
LINK_STATE_DOWN);
|
|
}
|
|
#ifdef IFNET_BUF_RING
|
|
num_tx_slices = sc->num_slices;
|
|
#endif
|
|
/* grab all TX locks to ensure no tx */
|
|
for (s = 0; s < num_tx_slices; s++) {
|
|
ss = &sc->ss[s];
|
|
mtx_lock(&ss->tx.mtx);
|
|
}
|
|
mxge_close(sc, 1);
|
|
}
|
|
/* restore PCI configuration space */
|
|
dinfo = device_get_ivars(sc->dev);
|
|
pci_cfg_restore(sc->dev, dinfo);
|
|
|
|
/* and redo any changes we made to our config space */
|
|
mxge_setup_cfg_space(sc);
|
|
|
|
/* reload f/w */
|
|
err = mxge_load_firmware(sc, 0);
|
|
if (err) {
|
|
device_printf(sc->dev,
|
|
"Unable to re-load f/w\n");
|
|
}
|
|
if (running) {
|
|
if (!err)
|
|
err = mxge_open(sc);
|
|
/* release all TX locks */
|
|
for (s = 0; s < num_tx_slices; s++) {
|
|
ss = &sc->ss[s];
|
|
#ifdef IFNET_BUF_RING
|
|
mxge_start_locked(ss);
|
|
#endif
|
|
mtx_unlock(&ss->tx.mtx);
|
|
}
|
|
}
|
|
sc->watchdog_resets++;
|
|
} else {
|
|
device_printf(sc->dev,
|
|
"NIC did not reboot, not resetting\n");
|
|
err = 0;
|
|
}
|
|
if (err) {
|
|
device_printf(sc->dev, "watchdog reset failed\n");
|
|
} else {
|
|
if (sc->dying == 2)
|
|
sc->dying = 0;
|
|
callout_reset(&sc->co_hdl, mxge_ticks, mxge_tick, sc);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mxge_watchdog_task(void *arg, int pending)
|
|
{
|
|
mxge_softc_t *sc = arg;
|
|
|
|
|
|
mtx_lock(&sc->driver_mtx);
|
|
mxge_watchdog_reset(sc);
|
|
mtx_unlock(&sc->driver_mtx);
|
|
}
|
|
|
|
static void
|
|
mxge_warn_stuck(mxge_softc_t *sc, mxge_tx_ring_t *tx, int slice)
|
|
{
|
|
tx = &sc->ss[slice].tx;
|
|
device_printf(sc->dev, "slice %d struck? ring state:\n", slice);
|
|
device_printf(sc->dev,
|
|
"tx.req=%d tx.done=%d, tx.queue_active=%d\n",
|
|
tx->req, tx->done, tx->queue_active);
|
|
device_printf(sc->dev, "tx.activate=%d tx.deactivate=%d\n",
|
|
tx->activate, tx->deactivate);
|
|
device_printf(sc->dev, "pkt_done=%d fw=%d\n",
|
|
tx->pkt_done,
|
|
be32toh(sc->ss->fw_stats->send_done_count));
|
|
}
|
|
|
|
static int
|
|
mxge_watchdog(mxge_softc_t *sc)
|
|
{
|
|
mxge_tx_ring_t *tx;
|
|
uint32_t rx_pause = be32toh(sc->ss->fw_stats->dropped_pause);
|
|
int i, err = 0;
|
|
|
|
/* see if we have outstanding transmits, which
|
|
have been pending for more than mxge_ticks */
|
|
for (i = 0;
|
|
#ifdef IFNET_BUF_RING
|
|
(i < sc->num_slices) && (err == 0);
|
|
#else
|
|
(i < 1) && (err == 0);
|
|
#endif
|
|
i++) {
|
|
tx = &sc->ss[i].tx;
|
|
if (tx->req != tx->done &&
|
|
tx->watchdog_req != tx->watchdog_done &&
|
|
tx->done == tx->watchdog_done) {
|
|
/* check for pause blocking before resetting */
|
|
if (tx->watchdog_rx_pause == rx_pause) {
|
|
mxge_warn_stuck(sc, tx, i);
|
|
taskqueue_enqueue(sc->tq, &sc->watchdog_task);
|
|
return (ENXIO);
|
|
}
|
|
else
|
|
device_printf(sc->dev, "Flow control blocking "
|
|
"xmits, check link partner\n");
|
|
}
|
|
|
|
tx->watchdog_req = tx->req;
|
|
tx->watchdog_done = tx->done;
|
|
tx->watchdog_rx_pause = rx_pause;
|
|
}
|
|
|
|
if (sc->need_media_probe)
|
|
mxge_media_probe(sc);
|
|
return (err);
|
|
}
|
|
|
|
static uint64_t
|
|
mxge_get_counter(struct ifnet *ifp, ift_counter cnt)
|
|
{
|
|
struct mxge_softc *sc;
|
|
uint64_t rv;
|
|
|
|
sc = if_getsoftc(ifp);
|
|
rv = 0;
|
|
|
|
switch (cnt) {
|
|
case IFCOUNTER_IPACKETS:
|
|
for (int s = 0; s < sc->num_slices; s++)
|
|
rv += sc->ss[s].ipackets;
|
|
return (rv);
|
|
case IFCOUNTER_OPACKETS:
|
|
for (int s = 0; s < sc->num_slices; s++)
|
|
rv += sc->ss[s].opackets;
|
|
return (rv);
|
|
case IFCOUNTER_OERRORS:
|
|
for (int s = 0; s < sc->num_slices; s++)
|
|
rv += sc->ss[s].oerrors;
|
|
return (rv);
|
|
#ifdef IFNET_BUF_RING
|
|
case IFCOUNTER_OBYTES:
|
|
for (int s = 0; s < sc->num_slices; s++)
|
|
rv += sc->ss[s].obytes;
|
|
return (rv);
|
|
case IFCOUNTER_OMCASTS:
|
|
for (int s = 0; s < sc->num_slices; s++)
|
|
rv += sc->ss[s].omcasts;
|
|
return (rv);
|
|
case IFCOUNTER_OQDROPS:
|
|
for (int s = 0; s < sc->num_slices; s++)
|
|
rv += sc->ss[s].tx.br->br_drops;
|
|
return (rv);
|
|
#endif
|
|
default:
|
|
return (if_get_counter_default(ifp, cnt));
|
|
}
|
|
}
|
|
|
|
static void
|
|
mxge_tick(void *arg)
|
|
{
|
|
mxge_softc_t *sc = arg;
|
|
u_long pkts = 0;
|
|
int err = 0;
|
|
int running, ticks;
|
|
uint16_t cmd;
|
|
|
|
ticks = mxge_ticks;
|
|
running = sc->ifp->if_drv_flags & IFF_DRV_RUNNING;
|
|
if (running) {
|
|
if (!sc->watchdog_countdown) {
|
|
err = mxge_watchdog(sc);
|
|
sc->watchdog_countdown = 4;
|
|
}
|
|
sc->watchdog_countdown--;
|
|
}
|
|
if (pkts == 0) {
|
|
/* ensure NIC did not suffer h/w fault while idle */
|
|
cmd = pci_read_config(sc->dev, PCIR_COMMAND, 2);
|
|
if ((cmd & PCIM_CMD_BUSMASTEREN) == 0) {
|
|
sc->dying = 2;
|
|
taskqueue_enqueue(sc->tq, &sc->watchdog_task);
|
|
err = ENXIO;
|
|
}
|
|
/* look less often if NIC is idle */
|
|
ticks *= 4;
|
|
}
|
|
|
|
if (err == 0)
|
|
callout_reset(&sc->co_hdl, ticks, mxge_tick, sc);
|
|
|
|
}
|
|
|
|
static int
|
|
mxge_media_change(struct ifnet *ifp)
|
|
{
|
|
return EINVAL;
|
|
}
|
|
|
|
static int
|
|
mxge_change_mtu(mxge_softc_t *sc, int mtu)
|
|
{
|
|
struct ifnet *ifp = sc->ifp;
|
|
int real_mtu, old_mtu;
|
|
int err = 0;
|
|
|
|
|
|
real_mtu = mtu + ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
|
|
if ((real_mtu > sc->max_mtu) || real_mtu < 60)
|
|
return EINVAL;
|
|
mtx_lock(&sc->driver_mtx);
|
|
old_mtu = ifp->if_mtu;
|
|
ifp->if_mtu = mtu;
|
|
if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
|
|
mxge_close(sc, 0);
|
|
err = mxge_open(sc);
|
|
if (err != 0) {
|
|
ifp->if_mtu = old_mtu;
|
|
mxge_close(sc, 0);
|
|
(void) mxge_open(sc);
|
|
}
|
|
}
|
|
mtx_unlock(&sc->driver_mtx);
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
mxge_media_status(struct ifnet *ifp, struct ifmediareq *ifmr)
|
|
{
|
|
mxge_softc_t *sc = ifp->if_softc;
|
|
|
|
|
|
if (sc == NULL)
|
|
return;
|
|
ifmr->ifm_status = IFM_AVALID;
|
|
ifmr->ifm_active = IFM_ETHER | IFM_FDX;
|
|
ifmr->ifm_status |= sc->link_state ? IFM_ACTIVE : 0;
|
|
ifmr->ifm_active |= sc->current_media;
|
|
}
|
|
|
|
static int
|
|
mxge_ioctl(struct ifnet *ifp, u_long command, caddr_t data)
|
|
{
|
|
mxge_softc_t *sc = ifp->if_softc;
|
|
struct ifreq *ifr = (struct ifreq *)data;
|
|
int err, mask;
|
|
|
|
err = 0;
|
|
switch (command) {
|
|
case SIOCSIFMTU:
|
|
err = mxge_change_mtu(sc, ifr->ifr_mtu);
|
|
break;
|
|
|
|
case SIOCSIFFLAGS:
|
|
mtx_lock(&sc->driver_mtx);
|
|
if (sc->dying) {
|
|
mtx_unlock(&sc->driver_mtx);
|
|
return EINVAL;
|
|
}
|
|
if (ifp->if_flags & IFF_UP) {
|
|
if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
|
|
err = mxge_open(sc);
|
|
} else {
|
|
/* take care of promis can allmulti
|
|
flag chages */
|
|
mxge_change_promisc(sc,
|
|
ifp->if_flags & IFF_PROMISC);
|
|
mxge_set_multicast_list(sc);
|
|
}
|
|
} else {
|
|
if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
|
|
mxge_close(sc, 0);
|
|
}
|
|
}
|
|
mtx_unlock(&sc->driver_mtx);
|
|
break;
|
|
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI:
|
|
mtx_lock(&sc->driver_mtx);
|
|
mxge_set_multicast_list(sc);
|
|
mtx_unlock(&sc->driver_mtx);
|
|
break;
|
|
|
|
case SIOCSIFCAP:
|
|
mtx_lock(&sc->driver_mtx);
|
|
mask = ifr->ifr_reqcap ^ ifp->if_capenable;
|
|
if (mask & IFCAP_TXCSUM) {
|
|
if (IFCAP_TXCSUM & ifp->if_capenable) {
|
|
ifp->if_capenable &= ~(IFCAP_TXCSUM|IFCAP_TSO4);
|
|
ifp->if_hwassist &= ~(CSUM_TCP | CSUM_UDP);
|
|
} else {
|
|
ifp->if_capenable |= IFCAP_TXCSUM;
|
|
ifp->if_hwassist |= (CSUM_TCP | CSUM_UDP);
|
|
}
|
|
} else if (mask & IFCAP_RXCSUM) {
|
|
if (IFCAP_RXCSUM & ifp->if_capenable) {
|
|
ifp->if_capenable &= ~IFCAP_RXCSUM;
|
|
} else {
|
|
ifp->if_capenable |= IFCAP_RXCSUM;
|
|
}
|
|
}
|
|
if (mask & IFCAP_TSO4) {
|
|
if (IFCAP_TSO4 & ifp->if_capenable) {
|
|
ifp->if_capenable &= ~IFCAP_TSO4;
|
|
} else if (IFCAP_TXCSUM & ifp->if_capenable) {
|
|
ifp->if_capenable |= IFCAP_TSO4;
|
|
ifp->if_hwassist |= CSUM_TSO;
|
|
} else {
|
|
printf("mxge requires tx checksum offload"
|
|
" be enabled to use TSO\n");
|
|
err = EINVAL;
|
|
}
|
|
}
|
|
#if IFCAP_TSO6
|
|
if (mask & IFCAP_TXCSUM_IPV6) {
|
|
if (IFCAP_TXCSUM_IPV6 & ifp->if_capenable) {
|
|
ifp->if_capenable &= ~(IFCAP_TXCSUM_IPV6
|
|
| IFCAP_TSO6);
|
|
ifp->if_hwassist &= ~(CSUM_TCP_IPV6
|
|
| CSUM_UDP);
|
|
} else {
|
|
ifp->if_capenable |= IFCAP_TXCSUM_IPV6;
|
|
ifp->if_hwassist |= (CSUM_TCP_IPV6
|
|
| CSUM_UDP_IPV6);
|
|
}
|
|
} else if (mask & IFCAP_RXCSUM_IPV6) {
|
|
if (IFCAP_RXCSUM_IPV6 & ifp->if_capenable) {
|
|
ifp->if_capenable &= ~IFCAP_RXCSUM_IPV6;
|
|
} else {
|
|
ifp->if_capenable |= IFCAP_RXCSUM_IPV6;
|
|
}
|
|
}
|
|
if (mask & IFCAP_TSO6) {
|
|
if (IFCAP_TSO6 & ifp->if_capenable) {
|
|
ifp->if_capenable &= ~IFCAP_TSO6;
|
|
} else if (IFCAP_TXCSUM_IPV6 & ifp->if_capenable) {
|
|
ifp->if_capenable |= IFCAP_TSO6;
|
|
ifp->if_hwassist |= CSUM_TSO;
|
|
} else {
|
|
printf("mxge requires tx checksum offload"
|
|
" be enabled to use TSO\n");
|
|
err = EINVAL;
|
|
}
|
|
}
|
|
#endif /*IFCAP_TSO6 */
|
|
|
|
if (mask & IFCAP_LRO)
|
|
ifp->if_capenable ^= IFCAP_LRO;
|
|
if (mask & IFCAP_VLAN_HWTAGGING)
|
|
ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING;
|
|
if (mask & IFCAP_VLAN_HWTSO)
|
|
ifp->if_capenable ^= IFCAP_VLAN_HWTSO;
|
|
|
|
if (!(ifp->if_capabilities & IFCAP_VLAN_HWTSO) ||
|
|
!(ifp->if_capenable & IFCAP_VLAN_HWTAGGING))
|
|
ifp->if_capenable &= ~IFCAP_VLAN_HWTSO;
|
|
|
|
mtx_unlock(&sc->driver_mtx);
|
|
VLAN_CAPABILITIES(ifp);
|
|
|
|
break;
|
|
|
|
case SIOCGIFMEDIA:
|
|
mtx_lock(&sc->driver_mtx);
|
|
mxge_media_probe(sc);
|
|
mtx_unlock(&sc->driver_mtx);
|
|
err = ifmedia_ioctl(ifp, (struct ifreq *)data,
|
|
&sc->media, command);
|
|
break;
|
|
|
|
default:
|
|
err = ether_ioctl(ifp, command, data);
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
mxge_fetch_tunables(mxge_softc_t *sc)
|
|
{
|
|
|
|
TUNABLE_INT_FETCH("hw.mxge.max_slices", &mxge_max_slices);
|
|
TUNABLE_INT_FETCH("hw.mxge.flow_control_enabled",
|
|
&mxge_flow_control);
|
|
TUNABLE_INT_FETCH("hw.mxge.intr_coal_delay",
|
|
&mxge_intr_coal_delay);
|
|
TUNABLE_INT_FETCH("hw.mxge.nvidia_ecrc_enable",
|
|
&mxge_nvidia_ecrc_enable);
|
|
TUNABLE_INT_FETCH("hw.mxge.force_firmware",
|
|
&mxge_force_firmware);
|
|
TUNABLE_INT_FETCH("hw.mxge.deassert_wait",
|
|
&mxge_deassert_wait);
|
|
TUNABLE_INT_FETCH("hw.mxge.verbose",
|
|
&mxge_verbose);
|
|
TUNABLE_INT_FETCH("hw.mxge.ticks", &mxge_ticks);
|
|
TUNABLE_INT_FETCH("hw.mxge.always_promisc", &mxge_always_promisc);
|
|
TUNABLE_INT_FETCH("hw.mxge.rss_hash_type", &mxge_rss_hash_type);
|
|
TUNABLE_INT_FETCH("hw.mxge.rss_hashtype", &mxge_rss_hash_type);
|
|
TUNABLE_INT_FETCH("hw.mxge.initial_mtu", &mxge_initial_mtu);
|
|
TUNABLE_INT_FETCH("hw.mxge.throttle", &mxge_throttle);
|
|
|
|
if (bootverbose)
|
|
mxge_verbose = 1;
|
|
if (mxge_intr_coal_delay < 0 || mxge_intr_coal_delay > 10*1000)
|
|
mxge_intr_coal_delay = 30;
|
|
if (mxge_ticks == 0)
|
|
mxge_ticks = hz / 2;
|
|
sc->pause = mxge_flow_control;
|
|
if (mxge_rss_hash_type < MXGEFW_RSS_HASH_TYPE_IPV4
|
|
|| mxge_rss_hash_type > MXGEFW_RSS_HASH_TYPE_MAX) {
|
|
mxge_rss_hash_type = MXGEFW_RSS_HASH_TYPE_SRC_DST_PORT;
|
|
}
|
|
if (mxge_initial_mtu > ETHERMTU_JUMBO ||
|
|
mxge_initial_mtu < ETHER_MIN_LEN)
|
|
mxge_initial_mtu = ETHERMTU_JUMBO;
|
|
|
|
if (mxge_throttle && mxge_throttle > MXGE_MAX_THROTTLE)
|
|
mxge_throttle = MXGE_MAX_THROTTLE;
|
|
if (mxge_throttle && mxge_throttle < MXGE_MIN_THROTTLE)
|
|
mxge_throttle = MXGE_MIN_THROTTLE;
|
|
sc->throttle = mxge_throttle;
|
|
}
|
|
|
|
|
|
static void
|
|
mxge_free_slices(mxge_softc_t *sc)
|
|
{
|
|
struct mxge_slice_state *ss;
|
|
int i;
|
|
|
|
|
|
if (sc->ss == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < sc->num_slices; i++) {
|
|
ss = &sc->ss[i];
|
|
if (ss->fw_stats != NULL) {
|
|
mxge_dma_free(&ss->fw_stats_dma);
|
|
ss->fw_stats = NULL;
|
|
#ifdef IFNET_BUF_RING
|
|
if (ss->tx.br != NULL) {
|
|
drbr_free(ss->tx.br, M_DEVBUF);
|
|
ss->tx.br = NULL;
|
|
}
|
|
#endif
|
|
mtx_destroy(&ss->tx.mtx);
|
|
}
|
|
if (ss->rx_done.entry != NULL) {
|
|
mxge_dma_free(&ss->rx_done.dma);
|
|
ss->rx_done.entry = NULL;
|
|
}
|
|
}
|
|
free(sc->ss, M_DEVBUF);
|
|
sc->ss = NULL;
|
|
}
|
|
|
|
static int
|
|
mxge_alloc_slices(mxge_softc_t *sc)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
struct mxge_slice_state *ss;
|
|
size_t bytes;
|
|
int err, i, max_intr_slots;
|
|
|
|
err = mxge_send_cmd(sc, MXGEFW_CMD_GET_RX_RING_SIZE, &cmd);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Cannot determine rx ring size\n");
|
|
return err;
|
|
}
|
|
sc->rx_ring_size = cmd.data0;
|
|
max_intr_slots = 2 * (sc->rx_ring_size / sizeof (mcp_dma_addr_t));
|
|
|
|
bytes = sizeof (*sc->ss) * sc->num_slices;
|
|
sc->ss = malloc(bytes, M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
if (sc->ss == NULL)
|
|
return (ENOMEM);
|
|
for (i = 0; i < sc->num_slices; i++) {
|
|
ss = &sc->ss[i];
|
|
|
|
ss->sc = sc;
|
|
|
|
/* allocate per-slice rx interrupt queues */
|
|
|
|
bytes = max_intr_slots * sizeof (*ss->rx_done.entry);
|
|
err = mxge_dma_alloc(sc, &ss->rx_done.dma, bytes, 4096);
|
|
if (err != 0)
|
|
goto abort;
|
|
ss->rx_done.entry = ss->rx_done.dma.addr;
|
|
bzero(ss->rx_done.entry, bytes);
|
|
|
|
/*
|
|
* allocate the per-slice firmware stats; stats
|
|
* (including tx) are used used only on the first
|
|
* slice for now
|
|
*/
|
|
#ifndef IFNET_BUF_RING
|
|
if (i > 0)
|
|
continue;
|
|
#endif
|
|
|
|
bytes = sizeof (*ss->fw_stats);
|
|
err = mxge_dma_alloc(sc, &ss->fw_stats_dma,
|
|
sizeof (*ss->fw_stats), 64);
|
|
if (err != 0)
|
|
goto abort;
|
|
ss->fw_stats = (mcp_irq_data_t *)ss->fw_stats_dma.addr;
|
|
snprintf(ss->tx.mtx_name, sizeof(ss->tx.mtx_name),
|
|
"%s:tx(%d)", device_get_nameunit(sc->dev), i);
|
|
mtx_init(&ss->tx.mtx, ss->tx.mtx_name, NULL, MTX_DEF);
|
|
#ifdef IFNET_BUF_RING
|
|
ss->tx.br = buf_ring_alloc(2048, M_DEVBUF, M_WAITOK,
|
|
&ss->tx.mtx);
|
|
#endif
|
|
}
|
|
|
|
return (0);
|
|
|
|
abort:
|
|
mxge_free_slices(sc);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
static void
|
|
mxge_slice_probe(mxge_softc_t *sc)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
char *old_fw;
|
|
int msix_cnt, status, max_intr_slots;
|
|
|
|
sc->num_slices = 1;
|
|
/*
|
|
* don't enable multiple slices if they are not enabled,
|
|
* or if this is not an SMP system
|
|
*/
|
|
|
|
if (mxge_max_slices == 0 || mxge_max_slices == 1 || mp_ncpus < 2)
|
|
return;
|
|
|
|
/* see how many MSI-X interrupts are available */
|
|
msix_cnt = pci_msix_count(sc->dev);
|
|
if (msix_cnt < 2)
|
|
return;
|
|
|
|
/* now load the slice aware firmware see what it supports */
|
|
old_fw = sc->fw_name;
|
|
if (old_fw == mxge_fw_aligned)
|
|
sc->fw_name = mxge_fw_rss_aligned;
|
|
else
|
|
sc->fw_name = mxge_fw_rss_unaligned;
|
|
status = mxge_load_firmware(sc, 0);
|
|
if (status != 0) {
|
|
device_printf(sc->dev, "Falling back to a single slice\n");
|
|
return;
|
|
}
|
|
|
|
/* try to send a reset command to the card to see if it
|
|
is alive */
|
|
memset(&cmd, 0, sizeof (cmd));
|
|
status = mxge_send_cmd(sc, MXGEFW_CMD_RESET, &cmd);
|
|
if (status != 0) {
|
|
device_printf(sc->dev, "failed reset\n");
|
|
goto abort_with_fw;
|
|
}
|
|
|
|
/* get rx ring size */
|
|
status = mxge_send_cmd(sc, MXGEFW_CMD_GET_RX_RING_SIZE, &cmd);
|
|
if (status != 0) {
|
|
device_printf(sc->dev, "Cannot determine rx ring size\n");
|
|
goto abort_with_fw;
|
|
}
|
|
max_intr_slots = 2 * (cmd.data0 / sizeof (mcp_dma_addr_t));
|
|
|
|
/* tell it the size of the interrupt queues */
|
|
cmd.data0 = max_intr_slots * sizeof (struct mcp_slot);
|
|
status = mxge_send_cmd(sc, MXGEFW_CMD_SET_INTRQ_SIZE, &cmd);
|
|
if (status != 0) {
|
|
device_printf(sc->dev, "failed MXGEFW_CMD_SET_INTRQ_SIZE\n");
|
|
goto abort_with_fw;
|
|
}
|
|
|
|
/* ask the maximum number of slices it supports */
|
|
status = mxge_send_cmd(sc, MXGEFW_CMD_GET_MAX_RSS_QUEUES, &cmd);
|
|
if (status != 0) {
|
|
device_printf(sc->dev,
|
|
"failed MXGEFW_CMD_GET_MAX_RSS_QUEUES\n");
|
|
goto abort_with_fw;
|
|
}
|
|
sc->num_slices = cmd.data0;
|
|
if (sc->num_slices > msix_cnt)
|
|
sc->num_slices = msix_cnt;
|
|
|
|
if (mxge_max_slices == -1) {
|
|
/* cap to number of CPUs in system */
|
|
if (sc->num_slices > mp_ncpus)
|
|
sc->num_slices = mp_ncpus;
|
|
} else {
|
|
if (sc->num_slices > mxge_max_slices)
|
|
sc->num_slices = mxge_max_slices;
|
|
}
|
|
/* make sure it is a power of two */
|
|
while (sc->num_slices & (sc->num_slices - 1))
|
|
sc->num_slices--;
|
|
|
|
if (mxge_verbose)
|
|
device_printf(sc->dev, "using %d slices\n",
|
|
sc->num_slices);
|
|
|
|
return;
|
|
|
|
abort_with_fw:
|
|
sc->fw_name = old_fw;
|
|
(void) mxge_load_firmware(sc, 0);
|
|
}
|
|
|
|
static int
|
|
mxge_add_msix_irqs(mxge_softc_t *sc)
|
|
{
|
|
size_t bytes;
|
|
int count, err, i, rid;
|
|
|
|
rid = PCIR_BAR(2);
|
|
sc->msix_table_res = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY,
|
|
&rid, RF_ACTIVE);
|
|
|
|
if (sc->msix_table_res == NULL) {
|
|
device_printf(sc->dev, "couldn't alloc MSIX table res\n");
|
|
return ENXIO;
|
|
}
|
|
|
|
count = sc->num_slices;
|
|
err = pci_alloc_msix(sc->dev, &count);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "pci_alloc_msix: failed, wanted %d"
|
|
"err = %d \n", sc->num_slices, err);
|
|
goto abort_with_msix_table;
|
|
}
|
|
if (count < sc->num_slices) {
|
|
device_printf(sc->dev, "pci_alloc_msix: need %d, got %d\n",
|
|
count, sc->num_slices);
|
|
device_printf(sc->dev,
|
|
"Try setting hw.mxge.max_slices to %d\n",
|
|
count);
|
|
err = ENOSPC;
|
|
goto abort_with_msix;
|
|
}
|
|
bytes = sizeof (*sc->msix_irq_res) * sc->num_slices;
|
|
sc->msix_irq_res = malloc(bytes, M_DEVBUF, M_NOWAIT|M_ZERO);
|
|
if (sc->msix_irq_res == NULL) {
|
|
err = ENOMEM;
|
|
goto abort_with_msix;
|
|
}
|
|
|
|
for (i = 0; i < sc->num_slices; i++) {
|
|
rid = i + 1;
|
|
sc->msix_irq_res[i] = bus_alloc_resource_any(sc->dev,
|
|
SYS_RES_IRQ,
|
|
&rid, RF_ACTIVE);
|
|
if (sc->msix_irq_res[i] == NULL) {
|
|
device_printf(sc->dev, "couldn't allocate IRQ res"
|
|
" for message %d\n", i);
|
|
err = ENXIO;
|
|
goto abort_with_res;
|
|
}
|
|
}
|
|
|
|
bytes = sizeof (*sc->msix_ih) * sc->num_slices;
|
|
sc->msix_ih = malloc(bytes, M_DEVBUF, M_NOWAIT|M_ZERO);
|
|
|
|
for (i = 0; i < sc->num_slices; i++) {
|
|
err = bus_setup_intr(sc->dev, sc->msix_irq_res[i],
|
|
INTR_TYPE_NET | INTR_MPSAFE,
|
|
#if __FreeBSD_version > 700030
|
|
NULL,
|
|
#endif
|
|
mxge_intr, &sc->ss[i], &sc->msix_ih[i]);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "couldn't setup intr for "
|
|
"message %d\n", i);
|
|
goto abort_with_intr;
|
|
}
|
|
bus_describe_intr(sc->dev, sc->msix_irq_res[i],
|
|
sc->msix_ih[i], "s%d", i);
|
|
}
|
|
|
|
if (mxge_verbose) {
|
|
device_printf(sc->dev, "using %d msix IRQs:",
|
|
sc->num_slices);
|
|
for (i = 0; i < sc->num_slices; i++)
|
|
printf(" %jd", rman_get_start(sc->msix_irq_res[i]));
|
|
printf("\n");
|
|
}
|
|
return (0);
|
|
|
|
abort_with_intr:
|
|
for (i = 0; i < sc->num_slices; i++) {
|
|
if (sc->msix_ih[i] != NULL) {
|
|
bus_teardown_intr(sc->dev, sc->msix_irq_res[i],
|
|
sc->msix_ih[i]);
|
|
sc->msix_ih[i] = NULL;
|
|
}
|
|
}
|
|
free(sc->msix_ih, M_DEVBUF);
|
|
|
|
|
|
abort_with_res:
|
|
for (i = 0; i < sc->num_slices; i++) {
|
|
rid = i + 1;
|
|
if (sc->msix_irq_res[i] != NULL)
|
|
bus_release_resource(sc->dev, SYS_RES_IRQ, rid,
|
|
sc->msix_irq_res[i]);
|
|
sc->msix_irq_res[i] = NULL;
|
|
}
|
|
free(sc->msix_irq_res, M_DEVBUF);
|
|
|
|
|
|
abort_with_msix:
|
|
pci_release_msi(sc->dev);
|
|
|
|
abort_with_msix_table:
|
|
bus_release_resource(sc->dev, SYS_RES_MEMORY, PCIR_BAR(2),
|
|
sc->msix_table_res);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
mxge_add_single_irq(mxge_softc_t *sc)
|
|
{
|
|
int count, err, rid;
|
|
|
|
count = pci_msi_count(sc->dev);
|
|
if (count == 1 && pci_alloc_msi(sc->dev, &count) == 0) {
|
|
rid = 1;
|
|
} else {
|
|
rid = 0;
|
|
sc->legacy_irq = 1;
|
|
}
|
|
sc->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &rid,
|
|
RF_SHAREABLE | RF_ACTIVE);
|
|
if (sc->irq_res == NULL) {
|
|
device_printf(sc->dev, "could not alloc interrupt\n");
|
|
return ENXIO;
|
|
}
|
|
if (mxge_verbose)
|
|
device_printf(sc->dev, "using %s irq %jd\n",
|
|
sc->legacy_irq ? "INTx" : "MSI",
|
|
rman_get_start(sc->irq_res));
|
|
err = bus_setup_intr(sc->dev, sc->irq_res,
|
|
INTR_TYPE_NET | INTR_MPSAFE,
|
|
#if __FreeBSD_version > 700030
|
|
NULL,
|
|
#endif
|
|
mxge_intr, &sc->ss[0], &sc->ih);
|
|
if (err != 0) {
|
|
bus_release_resource(sc->dev, SYS_RES_IRQ,
|
|
sc->legacy_irq ? 0 : 1, sc->irq_res);
|
|
if (!sc->legacy_irq)
|
|
pci_release_msi(sc->dev);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
mxge_rem_msix_irqs(mxge_softc_t *sc)
|
|
{
|
|
int i, rid;
|
|
|
|
for (i = 0; i < sc->num_slices; i++) {
|
|
if (sc->msix_ih[i] != NULL) {
|
|
bus_teardown_intr(sc->dev, sc->msix_irq_res[i],
|
|
sc->msix_ih[i]);
|
|
sc->msix_ih[i] = NULL;
|
|
}
|
|
}
|
|
free(sc->msix_ih, M_DEVBUF);
|
|
|
|
for (i = 0; i < sc->num_slices; i++) {
|
|
rid = i + 1;
|
|
if (sc->msix_irq_res[i] != NULL)
|
|
bus_release_resource(sc->dev, SYS_RES_IRQ, rid,
|
|
sc->msix_irq_res[i]);
|
|
sc->msix_irq_res[i] = NULL;
|
|
}
|
|
free(sc->msix_irq_res, M_DEVBUF);
|
|
|
|
bus_release_resource(sc->dev, SYS_RES_MEMORY, PCIR_BAR(2),
|
|
sc->msix_table_res);
|
|
|
|
pci_release_msi(sc->dev);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
mxge_rem_single_irq(mxge_softc_t *sc)
|
|
{
|
|
bus_teardown_intr(sc->dev, sc->irq_res, sc->ih);
|
|
bus_release_resource(sc->dev, SYS_RES_IRQ,
|
|
sc->legacy_irq ? 0 : 1, sc->irq_res);
|
|
if (!sc->legacy_irq)
|
|
pci_release_msi(sc->dev);
|
|
}
|
|
|
|
static void
|
|
mxge_rem_irq(mxge_softc_t *sc)
|
|
{
|
|
if (sc->num_slices > 1)
|
|
mxge_rem_msix_irqs(sc);
|
|
else
|
|
mxge_rem_single_irq(sc);
|
|
}
|
|
|
|
static int
|
|
mxge_add_irq(mxge_softc_t *sc)
|
|
{
|
|
int err;
|
|
|
|
if (sc->num_slices > 1)
|
|
err = mxge_add_msix_irqs(sc);
|
|
else
|
|
err = mxge_add_single_irq(sc);
|
|
|
|
if (0 && err == 0 && sc->num_slices > 1) {
|
|
mxge_rem_msix_irqs(sc);
|
|
err = mxge_add_msix_irqs(sc);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
static int
|
|
mxge_attach(device_t dev)
|
|
{
|
|
mxge_cmd_t cmd;
|
|
mxge_softc_t *sc = device_get_softc(dev);
|
|
struct ifnet *ifp;
|
|
int err, rid;
|
|
|
|
sc->dev = dev;
|
|
mxge_fetch_tunables(sc);
|
|
|
|
TASK_INIT(&sc->watchdog_task, 1, mxge_watchdog_task, sc);
|
|
sc->tq = taskqueue_create("mxge_taskq", M_WAITOK,
|
|
taskqueue_thread_enqueue, &sc->tq);
|
|
if (sc->tq == NULL) {
|
|
err = ENOMEM;
|
|
goto abort_with_nothing;
|
|
}
|
|
|
|
err = bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */
|
|
1, /* alignment */
|
|
0, /* boundary */
|
|
BUS_SPACE_MAXADDR, /* low */
|
|
BUS_SPACE_MAXADDR, /* high */
|
|
NULL, NULL, /* filter */
|
|
65536 + 256, /* maxsize */
|
|
MXGE_MAX_SEND_DESC, /* num segs */
|
|
65536, /* maxsegsize */
|
|
0, /* flags */
|
|
NULL, NULL, /* lock */
|
|
&sc->parent_dmat); /* tag */
|
|
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "Err %d allocating parent dmat\n",
|
|
err);
|
|
goto abort_with_tq;
|
|
}
|
|
|
|
ifp = sc->ifp = if_alloc(IFT_ETHER);
|
|
if (ifp == NULL) {
|
|
device_printf(dev, "can not if_alloc()\n");
|
|
err = ENOSPC;
|
|
goto abort_with_parent_dmat;
|
|
}
|
|
if_initname(ifp, device_get_name(dev), device_get_unit(dev));
|
|
|
|
snprintf(sc->cmd_mtx_name, sizeof(sc->cmd_mtx_name), "%s:cmd",
|
|
device_get_nameunit(dev));
|
|
mtx_init(&sc->cmd_mtx, sc->cmd_mtx_name, NULL, MTX_DEF);
|
|
snprintf(sc->driver_mtx_name, sizeof(sc->driver_mtx_name),
|
|
"%s:drv", device_get_nameunit(dev));
|
|
mtx_init(&sc->driver_mtx, sc->driver_mtx_name,
|
|
MTX_NETWORK_LOCK, MTX_DEF);
|
|
|
|
callout_init_mtx(&sc->co_hdl, &sc->driver_mtx, 0);
|
|
|
|
mxge_setup_cfg_space(sc);
|
|
|
|
/* Map the board into the kernel */
|
|
rid = PCIR_BARS;
|
|
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
|
|
RF_ACTIVE);
|
|
if (sc->mem_res == NULL) {
|
|
device_printf(dev, "could not map memory\n");
|
|
err = ENXIO;
|
|
goto abort_with_lock;
|
|
}
|
|
sc->sram = rman_get_virtual(sc->mem_res);
|
|
sc->sram_size = 2*1024*1024 - (2*(48*1024)+(32*1024)) - 0x100;
|
|
if (sc->sram_size > rman_get_size(sc->mem_res)) {
|
|
device_printf(dev, "impossible memory region size %jd\n",
|
|
rman_get_size(sc->mem_res));
|
|
err = ENXIO;
|
|
goto abort_with_mem_res;
|
|
}
|
|
|
|
/* make NULL terminated copy of the EEPROM strings section of
|
|
lanai SRAM */
|
|
bzero(sc->eeprom_strings, MXGE_EEPROM_STRINGS_SIZE);
|
|
bus_space_read_region_1(rman_get_bustag(sc->mem_res),
|
|
rman_get_bushandle(sc->mem_res),
|
|
sc->sram_size - MXGE_EEPROM_STRINGS_SIZE,
|
|
sc->eeprom_strings,
|
|
MXGE_EEPROM_STRINGS_SIZE - 2);
|
|
err = mxge_parse_strings(sc);
|
|
if (err != 0)
|
|
goto abort_with_mem_res;
|
|
|
|
/* Enable write combining for efficient use of PCIe bus */
|
|
mxge_enable_wc(sc);
|
|
|
|
/* Allocate the out of band dma memory */
|
|
err = mxge_dma_alloc(sc, &sc->cmd_dma,
|
|
sizeof (mxge_cmd_t), 64);
|
|
if (err != 0)
|
|
goto abort_with_mem_res;
|
|
sc->cmd = (mcp_cmd_response_t *) sc->cmd_dma.addr;
|
|
err = mxge_dma_alloc(sc, &sc->zeropad_dma, 64, 64);
|
|
if (err != 0)
|
|
goto abort_with_cmd_dma;
|
|
|
|
err = mxge_dma_alloc(sc, &sc->dmabench_dma, 4096, 4096);
|
|
if (err != 0)
|
|
goto abort_with_zeropad_dma;
|
|
|
|
/* select & load the firmware */
|
|
err = mxge_select_firmware(sc);
|
|
if (err != 0)
|
|
goto abort_with_dmabench;
|
|
sc->intr_coal_delay = mxge_intr_coal_delay;
|
|
|
|
mxge_slice_probe(sc);
|
|
err = mxge_alloc_slices(sc);
|
|
if (err != 0)
|
|
goto abort_with_dmabench;
|
|
|
|
err = mxge_reset(sc, 0);
|
|
if (err != 0)
|
|
goto abort_with_slices;
|
|
|
|
err = mxge_alloc_rings(sc);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "failed to allocate rings\n");
|
|
goto abort_with_slices;
|
|
}
|
|
|
|
err = mxge_add_irq(sc);
|
|
if (err != 0) {
|
|
device_printf(sc->dev, "failed to add irq\n");
|
|
goto abort_with_rings;
|
|
}
|
|
|
|
ifp->if_baudrate = IF_Gbps(10);
|
|
ifp->if_capabilities = IFCAP_RXCSUM | IFCAP_TXCSUM | IFCAP_TSO4 |
|
|
IFCAP_VLAN_MTU | IFCAP_LINKSTATE | IFCAP_TXCSUM_IPV6 |
|
|
IFCAP_RXCSUM_IPV6;
|
|
#if defined(INET) || defined(INET6)
|
|
ifp->if_capabilities |= IFCAP_LRO;
|
|
#endif
|
|
|
|
#ifdef MXGE_NEW_VLAN_API
|
|
ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_HWCSUM;
|
|
|
|
/* Only FW 1.4.32 and newer can do TSO over vlans */
|
|
if (sc->fw_ver_major == 1 && sc->fw_ver_minor == 4 &&
|
|
sc->fw_ver_tiny >= 32)
|
|
ifp->if_capabilities |= IFCAP_VLAN_HWTSO;
|
|
#endif
|
|
sc->max_mtu = mxge_max_mtu(sc);
|
|
if (sc->max_mtu >= 9000)
|
|
ifp->if_capabilities |= IFCAP_JUMBO_MTU;
|
|
else
|
|
device_printf(dev, "MTU limited to %d. Install "
|
|
"latest firmware for 9000 byte jumbo support\n",
|
|
sc->max_mtu - ETHER_HDR_LEN);
|
|
ifp->if_hwassist = CSUM_TCP | CSUM_UDP | CSUM_TSO;
|
|
ifp->if_hwassist |= CSUM_TCP_IPV6 | CSUM_UDP_IPV6;
|
|
/* check to see if f/w supports TSO for IPv6 */
|
|
if (!mxge_send_cmd(sc, MXGEFW_CMD_GET_MAX_TSO6_HDR_SIZE, &cmd)) {
|
|
if (CSUM_TCP_IPV6)
|
|
ifp->if_capabilities |= IFCAP_TSO6;
|
|
sc->max_tso6_hlen = min(cmd.data0,
|
|
sizeof (sc->ss[0].scratch));
|
|
}
|
|
ifp->if_capenable = ifp->if_capabilities;
|
|
if (sc->lro_cnt == 0)
|
|
ifp->if_capenable &= ~IFCAP_LRO;
|
|
ifp->if_init = mxge_init;
|
|
ifp->if_softc = sc;
|
|
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
|
|
ifp->if_ioctl = mxge_ioctl;
|
|
ifp->if_start = mxge_start;
|
|
ifp->if_get_counter = mxge_get_counter;
|
|
/* Initialise the ifmedia structure */
|
|
ifmedia_init(&sc->media, 0, mxge_media_change,
|
|
mxge_media_status);
|
|
mxge_media_init(sc);
|
|
mxge_media_probe(sc);
|
|
sc->dying = 0;
|
|
ether_ifattach(ifp, sc->mac_addr);
|
|
/* ether_ifattach sets mtu to ETHERMTU */
|
|
if (mxge_initial_mtu != ETHERMTU)
|
|
mxge_change_mtu(sc, mxge_initial_mtu);
|
|
|
|
mxge_add_sysctls(sc);
|
|
#ifdef IFNET_BUF_RING
|
|
ifp->if_transmit = mxge_transmit;
|
|
ifp->if_qflush = mxge_qflush;
|
|
#endif
|
|
taskqueue_start_threads(&sc->tq, 1, PI_NET, "%s taskq",
|
|
device_get_nameunit(sc->dev));
|
|
callout_reset(&sc->co_hdl, mxge_ticks, mxge_tick, sc);
|
|
return 0;
|
|
|
|
abort_with_rings:
|
|
mxge_free_rings(sc);
|
|
abort_with_slices:
|
|
mxge_free_slices(sc);
|
|
abort_with_dmabench:
|
|
mxge_dma_free(&sc->dmabench_dma);
|
|
abort_with_zeropad_dma:
|
|
mxge_dma_free(&sc->zeropad_dma);
|
|
abort_with_cmd_dma:
|
|
mxge_dma_free(&sc->cmd_dma);
|
|
abort_with_mem_res:
|
|
bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BARS, sc->mem_res);
|
|
abort_with_lock:
|
|
pci_disable_busmaster(dev);
|
|
mtx_destroy(&sc->cmd_mtx);
|
|
mtx_destroy(&sc->driver_mtx);
|
|
if_free(ifp);
|
|
abort_with_parent_dmat:
|
|
bus_dma_tag_destroy(sc->parent_dmat);
|
|
abort_with_tq:
|
|
if (sc->tq != NULL) {
|
|
taskqueue_drain(sc->tq, &sc->watchdog_task);
|
|
taskqueue_free(sc->tq);
|
|
sc->tq = NULL;
|
|
}
|
|
abort_with_nothing:
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
mxge_detach(device_t dev)
|
|
{
|
|
mxge_softc_t *sc = device_get_softc(dev);
|
|
|
|
if (mxge_vlans_active(sc)) {
|
|
device_printf(sc->dev,
|
|
"Detach vlans before removing module\n");
|
|
return EBUSY;
|
|
}
|
|
mtx_lock(&sc->driver_mtx);
|
|
sc->dying = 1;
|
|
if (sc->ifp->if_drv_flags & IFF_DRV_RUNNING)
|
|
mxge_close(sc, 0);
|
|
mtx_unlock(&sc->driver_mtx);
|
|
ether_ifdetach(sc->ifp);
|
|
if (sc->tq != NULL) {
|
|
taskqueue_drain(sc->tq, &sc->watchdog_task);
|
|
taskqueue_free(sc->tq);
|
|
sc->tq = NULL;
|
|
}
|
|
callout_drain(&sc->co_hdl);
|
|
ifmedia_removeall(&sc->media);
|
|
mxge_dummy_rdma(sc, 0);
|
|
mxge_rem_sysctls(sc);
|
|
mxge_rem_irq(sc);
|
|
mxge_free_rings(sc);
|
|
mxge_free_slices(sc);
|
|
mxge_dma_free(&sc->dmabench_dma);
|
|
mxge_dma_free(&sc->zeropad_dma);
|
|
mxge_dma_free(&sc->cmd_dma);
|
|
bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BARS, sc->mem_res);
|
|
pci_disable_busmaster(dev);
|
|
mtx_destroy(&sc->cmd_mtx);
|
|
mtx_destroy(&sc->driver_mtx);
|
|
if_free(sc->ifp);
|
|
bus_dma_tag_destroy(sc->parent_dmat);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mxge_shutdown(device_t dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
This file uses Myri10GE driver indentation.
|
|
|
|
Local Variables:
|
|
c-file-style:"linux"
|
|
tab-width:8
|
|
End:
|
|
*/
|