Index: head/sys/dev/bce/if_bce.c =================================================================== --- head/sys/dev/bce/if_bce.c (revision 162374) +++ head/sys/dev/bce/if_bce.c (revision 162375) @@ -1,6825 +1,6822 @@ /*- * Copyright (c) 2006 Broadcom Corporation * David Christensen . 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Broadcom Corporation nor the name of its contributors * may be used to endorse or promote products derived from this software * without specific prior written consent. * * 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 __FBSDID("$FreeBSD$"); /* * The following controllers are supported by this driver: * BCM5706C A2, A3 * BCM5708C B1 * * The following controllers are not supported by this driver: * (These are not "Production" versions of the controller.) * * BCM5706C A0, A1 * BCM5706S A0, A1, A2, A3 * BCM5708C A0, B0 * BCM5708S A0, B0, B1 */ #include "opt_bce.h" #include #include /****************************************************************************/ /* BCE Driver Version */ /****************************************************************************/ char bce_driver_version[] = "v0.9.6"; /****************************************************************************/ /* BCE Debug Options */ /****************************************************************************/ #ifdef BCE_DEBUG u32 bce_debug = BCE_WARN; /* 0 = Never */ /* 1 = 1 in 2,147,483,648 */ /* 256 = 1 in 8,388,608 */ /* 2048 = 1 in 1,048,576 */ /* 65536 = 1 in 32,768 */ /* 1048576 = 1 in 2,048 */ /* 268435456 = 1 in 8 */ /* 536870912 = 1 in 4 */ /* 1073741824 = 1 in 2 */ /* Controls how often the l2_fhdr frame error check will fail. */ int bce_debug_l2fhdr_status_check = 0; /* Controls how often the unexpected attention check will fail. */ int bce_debug_unexpected_attention = 0; /* Controls how often to simulate an mbuf allocation failure. */ int bce_debug_mbuf_allocation_failure = 0; /* Controls how often to simulate a DMA mapping failure. */ int bce_debug_dma_map_addr_failure = 0; /* Controls how often to simulate a bootcode failure. */ int bce_debug_bootcode_running_failure = 0; #endif /****************************************************************************/ /* PCI Device ID Table */ /* */ /* Used by bce_probe() to identify the devices supported by this driver. */ /****************************************************************************/ #define BCE_DEVDESC_MAX 64 static struct bce_type bce_devs[] = { /* BCM5706C Controllers and OEM boards. */ { BRCM_VENDORID, BRCM_DEVICEID_BCM5706, HP_VENDORID, 0x3101, "HP NC370T Multifunction Gigabit Server Adapter" }, { BRCM_VENDORID, BRCM_DEVICEID_BCM5706, HP_VENDORID, 0x3106, "HP NC370i Multifunction Gigabit Server Adapter" }, { BRCM_VENDORID, BRCM_DEVICEID_BCM5706, PCI_ANY_ID, PCI_ANY_ID, "Broadcom NetXtreme II BCM5706 1000Base-T" }, /* BCM5706S controllers and OEM boards. */ { BRCM_VENDORID, BRCM_DEVICEID_BCM5706S, HP_VENDORID, 0x3102, "HP NC370F Multifunction Gigabit Server Adapter" }, { BRCM_VENDORID, BRCM_DEVICEID_BCM5706S, PCI_ANY_ID, PCI_ANY_ID, "Broadcom NetXtreme II BCM5706 1000Base-SX" }, /* BCM5708C controllers and OEM boards. */ { BRCM_VENDORID, BRCM_DEVICEID_BCM5708, PCI_ANY_ID, PCI_ANY_ID, "Broadcom NetXtreme II BCM5708 1000Base-T" }, /* BCM5708S controllers and OEM boards. */ { BRCM_VENDORID, BRCM_DEVICEID_BCM5708, PCI_ANY_ID, PCI_ANY_ID, "Broadcom NetXtreme II BCM5708 1000Base-T" }, { 0, 0, 0, 0, NULL } }; /****************************************************************************/ /* Supported Flash NVRAM device data. */ /****************************************************************************/ static struct flash_spec flash_table[] = { /* Slow EEPROM */ {0x00000000, 0x40830380, 0x009f0081, 0xa184a053, 0xaf000400, 1, SEEPROM_PAGE_BITS, SEEPROM_PAGE_SIZE, SEEPROM_BYTE_ADDR_MASK, SEEPROM_TOTAL_SIZE, "EEPROM - slow"}, /* Expansion entry 0001 */ {0x08000002, 0x4b808201, 0x00050081, 0x03840253, 0xaf020406, 0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, SAIFUN_FLASH_BYTE_ADDR_MASK, 0, "Entry 0001"}, /* Saifun SA25F010 (non-buffered flash) */ /* strap, cfg1, & write1 need updates */ {0x04000001, 0x47808201, 0x00050081, 0x03840253, 0xaf020406, 0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, SAIFUN_FLASH_BYTE_ADDR_MASK, SAIFUN_FLASH_BASE_TOTAL_SIZE*2, "Non-buffered flash (128kB)"}, /* Saifun SA25F020 (non-buffered flash) */ /* strap, cfg1, & write1 need updates */ {0x0c000003, 0x4f808201, 0x00050081, 0x03840253, 0xaf020406, 0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, SAIFUN_FLASH_BYTE_ADDR_MASK, SAIFUN_FLASH_BASE_TOTAL_SIZE*4, "Non-buffered flash (256kB)"}, /* Expansion entry 0100 */ {0x11000000, 0x53808201, 0x00050081, 0x03840253, 0xaf020406, 0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, SAIFUN_FLASH_BYTE_ADDR_MASK, 0, "Entry 0100"}, /* Entry 0101: ST M45PE10 (non-buffered flash, TetonII B0) */ {0x19000002, 0x5b808201, 0x000500db, 0x03840253, 0xaf020406, 0, ST_MICRO_FLASH_PAGE_BITS, ST_MICRO_FLASH_PAGE_SIZE, ST_MICRO_FLASH_BYTE_ADDR_MASK, ST_MICRO_FLASH_BASE_TOTAL_SIZE*2, "Entry 0101: ST M45PE10 (128kB non-bufferred)"}, /* Entry 0110: ST M45PE20 (non-buffered flash)*/ {0x15000001, 0x57808201, 0x000500db, 0x03840253, 0xaf020406, 0, ST_MICRO_FLASH_PAGE_BITS, ST_MICRO_FLASH_PAGE_SIZE, ST_MICRO_FLASH_BYTE_ADDR_MASK, ST_MICRO_FLASH_BASE_TOTAL_SIZE*4, "Entry 0110: ST M45PE20 (256kB non-bufferred)"}, /* Saifun SA25F005 (non-buffered flash) */ /* strap, cfg1, & write1 need updates */ {0x1d000003, 0x5f808201, 0x00050081, 0x03840253, 0xaf020406, 0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, SAIFUN_FLASH_BYTE_ADDR_MASK, SAIFUN_FLASH_BASE_TOTAL_SIZE, "Non-buffered flash (64kB)"}, /* Fast EEPROM */ {0x22000000, 0x62808380, 0x009f0081, 0xa184a053, 0xaf000400, 1, SEEPROM_PAGE_BITS, SEEPROM_PAGE_SIZE, SEEPROM_BYTE_ADDR_MASK, SEEPROM_TOTAL_SIZE, "EEPROM - fast"}, /* Expansion entry 1001 */ {0x2a000002, 0x6b808201, 0x00050081, 0x03840253, 0xaf020406, 0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, SAIFUN_FLASH_BYTE_ADDR_MASK, 0, "Entry 1001"}, /* Expansion entry 1010 */ {0x26000001, 0x67808201, 0x00050081, 0x03840253, 0xaf020406, 0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, SAIFUN_FLASH_BYTE_ADDR_MASK, 0, "Entry 1010"}, /* ATMEL AT45DB011B (buffered flash) */ {0x2e000003, 0x6e808273, 0x00570081, 0x68848353, 0xaf000400, 1, BUFFERED_FLASH_PAGE_BITS, BUFFERED_FLASH_PAGE_SIZE, BUFFERED_FLASH_BYTE_ADDR_MASK, BUFFERED_FLASH_TOTAL_SIZE, "Buffered flash (128kB)"}, /* Expansion entry 1100 */ {0x33000000, 0x73808201, 0x00050081, 0x03840253, 0xaf020406, 0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, SAIFUN_FLASH_BYTE_ADDR_MASK, 0, "Entry 1100"}, /* Expansion entry 1101 */ {0x3b000002, 0x7b808201, 0x00050081, 0x03840253, 0xaf020406, 0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, SAIFUN_FLASH_BYTE_ADDR_MASK, 0, "Entry 1101"}, /* Ateml Expansion entry 1110 */ {0x37000001, 0x76808273, 0x00570081, 0x68848353, 0xaf000400, 1, BUFFERED_FLASH_PAGE_BITS, BUFFERED_FLASH_PAGE_SIZE, BUFFERED_FLASH_BYTE_ADDR_MASK, 0, "Entry 1110 (Atmel)"}, /* ATMEL AT45DB021B (buffered flash) */ {0x3f000003, 0x7e808273, 0x00570081, 0x68848353, 0xaf000400, 1, BUFFERED_FLASH_PAGE_BITS, BUFFERED_FLASH_PAGE_SIZE, BUFFERED_FLASH_BYTE_ADDR_MASK, BUFFERED_FLASH_TOTAL_SIZE*2, "Buffered flash (256kB)"}, }; /****************************************************************************/ /* FreeBSD device entry points. */ /****************************************************************************/ static int bce_probe (device_t); static int bce_attach (device_t); static int bce_detach (device_t); static void bce_shutdown (device_t); /****************************************************************************/ /* BCE Debug Data Structure Dump Routines */ /****************************************************************************/ #ifdef BCE_DEBUG static void bce_dump_mbuf (struct bce_softc *, struct mbuf *); static void bce_dump_tx_mbuf_chain (struct bce_softc *, int, int); static void bce_dump_rx_mbuf_chain (struct bce_softc *, int, int); static void bce_dump_txbd (struct bce_softc *, int, struct tx_bd *); static void bce_dump_rxbd (struct bce_softc *, int, struct rx_bd *); static void bce_dump_l2fhdr (struct bce_softc *, int, struct l2_fhdr *); static void bce_dump_tx_chain (struct bce_softc *, int, int); static void bce_dump_rx_chain (struct bce_softc *, int, int); static void bce_dump_status_block (struct bce_softc *); static void bce_dump_stats_block (struct bce_softc *); static void bce_dump_driver_state (struct bce_softc *); static void bce_dump_hw_state (struct bce_softc *); static void bce_breakpoint (struct bce_softc *); #endif /****************************************************************************/ /* BCE Register/Memory Access Routines */ /****************************************************************************/ static u32 bce_reg_rd_ind (struct bce_softc *, u32); static void bce_reg_wr_ind (struct bce_softc *, u32, u32); static void bce_ctx_wr (struct bce_softc *, u32, u32, u32); static int bce_miibus_read_reg (device_t, int, int); static int bce_miibus_write_reg (device_t, int, int, int); static void bce_miibus_statchg (device_t); /****************************************************************************/ /* BCE NVRAM Access Routines */ /****************************************************************************/ static int bce_acquire_nvram_lock (struct bce_softc *); static int bce_release_nvram_lock (struct bce_softc *); static void bce_enable_nvram_access (struct bce_softc *); static void bce_disable_nvram_access(struct bce_softc *); static int bce_nvram_read_dword (struct bce_softc *, u32, u8 *, u32); static int bce_init_nvram (struct bce_softc *); static int bce_nvram_read (struct bce_softc *, u32, u8 *, int); static int bce_nvram_test (struct bce_softc *); #ifdef BCE_NVRAM_WRITE_SUPPORT static int bce_enable_nvram_write (struct bce_softc *); static void bce_disable_nvram_write (struct bce_softc *); static int bce_nvram_erase_page (struct bce_softc *, u32); static int bce_nvram_write_dword (struct bce_softc *, u32, u8 *, u32); static int bce_nvram_write (struct bce_softc *, u32, u8 *, int); #endif /****************************************************************************/ /* */ /****************************************************************************/ static void bce_dma_map_addr (void *, bus_dma_segment_t *, int, int); static void bce_dma_map_tx_desc (void *, bus_dma_segment_t *, int, bus_size_t, int); static int bce_dma_alloc (device_t); static void bce_dma_free (struct bce_softc *); static void bce_release_resources (struct bce_softc *); /****************************************************************************/ /* BCE Firmware Synchronization and Load */ /****************************************************************************/ static int bce_fw_sync (struct bce_softc *, u32); static void bce_load_rv2p_fw (struct bce_softc *, u32 *, u32, u32); static void bce_load_cpu_fw (struct bce_softc *, struct cpu_reg *, struct fw_info *); static void bce_init_cpus (struct bce_softc *); static void bce_stop (struct bce_softc *); static int bce_reset (struct bce_softc *, u32); static int bce_chipinit (struct bce_softc *); static int bce_blockinit (struct bce_softc *); static int bce_get_buf (struct bce_softc *, struct mbuf *, u16 *, u16 *, u32 *); static int bce_init_tx_chain (struct bce_softc *); static int bce_init_rx_chain (struct bce_softc *); static void bce_free_rx_chain (struct bce_softc *); static void bce_free_tx_chain (struct bce_softc *); static int bce_tx_encap (struct bce_softc *, struct mbuf *, u16 *, u16 *, u32 *); static void bce_start_locked (struct ifnet *); static void bce_start (struct ifnet *); static int bce_ioctl (struct ifnet *, u_long, caddr_t); static void bce_watchdog (struct ifnet *); static int bce_ifmedia_upd (struct ifnet *); static void bce_ifmedia_sts (struct ifnet *, struct ifmediareq *); static void bce_init_locked (struct bce_softc *); static void bce_init (void *); static void bce_init_context (struct bce_softc *); static void bce_get_mac_addr (struct bce_softc *); static void bce_set_mac_addr (struct bce_softc *); static void bce_phy_intr (struct bce_softc *); static void bce_rx_intr (struct bce_softc *); static void bce_tx_intr (struct bce_softc *); static void bce_disable_intr (struct bce_softc *); static void bce_enable_intr (struct bce_softc *); #ifdef DEVICE_POLLING static void bce_poll_locked (struct ifnet *, enum poll_cmd, int); static void bce_poll (struct ifnet *, enum poll_cmd, int); #endif static void bce_intr (void *); static void bce_set_rx_mode (struct bce_softc *); static void bce_stats_update (struct bce_softc *); static void bce_tick_locked (struct bce_softc *); static void bce_tick (void *); static void bce_add_sysctls (struct bce_softc *); /****************************************************************************/ /* FreeBSD device dispatch table. */ /****************************************************************************/ static device_method_t bce_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bce_probe), DEVMETHOD(device_attach, bce_attach), DEVMETHOD(device_detach, bce_detach), DEVMETHOD(device_shutdown, bce_shutdown), /* bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, bce_miibus_read_reg), DEVMETHOD(miibus_writereg, bce_miibus_write_reg), DEVMETHOD(miibus_statchg, bce_miibus_statchg), { 0, 0 } }; static driver_t bce_driver = { "bce", bce_methods, sizeof(struct bce_softc) }; static devclass_t bce_devclass; MODULE_DEPEND(bce, pci, 1, 1, 1); MODULE_DEPEND(bce, ether, 1, 1, 1); MODULE_DEPEND(bce, miibus, 1, 1, 1); DRIVER_MODULE(bce, pci, bce_driver, bce_devclass, 0, 0); DRIVER_MODULE(miibus, bce, miibus_driver, miibus_devclass, 0, 0); /****************************************************************************/ /* Device probe function. */ /* */ /* Compares the device to the driver's list of supported devices and */ /* reports back to the OS whether this is the right driver for the device. */ /* */ /* Returns: */ /* BUS_PROBE_DEFAULT on success, positive value on failure. */ /****************************************************************************/ static int bce_probe(device_t dev) { struct bce_type *t; struct bce_softc *sc; char *descbuf; u16 vid = 0, did = 0, svid = 0, sdid = 0; t = bce_devs; sc = device_get_softc(dev); bzero(sc, sizeof(struct bce_softc)); sc->bce_unit = device_get_unit(dev); sc->bce_dev = dev; /* Get the data for the device to be probed. */ vid = pci_get_vendor(dev); did = pci_get_device(dev); svid = pci_get_subvendor(dev); sdid = pci_get_subdevice(dev); DBPRINT(sc, BCE_VERBOSE_LOAD, "%s(); VID = 0x%04X, DID = 0x%04X, SVID = 0x%04X, " "SDID = 0x%04X\n", __FUNCTION__, vid, did, svid, sdid); /* Look through the list of known devices for a match. */ while(t->bce_name != NULL) { if ((vid == t->bce_vid) && (did == t->bce_did) && ((svid == t->bce_svid) || (t->bce_svid == PCI_ANY_ID)) && ((sdid == t->bce_sdid) || (t->bce_sdid == PCI_ANY_ID))) { descbuf = malloc(BCE_DEVDESC_MAX, M_TEMP, M_NOWAIT); if (descbuf == NULL) return(ENOMEM); /* Print out the device identity. */ snprintf(descbuf, BCE_DEVDESC_MAX, "%s (%c%d), %s", t->bce_name, (((pci_read_config(dev, PCIR_REVID, 4) & 0xf0) >> 4) + 'A'), (pci_read_config(dev, PCIR_REVID, 4) & 0xf), bce_driver_version); device_set_desc_copy(dev, descbuf); free(descbuf, M_TEMP); return(BUS_PROBE_DEFAULT); } t++; } DBPRINT(sc, BCE_VERBOSE_LOAD, "%s(%d): No IOCTL match found!\n", __FILE__, __LINE__); return(ENXIO); } /****************************************************************************/ /* Device attach function. */ /* */ /* Allocates device resources, performs secondary chip identification, */ /* resets and initializes the hardware, and initializes driver instance */ /* variables. */ /* */ /* Returns: */ /* 0 on success, positive value on failure. */ /****************************************************************************/ static int bce_attach(device_t dev) { struct bce_softc *sc; struct ifnet *ifp; u32 val; int mbuf, rid, rc = 0; sc = device_get_softc(dev); sc->bce_dev = dev; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); mbuf = device_get_unit(dev); sc->bce_unit = mbuf; pci_enable_busmaster(dev); /* Allocate PCI memory resources. */ rid = PCIR_BAR(0); sc->bce_res = bus_alloc_resource_any( dev, /* dev */ SYS_RES_MEMORY, /* type */ &rid, /* rid */ RF_ACTIVE | PCI_RF_DENSE); /* flags */ if (sc->bce_res == NULL) { BCE_PRINTF(sc, "%s(%d): PCI memory allocation failed\n", __FILE__, __LINE__); rc = ENXIO; goto bce_attach_fail; } /* Get various resource handles. */ sc->bce_btag = rman_get_bustag(sc->bce_res); sc->bce_bhandle = rman_get_bushandle(sc->bce_res); sc->bce_vhandle = (vm_offset_t) rman_get_virtual(sc->bce_res); /* Allocate PCI IRQ resources. */ rid = 0; sc->bce_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->bce_irq == NULL) { BCE_PRINTF(sc, "%s(%d): PCI map interrupt failed\n", __FILE__, __LINE__); rc = ENXIO; goto bce_attach_fail; } /* Initialize mutex for the current device instance. */ BCE_LOCK_INIT(sc, device_get_nameunit(dev)); /* * Configure byte swap and enable indirect register access. * Rely on CPU to do target byte swapping on big endian systems. * Access to registers outside of PCI configurtion space are not * valid until this is done. */ pci_write_config(dev, BCE_PCICFG_MISC_CONFIG, BCE_PCICFG_MISC_CONFIG_REG_WINDOW_ENA | BCE_PCICFG_MISC_CONFIG_TARGET_MB_WORD_SWAP, 4); /* Save ASIC revsion info. */ sc->bce_chipid = REG_RD(sc, BCE_MISC_ID); /* Weed out any non-production controller revisions. */ switch(BCE_CHIP_ID(sc)) { case BCE_CHIP_ID_5706_A0: case BCE_CHIP_ID_5706_A1: case BCE_CHIP_ID_5708_A0: case BCE_CHIP_ID_5708_B0: BCE_PRINTF(sc, "%s(%d): Unsupported controller revision (%c%d)!\n", __FILE__, __LINE__, (((pci_read_config(dev, PCIR_REVID, 4) & 0xf0) >> 4) + 'A'), (pci_read_config(dev, PCIR_REVID, 4) & 0xf)); rc = ENODEV; goto bce_attach_fail; } if (BCE_CHIP_BOND_ID(sc) & BCE_CHIP_BOND_ID_SERDES_BIT) { BCE_PRINTF(sc, "%s(%d): SerDes controllers are not supported!\n", __FILE__, __LINE__); rc = ENODEV; goto bce_attach_fail; } /* * The embedded PCIe to PCI-X bridge (EPB) * in the 5708 cannot address memory above * 40 bits (E7_5708CB1_23043 & E6_5708SB1_23043). */ if (BCE_CHIP_NUM(sc) == BCE_CHIP_NUM_5708) sc->max_bus_addr = BCE_BUS_SPACE_MAXADDR; else sc->max_bus_addr = BUS_SPACE_MAXADDR; /* * Find the base address for shared memory access. * Newer versions of bootcode use a signature and offset * while older versions use a fixed address. */ val = REG_RD_IND(sc, BCE_SHM_HDR_SIGNATURE); if ((val & BCE_SHM_HDR_SIGNATURE_SIG_MASK) == BCE_SHM_HDR_SIGNATURE_SIG) sc->bce_shmem_base = REG_RD_IND(sc, BCE_SHM_HDR_ADDR_0); else sc->bce_shmem_base = HOST_VIEW_SHMEM_BASE; DBPRINT(sc, BCE_INFO, "bce_shmem_base = 0x%08X\n", sc->bce_shmem_base); /* Set initial device and PHY flags */ sc->bce_flags = 0; sc->bce_phy_flags = 0; /* Get PCI bus information (speed and type). */ val = REG_RD(sc, BCE_PCICFG_MISC_STATUS); if (val & BCE_PCICFG_MISC_STATUS_PCIX_DET) { u32 clkreg; sc->bce_flags |= BCE_PCIX_FLAG; clkreg = REG_RD(sc, BCE_PCICFG_PCI_CLOCK_CONTROL_BITS); clkreg &= BCE_PCICFG_PCI_CLOCK_CONTROL_BITS_PCI_CLK_SPD_DET; switch (clkreg) { case BCE_PCICFG_PCI_CLOCK_CONTROL_BITS_PCI_CLK_SPD_DET_133MHZ: sc->bus_speed_mhz = 133; break; case BCE_PCICFG_PCI_CLOCK_CONTROL_BITS_PCI_CLK_SPD_DET_95MHZ: sc->bus_speed_mhz = 100; break; case BCE_PCICFG_PCI_CLOCK_CONTROL_BITS_PCI_CLK_SPD_DET_66MHZ: case BCE_PCICFG_PCI_CLOCK_CONTROL_BITS_PCI_CLK_SPD_DET_80MHZ: sc->bus_speed_mhz = 66; break; case BCE_PCICFG_PCI_CLOCK_CONTROL_BITS_PCI_CLK_SPD_DET_48MHZ: case BCE_PCICFG_PCI_CLOCK_CONTROL_BITS_PCI_CLK_SPD_DET_55MHZ: sc->bus_speed_mhz = 50; break; case BCE_PCICFG_PCI_CLOCK_CONTROL_BITS_PCI_CLK_SPD_DET_LOW: case BCE_PCICFG_PCI_CLOCK_CONTROL_BITS_PCI_CLK_SPD_DET_32MHZ: case BCE_PCICFG_PCI_CLOCK_CONTROL_BITS_PCI_CLK_SPD_DET_38MHZ: sc->bus_speed_mhz = 33; break; } } else { if (val & BCE_PCICFG_MISC_STATUS_M66EN) sc->bus_speed_mhz = 66; else sc->bus_speed_mhz = 33; } if (val & BCE_PCICFG_MISC_STATUS_32BIT_DET) sc->bce_flags |= BCE_PCI_32BIT_FLAG; BCE_PRINTF(sc, "ASIC ID 0x%08X; Revision (%c%d); PCI%s %s %dMHz\n", sc->bce_chipid, ((BCE_CHIP_ID(sc) & 0xf000) >> 12) + 'A', ((BCE_CHIP_ID(sc) & 0x0ff0) >> 4), ((sc->bce_flags & BCE_PCIX_FLAG) ? "-X" : ""), ((sc->bce_flags & BCE_PCI_32BIT_FLAG) ? "32-bit" : "64-bit"), sc->bus_speed_mhz); /* Reset the controller. */ if (bce_reset(sc, BCE_DRV_MSG_CODE_RESET)) { rc = ENXIO; goto bce_attach_fail; } /* Initialize the controller. */ if (bce_chipinit(sc)) { BCE_PRINTF(sc, "%s(%d): Controller initialization failed!\n", __FILE__, __LINE__); rc = ENXIO; goto bce_attach_fail; } /* Perform NVRAM test. */ if (bce_nvram_test(sc)) { BCE_PRINTF(sc, "%s(%d): NVRAM test failed!\n", __FILE__, __LINE__); rc = ENXIO; goto bce_attach_fail; } /* Fetch the permanent Ethernet MAC address. */ bce_get_mac_addr(sc); /* * Trip points control how many BDs * should be ready before generating an * interrupt while ticks control how long * a BD can sit in the chain before * generating an interrupt. Set the default * values for the RX and TX rings. */ #ifdef BCE_DRBUG /* Force more frequent interrupts. */ sc->bce_tx_quick_cons_trip_int = 1; sc->bce_tx_quick_cons_trip = 1; sc->bce_tx_ticks_int = 0; sc->bce_tx_ticks = 0; sc->bce_rx_quick_cons_trip_int = 1; sc->bce_rx_quick_cons_trip = 1; sc->bce_rx_ticks_int = 0; sc->bce_rx_ticks = 0; #else sc->bce_tx_quick_cons_trip_int = 20; sc->bce_tx_quick_cons_trip = 20; sc->bce_tx_ticks_int = 80; sc->bce_tx_ticks = 80; sc->bce_rx_quick_cons_trip_int = 6; sc->bce_rx_quick_cons_trip = 6; sc->bce_rx_ticks_int = 18; sc->bce_rx_ticks = 18; #endif /* Update statistics once every second. */ sc->bce_stats_ticks = 1000000 & 0xffff00; /* * The copper based NetXtreme II controllers * use an integrated PHY at address 1 while * the SerDes controllers use a PHY at * address 2. */ sc->bce_phy_addr = 1; if (BCE_CHIP_BOND_ID(sc) & BCE_CHIP_BOND_ID_SERDES_BIT) { sc->bce_phy_flags |= BCE_PHY_SERDES_FLAG; sc->bce_flags |= BCE_NO_WOL_FLAG; if (BCE_CHIP_NUM(sc) == BCE_CHIP_NUM_5708) { sc->bce_phy_addr = 2; val = REG_RD_IND(sc, sc->bce_shmem_base + BCE_SHARED_HW_CFG_CONFIG); if (val & BCE_SHARED_HW_CFG_PHY_2_5G) sc->bce_phy_flags |= BCE_PHY_2_5G_CAPABLE_FLAG; } } /* Allocate DMA memory resources. */ if (bce_dma_alloc(dev)) { BCE_PRINTF(sc, "%s(%d): DMA resource allocation failed!\n", __FILE__, __LINE__); rc = ENXIO; goto bce_attach_fail; } /* Allocate an ifnet structure. */ ifp = sc->bce_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { BCE_PRINTF(sc, "%s(%d): Interface allocation failed!\n", __FILE__, __LINE__); rc = ENXIO; goto bce_attach_fail; } /* Initialize the ifnet interface. */ ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = bce_ioctl; ifp->if_start = bce_start; ifp->if_timer = 0; ifp->if_watchdog = bce_watchdog; ifp->if_init = bce_init; ifp->if_mtu = ETHERMTU; ifp->if_hwassist = BCE_IF_HWASSIST; ifp->if_capabilities = BCE_IF_CAPABILITIES; ifp->if_capenable = ifp->if_capabilities; /* Assume a standard 1500 byte MTU size for mbuf allocations. */ sc->mbuf_alloc_size = MCLBYTES; #ifdef DEVICE_POLLING ifp->if_capabilities |= IFCAP_POLLING; #endif ifp->if_snd.ifq_drv_maxlen = USABLE_TX_BD; if (sc->bce_phy_flags & BCE_PHY_2_5G_CAPABLE_FLAG) ifp->if_baudrate = IF_Gbps(2.5); else ifp->if_baudrate = IF_Gbps(1); IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); IFQ_SET_READY(&ifp->if_snd); if (sc->bce_phy_flags & BCE_PHY_SERDES_FLAG) { BCE_PRINTF(sc, "%s(%d): SerDes is not supported by this driver!\n", __FILE__, __LINE__); rc = ENODEV; goto bce_attach_fail; } else { /* Look for our PHY. */ if (mii_phy_probe(dev, &sc->bce_miibus, bce_ifmedia_upd, bce_ifmedia_sts)) { BCE_PRINTF(sc, "%s(%d): PHY probe failed!\n", __FILE__, __LINE__); rc = ENXIO; goto bce_attach_fail; } } /* Attach to the Ethernet interface list. */ ether_ifattach(ifp, sc->eaddr); #if __FreeBSD_version < 500000 callout_init(&sc->bce_stat_ch); #else callout_init(&sc->bce_stat_ch, CALLOUT_MPSAFE); #endif /* Hookup IRQ last. */ rc = bus_setup_intr(dev, sc->bce_irq, INTR_TYPE_NET | INTR_MPSAFE, bce_intr, sc, &sc->bce_intrhand); if (rc) { BCE_PRINTF(sc, "%s(%d): Failed to setup IRQ!\n", __FILE__, __LINE__); bce_detach(dev); goto bce_attach_exit; } /* Print some important debugging info. */ DBRUN(BCE_INFO, bce_dump_driver_state(sc)); /* Add the supported sysctls to the kernel. */ bce_add_sysctls(sc); goto bce_attach_exit; bce_attach_fail: bce_release_resources(sc); bce_attach_exit: DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return(rc); } /****************************************************************************/ /* Device detach function. */ /* */ /* Stops the controller, resets the controller, and releases resources. */ /* */ /* Returns: */ /* 0 on success, positive value on failure. */ /****************************************************************************/ static int bce_detach(device_t dev) { struct bce_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); ifp = sc->bce_ifp; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) ether_poll_deregister(ifp); #endif /* Stop and reset the controller. */ BCE_LOCK(sc); bce_stop(sc); bce_reset(sc, BCE_DRV_MSG_CODE_RESET); BCE_UNLOCK(sc); ether_ifdetach(ifp); /* If we have a child device on the MII bus remove it too. */ if (sc->bce_phy_flags & BCE_PHY_SERDES_FLAG) { ifmedia_removeall(&sc->bce_ifmedia); } else { bus_generic_detach(dev); device_delete_child(dev, sc->bce_miibus); } /* Release all remaining resources. */ bce_release_resources(sc); DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return(0); } /****************************************************************************/ /* Device shutdown function. */ /* */ /* Stops and resets the controller. */ /* */ /* Returns: */ /* Nothing */ /****************************************************************************/ static void bce_shutdown(device_t dev) { struct bce_softc *sc = device_get_softc(dev); BCE_LOCK(sc); bce_stop(sc); bce_reset(sc, BCE_DRV_MSG_CODE_RESET); BCE_UNLOCK(sc); } /****************************************************************************/ /* Indirect register read. */ /* */ /* Reads NetXtreme II registers using an index/data register pair in PCI */ /* configuration space. Using this mechanism avoids issues with posted */ /* reads but is much slower than memory-mapped I/O. */ /* */ /* Returns: */ /* The value of the register. */ /****************************************************************************/ static u32 bce_reg_rd_ind(struct bce_softc *sc, u32 offset) { device_t dev; dev = sc->bce_dev; pci_write_config(dev, BCE_PCICFG_REG_WINDOW_ADDRESS, offset, 4); #ifdef BCE_DEBUG { u32 val; val = pci_read_config(dev, BCE_PCICFG_REG_WINDOW, 4); DBPRINT(sc, BCE_EXCESSIVE, "%s(); offset = 0x%08X, val = 0x%08X\n", __FUNCTION__, offset, val); return val; } #else return pci_read_config(dev, BCE_PCICFG_REG_WINDOW, 4); #endif } /****************************************************************************/ /* Indirect register write. */ /* */ /* Writes NetXtreme II registers using an index/data register pair in PCI */ /* configuration space. Using this mechanism avoids issues with posted */ /* writes but is muchh slower than memory-mapped I/O. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_reg_wr_ind(struct bce_softc *sc, u32 offset, u32 val) { device_t dev; dev = sc->bce_dev; DBPRINT(sc, BCE_EXCESSIVE, "%s(); offset = 0x%08X, val = 0x%08X\n", __FUNCTION__, offset, val); pci_write_config(dev, BCE_PCICFG_REG_WINDOW_ADDRESS, offset, 4); pci_write_config(dev, BCE_PCICFG_REG_WINDOW, val, 4); } /****************************************************************************/ /* Context memory write. */ /* */ /* The NetXtreme II controller uses context memory to track connection */ /* information for L2 and higher network protocols. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_ctx_wr(struct bce_softc *sc, u32 cid_addr, u32 offset, u32 val) { DBPRINT(sc, BCE_EXCESSIVE, "%s(); cid_addr = 0x%08X, offset = 0x%08X, " "val = 0x%08X\n", __FUNCTION__, cid_addr, offset, val); offset += cid_addr; REG_WR(sc, BCE_CTX_DATA_ADR, offset); REG_WR(sc, BCE_CTX_DATA, val); } /****************************************************************************/ /* PHY register read. */ /* */ /* Implements register reads on the MII bus. */ /* */ /* Returns: */ /* The value of the register. */ /****************************************************************************/ static int bce_miibus_read_reg(device_t dev, int phy, int reg) { struct bce_softc *sc; u32 val; int i; sc = device_get_softc(dev); /* Make sure we are accessing the correct PHY address. */ if (phy != sc->bce_phy_addr) { DBPRINT(sc, BCE_VERBOSE, "Invalid PHY address %d for PHY read!\n", phy); return(0); } if (sc->bce_phy_flags & BCE_PHY_INT_MODE_AUTO_POLLING_FLAG) { val = REG_RD(sc, BCE_EMAC_MDIO_MODE); val &= ~BCE_EMAC_MDIO_MODE_AUTO_POLL; REG_WR(sc, BCE_EMAC_MDIO_MODE, val); REG_RD(sc, BCE_EMAC_MDIO_MODE); DELAY(40); } val = BCE_MIPHY(phy) | BCE_MIREG(reg) | BCE_EMAC_MDIO_COMM_COMMAND_READ | BCE_EMAC_MDIO_COMM_DISEXT | BCE_EMAC_MDIO_COMM_START_BUSY; REG_WR(sc, BCE_EMAC_MDIO_COMM, val); for (i = 0; i < BCE_PHY_TIMEOUT; i++) { DELAY(10); val = REG_RD(sc, BCE_EMAC_MDIO_COMM); if (!(val & BCE_EMAC_MDIO_COMM_START_BUSY)) { DELAY(5); val = REG_RD(sc, BCE_EMAC_MDIO_COMM); val &= BCE_EMAC_MDIO_COMM_DATA; break; } } if (val & BCE_EMAC_MDIO_COMM_START_BUSY) { BCE_PRINTF(sc, "%s(%d): Error: PHY read timeout! phy = %d, reg = 0x%04X\n", __FILE__, __LINE__, phy, reg); val = 0x0; } else { val = REG_RD(sc, BCE_EMAC_MDIO_COMM); } DBPRINT(sc, BCE_EXCESSIVE, "%s(): phy = %d, reg = 0x%04X, val = 0x%04X\n", __FUNCTION__, phy, (u16) reg & 0xffff, (u16) val & 0xffff); if (sc->bce_phy_flags & BCE_PHY_INT_MODE_AUTO_POLLING_FLAG) { val = REG_RD(sc, BCE_EMAC_MDIO_MODE); val |= BCE_EMAC_MDIO_MODE_AUTO_POLL; REG_WR(sc, BCE_EMAC_MDIO_MODE, val); REG_RD(sc, BCE_EMAC_MDIO_MODE); DELAY(40); } return (val & 0xffff); } /****************************************************************************/ /* PHY register write. */ /* */ /* Implements register writes on the MII bus. */ /* */ /* Returns: */ /* The value of the register. */ /****************************************************************************/ static int bce_miibus_write_reg(device_t dev, int phy, int reg, int val) { struct bce_softc *sc; u32 val1; int i; sc = device_get_softc(dev); /* Make sure we are accessing the correct PHY address. */ if (phy != sc->bce_phy_addr) { DBPRINT(sc, BCE_WARN, "Invalid PHY address %d for PHY write!\n", phy); return(0); } DBPRINT(sc, BCE_EXCESSIVE, "%s(): phy = %d, reg = 0x%04X, val = 0x%04X\n", __FUNCTION__, phy, (u16) reg & 0xffff, (u16) val & 0xffff); if (sc->bce_phy_flags & BCE_PHY_INT_MODE_AUTO_POLLING_FLAG) { val1 = REG_RD(sc, BCE_EMAC_MDIO_MODE); val1 &= ~BCE_EMAC_MDIO_MODE_AUTO_POLL; REG_WR(sc, BCE_EMAC_MDIO_MODE, val1); REG_RD(sc, BCE_EMAC_MDIO_MODE); DELAY(40); } val1 = BCE_MIPHY(phy) | BCE_MIREG(reg) | val | BCE_EMAC_MDIO_COMM_COMMAND_WRITE | BCE_EMAC_MDIO_COMM_START_BUSY | BCE_EMAC_MDIO_COMM_DISEXT; REG_WR(sc, BCE_EMAC_MDIO_COMM, val1); for (i = 0; i < BCE_PHY_TIMEOUT; i++) { DELAY(10); val1 = REG_RD(sc, BCE_EMAC_MDIO_COMM); if (!(val1 & BCE_EMAC_MDIO_COMM_START_BUSY)) { DELAY(5); break; } } if (val1 & BCE_EMAC_MDIO_COMM_START_BUSY) BCE_PRINTF(sc, "%s(%d): PHY write timeout!\n", __FILE__, __LINE__); if (sc->bce_phy_flags & BCE_PHY_INT_MODE_AUTO_POLLING_FLAG) { val1 = REG_RD(sc, BCE_EMAC_MDIO_MODE); val1 |= BCE_EMAC_MDIO_MODE_AUTO_POLL; REG_WR(sc, BCE_EMAC_MDIO_MODE, val1); REG_RD(sc, BCE_EMAC_MDIO_MODE); DELAY(40); } return 0; } /****************************************************************************/ /* MII bus status change. */ /* */ /* Called by the MII bus driver when the PHY establishes link to set the */ /* MAC interface registers. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_miibus_statchg(device_t dev) { struct bce_softc *sc; struct mii_data *mii; sc = device_get_softc(dev); mii = device_get_softc(sc->bce_miibus); BCE_CLRBIT(sc, BCE_EMAC_MODE, BCE_EMAC_MODE_PORT); /* Set MII or GMII inerface based on the speed negotiated by the PHY. */ if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T) { DBPRINT(sc, BCE_INFO, "Setting GMII interface.\n"); BCE_SETBIT(sc, BCE_EMAC_MODE, BCE_EMAC_MODE_PORT_GMII); } else { DBPRINT(sc, BCE_INFO, "Setting MII interface.\n"); BCE_SETBIT(sc, BCE_EMAC_MODE, BCE_EMAC_MODE_PORT_MII); } /* Set half or full duplex based on the duplicity negotiated by the PHY. */ if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) { DBPRINT(sc, BCE_INFO, "Setting Full-Duplex interface.\n"); BCE_CLRBIT(sc, BCE_EMAC_MODE, BCE_EMAC_MODE_HALF_DUPLEX); } else { DBPRINT(sc, BCE_INFO, "Setting Half-Duplex interface.\n"); BCE_SETBIT(sc, BCE_EMAC_MODE, BCE_EMAC_MODE_HALF_DUPLEX); } } /****************************************************************************/ /* Acquire NVRAM lock. */ /* */ /* Before the NVRAM can be accessed the caller must acquire an NVRAM lock. */ /* Locks 0 and 2 are reserved, lock 1 is used by firmware and lock 2 is */ /* for use by the driver. */ /* */ /* Returns: */ /* 0 on success, positive value on failure. */ /****************************************************************************/ static int bce_acquire_nvram_lock(struct bce_softc *sc) { u32 val; int j; DBPRINT(sc, BCE_VERBOSE, "Acquiring NVRAM lock.\n"); /* Request access to the flash interface. */ REG_WR(sc, BCE_NVM_SW_ARB, BCE_NVM_SW_ARB_ARB_REQ_SET2); for (j = 0; j < NVRAM_TIMEOUT_COUNT; j++) { val = REG_RD(sc, BCE_NVM_SW_ARB); if (val & BCE_NVM_SW_ARB_ARB_ARB2) break; DELAY(5); } if (j >= NVRAM_TIMEOUT_COUNT) { DBPRINT(sc, BCE_WARN, "Timeout acquiring NVRAM lock!\n"); return EBUSY; } return 0; } /****************************************************************************/ /* Release NVRAM lock. */ /* */ /* When the caller is finished accessing NVRAM the lock must be released. */ /* Locks 0 and 2 are reserved, lock 1 is used by firmware and lock 2 is */ /* for use by the driver. */ /* */ /* Returns: */ /* 0 on success, positive value on failure. */ /****************************************************************************/ static int bce_release_nvram_lock(struct bce_softc *sc) { int j; u32 val; DBPRINT(sc, BCE_VERBOSE, "Releasing NVRAM lock.\n"); /* * Relinquish nvram interface. */ REG_WR(sc, BCE_NVM_SW_ARB, BCE_NVM_SW_ARB_ARB_REQ_CLR2); for (j = 0; j < NVRAM_TIMEOUT_COUNT; j++) { val = REG_RD(sc, BCE_NVM_SW_ARB); if (!(val & BCE_NVM_SW_ARB_ARB_ARB2)) break; DELAY(5); } if (j >= NVRAM_TIMEOUT_COUNT) { DBPRINT(sc, BCE_WARN, "Timeout reeasing NVRAM lock!\n"); return EBUSY; } return 0; } #ifdef BCE_NVRAM_WRITE_SUPPORT /****************************************************************************/ /* Enable NVRAM write access. */ /* */ /* Before writing to NVRAM the caller must enable NVRAM writes. */ /* */ /* Returns: */ /* 0 on success, positive value on failure. */ /****************************************************************************/ static int bce_enable_nvram_write(struct bce_softc *sc) { u32 val; DBPRINT(sc, BCE_VERBOSE, "Enabling NVRAM write.\n"); val = REG_RD(sc, BCE_MISC_CFG); REG_WR(sc, BCE_MISC_CFG, val | BCE_MISC_CFG_NVM_WR_EN_PCI); if (!sc->bce_flash_info->buffered) { int j; REG_WR(sc, BCE_NVM_COMMAND, BCE_NVM_COMMAND_DONE); REG_WR(sc, BCE_NVM_COMMAND, BCE_NVM_COMMAND_WREN | BCE_NVM_COMMAND_DOIT); for (j = 0; j < NVRAM_TIMEOUT_COUNT; j++) { DELAY(5); val = REG_RD(sc, BCE_NVM_COMMAND); if (val & BCE_NVM_COMMAND_DONE) break; } if (j >= NVRAM_TIMEOUT_COUNT) { DBPRINT(sc, BCE_WARN, "Timeout writing NVRAM!\n"); return EBUSY; } } return 0; } /****************************************************************************/ /* Disable NVRAM write access. */ /* */ /* When the caller is finished writing to NVRAM write access must be */ /* disabled. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_disable_nvram_write(struct bce_softc *sc) { u32 val; DBPRINT(sc, BCE_VERBOSE, "Disabling NVRAM write.\n"); val = REG_RD(sc, BCE_MISC_CFG); REG_WR(sc, BCE_MISC_CFG, val & ~BCE_MISC_CFG_NVM_WR_EN); } #endif /****************************************************************************/ /* Enable NVRAM access. */ /* */ /* Before accessing NVRAM for read or write operations the caller must */ /* enabled NVRAM access. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_enable_nvram_access(struct bce_softc *sc) { u32 val; DBPRINT(sc, BCE_VERBOSE, "Enabling NVRAM access.\n"); val = REG_RD(sc, BCE_NVM_ACCESS_ENABLE); /* Enable both bits, even on read. */ REG_WR(sc, BCE_NVM_ACCESS_ENABLE, val | BCE_NVM_ACCESS_ENABLE_EN | BCE_NVM_ACCESS_ENABLE_WR_EN); } /****************************************************************************/ /* Disable NVRAM access. */ /* */ /* When the caller is finished accessing NVRAM access must be disabled. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_disable_nvram_access(struct bce_softc *sc) { u32 val; DBPRINT(sc, BCE_VERBOSE, "Disabling NVRAM access.\n"); val = REG_RD(sc, BCE_NVM_ACCESS_ENABLE); /* Disable both bits, even after read. */ REG_WR(sc, BCE_NVM_ACCESS_ENABLE, val & ~(BCE_NVM_ACCESS_ENABLE_EN | BCE_NVM_ACCESS_ENABLE_WR_EN)); } #ifdef BCE_NVRAM_WRITE_SUPPORT /****************************************************************************/ /* Erase NVRAM page before writing. */ /* */ /* Non-buffered flash parts require that a page be erased before it is */ /* written. */ /* */ /* Returns: */ /* 0 on success, positive value on failure. */ /****************************************************************************/ static int bce_nvram_erase_page(struct bce_softc *sc, u32 offset) { u32 cmd; int j; /* Buffered flash doesn't require an erase. */ if (sc->bce_flash_info->buffered) return 0; DBPRINT(sc, BCE_VERBOSE, "Erasing NVRAM page.\n"); /* Build an erase command. */ cmd = BCE_NVM_COMMAND_ERASE | BCE_NVM_COMMAND_WR | BCE_NVM_COMMAND_DOIT; /* * Clear the DONE bit separately, set the NVRAM adress to erase, * and issue the erase command. */ REG_WR(sc, BCE_NVM_COMMAND, BCE_NVM_COMMAND_DONE); REG_WR(sc, BCE_NVM_ADDR, offset & BCE_NVM_ADDR_NVM_ADDR_VALUE); REG_WR(sc, BCE_NVM_COMMAND, cmd); /* Wait for completion. */ for (j = 0; j < NVRAM_TIMEOUT_COUNT; j++) { u32 val; DELAY(5); val = REG_RD(sc, BCE_NVM_COMMAND); if (val & BCE_NVM_COMMAND_DONE) break; } if (j >= NVRAM_TIMEOUT_COUNT) { DBPRINT(sc, BCE_WARN, "Timeout erasing NVRAM.\n"); return EBUSY; } return 0; } #endif /* BCE_NVRAM_WRITE_SUPPORT */ /****************************************************************************/ /* Read a dword (32 bits) from NVRAM. */ /* */ /* Read a 32 bit word from NVRAM. The caller is assumed to have already */ /* obtained the NVRAM lock and enabled the controller for NVRAM access. */ /* */ /* Returns: */ /* 0 on success and the 32 bit value read, positive value on failure. */ /****************************************************************************/ static int bce_nvram_read_dword(struct bce_softc *sc, u32 offset, u8 *ret_val, u32 cmd_flags) { u32 cmd; int i, rc = 0; /* Build the command word. */ cmd = BCE_NVM_COMMAND_DOIT | cmd_flags; /* Calculate the offset for buffered flash. */ if (sc->bce_flash_info->buffered) { offset = ((offset / sc->bce_flash_info->page_size) << sc->bce_flash_info->page_bits) + (offset % sc->bce_flash_info->page_size); } /* * Clear the DONE bit separately, set the address to read, * and issue the read. */ REG_WR(sc, BCE_NVM_COMMAND, BCE_NVM_COMMAND_DONE); REG_WR(sc, BCE_NVM_ADDR, offset & BCE_NVM_ADDR_NVM_ADDR_VALUE); REG_WR(sc, BCE_NVM_COMMAND, cmd); /* Wait for completion. */ for (i = 0; i < NVRAM_TIMEOUT_COUNT; i++) { u32 val; DELAY(5); val = REG_RD(sc, BCE_NVM_COMMAND); if (val & BCE_NVM_COMMAND_DONE) { val = REG_RD(sc, BCE_NVM_READ); val = bce_be32toh(val); memcpy(ret_val, &val, 4); break; } } /* Check for errors. */ if (i >= NVRAM_TIMEOUT_COUNT) { BCE_PRINTF(sc, "%s(%d): Timeout error reading NVRAM at offset 0x%08X!\n", __FILE__, __LINE__, offset); rc = EBUSY; } return(rc); } #ifdef BCE_NVRAM_WRITE_SUPPORT /****************************************************************************/ /* Write a dword (32 bits) to NVRAM. */ /* */ /* Write a 32 bit word to NVRAM. The caller is assumed to have already */ /* obtained the NVRAM lock, enabled the controller for NVRAM access, and */ /* enabled NVRAM write access. */ /* */ /* Returns: */ /* 0 on success, positive value on failure. */ /****************************************************************************/ static int bce_nvram_write_dword(struct bce_softc *sc, u32 offset, u8 *val, u32 cmd_flags) { u32 cmd, val32; int j; /* Build the command word. */ cmd = BCE_NVM_COMMAND_DOIT | BCE_NVM_COMMAND_WR | cmd_flags; /* Calculate the offset for buffered flash. */ if (sc->bce_flash_info->buffered) { offset = ((offset / sc->bce_flash_info->page_size) << sc->bce_flash_info->page_bits) + (offset % sc->bce_flash_info->page_size); } /* * Clear the DONE bit separately, convert NVRAM data to big-endian, * set the NVRAM address to write, and issue the write command */ REG_WR(sc, BCE_NVM_COMMAND, BCE_NVM_COMMAND_DONE); memcpy(&val32, val, 4); val32 = htobe32(val32); REG_WR(sc, BCE_NVM_WRITE, val32); REG_WR(sc, BCE_NVM_ADDR, offset & BCE_NVM_ADDR_NVM_ADDR_VALUE); REG_WR(sc, BCE_NVM_COMMAND, cmd); /* Wait for completion. */ for (j = 0; j < NVRAM_TIMEOUT_COUNT; j++) { DELAY(5); if (REG_RD(sc, BCE_NVM_COMMAND) & BCE_NVM_COMMAND_DONE) break; } if (j >= NVRAM_TIMEOUT_COUNT) { BCE_PRINTF(sc, "%s(%d): Timeout error writing NVRAM at offset 0x%08X\n", __FILE__, __LINE__, offset); return EBUSY; } return 0; } #endif /* BCE_NVRAM_WRITE_SUPPORT */ /****************************************************************************/ /* Initialize NVRAM access. */ /* */ /* Identify the NVRAM device in use and prepare the NVRAM interface to */ /* access that device. */ /* */ /* Returns: */ /* 0 on success, positive value on failure. */ /****************************************************************************/ static int bce_init_nvram(struct bce_softc *sc) { u32 val; int j, entry_count, rc; struct flash_spec *flash; DBPRINT(sc,BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); /* Determine the selected interface. */ val = REG_RD(sc, BCE_NVM_CFG1); entry_count = sizeof(flash_table) / sizeof(struct flash_spec); rc = 0; /* * Flash reconfiguration is required to support additional * NVRAM devices not directly supported in hardware. * Check if the flash interface was reconfigured * by the bootcode. */ if (val & 0x40000000) { /* Flash interface reconfigured by bootcode. */ DBPRINT(sc,BCE_INFO_LOAD, "bce_init_nvram(): Flash WAS reconfigured.\n"); for (j = 0, flash = &flash_table[0]; j < entry_count; j++, flash++) { if ((val & FLASH_BACKUP_STRAP_MASK) == (flash->config1 & FLASH_BACKUP_STRAP_MASK)) { sc->bce_flash_info = flash; break; } } } else { /* Flash interface not yet reconfigured. */ u32 mask; DBPRINT(sc,BCE_INFO_LOAD, "bce_init_nvram(): Flash was NOT reconfigured.\n"); if (val & (1 << 23)) mask = FLASH_BACKUP_STRAP_MASK; else mask = FLASH_STRAP_MASK; /* Look for the matching NVRAM device configuration data. */ for (j = 0, flash = &flash_table[0]; j < entry_count; j++, flash++) { /* Check if the device matches any of the known devices. */ if ((val & mask) == (flash->strapping & mask)) { /* Found a device match. */ sc->bce_flash_info = flash; /* Request access to the flash interface. */ if ((rc = bce_acquire_nvram_lock(sc)) != 0) return rc; /* Reconfigure the flash interface. */ bce_enable_nvram_access(sc); REG_WR(sc, BCE_NVM_CFG1, flash->config1); REG_WR(sc, BCE_NVM_CFG2, flash->config2); REG_WR(sc, BCE_NVM_CFG3, flash->config3); REG_WR(sc, BCE_NVM_WRITE1, flash->write1); bce_disable_nvram_access(sc); bce_release_nvram_lock(sc); break; } } } /* Check if a matching device was found. */ if (j == entry_count) { sc->bce_flash_info = NULL; BCE_PRINTF(sc, "%s(%d): Unknown Flash NVRAM found!\n", __FILE__, __LINE__); rc = ENODEV; } /* Write the flash config data to the shared memory interface. */ val = REG_RD_IND(sc, sc->bce_shmem_base + BCE_SHARED_HW_CFG_CONFIG2); val &= BCE_SHARED_HW_CFG2_NVM_SIZE_MASK; if (val) sc->bce_flash_size = val; else sc->bce_flash_size = sc->bce_flash_info->total_size; DBPRINT(sc, BCE_INFO_LOAD, "bce_init_nvram() flash->total_size = 0x%08X\n", sc->bce_flash_info->total_size); DBPRINT(sc,BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return rc; } /****************************************************************************/ /* Read an arbitrary range of data from NVRAM. */ /* */ /* Prepares the NVRAM interface for access and reads the requested data */ /* into the supplied buffer. */ /* */ /* Returns: */ /* 0 on success and the data read, positive value on failure. */ /****************************************************************************/ static int bce_nvram_read(struct bce_softc *sc, u32 offset, u8 *ret_buf, int buf_size) { int rc = 0; u32 cmd_flags, offset32, len32, extra; if (buf_size == 0) return 0; /* Request access to the flash interface. */ if ((rc = bce_acquire_nvram_lock(sc)) != 0) return rc; /* Enable access to flash interface */ bce_enable_nvram_access(sc); len32 = buf_size; offset32 = offset; extra = 0; cmd_flags = 0; if (offset32 & 3) { u8 buf[4]; u32 pre_len; offset32 &= ~3; pre_len = 4 - (offset & 3); if (pre_len >= len32) { pre_len = len32; cmd_flags = BCE_NVM_COMMAND_FIRST | BCE_NVM_COMMAND_LAST; } else { cmd_flags = BCE_NVM_COMMAND_FIRST; } rc = bce_nvram_read_dword(sc, offset32, buf, cmd_flags); if (rc) return rc; memcpy(ret_buf, buf + (offset & 3), pre_len); offset32 += 4; ret_buf += pre_len; len32 -= pre_len; } if (len32 & 3) { extra = 4 - (len32 & 3); len32 = (len32 + 4) & ~3; } if (len32 == 4) { u8 buf[4]; if (cmd_flags) cmd_flags = BCE_NVM_COMMAND_LAST; else cmd_flags = BCE_NVM_COMMAND_FIRST | BCE_NVM_COMMAND_LAST; rc = bce_nvram_read_dword(sc, offset32, buf, cmd_flags); memcpy(ret_buf, buf, 4 - extra); } else if (len32 > 0) { u8 buf[4]; /* Read the first word. */ if (cmd_flags) cmd_flags = 0; else cmd_flags = BCE_NVM_COMMAND_FIRST; rc = bce_nvram_read_dword(sc, offset32, ret_buf, cmd_flags); /* Advance to the next dword. */ offset32 += 4; ret_buf += 4; len32 -= 4; while (len32 > 4 && rc == 0) { rc = bce_nvram_read_dword(sc, offset32, ret_buf, 0); /* Advance to the next dword. */ offset32 += 4; ret_buf += 4; len32 -= 4; } if (rc) return rc; cmd_flags = BCE_NVM_COMMAND_LAST; rc = bce_nvram_read_dword(sc, offset32, buf, cmd_flags); memcpy(ret_buf, buf, 4 - extra); } /* Disable access to flash interface and release the lock. */ bce_disable_nvram_access(sc); bce_release_nvram_lock(sc); return rc; } #ifdef BCE_NVRAM_WRITE_SUPPORT /****************************************************************************/ /* Write an arbitrary range of data from NVRAM. */ /* */ /* Prepares the NVRAM interface for write access and writes the requested */ /* data from the supplied buffer. The caller is responsible for */ /* calculating any appropriate CRCs. */ /* */ /* Returns: */ /* 0 on success, positive value on failure. */ /****************************************************************************/ static int bce_nvram_write(struct bce_softc *sc, u32 offset, u8 *data_buf, int buf_size) { u32 written, offset32, len32; u8 *buf, start[4], end[4]; int rc = 0; int align_start, align_end; buf = data_buf; offset32 = offset; len32 = buf_size; align_start = align_end = 0; if ((align_start = (offset32 & 3))) { offset32 &= ~3; len32 += align_start; if ((rc = bce_nvram_read(sc, offset32, start, 4))) return rc; } if (len32 & 3) { if ((len32 > 4) || !align_start) { align_end = 4 - (len32 & 3); len32 += align_end; if ((rc = bce_nvram_read(sc, offset32 + len32 - 4, end, 4))) { return rc; } } } if (align_start || align_end) { buf = malloc(len32, M_DEVBUF, M_NOWAIT); if (buf == 0) return ENOMEM; if (align_start) { memcpy(buf, start, 4); } if (align_end) { memcpy(buf + len32 - 4, end, 4); } memcpy(buf + align_start, data_buf, buf_size); } written = 0; while ((written < len32) && (rc == 0)) { u32 page_start, page_end, data_start, data_end; u32 addr, cmd_flags; int i; u8 flash_buffer[264]; /* Find the page_start addr */ page_start = offset32 + written; page_start -= (page_start % sc->bce_flash_info->page_size); /* Find the page_end addr */ page_end = page_start + sc->bce_flash_info->page_size; /* Find the data_start addr */ data_start = (written == 0) ? offset32 : page_start; /* Find the data_end addr */ data_end = (page_end > offset32 + len32) ? (offset32 + len32) : page_end; /* Request access to the flash interface. */ if ((rc = bce_acquire_nvram_lock(sc)) != 0) goto nvram_write_end; /* Enable access to flash interface */ bce_enable_nvram_access(sc); cmd_flags = BCE_NVM_COMMAND_FIRST; if (sc->bce_flash_info->buffered == 0) { int j; /* Read the whole page into the buffer * (non-buffer flash only) */ for (j = 0; j < sc->bce_flash_info->page_size; j += 4) { if (j == (sc->bce_flash_info->page_size - 4)) { cmd_flags |= BCE_NVM_COMMAND_LAST; } rc = bce_nvram_read_dword(sc, page_start + j, &flash_buffer[j], cmd_flags); if (rc) goto nvram_write_end; cmd_flags = 0; } } /* Enable writes to flash interface (unlock write-protect) */ if ((rc = bce_enable_nvram_write(sc)) != 0) goto nvram_write_end; /* Erase the page */ if ((rc = bce_nvram_erase_page(sc, page_start)) != 0) goto nvram_write_end; /* Re-enable the write again for the actual write */ bce_enable_nvram_write(sc); /* Loop to write back the buffer data from page_start to * data_start */ i = 0; if (sc->bce_flash_info->buffered == 0) { for (addr = page_start; addr < data_start; addr += 4, i += 4) { rc = bce_nvram_write_dword(sc, addr, &flash_buffer[i], cmd_flags); if (rc != 0) goto nvram_write_end; cmd_flags = 0; } } /* Loop to write the new data from data_start to data_end */ for (addr = data_start; addr < data_end; addr += 4, i++) { if ((addr == page_end - 4) || ((sc->bce_flash_info->buffered) && (addr == data_end - 4))) { cmd_flags |= BCE_NVM_COMMAND_LAST; } rc = bce_nvram_write_dword(sc, addr, buf, cmd_flags); if (rc != 0) goto nvram_write_end; cmd_flags = 0; buf += 4; } /* Loop to write back the buffer data from data_end * to page_end */ if (sc->bce_flash_info->buffered == 0) { for (addr = data_end; addr < page_end; addr += 4, i += 4) { if (addr == page_end-4) { cmd_flags = BCE_NVM_COMMAND_LAST; } rc = bce_nvram_write_dword(sc, addr, &flash_buffer[i], cmd_flags); if (rc != 0) goto nvram_write_end; cmd_flags = 0; } } /* Disable writes to flash interface (lock write-protect) */ bce_disable_nvram_write(sc); /* Disable access to flash interface */ bce_disable_nvram_access(sc); bce_release_nvram_lock(sc); /* Increment written */ written += data_end - data_start; } nvram_write_end: if (align_start || align_end) free(buf, M_DEVBUF); return rc; } #endif /* BCE_NVRAM_WRITE_SUPPORT */ /****************************************************************************/ /* Verifies that NVRAM is accessible and contains valid data. */ /* */ /* Reads the configuration data from NVRAM and verifies that the CRC is */ /* correct. */ /* */ /* Returns: */ /* 0 on success, positive value on failure. */ /****************************************************************************/ static int bce_nvram_test(struct bce_softc *sc) { u32 buf[BCE_NVRAM_SIZE / 4]; u8 *data = (u8 *) buf; int rc = 0; u32 magic, csum; /* * Check that the device NVRAM is valid by reading * the magic value at offset 0. */ if ((rc = bce_nvram_read(sc, 0, data, 4)) != 0) goto bce_nvram_test_done; magic = bce_be32toh(buf[0]); if (magic != BCE_NVRAM_MAGIC) { rc = ENODEV; BCE_PRINTF(sc, "%s(%d): Invalid NVRAM magic value! Expected: 0x%08X, " "Found: 0x%08X\n", __FILE__, __LINE__, BCE_NVRAM_MAGIC, magic); goto bce_nvram_test_done; } /* * Verify that the device NVRAM includes valid * configuration data. */ if ((rc = bce_nvram_read(sc, 0x100, data, BCE_NVRAM_SIZE)) != 0) goto bce_nvram_test_done; csum = ether_crc32_le(data, 0x100); if (csum != BCE_CRC32_RESIDUAL) { rc = ENODEV; BCE_PRINTF(sc, "%s(%d): Invalid Manufacturing Information NVRAM CRC! " "Expected: 0x%08X, Found: 0x%08X\n", __FILE__, __LINE__, BCE_CRC32_RESIDUAL, csum); goto bce_nvram_test_done; } csum = ether_crc32_le(data + 0x100, 0x100); if (csum != BCE_CRC32_RESIDUAL) { BCE_PRINTF(sc, "%s(%d): Invalid Feature Configuration Information " "NVRAM CRC! Expected: 0x%08X, Found: 08%08X\n", __FILE__, __LINE__, BCE_CRC32_RESIDUAL, csum); rc = ENODEV; } bce_nvram_test_done: return rc; } /****************************************************************************/ /* Free any DMA memory owned by the driver. */ /* */ /* Scans through each data structre that requires DMA memory and frees */ /* the memory if allocated. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_dma_free(struct bce_softc *sc) { int i; DBPRINT(sc,BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); /* Destroy the status block. */ if (sc->status_block != NULL) bus_dmamem_free( sc->status_tag, sc->status_block, sc->status_map); if (sc->status_map != NULL) { bus_dmamap_unload( sc->status_tag, sc->status_map); bus_dmamap_destroy(sc->status_tag, sc->status_map); } if (sc->status_tag != NULL) bus_dma_tag_destroy(sc->status_tag); /* Destroy the statistics block. */ if (sc->stats_block != NULL) bus_dmamem_free( sc->stats_tag, sc->stats_block, sc->stats_map); if (sc->stats_map != NULL) { bus_dmamap_unload( sc->stats_tag, sc->stats_map); bus_dmamap_destroy(sc->stats_tag, sc->stats_map); } if (sc->stats_tag != NULL) bus_dma_tag_destroy(sc->stats_tag); /* Free, unmap and destroy all TX buffer descriptor chain pages. */ for (i = 0; i < TX_PAGES; i++ ) { if (sc->tx_bd_chain[i] != NULL) bus_dmamem_free( sc->tx_bd_chain_tag, sc->tx_bd_chain[i], sc->tx_bd_chain_map[i]); if (sc->tx_bd_chain_map[i] != NULL) { bus_dmamap_unload( sc->tx_bd_chain_tag, sc->tx_bd_chain_map[i]); bus_dmamap_destroy( sc->tx_bd_chain_tag, sc->tx_bd_chain_map[i]); } } /* Destroy the TX buffer descriptor tag. */ if (sc->tx_bd_chain_tag != NULL) bus_dma_tag_destroy(sc->tx_bd_chain_tag); /* Free, unmap and destroy all RX buffer descriptor chain pages. */ for (i = 0; i < RX_PAGES; i++ ) { if (sc->rx_bd_chain[i] != NULL) bus_dmamem_free( sc->rx_bd_chain_tag, sc->rx_bd_chain[i], sc->rx_bd_chain_map[i]); if (sc->rx_bd_chain_map[i] != NULL) { bus_dmamap_unload( sc->rx_bd_chain_tag, sc->rx_bd_chain_map[i]); bus_dmamap_destroy( sc->rx_bd_chain_tag, sc->rx_bd_chain_map[i]); } } /* Destroy the RX buffer descriptor tag. */ if (sc->rx_bd_chain_tag != NULL) bus_dma_tag_destroy(sc->rx_bd_chain_tag); /* Unload and destroy the TX mbuf maps. */ for (i = 0; i < TOTAL_TX_BD; i++) { if (sc->tx_mbuf_map[i] != NULL) { bus_dmamap_unload(sc->tx_mbuf_tag, sc->tx_mbuf_map[i]); bus_dmamap_destroy(sc->tx_mbuf_tag, sc->tx_mbuf_map[i]); } } /* Destroy the TX mbuf tag. */ if (sc->tx_mbuf_tag != NULL) bus_dma_tag_destroy(sc->tx_mbuf_tag); /* Unload and destroy the RX mbuf maps. */ for (i = 0; i < TOTAL_RX_BD; i++) { if (sc->rx_mbuf_map[i] != NULL) { bus_dmamap_unload(sc->rx_mbuf_tag, sc->rx_mbuf_map[i]); bus_dmamap_destroy(sc->rx_mbuf_tag, sc->rx_mbuf_map[i]); } } /* Destroy the RX mbuf tag. */ if (sc->rx_mbuf_tag != NULL) bus_dma_tag_destroy(sc->rx_mbuf_tag); /* Destroy the parent tag */ if (sc->parent_tag != NULL) bus_dma_tag_destroy(sc->parent_tag); DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); } /****************************************************************************/ /* Get DMA memory from the OS. */ /* */ /* Validates that the OS has provided DMA buffers in response to a */ /* bus_dmamap_load() call and saves the physical address of those buffers. */ /* When the callback is used the OS will return 0 for the mapping function */ /* (bus_dmamap_load()) so we use the value of map_arg->maxsegs to pass any */ /* failures back to the caller. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_dma_map_addr(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct bce_dmamap_arg *map_arg = arg; struct bce_softc *sc = map_arg->sc; /* Simulate a mapping failure. */ DBRUNIF(DB_RANDOMTRUE(bce_debug_dma_map_addr_failure), BCE_PRINTF(sc, "%s(%d): Simulating DMA mapping error.\n", __FILE__, __LINE__); error = ENOMEM); /* Check for an error and signal the caller that an error occurred. */ if (error || (nseg > map_arg->maxsegs)) { BCE_PRINTF(sc, "%s(%d): DMA mapping error! error = %d, " "nseg = %d, maxsegs = %d\n", __FILE__, __LINE__, error, nseg, map_arg->maxsegs); map_arg->maxsegs = 0; goto bce_dma_map_addr_exit; } map_arg->busaddr = segs->ds_addr; bce_dma_map_addr_exit: return; } /****************************************************************************/ /* Map TX buffers into TX buffer descriptors. */ /* */ /* Given a series of DMA memory containting an outgoing frame, map the */ /* segments into the tx_bd structure used by the hardware. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_dma_map_tx_desc(void *arg, bus_dma_segment_t *segs, int nseg, bus_size_t mapsize, int error) { struct bce_dmamap_arg *map_arg; struct bce_softc *sc; struct tx_bd *txbd = NULL; int i = 0; u16 prod, chain_prod; u32 prod_bseq; #ifdef BCE_DEBUG u16 debug_prod; #endif map_arg = arg; sc = map_arg->sc; if (error) { DBPRINT(sc, BCE_WARN, "%s(): Called with error = %d\n", __FUNCTION__, error); return; } /* Signal error to caller if there's too many segments */ if (nseg > map_arg->maxsegs) { DBPRINT(sc, BCE_WARN, "%s(): Mapped TX descriptors: max segs = %d, " "actual segs = %d\n", __FUNCTION__, map_arg->maxsegs, nseg); map_arg->maxsegs = 0; return; } /* prod points to an empty tx_bd at this point. */ prod = map_arg->prod; chain_prod = map_arg->chain_prod; prod_bseq = map_arg->prod_bseq; #ifdef BCE_DEBUG debug_prod = chain_prod; #endif DBPRINT(sc, BCE_INFO_SEND, "%s(): Start: prod = 0x%04X, chain_prod = %04X, " "prod_bseq = 0x%08X\n", __FUNCTION__, prod, chain_prod, prod_bseq); /* * Cycle through each mbuf segment that makes up * the outgoing frame, gathering the mapping info * for that segment and creating a tx_bd to for * the mbuf. */ txbd = &map_arg->tx_chain[TX_PAGE(chain_prod)][TX_IDX(chain_prod)]; /* Setup the first tx_bd for the first segment. */ txbd->tx_bd_haddr_lo = htole32(BCE_ADDR_LO(segs[i].ds_addr)); txbd->tx_bd_haddr_hi = htole32(BCE_ADDR_HI(segs[i].ds_addr)); txbd->tx_bd_mss_nbytes = htole16(segs[i].ds_len); txbd->tx_bd_vlan_tag_flags = htole16(map_arg->tx_flags | TX_BD_FLAGS_START); prod_bseq += segs[i].ds_len; /* Setup any remaing segments. */ for (i = 1; i < nseg; i++) { prod = NEXT_TX_BD(prod); chain_prod = TX_CHAIN_IDX(prod); txbd = &map_arg->tx_chain[TX_PAGE(chain_prod)][TX_IDX(chain_prod)]; txbd->tx_bd_haddr_lo = htole32(BCE_ADDR_LO(segs[i].ds_addr)); txbd->tx_bd_haddr_hi = htole32(BCE_ADDR_HI(segs[i].ds_addr)); txbd->tx_bd_mss_nbytes = htole16(segs[i].ds_len); txbd->tx_bd_vlan_tag_flags = htole16(map_arg->tx_flags); prod_bseq += segs[i].ds_len; } /* Set the END flag on the last TX buffer descriptor. */ txbd->tx_bd_vlan_tag_flags |= htole16(TX_BD_FLAGS_END); DBRUN(BCE_INFO_SEND, bce_dump_tx_chain(sc, debug_prod, nseg)); DBPRINT(sc, BCE_INFO_SEND, "%s(): End: prod = 0x%04X, chain_prod = %04X, " "prod_bseq = 0x%08X\n", __FUNCTION__, prod, chain_prod, prod_bseq); /* prod points to the last tx_bd at this point. */ map_arg->maxsegs = nseg; map_arg->prod = prod; map_arg->chain_prod = chain_prod; map_arg->prod_bseq = prod_bseq; } /****************************************************************************/ /* Allocate any DMA memory needed by the driver. */ /* */ /* Allocates DMA memory needed for the various global structures needed by */ /* hardware. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_dma_alloc(device_t dev) { struct bce_softc *sc; int i, error, rc = 0; struct bce_dmamap_arg map_arg; sc = device_get_softc(dev); DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); /* * Allocate the parent bus DMA tag appropriate for PCI. */ if (bus_dma_tag_create(NULL, /* parent */ BCE_DMA_ALIGN, /* alignment */ BCE_DMA_BOUNDARY, /* boundary */ sc->max_bus_addr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, /* filterfunc */ NULL, /* filterarg */ MAXBSIZE, /* maxsize */ BUS_SPACE_UNRESTRICTED, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, /* locfunc */ NULL, /* lockarg */ &sc->parent_tag)) { BCE_PRINTF(sc, "%s(%d): Could not allocate parent DMA tag!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } /* * Create a DMA tag for the status block, allocate and clear the * memory, map the memory into DMA space, and fetch the physical * address of the block. */ if (bus_dma_tag_create( sc->parent_tag, /* parent */ BCE_DMA_ALIGN, /* alignment */ BCE_DMA_BOUNDARY, /* boundary */ sc->max_bus_addr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, /* filterfunc */ NULL, /* filterarg */ BCE_STATUS_BLK_SZ, /* maxsize */ 1, /* nsegments */ BCE_STATUS_BLK_SZ, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->status_tag)) { BCE_PRINTF(sc, "%s(%d): Could not allocate status block DMA tag!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } if(bus_dmamem_alloc( sc->status_tag, /* dmat */ (void **)&sc->status_block, /* vaddr */ BUS_DMA_NOWAIT, /* flags */ &sc->status_map)) { BCE_PRINTF(sc, "%s(%d): Could not allocate status block DMA memory!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } bzero((char *)sc->status_block, BCE_STATUS_BLK_SZ); map_arg.sc = sc; map_arg.maxsegs = 1; error = bus_dmamap_load( sc->status_tag, /* dmat */ sc->status_map, /* map */ sc->status_block, /* buf */ BCE_STATUS_BLK_SZ, /* buflen */ bce_dma_map_addr, /* callback */ &map_arg, /* callbackarg */ BUS_DMA_NOWAIT); /* flags */ if(error || (map_arg.maxsegs == 0)) { BCE_PRINTF(sc, "%s(%d): Could not map status block DMA memory!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } sc->status_block_paddr = map_arg.busaddr; /* DRC - Fix for 64 bit addresses. */ DBPRINT(sc, BCE_INFO, "status_block_paddr = 0x%08X\n", (u32) sc->status_block_paddr); /* * Create a DMA tag for the statistics block, allocate and clear the * memory, map the memory into DMA space, and fetch the physical * address of the block. */ if (bus_dma_tag_create( sc->parent_tag, /* parent */ BCE_DMA_ALIGN, /* alignment */ BCE_DMA_BOUNDARY, /* boundary */ sc->max_bus_addr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, /* filterfunc */ NULL, /* filterarg */ BCE_STATS_BLK_SZ, /* maxsize */ 1, /* nsegments */ BCE_STATS_BLK_SZ, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->stats_tag)) { BCE_PRINTF(sc, "%s(%d): Could not allocate statistics block DMA tag!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } if (bus_dmamem_alloc( sc->stats_tag, /* dmat */ (void **)&sc->stats_block, /* vaddr */ BUS_DMA_NOWAIT, /* flags */ &sc->stats_map)) { BCE_PRINTF(sc, "%s(%d): Could not allocate statistics block DMA memory!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } bzero((char *)sc->stats_block, BCE_STATS_BLK_SZ); map_arg.sc = sc; map_arg.maxsegs = 1; error = bus_dmamap_load( sc->stats_tag, /* dmat */ sc->stats_map, /* map */ sc->stats_block, /* buf */ BCE_STATS_BLK_SZ, /* buflen */ bce_dma_map_addr, /* callback */ &map_arg, /* callbackarg */ BUS_DMA_NOWAIT); /* flags */ if(error || (map_arg.maxsegs == 0)) { BCE_PRINTF(sc, "%s(%d): Could not map statistics block DMA memory!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } sc->stats_block_paddr = map_arg.busaddr; /* DRC - Fix for 64 bit address. */ DBPRINT(sc,BCE_INFO, "stats_block_paddr = 0x%08X\n", (u32) sc->stats_block_paddr); /* * Create a DMA tag for the TX buffer descriptor chain, * allocate and clear the memory, and fetch the * physical address of the block. */ if(bus_dma_tag_create( sc->parent_tag, /* parent */ BCM_PAGE_SIZE, /* alignment */ BCE_DMA_BOUNDARY, /* boundary */ sc->max_bus_addr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, /* filterfunc */ NULL, /* filterarg */ BCE_TX_CHAIN_PAGE_SZ, /* maxsize */ 1, /* nsegments */ BCE_TX_CHAIN_PAGE_SZ, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->tx_bd_chain_tag)) { BCE_PRINTF(sc, "%s(%d): Could not allocate TX descriptor chain DMA tag!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } for (i = 0; i < TX_PAGES; i++) { if(bus_dmamem_alloc( sc->tx_bd_chain_tag, /* tag */ (void **)&sc->tx_bd_chain[i], /* vaddr */ BUS_DMA_NOWAIT, /* flags */ &sc->tx_bd_chain_map[i])) { BCE_PRINTF(sc, "%s(%d): Could not allocate TX descriptor " "chain DMA memory!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } map_arg.maxsegs = 1; map_arg.sc = sc; error = bus_dmamap_load( sc->tx_bd_chain_tag, /* dmat */ sc->tx_bd_chain_map[i], /* map */ sc->tx_bd_chain[i], /* buf */ BCE_TX_CHAIN_PAGE_SZ, /* buflen */ bce_dma_map_addr, /* callback */ &map_arg, /* callbackarg */ BUS_DMA_NOWAIT); /* flags */ if(error || (map_arg.maxsegs == 0)) { BCE_PRINTF(sc, "%s(%d): Could not map TX descriptor chain DMA memory!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } sc->tx_bd_chain_paddr[i] = map_arg.busaddr; /* DRC - Fix for 64 bit systems. */ DBPRINT(sc, BCE_INFO, "tx_bd_chain_paddr[%d] = 0x%08X\n", i, (u32) sc->tx_bd_chain_paddr[i]); } /* Create a DMA tag for TX mbufs. */ if (bus_dma_tag_create( sc->parent_tag, /* parent */ BCE_DMA_ALIGN, /* alignment */ BCE_DMA_BOUNDARY, /* boundary */ sc->max_bus_addr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, /* filterfunc */ NULL, /* filterarg */ MCLBYTES * BCE_MAX_SEGMENTS, /* maxsize */ BCE_MAX_SEGMENTS, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->tx_mbuf_tag)) { BCE_PRINTF(sc, "%s(%d): Could not allocate TX mbuf DMA tag!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } /* Create DMA maps for the TX mbufs clusters. */ for (i = 0; i < TOTAL_TX_BD; i++) { if (bus_dmamap_create(sc->tx_mbuf_tag, BUS_DMA_NOWAIT, &sc->tx_mbuf_map[i])) { BCE_PRINTF(sc, "%s(%d): Unable to create TX mbuf DMA map!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } } /* * Create a DMA tag for the RX buffer descriptor chain, * allocate and clear the memory, and fetch the physical * address of the blocks. */ if (bus_dma_tag_create( sc->parent_tag, /* parent */ BCM_PAGE_SIZE, /* alignment */ BCE_DMA_BOUNDARY, /* boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ sc->max_bus_addr, /* lowaddr */ NULL, /* filter */ NULL, /* filterarg */ BCE_RX_CHAIN_PAGE_SZ, /* maxsize */ 1, /* nsegments */ BCE_RX_CHAIN_PAGE_SZ, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->rx_bd_chain_tag)) { BCE_PRINTF(sc, "%s(%d): Could not allocate RX descriptor chain DMA tag!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } for (i = 0; i < RX_PAGES; i++) { if (bus_dmamem_alloc( sc->rx_bd_chain_tag, /* tag */ (void **)&sc->rx_bd_chain[i], /* vaddr */ BUS_DMA_NOWAIT, /* flags */ &sc->rx_bd_chain_map[i])) { BCE_PRINTF(sc, "%s(%d): Could not allocate RX descriptor chain " "DMA memory!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } bzero((char *)sc->rx_bd_chain[i], BCE_RX_CHAIN_PAGE_SZ); map_arg.maxsegs = 1; map_arg.sc = sc; error = bus_dmamap_load( sc->rx_bd_chain_tag, /* dmat */ sc->rx_bd_chain_map[i], /* map */ sc->rx_bd_chain[i], /* buf */ BCE_RX_CHAIN_PAGE_SZ, /* buflen */ bce_dma_map_addr, /* callback */ &map_arg, /* callbackarg */ BUS_DMA_NOWAIT); /* flags */ if(error || (map_arg.maxsegs == 0)) { BCE_PRINTF(sc, "%s(%d): Could not map RX descriptor chain DMA memory!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } sc->rx_bd_chain_paddr[i] = map_arg.busaddr; /* DRC - Fix for 64 bit systems. */ DBPRINT(sc, BCE_INFO, "rx_bd_chain_paddr[%d] = 0x%08X\n", i, (u32) sc->rx_bd_chain_paddr[i]); } /* * Create a DMA tag for RX mbufs. */ if (bus_dma_tag_create( sc->parent_tag, /* parent */ BCE_DMA_ALIGN, /* alignment */ BCE_DMA_BOUNDARY, /* boundary */ sc->max_bus_addr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, /* filterfunc */ NULL, /* filterarg */ MJUM9BYTES, /* maxsize */ BCE_MAX_SEGMENTS, /* nsegments */ MJUM9BYTES, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->rx_mbuf_tag)) { BCE_PRINTF(sc, "%s(%d): Could not allocate RX mbuf DMA tag!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } /* Create DMA maps for the RX mbuf clusters. */ for (i = 0; i < TOTAL_RX_BD; i++) { if (bus_dmamap_create(sc->rx_mbuf_tag, BUS_DMA_NOWAIT, &sc->rx_mbuf_map[i])) { BCE_PRINTF(sc, "%s(%d): Unable to create RX mbuf DMA map!\n", __FILE__, __LINE__); rc = ENOMEM; goto bce_dma_alloc_exit; } } bce_dma_alloc_exit: DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return(rc); } /****************************************************************************/ /* Release all resources used by the driver. */ /* */ /* Releases all resources acquired by the driver including interrupts, */ /* interrupt handler, interfaces, mutexes, and DMA memory. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_release_resources(struct bce_softc *sc) { device_t dev; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); dev = sc->bce_dev; bce_dma_free(sc); if (sc->bce_intrhand != NULL) bus_teardown_intr(dev, sc->bce_irq, sc->bce_intrhand); if (sc->bce_irq != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->bce_irq); if (sc->bce_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(0), sc->bce_res); if (sc->bce_ifp != NULL) if_free(sc->bce_ifp); if (mtx_initialized(&sc->bce_mtx)) BCE_LOCK_DESTROY(sc); DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); } /****************************************************************************/ /* Firmware synchronization. */ /* */ /* Before performing certain events such as a chip reset, synchronize with */ /* the firmware first. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_fw_sync(struct bce_softc *sc, u32 msg_data) { int i, rc = 0; u32 val; /* Don't waste any time if we've timed out before. */ if (sc->bce_fw_timed_out) { rc = EBUSY; goto bce_fw_sync_exit; } /* Increment the message sequence number. */ sc->bce_fw_wr_seq++; msg_data |= sc->bce_fw_wr_seq; DBPRINT(sc, BCE_VERBOSE, "bce_fw_sync(): msg_data = 0x%08X\n", msg_data); /* Send the message to the bootcode driver mailbox. */ REG_WR_IND(sc, sc->bce_shmem_base + BCE_DRV_MB, msg_data); /* Wait for the bootcode to acknowledge the message. */ for (i = 0; i < FW_ACK_TIME_OUT_MS; i++) { /* Check for a response in the bootcode firmware mailbox. */ val = REG_RD_IND(sc, sc->bce_shmem_base + BCE_FW_MB); if ((val & BCE_FW_MSG_ACK) == (msg_data & BCE_DRV_MSG_SEQ)) break; DELAY(1000); } /* If we've timed out, tell the bootcode that we've stopped waiting. */ if (((val & BCE_FW_MSG_ACK) != (msg_data & BCE_DRV_MSG_SEQ)) && ((msg_data & BCE_DRV_MSG_DATA) != BCE_DRV_MSG_DATA_WAIT0)) { BCE_PRINTF(sc, "%s(%d): Firmware synchronization timeout! " "msg_data = 0x%08X\n", __FILE__, __LINE__, msg_data); msg_data &= ~BCE_DRV_MSG_CODE; msg_data |= BCE_DRV_MSG_CODE_FW_TIMEOUT; REG_WR_IND(sc, sc->bce_shmem_base + BCE_DRV_MB, msg_data); sc->bce_fw_timed_out = 1; rc = EBUSY; } bce_fw_sync_exit: return (rc); } /****************************************************************************/ /* Load Receive Virtual 2 Physical (RV2P) processor firmware. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_load_rv2p_fw(struct bce_softc *sc, u32 *rv2p_code, u32 rv2p_code_len, u32 rv2p_proc) { int i; u32 val; for (i = 0; i < rv2p_code_len; i += 8) { REG_WR(sc, BCE_RV2P_INSTR_HIGH, *rv2p_code); rv2p_code++; REG_WR(sc, BCE_RV2P_INSTR_LOW, *rv2p_code); rv2p_code++; if (rv2p_proc == RV2P_PROC1) { val = (i / 8) | BCE_RV2P_PROC1_ADDR_CMD_RDWR; REG_WR(sc, BCE_RV2P_PROC1_ADDR_CMD, val); } else { val = (i / 8) | BCE_RV2P_PROC2_ADDR_CMD_RDWR; REG_WR(sc, BCE_RV2P_PROC2_ADDR_CMD, val); } } /* Reset the processor, un-stall is done later. */ if (rv2p_proc == RV2P_PROC1) { REG_WR(sc, BCE_RV2P_COMMAND, BCE_RV2P_COMMAND_PROC1_RESET); } else { REG_WR(sc, BCE_RV2P_COMMAND, BCE_RV2P_COMMAND_PROC2_RESET); } } /****************************************************************************/ /* Load RISC processor firmware. */ /* */ /* Loads firmware from the file if_bcefw.h into the scratchpad memory */ /* associated with a particular processor. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_load_cpu_fw(struct bce_softc *sc, struct cpu_reg *cpu_reg, struct fw_info *fw) { u32 offset; u32 val; /* Halt the CPU. */ val = REG_RD_IND(sc, cpu_reg->mode); val |= cpu_reg->mode_value_halt; REG_WR_IND(sc, cpu_reg->mode, val); REG_WR_IND(sc, cpu_reg->state, cpu_reg->state_value_clear); /* Load the Text area. */ offset = cpu_reg->spad_base + (fw->text_addr - cpu_reg->mips_view_base); if (fw->text) { int j; for (j = 0; j < (fw->text_len / 4); j++, offset += 4) { REG_WR_IND(sc, offset, fw->text[j]); } } /* Load the Data area. */ offset = cpu_reg->spad_base + (fw->data_addr - cpu_reg->mips_view_base); if (fw->data) { int j; for (j = 0; j < (fw->data_len / 4); j++, offset += 4) { REG_WR_IND(sc, offset, fw->data[j]); } } /* Load the SBSS area. */ offset = cpu_reg->spad_base + (fw->sbss_addr - cpu_reg->mips_view_base); if (fw->sbss) { int j; for (j = 0; j < (fw->sbss_len / 4); j++, offset += 4) { REG_WR_IND(sc, offset, fw->sbss[j]); } } /* Load the BSS area. */ offset = cpu_reg->spad_base + (fw->bss_addr - cpu_reg->mips_view_base); if (fw->bss) { int j; for (j = 0; j < (fw->bss_len/4); j++, offset += 4) { REG_WR_IND(sc, offset, fw->bss[j]); } } /* Load the Read-Only area. */ offset = cpu_reg->spad_base + (fw->rodata_addr - cpu_reg->mips_view_base); if (fw->rodata) { int j; for (j = 0; j < (fw->rodata_len / 4); j++, offset += 4) { REG_WR_IND(sc, offset, fw->rodata[j]); } } /* Clear the pre-fetch instruction. */ REG_WR_IND(sc, cpu_reg->inst, 0); REG_WR_IND(sc, cpu_reg->pc, fw->start_addr); /* Start the CPU. */ val = REG_RD_IND(sc, cpu_reg->mode); val &= ~cpu_reg->mode_value_halt; REG_WR_IND(sc, cpu_reg->state, cpu_reg->state_value_clear); REG_WR_IND(sc, cpu_reg->mode, val); } /****************************************************************************/ /* Initialize the RV2P, RX, TX, TPAT, and COM CPUs. */ /* */ /* Loads the firmware for each CPU and starts the CPU. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_init_cpus(struct bce_softc *sc) { struct cpu_reg cpu_reg; struct fw_info fw; /* Initialize the RV2P processor. */ bce_load_rv2p_fw(sc, bce_rv2p_proc1, sizeof(bce_rv2p_proc1), RV2P_PROC1); bce_load_rv2p_fw(sc, bce_rv2p_proc2, sizeof(bce_rv2p_proc2), RV2P_PROC2); /* Initialize the RX Processor. */ cpu_reg.mode = BCE_RXP_CPU_MODE; cpu_reg.mode_value_halt = BCE_RXP_CPU_MODE_SOFT_HALT; cpu_reg.mode_value_sstep = BCE_RXP_CPU_MODE_STEP_ENA; cpu_reg.state = BCE_RXP_CPU_STATE; cpu_reg.state_value_clear = 0xffffff; cpu_reg.gpr0 = BCE_RXP_CPU_REG_FILE; cpu_reg.evmask = BCE_RXP_CPU_EVENT_MASK; cpu_reg.pc = BCE_RXP_CPU_PROGRAM_COUNTER; cpu_reg.inst = BCE_RXP_CPU_INSTRUCTION; cpu_reg.bp = BCE_RXP_CPU_HW_BREAKPOINT; cpu_reg.spad_base = BCE_RXP_SCRATCH; cpu_reg.mips_view_base = 0x8000000; fw.ver_major = bce_RXP_b06FwReleaseMajor; fw.ver_minor = bce_RXP_b06FwReleaseMinor; fw.ver_fix = bce_RXP_b06FwReleaseFix; fw.start_addr = bce_RXP_b06FwStartAddr; fw.text_addr = bce_RXP_b06FwTextAddr; fw.text_len = bce_RXP_b06FwTextLen; fw.text_index = 0; fw.text = bce_RXP_b06FwText; fw.data_addr = bce_RXP_b06FwDataAddr; fw.data_len = bce_RXP_b06FwDataLen; fw.data_index = 0; fw.data = bce_RXP_b06FwData; fw.sbss_addr = bce_RXP_b06FwSbssAddr; fw.sbss_len = bce_RXP_b06FwSbssLen; fw.sbss_index = 0; fw.sbss = bce_RXP_b06FwSbss; fw.bss_addr = bce_RXP_b06FwBssAddr; fw.bss_len = bce_RXP_b06FwBssLen; fw.bss_index = 0; fw.bss = bce_RXP_b06FwBss; fw.rodata_addr = bce_RXP_b06FwRodataAddr; fw.rodata_len = bce_RXP_b06FwRodataLen; fw.rodata_index = 0; fw.rodata = bce_RXP_b06FwRodata; DBPRINT(sc, BCE_INFO_RESET, "Loading RX firmware.\n"); bce_load_cpu_fw(sc, &cpu_reg, &fw); /* Initialize the TX Processor. */ cpu_reg.mode = BCE_TXP_CPU_MODE; cpu_reg.mode_value_halt = BCE_TXP_CPU_MODE_SOFT_HALT; cpu_reg.mode_value_sstep = BCE_TXP_CPU_MODE_STEP_ENA; cpu_reg.state = BCE_TXP_CPU_STATE; cpu_reg.state_value_clear = 0xffffff; cpu_reg.gpr0 = BCE_TXP_CPU_REG_FILE; cpu_reg.evmask = BCE_TXP_CPU_EVENT_MASK; cpu_reg.pc = BCE_TXP_CPU_PROGRAM_COUNTER; cpu_reg.inst = BCE_TXP_CPU_INSTRUCTION; cpu_reg.bp = BCE_TXP_CPU_HW_BREAKPOINT; cpu_reg.spad_base = BCE_TXP_SCRATCH; cpu_reg.mips_view_base = 0x8000000; fw.ver_major = bce_TXP_b06FwReleaseMajor; fw.ver_minor = bce_TXP_b06FwReleaseMinor; fw.ver_fix = bce_TXP_b06FwReleaseFix; fw.start_addr = bce_TXP_b06FwStartAddr; fw.text_addr = bce_TXP_b06FwTextAddr; fw.text_len = bce_TXP_b06FwTextLen; fw.text_index = 0; fw.text = bce_TXP_b06FwText; fw.data_addr = bce_TXP_b06FwDataAddr; fw.data_len = bce_TXP_b06FwDataLen; fw.data_index = 0; fw.data = bce_TXP_b06FwData; fw.sbss_addr = bce_TXP_b06FwSbssAddr; fw.sbss_len = bce_TXP_b06FwSbssLen; fw.sbss_index = 0; fw.sbss = bce_TXP_b06FwSbss; fw.bss_addr = bce_TXP_b06FwBssAddr; fw.bss_len = bce_TXP_b06FwBssLen; fw.bss_index = 0; fw.bss = bce_TXP_b06FwBss; fw.rodata_addr = bce_TXP_b06FwRodataAddr; fw.rodata_len = bce_TXP_b06FwRodataLen; fw.rodata_index = 0; fw.rodata = bce_TXP_b06FwRodata; DBPRINT(sc, BCE_INFO_RESET, "Loading TX firmware.\n"); bce_load_cpu_fw(sc, &cpu_reg, &fw); /* Initialize the TX Patch-up Processor. */ cpu_reg.mode = BCE_TPAT_CPU_MODE; cpu_reg.mode_value_halt = BCE_TPAT_CPU_MODE_SOFT_HALT; cpu_reg.mode_value_sstep = BCE_TPAT_CPU_MODE_STEP_ENA; cpu_reg.state = BCE_TPAT_CPU_STATE; cpu_reg.state_value_clear = 0xffffff; cpu_reg.gpr0 = BCE_TPAT_CPU_REG_FILE; cpu_reg.evmask = BCE_TPAT_CPU_EVENT_MASK; cpu_reg.pc = BCE_TPAT_CPU_PROGRAM_COUNTER; cpu_reg.inst = BCE_TPAT_CPU_INSTRUCTION; cpu_reg.bp = BCE_TPAT_CPU_HW_BREAKPOINT; cpu_reg.spad_base = BCE_TPAT_SCRATCH; cpu_reg.mips_view_base = 0x8000000; fw.ver_major = bce_TPAT_b06FwReleaseMajor; fw.ver_minor = bce_TPAT_b06FwReleaseMinor; fw.ver_fix = bce_TPAT_b06FwReleaseFix; fw.start_addr = bce_TPAT_b06FwStartAddr; fw.text_addr = bce_TPAT_b06FwTextAddr; fw.text_len = bce_TPAT_b06FwTextLen; fw.text_index = 0; fw.text = bce_TPAT_b06FwText; fw.data_addr = bce_TPAT_b06FwDataAddr; fw.data_len = bce_TPAT_b06FwDataLen; fw.data_index = 0; fw.data = bce_TPAT_b06FwData; fw.sbss_addr = bce_TPAT_b06FwSbssAddr; fw.sbss_len = bce_TPAT_b06FwSbssLen; fw.sbss_index = 0; fw.sbss = bce_TPAT_b06FwSbss; fw.bss_addr = bce_TPAT_b06FwBssAddr; fw.bss_len = bce_TPAT_b06FwBssLen; fw.bss_index = 0; fw.bss = bce_TPAT_b06FwBss; fw.rodata_addr = bce_TPAT_b06FwRodataAddr; fw.rodata_len = bce_TPAT_b06FwRodataLen; fw.rodata_index = 0; fw.rodata = bce_TPAT_b06FwRodata; DBPRINT(sc, BCE_INFO_RESET, "Loading TPAT firmware.\n"); bce_load_cpu_fw(sc, &cpu_reg, &fw); /* Initialize the Completion Processor. */ cpu_reg.mode = BCE_COM_CPU_MODE; cpu_reg.mode_value_halt = BCE_COM_CPU_MODE_SOFT_HALT; cpu_reg.mode_value_sstep = BCE_COM_CPU_MODE_STEP_ENA; cpu_reg.state = BCE_COM_CPU_STATE; cpu_reg.state_value_clear = 0xffffff; cpu_reg.gpr0 = BCE_COM_CPU_REG_FILE; cpu_reg.evmask = BCE_COM_CPU_EVENT_MASK; cpu_reg.pc = BCE_COM_CPU_PROGRAM_COUNTER; cpu_reg.inst = BCE_COM_CPU_INSTRUCTION; cpu_reg.bp = BCE_COM_CPU_HW_BREAKPOINT; cpu_reg.spad_base = BCE_COM_SCRATCH; cpu_reg.mips_view_base = 0x8000000; fw.ver_major = bce_COM_b06FwReleaseMajor; fw.ver_minor = bce_COM_b06FwReleaseMinor; fw.ver_fix = bce_COM_b06FwReleaseFix; fw.start_addr = bce_COM_b06FwStartAddr; fw.text_addr = bce_COM_b06FwTextAddr; fw.text_len = bce_COM_b06FwTextLen; fw.text_index = 0; fw.text = bce_COM_b06FwText; fw.data_addr = bce_COM_b06FwDataAddr; fw.data_len = bce_COM_b06FwDataLen; fw.data_index = 0; fw.data = bce_COM_b06FwData; fw.sbss_addr = bce_COM_b06FwSbssAddr; fw.sbss_len = bce_COM_b06FwSbssLen; fw.sbss_index = 0; fw.sbss = bce_COM_b06FwSbss; fw.bss_addr = bce_COM_b06FwBssAddr; fw.bss_len = bce_COM_b06FwBssLen; fw.bss_index = 0; fw.bss = bce_COM_b06FwBss; fw.rodata_addr = bce_COM_b06FwRodataAddr; fw.rodata_len = bce_COM_b06FwRodataLen; fw.rodata_index = 0; fw.rodata = bce_COM_b06FwRodata; DBPRINT(sc, BCE_INFO_RESET, "Loading COM firmware.\n"); bce_load_cpu_fw(sc, &cpu_reg, &fw); } /****************************************************************************/ /* Initialize context memory. */ /* */ /* Clears the memory associated with each Context ID (CID). */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_init_context(struct bce_softc *sc) { u32 vcid; vcid = 96; while (vcid) { u32 vcid_addr, pcid_addr, offset; vcid--; vcid_addr = GET_CID_ADDR(vcid); pcid_addr = vcid_addr; REG_WR(sc, BCE_CTX_VIRT_ADDR, 0x00); REG_WR(sc, BCE_CTX_PAGE_TBL, pcid_addr); /* Zero out the context. */ for (offset = 0; offset < PHY_CTX_SIZE; offset += 4) { CTX_WR(sc, 0x00, offset, 0); } REG_WR(sc, BCE_CTX_VIRT_ADDR, vcid_addr); REG_WR(sc, BCE_CTX_PAGE_TBL, pcid_addr); } } /****************************************************************************/ /* Fetch the permanent MAC address of the controller. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_get_mac_addr(struct bce_softc *sc) { u32 mac_lo = 0, mac_hi = 0; /* * The NetXtreme II bootcode populates various NIC * power-on and runtime configuration items in a * shared memory area. The factory configured MAC * address is available from both NVRAM and the * shared memory area so we'll read the value from * shared memory for speed. */ mac_hi = REG_RD_IND(sc, sc->bce_shmem_base + BCE_PORT_HW_CFG_MAC_UPPER); mac_lo = REG_RD_IND(sc, sc->bce_shmem_base + BCE_PORT_HW_CFG_MAC_LOWER); if ((mac_lo == 0) && (mac_hi == 0)) { BCE_PRINTF(sc, "%s(%d): Invalid Ethernet address!\n", __FILE__, __LINE__); } else { sc->eaddr[0] = (u_char)(mac_hi >> 8); sc->eaddr[1] = (u_char)(mac_hi >> 0); sc->eaddr[2] = (u_char)(mac_lo >> 24); sc->eaddr[3] = (u_char)(mac_lo >> 16); sc->eaddr[4] = (u_char)(mac_lo >> 8); sc->eaddr[5] = (u_char)(mac_lo >> 0); } DBPRINT(sc, BCE_INFO, "Permanent Ethernet address = %6D\n", sc->eaddr, ":"); } /****************************************************************************/ /* Program the MAC address. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_set_mac_addr(struct bce_softc *sc) { u32 val; u8 *mac_addr = sc->eaddr; DBPRINT(sc, BCE_INFO, "Setting Ethernet address = %6D\n", sc->eaddr, ":"); val = (mac_addr[0] << 8) | mac_addr[1]; REG_WR(sc, BCE_EMAC_MAC_MATCH0, val); val = (mac_addr[2] << 24) | (mac_addr[3] << 16) | (mac_addr[4] << 8) | mac_addr[5]; REG_WR(sc, BCE_EMAC_MAC_MATCH1, val); } /****************************************************************************/ /* Stop the controller. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_stop(struct bce_softc *sc) { struct ifnet *ifp; struct ifmedia_entry *ifm; struct mii_data *mii = NULL; int mtmp, itmp; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); BCE_LOCK_ASSERT(sc); ifp = sc->bce_ifp; mii = device_get_softc(sc->bce_miibus); callout_stop(&sc->bce_stat_ch); /* Disable the transmit/receive blocks. */ REG_WR(sc, BCE_MISC_ENABLE_CLR_BITS, 0x5ffffff); REG_RD(sc, BCE_MISC_ENABLE_CLR_BITS); DELAY(20); bce_disable_intr(sc); /* Tell firmware that the driver is going away. */ bce_reset(sc, BCE_DRV_MSG_CODE_SUSPEND_NO_WOL); /* Free the RX lists. */ bce_free_rx_chain(sc); /* Free TX buffers. */ bce_free_tx_chain(sc); /* * Isolate/power down the PHY, but leave the media selection * unchanged so that things will be put back to normal when * we bring the interface back up. */ itmp = ifp->if_flags; ifp->if_flags |= IFF_UP; /* * If we are called from bce_detach(), mii is already NULL. */ if (mii != NULL) { ifm = mii->mii_media.ifm_cur; mtmp = ifm->ifm_media; ifm->ifm_media = IFM_ETHER | IFM_NONE; mii_mediachg(mii); ifm->ifm_media = mtmp; } ifp->if_flags = itmp; ifp->if_timer = 0; sc->bce_link = 0; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); } static int bce_reset(struct bce_softc *sc, u32 reset_code) { u32 val; int i, rc = 0; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); /* Wait for pending PCI transactions to complete. */ REG_WR(sc, BCE_MISC_ENABLE_CLR_BITS, BCE_MISC_ENABLE_CLR_BITS_TX_DMA_ENABLE | BCE_MISC_ENABLE_CLR_BITS_DMA_ENGINE_ENABLE | BCE_MISC_ENABLE_CLR_BITS_RX_DMA_ENABLE | BCE_MISC_ENABLE_CLR_BITS_HOST_COALESCE_ENABLE); val = REG_RD(sc, BCE_MISC_ENABLE_CLR_BITS); DELAY(5); /* Assume bootcode is running. */ sc->bce_fw_timed_out = 0; /* Give the firmware a chance to prepare for the reset. */ rc = bce_fw_sync(sc, BCE_DRV_MSG_DATA_WAIT0 | reset_code); if (rc) goto bce_reset_exit; /* Set a firmware reminder that this is a soft reset. */ REG_WR_IND(sc, sc->bce_shmem_base + BCE_DRV_RESET_SIGNATURE, BCE_DRV_RESET_SIGNATURE_MAGIC); /* Dummy read to force the chip to complete all current transactions. */ val = REG_RD(sc, BCE_MISC_ID); /* Chip reset. */ val = BCE_PCICFG_MISC_CONFIG_CORE_RST_REQ | BCE_PCICFG_MISC_CONFIG_REG_WINDOW_ENA | BCE_PCICFG_MISC_CONFIG_TARGET_MB_WORD_SWAP; REG_WR(sc, BCE_PCICFG_MISC_CONFIG, val); /* Allow up to 30us for reset to complete. */ for (i = 0; i < 10; i++) { val = REG_RD(sc, BCE_PCICFG_MISC_CONFIG); if ((val & (BCE_PCICFG_MISC_CONFIG_CORE_RST_REQ | BCE_PCICFG_MISC_CONFIG_CORE_RST_BSY)) == 0) { break; } DELAY(10); } /* Check that reset completed successfully. */ if (val & (BCE_PCICFG_MISC_CONFIG_CORE_RST_REQ | BCE_PCICFG_MISC_CONFIG_CORE_RST_BSY)) { BCE_PRINTF(sc, "%s(%d): Reset failed!\n", __FILE__, __LINE__); rc = EBUSY; goto bce_reset_exit; } /* Make sure byte swapping is properly configured. */ val = REG_RD(sc, BCE_PCI_SWAP_DIAG0); if (val != 0x01020304) { BCE_PRINTF(sc, "%s(%d): Byte swap is incorrect!\n", __FILE__, __LINE__); rc = ENODEV; goto bce_reset_exit; } /* Just completed a reset, assume that firmware is running again. */ sc->bce_fw_timed_out = 0; /* Wait for the firmware to finish its initialization. */ rc = bce_fw_sync(sc, BCE_DRV_MSG_DATA_WAIT1 | reset_code); if (rc) BCE_PRINTF(sc, "%s(%d): Firmware did not complete initialization!\n", __FILE__, __LINE__); bce_reset_exit: DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return (rc); } static int bce_chipinit(struct bce_softc *sc) { u32 val; int rc = 0; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); /* Make sure the interrupt is not active. */ REG_WR(sc, BCE_PCICFG_INT_ACK_CMD, BCE_PCICFG_INT_ACK_CMD_MASK_INT); /* Initialize DMA byte/word swapping, configure the number of DMA */ /* channels and PCI clock compensation delay. */ val = BCE_DMA_CONFIG_DATA_BYTE_SWAP | BCE_DMA_CONFIG_DATA_WORD_SWAP | #if BYTE_ORDER == BIG_ENDIAN BCE_DMA_CONFIG_CNTL_BYTE_SWAP | #endif BCE_DMA_CONFIG_CNTL_WORD_SWAP | DMA_READ_CHANS << 12 | DMA_WRITE_CHANS << 16; val |= (0x2 << 20) | BCE_DMA_CONFIG_CNTL_PCI_COMP_DLY; if ((sc->bce_flags & BCE_PCIX_FLAG) && (sc->bus_speed_mhz == 133)) val |= BCE_DMA_CONFIG_PCI_FAST_CLK_CMP; /* * This setting resolves a problem observed on certain Intel PCI * chipsets that cannot handle multiple outstanding DMA operations. * See errata E9_5706A1_65. */ if ((BCE_CHIP_NUM(sc) == BCE_CHIP_NUM_5706) && (BCE_CHIP_ID(sc) != BCE_CHIP_ID_5706_A0) && !(sc->bce_flags & BCE_PCIX_FLAG)) val |= BCE_DMA_CONFIG_CNTL_PING_PONG_DMA; REG_WR(sc, BCE_DMA_CONFIG, val); /* Clear the PCI-X relaxed ordering bit. See errata E3_5708CA0_570. */ if (sc->bce_flags & BCE_PCIX_FLAG) { u16 val; val = pci_read_config(sc->bce_dev, BCE_PCI_PCIX_CMD, 2); pci_write_config(sc->bce_dev, BCE_PCI_PCIX_CMD, val & ~0x2, 2); } /* Enable the RX_V2P and Context state machines before access. */ REG_WR(sc, BCE_MISC_ENABLE_SET_BITS, BCE_MISC_ENABLE_SET_BITS_HOST_COALESCE_ENABLE | BCE_MISC_ENABLE_STATUS_BITS_RX_V2P_ENABLE | BCE_MISC_ENABLE_STATUS_BITS_CONTEXT_ENABLE); /* Initialize context mapping and zero out the quick contexts. */ bce_init_context(sc); /* Initialize the on-boards CPUs */ bce_init_cpus(sc); /* Prepare NVRAM for access. */ if (bce_init_nvram(sc)) { rc = ENODEV; goto bce_chipinit_exit; } /* Set the kernel bypass block size */ val = REG_RD(sc, BCE_MQ_CONFIG); val &= ~BCE_MQ_CONFIG_KNL_BYP_BLK_SIZE; val |= BCE_MQ_CONFIG_KNL_BYP_BLK_SIZE_256; REG_WR(sc, BCE_MQ_CONFIG, val); val = 0x10000 + (MAX_CID_CNT * MB_KERNEL_CTX_SIZE); REG_WR(sc, BCE_MQ_KNL_BYP_WIND_START, val); REG_WR(sc, BCE_MQ_KNL_WIND_END, val); val = (BCM_PAGE_BITS - 8) << 24; REG_WR(sc, BCE_RV2P_CONFIG, val); /* Configure page size. */ val = REG_RD(sc, BCE_TBDR_CONFIG); val &= ~BCE_TBDR_CONFIG_PAGE_SIZE; val |= (BCM_PAGE_BITS - 8) << 24 | 0x40; REG_WR(sc, BCE_TBDR_CONFIG, val); bce_chipinit_exit: DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return(rc); } /****************************************************************************/ /* Initialize the controller in preparation to send/receive traffic. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_blockinit(struct bce_softc *sc) { u32 reg, val; int rc = 0; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); /* Load the hardware default MAC address. */ bce_set_mac_addr(sc); /* Set the Ethernet backoff seed value */ val = sc->eaddr[0] + (sc->eaddr[1] << 8) + (sc->eaddr[2] << 16) + (sc->eaddr[3] ) + (sc->eaddr[4] << 8) + (sc->eaddr[5] << 16); REG_WR(sc, BCE_EMAC_BACKOFF_SEED, val); sc->last_status_idx = 0; sc->rx_mode = BCE_EMAC_RX_MODE_SORT_MODE; /* Set up link change interrupt generation. */ REG_WR(sc, BCE_EMAC_ATTENTION_ENA, BCE_EMAC_ATTENTION_ENA_LINK); /* Program the physical address of the status block. */ REG_WR(sc, BCE_HC_STATUS_ADDR_L, BCE_ADDR_LO(sc->status_block_paddr)); REG_WR(sc, BCE_HC_STATUS_ADDR_H, BCE_ADDR_HI(sc->status_block_paddr)); /* Program the physical address of the statistics block. */ REG_WR(sc, BCE_HC_STATISTICS_ADDR_L, BCE_ADDR_LO(sc->stats_block_paddr)); REG_WR(sc, BCE_HC_STATISTICS_ADDR_H, BCE_ADDR_HI(sc->stats_block_paddr)); /* Program various host coalescing parameters. */ REG_WR(sc, BCE_HC_TX_QUICK_CONS_TRIP, (sc->bce_tx_quick_cons_trip_int << 16) | sc->bce_tx_quick_cons_trip); REG_WR(sc, BCE_HC_RX_QUICK_CONS_TRIP, (sc->bce_rx_quick_cons_trip_int << 16) | sc->bce_rx_quick_cons_trip); REG_WR(sc, BCE_HC_COMP_PROD_TRIP, (sc->bce_comp_prod_trip_int << 16) | sc->bce_comp_prod_trip); REG_WR(sc, BCE_HC_TX_TICKS, (sc->bce_tx_ticks_int << 16) | sc->bce_tx_ticks); REG_WR(sc, BCE_HC_RX_TICKS, (sc->bce_rx_ticks_int << 16) | sc->bce_rx_ticks); REG_WR(sc, BCE_HC_COM_TICKS, (sc->bce_com_ticks_int << 16) | sc->bce_com_ticks); REG_WR(sc, BCE_HC_CMD_TICKS, (sc->bce_cmd_ticks_int << 16) | sc->bce_cmd_ticks); REG_WR(sc, BCE_HC_STATS_TICKS, (sc->bce_stats_ticks & 0xffff00)); REG_WR(sc, BCE_HC_STAT_COLLECT_TICKS, 0xbb8); /* 3ms */ REG_WR(sc, BCE_HC_CONFIG, (BCE_HC_CONFIG_RX_TMR_MODE | BCE_HC_CONFIG_TX_TMR_MODE | BCE_HC_CONFIG_COLLECT_STATS)); /* Clear the internal statistics counters. */ REG_WR(sc, BCE_HC_COMMAND, BCE_HC_COMMAND_CLR_STAT_NOW); /* Verify that bootcode is running. */ reg = REG_RD_IND(sc, sc->bce_shmem_base + BCE_DEV_INFO_SIGNATURE); DBRUNIF(DB_RANDOMTRUE(bce_debug_bootcode_running_failure), BCE_PRINTF(sc, "%s(%d): Simulating bootcode failure.\n", __FILE__, __LINE__); reg = 0); if ((reg & BCE_DEV_INFO_SIGNATURE_MAGIC_MASK) != BCE_DEV_INFO_SIGNATURE_MAGIC) { BCE_PRINTF(sc, "%s(%d): Bootcode not running! Found: 0x%08X, " "Expected: 08%08X\n", __FILE__, __LINE__, (reg & BCE_DEV_INFO_SIGNATURE_MAGIC_MASK), BCE_DEV_INFO_SIGNATURE_MAGIC); rc = ENODEV; goto bce_blockinit_exit; } /* Check if any management firmware is running. */ reg = REG_RD_IND(sc, sc->bce_shmem_base + BCE_PORT_FEATURE); if (reg & (BCE_PORT_FEATURE_ASF_ENABLED | BCE_PORT_FEATURE_IMD_ENABLED)) { DBPRINT(sc, BCE_INFO, "Management F/W Enabled.\n"); sc->bce_flags |= BCE_MFW_ENABLE_FLAG; } sc->bce_fw_ver = REG_RD_IND(sc, sc->bce_shmem_base + BCE_DEV_INFO_BC_REV); DBPRINT(sc, BCE_INFO, "bootcode rev = 0x%08X\n", sc->bce_fw_ver); /* Allow bootcode to apply any additional fixes before enabling MAC. */ rc = bce_fw_sync(sc, BCE_DRV_MSG_DATA_WAIT2 | BCE_DRV_MSG_CODE_RESET); /* Enable link state change interrupt generation. */ REG_WR(sc, BCE_HC_ATTN_BITS_ENABLE, STATUS_ATTN_BITS_LINK_STATE); /* Enable all remaining blocks in the MAC. */ REG_WR(sc, BCE_MISC_ENABLE_SET_BITS, 0x5ffffff); REG_RD(sc, BCE_MISC_ENABLE_SET_BITS); DELAY(20); bce_blockinit_exit: DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return (rc); } /****************************************************************************/ /* Encapsulate an mbuf cluster into the rx_bd chain. */ /* */ /* The NetXtreme II can support Jumbo frames by using multiple rx_bd's. */ /* This routine will map an mbuf cluster into 1 or more rx_bd's as */ /* necessary. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_get_buf(struct bce_softc *sc, struct mbuf *m, u16 *prod, u16 *chain_prod, u32 *prod_bseq) { bus_dmamap_t map; bus_dma_segment_t segs[4]; struct mbuf *m_new = NULL; struct rx_bd *rxbd; int i, nsegs, error, rc = 0; #ifdef BCE_DEBUG u16 debug_chain_prod = *chain_prod; #endif DBPRINT(sc, (BCE_VERBOSE_RESET | BCE_VERBOSE_RECV), "Entering %s()\n", __FUNCTION__); /* Make sure the inputs are valid. */ DBRUNIF((*chain_prod > MAX_RX_BD), BCE_PRINTF(sc, "%s(%d): RX producer out of range: 0x%04X > 0x%04X\n", __FILE__, __LINE__, *chain_prod, (u16) MAX_RX_BD)); DBPRINT(sc, BCE_VERBOSE_RECV, "%s(enter): prod = 0x%04X, chain_prod = 0x%04X, " "prod_bseq = 0x%08X\n", __FUNCTION__, *prod, *chain_prod, *prod_bseq); if (m == NULL) { DBRUNIF(DB_RANDOMTRUE(bce_debug_mbuf_allocation_failure), BCE_PRINTF(sc, "%s(%d): Simulating mbuf allocation failure.\n", __FILE__, __LINE__); sc->mbuf_alloc_failed++; rc = ENOBUFS; goto bce_get_buf_exit); /* This is a new mbuf allocation. */ MGETHDR(m_new, M_DONTWAIT, MT_DATA); if (m_new == NULL) { DBPRINT(sc, BCE_WARN, "%s(%d): RX mbuf header allocation failed!\n", __FILE__, __LINE__); DBRUNIF(1, sc->mbuf_alloc_failed++); rc = ENOBUFS; goto bce_get_buf_exit; } DBRUNIF(1, sc->rx_mbuf_alloc++); m_cljget(m_new, M_DONTWAIT, sc->mbuf_alloc_size); if (!(m_new->m_flags & M_EXT)) { DBPRINT(sc, BCE_WARN, "%s(%d): RX mbuf chain allocation failed!\n", __FILE__, __LINE__); m_freem(m_new); DBRUNIF(1, sc->rx_mbuf_alloc--); DBRUNIF(1, sc->mbuf_alloc_failed++); rc = ENOBUFS; goto bce_get_buf_exit; } m_new->m_len = m_new->m_pkthdr.len = sc->mbuf_alloc_size; } else { m_new = m; m_new->m_len = m_new->m_pkthdr.len = sc->mbuf_alloc_size; m_new->m_data = m_new->m_ext.ext_buf; } /* Map the mbuf cluster into device memory. */ map = sc->rx_mbuf_map[*chain_prod]; error = bus_dmamap_load_mbuf_sg(sc->rx_mbuf_tag, map, m_new, segs, &nsegs, BUS_DMA_NOWAIT); if (error) { BCE_PRINTF(sc, "%s(%d): Error mapping mbuf into RX chain!\n", __FILE__, __LINE__); m_freem(m_new); DBRUNIF(1, sc->rx_mbuf_alloc--); rc = ENOBUFS; goto bce_get_buf_exit; } /* Watch for overflow. */ DBRUNIF((sc->free_rx_bd > USABLE_RX_BD), BCE_PRINTF(sc, "%s(%d): Too many free rx_bd (0x%04X > 0x%04X)!\n", __FILE__, __LINE__, sc->free_rx_bd, (u16) USABLE_RX_BD)); DBRUNIF((sc->free_rx_bd < sc->rx_low_watermark), sc->rx_low_watermark = sc->free_rx_bd); /* Setup the rx_bd for the first segment. */ rxbd = &sc->rx_bd_chain[RX_PAGE(*chain_prod)][RX_IDX(*chain_prod)]; rxbd->rx_bd_haddr_lo = htole32(BCE_ADDR_LO(segs[0].ds_addr)); rxbd->rx_bd_haddr_hi = htole32(BCE_ADDR_HI(segs[0].ds_addr)); rxbd->rx_bd_len = htole32(segs[0].ds_len); rxbd->rx_bd_flags = htole32(RX_BD_FLAGS_START); *prod_bseq += segs[0].ds_len; for (i = 1; i < nsegs; i++) { *prod = NEXT_RX_BD(*prod); *chain_prod = RX_CHAIN_IDX(*prod); rxbd = &sc->rx_bd_chain[RX_PAGE(*chain_prod)][RX_IDX(*chain_prod)]; rxbd->rx_bd_haddr_lo = htole32(BCE_ADDR_LO(segs[i].ds_addr)); rxbd->rx_bd_haddr_hi = htole32(BCE_ADDR_HI(segs[i].ds_addr)); rxbd->rx_bd_len = htole32(segs[i].ds_len); rxbd->rx_bd_flags = 0; *prod_bseq += segs[i].ds_len; } rxbd->rx_bd_flags |= htole32(RX_BD_FLAGS_END); /* Save the mbuf and update our counter. */ sc->rx_mbuf_ptr[*chain_prod] = m_new; sc->free_rx_bd -= nsegs; DBRUN(BCE_VERBOSE_RECV, bce_dump_rx_mbuf_chain(sc, debug_chain_prod, nsegs)); DBPRINT(sc, BCE_VERBOSE_RECV, "%s(exit): prod = 0x%04X, chain_prod = 0x%04X, " "prod_bseq = 0x%08X\n", __FUNCTION__, *prod, *chain_prod, *prod_bseq); bce_get_buf_exit: DBPRINT(sc, (BCE_VERBOSE_RESET | BCE_VERBOSE_RECV), "Exiting %s()\n", __FUNCTION__); return(rc); } /****************************************************************************/ /* Allocate memory and initialize the TX data structures. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_init_tx_chain(struct bce_softc *sc) { struct tx_bd *txbd; u32 val; int i, rc = 0; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); /* Set the initial TX producer/consumer indices. */ sc->tx_prod = 0; sc->tx_cons = 0; sc->tx_prod_bseq = 0; sc->used_tx_bd = 0; DBRUNIF(1, sc->tx_hi_watermark = USABLE_TX_BD); /* * The NetXtreme II supports a linked-list structre called * a Buffer Descriptor Chain (or BD chain). A BD chain * consists of a series of 1 or more chain pages, each of which * consists of a fixed number of BD entries. * The last BD entry on each page is a pointer to the next page * in the chain, and the last pointer in the BD chain * points back to the beginning of the chain. */ /* Set the TX next pointer chain entries. */ for (i = 0; i < TX_PAGES; i++) { int j; txbd = &sc->tx_bd_chain[i][USABLE_TX_BD_PER_PAGE]; /* Check if we've reached the last page. */ if (i == (TX_PAGES - 1)) j = 0; else j = i + 1; txbd->tx_bd_haddr_hi = htole32(BCE_ADDR_HI(sc->tx_bd_chain_paddr[j])); txbd->tx_bd_haddr_lo = htole32(BCE_ADDR_LO(sc->tx_bd_chain_paddr[j])); } /* * Initialize the context ID for an L2 TX chain. */ val = BCE_L2CTX_TYPE_TYPE_L2; val |= BCE_L2CTX_TYPE_SIZE_L2; CTX_WR(sc, GET_CID_ADDR(TX_CID), BCE_L2CTX_TYPE, val); val = BCE_L2CTX_CMD_TYPE_TYPE_L2 | (8 << 16); CTX_WR(sc, GET_CID_ADDR(TX_CID), BCE_L2CTX_CMD_TYPE, val); /* Point the hardware to the first page in the chain. */ val = BCE_ADDR_HI(sc->tx_bd_chain_paddr[0]); CTX_WR(sc, GET_CID_ADDR(TX_CID), BCE_L2CTX_TBDR_BHADDR_HI, val); val = BCE_ADDR_LO(sc->tx_bd_chain_paddr[0]); CTX_WR(sc, GET_CID_ADDR(TX_CID), BCE_L2CTX_TBDR_BHADDR_LO, val); DBRUN(BCE_VERBOSE_SEND, bce_dump_tx_chain(sc, 0, TOTAL_TX_BD)); DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return(rc); } /****************************************************************************/ /* Free memory and clear the TX data structures. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_free_tx_chain(struct bce_softc *sc) { int i; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); /* Unmap, unload, and free any mbufs still in the TX mbuf chain. */ for (i = 0; i < TOTAL_TX_BD; i++) { if (sc->tx_mbuf_ptr[i] != NULL) { if (sc->tx_mbuf_map != NULL) bus_dmamap_sync(sc->tx_mbuf_tag, sc->tx_mbuf_map[i], BUS_DMASYNC_POSTWRITE); m_freem(sc->tx_mbuf_ptr[i]); sc->tx_mbuf_ptr[i] = NULL; DBRUNIF(1, sc->tx_mbuf_alloc--); } } /* Clear each TX chain page. */ for (i = 0; i < TX_PAGES; i++) bzero((char *)sc->tx_bd_chain[i], BCE_TX_CHAIN_PAGE_SZ); /* Check if we lost any mbufs in the process. */ DBRUNIF((sc->tx_mbuf_alloc), BCE_PRINTF(sc, "%s(%d): Memory leak! Lost %d mbufs " "from tx chain!\n", __FILE__, __LINE__, sc->tx_mbuf_alloc)); DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); } /****************************************************************************/ /* Allocate memory and initialize the RX data structures. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_init_rx_chain(struct bce_softc *sc) { struct rx_bd *rxbd; int i, rc = 0; u16 prod, chain_prod; u32 prod_bseq, val; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); /* Initialize the RX producer and consumer indices. */ sc->rx_prod = 0; sc->rx_cons = 0; sc->rx_prod_bseq = 0; sc->free_rx_bd = BCE_RX_SLACK_SPACE; DBRUNIF(1, sc->rx_low_watermark = USABLE_RX_BD); /* Initialize the RX next pointer chain entries. */ for (i = 0; i < RX_PAGES; i++) { int j; rxbd = &sc->rx_bd_chain[i][USABLE_RX_BD_PER_PAGE]; /* Check if we've reached the last page. */ if (i == (RX_PAGES - 1)) j = 0; else j = i + 1; /* Setup the chain page pointers. */ rxbd->rx_bd_haddr_hi = htole32(BCE_ADDR_HI(sc->rx_bd_chain_paddr[j])); rxbd->rx_bd_haddr_lo = htole32(BCE_ADDR_LO(sc->rx_bd_chain_paddr[j])); } /* Initialize the context ID for an L2 RX chain. */ val = BCE_L2CTX_CTX_TYPE_CTX_BD_CHN_TYPE_VALUE; val |= BCE_L2CTX_CTX_TYPE_SIZE_L2; val |= 0x02 << 8; CTX_WR(sc, GET_CID_ADDR(RX_CID), BCE_L2CTX_CTX_TYPE, val); /* Point the hardware to the first page in the chain. */ val = BCE_ADDR_HI(sc->rx_bd_chain_paddr[0]); CTX_WR(sc, GET_CID_ADDR(RX_CID), BCE_L2CTX_NX_BDHADDR_HI, val); val = BCE_ADDR_LO(sc->rx_bd_chain_paddr[0]); CTX_WR(sc, GET_CID_ADDR(RX_CID), BCE_L2CTX_NX_BDHADDR_LO, val); /* Allocate mbuf clusters for the rx_bd chain. */ prod = prod_bseq = 0; while (prod < BCE_RX_SLACK_SPACE) { chain_prod = RX_CHAIN_IDX(prod); if (bce_get_buf(sc, NULL, &prod, &chain_prod, &prod_bseq)) { BCE_PRINTF(sc, "%s(%d): Error filling RX chain: rx_bd[0x%04X]!\n", __FILE__, __LINE__, chain_prod); rc = ENOBUFS; break; } prod = NEXT_RX_BD(prod); } /* Save the RX chain producer index. */ sc->rx_prod = prod; sc->rx_prod_bseq = prod_bseq; for (i = 0; i < RX_PAGES; i++) { bus_dmamap_sync( sc->rx_bd_chain_tag, sc->rx_bd_chain_map[i], BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } /* Tell the chip about the waiting rx_bd's. */ REG_WR16(sc, MB_RX_CID_ADDR + BCE_L2CTX_HOST_BDIDX, sc->rx_prod); REG_WR(sc, MB_RX_CID_ADDR + BCE_L2CTX_HOST_BSEQ, sc->rx_prod_bseq); DBRUN(BCE_VERBOSE_RECV, bce_dump_rx_chain(sc, 0, TOTAL_RX_BD)); DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return(rc); } /****************************************************************************/ /* Free memory and clear the RX data structures. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_free_rx_chain(struct bce_softc *sc) { int i; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); /* Free any mbufs still in the RX mbuf chain. */ for (i = 0; i < TOTAL_RX_BD; i++) { if (sc->rx_mbuf_ptr[i] != NULL) { if (sc->rx_mbuf_map[i] != NULL) bus_dmamap_sync(sc->rx_mbuf_tag, sc->rx_mbuf_map[i], BUS_DMASYNC_POSTREAD); m_freem(sc->rx_mbuf_ptr[i]); sc->rx_mbuf_ptr[i] = NULL; DBRUNIF(1, sc->rx_mbuf_alloc--); } } /* Clear each RX chain page. */ for (i = 0; i < RX_PAGES; i++) bzero((char *)sc->rx_bd_chain[i], BCE_RX_CHAIN_PAGE_SZ); /* Check if we lost any mbufs in the process. */ DBRUNIF((sc->rx_mbuf_alloc), BCE_PRINTF(sc, "%s(%d): Memory leak! Lost %d mbufs from rx chain!\n", __FILE__, __LINE__, sc->rx_mbuf_alloc)); DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); } /****************************************************************************/ /* Set media options. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_ifmedia_upd(struct ifnet *ifp) { struct bce_softc *sc; struct mii_data *mii; struct ifmedia *ifm; int rc = 0; sc = ifp->if_softc; ifm = &sc->bce_ifmedia; /* DRC - ToDo: Add SerDes support. */ mii = device_get_softc(sc->bce_miibus); sc->bce_link = 0; if (mii->mii_instance) { struct mii_softc *miisc; for (miisc = LIST_FIRST(&mii->mii_phys); miisc != NULL; miisc = LIST_NEXT(miisc, mii_list)) mii_phy_reset(miisc); } mii_mediachg(mii); return(rc); } /****************************************************************************/ /* Reports current media status. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct bce_softc *sc; struct mii_data *mii; sc = ifp->if_softc; BCE_LOCK(sc); mii = device_get_softc(sc->bce_miibus); /* DRC - ToDo: Add SerDes support. */ mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; BCE_UNLOCK(sc); } /****************************************************************************/ /* Handles PHY generated interrupt events. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_phy_intr(struct bce_softc *sc) { u32 new_link_state, old_link_state; new_link_state = sc->status_block->status_attn_bits & STATUS_ATTN_BITS_LINK_STATE; old_link_state = sc->status_block->status_attn_bits_ack & STATUS_ATTN_BITS_LINK_STATE; /* Handle any changes if the link state has changed. */ if (new_link_state != old_link_state) { DBRUN(BCE_VERBOSE_INTR, bce_dump_status_block(sc)); sc->bce_link = 0; callout_stop(&sc->bce_stat_ch); bce_tick_locked(sc); /* Update the status_attn_bits_ack field in the status block. */ if (new_link_state) { REG_WR(sc, BCE_PCICFG_STATUS_BIT_SET_CMD, STATUS_ATTN_BITS_LINK_STATE); DBPRINT(sc, BCE_INFO, "Link is now UP.\n"); } else { REG_WR(sc, BCE_PCICFG_STATUS_BIT_CLEAR_CMD, STATUS_ATTN_BITS_LINK_STATE); DBPRINT(sc, BCE_INFO, "Link is now DOWN.\n"); } } /* Acknowledge the link change interrupt. */ REG_WR(sc, BCE_EMAC_STATUS, BCE_EMAC_STATUS_LINK_CHANGE); } /****************************************************************************/ /* Handles received frame interrupt events. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_rx_intr(struct bce_softc *sc) { struct status_block *sblk = sc->status_block; struct ifnet *ifp = sc->bce_ifp; u16 hw_cons, sw_cons, sw_chain_cons, sw_prod, sw_chain_prod; u32 sw_prod_bseq; struct l2_fhdr *l2fhdr; DBRUNIF(1, sc->rx_interrupts++); /* Prepare the RX chain pages to be accessed by the host CPU. */ for (int i = 0; i < RX_PAGES; i++) bus_dmamap_sync(sc->rx_bd_chain_tag, sc->rx_bd_chain_map[i], BUS_DMASYNC_POSTWRITE); /* Get the hardware's view of the RX consumer index. */ hw_cons = sc->hw_rx_cons = sblk->status_rx_quick_consumer_index0; if ((hw_cons & USABLE_RX_BD_PER_PAGE) == USABLE_RX_BD_PER_PAGE) hw_cons++; /* Get working copies of the driver's view of the RX indices. */ sw_cons = sc->rx_cons; sw_prod = sc->rx_prod; sw_prod_bseq = sc->rx_prod_bseq; DBPRINT(sc, BCE_INFO_RECV, "%s(enter): sw_prod = 0x%04X, " "sw_cons = 0x%04X, sw_prod_bseq = 0x%08X\n", __FUNCTION__, sw_prod, sw_cons, sw_prod_bseq); /* Prevent speculative reads from getting ahead of the status block. */ bus_space_barrier(sc->bce_btag, sc->bce_bhandle, 0, 0, BUS_SPACE_BARRIER_READ); DBRUNIF((sc->free_rx_bd < sc->rx_low_watermark), sc->rx_low_watermark = sc->free_rx_bd); /* * Scan through the receive chain as long * as there is work to do. */ while (sw_cons != hw_cons) { struct mbuf *m; struct rx_bd *rxbd; unsigned int len; u32 status; /* Convert the producer/consumer indices to an actual rx_bd index. */ sw_chain_cons = RX_CHAIN_IDX(sw_cons); sw_chain_prod = RX_CHAIN_IDX(sw_prod); /* Get the used rx_bd. */ rxbd = &sc->rx_bd_chain[RX_PAGE(sw_chain_cons)][RX_IDX(sw_chain_cons)]; sc->free_rx_bd++; DBRUN(BCE_VERBOSE_RECV, BCE_PRINTF(sc, "%s(): ", __FUNCTION__); bce_dump_rxbd(sc, sw_chain_cons, rxbd)); #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { if (sc->bce_rxcycles <= 0) break; sc->bce_rxcycles--; } #endif /* The mbuf is stored with the last rx_bd entry of a packet. */ if (sc->rx_mbuf_ptr[sw_chain_cons] != NULL) { /* Validate that this is the last rx_bd. */ DBRUNIF((!(rxbd->rx_bd_flags & RX_BD_FLAGS_END)), BCE_PRINTF(sc, "%s(%d): Unexpected mbuf found in rx_bd[0x%04X]!\n", __FILE__, __LINE__, sw_chain_cons); bce_breakpoint(sc)); /* DRC - ToDo: If the received packet is small, say less */ /* than 128 bytes, allocate a new mbuf here, */ /* copy the data to that mbuf, and recycle */ /* the mapped jumbo frame. */ /* Unmap the mbuf from DMA space. */ bus_dmamap_sync(sc->rx_mbuf_tag, sc->rx_mbuf_map[sw_chain_cons], BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->rx_mbuf_tag, sc->rx_mbuf_map[sw_chain_cons]); /* Remove the mbuf from the driver's chain. */ m = sc->rx_mbuf_ptr[sw_chain_cons]; sc->rx_mbuf_ptr[sw_chain_cons] = NULL; /* * Frames received on the NetXteme II are prepended * with the l2_fhdr structure which provides status * information about the received frame (including * VLAN tags and checksum info) and are also * automatically adjusted to align the IP header * (i.e. two null bytes are inserted before the * Ethernet header). */ l2fhdr = mtod(m, struct l2_fhdr *); len = l2fhdr->l2_fhdr_pkt_len; status = l2fhdr->l2_fhdr_status; DBRUNIF(DB_RANDOMTRUE(bce_debug_l2fhdr_status_check), BCE_PRINTF(sc, "Simulating l2_fhdr status error.\n"); status = status | L2_FHDR_ERRORS_PHY_DECODE); /* Watch for unusual sized frames. */ DBRUNIF(((len < BCE_MIN_MTU) || (len > BCE_MAX_JUMBO_ETHER_MTU_VLAN)), BCE_PRINTF(sc, "%s(%d): Unusual frame size found. " "Min(%d), Actual(%d), Max(%d)\n", __FILE__, __LINE__, (int) BCE_MIN_MTU, len, (int) BCE_MAX_JUMBO_ETHER_MTU_VLAN); bce_dump_mbuf(sc, m); bce_breakpoint(sc)); len -= ETHER_CRC_LEN; /* Check the received frame for errors. */ if (status & (L2_FHDR_ERRORS_BAD_CRC | L2_FHDR_ERRORS_PHY_DECODE | L2_FHDR_ERRORS_ALIGNMENT | L2_FHDR_ERRORS_TOO_SHORT | L2_FHDR_ERRORS_GIANT_FRAME)) { ifp->if_ierrors++; DBRUNIF(1, sc->l2fhdr_status_errors++); /* Reuse the mbuf for a new frame. */ if (bce_get_buf(sc, m, &sw_prod, &sw_chain_prod, &sw_prod_bseq)) { DBRUNIF(1, bce_breakpoint(sc)); panic("bce%d: Can't reuse RX mbuf!\n", sc->bce_unit); } goto bce_rx_int_next_rx; } /* * Get a new mbuf for the rx_bd. If no new * mbufs are available then reuse the current mbuf, * log an ierror on the interface, and generate * an error in the system log. */ if (bce_get_buf(sc, NULL, &sw_prod, &sw_chain_prod, &sw_prod_bseq)) { DBRUN(BCE_WARN, BCE_PRINTF(sc, "%s(%d): Failed to allocate " "new mbuf, incoming frame dropped!\n", __FILE__, __LINE__)); ifp->if_ierrors++; /* Try and reuse the exisitng mbuf. */ if (bce_get_buf(sc, m, &sw_prod, &sw_chain_prod, &sw_prod_bseq)) { DBRUNIF(1, bce_breakpoint(sc)); panic("bce%d: Double mbuf allocation failure!", sc->bce_unit); } goto bce_rx_int_next_rx; } /* Skip over the l2_fhdr when passing the data up the stack. */ m_adj(m, sizeof(struct l2_fhdr) + ETHER_ALIGN); /* Adjust the packet length to match the received data. */ m->m_pkthdr.len = m->m_len = len; /* Send the packet to the appropriate interface. */ m->m_pkthdr.rcvif = ifp; DBRUN(BCE_VERBOSE_RECV, struct ether_header *eh; eh = mtod(m, struct ether_header *); BCE_PRINTF(sc, "%s(): to: %6D, from: %6D, type: 0x%04X\n", __FUNCTION__, eh->ether_dhost, ":", eh->ether_shost, ":", htons(eh->ether_type))); /* Validate the checksum if offload enabled. */ if (ifp->if_capenable & IFCAP_RXCSUM) { /* Check for an IP datagram. */ if (status & L2_FHDR_STATUS_IP_DATAGRAM) { m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; /* Check if the IP checksum is valid. */ if ((l2fhdr->l2_fhdr_ip_xsum ^ 0xffff) == 0) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; else DBPRINT(sc, BCE_WARN_SEND, "%s(): Invalid IP checksum = 0x%04X!\n", __FUNCTION__, l2fhdr->l2_fhdr_ip_xsum); } /* Check for a valid TCP/UDP frame. */ if (status & (L2_FHDR_STATUS_TCP_SEGMENT | L2_FHDR_STATUS_UDP_DATAGRAM)) { /* Check for a good TCP/UDP checksum. */ if ((status & (L2_FHDR_ERRORS_TCP_XSUM | L2_FHDR_ERRORS_UDP_XSUM)) == 0) { m->m_pkthdr.csum_data = l2fhdr->l2_fhdr_tcp_udp_xsum; m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | CSUM_PSEUDO_HDR); } else DBPRINT(sc, BCE_WARN_SEND, "%s(): Invalid TCP/UDP checksum = 0x%04X!\n", __FUNCTION__, l2fhdr->l2_fhdr_tcp_udp_xsum); } } /* * If we received a packet with a vlan tag, * attach that information to the packet. */ if (status & L2_FHDR_STATUS_L2_VLAN_TAG) { DBPRINT(sc, BCE_VERBOSE_SEND, "%s(): VLAN tag = 0x%04X\n", __FUNCTION__, l2fhdr->l2_fhdr_vlan_tag); #if __FreeBSD_version < 700000 VLAN_INPUT_TAG(ifp, m, l2fhdr->l2_fhdr_vlan_tag, continue); #else - VLAN_INPUT_TAG(ifp, m, l2fhdr->l2_fhdr_vlan_tag); - if (m == NULL) - continue; + m->m_pkthdr.ether_vtag = l2fhdr->l2_fhdr_vlan_tag; + m->m_flags |= M_VLANTAG; #endif } /* Pass the mbuf off to the upper layers. */ ifp->if_ipackets++; DBPRINT(sc, BCE_VERBOSE_RECV, "%s(): Passing received frame up.\n", __FUNCTION__); BCE_UNLOCK(sc); (*ifp->if_input)(ifp, m); DBRUNIF(1, sc->rx_mbuf_alloc--); BCE_LOCK(sc); bce_rx_int_next_rx: sw_prod = NEXT_RX_BD(sw_prod); } sw_cons = NEXT_RX_BD(sw_cons); /* Refresh hw_cons to see if there's new work */ if (sw_cons == hw_cons) { hw_cons = sc->hw_rx_cons = sblk->status_rx_quick_consumer_index0; if ((hw_cons & USABLE_RX_BD_PER_PAGE) == USABLE_RX_BD_PER_PAGE) hw_cons++; } /* Prevent speculative reads from getting ahead of the status block. */ bus_space_barrier(sc->bce_btag, sc->bce_bhandle, 0, 0, BUS_SPACE_BARRIER_READ); } for (int i = 0; i < RX_PAGES; i++) bus_dmamap_sync(sc->rx_bd_chain_tag, sc->rx_bd_chain_map[i], BUS_DMASYNC_PREWRITE); sc->rx_cons = sw_cons; sc->rx_prod = sw_prod; sc->rx_prod_bseq = sw_prod_bseq; REG_WR16(sc, MB_RX_CID_ADDR + BCE_L2CTX_HOST_BDIDX, sc->rx_prod); REG_WR(sc, MB_RX_CID_ADDR + BCE_L2CTX_HOST_BSEQ, sc->rx_prod_bseq); DBPRINT(sc, BCE_INFO_RECV, "%s(exit): rx_prod = 0x%04X, " "rx_cons = 0x%04X, rx_prod_bseq = 0x%08X\n", __FUNCTION__, sc->rx_prod, sc->rx_cons, sc->rx_prod_bseq); } /****************************************************************************/ /* Handles transmit completion interrupt events. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_tx_intr(struct bce_softc *sc) { struct status_block *sblk = sc->status_block; struct ifnet *ifp = sc->bce_ifp; u16 hw_tx_cons, sw_tx_cons, sw_tx_chain_cons; BCE_LOCK_ASSERT(sc); DBRUNIF(1, sc->tx_interrupts++); /* Get the hardware's view of the TX consumer index. */ hw_tx_cons = sc->hw_tx_cons = sblk->status_tx_quick_consumer_index0; /* Skip to the next entry if this is a chain page pointer. */ if ((hw_tx_cons & USABLE_TX_BD_PER_PAGE) == USABLE_TX_BD_PER_PAGE) hw_tx_cons++; sw_tx_cons = sc->tx_cons; /* Prevent speculative reads from getting ahead of the status block. */ bus_space_barrier(sc->bce_btag, sc->bce_bhandle, 0, 0, BUS_SPACE_BARRIER_READ); /* Cycle through any completed TX chain page entries. */ while (sw_tx_cons != hw_tx_cons) { #ifdef BCE_DEBUG struct tx_bd *txbd = NULL; #endif sw_tx_chain_cons = TX_CHAIN_IDX(sw_tx_cons); DBPRINT(sc, BCE_INFO_SEND, "%s(): hw_tx_cons = 0x%04X, sw_tx_cons = 0x%04X, " "sw_tx_chain_cons = 0x%04X\n", __FUNCTION__, hw_tx_cons, sw_tx_cons, sw_tx_chain_cons); DBRUNIF((sw_tx_chain_cons > MAX_TX_BD), BCE_PRINTF(sc, "%s(%d): TX chain consumer out of range! " " 0x%04X > 0x%04X\n", __FILE__, __LINE__, sw_tx_chain_cons, (int) MAX_TX_BD); bce_breakpoint(sc)); DBRUNIF(1, txbd = &sc->tx_bd_chain[TX_PAGE(sw_tx_chain_cons)] [TX_IDX(sw_tx_chain_cons)]); DBRUNIF((txbd == NULL), BCE_PRINTF(sc, "%s(%d): Unexpected NULL tx_bd[0x%04X]!\n", __FILE__, __LINE__, sw_tx_chain_cons); bce_breakpoint(sc)); DBRUN(BCE_INFO_SEND, BCE_PRINTF(sc, "%s(): ", __FUNCTION__); bce_dump_txbd(sc, sw_tx_chain_cons, txbd)); /* * Free the associated mbuf. Remember * that only the last tx_bd of a packet * has an mbuf pointer and DMA map. */ if (sc->tx_mbuf_ptr[sw_tx_chain_cons] != NULL) { /* Validate that this is the last tx_bd. */ DBRUNIF((!(txbd->tx_bd_vlan_tag_flags & TX_BD_FLAGS_END)), BCE_PRINTF(sc, "%s(%d): tx_bd END flag not set but " "txmbuf == NULL!\n", __FILE__, __LINE__); bce_breakpoint(sc)); DBRUN(BCE_INFO_SEND, BCE_PRINTF(sc, "%s(): Unloading map/freeing mbuf " "from tx_bd[0x%04X]\n", __FUNCTION__, sw_tx_chain_cons)); /* Unmap the mbuf. */ bus_dmamap_unload(sc->tx_mbuf_tag, sc->tx_mbuf_map[sw_tx_chain_cons]); /* Free the mbuf. */ m_freem(sc->tx_mbuf_ptr[sw_tx_chain_cons]); sc->tx_mbuf_ptr[sw_tx_chain_cons] = NULL; DBRUNIF(1, sc->tx_mbuf_alloc--); ifp->if_opackets++; } sc->used_tx_bd--; sw_tx_cons = NEXT_TX_BD(sw_tx_cons); /* Refresh hw_cons to see if there's new work. */ hw_tx_cons = sc->hw_tx_cons = sblk->status_tx_quick_consumer_index0; if ((hw_tx_cons & USABLE_TX_BD_PER_PAGE) == USABLE_TX_BD_PER_PAGE) hw_tx_cons++; /* Prevent speculative reads from getting ahead of the status block. */ bus_space_barrier(sc->bce_btag, sc->bce_bhandle, 0, 0, BUS_SPACE_BARRIER_READ); } /* Clear the TX timeout timer. */ ifp->if_timer = 0; /* Clear the tx hardware queue full flag. */ if ((sc->used_tx_bd + BCE_TX_SLACK_SPACE) < USABLE_TX_BD) { DBRUNIF((ifp->if_drv_flags & IFF_DRV_OACTIVE), BCE_PRINTF(sc, "%s(): TX chain is open for business! Used tx_bd = %d\n", __FUNCTION__, sc->used_tx_bd)); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; } sc->tx_cons = sw_tx_cons; } /****************************************************************************/ /* Disables interrupt generation. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_disable_intr(struct bce_softc *sc) { REG_WR(sc, BCE_PCICFG_INT_ACK_CMD, BCE_PCICFG_INT_ACK_CMD_MASK_INT); REG_RD(sc, BCE_PCICFG_INT_ACK_CMD); } /****************************************************************************/ /* Enables interrupt generation. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_enable_intr(struct bce_softc *sc) { u32 val; REG_WR(sc, BCE_PCICFG_INT_ACK_CMD, BCE_PCICFG_INT_ACK_CMD_INDEX_VALID | BCE_PCICFG_INT_ACK_CMD_MASK_INT | sc->last_status_idx); REG_WR(sc, BCE_PCICFG_INT_ACK_CMD, BCE_PCICFG_INT_ACK_CMD_INDEX_VALID | sc->last_status_idx); val = REG_RD(sc, BCE_HC_COMMAND); REG_WR(sc, BCE_HC_COMMAND, val | BCE_HC_COMMAND_COAL_NOW); } /****************************************************************************/ /* Handles controller initialization. */ /* */ /* Must be called from a locked routine. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_init_locked(struct bce_softc *sc) { struct ifnet *ifp; u32 ether_mtu; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); BCE_LOCK_ASSERT(sc); ifp = sc->bce_ifp; /* Check if the driver is still running and bail out if it is. */ if (ifp->if_drv_flags & IFF_DRV_RUNNING) goto bce_init_locked_exit; bce_stop(sc); if (bce_reset(sc, BCE_DRV_MSG_CODE_RESET)) { BCE_PRINTF(sc, "%s(%d): Controller reset failed!\n", __FILE__, __LINE__); goto bce_init_locked_exit; } if (bce_chipinit(sc)) { BCE_PRINTF(sc, "%s(%d): Controller initialization failed!\n", __FILE__, __LINE__); goto bce_init_locked_exit; } if (bce_blockinit(sc)) { BCE_PRINTF(sc, "%s(%d): Block initialization failed!\n", __FILE__, __LINE__); goto bce_init_locked_exit; } /* Load our MAC address. */ bcopy(IF_LLADDR(sc->bce_ifp), sc->eaddr, ETHER_ADDR_LEN); bce_set_mac_addr(sc); /* Calculate and program the Ethernet MTU size. */ ether_mtu = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN + ifp->if_mtu + ETHER_CRC_LEN; DBPRINT(sc, BCE_INFO, "%s(): setting mtu = %d\n",__FUNCTION__, ether_mtu); /* * Program the mtu, enabling jumbo frame * support if necessary. Also set the mbuf * allocation count for RX frames. */ if (ether_mtu > ETHER_MAX_LEN + ETHER_VLAN_ENCAP_LEN) { REG_WR(sc, BCE_EMAC_RX_MTU_SIZE, ether_mtu | BCE_EMAC_RX_MTU_SIZE_JUMBO_ENA); sc->mbuf_alloc_size = MJUM9BYTES; } else { REG_WR(sc, BCE_EMAC_RX_MTU_SIZE, ether_mtu); sc->mbuf_alloc_size = MCLBYTES; } /* Calculate the RX Ethernet frame size for rx_bd's. */ sc->max_frame_size = sizeof(struct l2_fhdr) + 2 + ether_mtu + 8; DBPRINT(sc, BCE_INFO, "%s(): mclbytes = %d, mbuf_alloc_size = %d, " "max_frame_size = %d\n", __FUNCTION__, (int) MCLBYTES, sc->mbuf_alloc_size, sc->max_frame_size); /* Program appropriate promiscuous/multicast filtering. */ bce_set_rx_mode(sc); /* Init RX buffer descriptor chain. */ bce_init_rx_chain(sc); /* Init TX buffer descriptor chain. */ bce_init_tx_chain(sc); #ifdef DEVICE_POLLING /* Disable interrupts if we are polling. */ if (ifp->if_capenable & IFCAP_POLLING) { bce_disable_intr(sc); REG_WR(sc, BCE_HC_RX_QUICK_CONS_TRIP, (1 << 16) | sc->bce_rx_quick_cons_trip); REG_WR(sc, BCE_HC_TX_QUICK_CONS_TRIP, (1 << 16) | sc->bce_tx_quick_cons_trip); } else #endif /* Enable host interrupts. */ bce_enable_intr(sc); bce_ifmedia_upd(ifp); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; callout_reset(&sc->bce_stat_ch, hz, bce_tick, sc); bce_init_locked_exit: DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return; } /****************************************************************************/ /* Handles controller initialization when called from an unlocked routine. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_init(void *xsc) { struct bce_softc *sc = xsc; BCE_LOCK(sc); bce_init_locked(sc); BCE_UNLOCK(sc); } /****************************************************************************/ /* Encapsultes an mbuf cluster into the tx_bd chain structure and makes the */ /* memory visible to the controller. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_tx_encap(struct bce_softc *sc, struct mbuf *m_head, u16 *prod, u16 *chain_prod, u32 *prod_bseq) { u32 vlan_tag_flags = 0; - struct m_tag *mtag; struct bce_dmamap_arg map_arg; bus_dmamap_t map; int i, error, rc = 0; /* Transfer any checksum offload flags to the bd. */ if (m_head->m_pkthdr.csum_flags) { if (m_head->m_pkthdr.csum_flags & CSUM_IP) vlan_tag_flags |= TX_BD_FLAGS_IP_CKSUM; if (m_head->m_pkthdr.csum_flags & (CSUM_TCP | CSUM_UDP)) vlan_tag_flags |= TX_BD_FLAGS_TCP_UDP_CKSUM; } /* Transfer any VLAN tags to the bd. */ - mtag = VLAN_OUTPUT_TAG(sc->bce_ifp, m_head); - if (mtag != NULL) + if (m_head->m_flags & M_VLANTAG) vlan_tag_flags |= (TX_BD_FLAGS_VLAN_TAG | - (VLAN_TAG_VALUE(mtag) << 16)); + (m_head->m_pkthdr.ether_vtag << 16)); /* Map the mbuf into DMAable memory. */ map = sc->tx_mbuf_map[*chain_prod]; map_arg.sc = sc; map_arg.prod = *prod; map_arg.chain_prod = *chain_prod; map_arg.prod_bseq = *prod_bseq; map_arg.tx_flags = vlan_tag_flags; map_arg.maxsegs = USABLE_TX_BD - sc->used_tx_bd - BCE_TX_SLACK_SPACE; KASSERT(map_arg.maxsegs > 0, ("Invalid TX maxsegs value!")); for (i = 0; i < TX_PAGES; i++) map_arg.tx_chain[i] = sc->tx_bd_chain[i]; /* Map the mbuf into our DMA address space. */ error = bus_dmamap_load_mbuf(sc->tx_mbuf_tag, map, m_head, bce_dma_map_tx_desc, &map_arg, BUS_DMA_NOWAIT); if (error || map_arg.maxsegs == 0) { /* Try to defrag the mbuf if there are too many segments. */ if (error == EFBIG && map_arg.maxsegs != 0) { struct mbuf *m0; DBPRINT(sc, BCE_WARN, "%s(): fragmented mbuf (%d pieces)\n", __FUNCTION__, map_arg.maxsegs); m0 = m_defrag(m_head, M_DONTWAIT); if (m0 != NULL) { m_head = m0; error = bus_dmamap_load_mbuf(sc->tx_mbuf_tag, map, m_head, bce_dma_map_tx_desc, &map_arg, BUS_DMA_NOWAIT); } } /* Still getting an error after a defrag. */ if (error) { BCE_PRINTF(sc, "%s(%d): Error mapping mbuf into TX chain!\n", __FILE__, __LINE__); rc = ENOBUFS; goto bce_tx_encap_exit; } } /* * Ensure that the map for this transmission * is placed at the array index of the last * descriptor in this chain. This is done * because a single map is used for all * segments of the mbuf and we don't want to * delete the map before all of the segments * have been freed. */ sc->tx_mbuf_map[*chain_prod] = sc->tx_mbuf_map[map_arg.chain_prod]; sc->tx_mbuf_map[map_arg.chain_prod] = map; sc->tx_mbuf_ptr[map_arg.chain_prod] = m_head; sc->used_tx_bd += map_arg.maxsegs; DBRUNIF((sc->used_tx_bd > sc->tx_hi_watermark), sc->tx_hi_watermark = sc->used_tx_bd); DBRUNIF(1, sc->tx_mbuf_alloc++); DBRUN(BCE_VERBOSE_SEND, bce_dump_tx_mbuf_chain(sc, *chain_prod, map_arg.maxsegs)); /* prod still points the last used tx_bd at this point. */ *prod = map_arg.prod; *chain_prod = map_arg.chain_prod; *prod_bseq = map_arg.prod_bseq; bce_tx_encap_exit: return(rc); } /****************************************************************************/ /* Main transmit routine when called from another routine with a lock. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_start_locked(struct ifnet *ifp) { struct bce_softc *sc = ifp->if_softc; struct mbuf *m_head = NULL; int count = 0; u16 tx_prod, tx_chain_prod; u32 tx_prod_bseq; /* If there's no link or the transmit queue is empty then just exit. */ if (!sc->bce_link || IFQ_DRV_IS_EMPTY(&ifp->if_snd)) { DBPRINT(sc, BCE_INFO_SEND, "%s(): No link or transmit queue empty.\n", __FUNCTION__); goto bce_start_locked_exit; } /* prod points to the next free tx_bd. */ tx_prod = sc->tx_prod; tx_chain_prod = TX_CHAIN_IDX(tx_prod); tx_prod_bseq = sc->tx_prod_bseq; DBPRINT(sc, BCE_INFO_SEND, "%s(): Start: tx_prod = 0x%04X, tx_chain_prod = %04X, " "tx_prod_bseq = 0x%08X\n", __FUNCTION__, tx_prod, tx_chain_prod, tx_prod_bseq); /* Keep adding entries while there is space in the ring. */ while(sc->tx_mbuf_ptr[tx_chain_prod] == NULL) { /* Check for any frames to send. */ IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * Pack the data into the transmit ring. If we * don't have room, place the mbuf back at the * head of the queue and set the OACTIVE flag * to wait for the NIC to drain the chain. */ if (bce_tx_encap(sc, m_head, &tx_prod, &tx_chain_prod, &tx_prod_bseq)) { IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; DBPRINT(sc, BCE_INFO_SEND, "TX chain is closed for business! Total tx_bd used = %d\n", sc->used_tx_bd); break; } count++; /* Send a copy of the frame to any BPF listeners. */ BPF_MTAP(ifp, m_head); tx_prod = NEXT_TX_BD(tx_prod); tx_chain_prod = TX_CHAIN_IDX(tx_prod); } if (count == 0) { /* no packets were dequeued */ DBPRINT(sc, BCE_VERBOSE_SEND, "%s(): No packets were dequeued\n", __FUNCTION__); goto bce_start_locked_exit; } /* Update the driver's counters. */ sc->tx_prod = tx_prod; sc->tx_prod_bseq = tx_prod_bseq; DBPRINT(sc, BCE_INFO_SEND, "%s(): End: tx_prod = 0x%04X, tx_chain_prod = 0x%04X, " "tx_prod_bseq = 0x%08X\n", __FUNCTION__, tx_prod, tx_chain_prod, tx_prod_bseq); /* Start the transmit. */ REG_WR16(sc, MB_TX_CID_ADDR + BCE_L2CTX_TX_HOST_BIDX, sc->tx_prod); REG_WR(sc, MB_TX_CID_ADDR + BCE_L2CTX_TX_HOST_BSEQ, sc->tx_prod_bseq); /* Set the tx timeout. */ ifp->if_timer = BCE_TX_TIMEOUT; bce_start_locked_exit: return; } /****************************************************************************/ /* Main transmit routine when called from another routine without a lock. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_start(struct ifnet *ifp) { struct bce_softc *sc = ifp->if_softc; BCE_LOCK(sc); bce_start_locked(ifp); BCE_UNLOCK(sc); } /****************************************************************************/ /* Handles any IOCTL calls from the operating system. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct bce_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; struct mii_data *mii; int mask, error = 0; DBPRINT(sc, BCE_VERBOSE_RESET, "Entering %s()\n", __FUNCTION__); switch(command) { /* Set the MTU. */ case SIOCSIFMTU: /* Check that the MTU setting is supported. */ if ((ifr->ifr_mtu < BCE_MIN_MTU) || (ifr->ifr_mtu > BCE_MAX_JUMBO_MTU)) { error = EINVAL; break; } DBPRINT(sc, BCE_INFO, "Setting new MTU of %d\n", ifr->ifr_mtu); BCE_LOCK(sc); ifp->if_mtu = ifr->ifr_mtu; ifp->if_drv_flags &= ~IFF_DRV_RUNNING; bce_init_locked(sc); BCE_UNLOCK(sc); break; /* Set interface. */ case SIOCSIFFLAGS: DBPRINT(sc, BCE_VERBOSE, "Received SIOCSIFFLAGS\n"); BCE_LOCK(sc); /* Check if the interface is up. */ if (ifp->if_flags & IFF_UP) { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { /* Change the promiscuous/multicast flags as necessary. */ bce_set_rx_mode(sc); } else { /* Start the HW */ bce_init_locked(sc); } } else { /* The interface is down. Check if the driver is running. */ if (ifp->if_drv_flags & IFF_DRV_RUNNING) { bce_stop(sc); } } BCE_UNLOCK(sc); error = 0; break; /* Add/Delete multicast address */ case SIOCADDMULTI: case SIOCDELMULTI: DBPRINT(sc, BCE_VERBOSE, "Received SIOCADDMULTI/SIOCDELMULTI\n"); BCE_LOCK(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) { bce_set_rx_mode(sc); error = 0; } BCE_UNLOCK(sc); break; /* Set/Get Interface media */ case SIOCSIFMEDIA: case SIOCGIFMEDIA: DBPRINT(sc, BCE_VERBOSE, "Received SIOCSIFMEDIA/SIOCGIFMEDIA\n"); DBPRINT(sc, BCE_VERBOSE, "bce_phy_flags = 0x%08X\n", sc->bce_phy_flags); if (sc->bce_phy_flags & BCE_PHY_SERDES_FLAG) { DBPRINT(sc, BCE_VERBOSE, "SerDes media set/get\n"); error = ifmedia_ioctl(ifp, ifr, &sc->bce_ifmedia, command); } else { DBPRINT(sc, BCE_VERBOSE, "Copper media set/get\n"); mii = device_get_softc(sc->bce_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); } break; /* Set interface capability */ case SIOCSIFCAP: mask = ifr->ifr_reqcap ^ ifp->if_capenable; DBPRINT(sc, BCE_INFO, "Received SIOCSIFCAP = 0x%08X\n", (u32) mask); #ifdef DEVICE_POLLING if (mask & IFCAP_POLLING) { if (ifr->ifr_reqcap & IFCAP_POLLING) { /* Setup the poll routine to call. */ error = ether_poll_register(bce_poll, ifp); if (error) { BCE_PRINTF(sc, "%s(%d): Error registering poll function!\n", __FILE__, __LINE__); goto bce_ioctl_exit; } /* Clear the interrupt. */ BCE_LOCK(sc); bce_disable_intr(sc); REG_WR(sc, BCE_HC_RX_QUICK_CONS_TRIP, (1 << 16) | sc->bce_rx_quick_cons_trip); REG_WR(sc, BCE_HC_TX_QUICK_CONS_TRIP, (1 << 16) | sc->bce_tx_quick_cons_trip); ifp->if_capenable |= IFCAP_POLLING; BCE_UNLOCK(sc); } else { /* Clear the poll routine. */ error = ether_poll_deregister(ifp); /* Enable interrupt even in error case */ BCE_LOCK(sc); bce_enable_intr(sc); REG_WR(sc, BCE_HC_TX_QUICK_CONS_TRIP, (sc->bce_tx_quick_cons_trip_int << 16) | sc->bce_tx_quick_cons_trip); REG_WR(sc, BCE_HC_RX_QUICK_CONS_TRIP, (sc->bce_rx_quick_cons_trip_int << 16) | sc->bce_rx_quick_cons_trip); ifp->if_capenable &= ~IFCAP_POLLING; BCE_UNLOCK(sc); } } #endif /*DEVICE_POLLING */ /* Toggle the TX checksum capabilites enable flag. */ if (mask & IFCAP_TXCSUM) { ifp->if_capenable ^= IFCAP_TXCSUM; if (IFCAP_TXCSUM & ifp->if_capenable) ifp->if_hwassist = BCE_IF_HWASSIST; else ifp->if_hwassist = 0; } /* Toggle the RX checksum capabilities enable flag. */ if (mask & IFCAP_RXCSUM) { ifp->if_capenable ^= IFCAP_RXCSUM; if (IFCAP_RXCSUM & ifp->if_capenable) ifp->if_hwassist = BCE_IF_HWASSIST; else ifp->if_hwassist = 0; } /* Toggle VLAN_MTU capabilities enable flag. */ if (mask & IFCAP_VLAN_MTU) { BCE_PRINTF(sc, "%s(%d): Changing VLAN_MTU not supported.\n", __FILE__, __LINE__); } /* Toggle VLANHWTAG capabilities enabled flag. */ if (mask & IFCAP_VLAN_HWTAGGING) { if (sc->bce_flags & BCE_MFW_ENABLE_FLAG) BCE_PRINTF(sc, "%s(%d): Cannot change VLAN_HWTAGGING while " "management firmware (ASF/IPMI/UMP) is running!\n", __FILE__, __LINE__); else BCE_PRINTF(sc, "%s(%d): Changing VLAN_HWTAGGING not supported!\n", __FILE__, __LINE__); } break; default: DBPRINT(sc, BCE_INFO, "Received unsupported IOCTL: 0x%08X\n", (u32) command); /* We don't know how to handle the IOCTL, pass it on. */ error = ether_ioctl(ifp, command, data); break; } #ifdef DEVICE_POLLING bce_ioctl_exit: #endif DBPRINT(sc, BCE_VERBOSE_RESET, "Exiting %s()\n", __FUNCTION__); return(error); } /****************************************************************************/ /* Transmit timeout handler. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_watchdog(struct ifnet *ifp) { struct bce_softc *sc = ifp->if_softc; DBRUN(BCE_WARN_SEND, bce_dump_driver_state(sc); bce_dump_status_block(sc)); BCE_PRINTF(sc, "%s(%d): Watchdog timeout occurred, resetting!\n", __FILE__, __LINE__); /* DBRUN(BCE_FATAL, bce_breakpoint(sc)); */ BCE_LOCK(sc); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; bce_init_locked(sc); ifp->if_oerrors++; BCE_UNLOCK(sc); } #ifdef DEVICE_POLLING static void bce_poll_locked(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct bce_softc *sc = ifp->if_softc; BCE_LOCK_ASSERT(sc); sc->bce_rxcycles = count; bus_dmamap_sync(sc->status_tag, sc->status_map, BUS_DMASYNC_POSTWRITE); /* Check for any completed RX frames. */ if (sc->status_block->status_rx_quick_consumer_index0 != sc->hw_rx_cons) bce_rx_intr(sc); /* Check for any completed TX frames. */ if (sc->status_block->status_tx_quick_consumer_index0 != sc->hw_tx_cons) bce_tx_intr(sc); /* Check for new frames to transmit. */ if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) bce_start_locked(ifp); } static void bce_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct bce_softc *sc = ifp->if_softc; BCE_LOCK(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) bce_poll_locked(ifp, cmd, count); BCE_UNLOCK(sc); } #endif /* DEVICE_POLLING */ #if 0 static inline int bce_has_work(struct bce_softc *sc) { struct status_block *stat = sc->status_block; if ((stat->status_rx_quick_consumer_index0 != sc->hw_rx_cons) || (stat->status_tx_quick_consumer_index0 != sc->hw_tx_cons)) return 1; if (((stat->status_attn_bits & STATUS_ATTN_BITS_LINK_STATE) != 0) != bp->link_up) return 1; return 0; } #endif /* * Interrupt handler. */ /****************************************************************************/ /* Main interrupt entry point. Verifies that the controller generated the */ /* interrupt and then calls a separate routine for handle the various */ /* interrupt causes (PHY, TX, RX). */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static void bce_intr(void *xsc) { struct bce_softc *sc; struct ifnet *ifp; u32 status_attn_bits; sc = xsc; ifp = sc->bce_ifp; BCE_LOCK(sc); DBRUNIF(1, sc->interrupts_generated++); #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { DBPRINT(sc, BCE_INFO, "Polling enabled!\n"); goto bce_intr_exit; } #endif bus_dmamap_sync(sc->status_tag, sc->status_map, BUS_DMASYNC_POSTWRITE); /* * If the hardware status block index * matches the last value read by the * driver and we haven't asserted our * interrupt then there's nothing to do. */ if ((sc->status_block->status_idx == sc->last_status_idx) && (REG_RD(sc, BCE_PCICFG_MISC_STATUS) & BCE_PCICFG_MISC_STATUS_INTA_VALUE)) goto bce_intr_exit; /* Ack the interrupt and stop others from occuring. */ REG_WR(sc, BCE_PCICFG_INT_ACK_CMD, BCE_PCICFG_INT_ACK_CMD_USE_INT_HC_PARAM | BCE_PCICFG_INT_ACK_CMD_MASK_INT); /* Keep processing data as long as there is work to do. */ for (;;) { status_attn_bits = sc->status_block->status_attn_bits; DBRUNIF(DB_RANDOMTRUE(bce_debug_unexpected_attention), BCE_PRINTF(sc, "Simulating unexpected status attention bit set."); status_attn_bits = status_attn_bits | STATUS_ATTN_BITS_PARITY_ERROR); /* Was it a link change interrupt? */ if ((status_attn_bits & STATUS_ATTN_BITS_LINK_STATE) != (sc->status_block->status_attn_bits_ack & STATUS_ATTN_BITS_LINK_STATE)) bce_phy_intr(sc); /* If any other attention is asserted then the chip is toast. */ if (((status_attn_bits & ~STATUS_ATTN_BITS_LINK_STATE) != (sc->status_block->status_attn_bits_ack & ~STATUS_ATTN_BITS_LINK_STATE))) { DBRUN(1, sc->unexpected_attentions++); BCE_PRINTF(sc, "%s(%d): Fatal attention detected: 0x%08X\n", __FILE__, __LINE__, sc->status_block->status_attn_bits); DBRUN(BCE_FATAL, if (bce_debug_unexpected_attention == 0) bce_breakpoint(sc)); bce_init_locked(sc); goto bce_intr_exit; } /* Check for any completed RX frames. */ if (sc->status_block->status_rx_quick_consumer_index0 != sc->hw_rx_cons) bce_rx_intr(sc); /* Check for any completed TX frames. */ if (sc->status_block->status_tx_quick_consumer_index0 != sc->hw_tx_cons) bce_tx_intr(sc); /* Save the status block index value for use during the next interrupt. */ sc->last_status_idx = sc->status_block->status_idx; /* Prevent speculative reads from getting ahead of the status block. */ bus_space_barrier(sc->bce_btag, sc->bce_bhandle, 0, 0, BUS_SPACE_BARRIER_READ); /* If there's no work left then exit the interrupt service routine. */ if ((sc->status_block->status_rx_quick_consumer_index0 == sc->hw_rx_cons) && (sc->status_block->status_tx_quick_consumer_index0 == sc->hw_tx_cons)) break; } bus_dmamap_sync(sc->status_tag, sc->status_map, BUS_DMASYNC_PREWRITE); /* Re-enable interrupts. */ REG_WR(sc, BCE_PCICFG_INT_ACK_CMD, BCE_PCICFG_INT_ACK_CMD_INDEX_VALID | sc->last_status_idx | BCE_PCICFG_INT_ACK_CMD_MASK_INT); REG_WR(sc, BCE_PCICFG_INT_ACK_CMD, BCE_PCICFG_INT_ACK_CMD_INDEX_VALID | sc->last_status_idx); /* Handle any frames that arrived while handling the interrupt. */ if (ifp->if_drv_flags & IFF_DRV_RUNNING && !IFQ_DRV_IS_EMPTY(&ifp->if_snd)) bce_start_locked(ifp); bce_intr_exit: BCE_UNLOCK(sc); } /****************************************************************************/ /* Programs the various packet receive modes (broadcast and multicast). */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_set_rx_mode(struct bce_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; u32 hashes[4] = { 0, 0, 0, 0 }; u32 rx_mode, sort_mode; int h, i; BCE_LOCK_ASSERT(sc); ifp = sc->bce_ifp; /* Initialize receive mode default settings. */ rx_mode = sc->rx_mode & ~(BCE_EMAC_RX_MODE_PROMISCUOUS | BCE_EMAC_RX_MODE_KEEP_VLAN_TAG); sort_mode = 1 | BCE_RPM_SORT_USER0_BC_EN; /* * ASF/IPMI/UMP firmware requires that VLAN tag stripping * be enbled. */ if (!(BCE_IF_CAPABILITIES & IFCAP_VLAN_HWTAGGING) && (!(sc->bce_flags & BCE_MFW_ENABLE_FLAG))) rx_mode |= BCE_EMAC_RX_MODE_KEEP_VLAN_TAG; /* * Check for promiscuous, all multicast, or selected * multicast address filtering. */ if (ifp->if_flags & IFF_PROMISC) { DBPRINT(sc, BCE_INFO, "Enabling promiscuous mode.\n"); /* Enable promiscuous mode. */ rx_mode |= BCE_EMAC_RX_MODE_PROMISCUOUS; sort_mode |= BCE_RPM_SORT_USER0_PROM_EN; } else if (ifp->if_flags & IFF_ALLMULTI) { DBPRINT(sc, BCE_INFO, "Enabling all multicast mode.\n"); /* Enable all multicast addresses. */ for (i = 0; i < NUM_MC_HASH_REGISTERS; i++) { REG_WR(sc, BCE_EMAC_MULTICAST_HASH0 + (i * 4), 0xffffffff); } sort_mode |= BCE_RPM_SORT_USER0_MC_EN; } else { /* Accept one or more multicast(s). */ DBPRINT(sc, BCE_INFO, "Enabling selective multicast mode.\n"); IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; h = ether_crc32_le(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN) & 0x7F; hashes[(h & 0x60) >> 5] |= 1 << (h & 0x1F); } IF_ADDR_UNLOCK(ifp); for (i = 0; i < 4; i++) REG_WR(sc, BCE_EMAC_MULTICAST_HASH0 + (i * 4), hashes[i]); sort_mode |= BCE_RPM_SORT_USER0_MC_HSH_EN; } /* Only make changes if the recive mode has actually changed. */ if (rx_mode != sc->rx_mode) { DBPRINT(sc, BCE_VERBOSE, "Enabling new receive mode: 0x%08X\n", rx_mode); sc->rx_mode = rx_mode; REG_WR(sc, BCE_EMAC_RX_MODE, rx_mode); } /* Disable and clear the exisitng sort before enabling a new sort. */ REG_WR(sc, BCE_RPM_SORT_USER0, 0x0); REG_WR(sc, BCE_RPM_SORT_USER0, sort_mode); REG_WR(sc, BCE_RPM_SORT_USER0, sort_mode | BCE_RPM_SORT_USER0_ENA); } /****************************************************************************/ /* Called periodically to updates statistics from the controllers */ /* statistics block. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_stats_update(struct bce_softc *sc) { struct ifnet *ifp; struct statistics_block *stats; DBPRINT(sc, BCE_EXCESSIVE, "Entering %s()\n", __FUNCTION__); ifp = sc->bce_ifp; stats = (struct statistics_block *) sc->stats_block; /* * Update the interface statistics from the * hardware statistics. */ ifp->if_collisions = (u_long) stats->stat_EtherStatsCollisions; ifp->if_ierrors = (u_long) stats->stat_EtherStatsUndersizePkts + (u_long) stats->stat_EtherStatsOverrsizePkts + (u_long) stats->stat_IfInMBUFDiscards + (u_long) stats->stat_Dot3StatsAlignmentErrors + (u_long) stats->stat_Dot3StatsFCSErrors; ifp->if_oerrors = (u_long) stats->stat_emac_tx_stat_dot3statsinternalmactransmiterrors + (u_long) stats->stat_Dot3StatsExcessiveCollisions + (u_long) stats->stat_Dot3StatsLateCollisions; /* * Certain controllers don't report * carrier sense errors correctly. * See errata E11_5708CA0_1165. */ if (!(BCE_CHIP_NUM(sc) == BCE_CHIP_NUM_5706) && !(BCE_CHIP_ID(sc) == BCE_CHIP_ID_5708_A0)) ifp->if_oerrors += (u_long) stats->stat_Dot3StatsCarrierSenseErrors; /* * Update the sysctl statistics from the * hardware statistics. */ sc->stat_IfHCInOctets = ((u64) stats->stat_IfHCInOctets_hi << 32) + (u64) stats->stat_IfHCInOctets_lo; sc->stat_IfHCInBadOctets = ((u64) stats->stat_IfHCInBadOctets_hi << 32) + (u64) stats->stat_IfHCInBadOctets_lo; sc->stat_IfHCOutOctets = ((u64) stats->stat_IfHCOutOctets_hi << 32) + (u64) stats->stat_IfHCOutOctets_lo; sc->stat_IfHCOutBadOctets = ((u64) stats->stat_IfHCOutBadOctets_hi << 32) + (u64) stats->stat_IfHCOutBadOctets_lo; sc->stat_IfHCInUcastPkts = ((u64) stats->stat_IfHCInUcastPkts_hi << 32) + (u64) stats->stat_IfHCInUcastPkts_lo; sc->stat_IfHCInMulticastPkts = ((u64) stats->stat_IfHCInMulticastPkts_hi << 32) + (u64) stats->stat_IfHCInMulticastPkts_lo; sc->stat_IfHCInBroadcastPkts = ((u64) stats->stat_IfHCInBroadcastPkts_hi << 32) + (u64) stats->stat_IfHCInBroadcastPkts_lo; sc->stat_IfHCOutUcastPkts = ((u64) stats->stat_IfHCOutUcastPkts_hi << 32) + (u64) stats->stat_IfHCOutUcastPkts_lo; sc->stat_IfHCOutMulticastPkts = ((u64) stats->stat_IfHCOutMulticastPkts_hi << 32) + (u64) stats->stat_IfHCOutMulticastPkts_lo; sc->stat_IfHCOutBroadcastPkts = ((u64) stats->stat_IfHCOutBroadcastPkts_hi << 32) + (u64) stats->stat_IfHCOutBroadcastPkts_lo; sc->stat_emac_tx_stat_dot3statsinternalmactransmiterrors = stats->stat_emac_tx_stat_dot3statsinternalmactransmiterrors; sc->stat_Dot3StatsCarrierSenseErrors = stats->stat_Dot3StatsCarrierSenseErrors; sc->stat_Dot3StatsFCSErrors = stats->stat_Dot3StatsFCSErrors; sc->stat_Dot3StatsAlignmentErrors = stats->stat_Dot3StatsAlignmentErrors; sc->stat_Dot3StatsSingleCollisionFrames = stats->stat_Dot3StatsSingleCollisionFrames; sc->stat_Dot3StatsMultipleCollisionFrames = stats->stat_Dot3StatsMultipleCollisionFrames; sc->stat_Dot3StatsDeferredTransmissions = stats->stat_Dot3StatsDeferredTransmissions; sc->stat_Dot3StatsExcessiveCollisions = stats->stat_Dot3StatsExcessiveCollisions; sc->stat_Dot3StatsLateCollisions = stats->stat_Dot3StatsLateCollisions; sc->stat_EtherStatsCollisions = stats->stat_EtherStatsCollisions; sc->stat_EtherStatsFragments = stats->stat_EtherStatsFragments; sc->stat_EtherStatsJabbers = stats->stat_EtherStatsJabbers; sc->stat_EtherStatsUndersizePkts = stats->stat_EtherStatsUndersizePkts; sc->stat_EtherStatsOverrsizePkts = stats->stat_EtherStatsOverrsizePkts; sc->stat_EtherStatsPktsRx64Octets = stats->stat_EtherStatsPktsRx64Octets; sc->stat_EtherStatsPktsRx65Octetsto127Octets = stats->stat_EtherStatsPktsRx65Octetsto127Octets; sc->stat_EtherStatsPktsRx128Octetsto255Octets = stats->stat_EtherStatsPktsRx128Octetsto255Octets; sc->stat_EtherStatsPktsRx256Octetsto511Octets = stats->stat_EtherStatsPktsRx256Octetsto511Octets; sc->stat_EtherStatsPktsRx512Octetsto1023Octets = stats->stat_EtherStatsPktsRx512Octetsto1023Octets; sc->stat_EtherStatsPktsRx1024Octetsto1522Octets = stats->stat_EtherStatsPktsRx1024Octetsto1522Octets; sc->stat_EtherStatsPktsRx1523Octetsto9022Octets = stats->stat_EtherStatsPktsRx1523Octetsto9022Octets; sc->stat_EtherStatsPktsTx64Octets = stats->stat_EtherStatsPktsTx64Octets; sc->stat_EtherStatsPktsTx65Octetsto127Octets = stats->stat_EtherStatsPktsTx65Octetsto127Octets; sc->stat_EtherStatsPktsTx128Octetsto255Octets = stats->stat_EtherStatsPktsTx128Octetsto255Octets; sc->stat_EtherStatsPktsTx256Octetsto511Octets = stats->stat_EtherStatsPktsTx256Octetsto511Octets; sc->stat_EtherStatsPktsTx512Octetsto1023Octets = stats->stat_EtherStatsPktsTx512Octetsto1023Octets; sc->stat_EtherStatsPktsTx1024Octetsto1522Octets = stats->stat_EtherStatsPktsTx1024Octetsto1522Octets; sc->stat_EtherStatsPktsTx1523Octetsto9022Octets = stats->stat_EtherStatsPktsTx1523Octetsto9022Octets; sc->stat_XonPauseFramesReceived = stats->stat_XonPauseFramesReceived; sc->stat_XoffPauseFramesReceived = stats->stat_XoffPauseFramesReceived; sc->stat_OutXonSent = stats->stat_OutXonSent; sc->stat_OutXoffSent = stats->stat_OutXoffSent; sc->stat_FlowControlDone = stats->stat_FlowControlDone; sc->stat_MacControlFramesReceived = stats->stat_MacControlFramesReceived; sc->stat_XoffStateEntered = stats->stat_XoffStateEntered; sc->stat_IfInFramesL2FilterDiscards = stats->stat_IfInFramesL2FilterDiscards; sc->stat_IfInRuleCheckerDiscards = stats->stat_IfInRuleCheckerDiscards; sc->stat_IfInFTQDiscards = stats->stat_IfInFTQDiscards; sc->stat_IfInMBUFDiscards = stats->stat_IfInMBUFDiscards; sc->stat_IfInRuleCheckerP4Hit = stats->stat_IfInRuleCheckerP4Hit; sc->stat_CatchupInRuleCheckerDiscards = stats->stat_CatchupInRuleCheckerDiscards; sc->stat_CatchupInFTQDiscards = stats->stat_CatchupInFTQDiscards; sc->stat_CatchupInMBUFDiscards = stats->stat_CatchupInMBUFDiscards; sc->stat_CatchupInRuleCheckerP4Hit = stats->stat_CatchupInRuleCheckerP4Hit; DBPRINT(sc, BCE_EXCESSIVE, "Exiting %s()\n", __FUNCTION__); } static void bce_tick_locked(struct bce_softc *sc) { struct mii_data *mii = NULL; struct ifnet *ifp; u32 msg; ifp = sc->bce_ifp; BCE_LOCK_ASSERT(sc); /* Tell the firmware that the driver is still running. */ #ifdef BCE_DEBUG msg = (u32) BCE_DRV_MSG_DATA_PULSE_CODE_ALWAYS_ALIVE; #else msg = (u32) ++sc->bce_fw_drv_pulse_wr_seq; #endif REG_WR_IND(sc, sc->bce_shmem_base + BCE_DRV_PULSE_MB, msg); /* Update the statistics from the hardware statistics block. */ bce_stats_update(sc); /* Schedule the next tick. */ callout_reset( &sc->bce_stat_ch, /* callout */ hz, /* ticks */ bce_tick, /* function */ sc); /* function argument */ /* If link is up already up then we're done. */ if (sc->bce_link) goto bce_tick_locked_exit; /* DRC - ToDo: Add SerDes support and check SerDes link here. */ mii = device_get_softc(sc->bce_miibus); mii_tick(mii); /* Check if the link has come up. */ if (!sc->bce_link && mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->bce_link++; if ((IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T || IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_SX) && bootverbose) BCE_PRINTF(sc, "Gigabit link up\n"); /* Now that link is up, handle any outstanding TX traffic. */ if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) bce_start_locked(ifp); } bce_tick_locked_exit: return; } static void bce_tick(void *xsc) { struct bce_softc *sc; sc = xsc; BCE_LOCK(sc); bce_tick_locked(sc); BCE_UNLOCK(sc); } #ifdef BCE_DEBUG /****************************************************************************/ /* Allows the driver state to be dumped through the sysctl interface. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_sysctl_driver_state(SYSCTL_HANDLER_ARGS) { int error; int result; struct bce_softc *sc; result = -1; error = sysctl_handle_int(oidp, &result, 0, req); if (error || !req->newptr) return (error); if (result == 1) { sc = (struct bce_softc *)arg1; bce_dump_driver_state(sc); } return error; } /****************************************************************************/ /* Allows the hardware state to be dumped through the sysctl interface. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_sysctl_hw_state(SYSCTL_HANDLER_ARGS) { int error; int result; struct bce_softc *sc; result = -1; error = sysctl_handle_int(oidp, &result, 0, req); if (error || !req->newptr) return (error); if (result == 1) { sc = (struct bce_softc *)arg1; bce_dump_hw_state(sc); } return error; } /****************************************************************************/ /* */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_sysctl_dump_rx_chain(SYSCTL_HANDLER_ARGS) { int error; int result; struct bce_softc *sc; result = -1; error = sysctl_handle_int(oidp, &result, 0, req); if (error || !req->newptr) return (error); if (result == 1) { sc = (struct bce_softc *)arg1; bce_dump_rx_chain(sc, 0, USABLE_RX_BD); } return error; } /****************************************************************************/ /* */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static int bce_sysctl_breakpoint(SYSCTL_HANDLER_ARGS) { int error; int result; struct bce_softc *sc; result = -1; error = sysctl_handle_int(oidp, &result, 0, req); if (error || !req->newptr) return (error); if (result == 1) { sc = (struct bce_softc *)arg1; bce_breakpoint(sc); } return error; } #endif /****************************************************************************/ /* Adds any sysctl parameters for tuning or debugging purposes. */ /* */ /* Returns: */ /* 0 for success, positive value for failure. */ /****************************************************************************/ static void bce_add_sysctls(struct bce_softc *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid_list *children; ctx = device_get_sysctl_ctx(sc->bce_dev); children = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->bce_dev)); SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "driver_version", CTLFLAG_RD, &bce_driver_version, 0, "bce driver version"); #ifdef BCE_DEBUG SYSCTL_ADD_INT(ctx, children, OID_AUTO, "rx_low_watermark", CTLFLAG_RD, &sc->rx_low_watermark, 0, "Lowest level of free rx_bd's"); SYSCTL_ADD_INT(ctx, children, OID_AUTO, "tx_hi_watermark", CTLFLAG_RD, &sc->tx_hi_watermark, 0, "Highest level of used tx_bd's"); SYSCTL_ADD_INT(ctx, children, OID_AUTO, "l2fhdr_status_errors", CTLFLAG_RD, &sc->l2fhdr_status_errors, 0, "l2_fhdr status errors"); SYSCTL_ADD_INT(ctx, children, OID_AUTO, "unexpected_attentions", CTLFLAG_RD, &sc->unexpected_attentions, 0, "unexpected attentions"); SYSCTL_ADD_INT(ctx, children, OID_AUTO, "lost_status_block_updates", CTLFLAG_RD, &sc->lost_status_block_updates, 0, "lost status block updates"); SYSCTL_ADD_INT(ctx, children, OID_AUTO, "mbuf_alloc_failed", CTLFLAG_RD, &sc->mbuf_alloc_failed, 0, "mbuf cluster allocation failures"); #endif SYSCTL_ADD_ULONG(ctx, children, OID_AUTO, "stat_IfHcInOctets", CTLFLAG_RD, &sc->stat_IfHCInOctets, "Bytes received"); SYSCTL_ADD_ULONG(ctx, children, OID_AUTO, "stat_IfHCInBadOctets", CTLFLAG_RD, &sc->stat_IfHCInBadOctets, "Bad bytes received"); SYSCTL_ADD_ULONG(ctx, children, OID_AUTO, "stat_IfHCOutOctets", CTLFLAG_RD, &sc->stat_IfHCOutOctets, "Bytes sent"); SYSCTL_ADD_ULONG(ctx, children, OID_AUTO, "stat_IfHCOutBadOctets", CTLFLAG_RD, &sc->stat_IfHCOutBadOctets, "Bad bytes sent"); SYSCTL_ADD_ULONG(ctx, children, OID_AUTO, "stat_IfHCInUcastPkts", CTLFLAG_RD, &sc->stat_IfHCInUcastPkts, "Unicast packets received"); SYSCTL_ADD_ULONG(ctx, children, OID_AUTO, "stat_IfHCInMulticastPkts", CTLFLAG_RD, &sc->stat_IfHCInMulticastPkts, "Multicast packets received"); SYSCTL_ADD_ULONG(ctx, children, OID_AUTO, "stat_IfHCInBroadcastPkts", CTLFLAG_RD, &sc->stat_IfHCInBroadcastPkts, "Broadcast packets received"); SYSCTL_ADD_ULONG(ctx, children, OID_AUTO, "stat_IfHCOutUcastPkts", CTLFLAG_RD, &sc->stat_IfHCOutUcastPkts, "Unicast packets sent"); SYSCTL_ADD_ULONG(ctx, children, OID_AUTO, "stat_IfHCOutMulticastPkts", CTLFLAG_RD, &sc->stat_IfHCOutMulticastPkts, "Multicast packets sent"); SYSCTL_ADD_ULONG(ctx, children, OID_AUTO, "stat_IfHCOutBroadcastPkts", CTLFLAG_RD, &sc->stat_IfHCOutBroadcastPkts, "Broadcast packets sent"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_emac_tx_stat_dot3statsinternalmactransmiterrors", CTLFLAG_RD, &sc->stat_emac_tx_stat_dot3statsinternalmactransmiterrors, 0, "Internal MAC transmit errors"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_Dot3StatsCarrierSenseErrors", CTLFLAG_RD, &sc->stat_Dot3StatsCarrierSenseErrors, 0, "Carrier sense errors"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_Dot3StatsFCSErrors", CTLFLAG_RD, &sc->stat_Dot3StatsFCSErrors, 0, "Frame check sequence errors"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_Dot3StatsAlignmentErrors", CTLFLAG_RD, &sc->stat_Dot3StatsAlignmentErrors, 0, "Alignment errors"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_Dot3StatsSingleCollisionFrames", CTLFLAG_RD, &sc->stat_Dot3StatsSingleCollisionFrames, 0, "Single Collision Frames"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_Dot3StatsMultipleCollisionFrames", CTLFLAG_RD, &sc->stat_Dot3StatsMultipleCollisionFrames, 0, "Multiple Collision Frames"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_Dot3StatsDeferredTransmissions", CTLFLAG_RD, &sc->stat_Dot3StatsDeferredTransmissions, 0, "Deferred Transmissions"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_Dot3StatsExcessiveCollisions", CTLFLAG_RD, &sc->stat_Dot3StatsExcessiveCollisions, 0, "Excessive Collisions"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_Dot3StatsLateCollisions", CTLFLAG_RD, &sc->stat_Dot3StatsLateCollisions, 0, "Late Collisions"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsCollisions", CTLFLAG_RD, &sc->stat_EtherStatsCollisions, 0, "Collisions"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsFragments", CTLFLAG_RD, &sc->stat_EtherStatsFragments, 0, "Fragments"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsJabbers", CTLFLAG_RD, &sc->stat_EtherStatsJabbers, 0, "Jabbers"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsUndersizePkts", CTLFLAG_RD, &sc->stat_EtherStatsUndersizePkts, 0, "Undersize packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsOverrsizePkts", CTLFLAG_RD, &sc->stat_EtherStatsOverrsizePkts, 0, "stat_EtherStatsOverrsizePkts"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsRx64Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsRx64Octets, 0, "Bytes received in 64 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsRx65Octetsto127Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsRx65Octetsto127Octets, 0, "Bytes received in 65 to 127 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsRx128Octetsto255Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsRx128Octetsto255Octets, 0, "Bytes received in 128 to 255 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsRx256Octetsto511Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsRx256Octetsto511Octets, 0, "Bytes received in 256 to 511 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsRx512Octetsto1023Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsRx512Octetsto1023Octets, 0, "Bytes received in 512 to 1023 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsRx1024Octetsto1522Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsRx1024Octetsto1522Octets, 0, "Bytes received in 1024 t0 1522 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsRx1523Octetsto9022Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsRx1523Octetsto9022Octets, 0, "Bytes received in 1523 to 9022 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsTx64Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsTx64Octets, 0, "Bytes sent in 64 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsTx65Octetsto127Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsTx65Octetsto127Octets, 0, "Bytes sent in 65 to 127 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsTx128Octetsto255Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsTx128Octetsto255Octets, 0, "Bytes sent in 128 to 255 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsTx256Octetsto511Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsTx256Octetsto511Octets, 0, "Bytes sent in 256 to 511 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsTx512Octetsto1023Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsTx512Octetsto1023Octets, 0, "Bytes sent in 512 to 1023 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsTx1024Octetsto1522Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsTx1024Octetsto1522Octets, 0, "Bytes sent in 1024 to 1522 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_EtherStatsPktsTx1523Octetsto9022Octets", CTLFLAG_RD, &sc->stat_EtherStatsPktsTx1523Octetsto9022Octets, 0, "Bytes sent in 1523 to 9022 byte packets"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_XonPauseFramesReceived", CTLFLAG_RD, &sc->stat_XonPauseFramesReceived, 0, "XON pause frames receved"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_XoffPauseFramesReceived", CTLFLAG_RD, &sc->stat_XoffPauseFramesReceived, 0, "XOFF pause frames received"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_OutXonSent", CTLFLAG_RD, &sc->stat_OutXonSent, 0, "XON pause frames sent"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_OutXoffSent", CTLFLAG_RD, &sc->stat_OutXoffSent, 0, "XOFF pause frames sent"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_FlowControlDone", CTLFLAG_RD, &sc->stat_FlowControlDone, 0, "Flow control done"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_MacControlFramesReceived", CTLFLAG_RD, &sc->stat_MacControlFramesReceived, 0, "MAC control frames received"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_XoffStateEntered", CTLFLAG_RD, &sc->stat_XoffStateEntered, 0, "XOFF state entered"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_IfInFramesL2FilterDiscards", CTLFLAG_RD, &sc->stat_IfInFramesL2FilterDiscards, 0, "Received L2 packets discarded"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_IfInRuleCheckerDiscards", CTLFLAG_RD, &sc->stat_IfInRuleCheckerDiscards, 0, "Received packets discarded by rule"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_IfInFTQDiscards", CTLFLAG_RD, &sc->stat_IfInFTQDiscards, 0, "Received packet FTQ discards"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_IfInMBUFDiscards", CTLFLAG_RD, &sc->stat_IfInMBUFDiscards, 0, "Received packets discarded due to lack of controller buffer memory"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_IfInRuleCheckerP4Hit", CTLFLAG_RD, &sc->stat_IfInRuleCheckerP4Hit, 0, "Received packets rule checker hits"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_CatchupInRuleCheckerDiscards", CTLFLAG_RD, &sc->stat_CatchupInRuleCheckerDiscards, 0, "Received packets discarded in Catchup path"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_CatchupInFTQDiscards", CTLFLAG_RD, &sc->stat_CatchupInFTQDiscards, 0, "Received packets discarded in FTQ in Catchup path"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_CatchupInMBUFDiscards", CTLFLAG_RD, &sc->stat_CatchupInMBUFDiscards, 0, "Received packets discarded in controller buffer memory in Catchup path"); SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "stat_CatchupInRuleCheckerP4Hit", CTLFLAG_RD, &sc->stat_CatchupInRuleCheckerP4Hit, 0, "Received packets rule checker hits in Catchup path"); #ifdef BCE_DEBUG SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "driver_state", CTLTYPE_INT | CTLFLAG_RW, (void *)sc, 0, bce_sysctl_driver_state, "I", "Drive state information"); SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "hw_state", CTLTYPE_INT | CTLFLAG_RW, (void *)sc, 0, bce_sysctl_hw_state, "I", "Hardware state information"); SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "dump_rx_chain", CTLTYPE_INT | CTLFLAG_RW, (void *)sc, 0, bce_sysctl_dump_rx_chain, "I", "Dump rx_bd chain"); SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "breakpoint", CTLTYPE_INT | CTLFLAG_RW, (void *)sc, 0, bce_sysctl_breakpoint, "I", "Driver breakpoint"); #endif } /****************************************************************************/ /* BCE Debug Routines */ /****************************************************************************/ #ifdef BCE_DEBUG /****************************************************************************/ /* Prints out information about an mbuf. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_dump_mbuf(struct bce_softc *sc, struct mbuf *m) { u32 val_hi, val_lo; struct mbuf *mp = m; if (m == NULL) { /* Index out of range. */ printf("mbuf ptr is null!\n"); return; } while (mp) { val_hi = BCE_ADDR_HI(mp); val_lo = BCE_ADDR_LO(mp); BCE_PRINTF(sc, "mbuf: vaddr = 0x%08X:%08X, m_len = %d, m_flags = ", val_hi, val_lo, mp->m_len); if (mp->m_flags & M_EXT) printf("M_EXT "); if (mp->m_flags & M_PKTHDR) printf("M_PKTHDR "); printf("\n"); if (mp->m_flags & M_EXT) { val_hi = BCE_ADDR_HI(mp->m_ext.ext_buf); val_lo = BCE_ADDR_LO(mp->m_ext.ext_buf); BCE_PRINTF(sc, "- m_ext: vaddr = 0x%08X:%08X, ext_size = 0x%04X\n", val_hi, val_lo, mp->m_ext.ext_size); } mp = mp->m_next; } } /****************************************************************************/ /* Prints out the mbufs in the TX mbuf chain. */ /* */ /* Returns: */ /* Nothing. */ /****************************************************************************/ static void bce_dump_tx_mbuf_chain(struct bce_softc *sc, int chain_prod, int count) { struct mbuf *m; BCE_PRINTF(sc, "----------------------------" " tx mbuf data " "----------------------------\n"); for (int i = 0; i < count; i++) { m = sc->tx_mbuf_ptr[chain_prod]; BCE_PRINTF(sc, "txmbuf[%d]\n", chain_prod); bce_dump_mbuf(sc, m); chain_prod = TX_CHAIN_IDX(NEXT_TX_BD(chain_prod)); } BCE_PRINTF(sc, "----------------------------" "----------------" "----------------------------\n"); } /* * This routine prints the RX mbuf chain. */ static void bce_dump_rx_mbuf_chain(struct bce_softc *sc, int chain_prod, int count) { struct mbuf *m; BCE_PRINTF(sc, "----------------------------" " rx mbuf data " "----------------------------\n"); for (int i = 0; i < count; i++) { m = sc->rx_mbuf_ptr[chain_prod]; BCE_PRINTF(sc, "rxmbuf[0x%04X]\n", chain_prod); bce_dump_mbuf(sc, m); chain_prod = RX_CHAIN_IDX(NEXT_RX_BD(chain_prod)); } BCE_PRINTF(sc, "----------------------------" "----------------" "----------------------------\n"); } static void bce_dump_txbd(struct bce_softc *sc, int idx, struct tx_bd *txbd) { if (idx > MAX_TX_BD) /* Index out of range. */ BCE_PRINTF(sc, "tx_bd[0x%04X]: Invalid tx_bd index!\n", idx); else if ((idx & USABLE_TX_BD_PER_PAGE) == USABLE_TX_BD_PER_PAGE) /* TX Chain page pointer. */ BCE_PRINTF(sc, "tx_bd[0x%04X]: haddr = 0x%08X:%08X, chain page pointer\n", idx, txbd->tx_bd_haddr_hi, txbd->tx_bd_haddr_lo); else /* Normal tx_bd entry. */ BCE_PRINTF(sc, "tx_bd[0x%04X]: haddr = 0x%08X:%08X, nbytes = 0x%08X, " "flags = 0x%08X\n", idx, txbd->tx_bd_haddr_hi, txbd->tx_bd_haddr_lo, txbd->tx_bd_mss_nbytes, txbd->tx_bd_vlan_tag_flags); } static void bce_dump_rxbd(struct bce_softc *sc, int idx, struct rx_bd *rxbd) { if (idx > MAX_RX_BD) /* Index out of range. */ BCE_PRINTF(sc, "rx_bd[0x%04X]: Invalid rx_bd index!\n", idx); else if ((idx & USABLE_RX_BD_PER_PAGE) == USABLE_RX_BD_PER_PAGE) /* TX Chain page pointer. */ BCE_PRINTF(sc, "rx_bd[0x%04X]: haddr = 0x%08X:%08X, chain page pointer\n", idx, rxbd->rx_bd_haddr_hi, rxbd->rx_bd_haddr_lo); else /* Normal tx_bd entry. */ BCE_PRINTF(sc, "rx_bd[0x%04X]: haddr = 0x%08X:%08X, nbytes = 0x%08X, " "flags = 0x%08X\n", idx, rxbd->rx_bd_haddr_hi, rxbd->rx_bd_haddr_lo, rxbd->rx_bd_len, rxbd->rx_bd_flags); } static void bce_dump_l2fhdr(struct bce_softc *sc, int idx, struct l2_fhdr *l2fhdr) { BCE_PRINTF(sc, "l2_fhdr[0x%04X]: status = 0x%08X, " "pkt_len = 0x%04X, vlan = 0x%04x, ip_xsum = 0x%04X, " "tcp_udp_xsum = 0x%04X\n", idx, l2fhdr->l2_fhdr_status, l2fhdr->l2_fhdr_pkt_len, l2fhdr->l2_fhdr_vlan_tag, l2fhdr->l2_fhdr_ip_xsum, l2fhdr->l2_fhdr_tcp_udp_xsum); } /* * This routine prints the TX chain. */ static void bce_dump_tx_chain(struct bce_softc *sc, int tx_prod, int count) { struct tx_bd *txbd; /* First some info about the tx_bd chain structure. */ BCE_PRINTF(sc, "----------------------------" " tx_bd chain " "----------------------------\n"); BCE_PRINTF(sc, "page size = 0x%08X, tx chain pages = 0x%08X\n", (u32) BCM_PAGE_SIZE, (u32) TX_PAGES); BCE_PRINTF(sc, "tx_bd per page = 0x%08X, usable tx_bd per page = 0x%08X\n", (u32) TOTAL_TX_BD_PER_PAGE, (u32) USABLE_TX_BD_PER_PAGE); BCE_PRINTF(sc, "total tx_bd = 0x%08X\n", (u32) TOTAL_TX_BD); BCE_PRINTF(sc, "" "-----------------------------" " tx_bd data " "-----------------------------\n"); /* Now print out the tx_bd's themselves. */ for (int i = 0; i < count; i++) { txbd = &sc->tx_bd_chain[TX_PAGE(tx_prod)][TX_IDX(tx_prod)]; bce_dump_txbd(sc, tx_prod, txbd); tx_prod = TX_CHAIN_IDX(NEXT_TX_BD(tx_prod)); } BCE_PRINTF(sc, "-----------------------------" "--------------" "-----------------------------\n"); } /* * This routine prints the RX chain. */ static void bce_dump_rx_chain(struct bce_softc *sc, int rx_prod, int count) { struct rx_bd *rxbd; /* First some info about the tx_bd chain structure. */ BCE_PRINTF(sc, "----------------------------" " rx_bd chain " "----------------------------\n"); BCE_PRINTF(sc, "----- RX_BD Chain -----\n"); BCE_PRINTF(sc, "page size = 0x%08X, rx chain pages = 0x%08X\n", (u32) BCM_PAGE_SIZE, (u32) RX_PAGES); BCE_PRINTF(sc, "rx_bd per page = 0x%08X, usable rx_bd per page = 0x%08X\n", (u32) TOTAL_RX_BD_PER_PAGE, (u32) USABLE_RX_BD_PER_PAGE); BCE_PRINTF(sc, "total rx_bd = 0x%08X\n", (u32) TOTAL_RX_BD); BCE_PRINTF(sc, "----------------------------" " rx_bd data " "----------------------------\n"); /* Now print out the rx_bd's themselves. */ for (int i = 0; i < count; i++) { rxbd = &sc->rx_bd_chain[RX_PAGE(rx_prod)][RX_IDX(rx_prod)]; bce_dump_rxbd(sc, rx_prod, rxbd); rx_prod = RX_CHAIN_IDX(NEXT_RX_BD(rx_prod)); } BCE_PRINTF(sc, "----------------------------" "--------------" "----------------------------\n"); } /* * This routine prints the status block. */ static void bce_dump_status_block(struct bce_softc *sc) { struct status_block *sblk; sblk = sc->status_block; BCE_PRINTF(sc, "----------------------------- Status Block " "-----------------------------\n"); BCE_PRINTF(sc, "attn_bits = 0x%08X, attn_bits_ack = 0x%08X, index = 0x%04X\n", sblk->status_attn_bits, sblk->status_attn_bits_ack, sblk->status_idx); BCE_PRINTF(sc, "rx_cons0 = 0x%08X, tx_cons0 = 0x%08X\n", sblk->status_rx_quick_consumer_index0, sblk->status_tx_quick_consumer_index0); BCE_PRINTF(sc, "status_idx = 0x%04X\n", sblk->status_idx); /* Theses indices are not used for normal L2 drivers. */ if (sblk->status_rx_quick_consumer_index1 || sblk->status_tx_quick_consumer_index1) BCE_PRINTF(sc, "rx_cons1 = 0x%08X, tx_cons1 = 0x%08X\n", sblk->status_rx_quick_consumer_index1, sblk->status_tx_quick_consumer_index1); if (sblk->status_rx_quick_consumer_index2 || sblk->status_tx_quick_consumer_index2) BCE_PRINTF(sc, "rx_cons2 = 0x%08X, tx_cons2 = 0x%08X\n", sblk->status_rx_quick_consumer_index2, sblk->status_tx_quick_consumer_index2); if (sblk->status_rx_quick_consumer_index3 || sblk->status_tx_quick_consumer_index3) BCE_PRINTF(sc, "rx_cons3 = 0x%08X, tx_cons3 = 0x%08X\n", sblk->status_rx_quick_consumer_index3, sblk->status_tx_quick_consumer_index3); if (sblk->status_rx_quick_consumer_index4 || sblk->status_rx_quick_consumer_index5) BCE_PRINTF(sc, "rx_cons4 = 0x%08X, rx_cons5 = 0x%08X\n", sblk->status_rx_quick_consumer_index4, sblk->status_rx_quick_consumer_index5); if (sblk->status_rx_quick_consumer_index6 || sblk->status_rx_quick_consumer_index7) BCE_PRINTF(sc, "rx_cons6 = 0x%08X, rx_cons7 = 0x%08X\n", sblk->status_rx_quick_consumer_index6, sblk->status_rx_quick_consumer_index7); if (sblk->status_rx_quick_consumer_index8 || sblk->status_rx_quick_consumer_index9) BCE_PRINTF(sc, "rx_cons8 = 0x%08X, rx_cons9 = 0x%08X\n", sblk->status_rx_quick_consumer_index8, sblk->status_rx_quick_consumer_index9); if (sblk->status_rx_quick_consumer_index10 || sblk->status_rx_quick_consumer_index11) BCE_PRINTF(sc, "rx_cons10 = 0x%08X, rx_cons11 = 0x%08X\n", sblk->status_rx_quick_consumer_index10, sblk->status_rx_quick_consumer_index11); if (sblk->status_rx_quick_consumer_index12 || sblk->status_rx_quick_consumer_index13) BCE_PRINTF(sc, "rx_cons12 = 0x%08X, rx_cons13 = 0x%08X\n", sblk->status_rx_quick_consumer_index12, sblk->status_rx_quick_consumer_index13); if (sblk->status_rx_quick_consumer_index14 || sblk->status_rx_quick_consumer_index15) BCE_PRINTF(sc, "rx_cons14 = 0x%08X, rx_cons15 = 0x%08X\n", sblk->status_rx_quick_consumer_index14, sblk->status_rx_quick_consumer_index15); if (sblk->status_completion_producer_index || sblk->status_cmd_consumer_index) BCE_PRINTF(sc, "com_prod = 0x%08X, cmd_cons = 0x%08X\n", sblk->status_completion_producer_index, sblk->status_cmd_consumer_index); BCE_PRINTF(sc, "-------------------------------------------" "-----------------------------\n"); } /* * This routine prints the statistics block. */ static void bce_dump_stats_block(struct bce_softc *sc) { struct statistics_block *sblk; sblk = sc->stats_block; BCE_PRINTF(sc, "" "-----------------------------" " Stats Block " "-----------------------------\n"); BCE_PRINTF(sc, "IfHcInOctets = 0x%08X:%08X, " "IfHcInBadOctets = 0x%08X:%08X\n", sblk->stat_IfHCInOctets_hi, sblk->stat_IfHCInOctets_lo, sblk->stat_IfHCInBadOctets_hi, sblk->stat_IfHCInBadOctets_lo); BCE_PRINTF(sc, "IfHcOutOctets = 0x%08X:%08X, " "IfHcOutBadOctets = 0x%08X:%08X\n", sblk->stat_IfHCOutOctets_hi, sblk->stat_IfHCOutOctets_lo, sblk->stat_IfHCOutBadOctets_hi, sblk->stat_IfHCOutBadOctets_lo); BCE_PRINTF(sc, "IfHcInUcastPkts = 0x%08X:%08X, " "IfHcInMulticastPkts = 0x%08X:%08X\n", sblk->stat_IfHCInUcastPkts_hi, sblk->stat_IfHCInUcastPkts_lo, sblk->stat_IfHCInMulticastPkts_hi, sblk->stat_IfHCInMulticastPkts_lo); BCE_PRINTF(sc, "IfHcInBroadcastPkts = 0x%08X:%08X, " "IfHcOutUcastPkts = 0x%08X:%08X\n", sblk->stat_IfHCInBroadcastPkts_hi, sblk->stat_IfHCInBroadcastPkts_lo, sblk->stat_IfHCOutUcastPkts_hi, sblk->stat_IfHCOutUcastPkts_lo); BCE_PRINTF(sc, "IfHcOutMulticastPkts = 0x%08X:%08X, IfHcOutBroadcastPkts = 0x%08X:%08X\n", sblk->stat_IfHCOutMulticastPkts_hi, sblk->stat_IfHCOutMulticastPkts_lo, sblk->stat_IfHCOutBroadcastPkts_hi, sblk->stat_IfHCOutBroadcastPkts_lo); if (sblk->stat_emac_tx_stat_dot3statsinternalmactransmiterrors) BCE_PRINTF(sc, "0x%08X : " "emac_tx_stat_dot3statsinternalmactransmiterrors\n", sblk->stat_emac_tx_stat_dot3statsinternalmactransmiterrors); if (sblk->stat_Dot3StatsCarrierSenseErrors) BCE_PRINTF(sc, "0x%08X : Dot3StatsCarrierSenseErrors\n", sblk->stat_Dot3StatsCarrierSenseErrors); if (sblk->stat_Dot3StatsFCSErrors) BCE_PRINTF(sc, "0x%08X : Dot3StatsFCSErrors\n", sblk->stat_Dot3StatsFCSErrors); if (sblk->stat_Dot3StatsAlignmentErrors) BCE_PRINTF(sc, "0x%08X : Dot3StatsAlignmentErrors\n", sblk->stat_Dot3StatsAlignmentErrors); if (sblk->stat_Dot3StatsSingleCollisionFrames) BCE_PRINTF(sc, "0x%08X : Dot3StatsSingleCollisionFrames\n", sblk->stat_Dot3StatsSingleCollisionFrames); if (sblk->stat_Dot3StatsMultipleCollisionFrames) BCE_PRINTF(sc, "0x%08X : Dot3StatsMultipleCollisionFrames\n", sblk->stat_Dot3StatsMultipleCollisionFrames); if (sblk->stat_Dot3StatsDeferredTransmissions) BCE_PRINTF(sc, "0x%08X : Dot3StatsDeferredTransmissions\n", sblk->stat_Dot3StatsDeferredTransmissions); if (sblk->stat_Dot3StatsExcessiveCollisions) BCE_PRINTF(sc, "0x%08X : Dot3StatsExcessiveCollisions\n", sblk->stat_Dot3StatsExcessiveCollisions); if (sblk->stat_Dot3StatsLateCollisions) BCE_PRINTF(sc, "0x%08X : Dot3StatsLateCollisions\n", sblk->stat_Dot3StatsLateCollisions); if (sblk->stat_EtherStatsCollisions) BCE_PRINTF(sc, "0x%08X : EtherStatsCollisions\n", sblk->stat_EtherStatsCollisions); if (sblk->stat_EtherStatsFragments) BCE_PRINTF(sc, "0x%08X : EtherStatsFragments\n", sblk->stat_EtherStatsFragments); if (sblk->stat_EtherStatsJabbers) BCE_PRINTF(sc, "0x%08X : EtherStatsJabbers\n", sblk->stat_EtherStatsJabbers); if (sblk->stat_EtherStatsUndersizePkts) BCE_PRINTF(sc, "0x%08X : EtherStatsUndersizePkts\n", sblk->stat_EtherStatsUndersizePkts); if (sblk->stat_EtherStatsOverrsizePkts) BCE_PRINTF(sc, "0x%08X : EtherStatsOverrsizePkts\n", sblk->stat_EtherStatsOverrsizePkts); if (sblk->stat_EtherStatsPktsRx64Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsRx64Octets\n", sblk->stat_EtherStatsPktsRx64Octets); if (sblk->stat_EtherStatsPktsRx65Octetsto127Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsRx65Octetsto127Octets\n", sblk->stat_EtherStatsPktsRx65Octetsto127Octets); if (sblk->stat_EtherStatsPktsRx128Octetsto255Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsRx128Octetsto255Octets\n", sblk->stat_EtherStatsPktsRx128Octetsto255Octets); if (sblk->stat_EtherStatsPktsRx256Octetsto511Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsRx256Octetsto511Octets\n", sblk->stat_EtherStatsPktsRx256Octetsto511Octets); if (sblk->stat_EtherStatsPktsRx512Octetsto1023Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsRx512Octetsto1023Octets\n", sblk->stat_EtherStatsPktsRx512Octetsto1023Octets); if (sblk->stat_EtherStatsPktsRx1024Octetsto1522Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsRx1024Octetsto1522Octets\n", sblk->stat_EtherStatsPktsRx1024Octetsto1522Octets); if (sblk->stat_EtherStatsPktsRx1523Octetsto9022Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsRx1523Octetsto9022Octets\n", sblk->stat_EtherStatsPktsRx1523Octetsto9022Octets); if (sblk->stat_EtherStatsPktsTx64Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsTx64Octets\n", sblk->stat_EtherStatsPktsTx64Octets); if (sblk->stat_EtherStatsPktsTx65Octetsto127Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsTx65Octetsto127Octets\n", sblk->stat_EtherStatsPktsTx65Octetsto127Octets); if (sblk->stat_EtherStatsPktsTx128Octetsto255Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsTx128Octetsto255Octets\n", sblk->stat_EtherStatsPktsTx128Octetsto255Octets); if (sblk->stat_EtherStatsPktsTx256Octetsto511Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsTx256Octetsto511Octets\n", sblk->stat_EtherStatsPktsTx256Octetsto511Octets); if (sblk->stat_EtherStatsPktsTx512Octetsto1023Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsTx512Octetsto1023Octets\n", sblk->stat_EtherStatsPktsTx512Octetsto1023Octets); if (sblk->stat_EtherStatsPktsTx1024Octetsto1522Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsTx1024Octetsto1522Octets\n", sblk->stat_EtherStatsPktsTx1024Octetsto1522Octets); if (sblk->stat_EtherStatsPktsTx1523Octetsto9022Octets) BCE_PRINTF(sc, "0x%08X : EtherStatsPktsTx1523Octetsto9022Octets\n", sblk->stat_EtherStatsPktsTx1523Octetsto9022Octets); if (sblk->stat_XonPauseFramesReceived) BCE_PRINTF(sc, "0x%08X : XonPauseFramesReceived\n", sblk->stat_XonPauseFramesReceived); if (sblk->stat_XoffPauseFramesReceived) BCE_PRINTF(sc, "0x%08X : XoffPauseFramesReceived\n", sblk->stat_XoffPauseFramesReceived); if (sblk->stat_OutXonSent) BCE_PRINTF(sc, "0x%08X : OutXonSent\n", sblk->stat_OutXonSent); if (sblk->stat_OutXoffSent) BCE_PRINTF(sc, "0x%08X : OutXoffSent\n", sblk->stat_OutXoffSent); if (sblk->stat_FlowControlDone) BCE_PRINTF(sc, "0x%08X : FlowControlDone\n", sblk->stat_FlowControlDone); if (sblk->stat_MacControlFramesReceived) BCE_PRINTF(sc, "0x%08X : MacControlFramesReceived\n", sblk->stat_MacControlFramesReceived); if (sblk->stat_XoffStateEntered) BCE_PRINTF(sc, "0x%08X : XoffStateEntered\n", sblk->stat_XoffStateEntered); if (sblk->stat_IfInFramesL2FilterDiscards) BCE_PRINTF(sc, "0x%08X : IfInFramesL2FilterDiscards\n", sblk->stat_IfInFramesL2FilterDiscards); if (sblk->stat_IfInRuleCheckerDiscards) BCE_PRINTF(sc, "0x%08X : IfInRuleCheckerDiscards\n", sblk->stat_IfInRuleCheckerDiscards); if (sblk->stat_IfInFTQDiscards) BCE_PRINTF(sc, "0x%08X : IfInFTQDiscards\n", sblk->stat_IfInFTQDiscards); if (sblk->stat_IfInMBUFDiscards) BCE_PRINTF(sc, "0x%08X : IfInMBUFDiscards\n", sblk->stat_IfInMBUFDiscards); if (sblk->stat_IfInRuleCheckerP4Hit) BCE_PRINTF(sc, "0x%08X : IfInRuleCheckerP4Hit\n", sblk->stat_IfInRuleCheckerP4Hit); if (sblk->stat_CatchupInRuleCheckerDiscards) BCE_PRINTF(sc, "0x%08X : CatchupInRuleCheckerDiscards\n", sblk->stat_CatchupInRuleCheckerDiscards); if (sblk->stat_CatchupInFTQDiscards) BCE_PRINTF(sc, "0x%08X : CatchupInFTQDiscards\n", sblk->stat_CatchupInFTQDiscards); if (sblk->stat_CatchupInMBUFDiscards) BCE_PRINTF(sc, "0x%08X : CatchupInMBUFDiscards\n", sblk->stat_CatchupInMBUFDiscards); if (sblk->stat_CatchupInRuleCheckerP4Hit) BCE_PRINTF(sc, "0x%08X : CatchupInRuleCheckerP4Hit\n", sblk->stat_CatchupInRuleCheckerP4Hit); BCE_PRINTF(sc, "-----------------------------" "--------------" "-----------------------------\n"); } static void bce_dump_driver_state(struct bce_softc *sc) { u32 val_hi, val_lo; BCE_PRINTF(sc, "-----------------------------" " Driver State " "-----------------------------\n"); val_hi = BCE_ADDR_HI(sc); val_lo = BCE_ADDR_LO(sc); BCE_PRINTF(sc, "0x%08X:%08X - (sc) driver softc structure virtual address\n", val_hi, val_lo); val_hi = BCE_ADDR_HI(sc->bce_vhandle); val_lo = BCE_ADDR_LO(sc->bce_vhandle); BCE_PRINTF(sc, "0x%08X:%08X - (sc->bce_vhandle) PCI BAR virtual address\n", val_hi, val_lo); val_hi = BCE_ADDR_HI(sc->status_block); val_lo = BCE_ADDR_LO(sc->status_block); BCE_PRINTF(sc, "0x%08X:%08X - (sc->status_block) status block virtual address\n", val_hi, val_lo); val_hi = BCE_ADDR_HI(sc->stats_block); val_lo = BCE_ADDR_LO(sc->stats_block); BCE_PRINTF(sc, "0x%08X:%08X - (sc->stats_block) statistics block virtual address\n", val_hi, val_lo); val_hi = BCE_ADDR_HI(sc->tx_bd_chain); val_lo = BCE_ADDR_LO(sc->tx_bd_chain); BCE_PRINTF(sc, "0x%08X:%08X - (sc->tx_bd_chain) tx_bd chain virtual adddress\n", val_hi, val_lo); val_hi = BCE_ADDR_HI(sc->rx_bd_chain); val_lo = BCE_ADDR_LO(sc->rx_bd_chain); BCE_PRINTF(sc, "0x%08X:%08X - (sc->rx_bd_chain) rx_bd chain virtual address\n", val_hi, val_lo); val_hi = BCE_ADDR_HI(sc->tx_mbuf_ptr); val_lo = BCE_ADDR_LO(sc->tx_mbuf_ptr); BCE_PRINTF(sc, "0x%08X:%08X - (sc->tx_mbuf_ptr) tx mbuf chain virtual address\n", val_hi, val_lo); val_hi = BCE_ADDR_HI(sc->rx_mbuf_ptr); val_lo = BCE_ADDR_LO(sc->rx_mbuf_ptr); BCE_PRINTF(sc, "0x%08X:%08X - (sc->rx_mbuf_ptr) rx mbuf chain virtual address\n", val_hi, val_lo); BCE_PRINTF(sc, " 0x%08X - (sc->interrupts_generated) h/w intrs\n", sc->interrupts_generated); BCE_PRINTF(sc, " 0x%08X - (sc->rx_interrupts) rx interrupts handled\n", sc->rx_interrupts); BCE_PRINTF(sc, " 0x%08X - (sc->tx_interrupts) tx interrupts handled\n", sc->tx_interrupts); BCE_PRINTF(sc, " 0x%08X - (sc->last_status_idx) status block index\n", sc->last_status_idx); BCE_PRINTF(sc, " 0x%08X - (sc->tx_prod) tx producer index\n", sc->tx_prod); BCE_PRINTF(sc, " 0x%08X - (sc->tx_cons) tx consumer index\n", sc->tx_cons); BCE_PRINTF(sc, " 0x%08X - (sc->tx_prod_bseq) tx producer bseq index\n", sc->tx_prod_bseq); BCE_PRINTF(sc, " 0x%08X - (sc->rx_prod) rx producer index\n", sc->rx_prod); BCE_PRINTF(sc, " 0x%08X - (sc->rx_cons) rx consumer index\n", sc->rx_cons); BCE_PRINTF(sc, " 0x%08X - (sc->rx_prod_bseq) rx producer bseq index\n", sc->rx_prod_bseq); BCE_PRINTF(sc, " 0x%08X - (sc->rx_mbuf_alloc) rx mbufs allocated\n", sc->rx_mbuf_alloc); BCE_PRINTF(sc, " 0x%08X - (sc->free_rx_bd) free rx_bd's\n", sc->free_rx_bd); BCE_PRINTF(sc, "0x%08X/%08X - (sc->rx_low_watermark) rx low watermark\n", sc->rx_low_watermark, (u32) USABLE_RX_BD); BCE_PRINTF(sc, " 0x%08X - (sc->txmbuf_alloc) tx mbufs allocated\n", sc->tx_mbuf_alloc); BCE_PRINTF(sc, " 0x%08X - (sc->rx_mbuf_alloc) rx mbufs allocated\n", sc->rx_mbuf_alloc); BCE_PRINTF(sc, " 0x%08X - (sc->used_tx_bd) used tx_bd's\n", sc->used_tx_bd); BCE_PRINTF(sc, "0x%08X/%08X - (sc->tx_hi_watermark) tx hi watermark\n", sc->tx_hi_watermark, (u32) USABLE_TX_BD); BCE_PRINTF(sc, " 0x%08X - (sc->mbuf_alloc_failed) failed mbuf alloc\n", sc->mbuf_alloc_failed); BCE_PRINTF(sc, "-----------------------------" "--------------" "-----------------------------\n"); } static void bce_dump_hw_state(struct bce_softc *sc) { u32 val1; BCE_PRINTF(sc, "----------------------------" " Hardware State " "----------------------------\n"); BCE_PRINTF(sc, "0x%08X : bootcode version\n", sc->bce_fw_ver); val1 = REG_RD(sc, BCE_MISC_ENABLE_STATUS_BITS); BCE_PRINTF(sc, "0x%08X : (0x%04X) misc_enable_status_bits\n", val1, BCE_MISC_ENABLE_STATUS_BITS); val1 = REG_RD(sc, BCE_DMA_STATUS); BCE_PRINTF(sc, "0x%08X : (0x%04X) dma_status\n", val1, BCE_DMA_STATUS); val1 = REG_RD(sc, BCE_CTX_STATUS); BCE_PRINTF(sc, "0x%08X : (0x%04X) ctx_status\n", val1, BCE_CTX_STATUS); val1 = REG_RD(sc, BCE_EMAC_STATUS); BCE_PRINTF(sc, "0x%08X : (0x%04X) emac_status\n", val1, BCE_EMAC_STATUS); val1 = REG_RD(sc, BCE_RPM_STATUS); BCE_PRINTF(sc, "0x%08X : (0x%04X) rpm_status\n", val1, BCE_RPM_STATUS); val1 = REG_RD(sc, BCE_TBDR_STATUS); BCE_PRINTF(sc, "0x%08X : (0x%04X) tbdr_status\n", val1, BCE_TBDR_STATUS); val1 = REG_RD(sc, BCE_TDMA_STATUS); BCE_PRINTF(sc, "0x%08X : (0x%04X) tdma_status\n", val1, BCE_TDMA_STATUS); val1 = REG_RD(sc, BCE_HC_STATUS); BCE_PRINTF(sc, "0x%08X : (0x%04X) hc_status\n", val1, BCE_HC_STATUS); BCE_PRINTF(sc, "----------------------------" "----------------" "----------------------------\n"); BCE_PRINTF(sc, "----------------------------" " Register Dump " "----------------------------\n"); for (int i = 0x400; i < 0x8000; i += 0x10) BCE_PRINTF(sc, "0x%04X: 0x%08X 0x%08X 0x%08X 0x%08X\n", i, REG_RD(sc, i), REG_RD(sc, i + 0x4), REG_RD(sc, i + 0x8), REG_RD(sc, i + 0xC)); BCE_PRINTF(sc, "----------------------------" "----------------" "----------------------------\n"); } static void bce_breakpoint(struct bce_softc *sc) { /* Unreachable code to shut the compiler up about unused functions. */ if (0) { bce_dump_txbd(sc, 0, NULL); bce_dump_rxbd(sc, 0, NULL); bce_dump_tx_mbuf_chain(sc, 0, USABLE_TX_BD); bce_dump_rx_mbuf_chain(sc, 0, USABLE_RX_BD); bce_dump_l2fhdr(sc, 0, NULL); bce_dump_tx_chain(sc, 0, USABLE_TX_BD); bce_dump_rx_chain(sc, 0, USABLE_RX_BD); bce_dump_status_block(sc); bce_dump_stats_block(sc); bce_dump_driver_state(sc); bce_dump_hw_state(sc); } bce_dump_driver_state(sc); /* Print the important status block fields. */ bce_dump_status_block(sc); /* Call the debugger. */ breakpoint(); return; } #endif Index: head/sys/dev/bge/if_bge.c =================================================================== --- head/sys/dev/bge/if_bge.c (revision 162374) +++ head/sys/dev/bge/if_bge.c (revision 162375) @@ -1,3955 +1,3953 @@ /*- * Copyright (c) 2001 Wind River Systems * Copyright (c) 1997, 1998, 1999, 2001 * Bill Paul . 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * Broadcom BCM570x family gigabit ethernet driver for FreeBSD. * * The Broadcom BCM5700 is based on technology originally developed by * Alteon Networks as part of the Tigon I and Tigon II gigabit ethernet * MAC chips. The BCM5700, sometimes refered to as the Tigon III, has * two on-board MIPS R4000 CPUs and can have as much as 16MB of external * SSRAM. The BCM5700 supports TCP, UDP and IP checksum offload, jumbo * frames, highly configurable RX filtering, and 16 RX and TX queues * (which, along with RX filter rules, can be used for QOS applications). * Other features, such as TCP segmentation, may be available as part * of value-added firmware updates. Unlike the Tigon I and Tigon II, * firmware images can be stored in hardware and need not be compiled * into the driver. * * The BCM5700 supports the PCI v2.2 and PCI-X v1.0 standards, and will * function in a 32-bit/64-bit 33/66Mhz bus, or a 64-bit/133Mhz bus. * * The BCM5701 is a single-chip solution incorporating both the BCM5700 * MAC and a BCM5401 10/100/1000 PHY. Unlike the BCM5700, the BCM5701 * does not support external SSRAM. * * Broadcom also produces a variation of the BCM5700 under the "Altima" * brand name, which is functionally similar but lacks PCI-X support. * * Without external SSRAM, you can only have at most 4 TX rings, * and the use of the mini RX ring is disabled. This seems to imply * that these features are simply not available on the BCM5701. As a * result, this driver does not implement any support for the mini RX * ring. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_device_polling.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "miidevs.h" #include #include #include #include #define BGE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) #define ETHER_MIN_NOPAD (ETHER_MIN_LEN - ETHER_CRC_LEN) /* i.e., 60 */ MODULE_DEPEND(bge, pci, 1, 1, 1); MODULE_DEPEND(bge, ether, 1, 1, 1); MODULE_DEPEND(bge, miibus, 1, 1, 1); /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" /* * Various supported device vendors/types and their names. Note: the * spec seems to indicate that the hardware still has Alteon's vendor * ID burned into it, though it will always be overriden by the vendor * ID in the EEPROM. Just to be safe, we cover all possibilities. */ static struct bge_type { uint16_t bge_vid; uint16_t bge_did; } bge_devs[] = { { ALTEON_VENDORID, ALTEON_DEVICEID_BCM5700 }, { ALTEON_VENDORID, ALTEON_DEVICEID_BCM5701 }, { ALTIMA_VENDORID, ALTIMA_DEVICE_AC1000 }, { ALTIMA_VENDORID, ALTIMA_DEVICE_AC1002 }, { ALTIMA_VENDORID, ALTIMA_DEVICE_AC9100 }, { APPLE_VENDORID, APPLE_DEVICE_BCM5701 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5700 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5701 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5702 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5702_ALT }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5702X }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5703 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5703_ALT }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5703X }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5704C }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5704S }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5704S_ALT }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5705 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5705F }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5705K }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5705M }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5705M_ALT }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5714C }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5714S }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5715 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5715S }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5720 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5721 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5750 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5750M }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5751 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5751F }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5751M }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5752 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5752M }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5753 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5753F }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5753M }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5754 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5754M }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5755 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5755M }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5780 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5780S }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5781 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5782 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5786 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5787 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5787M }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5788 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5789 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5901 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5901A2 }, { BCOM_VENDORID, BCOM_DEVICEID_BCM5903M }, { SK_VENDORID, SK_DEVICEID_ALTIMA }, { TC_VENDORID, TC_DEVICEID_3C985 }, { TC_VENDORID, TC_DEVICEID_3C996 }, { 0, 0 } }; static const struct bge_vendor { uint16_t v_id; const char *v_name; } bge_vendors[] = { { ALTEON_VENDORID, "Alteon" }, { ALTIMA_VENDORID, "Altima" }, { APPLE_VENDORID, "Apple" }, { BCOM_VENDORID, "Broadcom" }, { SK_VENDORID, "SysKonnect" }, { TC_VENDORID, "3Com" }, { 0, NULL } }; static const struct bge_revision { uint32_t br_chipid; const char *br_name; } bge_revisions[] = { { BGE_CHIPID_BCM5700_A0, "BCM5700 A0" }, { BGE_CHIPID_BCM5700_A1, "BCM5700 A1" }, { BGE_CHIPID_BCM5700_B0, "BCM5700 B0" }, { BGE_CHIPID_BCM5700_B1, "BCM5700 B1" }, { BGE_CHIPID_BCM5700_B2, "BCM5700 B2" }, { BGE_CHIPID_BCM5700_B3, "BCM5700 B3" }, { BGE_CHIPID_BCM5700_ALTIMA, "BCM5700 Altima" }, { BGE_CHIPID_BCM5700_C0, "BCM5700 C0" }, { BGE_CHIPID_BCM5701_A0, "BCM5701 A0" }, { BGE_CHIPID_BCM5701_B0, "BCM5701 B0" }, { BGE_CHIPID_BCM5701_B2, "BCM5701 B2" }, { BGE_CHIPID_BCM5701_B5, "BCM5701 B5" }, { BGE_CHIPID_BCM5703_A0, "BCM5703 A0" }, { BGE_CHIPID_BCM5703_A1, "BCM5703 A1" }, { BGE_CHIPID_BCM5703_A2, "BCM5703 A2" }, { BGE_CHIPID_BCM5703_A3, "BCM5703 A3" }, { BGE_CHIPID_BCM5703_B0, "BCM5703 B0" }, { BGE_CHIPID_BCM5704_A0, "BCM5704 A0" }, { BGE_CHIPID_BCM5704_A1, "BCM5704 A1" }, { BGE_CHIPID_BCM5704_A2, "BCM5704 A2" }, { BGE_CHIPID_BCM5704_A3, "BCM5704 A3" }, { BGE_CHIPID_BCM5704_B0, "BCM5704 B0" }, { BGE_CHIPID_BCM5705_A0, "BCM5705 A0" }, { BGE_CHIPID_BCM5705_A1, "BCM5705 A1" }, { BGE_CHIPID_BCM5705_A2, "BCM5705 A2" }, { BGE_CHIPID_BCM5705_A3, "BCM5705 A3" }, { BGE_CHIPID_BCM5750_A0, "BCM5750 A0" }, { BGE_CHIPID_BCM5750_A1, "BCM5750 A1" }, { BGE_CHIPID_BCM5750_A3, "BCM5750 A3" }, { BGE_CHIPID_BCM5750_B0, "BCM5750 B0" }, { BGE_CHIPID_BCM5750_B1, "BCM5750 B1" }, { BGE_CHIPID_BCM5750_C0, "BCM5750 C0" }, { BGE_CHIPID_BCM5750_C1, "BCM5750 C1" }, { BGE_CHIPID_BCM5750_C2, "BCM5750 C2" }, { BGE_CHIPID_BCM5714_A0, "BCM5714 A0" }, { BGE_CHIPID_BCM5752_A0, "BCM5752 A0" }, { BGE_CHIPID_BCM5752_A1, "BCM5752 A1" }, { BGE_CHIPID_BCM5752_A2, "BCM5752 A2" }, { BGE_CHIPID_BCM5714_B0, "BCM5714 B0" }, { BGE_CHIPID_BCM5714_B3, "BCM5714 B3" }, { BGE_CHIPID_BCM5715_A0, "BCM5715 A0" }, { BGE_CHIPID_BCM5715_A1, "BCM5715 A1" }, { 0, NULL } }; /* * Some defaults for major revisions, so that newer steppings * that we don't know about have a shot at working. */ static const struct bge_revision bge_majorrevs[] = { { BGE_ASICREV_BCM5700, "unknown BCM5700" }, { BGE_ASICREV_BCM5701, "unknown BCM5701" }, { BGE_ASICREV_BCM5703, "unknown BCM5703" }, { BGE_ASICREV_BCM5704, "unknown BCM5704" }, { BGE_ASICREV_BCM5705, "unknown BCM5705" }, { BGE_ASICREV_BCM5750, "unknown BCM5750" }, { BGE_ASICREV_BCM5714_A0, "unknown BCM5714" }, { BGE_ASICREV_BCM5752, "unknown BCM5752" }, { BGE_ASICREV_BCM5780, "unknown BCM5780" }, { BGE_ASICREV_BCM5714, "unknown BCM5714" }, { BGE_ASICREV_BCM5755, "unknown BCM5755" }, { BGE_ASICREV_BCM5787, "unknown BCM5787" }, { 0, NULL } }; #define BGE_IS_5705_OR_BEYOND(sc) \ ((sc)->bge_asicrev == BGE_ASICREV_BCM5705 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5750 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5714_A0 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5780 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5714 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5752 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5755 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5787) #define BGE_IS_575X_PLUS(sc) \ ((sc)->bge_asicrev == BGE_ASICREV_BCM5750 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5714_A0 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5780 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5714 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5752 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5755 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5787) #define BGE_IS_5714_FAMILY(sc) \ ((sc)->bge_asicrev == BGE_ASICREV_BCM5714_A0 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5780 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5714) #define BGE_IS_JUMBO_CAPABLE(sc) \ ((sc)->bge_asicrev == BGE_ASICREV_BCM5700 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5701 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5703 || \ (sc)->bge_asicrev == BGE_ASICREV_BCM5704) const struct bge_revision * bge_lookup_rev(uint32_t); const struct bge_vendor * bge_lookup_vendor(uint16_t); static int bge_probe(device_t); static int bge_attach(device_t); static int bge_detach(device_t); static int bge_suspend(device_t); static int bge_resume(device_t); static void bge_release_resources(struct bge_softc *); static void bge_dma_map_addr(void *, bus_dma_segment_t *, int, int); static int bge_dma_alloc(device_t); static void bge_dma_free(struct bge_softc *); static void bge_txeof(struct bge_softc *); static void bge_rxeof(struct bge_softc *); static void bge_asf_driver_up (struct bge_softc *); static void bge_tick_locked(struct bge_softc *); static void bge_tick(void *); static void bge_stats_update(struct bge_softc *); static void bge_stats_update_regs(struct bge_softc *); static int bge_encap(struct bge_softc *, struct mbuf **, uint32_t *); static void bge_intr(void *); static void bge_start_locked(struct ifnet *); static void bge_start(struct ifnet *); static int bge_ioctl(struct ifnet *, u_long, caddr_t); static void bge_init_locked(struct bge_softc *); static void bge_init(void *); static void bge_stop(struct bge_softc *); static void bge_watchdog(struct ifnet *); static void bge_shutdown(device_t); static int bge_ifmedia_upd_locked(struct ifnet *); static int bge_ifmedia_upd(struct ifnet *); static void bge_ifmedia_sts(struct ifnet *, struct ifmediareq *); static uint8_t bge_eeprom_getbyte(struct bge_softc *, int, uint8_t *); static int bge_read_eeprom(struct bge_softc *, caddr_t, int, int); static void bge_setmulti(struct bge_softc *); static int bge_newbuf_std(struct bge_softc *, int, struct mbuf *); static int bge_newbuf_jumbo(struct bge_softc *, int, struct mbuf *); static int bge_init_rx_ring_std(struct bge_softc *); static void bge_free_rx_ring_std(struct bge_softc *); static int bge_init_rx_ring_jumbo(struct bge_softc *); static void bge_free_rx_ring_jumbo(struct bge_softc *); static void bge_free_tx_ring(struct bge_softc *); static int bge_init_tx_ring(struct bge_softc *); static int bge_chipinit(struct bge_softc *); static int bge_blockinit(struct bge_softc *); static uint32_t bge_readmem_ind(struct bge_softc *, int); static void bge_writemem_ind(struct bge_softc *, int, int); #ifdef notdef static uint32_t bge_readreg_ind(struct bge_softc *, int); #endif static void bge_writereg_ind(struct bge_softc *, int, int); static int bge_miibus_readreg(device_t, int, int); static int bge_miibus_writereg(device_t, int, int, int); static void bge_miibus_statchg(device_t); #ifdef DEVICE_POLLING static void bge_poll(struct ifnet *ifp, enum poll_cmd cmd, int count); #endif #define BGE_RESET_START 1 #define BGE_RESET_STOP 2 static void bge_sig_post_reset(struct bge_softc *, int); static void bge_sig_legacy(struct bge_softc *, int); static void bge_sig_pre_reset(struct bge_softc *, int); static int bge_reset(struct bge_softc *); static void bge_link_upd(struct bge_softc *); static device_method_t bge_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bge_probe), DEVMETHOD(device_attach, bge_attach), DEVMETHOD(device_detach, bge_detach), DEVMETHOD(device_shutdown, bge_shutdown), DEVMETHOD(device_suspend, bge_suspend), DEVMETHOD(device_resume, bge_resume), /* bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, bge_miibus_readreg), DEVMETHOD(miibus_writereg, bge_miibus_writereg), DEVMETHOD(miibus_statchg, bge_miibus_statchg), { 0, 0 } }; static driver_t bge_driver = { "bge", bge_methods, sizeof(struct bge_softc) }; static devclass_t bge_devclass; DRIVER_MODULE(bge, pci, bge_driver, bge_devclass, 0, 0); DRIVER_MODULE(miibus, bge, miibus_driver, miibus_devclass, 0, 0); static int bge_fake_autoneg = 0; TUNABLE_INT("hw.bge.fake_autoneg", &bge_fake_autoneg); static uint32_t bge_readmem_ind(struct bge_softc *sc, int off) { device_t dev; dev = sc->bge_dev; pci_write_config(dev, BGE_PCI_MEMWIN_BASEADDR, off, 4); return (pci_read_config(dev, BGE_PCI_MEMWIN_DATA, 4)); } static void bge_writemem_ind(struct bge_softc *sc, int off, int val) { device_t dev; dev = sc->bge_dev; pci_write_config(dev, BGE_PCI_MEMWIN_BASEADDR, off, 4); pci_write_config(dev, BGE_PCI_MEMWIN_DATA, val, 4); } #ifdef notdef static uint32_t bge_readreg_ind(struct bge_softc *sc, int off) { device_t dev; dev = sc->bge_dev; pci_write_config(dev, BGE_PCI_REG_BASEADDR, off, 4); return (pci_read_config(dev, BGE_PCI_REG_DATA, 4)); } #endif static void bge_writereg_ind(struct bge_softc *sc, int off, int val) { device_t dev; dev = sc->bge_dev; pci_write_config(dev, BGE_PCI_REG_BASEADDR, off, 4); pci_write_config(dev, BGE_PCI_REG_DATA, val, 4); } /* * Map a single buffer address. */ static void bge_dma_map_addr(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct bge_dmamap_arg *ctx; if (error) return; ctx = arg; if (nseg > ctx->bge_maxsegs) { ctx->bge_maxsegs = 0; return; } ctx->bge_busaddr = segs->ds_addr; } /* * Read a byte of data stored in the EEPROM at address 'addr.' The * BCM570x supports both the traditional bitbang interface and an * auto access interface for reading the EEPROM. We use the auto * access method. */ static uint8_t bge_eeprom_getbyte(struct bge_softc *sc, int addr, uint8_t *dest) { int i; uint32_t byte = 0; /* * Enable use of auto EEPROM access so we can avoid * having to use the bitbang method. */ BGE_SETBIT(sc, BGE_MISC_LOCAL_CTL, BGE_MLC_AUTO_EEPROM); /* Reset the EEPROM, load the clock period. */ CSR_WRITE_4(sc, BGE_EE_ADDR, BGE_EEADDR_RESET|BGE_EEHALFCLK(BGE_HALFCLK_384SCL)); DELAY(20); /* Issue the read EEPROM command. */ CSR_WRITE_4(sc, BGE_EE_ADDR, BGE_EE_READCMD | addr); /* Wait for completion */ for(i = 0; i < BGE_TIMEOUT * 10; i++) { DELAY(10); if (CSR_READ_4(sc, BGE_EE_ADDR) & BGE_EEADDR_DONE) break; } if (i == BGE_TIMEOUT) { device_printf(sc->bge_dev, "EEPROM read timed out\n"); return (1); } /* Get result. */ byte = CSR_READ_4(sc, BGE_EE_DATA); *dest = (byte >> ((addr % 4) * 8)) & 0xFF; return (0); } /* * Read a sequence of bytes from the EEPROM. */ static int bge_read_eeprom(struct bge_softc *sc, caddr_t dest, int off, int cnt) { int i, error = 0; uint8_t byte = 0; for (i = 0; i < cnt; i++) { error = bge_eeprom_getbyte(sc, off + i, &byte); if (error) break; *(dest + i) = byte; } return (error ? 1 : 0); } static int bge_miibus_readreg(device_t dev, int phy, int reg) { struct bge_softc *sc; uint32_t val, autopoll; int i; sc = device_get_softc(dev); /* * Broadcom's own driver always assumes the internal * PHY is at GMII address 1. On some chips, the PHY responds * to accesses at all addresses, which could cause us to * bogusly attach the PHY 32 times at probe type. Always * restricting the lookup to address 1 is simpler than * trying to figure out which chips revisions should be * special-cased. */ if (phy != 1) return (0); /* Reading with autopolling on may trigger PCI errors */ autopoll = CSR_READ_4(sc, BGE_MI_MODE); if (autopoll & BGE_MIMODE_AUTOPOLL) { BGE_CLRBIT(sc, BGE_MI_MODE, BGE_MIMODE_AUTOPOLL); DELAY(40); } CSR_WRITE_4(sc, BGE_MI_COMM, BGE_MICMD_READ|BGE_MICOMM_BUSY| BGE_MIPHY(phy)|BGE_MIREG(reg)); for (i = 0; i < BGE_TIMEOUT; i++) { val = CSR_READ_4(sc, BGE_MI_COMM); if (!(val & BGE_MICOMM_BUSY)) break; } if (i == BGE_TIMEOUT) { device_printf(sc->bge_dev, "PHY read timed out\n"); val = 0; goto done; } val = CSR_READ_4(sc, BGE_MI_COMM); done: if (autopoll & BGE_MIMODE_AUTOPOLL) { BGE_SETBIT(sc, BGE_MI_MODE, BGE_MIMODE_AUTOPOLL); DELAY(40); } if (val & BGE_MICOMM_READFAIL) return (0); return (val & 0xFFFF); } static int bge_miibus_writereg(device_t dev, int phy, int reg, int val) { struct bge_softc *sc; uint32_t autopoll; int i; sc = device_get_softc(dev); /* Reading with autopolling on may trigger PCI errors */ autopoll = CSR_READ_4(sc, BGE_MI_MODE); if (autopoll & BGE_MIMODE_AUTOPOLL) { BGE_CLRBIT(sc, BGE_MI_MODE, BGE_MIMODE_AUTOPOLL); DELAY(40); } CSR_WRITE_4(sc, BGE_MI_COMM, BGE_MICMD_WRITE|BGE_MICOMM_BUSY| BGE_MIPHY(phy)|BGE_MIREG(reg)|val); for (i = 0; i < BGE_TIMEOUT; i++) { if (!(CSR_READ_4(sc, BGE_MI_COMM) & BGE_MICOMM_BUSY)) break; } if (autopoll & BGE_MIMODE_AUTOPOLL) { BGE_SETBIT(sc, BGE_MI_MODE, BGE_MIMODE_AUTOPOLL); DELAY(40); } if (i == BGE_TIMEOUT) { device_printf(sc->bge_dev, "PHY read timed out\n"); return (0); } return (0); } static void bge_miibus_statchg(device_t dev) { struct bge_softc *sc; struct mii_data *mii; sc = device_get_softc(dev); mii = device_get_softc(sc->bge_miibus); BGE_CLRBIT(sc, BGE_MAC_MODE, BGE_MACMODE_PORTMODE); if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T) BGE_SETBIT(sc, BGE_MAC_MODE, BGE_PORTMODE_GMII); else BGE_SETBIT(sc, BGE_MAC_MODE, BGE_PORTMODE_MII); if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) BGE_CLRBIT(sc, BGE_MAC_MODE, BGE_MACMODE_HALF_DUPLEX); else BGE_SETBIT(sc, BGE_MAC_MODE, BGE_MACMODE_HALF_DUPLEX); } /* * Intialize a standard receive ring descriptor. */ static int bge_newbuf_std(struct bge_softc *sc, int i, struct mbuf *m) { struct mbuf *m_new = NULL; struct bge_rx_bd *r; struct bge_dmamap_arg ctx; int error; if (m == NULL) { m_new = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (m_new == NULL) return (ENOBUFS); m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; } else { m_new = m; m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; m_new->m_data = m_new->m_ext.ext_buf; } if ((sc->bge_flags & BGE_FLAG_RX_ALIGNBUG) == 0) m_adj(m_new, ETHER_ALIGN); sc->bge_cdata.bge_rx_std_chain[i] = m_new; r = &sc->bge_ldata.bge_rx_std_ring[i]; ctx.bge_maxsegs = 1; ctx.sc = sc; error = bus_dmamap_load(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_rx_std_dmamap[i], mtod(m_new, void *), m_new->m_len, bge_dma_map_addr, &ctx, BUS_DMA_NOWAIT); if (error || ctx.bge_maxsegs == 0) { if (m == NULL) { sc->bge_cdata.bge_rx_std_chain[i] = NULL; m_freem(m_new); } return (ENOMEM); } r->bge_addr.bge_addr_lo = BGE_ADDR_LO(ctx.bge_busaddr); r->bge_addr.bge_addr_hi = BGE_ADDR_HI(ctx.bge_busaddr); r->bge_flags = BGE_RXBDFLAG_END; r->bge_len = m_new->m_len; r->bge_idx = i; bus_dmamap_sync(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_rx_std_dmamap[i], BUS_DMASYNC_PREREAD); return (0); } /* * Initialize a jumbo receive ring descriptor. This allocates * a jumbo buffer from the pool managed internally by the driver. */ static int bge_newbuf_jumbo(struct bge_softc *sc, int i, struct mbuf *m) { bus_dma_segment_t segs[BGE_NSEG_JUMBO]; struct bge_extrx_bd *r; struct mbuf *m_new = NULL; int nsegs; int error; if (m == NULL) { MGETHDR(m_new, M_DONTWAIT, MT_DATA); if (m_new == NULL) return (ENOBUFS); m_cljget(m_new, M_DONTWAIT, MJUM9BYTES); if (!(m_new->m_flags & M_EXT)) { m_freem(m_new); return (ENOBUFS); } m_new->m_len = m_new->m_pkthdr.len = MJUM9BYTES; } else { m_new = m; m_new->m_len = m_new->m_pkthdr.len = MJUM9BYTES; m_new->m_data = m_new->m_ext.ext_buf; } if ((sc->bge_flags & BGE_FLAG_RX_ALIGNBUG) == 0) m_adj(m_new, ETHER_ALIGN); error = bus_dmamap_load_mbuf_sg(sc->bge_cdata.bge_mtag_jumbo, sc->bge_cdata.bge_rx_jumbo_dmamap[i], m_new, segs, &nsegs, BUS_DMA_NOWAIT); if (error) { if (m == NULL) m_freem(m_new); return (error); } sc->bge_cdata.bge_rx_jumbo_chain[i] = m_new; /* * Fill in the extended RX buffer descriptor. */ r = &sc->bge_ldata.bge_rx_jumbo_ring[i]; r->bge_flags = BGE_RXBDFLAG_JUMBO_RING|BGE_RXBDFLAG_END; r->bge_idx = i; r->bge_len3 = r->bge_len2 = r->bge_len1 = 0; switch (nsegs) { case 4: r->bge_addr3.bge_addr_lo = BGE_ADDR_LO(segs[3].ds_addr); r->bge_addr3.bge_addr_hi = BGE_ADDR_HI(segs[3].ds_addr); r->bge_len3 = segs[3].ds_len; case 3: r->bge_addr2.bge_addr_lo = BGE_ADDR_LO(segs[2].ds_addr); r->bge_addr2.bge_addr_hi = BGE_ADDR_HI(segs[2].ds_addr); r->bge_len2 = segs[2].ds_len; case 2: r->bge_addr1.bge_addr_lo = BGE_ADDR_LO(segs[1].ds_addr); r->bge_addr1.bge_addr_hi = BGE_ADDR_HI(segs[1].ds_addr); r->bge_len1 = segs[1].ds_len; case 1: r->bge_addr0.bge_addr_lo = BGE_ADDR_LO(segs[0].ds_addr); r->bge_addr0.bge_addr_hi = BGE_ADDR_HI(segs[0].ds_addr); r->bge_len0 = segs[0].ds_len; break; default: panic("%s: %d segments\n", __func__, nsegs); } bus_dmamap_sync(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_rx_jumbo_dmamap[i], BUS_DMASYNC_PREREAD); return (0); } /* * The standard receive ring has 512 entries in it. At 2K per mbuf cluster, * that's 1MB or memory, which is a lot. For now, we fill only the first * 256 ring entries and hope that our CPU is fast enough to keep up with * the NIC. */ static int bge_init_rx_ring_std(struct bge_softc *sc) { int i; for (i = 0; i < BGE_SSLOTS; i++) { if (bge_newbuf_std(sc, i, NULL) == ENOBUFS) return (ENOBUFS); }; bus_dmamap_sync(sc->bge_cdata.bge_rx_std_ring_tag, sc->bge_cdata.bge_rx_std_ring_map, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); sc->bge_std = i - 1; CSR_WRITE_4(sc, BGE_MBX_RX_STD_PROD_LO, sc->bge_std); return (0); } static void bge_free_rx_ring_std(struct bge_softc *sc) { int i; for (i = 0; i < BGE_STD_RX_RING_CNT; i++) { if (sc->bge_cdata.bge_rx_std_chain[i] != NULL) { bus_dmamap_sync(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_rx_std_dmamap[i], BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_rx_std_dmamap[i]); m_freem(sc->bge_cdata.bge_rx_std_chain[i]); sc->bge_cdata.bge_rx_std_chain[i] = NULL; } bzero((char *)&sc->bge_ldata.bge_rx_std_ring[i], sizeof(struct bge_rx_bd)); } } static int bge_init_rx_ring_jumbo(struct bge_softc *sc) { struct bge_rcb *rcb; int i; for (i = 0; i < BGE_JUMBO_RX_RING_CNT; i++) { if (bge_newbuf_jumbo(sc, i, NULL) == ENOBUFS) return (ENOBUFS); }; bus_dmamap_sync(sc->bge_cdata.bge_rx_jumbo_ring_tag, sc->bge_cdata.bge_rx_jumbo_ring_map, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); sc->bge_jumbo = i - 1; rcb = &sc->bge_ldata.bge_info.bge_jumbo_rx_rcb; rcb->bge_maxlen_flags = BGE_RCB_MAXLEN_FLAGS(0, BGE_RCB_FLAG_USE_EXT_RX_BD); CSR_WRITE_4(sc, BGE_RX_JUMBO_RCB_MAXLEN_FLAGS, rcb->bge_maxlen_flags); CSR_WRITE_4(sc, BGE_MBX_RX_JUMBO_PROD_LO, sc->bge_jumbo); return (0); } static void bge_free_rx_ring_jumbo(struct bge_softc *sc) { int i; for (i = 0; i < BGE_JUMBO_RX_RING_CNT; i++) { if (sc->bge_cdata.bge_rx_jumbo_chain[i] != NULL) { bus_dmamap_sync(sc->bge_cdata.bge_mtag_jumbo, sc->bge_cdata.bge_rx_jumbo_dmamap[i], BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->bge_cdata.bge_mtag_jumbo, sc->bge_cdata.bge_rx_jumbo_dmamap[i]); m_freem(sc->bge_cdata.bge_rx_jumbo_chain[i]); sc->bge_cdata.bge_rx_jumbo_chain[i] = NULL; } bzero((char *)&sc->bge_ldata.bge_rx_jumbo_ring[i], sizeof(struct bge_extrx_bd)); } } static void bge_free_tx_ring(struct bge_softc *sc) { int i; if (sc->bge_ldata.bge_tx_ring == NULL) return; for (i = 0; i < BGE_TX_RING_CNT; i++) { if (sc->bge_cdata.bge_tx_chain[i] != NULL) { bus_dmamap_sync(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_tx_dmamap[i], BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_tx_dmamap[i]); m_freem(sc->bge_cdata.bge_tx_chain[i]); sc->bge_cdata.bge_tx_chain[i] = NULL; } bzero((char *)&sc->bge_ldata.bge_tx_ring[i], sizeof(struct bge_tx_bd)); } } static int bge_init_tx_ring(struct bge_softc *sc) { sc->bge_txcnt = 0; sc->bge_tx_saved_considx = 0; /* Initialize transmit producer index for host-memory send ring. */ sc->bge_tx_prodidx = 0; CSR_WRITE_4(sc, BGE_MBX_TX_HOST_PROD0_LO, sc->bge_tx_prodidx); /* 5700 b2 errata */ if (sc->bge_chiprev == BGE_CHIPREV_5700_BX) CSR_WRITE_4(sc, BGE_MBX_TX_HOST_PROD0_LO, sc->bge_tx_prodidx); /* NIC-memory send ring not used; initialize to zero. */ CSR_WRITE_4(sc, BGE_MBX_TX_NIC_PROD0_LO, 0); /* 5700 b2 errata */ if (sc->bge_chiprev == BGE_CHIPREV_5700_BX) CSR_WRITE_4(sc, BGE_MBX_TX_NIC_PROD0_LO, 0); return (0); } static void bge_setmulti(struct bge_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; uint32_t hashes[4] = { 0, 0, 0, 0 }; int h, i; BGE_LOCK_ASSERT(sc); ifp = sc->bge_ifp; if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { for (i = 0; i < 4; i++) CSR_WRITE_4(sc, BGE_MAR0 + (i * 4), 0xFFFFFFFF); return; } /* First, zot all the existing filters. */ for (i = 0; i < 4; i++) CSR_WRITE_4(sc, BGE_MAR0 + (i * 4), 0); /* Now program new ones. */ IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; h = ether_crc32_le(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN) & 0x7F; hashes[(h & 0x60) >> 5] |= 1 << (h & 0x1F); } IF_ADDR_UNLOCK(ifp); for (i = 0; i < 4; i++) CSR_WRITE_4(sc, BGE_MAR0 + (i * 4), hashes[i]); } static void bge_sig_pre_reset(sc, type) struct bge_softc *sc; int type; { /* * Some chips don't like this so only do this if ASF is enabled */ if (sc->bge_asf_mode) bge_writemem_ind(sc, BGE_SOFTWARE_GENCOMM, BGE_MAGIC_NUMBER); if (sc->bge_asf_mode & ASF_NEW_HANDSHAKE) { switch (type) { case BGE_RESET_START: bge_writemem_ind(sc, BGE_SDI_STATUS, 0x1); /* START */ break; case BGE_RESET_STOP: bge_writemem_ind(sc, BGE_SDI_STATUS, 0x2); /* UNLOAD */ break; } } } static void bge_sig_post_reset(sc, type) struct bge_softc *sc; int type; { if (sc->bge_asf_mode & ASF_NEW_HANDSHAKE) { switch (type) { case BGE_RESET_START: bge_writemem_ind(sc, BGE_SDI_STATUS, 0x80000001); /* START DONE */ break; case BGE_RESET_STOP: bge_writemem_ind(sc, BGE_SDI_STATUS, 0x80000002); break; } } } static void bge_sig_legacy(sc, type) struct bge_softc *sc; int type; { if (sc->bge_asf_mode) { switch (type) { case BGE_RESET_START: bge_writemem_ind(sc, BGE_SDI_STATUS, 0x1); /* START */ break; case BGE_RESET_STOP: bge_writemem_ind(sc, BGE_SDI_STATUS, 0x2); /* UNLOAD */ break; } } } void bge_stop_fw(struct bge_softc *); void bge_stop_fw(sc) struct bge_softc *sc; { int i; if (sc->bge_asf_mode) { bge_writemem_ind(sc, BGE_SOFTWARE_GENCOMM_FW, BGE_FW_PAUSE); CSR_WRITE_4(sc, BGE_CPU_EVENT, CSR_READ_4(sc, BGE_CPU_EVENT) != (1 << 14)); for (i = 0; i < 100; i++ ) { if (!(CSR_READ_4(sc, BGE_CPU_EVENT) & (1 << 14))) break; DELAY(10); } } } /* * Do endian, PCI and DMA initialization. Also check the on-board ROM * self-test results. */ static int bge_chipinit(struct bge_softc *sc) { uint32_t dma_rw_ctl; int i; /* Set endianness before we access any non-PCI registers. */ pci_write_config(sc->bge_dev, BGE_PCI_MISC_CTL, BGE_INIT, 4); /* * Check the 'ROM failed' bit on the RX CPU to see if * self-tests passed. */ if (CSR_READ_4(sc, BGE_RXCPU_MODE) & BGE_RXCPUMODE_ROMFAIL) { device_printf(sc->bge_dev, "RX CPU self-diagnostics failed!\n"); return (ENODEV); } /* Clear the MAC control register */ CSR_WRITE_4(sc, BGE_MAC_MODE, 0); /* * Clear the MAC statistics block in the NIC's * internal memory. */ for (i = BGE_STATS_BLOCK; i < BGE_STATS_BLOCK_END + 1; i += sizeof(uint32_t)) BGE_MEMWIN_WRITE(sc, i, 0); for (i = BGE_STATUS_BLOCK; i < BGE_STATUS_BLOCK_END + 1; i += sizeof(uint32_t)) BGE_MEMWIN_WRITE(sc, i, 0); /* Set up the PCI DMA control register. */ if (sc->bge_flags & BGE_FLAG_PCIE) { /* PCI Express bus */ dma_rw_ctl = BGE_PCI_READ_CMD|BGE_PCI_WRITE_CMD | (0xf << BGE_PCIDMARWCTL_RD_WAT_SHIFT) | (0x2 << BGE_PCIDMARWCTL_WR_WAT_SHIFT); } else if (sc->bge_flags & BGE_FLAG_PCIX) { /* PCI-X bus */ if (BGE_IS_5714_FAMILY(sc)) { dma_rw_ctl = BGE_PCI_READ_CMD|BGE_PCI_WRITE_CMD; dma_rw_ctl &= ~BGE_PCIDMARWCTL_ONEDMA_ATONCE; /* XXX */ /* XXX magic values, Broadcom-supplied Linux driver */ if (sc->bge_asicrev == BGE_ASICREV_BCM5780) dma_rw_ctl |= (1 << 20) | (1 << 18) | BGE_PCIDMARWCTL_ONEDMA_ATONCE; else dma_rw_ctl |= (1 << 20) | (1 << 18) | (1 << 15); } else if (sc->bge_asicrev == BGE_ASICREV_BCM5704) /* * The 5704 uses a different encoding of read/write * watermarks. */ dma_rw_ctl = BGE_PCI_READ_CMD|BGE_PCI_WRITE_CMD | (0x7 << BGE_PCIDMARWCTL_RD_WAT_SHIFT) | (0x3 << BGE_PCIDMARWCTL_WR_WAT_SHIFT); else dma_rw_ctl = BGE_PCI_READ_CMD|BGE_PCI_WRITE_CMD | (0x3 << BGE_PCIDMARWCTL_RD_WAT_SHIFT) | (0x3 << BGE_PCIDMARWCTL_WR_WAT_SHIFT) | (0x0F); /* * 5703 and 5704 need ONEDMA_AT_ONCE as a workaround * for hardware bugs. */ if (sc->bge_asicrev == BGE_ASICREV_BCM5703 || sc->bge_asicrev == BGE_ASICREV_BCM5704) { uint32_t tmp; tmp = CSR_READ_4(sc, BGE_PCI_CLKCTL) & 0x1f; if (tmp == 0x6 || tmp == 0x7) dma_rw_ctl |= BGE_PCIDMARWCTL_ONEDMA_ATONCE; } } else /* Conventional PCI bus */ dma_rw_ctl = BGE_PCI_READ_CMD|BGE_PCI_WRITE_CMD | (0x7 << BGE_PCIDMARWCTL_RD_WAT_SHIFT) | (0x7 << BGE_PCIDMARWCTL_WR_WAT_SHIFT) | (0x0F); if (sc->bge_asicrev == BGE_ASICREV_BCM5703 || sc->bge_asicrev == BGE_ASICREV_BCM5704 || sc->bge_asicrev == BGE_ASICREV_BCM5705) dma_rw_ctl &= ~BGE_PCIDMARWCTL_MINDMA; pci_write_config(sc->bge_dev, BGE_PCI_DMA_RW_CTL, dma_rw_ctl, 4); /* * Set up general mode register. */ CSR_WRITE_4(sc, BGE_MODE_CTL, BGE_DMA_SWAP_OPTIONS| BGE_MODECTL_MAC_ATTN_INTR|BGE_MODECTL_HOST_SEND_BDS| BGE_MODECTL_TX_NO_PHDR_CSUM); /* * Tell the firmware the driver is running */ if (sc->bge_asf_mode & ASF_STACKUP) BGE_SETBIT(sc, BGE_MODE_CTL, BGE_MODECTL_STACKUP); /* * Disable memory write invalidate. Apparently it is not supported * properly by these devices. */ PCI_CLRBIT(sc->bge_dev, BGE_PCI_CMD, PCIM_CMD_MWIEN, 4); #ifdef __brokenalpha__ /* * Must insure that we do not cross an 8K (bytes) boundary * for DMA reads. Our highest limit is 1K bytes. This is a * restriction on some ALPHA platforms with early revision * 21174 PCI chipsets, such as the AlphaPC 164lx */ PCI_SETBIT(sc->bge_dev, BGE_PCI_DMA_RW_CTL, BGE_PCI_READ_BNDRY_1024BYTES, 4); #endif /* Set the timer prescaler (always 66Mhz) */ CSR_WRITE_4(sc, BGE_MISC_CFG, 65 << 1/*BGE_32BITTIME_66MHZ*/); return (0); } static int bge_blockinit(struct bge_softc *sc) { struct bge_rcb *rcb; bus_size_t vrcb; bge_hostaddr taddr; int i; /* * Initialize the memory window pointer register so that * we can access the first 32K of internal NIC RAM. This will * allow us to set up the TX send ring RCBs and the RX return * ring RCBs, plus other things which live in NIC memory. */ CSR_WRITE_4(sc, BGE_PCI_MEMWIN_BASEADDR, 0); /* Note: the BCM5704 has a smaller mbuf space than other chips. */ if (!(BGE_IS_5705_OR_BEYOND(sc))) { /* Configure mbuf memory pool */ if (sc->bge_flags & BGE_FLAG_EXTRAM) { CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_BASEADDR, BGE_EXT_SSRAM); if (sc->bge_asicrev == BGE_ASICREV_BCM5704) CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_LEN, 0x10000); else CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_LEN, 0x18000); } else { CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_BASEADDR, BGE_BUFFPOOL_1); if (sc->bge_asicrev == BGE_ASICREV_BCM5704) CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_LEN, 0x10000); else CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_LEN, 0x18000); } /* Configure DMA resource pool */ CSR_WRITE_4(sc, BGE_BMAN_DMA_DESCPOOL_BASEADDR, BGE_DMA_DESCRIPTORS); CSR_WRITE_4(sc, BGE_BMAN_DMA_DESCPOOL_LEN, 0x2000); } /* Configure mbuf pool watermarks */ if (!(BGE_IS_5705_OR_BEYOND(sc))) { CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_READDMA_LOWAT, 0x0); CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_MACRX_LOWAT, 0x10); } else { CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_READDMA_LOWAT, 0x50); CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_MACRX_LOWAT, 0x20); } CSR_WRITE_4(sc, BGE_BMAN_MBUFPOOL_HIWAT, 0x60); /* Configure DMA resource watermarks */ CSR_WRITE_4(sc, BGE_BMAN_DMA_DESCPOOL_LOWAT, 5); CSR_WRITE_4(sc, BGE_BMAN_DMA_DESCPOOL_HIWAT, 10); /* Enable buffer manager */ if (!(BGE_IS_5705_OR_BEYOND(sc))) { CSR_WRITE_4(sc, BGE_BMAN_MODE, BGE_BMANMODE_ENABLE|BGE_BMANMODE_LOMBUF_ATTN); /* Poll for buffer manager start indication */ for (i = 0; i < BGE_TIMEOUT; i++) { if (CSR_READ_4(sc, BGE_BMAN_MODE) & BGE_BMANMODE_ENABLE) break; DELAY(10); } if (i == BGE_TIMEOUT) { device_printf(sc->bge_dev, "buffer manager failed to start\n"); return (ENXIO); } } /* Enable flow-through queues */ CSR_WRITE_4(sc, BGE_FTQ_RESET, 0xFFFFFFFF); CSR_WRITE_4(sc, BGE_FTQ_RESET, 0); /* Wait until queue initialization is complete */ for (i = 0; i < BGE_TIMEOUT; i++) { if (CSR_READ_4(sc, BGE_FTQ_RESET) == 0) break; DELAY(10); } if (i == BGE_TIMEOUT) { device_printf(sc->bge_dev, "flow-through queue init failed\n"); return (ENXIO); } /* Initialize the standard RX ring control block */ rcb = &sc->bge_ldata.bge_info.bge_std_rx_rcb; rcb->bge_hostaddr.bge_addr_lo = BGE_ADDR_LO(sc->bge_ldata.bge_rx_std_ring_paddr); rcb->bge_hostaddr.bge_addr_hi = BGE_ADDR_HI(sc->bge_ldata.bge_rx_std_ring_paddr); bus_dmamap_sync(sc->bge_cdata.bge_rx_std_ring_tag, sc->bge_cdata.bge_rx_std_ring_map, BUS_DMASYNC_PREREAD); if (BGE_IS_5705_OR_BEYOND(sc)) rcb->bge_maxlen_flags = BGE_RCB_MAXLEN_FLAGS(512, 0); else rcb->bge_maxlen_flags = BGE_RCB_MAXLEN_FLAGS(BGE_MAX_FRAMELEN, 0); if (sc->bge_flags & BGE_FLAG_EXTRAM) rcb->bge_nicaddr = BGE_EXT_STD_RX_RINGS; else rcb->bge_nicaddr = BGE_STD_RX_RINGS; CSR_WRITE_4(sc, BGE_RX_STD_RCB_HADDR_HI, rcb->bge_hostaddr.bge_addr_hi); CSR_WRITE_4(sc, BGE_RX_STD_RCB_HADDR_LO, rcb->bge_hostaddr.bge_addr_lo); CSR_WRITE_4(sc, BGE_RX_STD_RCB_MAXLEN_FLAGS, rcb->bge_maxlen_flags); CSR_WRITE_4(sc, BGE_RX_STD_RCB_NICADDR, rcb->bge_nicaddr); /* * Initialize the jumbo RX ring control block * We set the 'ring disabled' bit in the flags * field until we're actually ready to start * using this ring (i.e. once we set the MTU * high enough to require it). */ if (BGE_IS_JUMBO_CAPABLE(sc)) { rcb = &sc->bge_ldata.bge_info.bge_jumbo_rx_rcb; rcb->bge_hostaddr.bge_addr_lo = BGE_ADDR_LO(sc->bge_ldata.bge_rx_jumbo_ring_paddr); rcb->bge_hostaddr.bge_addr_hi = BGE_ADDR_HI(sc->bge_ldata.bge_rx_jumbo_ring_paddr); bus_dmamap_sync(sc->bge_cdata.bge_rx_jumbo_ring_tag, sc->bge_cdata.bge_rx_jumbo_ring_map, BUS_DMASYNC_PREREAD); rcb->bge_maxlen_flags = BGE_RCB_MAXLEN_FLAGS(0, BGE_RCB_FLAG_USE_EXT_RX_BD|BGE_RCB_FLAG_RING_DISABLED); if (sc->bge_flags & BGE_FLAG_EXTRAM) rcb->bge_nicaddr = BGE_EXT_JUMBO_RX_RINGS; else rcb->bge_nicaddr = BGE_JUMBO_RX_RINGS; CSR_WRITE_4(sc, BGE_RX_JUMBO_RCB_HADDR_HI, rcb->bge_hostaddr.bge_addr_hi); CSR_WRITE_4(sc, BGE_RX_JUMBO_RCB_HADDR_LO, rcb->bge_hostaddr.bge_addr_lo); CSR_WRITE_4(sc, BGE_RX_JUMBO_RCB_MAXLEN_FLAGS, rcb->bge_maxlen_flags); CSR_WRITE_4(sc, BGE_RX_JUMBO_RCB_NICADDR, rcb->bge_nicaddr); /* Set up dummy disabled mini ring RCB */ rcb = &sc->bge_ldata.bge_info.bge_mini_rx_rcb; rcb->bge_maxlen_flags = BGE_RCB_MAXLEN_FLAGS(0, BGE_RCB_FLAG_RING_DISABLED); CSR_WRITE_4(sc, BGE_RX_MINI_RCB_MAXLEN_FLAGS, rcb->bge_maxlen_flags); } /* * Set the BD ring replentish thresholds. The recommended * values are 1/8th the number of descriptors allocated to * each ring. */ CSR_WRITE_4(sc, BGE_RBDI_STD_REPL_THRESH, BGE_STD_RX_RING_CNT/8); CSR_WRITE_4(sc, BGE_RBDI_JUMBO_REPL_THRESH, BGE_JUMBO_RX_RING_CNT/8); /* * Disable all unused send rings by setting the 'ring disabled' * bit in the flags field of all the TX send ring control blocks. * These are located in NIC memory. */ vrcb = BGE_MEMWIN_START + BGE_SEND_RING_RCB; for (i = 0; i < BGE_TX_RINGS_EXTSSRAM_MAX; i++) { RCB_WRITE_4(sc, vrcb, bge_maxlen_flags, BGE_RCB_MAXLEN_FLAGS(0, BGE_RCB_FLAG_RING_DISABLED)); RCB_WRITE_4(sc, vrcb, bge_nicaddr, 0); vrcb += sizeof(struct bge_rcb); } /* Configure TX RCB 0 (we use only the first ring) */ vrcb = BGE_MEMWIN_START + BGE_SEND_RING_RCB; BGE_HOSTADDR(taddr, sc->bge_ldata.bge_tx_ring_paddr); RCB_WRITE_4(sc, vrcb, bge_hostaddr.bge_addr_hi, taddr.bge_addr_hi); RCB_WRITE_4(sc, vrcb, bge_hostaddr.bge_addr_lo, taddr.bge_addr_lo); RCB_WRITE_4(sc, vrcb, bge_nicaddr, BGE_NIC_TXRING_ADDR(0, BGE_TX_RING_CNT)); if (!(BGE_IS_5705_OR_BEYOND(sc))) RCB_WRITE_4(sc, vrcb, bge_maxlen_flags, BGE_RCB_MAXLEN_FLAGS(BGE_TX_RING_CNT, 0)); /* Disable all unused RX return rings */ vrcb = BGE_MEMWIN_START + BGE_RX_RETURN_RING_RCB; for (i = 0; i < BGE_RX_RINGS_MAX; i++) { RCB_WRITE_4(sc, vrcb, bge_hostaddr.bge_addr_hi, 0); RCB_WRITE_4(sc, vrcb, bge_hostaddr.bge_addr_lo, 0); RCB_WRITE_4(sc, vrcb, bge_maxlen_flags, BGE_RCB_MAXLEN_FLAGS(sc->bge_return_ring_cnt, BGE_RCB_FLAG_RING_DISABLED)); RCB_WRITE_4(sc, vrcb, bge_nicaddr, 0); CSR_WRITE_4(sc, BGE_MBX_RX_CONS0_LO + (i * (sizeof(uint64_t))), 0); vrcb += sizeof(struct bge_rcb); } /* Initialize RX ring indexes */ CSR_WRITE_4(sc, BGE_MBX_RX_STD_PROD_LO, 0); CSR_WRITE_4(sc, BGE_MBX_RX_JUMBO_PROD_LO, 0); CSR_WRITE_4(sc, BGE_MBX_RX_MINI_PROD_LO, 0); /* * Set up RX return ring 0 * Note that the NIC address for RX return rings is 0x00000000. * The return rings live entirely within the host, so the * nicaddr field in the RCB isn't used. */ vrcb = BGE_MEMWIN_START + BGE_RX_RETURN_RING_RCB; BGE_HOSTADDR(taddr, sc->bge_ldata.bge_rx_return_ring_paddr); RCB_WRITE_4(sc, vrcb, bge_hostaddr.bge_addr_hi, taddr.bge_addr_hi); RCB_WRITE_4(sc, vrcb, bge_hostaddr.bge_addr_lo, taddr.bge_addr_lo); RCB_WRITE_4(sc, vrcb, bge_nicaddr, 0x00000000); RCB_WRITE_4(sc, vrcb, bge_maxlen_flags, BGE_RCB_MAXLEN_FLAGS(sc->bge_return_ring_cnt, 0)); /* Set random backoff seed for TX */ CSR_WRITE_4(sc, BGE_TX_RANDOM_BACKOFF, IF_LLADDR(sc->bge_ifp)[0] + IF_LLADDR(sc->bge_ifp)[1] + IF_LLADDR(sc->bge_ifp)[2] + IF_LLADDR(sc->bge_ifp)[3] + IF_LLADDR(sc->bge_ifp)[4] + IF_LLADDR(sc->bge_ifp)[5] + BGE_TX_BACKOFF_SEED_MASK); /* Set inter-packet gap */ CSR_WRITE_4(sc, BGE_TX_LENGTHS, 0x2620); /* * Specify which ring to use for packets that don't match * any RX rules. */ CSR_WRITE_4(sc, BGE_RX_RULES_CFG, 0x08); /* * Configure number of RX lists. One interrupt distribution * list, sixteen active lists, one bad frames class. */ CSR_WRITE_4(sc, BGE_RXLP_CFG, 0x181); /* Inialize RX list placement stats mask. */ CSR_WRITE_4(sc, BGE_RXLP_STATS_ENABLE_MASK, 0x007FFFFF); CSR_WRITE_4(sc, BGE_RXLP_STATS_CTL, 0x1); /* Disable host coalescing until we get it set up */ CSR_WRITE_4(sc, BGE_HCC_MODE, 0x00000000); /* Poll to make sure it's shut down. */ for (i = 0; i < BGE_TIMEOUT; i++) { if (!(CSR_READ_4(sc, BGE_HCC_MODE) & BGE_HCCMODE_ENABLE)) break; DELAY(10); } if (i == BGE_TIMEOUT) { device_printf(sc->bge_dev, "host coalescing engine failed to idle\n"); return (ENXIO); } /* Set up host coalescing defaults */ CSR_WRITE_4(sc, BGE_HCC_RX_COAL_TICKS, sc->bge_rx_coal_ticks); CSR_WRITE_4(sc, BGE_HCC_TX_COAL_TICKS, sc->bge_tx_coal_ticks); CSR_WRITE_4(sc, BGE_HCC_RX_MAX_COAL_BDS, sc->bge_rx_max_coal_bds); CSR_WRITE_4(sc, BGE_HCC_TX_MAX_COAL_BDS, sc->bge_tx_max_coal_bds); if (!(BGE_IS_5705_OR_BEYOND(sc))) { CSR_WRITE_4(sc, BGE_HCC_RX_COAL_TICKS_INT, 0); CSR_WRITE_4(sc, BGE_HCC_TX_COAL_TICKS_INT, 0); } CSR_WRITE_4(sc, BGE_HCC_RX_MAX_COAL_BDS_INT, 0); CSR_WRITE_4(sc, BGE_HCC_TX_MAX_COAL_BDS_INT, 0); /* Set up address of statistics block */ if (!(BGE_IS_5705_OR_BEYOND(sc))) { CSR_WRITE_4(sc, BGE_HCC_STATS_ADDR_HI, BGE_ADDR_HI(sc->bge_ldata.bge_stats_paddr)); CSR_WRITE_4(sc, BGE_HCC_STATS_ADDR_LO, BGE_ADDR_LO(sc->bge_ldata.bge_stats_paddr)); CSR_WRITE_4(sc, BGE_HCC_STATS_BASEADDR, BGE_STATS_BLOCK); CSR_WRITE_4(sc, BGE_HCC_STATUSBLK_BASEADDR, BGE_STATUS_BLOCK); CSR_WRITE_4(sc, BGE_HCC_STATS_TICKS, sc->bge_stat_ticks); } /* Set up address of status block */ CSR_WRITE_4(sc, BGE_HCC_STATUSBLK_ADDR_HI, BGE_ADDR_HI(sc->bge_ldata.bge_status_block_paddr)); CSR_WRITE_4(sc, BGE_HCC_STATUSBLK_ADDR_LO, BGE_ADDR_LO(sc->bge_ldata.bge_status_block_paddr)); sc->bge_ldata.bge_status_block->bge_idx[0].bge_rx_prod_idx = 0; sc->bge_ldata.bge_status_block->bge_idx[0].bge_tx_cons_idx = 0; /* Turn on host coalescing state machine */ CSR_WRITE_4(sc, BGE_HCC_MODE, BGE_HCCMODE_ENABLE); /* Turn on RX BD completion state machine and enable attentions */ CSR_WRITE_4(sc, BGE_RBDC_MODE, BGE_RBDCMODE_ENABLE|BGE_RBDCMODE_ATTN); /* Turn on RX list placement state machine */ CSR_WRITE_4(sc, BGE_RXLP_MODE, BGE_RXLPMODE_ENABLE); /* Turn on RX list selector state machine. */ if (!(BGE_IS_5705_OR_BEYOND(sc))) CSR_WRITE_4(sc, BGE_RXLS_MODE, BGE_RXLSMODE_ENABLE); /* Turn on DMA, clear stats */ CSR_WRITE_4(sc, BGE_MAC_MODE, BGE_MACMODE_TXDMA_ENB| BGE_MACMODE_RXDMA_ENB|BGE_MACMODE_RX_STATS_CLEAR| BGE_MACMODE_TX_STATS_CLEAR|BGE_MACMODE_RX_STATS_ENB| BGE_MACMODE_TX_STATS_ENB|BGE_MACMODE_FRMHDR_DMA_ENB| ((sc->bge_flags & BGE_FLAG_TBI) ? BGE_PORTMODE_TBI : BGE_PORTMODE_MII)); /* Set misc. local control, enable interrupts on attentions */ CSR_WRITE_4(sc, BGE_MISC_LOCAL_CTL, BGE_MLC_INTR_ONATTN); #ifdef notdef /* Assert GPIO pins for PHY reset */ BGE_SETBIT(sc, BGE_MISC_LOCAL_CTL, BGE_MLC_MISCIO_OUT0| BGE_MLC_MISCIO_OUT1|BGE_MLC_MISCIO_OUT2); BGE_SETBIT(sc, BGE_MISC_LOCAL_CTL, BGE_MLC_MISCIO_OUTEN0| BGE_MLC_MISCIO_OUTEN1|BGE_MLC_MISCIO_OUTEN2); #endif /* Turn on DMA completion state machine */ if (!(BGE_IS_5705_OR_BEYOND(sc))) CSR_WRITE_4(sc, BGE_DMAC_MODE, BGE_DMACMODE_ENABLE); /* Turn on write DMA state machine */ CSR_WRITE_4(sc, BGE_WDMA_MODE, BGE_WDMAMODE_ENABLE|BGE_WDMAMODE_ALL_ATTNS); /* Turn on read DMA state machine */ CSR_WRITE_4(sc, BGE_RDMA_MODE, BGE_RDMAMODE_ENABLE|BGE_RDMAMODE_ALL_ATTNS); /* Turn on RX data completion state machine */ CSR_WRITE_4(sc, BGE_RDC_MODE, BGE_RDCMODE_ENABLE); /* Turn on RX BD initiator state machine */ CSR_WRITE_4(sc, BGE_RBDI_MODE, BGE_RBDIMODE_ENABLE); /* Turn on RX data and RX BD initiator state machine */ CSR_WRITE_4(sc, BGE_RDBDI_MODE, BGE_RDBDIMODE_ENABLE); /* Turn on Mbuf cluster free state machine */ if (!(BGE_IS_5705_OR_BEYOND(sc))) CSR_WRITE_4(sc, BGE_MBCF_MODE, BGE_MBCFMODE_ENABLE); /* Turn on send BD completion state machine */ CSR_WRITE_4(sc, BGE_SBDC_MODE, BGE_SBDCMODE_ENABLE); /* Turn on send data completion state machine */ CSR_WRITE_4(sc, BGE_SDC_MODE, BGE_SDCMODE_ENABLE); /* Turn on send data initiator state machine */ CSR_WRITE_4(sc, BGE_SDI_MODE, BGE_SDIMODE_ENABLE); /* Turn on send BD initiator state machine */ CSR_WRITE_4(sc, BGE_SBDI_MODE, BGE_SBDIMODE_ENABLE); /* Turn on send BD selector state machine */ CSR_WRITE_4(sc, BGE_SRS_MODE, BGE_SRSMODE_ENABLE); CSR_WRITE_4(sc, BGE_SDI_STATS_ENABLE_MASK, 0x007FFFFF); CSR_WRITE_4(sc, BGE_SDI_STATS_CTL, BGE_SDISTATSCTL_ENABLE|BGE_SDISTATSCTL_FASTER); /* ack/clear link change events */ CSR_WRITE_4(sc, BGE_MAC_STS, BGE_MACSTAT_SYNC_CHANGED| BGE_MACSTAT_CFG_CHANGED|BGE_MACSTAT_MI_COMPLETE| BGE_MACSTAT_LINK_CHANGED); CSR_WRITE_4(sc, BGE_MI_STS, 0); /* Enable PHY auto polling (for MII/GMII only) */ if (sc->bge_flags & BGE_FLAG_TBI) { CSR_WRITE_4(sc, BGE_MI_STS, BGE_MISTS_LINK); } else { BGE_SETBIT(sc, BGE_MI_MODE, BGE_MIMODE_AUTOPOLL|10<<16); if (sc->bge_asicrev == BGE_ASICREV_BCM5700 && sc->bge_chipid != BGE_CHIPID_BCM5700_B2) CSR_WRITE_4(sc, BGE_MAC_EVT_ENB, BGE_EVTENB_MI_INTERRUPT); } /* * Clear any pending link state attention. * Otherwise some link state change events may be lost until attention * is cleared by bge_intr() -> bge_link_upd() sequence. * It's not necessary on newer BCM chips - perhaps enabling link * state change attentions implies clearing pending attention. */ CSR_WRITE_4(sc, BGE_MAC_STS, BGE_MACSTAT_SYNC_CHANGED| BGE_MACSTAT_CFG_CHANGED|BGE_MACSTAT_MI_COMPLETE| BGE_MACSTAT_LINK_CHANGED); /* Enable link state change attentions. */ BGE_SETBIT(sc, BGE_MAC_EVT_ENB, BGE_EVTENB_LINK_CHANGED); return (0); } const struct bge_revision * bge_lookup_rev(uint32_t chipid) { const struct bge_revision *br; for (br = bge_revisions; br->br_name != NULL; br++) { if (br->br_chipid == chipid) return (br); } for (br = bge_majorrevs; br->br_name != NULL; br++) { if (br->br_chipid == BGE_ASICREV(chipid)) return (br); } return (NULL); } const struct bge_vendor * bge_lookup_vendor(uint16_t vid) { const struct bge_vendor *v; for (v = bge_vendors; v->v_name != NULL; v++) if (v->v_id == vid) return (v); panic("%s: unknown vendor %d", __func__, vid); return (NULL); } /* * Probe for a Broadcom chip. Check the PCI vendor and device IDs * against our list and return its name if we find a match. * * Note that since the Broadcom controller contains VPD support, we * can get the device name string from the controller itself instead * of the compiled-in string. This is a little slow, but it guarantees * we'll always announce the right product name. Unfortunately, this * is possible only later in bge_attach(), when we have established * access to EEPROM. */ static int bge_probe(device_t dev) { struct bge_type *t = bge_devs; struct bge_softc *sc = device_get_softc(dev); bzero(sc, sizeof(struct bge_softc)); sc->bge_dev = dev; while(t->bge_vid != 0) { if ((pci_get_vendor(dev) == t->bge_vid) && (pci_get_device(dev) == t->bge_did)) { char buf[64]; const struct bge_revision *br; const struct bge_vendor *v; uint32_t id; id = pci_read_config(dev, BGE_PCI_MISC_CTL, 4) & BGE_PCIMISCCTL_ASICREV; br = bge_lookup_rev(id); id >>= 16; v = bge_lookup_vendor(t->bge_vid); if (br == NULL) snprintf(buf, 64, "%s unknown ASIC (%#04x)", v->v_name, id); else snprintf(buf, 64, "%s %s, ASIC rev. %#04x", v->v_name, br->br_name, id); device_set_desc_copy(dev, buf); if (pci_get_subvendor(dev) == DELL_VENDORID) sc->bge_flags |= BGE_FLAG_NO3LED; return (0); } t++; } return (ENXIO); } static void bge_dma_free(struct bge_softc *sc) { int i; /* Destroy DMA maps for RX buffers. */ for (i = 0; i < BGE_STD_RX_RING_CNT; i++) { if (sc->bge_cdata.bge_rx_std_dmamap[i]) bus_dmamap_destroy(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_rx_std_dmamap[i]); } /* Destroy DMA maps for jumbo RX buffers. */ for (i = 0; i < BGE_JUMBO_RX_RING_CNT; i++) { if (sc->bge_cdata.bge_rx_jumbo_dmamap[i]) bus_dmamap_destroy(sc->bge_cdata.bge_mtag_jumbo, sc->bge_cdata.bge_rx_jumbo_dmamap[i]); } /* Destroy DMA maps for TX buffers. */ for (i = 0; i < BGE_TX_RING_CNT; i++) { if (sc->bge_cdata.bge_tx_dmamap[i]) bus_dmamap_destroy(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_tx_dmamap[i]); } if (sc->bge_cdata.bge_mtag) bus_dma_tag_destroy(sc->bge_cdata.bge_mtag); /* Destroy standard RX ring. */ if (sc->bge_cdata.bge_rx_std_ring_map) bus_dmamap_unload(sc->bge_cdata.bge_rx_std_ring_tag, sc->bge_cdata.bge_rx_std_ring_map); if (sc->bge_cdata.bge_rx_std_ring_map && sc->bge_ldata.bge_rx_std_ring) bus_dmamem_free(sc->bge_cdata.bge_rx_std_ring_tag, sc->bge_ldata.bge_rx_std_ring, sc->bge_cdata.bge_rx_std_ring_map); if (sc->bge_cdata.bge_rx_std_ring_tag) bus_dma_tag_destroy(sc->bge_cdata.bge_rx_std_ring_tag); /* Destroy jumbo RX ring. */ if (sc->bge_cdata.bge_rx_jumbo_ring_map) bus_dmamap_unload(sc->bge_cdata.bge_rx_jumbo_ring_tag, sc->bge_cdata.bge_rx_jumbo_ring_map); if (sc->bge_cdata.bge_rx_jumbo_ring_map && sc->bge_ldata.bge_rx_jumbo_ring) bus_dmamem_free(sc->bge_cdata.bge_rx_jumbo_ring_tag, sc->bge_ldata.bge_rx_jumbo_ring, sc->bge_cdata.bge_rx_jumbo_ring_map); if (sc->bge_cdata.bge_rx_jumbo_ring_tag) bus_dma_tag_destroy(sc->bge_cdata.bge_rx_jumbo_ring_tag); /* Destroy RX return ring. */ if (sc->bge_cdata.bge_rx_return_ring_map) bus_dmamap_unload(sc->bge_cdata.bge_rx_return_ring_tag, sc->bge_cdata.bge_rx_return_ring_map); if (sc->bge_cdata.bge_rx_return_ring_map && sc->bge_ldata.bge_rx_return_ring) bus_dmamem_free(sc->bge_cdata.bge_rx_return_ring_tag, sc->bge_ldata.bge_rx_return_ring, sc->bge_cdata.bge_rx_return_ring_map); if (sc->bge_cdata.bge_rx_return_ring_tag) bus_dma_tag_destroy(sc->bge_cdata.bge_rx_return_ring_tag); /* Destroy TX ring. */ if (sc->bge_cdata.bge_tx_ring_map) bus_dmamap_unload(sc->bge_cdata.bge_tx_ring_tag, sc->bge_cdata.bge_tx_ring_map); if (sc->bge_cdata.bge_tx_ring_map && sc->bge_ldata.bge_tx_ring) bus_dmamem_free(sc->bge_cdata.bge_tx_ring_tag, sc->bge_ldata.bge_tx_ring, sc->bge_cdata.bge_tx_ring_map); if (sc->bge_cdata.bge_tx_ring_tag) bus_dma_tag_destroy(sc->bge_cdata.bge_tx_ring_tag); /* Destroy status block. */ if (sc->bge_cdata.bge_status_map) bus_dmamap_unload(sc->bge_cdata.bge_status_tag, sc->bge_cdata.bge_status_map); if (sc->bge_cdata.bge_status_map && sc->bge_ldata.bge_status_block) bus_dmamem_free(sc->bge_cdata.bge_status_tag, sc->bge_ldata.bge_status_block, sc->bge_cdata.bge_status_map); if (sc->bge_cdata.bge_status_tag) bus_dma_tag_destroy(sc->bge_cdata.bge_status_tag); /* Destroy statistics block. */ if (sc->bge_cdata.bge_stats_map) bus_dmamap_unload(sc->bge_cdata.bge_stats_tag, sc->bge_cdata.bge_stats_map); if (sc->bge_cdata.bge_stats_map && sc->bge_ldata.bge_stats) bus_dmamem_free(sc->bge_cdata.bge_stats_tag, sc->bge_ldata.bge_stats, sc->bge_cdata.bge_stats_map); if (sc->bge_cdata.bge_stats_tag) bus_dma_tag_destroy(sc->bge_cdata.bge_stats_tag); /* Destroy the parent tag. */ if (sc->bge_cdata.bge_parent_tag) bus_dma_tag_destroy(sc->bge_cdata.bge_parent_tag); } static int bge_dma_alloc(device_t dev) { struct bge_dmamap_arg ctx; struct bge_softc *sc; int i, error; sc = device_get_softc(dev); /* * Allocate the parent bus DMA tag appropriate for PCI. */ error = bus_dma_tag_create(bus_get_dma_tag(sc->bge_dev),/* parent */ PAGE_SIZE, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MAXBSIZE, BGE_NSEG_NEW, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT,/* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->bge_cdata.bge_parent_tag); if (error != 0) { device_printf(sc->bge_dev, "could not allocate parent dma tag\n"); return (ENOMEM); } /* * Create tag for RX mbufs. */ error = bus_dma_tag_create(sc->bge_cdata.bge_parent_tag, 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES * BGE_NSEG_NEW, BGE_NSEG_NEW, MCLBYTES, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->bge_cdata.bge_mtag); if (error) { device_printf(sc->bge_dev, "could not allocate dma tag\n"); return (ENOMEM); } /* Create DMA maps for RX buffers. */ for (i = 0; i < BGE_STD_RX_RING_CNT; i++) { error = bus_dmamap_create(sc->bge_cdata.bge_mtag, 0, &sc->bge_cdata.bge_rx_std_dmamap[i]); if (error) { device_printf(sc->bge_dev, "can't create DMA map for RX\n"); return (ENOMEM); } } /* Create DMA maps for TX buffers. */ for (i = 0; i < BGE_TX_RING_CNT; i++) { error = bus_dmamap_create(sc->bge_cdata.bge_mtag, 0, &sc->bge_cdata.bge_tx_dmamap[i]); if (error) { device_printf(sc->bge_dev, "can't create DMA map for RX\n"); return (ENOMEM); } } /* Create tag for standard RX ring. */ error = bus_dma_tag_create(sc->bge_cdata.bge_parent_tag, PAGE_SIZE, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BGE_STD_RX_RING_SZ, 1, BGE_STD_RX_RING_SZ, 0, NULL, NULL, &sc->bge_cdata.bge_rx_std_ring_tag); if (error) { device_printf(sc->bge_dev, "could not allocate dma tag\n"); return (ENOMEM); } /* Allocate DMA'able memory for standard RX ring. */ error = bus_dmamem_alloc(sc->bge_cdata.bge_rx_std_ring_tag, (void **)&sc->bge_ldata.bge_rx_std_ring, BUS_DMA_NOWAIT, &sc->bge_cdata.bge_rx_std_ring_map); if (error) return (ENOMEM); bzero((char *)sc->bge_ldata.bge_rx_std_ring, BGE_STD_RX_RING_SZ); /* Load the address of the standard RX ring. */ ctx.bge_maxsegs = 1; ctx.sc = sc; error = bus_dmamap_load(sc->bge_cdata.bge_rx_std_ring_tag, sc->bge_cdata.bge_rx_std_ring_map, sc->bge_ldata.bge_rx_std_ring, BGE_STD_RX_RING_SZ, bge_dma_map_addr, &ctx, BUS_DMA_NOWAIT); if (error) return (ENOMEM); sc->bge_ldata.bge_rx_std_ring_paddr = ctx.bge_busaddr; /* Create tags for jumbo mbufs. */ if (BGE_IS_JUMBO_CAPABLE(sc)) { error = bus_dma_tag_create(sc->bge_cdata.bge_parent_tag, 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, MJUM9BYTES, BGE_NSEG_JUMBO, PAGE_SIZE, 0, NULL, NULL, &sc->bge_cdata.bge_mtag_jumbo); if (error) { device_printf(sc->bge_dev, "could not allocate jumbo dma tag\n"); return (ENOMEM); } /* Create tag for jumbo RX ring. */ error = bus_dma_tag_create(sc->bge_cdata.bge_parent_tag, PAGE_SIZE, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BGE_JUMBO_RX_RING_SZ, 1, BGE_JUMBO_RX_RING_SZ, 0, NULL, NULL, &sc->bge_cdata.bge_rx_jumbo_ring_tag); if (error) { device_printf(sc->bge_dev, "could not allocate jumbo ring dma tag\n"); return (ENOMEM); } /* Allocate DMA'able memory for jumbo RX ring. */ error = bus_dmamem_alloc(sc->bge_cdata.bge_rx_jumbo_ring_tag, (void **)&sc->bge_ldata.bge_rx_jumbo_ring, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &sc->bge_cdata.bge_rx_jumbo_ring_map); if (error) return (ENOMEM); /* Load the address of the jumbo RX ring. */ ctx.bge_maxsegs = 1; ctx.sc = sc; error = bus_dmamap_load(sc->bge_cdata.bge_rx_jumbo_ring_tag, sc->bge_cdata.bge_rx_jumbo_ring_map, sc->bge_ldata.bge_rx_jumbo_ring, BGE_JUMBO_RX_RING_SZ, bge_dma_map_addr, &ctx, BUS_DMA_NOWAIT); if (error) return (ENOMEM); sc->bge_ldata.bge_rx_jumbo_ring_paddr = ctx.bge_busaddr; /* Create DMA maps for jumbo RX buffers. */ for (i = 0; i < BGE_JUMBO_RX_RING_CNT; i++) { error = bus_dmamap_create(sc->bge_cdata.bge_mtag_jumbo, 0, &sc->bge_cdata.bge_rx_jumbo_dmamap[i]); if (error) { device_printf(sc->bge_dev, "can't create DMA map for jumbo RX\n"); return (ENOMEM); } } } /* Create tag for RX return ring. */ error = bus_dma_tag_create(sc->bge_cdata.bge_parent_tag, PAGE_SIZE, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BGE_RX_RTN_RING_SZ(sc), 1, BGE_RX_RTN_RING_SZ(sc), 0, NULL, NULL, &sc->bge_cdata.bge_rx_return_ring_tag); if (error) { device_printf(sc->bge_dev, "could not allocate dma tag\n"); return (ENOMEM); } /* Allocate DMA'able memory for RX return ring. */ error = bus_dmamem_alloc(sc->bge_cdata.bge_rx_return_ring_tag, (void **)&sc->bge_ldata.bge_rx_return_ring, BUS_DMA_NOWAIT, &sc->bge_cdata.bge_rx_return_ring_map); if (error) return (ENOMEM); bzero((char *)sc->bge_ldata.bge_rx_return_ring, BGE_RX_RTN_RING_SZ(sc)); /* Load the address of the RX return ring. */ ctx.bge_maxsegs = 1; ctx.sc = sc; error = bus_dmamap_load(sc->bge_cdata.bge_rx_return_ring_tag, sc->bge_cdata.bge_rx_return_ring_map, sc->bge_ldata.bge_rx_return_ring, BGE_RX_RTN_RING_SZ(sc), bge_dma_map_addr, &ctx, BUS_DMA_NOWAIT); if (error) return (ENOMEM); sc->bge_ldata.bge_rx_return_ring_paddr = ctx.bge_busaddr; /* Create tag for TX ring. */ error = bus_dma_tag_create(sc->bge_cdata.bge_parent_tag, PAGE_SIZE, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BGE_TX_RING_SZ, 1, BGE_TX_RING_SZ, 0, NULL, NULL, &sc->bge_cdata.bge_tx_ring_tag); if (error) { device_printf(sc->bge_dev, "could not allocate dma tag\n"); return (ENOMEM); } /* Allocate DMA'able memory for TX ring. */ error = bus_dmamem_alloc(sc->bge_cdata.bge_tx_ring_tag, (void **)&sc->bge_ldata.bge_tx_ring, BUS_DMA_NOWAIT, &sc->bge_cdata.bge_tx_ring_map); if (error) return (ENOMEM); bzero((char *)sc->bge_ldata.bge_tx_ring, BGE_TX_RING_SZ); /* Load the address of the TX ring. */ ctx.bge_maxsegs = 1; ctx.sc = sc; error = bus_dmamap_load(sc->bge_cdata.bge_tx_ring_tag, sc->bge_cdata.bge_tx_ring_map, sc->bge_ldata.bge_tx_ring, BGE_TX_RING_SZ, bge_dma_map_addr, &ctx, BUS_DMA_NOWAIT); if (error) return (ENOMEM); sc->bge_ldata.bge_tx_ring_paddr = ctx.bge_busaddr; /* Create tag for status block. */ error = bus_dma_tag_create(sc->bge_cdata.bge_parent_tag, PAGE_SIZE, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BGE_STATUS_BLK_SZ, 1, BGE_STATUS_BLK_SZ, 0, NULL, NULL, &sc->bge_cdata.bge_status_tag); if (error) { device_printf(sc->bge_dev, "could not allocate dma tag\n"); return (ENOMEM); } /* Allocate DMA'able memory for status block. */ error = bus_dmamem_alloc(sc->bge_cdata.bge_status_tag, (void **)&sc->bge_ldata.bge_status_block, BUS_DMA_NOWAIT, &sc->bge_cdata.bge_status_map); if (error) return (ENOMEM); bzero((char *)sc->bge_ldata.bge_status_block, BGE_STATUS_BLK_SZ); /* Load the address of the status block. */ ctx.sc = sc; ctx.bge_maxsegs = 1; error = bus_dmamap_load(sc->bge_cdata.bge_status_tag, sc->bge_cdata.bge_status_map, sc->bge_ldata.bge_status_block, BGE_STATUS_BLK_SZ, bge_dma_map_addr, &ctx, BUS_DMA_NOWAIT); if (error) return (ENOMEM); sc->bge_ldata.bge_status_block_paddr = ctx.bge_busaddr; /* Create tag for statistics block. */ error = bus_dma_tag_create(sc->bge_cdata.bge_parent_tag, PAGE_SIZE, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BGE_STATS_SZ, 1, BGE_STATS_SZ, 0, NULL, NULL, &sc->bge_cdata.bge_stats_tag); if (error) { device_printf(sc->bge_dev, "could not allocate dma tag\n"); return (ENOMEM); } /* Allocate DMA'able memory for statistics block. */ error = bus_dmamem_alloc(sc->bge_cdata.bge_stats_tag, (void **)&sc->bge_ldata.bge_stats, BUS_DMA_NOWAIT, &sc->bge_cdata.bge_stats_map); if (error) return (ENOMEM); bzero((char *)sc->bge_ldata.bge_stats, BGE_STATS_SZ); /* Load the address of the statstics block. */ ctx.sc = sc; ctx.bge_maxsegs = 1; error = bus_dmamap_load(sc->bge_cdata.bge_stats_tag, sc->bge_cdata.bge_stats_map, sc->bge_ldata.bge_stats, BGE_STATS_SZ, bge_dma_map_addr, &ctx, BUS_DMA_NOWAIT); if (error) return (ENOMEM); sc->bge_ldata.bge_stats_paddr = ctx.bge_busaddr; return (0); } static int bge_attach(device_t dev) { struct ifnet *ifp; struct bge_softc *sc; uint32_t hwcfg = 0; uint32_t mac_tmp = 0; u_char eaddr[6]; int error = 0, rid; int trys; sc = device_get_softc(dev); sc->bge_dev = dev; /* * Map control/status registers. */ pci_enable_busmaster(dev); rid = BGE_PCI_BAR0; sc->bge_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE|PCI_RF_DENSE); if (sc->bge_res == NULL) { device_printf (sc->bge_dev, "couldn't map memory\n"); error = ENXIO; goto fail; } sc->bge_btag = rman_get_bustag(sc->bge_res); sc->bge_bhandle = rman_get_bushandle(sc->bge_res); /* Allocate interrupt. */ rid = 0; sc->bge_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->bge_irq == NULL) { device_printf(sc->bge_dev, "couldn't map interrupt\n"); error = ENXIO; goto fail; } BGE_LOCK_INIT(sc, device_get_nameunit(dev)); /* Save ASIC rev. */ sc->bge_chipid = pci_read_config(dev, BGE_PCI_MISC_CTL, 4) & BGE_PCIMISCCTL_ASICREV; sc->bge_asicrev = BGE_ASICREV(sc->bge_chipid); sc->bge_chiprev = BGE_CHIPREV(sc->bge_chipid); /* * XXX: Broadcom Linux driver. Not in specs or eratta. * PCI-Express? */ if (BGE_IS_5705_OR_BEYOND(sc)) { uint32_t v; v = pci_read_config(dev, BGE_PCI_MSI_CAPID, 4); if (((v >> 8) & 0xff) == BGE_PCIE_CAPID_REG) { v = pci_read_config(dev, BGE_PCIE_CAPID_REG, 4); if ((v & 0xff) == BGE_PCIE_CAPID) sc->bge_flags |= BGE_FLAG_PCIE; } } /* * PCI-X ? */ if ((pci_read_config(sc->bge_dev, BGE_PCI_PCISTATE, 4) & BGE_PCISTATE_PCI_BUSMODE) == 0) sc->bge_flags |= BGE_FLAG_PCIX; /* Try to reset the chip. */ if (bge_reset(sc)) { device_printf(sc->bge_dev, "chip reset failed\n"); bge_release_resources(sc); error = ENXIO; goto fail; } sc->bge_asf_mode = 0; if (bge_readmem_ind(sc, BGE_SOFTWARE_GENCOMM_SIG) == BGE_MAGIC_NUMBER) { if (bge_readmem_ind(sc, BGE_SOFTWARE_GENCOMM_NICCFG) & BGE_HWCFG_ASF) { sc->bge_asf_mode |= ASF_ENABLE; sc->bge_asf_mode |= ASF_STACKUP; if (sc->bge_asicrev == BGE_ASICREV_BCM5750) { sc->bge_asf_mode |= ASF_NEW_HANDSHAKE; } } } /* Try to reset the chip again the nice way. */ bge_stop_fw(sc); bge_sig_pre_reset(sc, BGE_RESET_STOP); if (bge_reset(sc)) { device_printf(sc->bge_dev, "chip reset failed\n"); bge_release_resources(sc); error = ENXIO; goto fail; } bge_sig_legacy(sc, BGE_RESET_STOP); bge_sig_post_reset(sc, BGE_RESET_STOP); if (bge_chipinit(sc)) { device_printf(sc->bge_dev, "chip initialization failed\n"); bge_release_resources(sc); error = ENXIO; goto fail; } /* * Get station address from the EEPROM. */ mac_tmp = bge_readmem_ind(sc, 0x0c14); if ((mac_tmp >> 16) == 0x484b) { eaddr[0] = (u_char)(mac_tmp >> 8); eaddr[1] = (u_char)mac_tmp; mac_tmp = bge_readmem_ind(sc, 0x0c18); eaddr[2] = (u_char)(mac_tmp >> 24); eaddr[3] = (u_char)(mac_tmp >> 16); eaddr[4] = (u_char)(mac_tmp >> 8); eaddr[5] = (u_char)mac_tmp; } else if (bge_read_eeprom(sc, eaddr, BGE_EE_MAC_OFFSET + 2, ETHER_ADDR_LEN)) { device_printf(sc->bge_dev, "failed to read station address\n"); bge_release_resources(sc); error = ENXIO; goto fail; } /* 5705 limits RX return ring to 512 entries. */ if (BGE_IS_5705_OR_BEYOND(sc)) sc->bge_return_ring_cnt = BGE_RETURN_RING_CNT_5705; else sc->bge_return_ring_cnt = BGE_RETURN_RING_CNT; if (bge_dma_alloc(dev)) { device_printf(sc->bge_dev, "failed to allocate DMA resources\n"); bge_release_resources(sc); error = ENXIO; goto fail; } /* Set default tuneable values. */ sc->bge_stat_ticks = BGE_TICKS_PER_SEC; sc->bge_rx_coal_ticks = 150; sc->bge_tx_coal_ticks = 150; sc->bge_rx_max_coal_bds = 64; sc->bge_tx_max_coal_bds = 128; /* Set up ifnet structure */ ifp = sc->bge_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(sc->bge_dev, "failed to if_alloc()\n"); bge_release_resources(sc); error = ENXIO; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = bge_ioctl; ifp->if_start = bge_start; ifp->if_watchdog = bge_watchdog; ifp->if_init = bge_init; ifp->if_mtu = ETHERMTU; ifp->if_snd.ifq_drv_maxlen = BGE_TX_RING_CNT - 1; IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); IFQ_SET_READY(&ifp->if_snd); ifp->if_hwassist = BGE_CSUM_FEATURES; ifp->if_capabilities = IFCAP_HWCSUM | IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_MTU | IFCAP_VLAN_HWCSUM; ifp->if_capenable = ifp->if_capabilities; #ifdef DEVICE_POLLING ifp->if_capabilities |= IFCAP_POLLING; #endif /* * 5700 B0 chips do not support checksumming correctly due * to hardware bugs. */ if (sc->bge_chipid == BGE_CHIPID_BCM5700_B0) { ifp->if_capabilities &= ~IFCAP_HWCSUM; ifp->if_capenable &= IFCAP_HWCSUM; ifp->if_hwassist = 0; } /* * Figure out what sort of media we have by checking the * hardware config word in the first 32k of NIC internal memory, * or fall back to examining the EEPROM if necessary. * Note: on some BCM5700 cards, this value appears to be unset. * If that's the case, we have to rely on identifying the NIC * by its PCI subsystem ID, as we do below for the SysKonnect * SK-9D41. */ if (bge_readmem_ind(sc, BGE_SOFTWARE_GENCOMM_SIG) == BGE_MAGIC_NUMBER) hwcfg = bge_readmem_ind(sc, BGE_SOFTWARE_GENCOMM_NICCFG); else { if (bge_read_eeprom(sc, (caddr_t)&hwcfg, BGE_EE_HWCFG_OFFSET, sizeof(hwcfg))) { device_printf(sc->bge_dev, "failed to read EEPROM\n"); bge_release_resources(sc); error = ENXIO; goto fail; } hwcfg = ntohl(hwcfg); } if ((hwcfg & BGE_HWCFG_MEDIA) == BGE_MEDIA_FIBER) sc->bge_flags |= BGE_FLAG_TBI; /* The SysKonnect SK-9D41 is a 1000baseSX card. */ if ((pci_read_config(dev, BGE_PCI_SUBSYS, 4) >> 16) == SK_SUBSYSID_9D41) sc->bge_flags |= BGE_FLAG_TBI; if (sc->bge_flags & BGE_FLAG_TBI) { ifmedia_init(&sc->bge_ifmedia, IFM_IMASK, bge_ifmedia_upd, bge_ifmedia_sts); ifmedia_add(&sc->bge_ifmedia, IFM_ETHER|IFM_1000_SX, 0, NULL); ifmedia_add(&sc->bge_ifmedia, IFM_ETHER|IFM_1000_SX|IFM_FDX, 0, NULL); ifmedia_add(&sc->bge_ifmedia, IFM_ETHER|IFM_AUTO, 0, NULL); ifmedia_set(&sc->bge_ifmedia, IFM_ETHER|IFM_AUTO); sc->bge_ifmedia.ifm_media = sc->bge_ifmedia.ifm_cur->ifm_media; } else { /* * Do transceiver setup and tell the firmware the * driver is down so we can try to get access the * probe if ASF is running. Retry a couple of times * if we get a conflict with the ASF firmware accessing * the PHY. */ BGE_CLRBIT(sc, BGE_MODE_CTL, BGE_MODECTL_STACKUP); again: bge_asf_driver_up(sc); trys = 0; if (mii_phy_probe(dev, &sc->bge_miibus, bge_ifmedia_upd, bge_ifmedia_sts)) { if (trys++ < 4) { device_printf(sc->bge_dev, "Try again\n"); bge_miibus_writereg(sc->bge_dev, 1, MII_BMCR, BMCR_RESET); goto again; } device_printf(sc->bge_dev, "MII without any PHY!\n"); bge_release_resources(sc); error = ENXIO; goto fail; } /* * Now tell the firmware we are going up after probing the PHY */ if (sc->bge_asf_mode & ASF_STACKUP) BGE_SETBIT(sc, BGE_MODE_CTL, BGE_MODECTL_STACKUP); } /* * When using the BCM5701 in PCI-X mode, data corruption has * been observed in the first few bytes of some received packets. * Aligning the packet buffer in memory eliminates the corruption. * Unfortunately, this misaligns the packet payloads. On platforms * which do not support unaligned accesses, we will realign the * payloads by copying the received packets. */ if (sc->bge_asicrev == BGE_ASICREV_BCM5701 && sc->bge_flags & BGE_FLAG_PCIX) sc->bge_flags |= BGE_FLAG_RX_ALIGNBUG; /* * Call MI attach routine. */ ether_ifattach(ifp, eaddr); callout_init(&sc->bge_stat_ch, CALLOUT_MPSAFE); /* * Hookup IRQ last. */ error = bus_setup_intr(dev, sc->bge_irq, INTR_TYPE_NET | INTR_MPSAFE, bge_intr, sc, &sc->bge_intrhand); if (error) { bge_detach(dev); device_printf(sc->bge_dev, "couldn't set up irq\n"); } fail: return (error); } static int bge_detach(device_t dev) { struct bge_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); ifp = sc->bge_ifp; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) ether_poll_deregister(ifp); #endif BGE_LOCK(sc); bge_stop(sc); bge_reset(sc); BGE_UNLOCK(sc); ether_ifdetach(ifp); if (sc->bge_flags & BGE_FLAG_TBI) { ifmedia_removeall(&sc->bge_ifmedia); } else { bus_generic_detach(dev); device_delete_child(dev, sc->bge_miibus); } bge_release_resources(sc); return (0); } static void bge_release_resources(struct bge_softc *sc) { device_t dev; dev = sc->bge_dev; if (sc->bge_vpd_prodname != NULL) free(sc->bge_vpd_prodname, M_DEVBUF); if (sc->bge_vpd_readonly != NULL) free(sc->bge_vpd_readonly, M_DEVBUF); if (sc->bge_intrhand != NULL) bus_teardown_intr(dev, sc->bge_irq, sc->bge_intrhand); if (sc->bge_irq != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->bge_irq); if (sc->bge_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, BGE_PCI_BAR0, sc->bge_res); if (sc->bge_ifp != NULL) if_free(sc->bge_ifp); bge_dma_free(sc); if (mtx_initialized(&sc->bge_mtx)) /* XXX */ BGE_LOCK_DESTROY(sc); } static int bge_reset(struct bge_softc *sc) { device_t dev; uint32_t cachesize, command, pcistate, reset; int i, val = 0; dev = sc->bge_dev; /* Save some important PCI state. */ cachesize = pci_read_config(dev, BGE_PCI_CACHESZ, 4); command = pci_read_config(dev, BGE_PCI_CMD, 4); pcistate = pci_read_config(dev, BGE_PCI_PCISTATE, 4); pci_write_config(dev, BGE_PCI_MISC_CTL, BGE_PCIMISCCTL_INDIRECT_ACCESS|BGE_PCIMISCCTL_MASK_PCI_INTR| BGE_HIF_SWAP_OPTIONS|BGE_PCIMISCCTL_PCISTATE_RW, 4); reset = BGE_MISCCFG_RESET_CORE_CLOCKS|(65<<1); /* XXX: Broadcom Linux driver. */ if (sc->bge_flags & BGE_FLAG_PCIE) { if (CSR_READ_4(sc, 0x7e2c) == 0x60) /* PCIE 1.0 */ CSR_WRITE_4(sc, 0x7e2c, 0x20); if (sc->bge_chipid != BGE_CHIPID_BCM5750_A0) { /* Prevent PCIE link training during global reset */ CSR_WRITE_4(sc, BGE_MISC_CFG, (1<<29)); reset |= (1<<29); } } /* * Write the magic number to the firmware mailbox at 0xb50 * so that the driver can synchronize with the firmware. */ bge_writemem_ind(sc, BGE_SOFTWARE_GENCOMM, BGE_MAGIC_NUMBER); /* Issue global reset */ bge_writereg_ind(sc, BGE_MISC_CFG, reset); DELAY(1000); /* XXX: Broadcom Linux driver. */ if (sc->bge_flags & BGE_FLAG_PCIE) { if (sc->bge_chipid == BGE_CHIPID_BCM5750_A0) { uint32_t v; DELAY(500000); /* wait for link training to complete */ v = pci_read_config(dev, 0xc4, 4); pci_write_config(dev, 0xc4, v | (1<<15), 4); } /* Set PCIE max payload size and clear error status. */ pci_write_config(dev, 0xd8, 0xf5000, 4); } /* Reset some of the PCI state that got zapped by reset. */ pci_write_config(dev, BGE_PCI_MISC_CTL, BGE_PCIMISCCTL_INDIRECT_ACCESS|BGE_PCIMISCCTL_MASK_PCI_INTR| BGE_HIF_SWAP_OPTIONS|BGE_PCIMISCCTL_PCISTATE_RW, 4); pci_write_config(dev, BGE_PCI_CACHESZ, cachesize, 4); pci_write_config(dev, BGE_PCI_CMD, command, 4); bge_writereg_ind(sc, BGE_MISC_CFG, (65 << 1)); /* Enable memory arbiter. */ if (BGE_IS_5714_FAMILY(sc)) { uint32_t val; val = CSR_READ_4(sc, BGE_MARB_MODE); CSR_WRITE_4(sc, BGE_MARB_MODE, BGE_MARBMODE_ENABLE | val); } else CSR_WRITE_4(sc, BGE_MARB_MODE, BGE_MARBMODE_ENABLE); /* * Poll the value location we just wrote until * we see the 1's complement of the magic number. * This indicates that the firmware initialization * is complete. */ for (i = 0; i < BGE_TIMEOUT; i++) { val = bge_readmem_ind(sc, BGE_SOFTWARE_GENCOMM); if (val == ~BGE_MAGIC_NUMBER) break; DELAY(10); } if (i == BGE_TIMEOUT) { device_printf(sc->bge_dev, "firmware handshake timed out\n"); return(0); } /* * XXX Wait for the value of the PCISTATE register to * return to its original pre-reset state. This is a * fairly good indicator of reset completion. If we don't * wait for the reset to fully complete, trying to read * from the device's non-PCI registers may yield garbage * results. */ for (i = 0; i < BGE_TIMEOUT; i++) { if (pci_read_config(dev, BGE_PCI_PCISTATE, 4) == pcistate) break; DELAY(10); } /* Fix up byte swapping. */ CSR_WRITE_4(sc, BGE_MODE_CTL, BGE_DMA_SWAP_OPTIONS| BGE_MODECTL_BYTESWAP_DATA); /* Tell the ASF firmware we are up */ if (sc->bge_asf_mode & ASF_STACKUP) BGE_SETBIT(sc, BGE_MODE_CTL, BGE_MODECTL_STACKUP); CSR_WRITE_4(sc, BGE_MAC_MODE, 0); /* * The 5704 in TBI mode apparently needs some special * adjustment to insure the SERDES drive level is set * to 1.2V. */ if (sc->bge_asicrev == BGE_ASICREV_BCM5704 && sc->bge_flags & BGE_FLAG_TBI) { uint32_t serdescfg; serdescfg = CSR_READ_4(sc, BGE_SERDES_CFG); serdescfg = (serdescfg & ~0xFFF) | 0x880; CSR_WRITE_4(sc, BGE_SERDES_CFG, serdescfg); } /* XXX: Broadcom Linux driver. */ if (sc->bge_flags & BGE_FLAG_PCIE && sc->bge_chipid != BGE_CHIPID_BCM5750_A0) { uint32_t v; v = CSR_READ_4(sc, 0x7c00); CSR_WRITE_4(sc, 0x7c00, v | (1<<25)); } DELAY(10000); return(0); } /* * Frame reception handling. This is called if there's a frame * on the receive return list. * * Note: we have to be able to handle two possibilities here: * 1) the frame is from the jumbo receive ring * 2) the frame is from the standard receive ring */ static void bge_rxeof(struct bge_softc *sc) { struct ifnet *ifp; int stdcnt = 0, jumbocnt = 0; BGE_LOCK_ASSERT(sc); /* Nothing to do. */ if (sc->bge_rx_saved_considx == sc->bge_ldata.bge_status_block->bge_idx[0].bge_rx_prod_idx) return; ifp = sc->bge_ifp; bus_dmamap_sync(sc->bge_cdata.bge_rx_return_ring_tag, sc->bge_cdata.bge_rx_return_ring_map, BUS_DMASYNC_POSTREAD); bus_dmamap_sync(sc->bge_cdata.bge_rx_std_ring_tag, sc->bge_cdata.bge_rx_std_ring_map, BUS_DMASYNC_POSTREAD); if (BGE_IS_JUMBO_CAPABLE(sc)) bus_dmamap_sync(sc->bge_cdata.bge_rx_jumbo_ring_tag, sc->bge_cdata.bge_rx_jumbo_ring_map, BUS_DMASYNC_POSTREAD); while(sc->bge_rx_saved_considx != sc->bge_ldata.bge_status_block->bge_idx[0].bge_rx_prod_idx) { struct bge_rx_bd *cur_rx; uint32_t rxidx; struct mbuf *m = NULL; uint16_t vlan_tag = 0; int have_tag = 0; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { if (sc->rxcycles <= 0) break; sc->rxcycles--; } #endif cur_rx = &sc->bge_ldata.bge_rx_return_ring[sc->bge_rx_saved_considx]; rxidx = cur_rx->bge_idx; BGE_INC(sc->bge_rx_saved_considx, sc->bge_return_ring_cnt); if (cur_rx->bge_flags & BGE_RXBDFLAG_VLAN_TAG) { have_tag = 1; vlan_tag = cur_rx->bge_vlan_tag; } if (cur_rx->bge_flags & BGE_RXBDFLAG_JUMBO_RING) { BGE_INC(sc->bge_jumbo, BGE_JUMBO_RX_RING_CNT); bus_dmamap_sync(sc->bge_cdata.bge_mtag_jumbo, sc->bge_cdata.bge_rx_jumbo_dmamap[rxidx], BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->bge_cdata.bge_mtag_jumbo, sc->bge_cdata.bge_rx_jumbo_dmamap[rxidx]); m = sc->bge_cdata.bge_rx_jumbo_chain[rxidx]; sc->bge_cdata.bge_rx_jumbo_chain[rxidx] = NULL; jumbocnt++; if (cur_rx->bge_flags & BGE_RXBDFLAG_ERROR) { ifp->if_ierrors++; bge_newbuf_jumbo(sc, sc->bge_jumbo, m); continue; } if (bge_newbuf_jumbo(sc, sc->bge_jumbo, NULL) == ENOBUFS) { ifp->if_ierrors++; bge_newbuf_jumbo(sc, sc->bge_jumbo, m); continue; } } else { BGE_INC(sc->bge_std, BGE_STD_RX_RING_CNT); bus_dmamap_sync(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_rx_std_dmamap[rxidx], BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_rx_std_dmamap[rxidx]); m = sc->bge_cdata.bge_rx_std_chain[rxidx]; sc->bge_cdata.bge_rx_std_chain[rxidx] = NULL; stdcnt++; if (cur_rx->bge_flags & BGE_RXBDFLAG_ERROR) { ifp->if_ierrors++; bge_newbuf_std(sc, sc->bge_std, m); continue; } if (bge_newbuf_std(sc, sc->bge_std, NULL) == ENOBUFS) { ifp->if_ierrors++; bge_newbuf_std(sc, sc->bge_std, m); continue; } } ifp->if_ipackets++; #ifndef __NO_STRICT_ALIGNMENT /* * For architectures with strict alignment we must make sure * the payload is aligned. */ if (sc->bge_flags & BGE_FLAG_RX_ALIGNBUG) { bcopy(m->m_data, m->m_data + ETHER_ALIGN, cur_rx->bge_len); m->m_data += ETHER_ALIGN; } #endif m->m_pkthdr.len = m->m_len = cur_rx->bge_len - ETHER_CRC_LEN; m->m_pkthdr.rcvif = ifp; if (ifp->if_capenable & IFCAP_RXCSUM) { if (cur_rx->bge_flags & BGE_RXBDFLAG_IP_CSUM) { m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; if ((cur_rx->bge_ip_csum ^ 0xffff) == 0) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; } if (cur_rx->bge_flags & BGE_RXBDFLAG_TCP_UDP_CSUM && m->m_pkthdr.len >= ETHER_MIN_NOPAD) { m->m_pkthdr.csum_data = cur_rx->bge_tcp_udp_csum; m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; } } /* * If we received a packet with a vlan tag, * attach that information to the packet. */ if (have_tag) { - VLAN_INPUT_TAG(ifp, m, vlan_tag); - if (m == NULL) - continue; + m->m_pkthdr.ether_vtag = vlan_tag; + m->m_flags |= M_VLANTAG; } BGE_UNLOCK(sc); (*ifp->if_input)(ifp, m); BGE_LOCK(sc); } if (stdcnt > 0) bus_dmamap_sync(sc->bge_cdata.bge_rx_std_ring_tag, sc->bge_cdata.bge_rx_std_ring_map, BUS_DMASYNC_PREWRITE); if (BGE_IS_JUMBO_CAPABLE(sc) && jumbocnt > 0) bus_dmamap_sync(sc->bge_cdata.bge_rx_jumbo_ring_tag, sc->bge_cdata.bge_rx_jumbo_ring_map, BUS_DMASYNC_PREWRITE); CSR_WRITE_4(sc, BGE_MBX_RX_CONS0_LO, sc->bge_rx_saved_considx); if (stdcnt) CSR_WRITE_4(sc, BGE_MBX_RX_STD_PROD_LO, sc->bge_std); if (jumbocnt) CSR_WRITE_4(sc, BGE_MBX_RX_JUMBO_PROD_LO, sc->bge_jumbo); } static void bge_txeof(struct bge_softc *sc) { struct bge_tx_bd *cur_tx = NULL; struct ifnet *ifp; BGE_LOCK_ASSERT(sc); /* Nothing to do. */ if (sc->bge_tx_saved_considx == sc->bge_ldata.bge_status_block->bge_idx[0].bge_tx_cons_idx) return; ifp = sc->bge_ifp; bus_dmamap_sync(sc->bge_cdata.bge_tx_ring_tag, sc->bge_cdata.bge_tx_ring_map, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE); /* * Go through our tx ring and free mbufs for those * frames that have been sent. */ while (sc->bge_tx_saved_considx != sc->bge_ldata.bge_status_block->bge_idx[0].bge_tx_cons_idx) { uint32_t idx = 0; idx = sc->bge_tx_saved_considx; cur_tx = &sc->bge_ldata.bge_tx_ring[idx]; if (cur_tx->bge_flags & BGE_TXBDFLAG_END) ifp->if_opackets++; if (sc->bge_cdata.bge_tx_chain[idx] != NULL) { bus_dmamap_sync(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_tx_dmamap[idx], BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->bge_cdata.bge_mtag, sc->bge_cdata.bge_tx_dmamap[idx]); m_freem(sc->bge_cdata.bge_tx_chain[idx]); sc->bge_cdata.bge_tx_chain[idx] = NULL; } sc->bge_txcnt--; BGE_INC(sc->bge_tx_saved_considx, BGE_TX_RING_CNT); ifp->if_timer = 0; } if (cur_tx != NULL) ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; } #ifdef DEVICE_POLLING static void bge_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct bge_softc *sc = ifp->if_softc; uint32_t statusword; BGE_LOCK(sc); if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { BGE_UNLOCK(sc); return; } bus_dmamap_sync(sc->bge_cdata.bge_status_tag, sc->bge_cdata.bge_status_map, BUS_DMASYNC_POSTREAD); statusword = atomic_readandclear_32( &sc->bge_ldata.bge_status_block->bge_status); bus_dmamap_sync(sc->bge_cdata.bge_status_tag, sc->bge_cdata.bge_status_map, BUS_DMASYNC_PREREAD); /* Note link event. It will be processed by POLL_AND_CHECK_STATUS cmd */ if (statusword & BGE_STATFLAG_LINKSTATE_CHANGED) sc->bge_link_evt++; if (cmd == POLL_AND_CHECK_STATUS) if ((sc->bge_asicrev == BGE_ASICREV_BCM5700 && sc->bge_chipid != BGE_CHIPID_BCM5700_B2) || sc->bge_link_evt || (sc->bge_flags & BGE_FLAG_TBI)) bge_link_upd(sc); sc->rxcycles = count; bge_rxeof(sc); bge_txeof(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) bge_start_locked(ifp); BGE_UNLOCK(sc); } #endif /* DEVICE_POLLING */ static void bge_intr(void *xsc) { struct bge_softc *sc; struct ifnet *ifp; uint32_t statusword; sc = xsc; BGE_LOCK(sc); ifp = sc->bge_ifp; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { BGE_UNLOCK(sc); return; } #endif /* * Do the mandatory PCI flush as well as get the link status. */ statusword = CSR_READ_4(sc, BGE_MAC_STS) & BGE_MACSTAT_LINK_CHANGED; /* Ack interrupt and stop others from occuring. */ CSR_WRITE_4(sc, BGE_MBX_IRQ0_LO, 1); /* Make sure the descriptor ring indexes are coherent. */ bus_dmamap_sync(sc->bge_cdata.bge_status_tag, sc->bge_cdata.bge_status_map, BUS_DMASYNC_POSTREAD); bus_dmamap_sync(sc->bge_cdata.bge_status_tag, sc->bge_cdata.bge_status_map, BUS_DMASYNC_PREREAD); if ((sc->bge_asicrev == BGE_ASICREV_BCM5700 && sc->bge_chipid != BGE_CHIPID_BCM5700_B2) || statusword || sc->bge_link_evt) bge_link_upd(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) { /* Check RX return ring producer/consumer. */ bge_rxeof(sc); /* Check TX ring producer/consumer. */ bge_txeof(sc); } /* Re-enable interrupts. */ CSR_WRITE_4(sc, BGE_MBX_IRQ0_LO, 0); if (ifp->if_drv_flags & IFF_DRV_RUNNING && !IFQ_DRV_IS_EMPTY(&ifp->if_snd)) bge_start_locked(ifp); BGE_UNLOCK(sc); } static void bge_asf_driver_up(struct bge_softc *sc) { if (sc->bge_asf_mode & ASF_STACKUP) { /* Send ASF heartbeat aprox. every 2s */ if (sc->bge_asf_count) sc->bge_asf_count --; else { sc->bge_asf_count = 5; bge_writemem_ind(sc, BGE_SOFTWARE_GENCOMM_FW, BGE_FW_DRV_ALIVE); bge_writemem_ind(sc, BGE_SOFTWARE_GENNCOMM_FW_LEN, 4); bge_writemem_ind(sc, BGE_SOFTWARE_GENNCOMM_FW_DATA, 3); CSR_WRITE_4(sc, BGE_CPU_EVENT, CSR_READ_4(sc, BGE_CPU_EVENT) != (1 << 14)); } } } static void bge_tick_locked(struct bge_softc *sc) { struct mii_data *mii = NULL; BGE_LOCK_ASSERT(sc); if (BGE_IS_5705_OR_BEYOND(sc)) bge_stats_update_regs(sc); else bge_stats_update(sc); if ((sc->bge_flags & BGE_FLAG_TBI) == 0) { mii = device_get_softc(sc->bge_miibus); /* Don't mess with the PHY in IPMI/ASF mode */ if (!((sc->bge_asf_mode & ASF_STACKUP) && (sc->bge_link))) mii_tick(mii); } else { /* * Since in TBI mode auto-polling can't be used we should poll * link status manually. Here we register pending link event * and trigger interrupt. */ #ifdef DEVICE_POLLING /* In polling mode we poll link state in bge_poll(). */ if (!(sc->bge_ifp->if_capenable & IFCAP_POLLING)) #endif { sc->bge_link_evt++; BGE_SETBIT(sc, BGE_MISC_LOCAL_CTL, BGE_MLC_INTR_SET); } } bge_asf_driver_up(sc); callout_reset(&sc->bge_stat_ch, hz, bge_tick, sc); } static void bge_tick(void *xsc) { struct bge_softc *sc; sc = xsc; BGE_LOCK(sc); bge_tick_locked(sc); BGE_UNLOCK(sc); } static void bge_stats_update_regs(struct bge_softc *sc) { struct bge_mac_stats_regs stats; struct ifnet *ifp; uint32_t *s; u_long cnt; /* current register value */ int i; ifp = sc->bge_ifp; s = (uint32_t *)&stats; for (i = 0; i < sizeof(struct bge_mac_stats_regs); i += 4) { *s = CSR_READ_4(sc, BGE_RX_STATS + i); s++; } cnt = stats.dot3StatsSingleCollisionFrames + stats.dot3StatsMultipleCollisionFrames + stats.dot3StatsExcessiveCollisions + stats.dot3StatsLateCollisions; ifp->if_collisions += cnt >= sc->bge_tx_collisions ? cnt - sc->bge_tx_collisions : cnt; sc->bge_tx_collisions = cnt; } static void bge_stats_update(struct bge_softc *sc) { struct ifnet *ifp; bus_size_t stats; u_long cnt; /* current register value */ ifp = sc->bge_ifp; stats = BGE_MEMWIN_START + BGE_STATS_BLOCK; #define READ_STAT(sc, stats, stat) \ CSR_READ_4(sc, stats + offsetof(struct bge_stats, stat)) cnt = READ_STAT(sc, stats, txstats.dot3StatsSingleCollisionFrames.bge_addr_lo); cnt += READ_STAT(sc, stats, txstats.dot3StatsMultipleCollisionFrames.bge_addr_lo); cnt += READ_STAT(sc, stats, txstats.dot3StatsExcessiveCollisions.bge_addr_lo); cnt += READ_STAT(sc, stats, txstats.dot3StatsLateCollisions.bge_addr_lo); ifp->if_collisions += cnt >= sc->bge_tx_collisions ? cnt - sc->bge_tx_collisions : cnt; sc->bge_tx_collisions = cnt; cnt = READ_STAT(sc, stats, ifInDiscards.bge_addr_lo); ifp->if_ierrors += cnt >= sc->bge_rx_discards ? cnt - sc->bge_rx_discards : cnt; sc->bge_rx_discards = cnt; cnt = READ_STAT(sc, stats, txstats.ifOutDiscards.bge_addr_lo); ifp->if_oerrors += cnt >= sc->bge_tx_discards ? cnt - sc->bge_tx_discards : cnt; sc->bge_tx_discards = cnt; #undef READ_STAT } /* * Pad outbound frame to ETHER_MIN_NOPAD for an unusual reason. * The bge hardware will pad out Tx runts to ETHER_MIN_NOPAD, * but when such padded frames employ the bge IP/TCP checksum offload, * the hardware checksum assist gives incorrect results (possibly * from incorporating its own padding into the UDP/TCP checksum; who knows). * If we pad such runts with zeros, the onboard checksum comes out correct. */ static __inline int bge_cksum_pad(struct mbuf *m) { int padlen = ETHER_MIN_NOPAD - m->m_pkthdr.len; struct mbuf *last; /* If there's only the packet-header and we can pad there, use it. */ if (m->m_pkthdr.len == m->m_len && M_WRITABLE(m) && M_TRAILINGSPACE(m) >= padlen) { last = m; } else { /* * Walk packet chain to find last mbuf. We will either * pad there, or append a new mbuf and pad it. */ for (last = m; last->m_next != NULL; last = last->m_next); if (!(M_WRITABLE(last) && M_TRAILINGSPACE(last) >= padlen)) { /* Allocate new empty mbuf, pad it. Compact later. */ struct mbuf *n; MGET(n, M_DONTWAIT, MT_DATA); if (n == NULL) return (ENOBUFS); n->m_len = 0; last->m_next = n; last = n; } } /* Now zero the pad area, to avoid the bge cksum-assist bug. */ memset(mtod(last, caddr_t) + last->m_len, 0, padlen); last->m_len += padlen; m->m_pkthdr.len += padlen; return (0); } /* * Encapsulate an mbuf chain in the tx ring by coupling the mbuf data * pointers to descriptors. */ static int bge_encap(struct bge_softc *sc, struct mbuf **m_head, uint32_t *txidx) { bus_dma_segment_t segs[BGE_NSEG_NEW]; bus_dmamap_t map; struct bge_tx_bd *d; struct mbuf *m = *m_head; - struct m_tag *mtag; uint32_t idx = *txidx; uint16_t csum_flags; int nsegs, i, error; csum_flags = 0; if (m->m_pkthdr.csum_flags) { if (m->m_pkthdr.csum_flags & CSUM_IP) csum_flags |= BGE_TXBDFLAG_IP_CSUM; if (m->m_pkthdr.csum_flags & (CSUM_TCP | CSUM_UDP)) { csum_flags |= BGE_TXBDFLAG_TCP_UDP_CSUM; if (m->m_pkthdr.len < ETHER_MIN_NOPAD && (error = bge_cksum_pad(m)) != 0) { m_freem(m); *m_head = NULL; return (error); } } if (m->m_flags & M_LASTFRAG) csum_flags |= BGE_TXBDFLAG_IP_FRAG_END; else if (m->m_flags & M_FRAG) csum_flags |= BGE_TXBDFLAG_IP_FRAG; } map = sc->bge_cdata.bge_tx_dmamap[idx]; error = bus_dmamap_load_mbuf_sg(sc->bge_cdata.bge_mtag, map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error == EFBIG) { m = m_defrag(m, M_DONTWAIT); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (ENOBUFS); } *m_head = m; error = bus_dmamap_load_mbuf_sg(sc->bge_cdata.bge_mtag, map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error) { m_freem(m); *m_head = NULL; return (error); } } else if (error != 0) return (error); /* * Sanity check: avoid coming within 16 descriptors * of the end of the ring. */ if (nsegs > (BGE_TX_RING_CNT - sc->bge_txcnt - 16)) { bus_dmamap_unload(sc->bge_cdata.bge_mtag, map); return (ENOBUFS); } bus_dmamap_sync(sc->bge_cdata.bge_mtag, map, BUS_DMASYNC_PREWRITE); for (i = 0; ; i++) { d = &sc->bge_ldata.bge_tx_ring[idx]; d->bge_addr.bge_addr_lo = BGE_ADDR_LO(segs[i].ds_addr); d->bge_addr.bge_addr_hi = BGE_ADDR_HI(segs[i].ds_addr); d->bge_len = segs[i].ds_len; d->bge_flags = csum_flags; if (i == nsegs - 1) break; BGE_INC(idx, BGE_TX_RING_CNT); } /* Mark the last segment as end of packet... */ d->bge_flags |= BGE_TXBDFLAG_END; /* ... and put VLAN tag into first segment. */ d = &sc->bge_ldata.bge_tx_ring[*txidx]; - if ((mtag = VLAN_OUTPUT_TAG(sc->bge_ifp, m)) != NULL) { + if (m->m_flags & M_VLANTAG) { d->bge_flags |= BGE_TXBDFLAG_VLAN_TAG; - d->bge_vlan_tag = VLAN_TAG_VALUE(mtag); + d->bge_vlan_tag = m->m_pkthdr.ether_vtag; } else d->bge_vlan_tag = 0; /* * Insure that the map for this transmission * is placed at the array index of the last descriptor * in this chain. */ sc->bge_cdata.bge_tx_dmamap[*txidx] = sc->bge_cdata.bge_tx_dmamap[idx]; sc->bge_cdata.bge_tx_dmamap[idx] = map; sc->bge_cdata.bge_tx_chain[idx] = m; sc->bge_txcnt += nsegs; BGE_INC(idx, BGE_TX_RING_CNT); *txidx = idx; return (0); } /* * Main transmit routine. To avoid having to do mbuf copies, we put pointers * to the mbuf data regions directly in the transmit descriptors. */ static void bge_start_locked(struct ifnet *ifp) { struct bge_softc *sc; struct mbuf *m_head = NULL; uint32_t prodidx; int count = 0; sc = ifp->if_softc; if (!sc->bge_link || IFQ_DRV_IS_EMPTY(&ifp->if_snd)) return; prodidx = sc->bge_tx_prodidx; while(sc->bge_cdata.bge_tx_chain[prodidx] == NULL) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * XXX * The code inside the if() block is never reached since we * must mark CSUM_IP_FRAGS in our if_hwassist to start getting * requests to checksum TCP/UDP in a fragmented packet. * * XXX * safety overkill. If this is a fragmented packet chain * with delayed TCP/UDP checksums, then only encapsulate * it if we have enough descriptors to handle the entire * chain at once. * (paranoia -- may not actually be needed) */ if (m_head->m_flags & M_FIRSTFRAG && m_head->m_pkthdr.csum_flags & (CSUM_DELAY_DATA)) { if ((BGE_TX_RING_CNT - sc->bge_txcnt) < m_head->m_pkthdr.csum_data + 16) { IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } } /* * Pack the data into the transmit ring. If we * don't have room, set the OACTIVE flag and wait * for the NIC to drain the ring. */ if (bge_encap(sc, &m_head, &prodidx)) { if (m_head == NULL) break; IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } ++count; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ BPF_MTAP(ifp, m_head); } if (count == 0) /* No packets were dequeued. */ return; /* Transmit. */ CSR_WRITE_4(sc, BGE_MBX_TX_HOST_PROD0_LO, prodidx); /* 5700 b2 errata */ if (sc->bge_chiprev == BGE_CHIPREV_5700_BX) CSR_WRITE_4(sc, BGE_MBX_TX_HOST_PROD0_LO, prodidx); sc->bge_tx_prodidx = prodidx; /* * Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; } /* * Main transmit routine. To avoid having to do mbuf copies, we put pointers * to the mbuf data regions directly in the transmit descriptors. */ static void bge_start(struct ifnet *ifp) { struct bge_softc *sc; sc = ifp->if_softc; BGE_LOCK(sc); bge_start_locked(ifp); BGE_UNLOCK(sc); } static void bge_init_locked(struct bge_softc *sc) { struct ifnet *ifp; uint16_t *m; BGE_LOCK_ASSERT(sc); ifp = sc->bge_ifp; if (ifp->if_drv_flags & IFF_DRV_RUNNING) return; /* Cancel pending I/O and flush buffers. */ bge_stop(sc); bge_stop_fw(sc); bge_sig_pre_reset(sc, BGE_RESET_START); bge_reset(sc); bge_sig_legacy(sc, BGE_RESET_START); bge_sig_post_reset(sc, BGE_RESET_START); bge_chipinit(sc); /* * Init the various state machines, ring * control blocks and firmware. */ if (bge_blockinit(sc)) { device_printf(sc->bge_dev, "initialization failure\n"); return; } ifp = sc->bge_ifp; /* Specify MTU. */ CSR_WRITE_4(sc, BGE_RX_MTU, ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN + ETHER_VLAN_ENCAP_LEN); /* Load our MAC address. */ m = (uint16_t *)IF_LLADDR(sc->bge_ifp); CSR_WRITE_4(sc, BGE_MAC_ADDR1_LO, htons(m[0])); CSR_WRITE_4(sc, BGE_MAC_ADDR1_HI, (htons(m[1]) << 16) | htons(m[2])); /* Enable or disable promiscuous mode as needed. */ if (ifp->if_flags & IFF_PROMISC) { BGE_SETBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC); } else { BGE_CLRBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC); } /* Program multicast filter. */ bge_setmulti(sc); /* Init RX ring. */ bge_init_rx_ring_std(sc); /* * Workaround for a bug in 5705 ASIC rev A0. Poll the NIC's * memory to insure that the chip has in fact read the first * entry of the ring. */ if (sc->bge_chipid == BGE_CHIPID_BCM5705_A0) { uint32_t v, i; for (i = 0; i < 10; i++) { DELAY(20); v = bge_readmem_ind(sc, BGE_STD_RX_RINGS + 8); if (v == (MCLBYTES - ETHER_ALIGN)) break; } if (i == 10) device_printf (sc->bge_dev, "5705 A0 chip failed to load RX ring\n"); } /* Init jumbo RX ring. */ if (ifp->if_mtu > (ETHERMTU + ETHER_HDR_LEN + ETHER_CRC_LEN)) bge_init_rx_ring_jumbo(sc); /* Init our RX return ring index. */ sc->bge_rx_saved_considx = 0; /* Init TX ring. */ bge_init_tx_ring(sc); /* Turn on transmitter. */ BGE_SETBIT(sc, BGE_TX_MODE, BGE_TXMODE_ENABLE); /* Turn on receiver. */ BGE_SETBIT(sc, BGE_RX_MODE, BGE_RXMODE_ENABLE); /* Tell firmware we're alive. */ BGE_SETBIT(sc, BGE_MODE_CTL, BGE_MODECTL_STACKUP); #ifdef DEVICE_POLLING /* Disable interrupts if we are polling. */ if (ifp->if_capenable & IFCAP_POLLING) { BGE_SETBIT(sc, BGE_PCI_MISC_CTL, BGE_PCIMISCCTL_MASK_PCI_INTR); CSR_WRITE_4(sc, BGE_MBX_IRQ0_LO, 1); CSR_WRITE_4(sc, BGE_HCC_RX_MAX_COAL_BDS_INT, 1); CSR_WRITE_4(sc, BGE_HCC_TX_MAX_COAL_BDS_INT, 1); } else #endif /* Enable host interrupts. */ { BGE_SETBIT(sc, BGE_PCI_MISC_CTL, BGE_PCIMISCCTL_CLEAR_INTA); BGE_CLRBIT(sc, BGE_PCI_MISC_CTL, BGE_PCIMISCCTL_MASK_PCI_INTR); CSR_WRITE_4(sc, BGE_MBX_IRQ0_LO, 0); } bge_ifmedia_upd_locked(ifp); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; callout_reset(&sc->bge_stat_ch, hz, bge_tick, sc); } static void bge_init(void *xsc) { struct bge_softc *sc = xsc; BGE_LOCK(sc); bge_init_locked(sc); BGE_UNLOCK(sc); } /* * Set media options. */ static int bge_ifmedia_upd(struct ifnet *ifp) { struct bge_softc *sc = ifp->if_softc; int res; BGE_LOCK(sc); res = bge_ifmedia_upd_locked(ifp); BGE_UNLOCK(sc); return (res); } static int bge_ifmedia_upd_locked(struct ifnet *ifp) { struct bge_softc *sc = ifp->if_softc; struct mii_data *mii; struct ifmedia *ifm; BGE_LOCK_ASSERT(sc); ifm = &sc->bge_ifmedia; /* If this is a 1000baseX NIC, enable the TBI port. */ if (sc->bge_flags & BGE_FLAG_TBI) { if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER) return (EINVAL); switch(IFM_SUBTYPE(ifm->ifm_media)) { case IFM_AUTO: /* * The BCM5704 ASIC appears to have a special * mechanism for programming the autoneg * advertisement registers in TBI mode. */ if (bge_fake_autoneg == 0 && sc->bge_asicrev == BGE_ASICREV_BCM5704) { uint32_t sgdig; CSR_WRITE_4(sc, BGE_TX_TBI_AUTONEG, 0); sgdig = CSR_READ_4(sc, BGE_SGDIG_CFG); sgdig |= BGE_SGDIGCFG_AUTO| BGE_SGDIGCFG_PAUSE_CAP| BGE_SGDIGCFG_ASYM_PAUSE; CSR_WRITE_4(sc, BGE_SGDIG_CFG, sgdig|BGE_SGDIGCFG_SEND); DELAY(5); CSR_WRITE_4(sc, BGE_SGDIG_CFG, sgdig); } break; case IFM_1000_SX: if ((ifm->ifm_media & IFM_GMASK) == IFM_FDX) { BGE_CLRBIT(sc, BGE_MAC_MODE, BGE_MACMODE_HALF_DUPLEX); } else { BGE_SETBIT(sc, BGE_MAC_MODE, BGE_MACMODE_HALF_DUPLEX); } break; default: return (EINVAL); } return (0); } sc->bge_link_evt++; mii = device_get_softc(sc->bge_miibus); if (mii->mii_instance) { struct mii_softc *miisc; for (miisc = LIST_FIRST(&mii->mii_phys); miisc != NULL; miisc = LIST_NEXT(miisc, mii_list)) mii_phy_reset(miisc); } mii_mediachg(mii); return (0); } /* * Report current media status. */ static void bge_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct bge_softc *sc = ifp->if_softc; struct mii_data *mii; BGE_LOCK(sc); if (sc->bge_flags & BGE_FLAG_TBI) { ifmr->ifm_status = IFM_AVALID; ifmr->ifm_active = IFM_ETHER; if (CSR_READ_4(sc, BGE_MAC_STS) & BGE_MACSTAT_TBI_PCS_SYNCHED) ifmr->ifm_status |= IFM_ACTIVE; else { ifmr->ifm_active |= IFM_NONE; BGE_UNLOCK(sc); return; } ifmr->ifm_active |= IFM_1000_SX; if (CSR_READ_4(sc, BGE_MAC_MODE) & BGE_MACMODE_HALF_DUPLEX) ifmr->ifm_active |= IFM_HDX; else ifmr->ifm_active |= IFM_FDX; BGE_UNLOCK(sc); return; } mii = device_get_softc(sc->bge_miibus); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; BGE_UNLOCK(sc); } static int bge_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct bge_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; struct mii_data *mii; int mask, error = 0; switch (command) { case SIOCSIFMTU: if (ifr->ifr_mtu < ETHERMIN || ((BGE_IS_JUMBO_CAPABLE(sc)) && ifr->ifr_mtu > BGE_JUMBO_MTU) || ((!BGE_IS_JUMBO_CAPABLE(sc)) && ifr->ifr_mtu > ETHERMTU)) error = EINVAL; else if (ifp->if_mtu != ifr->ifr_mtu) { ifp->if_mtu = ifr->ifr_mtu; ifp->if_drv_flags &= ~IFF_DRV_RUNNING; bge_init(sc); } break; case SIOCSIFFLAGS: BGE_LOCK(sc); if (ifp->if_flags & IFF_UP) { /* * If only the state of the PROMISC flag changed, * then just use the 'set promisc mode' command * instead of reinitializing the entire NIC. Doing * a full re-init means reloading the firmware and * waiting for it to start up, which may take a * second or two. Similarly for ALLMULTI. */ if (ifp->if_drv_flags & IFF_DRV_RUNNING && ifp->if_flags & IFF_PROMISC && !(sc->bge_if_flags & IFF_PROMISC)) { BGE_SETBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC); } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && !(ifp->if_flags & IFF_PROMISC) && sc->bge_if_flags & IFF_PROMISC) { BGE_CLRBIT(sc, BGE_RX_MODE, BGE_RXMODE_RX_PROMISC); } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && (ifp->if_flags ^ sc->bge_if_flags) & IFF_ALLMULTI) { bge_setmulti(sc); } else bge_init_locked(sc); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { bge_stop(sc); } } sc->bge_if_flags = ifp->if_flags; BGE_UNLOCK(sc); error = 0; break; case SIOCADDMULTI: case SIOCDELMULTI: if (ifp->if_drv_flags & IFF_DRV_RUNNING) { BGE_LOCK(sc); bge_setmulti(sc); BGE_UNLOCK(sc); error = 0; } break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: if (sc->bge_flags & BGE_FLAG_TBI) { error = ifmedia_ioctl(ifp, ifr, &sc->bge_ifmedia, command); } else { mii = device_get_softc(sc->bge_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); } break; case SIOCSIFCAP: mask = ifr->ifr_reqcap ^ ifp->if_capenable; #ifdef DEVICE_POLLING if (mask & IFCAP_POLLING) { if (ifr->ifr_reqcap & IFCAP_POLLING) { error = ether_poll_register(bge_poll, ifp); if (error) return (error); BGE_LOCK(sc); BGE_SETBIT(sc, BGE_PCI_MISC_CTL, BGE_PCIMISCCTL_MASK_PCI_INTR); CSR_WRITE_4(sc, BGE_MBX_IRQ0_LO, 1); CSR_WRITE_4(sc, BGE_HCC_RX_MAX_COAL_BDS_INT, 1); CSR_WRITE_4(sc, BGE_HCC_TX_MAX_COAL_BDS_INT, 1); ifp->if_capenable |= IFCAP_POLLING; BGE_UNLOCK(sc); } else { error = ether_poll_deregister(ifp); /* Enable interrupt even in error case */ BGE_LOCK(sc); CSR_WRITE_4(sc, BGE_HCC_RX_MAX_COAL_BDS_INT, 0); CSR_WRITE_4(sc, BGE_HCC_TX_MAX_COAL_BDS_INT, 0); BGE_CLRBIT(sc, BGE_PCI_MISC_CTL, BGE_PCIMISCCTL_MASK_PCI_INTR); CSR_WRITE_4(sc, BGE_MBX_IRQ0_LO, 0); ifp->if_capenable &= ~IFCAP_POLLING; BGE_UNLOCK(sc); } } #endif if (mask & IFCAP_HWCSUM) { ifp->if_capenable ^= IFCAP_HWCSUM; if (IFCAP_HWCSUM & ifp->if_capenable && IFCAP_HWCSUM & ifp->if_capabilities) ifp->if_hwassist = BGE_CSUM_FEATURES; else ifp->if_hwassist = 0; VLAN_CAPABILITIES(ifp); } break; default: error = ether_ioctl(ifp, command, data); break; } return (error); } static void bge_watchdog(struct ifnet *ifp) { struct bge_softc *sc; sc = ifp->if_softc; if_printf(ifp, "watchdog timeout -- resetting\n"); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; bge_init(sc); ifp->if_oerrors++; } /* * Stop the adapter and free any mbufs allocated to the * RX and TX lists. */ static void bge_stop(struct bge_softc *sc) { struct ifnet *ifp; struct ifmedia_entry *ifm; struct mii_data *mii = NULL; int mtmp, itmp; BGE_LOCK_ASSERT(sc); ifp = sc->bge_ifp; if ((sc->bge_flags & BGE_FLAG_TBI) == 0) mii = device_get_softc(sc->bge_miibus); callout_stop(&sc->bge_stat_ch); /* * Disable all of the receiver blocks. */ BGE_CLRBIT(sc, BGE_RX_MODE, BGE_RXMODE_ENABLE); BGE_CLRBIT(sc, BGE_RBDI_MODE, BGE_RBDIMODE_ENABLE); BGE_CLRBIT(sc, BGE_RXLP_MODE, BGE_RXLPMODE_ENABLE); if (!(BGE_IS_5705_OR_BEYOND(sc))) BGE_CLRBIT(sc, BGE_RXLS_MODE, BGE_RXLSMODE_ENABLE); BGE_CLRBIT(sc, BGE_RDBDI_MODE, BGE_RBDIMODE_ENABLE); BGE_CLRBIT(sc, BGE_RDC_MODE, BGE_RDCMODE_ENABLE); BGE_CLRBIT(sc, BGE_RBDC_MODE, BGE_RBDCMODE_ENABLE); /* * Disable all of the transmit blocks. */ BGE_CLRBIT(sc, BGE_SRS_MODE, BGE_SRSMODE_ENABLE); BGE_CLRBIT(sc, BGE_SBDI_MODE, BGE_SBDIMODE_ENABLE); BGE_CLRBIT(sc, BGE_SDI_MODE, BGE_SDIMODE_ENABLE); BGE_CLRBIT(sc, BGE_RDMA_MODE, BGE_RDMAMODE_ENABLE); BGE_CLRBIT(sc, BGE_SDC_MODE, BGE_SDCMODE_ENABLE); if (!(BGE_IS_5705_OR_BEYOND(sc))) BGE_CLRBIT(sc, BGE_DMAC_MODE, BGE_DMACMODE_ENABLE); BGE_CLRBIT(sc, BGE_SBDC_MODE, BGE_SBDCMODE_ENABLE); /* * Shut down all of the memory managers and related * state machines. */ BGE_CLRBIT(sc, BGE_HCC_MODE, BGE_HCCMODE_ENABLE); BGE_CLRBIT(sc, BGE_WDMA_MODE, BGE_WDMAMODE_ENABLE); if (!(BGE_IS_5705_OR_BEYOND(sc))) BGE_CLRBIT(sc, BGE_MBCF_MODE, BGE_MBCFMODE_ENABLE); CSR_WRITE_4(sc, BGE_FTQ_RESET, 0xFFFFFFFF); CSR_WRITE_4(sc, BGE_FTQ_RESET, 0); if (!(BGE_IS_5705_OR_BEYOND(sc))) { BGE_CLRBIT(sc, BGE_BMAN_MODE, BGE_BMANMODE_ENABLE); BGE_CLRBIT(sc, BGE_MARB_MODE, BGE_MARBMODE_ENABLE); } /* Disable host interrupts. */ BGE_SETBIT(sc, BGE_PCI_MISC_CTL, BGE_PCIMISCCTL_MASK_PCI_INTR); CSR_WRITE_4(sc, BGE_MBX_IRQ0_LO, 1); /* * Tell firmware we're shutting down. */ bge_stop_fw(sc); bge_sig_pre_reset(sc, BGE_RESET_STOP); bge_reset(sc); bge_sig_legacy(sc, BGE_RESET_STOP); bge_sig_post_reset(sc, BGE_RESET_STOP); /* * Keep the ASF firmware running if up. */ if (sc->bge_asf_mode & ASF_STACKUP) BGE_SETBIT(sc, BGE_MODE_CTL, BGE_MODECTL_STACKUP); else BGE_CLRBIT(sc, BGE_MODE_CTL, BGE_MODECTL_STACKUP); /* Free the RX lists. */ bge_free_rx_ring_std(sc); /* Free jumbo RX list. */ if (BGE_IS_JUMBO_CAPABLE(sc)) bge_free_rx_ring_jumbo(sc); /* Free TX buffers. */ bge_free_tx_ring(sc); /* * Isolate/power down the PHY, but leave the media selection * unchanged so that things will be put back to normal when * we bring the interface back up. */ if ((sc->bge_flags & BGE_FLAG_TBI) == 0) { itmp = ifp->if_flags; ifp->if_flags |= IFF_UP; /* * If we are called from bge_detach(), mii is already NULL. */ if (mii != NULL) { ifm = mii->mii_media.ifm_cur; mtmp = ifm->ifm_media; ifm->ifm_media = IFM_ETHER|IFM_NONE; mii_mediachg(mii); ifm->ifm_media = mtmp; } ifp->if_flags = itmp; } sc->bge_tx_saved_considx = BGE_TXCONS_UNSET; /* * We can't just call bge_link_upd() cause chip is almost stopped so * bge_link_upd -> bge_tick_locked -> bge_stats_update sequence may * lead to hardware deadlock. So we just clearing MAC's link state * (PHY may still have link UP). */ if (bootverbose && sc->bge_link) if_printf(sc->bge_ifp, "link DOWN\n"); sc->bge_link = 0; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); } /* * Stop all chip I/O so that the kernel's probe routines don't * get confused by errant DMAs when rebooting. */ static void bge_shutdown(device_t dev) { struct bge_softc *sc; sc = device_get_softc(dev); BGE_LOCK(sc); bge_stop(sc); bge_reset(sc); BGE_UNLOCK(sc); } static int bge_suspend(device_t dev) { struct bge_softc *sc; sc = device_get_softc(dev); BGE_LOCK(sc); bge_stop(sc); BGE_UNLOCK(sc); return (0); } static int bge_resume(device_t dev) { struct bge_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); BGE_LOCK(sc); ifp = sc->bge_ifp; if (ifp->if_flags & IFF_UP) { bge_init_locked(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) bge_start_locked(ifp); } BGE_UNLOCK(sc); return (0); } static void bge_link_upd(struct bge_softc *sc) { struct mii_data *mii; uint32_t link, status; BGE_LOCK_ASSERT(sc); /* Clear 'pending link event' flag. */ sc->bge_link_evt = 0; /* * Process link state changes. * Grrr. The link status word in the status block does * not work correctly on the BCM5700 rev AX and BX chips, * according to all available information. Hence, we have * to enable MII interrupts in order to properly obtain * async link changes. Unfortunately, this also means that * we have to read the MAC status register to detect link * changes, thereby adding an additional register access to * the interrupt handler. * * XXX: perhaps link state detection procedure used for * BGE_CHIPID_BCM5700_B2 can be used for others BCM5700 revisions. */ if (sc->bge_asicrev == BGE_ASICREV_BCM5700 && sc->bge_chipid != BGE_CHIPID_BCM5700_B2) { status = CSR_READ_4(sc, BGE_MAC_STS); if (status & BGE_MACSTAT_MI_INTERRUPT) { callout_stop(&sc->bge_stat_ch); bge_tick_locked(sc); mii = device_get_softc(sc->bge_miibus); if (!sc->bge_link && mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->bge_link++; if (bootverbose) if_printf(sc->bge_ifp, "link UP\n"); } else if (sc->bge_link && (!(mii->mii_media_status & IFM_ACTIVE) || IFM_SUBTYPE(mii->mii_media_active) == IFM_NONE)) { sc->bge_link = 0; if (bootverbose) if_printf(sc->bge_ifp, "link DOWN\n"); } /* Clear the interrupt. */ CSR_WRITE_4(sc, BGE_MAC_EVT_ENB, BGE_EVTENB_MI_INTERRUPT); bge_miibus_readreg(sc->bge_dev, 1, BRGPHY_MII_ISR); bge_miibus_writereg(sc->bge_dev, 1, BRGPHY_MII_IMR, BRGPHY_INTRS); } return; } if (sc->bge_flags & BGE_FLAG_TBI) { status = CSR_READ_4(sc, BGE_MAC_STS); if (status & BGE_MACSTAT_TBI_PCS_SYNCHED) { if (!sc->bge_link) { sc->bge_link++; if (sc->bge_asicrev == BGE_ASICREV_BCM5704) BGE_CLRBIT(sc, BGE_MAC_MODE, BGE_MACMODE_TBI_SEND_CFGS); CSR_WRITE_4(sc, BGE_MAC_STS, 0xFFFFFFFF); if (bootverbose) if_printf(sc->bge_ifp, "link UP\n"); if_link_state_change(sc->bge_ifp, LINK_STATE_UP); } } else if (sc->bge_link) { sc->bge_link = 0; if (bootverbose) if_printf(sc->bge_ifp, "link DOWN\n"); if_link_state_change(sc->bge_ifp, LINK_STATE_DOWN); } /* Discard link events for MII/GMII cards if MI auto-polling disabled */ } else if (CSR_READ_4(sc, BGE_MI_MODE) & BGE_MIMODE_AUTOPOLL) { /* * Some broken BCM chips have BGE_STATFLAG_LINKSTATE_CHANGED bit * in status word always set. Workaround this bug by reading * PHY link status directly. */ link = (CSR_READ_4(sc, BGE_MI_STS) & BGE_MISTS_LINK) ? 1 : 0; if (link != sc->bge_link || sc->bge_asicrev == BGE_ASICREV_BCM5700) { callout_stop(&sc->bge_stat_ch); bge_tick_locked(sc); mii = device_get_softc(sc->bge_miibus); if (!sc->bge_link && mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->bge_link++; if (bootverbose) if_printf(sc->bge_ifp, "link UP\n"); } else if (sc->bge_link && (!(mii->mii_media_status & IFM_ACTIVE) || IFM_SUBTYPE(mii->mii_media_active) == IFM_NONE)) { sc->bge_link = 0; if (bootverbose) if_printf(sc->bge_ifp, "link DOWN\n"); } } } /* Clear the attention. */ CSR_WRITE_4(sc, BGE_MAC_STS, BGE_MACSTAT_SYNC_CHANGED| BGE_MACSTAT_CFG_CHANGED|BGE_MACSTAT_MI_COMPLETE| BGE_MACSTAT_LINK_CHANGED); } Index: head/sys/dev/em/if_em.c =================================================================== --- head/sys/dev/em/if_em.c (revision 162374) +++ head/sys/dev/em/if_em.c (revision 162375) @@ -1,3992 +1,3987 @@ /************************************************************************** Copyright (c) 2001-2006, Intel Corporation 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. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the Intel Corporation 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. ***************************************************************************/ /*$FreeBSD$*/ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_device_polling.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /********************************************************************* * Set this to one to display debug statistics *********************************************************************/ int em_display_debug_stats = 0; /********************************************************************* * Driver version *********************************************************************/ char em_driver_version[] = "Version - 6.1.4 - TSO"; /********************************************************************* * PCI Device ID Table * * Used by probe to select devices to load on * Last field stores an index into em_strings * Last entry must be all 0s * * { Vendor ID, Device ID, SubVendor ID, SubDevice ID, String Index } *********************************************************************/ static em_vendor_info_t em_vendor_info_array[] = { /* Intel(R) PRO/1000 Network Connection */ { 0x8086, E1000_DEV_ID_82540EM, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82540EM_LOM, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82540EP, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82540EP_LOM, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82540EP_LP, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82541EI, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82541ER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82541ER_LOM, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82541EI_MOBILE, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82541GI, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82541GI_LF, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82541GI_MOBILE, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82542, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82543GC_FIBER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82543GC_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82544EI_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82544EI_FIBER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82544GC_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82544GC_LOM, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82545EM_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82545EM_FIBER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82545GM_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82545GM_FIBER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82545GM_SERDES, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82546EB_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82546EB_FIBER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82546EB_QUAD_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82546GB_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82546GB_FIBER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82546GB_SERDES, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82546GB_PCIE, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82546GB_QUAD_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82546GB_QUAD_COPPER_KSP3, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82547EI, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82547EI_MOBILE, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82547GI, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82571EB_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82571EB_FIBER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82571EB_SERDES, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82571EB_QUAD_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82572EI_COPPER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82572EI_FIBER, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82572EI_SERDES, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82572EI, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82573E, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82573E_IAMT, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_82573L, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_80003ES2LAN_COPPER_SPT, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_80003ES2LAN_SERDES_SPT, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_80003ES2LAN_COPPER_DPT, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_80003ES2LAN_SERDES_DPT, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_ICH8_IGP_AMT, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_ICH8_IGP_C, PCI_ANY_ID, PCI_ANY_ID, 0}, { 0x8086, E1000_DEV_ID_ICH8_IFE, PCI_ANY_ID, PCI_ANY_ID, 0}, /* required last entry */ { 0, 0, 0, 0, 0} }; /********************************************************************* * Table of branding strings for all supported NICs. *********************************************************************/ static char *em_strings[] = { "Intel(R) PRO/1000 Network Connection" }; /********************************************************************* * Function prototypes *********************************************************************/ static int em_probe(device_t); static int em_attach(device_t); static int em_detach(device_t); static int em_shutdown(device_t); static int em_suspend(device_t); static int em_resume(device_t); static void em_start(struct ifnet *); static void em_start_locked(struct ifnet *ifp); static int em_ioctl(struct ifnet *, u_long, caddr_t); static void em_watchdog(struct ifnet *); static void em_init(void *); static void em_init_locked(struct adapter *); static void em_stop(void *); static void em_media_status(struct ifnet *, struct ifmediareq *); static int em_media_change(struct ifnet *); static void em_identify_hardware(struct adapter *); static int em_allocate_pci_resources(struct adapter *); static int em_allocate_intr(struct adapter *); static void em_free_intr(struct adapter *); static void em_free_pci_resources(struct adapter *); static void em_local_timer(void *); static int em_hardware_init(struct adapter *); static void em_setup_interface(device_t, struct adapter *); static int em_setup_transmit_structures(struct adapter *); static void em_initialize_transmit_unit(struct adapter *); static int em_setup_receive_structures(struct adapter *); static void em_initialize_receive_unit(struct adapter *); static void em_enable_intr(struct adapter *); static void em_disable_intr(struct adapter *); static void em_free_transmit_structures(struct adapter *); static void em_free_receive_structures(struct adapter *); static void em_update_stats_counters(struct adapter *); static void em_txeof(struct adapter *); static int em_allocate_receive_structures(struct adapter *); static int em_allocate_transmit_structures(struct adapter *); static int em_rxeof(struct adapter *, int); #ifndef __NO_STRICT_ALIGNMENT static int em_fixup_rx(struct adapter *); #endif static void em_receive_checksum(struct adapter *, struct em_rx_desc *, struct mbuf *); static void em_transmit_checksum_setup(struct adapter *, struct mbuf *, uint32_t *, uint32_t *); static boolean_t em_tso_setup(struct adapter *, struct mbuf *, uint32_t *, uint32_t *); static void em_set_promisc(struct adapter *); static void em_disable_promisc(struct adapter *); static void em_set_multi(struct adapter *); static void em_print_hw_stats(struct adapter *); static void em_update_link_status(struct adapter *); static int em_get_buf(struct adapter *, int); static void em_enable_vlans(struct adapter *); static void em_disable_vlans(struct adapter *); static int em_encap(struct adapter *, struct mbuf **); static void em_smartspeed(struct adapter *); static int em_82547_fifo_workaround(struct adapter *, int); static void em_82547_update_fifo_head(struct adapter *, int); static int em_82547_tx_fifo_reset(struct adapter *); static void em_82547_move_tail(void *arg); static void em_82547_move_tail_locked(struct adapter *); static int em_dma_malloc(struct adapter *, bus_size_t, struct em_dma_alloc *, int); static void em_dma_free(struct adapter *, struct em_dma_alloc *); static void em_print_debug_info(struct adapter *); static int em_is_valid_ether_addr(uint8_t *); static int em_sysctl_stats(SYSCTL_HANDLER_ARGS); static int em_sysctl_debug_info(SYSCTL_HANDLER_ARGS); static uint32_t em_fill_descriptors (bus_addr_t address, uint32_t length, PDESC_ARRAY desc_array); static int em_sysctl_int_delay(SYSCTL_HANDLER_ARGS); static void em_add_int_delay_sysctl(struct adapter *, const char *, const char *, struct em_int_delay_info *, int, int); /* * Fast interrupt handler and legacy ithread/polling modes are * mutually exclusive. */ #ifdef DEVICE_POLLING static poll_handler_t em_poll; static void em_intr(void *); #else static void em_intr_fast(void *); static void em_add_int_process_limit(struct adapter *, const char *, const char *, int *, int); static void em_handle_rxtx(void *context, int pending); static void em_handle_link(void *context, int pending); #endif /********************************************************************* * FreeBSD Device Interface Entry Points *********************************************************************/ static device_method_t em_methods[] = { /* Device interface */ DEVMETHOD(device_probe, em_probe), DEVMETHOD(device_attach, em_attach), DEVMETHOD(device_detach, em_detach), DEVMETHOD(device_shutdown, em_shutdown), DEVMETHOD(device_suspend, em_suspend), DEVMETHOD(device_resume, em_resume), {0, 0} }; static driver_t em_driver = { "em", em_methods, sizeof(struct adapter), }; static devclass_t em_devclass; DRIVER_MODULE(em, pci, em_driver, em_devclass, 0, 0); MODULE_DEPEND(em, pci, 1, 1, 1); MODULE_DEPEND(em, ether, 1, 1, 1); /********************************************************************* * Tunable default values. *********************************************************************/ #define E1000_TICKS_TO_USECS(ticks) ((1024 * (ticks) + 500) / 1000) #define E1000_USECS_TO_TICKS(usecs) ((1000 * (usecs) + 512) / 1024) #define M_TSO_LEN 66 static int em_tx_int_delay_dflt = E1000_TICKS_TO_USECS(EM_TIDV); static int em_rx_int_delay_dflt = E1000_TICKS_TO_USECS(EM_RDTR); static int em_tx_abs_int_delay_dflt = E1000_TICKS_TO_USECS(EM_TADV); static int em_rx_abs_int_delay_dflt = E1000_TICKS_TO_USECS(EM_RADV); static int em_rxd = EM_DEFAULT_RXD; static int em_txd = EM_DEFAULT_TXD; static int em_smart_pwr_down = FALSE; TUNABLE_INT("hw.em.tx_int_delay", &em_tx_int_delay_dflt); TUNABLE_INT("hw.em.rx_int_delay", &em_rx_int_delay_dflt); TUNABLE_INT("hw.em.tx_abs_int_delay", &em_tx_abs_int_delay_dflt); TUNABLE_INT("hw.em.rx_abs_int_delay", &em_rx_abs_int_delay_dflt); TUNABLE_INT("hw.em.rxd", &em_rxd); TUNABLE_INT("hw.em.txd", &em_txd); TUNABLE_INT("hw.em.smart_pwr_down", &em_smart_pwr_down); #ifndef DEVICE_POLLING static int em_rx_process_limit = 100; TUNABLE_INT("hw.em.rx_process_limit", &em_rx_process_limit); #endif /********************************************************************* * Device identification routine * * em_probe determines if the driver should be loaded on * adapter based on PCI vendor/device id of the adapter. * * return BUS_PROBE_DEFAULT on success, positive on failure *********************************************************************/ static int em_probe(device_t dev) { char adapter_name[60]; uint16_t pci_vendor_id = 0; uint16_t pci_device_id = 0; uint16_t pci_subvendor_id = 0; uint16_t pci_subdevice_id = 0; em_vendor_info_t *ent; INIT_DEBUGOUT("em_probe: begin"); pci_vendor_id = pci_get_vendor(dev); if (pci_vendor_id != EM_VENDOR_ID) return (ENXIO); pci_device_id = pci_get_device(dev); pci_subvendor_id = pci_get_subvendor(dev); pci_subdevice_id = pci_get_subdevice(dev); ent = em_vendor_info_array; while (ent->vendor_id != 0) { if ((pci_vendor_id == ent->vendor_id) && (pci_device_id == ent->device_id) && ((pci_subvendor_id == ent->subvendor_id) || (ent->subvendor_id == PCI_ANY_ID)) && ((pci_subdevice_id == ent->subdevice_id) || (ent->subdevice_id == PCI_ANY_ID))) { sprintf(adapter_name, "%s %s", em_strings[ent->index], em_driver_version); device_set_desc_copy(dev, adapter_name); return (BUS_PROBE_DEFAULT); } ent++; } return (ENXIO); } /********************************************************************* * Device initialization routine * * The attach entry point is called when the driver is being loaded. * This routine identifies the type of hardware, allocates all resources * and initializes the hardware. * * return 0 on success, positive on failure *********************************************************************/ static int em_attach(device_t dev) { struct adapter *adapter; int tsize, rsize; int error = 0; INIT_DEBUGOUT("em_attach: begin"); adapter = device_get_softc(dev); adapter->dev = adapter->osdep.dev = dev; EM_LOCK_INIT(adapter, device_get_nameunit(dev)); /* SYSCTL stuff */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "debug_info", CTLTYPE_INT|CTLFLAG_RW, adapter, 0, em_sysctl_debug_info, "I", "Debug Information"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "stats", CTLTYPE_INT|CTLFLAG_RW, adapter, 0, em_sysctl_stats, "I", "Statistics"); callout_init(&adapter->timer, CALLOUT_MPSAFE); callout_init(&adapter->tx_fifo_timer, CALLOUT_MPSAFE); /* Determine hardware revision */ em_identify_hardware(adapter); /* Set up some sysctls for the tunable interrupt delays */ em_add_int_delay_sysctl(adapter, "rx_int_delay", "receive interrupt delay in usecs", &adapter->rx_int_delay, E1000_REG_OFFSET(&adapter->hw, RDTR), em_rx_int_delay_dflt); em_add_int_delay_sysctl(adapter, "tx_int_delay", "transmit interrupt delay in usecs", &adapter->tx_int_delay, E1000_REG_OFFSET(&adapter->hw, TIDV), em_tx_int_delay_dflt); if (adapter->hw.mac_type >= em_82540) { em_add_int_delay_sysctl(adapter, "rx_abs_int_delay", "receive interrupt delay limit in usecs", &adapter->rx_abs_int_delay, E1000_REG_OFFSET(&adapter->hw, RADV), em_rx_abs_int_delay_dflt); em_add_int_delay_sysctl(adapter, "tx_abs_int_delay", "transmit interrupt delay limit in usecs", &adapter->tx_abs_int_delay, E1000_REG_OFFSET(&adapter->hw, TADV), em_tx_abs_int_delay_dflt); } #ifndef DEVICE_POLLING /* Sysctls for limiting the amount of work done in the taskqueue */ em_add_int_process_limit(adapter, "rx_processing_limit", "max number of rx packets to process", &adapter->rx_process_limit, em_rx_process_limit); #endif /* * Validate number of transmit and receive descriptors. It * must not exceed hardware maximum, and must be multiple * of EM_DBA_ALIGN. */ if (((em_txd * sizeof(struct em_tx_desc)) % EM_DBA_ALIGN) != 0 || (adapter->hw.mac_type >= em_82544 && em_txd > EM_MAX_TXD) || (adapter->hw.mac_type < em_82544 && em_txd > EM_MAX_TXD_82543) || (em_txd < EM_MIN_TXD)) { device_printf(dev, "Using %d TX descriptors instead of %d!\n", EM_DEFAULT_TXD, em_txd); adapter->num_tx_desc = EM_DEFAULT_TXD; } else adapter->num_tx_desc = em_txd; if (((em_rxd * sizeof(struct em_rx_desc)) % EM_DBA_ALIGN) != 0 || (adapter->hw.mac_type >= em_82544 && em_rxd > EM_MAX_RXD) || (adapter->hw.mac_type < em_82544 && em_rxd > EM_MAX_RXD_82543) || (em_rxd < EM_MIN_RXD)) { device_printf(dev, "Using %d RX descriptors instead of %d!\n", EM_DEFAULT_RXD, em_rxd); adapter->num_rx_desc = EM_DEFAULT_RXD; } else adapter->num_rx_desc = em_rxd; adapter->hw.autoneg = DO_AUTO_NEG; adapter->hw.wait_autoneg_complete = WAIT_FOR_AUTO_NEG_DEFAULT; adapter->hw.autoneg_advertised = AUTONEG_ADV_DEFAULT; adapter->hw.tbi_compatibility_en = TRUE; adapter->rx_buffer_len = EM_RXBUFFER_2048; adapter->hw.phy_init_script = 1; adapter->hw.phy_reset_disable = FALSE; #ifndef EM_MASTER_SLAVE adapter->hw.master_slave = em_ms_hw_default; #else adapter->hw.master_slave = EM_MASTER_SLAVE; #endif /* * Set the max frame size assuming standard ethernet * sized frames. */ adapter->hw.max_frame_size = ETHERMTU + ETHER_HDR_LEN + ETHER_CRC_LEN; adapter->hw.min_frame_size = MINIMUM_ETHERNET_PACKET_SIZE + ETHER_CRC_LEN; /* * This controls when hardware reports transmit completion * status. */ adapter->hw.report_tx_early = 1; if (em_allocate_pci_resources(adapter)) { device_printf(dev, "Allocation of PCI resources failed\n"); error = ENXIO; goto err_pci; } /* Initialize eeprom parameters */ em_init_eeprom_params(&adapter->hw); tsize = roundup2(adapter->num_tx_desc * sizeof(struct em_tx_desc), EM_DBA_ALIGN); /* Allocate Transmit Descriptor ring */ if (em_dma_malloc(adapter, tsize, &adapter->txdma, BUS_DMA_NOWAIT)) { device_printf(dev, "Unable to allocate tx_desc memory\n"); error = ENOMEM; goto err_tx_desc; } adapter->tx_desc_base = (struct em_tx_desc *)adapter->txdma.dma_vaddr; rsize = roundup2(adapter->num_rx_desc * sizeof(struct em_rx_desc), EM_DBA_ALIGN); /* Allocate Receive Descriptor ring */ if (em_dma_malloc(adapter, rsize, &adapter->rxdma, BUS_DMA_NOWAIT)) { device_printf(dev, "Unable to allocate rx_desc memory\n"); error = ENOMEM; goto err_rx_desc; } adapter->rx_desc_base = (struct em_rx_desc *)adapter->rxdma.dma_vaddr; /* Initialize the hardware */ if (em_hardware_init(adapter)) { device_printf(dev, "Unable to initialize the hardware\n"); error = EIO; goto err_hw_init; } /* Copy the permanent MAC address out of the EEPROM */ if (em_read_mac_addr(&adapter->hw) < 0) { device_printf(dev, "EEPROM read error while reading MAC" " address\n"); error = EIO; goto err_hw_init; } if (!em_is_valid_ether_addr(adapter->hw.mac_addr)) { device_printf(dev, "Invalid MAC address\n"); error = EIO; goto err_hw_init; } /* Setup OS specific network interface */ em_setup_interface(dev, adapter); em_allocate_intr(adapter); /* Initialize statistics */ em_clear_hw_cntrs(&adapter->hw); em_update_stats_counters(adapter); adapter->hw.get_link_status = 1; em_update_link_status(adapter); /* Indicate SOL/IDER usage */ if (em_check_phy_reset_block(&adapter->hw)) device_printf(dev, "PHY reset is blocked due to SOL/IDER session.\n"); /* Identify 82544 on PCIX */ em_get_bus_info(&adapter->hw); if(adapter->hw.bus_type == em_bus_type_pcix && adapter->hw.mac_type == em_82544) adapter->pcix_82544 = TRUE; else adapter->pcix_82544 = FALSE; INIT_DEBUGOUT("em_attach: end"); return (0); err_hw_init: em_dma_free(adapter, &adapter->rxdma); err_rx_desc: em_dma_free(adapter, &adapter->txdma); err_tx_desc: err_pci: em_free_intr(adapter); em_free_pci_resources(adapter); EM_LOCK_DESTROY(adapter); return (error); } /********************************************************************* * Device removal routine * * The detach entry point is called when the driver is being removed. * This routine stops the adapter and deallocates all the resources * that were allocated for driver operation. * * return 0 on success, positive on failure *********************************************************************/ static int em_detach(device_t dev) { struct adapter *adapter = device_get_softc(dev); struct ifnet *ifp = adapter->ifp; INIT_DEBUGOUT("em_detach: begin"); #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) ether_poll_deregister(ifp); #endif em_free_intr(adapter); EM_LOCK(adapter); adapter->in_detach = 1; em_stop(adapter); em_phy_hw_reset(&adapter->hw); EM_UNLOCK(adapter); ether_ifdetach(adapter->ifp); em_free_pci_resources(adapter); bus_generic_detach(dev); if_free(ifp); /* Free Transmit Descriptor ring */ if (adapter->tx_desc_base) { em_dma_free(adapter, &adapter->txdma); adapter->tx_desc_base = NULL; } /* Free Receive Descriptor ring */ if (adapter->rx_desc_base) { em_dma_free(adapter, &adapter->rxdma); adapter->rx_desc_base = NULL; } EM_LOCK_DESTROY(adapter); return (0); } /********************************************************************* * * Shutdown entry point * **********************************************************************/ static int em_shutdown(device_t dev) { struct adapter *adapter = device_get_softc(dev); EM_LOCK(adapter); em_stop(adapter); EM_UNLOCK(adapter); return (0); } /* * Suspend/resume device methods. */ static int em_suspend(device_t dev) { struct adapter *adapter = device_get_softc(dev); EM_LOCK(adapter); em_stop(adapter); EM_UNLOCK(adapter); return bus_generic_suspend(dev); } static int em_resume(device_t dev) { struct adapter *adapter = device_get_softc(dev); struct ifnet *ifp = adapter->ifp; EM_LOCK(adapter); em_init_locked(adapter); if ((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING)) em_start_locked(ifp); EM_UNLOCK(adapter); return bus_generic_resume(dev); } /********************************************************************* * Transmit entry point * * em_start is called by the stack to initiate a transmit. * The driver will remain in this routine as long as there are * packets to transmit and transmit resources are available. * In case resources are not available stack is notified and * the packet is requeued. **********************************************************************/ static void em_start_locked(struct ifnet *ifp) { struct adapter *adapter = ifp->if_softc; struct mbuf *m_head; EM_LOCK_ASSERT(adapter); if ((ifp->if_drv_flags & (IFF_DRV_RUNNING|IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) return; if (!adapter->link_active) return; while (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * em_encap() can modify our pointer, and or make it NULL on * failure. In that event, we can't requeue. */ if (em_encap(adapter, &m_head)) { if (m_head == NULL) break; ifp->if_drv_flags |= IFF_DRV_OACTIVE; IFQ_DRV_PREPEND(&ifp->if_snd, m_head); break; } /* Send a copy of the frame to the BPF listener */ BPF_MTAP(ifp, m_head); /* Set timeout in case hardware has problems transmitting. */ ifp->if_timer = EM_TX_TIMEOUT; } } static void em_start(struct ifnet *ifp) { struct adapter *adapter = ifp->if_softc; EM_LOCK(adapter); if (ifp->if_drv_flags & IFF_DRV_RUNNING) em_start_locked(ifp); EM_UNLOCK(adapter); } /********************************************************************* * Ioctl entry point * * em_ioctl is called when the user wants to configure the * interface. * * return 0 on success, positive on failure **********************************************************************/ static int em_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct adapter *adapter = ifp->if_softc; struct ifreq *ifr = (struct ifreq *)data; struct ifaddr *ifa = (struct ifaddr *)data; int error = 0; if (adapter->in_detach) return (error); switch (command) { case SIOCSIFADDR: case SIOCGIFADDR: if (ifa->ifa_addr->sa_family == AF_INET) { /* * XXX * Since resetting hardware takes a very long time * and results in link renegotiation we only * initialize the hardware only when it is absolutely * required. */ ifp->if_flags |= IFF_UP; if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { EM_LOCK(adapter); em_init_locked(adapter); EM_UNLOCK(adapter); } arp_ifinit(ifp, ifa); } else error = ether_ioctl(ifp, command, data); break; case SIOCSIFMTU: { int max_frame_size; uint16_t eeprom_data = 0; IOCTL_DEBUGOUT("ioctl rcv'd: SIOCSIFMTU (Set Interface MTU)"); EM_LOCK(adapter); switch (adapter->hw.mac_type) { case em_82573: /* * 82573 only supports jumbo frames * if ASPM is disabled. */ em_read_eeprom(&adapter->hw, EEPROM_INIT_3GIO_3, 1, &eeprom_data); if (eeprom_data & EEPROM_WORD1A_ASPM_MASK) { max_frame_size = ETHER_MAX_LEN; break; } /* Allow Jumbo frames - fall thru */ case em_82571: case em_82572: case em_80003es2lan: /* Limit Jumbo Frame size */ max_frame_size = 9234; break; case em_ich8lan: /* ICH8 does not support jumbo frames */ max_frame_size = ETHER_MAX_LEN; break; default: max_frame_size = MAX_JUMBO_FRAME_SIZE; } if (ifr->ifr_mtu > max_frame_size - ETHER_HDR_LEN - ETHER_CRC_LEN) { EM_UNLOCK(adapter); error = EINVAL; break; } ifp->if_mtu = ifr->ifr_mtu; adapter->hw.max_frame_size = ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN; em_init_locked(adapter); EM_UNLOCK(adapter); break; } case SIOCSIFFLAGS: IOCTL_DEBUGOUT("ioctl rcv'd: SIOCSIFFLAGS (Set Interface Flags)"); EM_LOCK(adapter); if (ifp->if_flags & IFF_UP) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING)) { if ((ifp->if_flags ^ adapter->if_flags) & IFF_PROMISC) { em_disable_promisc(adapter); em_set_promisc(adapter); } } else em_init_locked(adapter); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { em_stop(adapter); } } adapter->if_flags = ifp->if_flags; EM_UNLOCK(adapter); break; case SIOCADDMULTI: case SIOCDELMULTI: IOCTL_DEBUGOUT("ioctl rcv'd: SIOC(ADD|DEL)MULTI"); if (ifp->if_drv_flags & IFF_DRV_RUNNING) { EM_LOCK(adapter); em_disable_intr(adapter); em_set_multi(adapter); if (adapter->hw.mac_type == em_82542_rev2_0) { em_initialize_receive_unit(adapter); } #ifdef DEVICE_POLLING if (!(ifp->if_capenable & IFCAP_POLLING)) #endif em_enable_intr(adapter); EM_UNLOCK(adapter); } break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: IOCTL_DEBUGOUT("ioctl rcv'd: SIOCxIFMEDIA (Get/Set Interface Media)"); error = ifmedia_ioctl(ifp, ifr, &adapter->media, command); break; case SIOCSIFCAP: { int mask, reinit; IOCTL_DEBUGOUT("ioctl rcv'd: SIOCSIFCAP (Set Capabilities)"); reinit = 0; mask = ifr->ifr_reqcap ^ ifp->if_capenable; #ifdef DEVICE_POLLING if (mask & IFCAP_POLLING) { if (ifr->ifr_reqcap & IFCAP_POLLING) { error = ether_poll_register(em_poll, ifp); if (error) return (error); EM_LOCK(adapter); em_disable_intr(adapter); ifp->if_capenable |= IFCAP_POLLING; EM_UNLOCK(adapter); } else { error = ether_poll_deregister(ifp); /* Enable interrupt even in error case */ EM_LOCK(adapter); em_enable_intr(adapter); ifp->if_capenable &= ~IFCAP_POLLING; EM_UNLOCK(adapter); } } #endif if (mask & IFCAP_HWCSUM) { ifp->if_capenable ^= IFCAP_HWCSUM; reinit = 1; } if (mask & IFCAP_TSO) { ifp->if_capenable ^= IFCAP_TSO; reinit = 1; } if (mask & IFCAP_VLAN_HWTAGGING) { ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING; reinit = 1; } if (reinit && (ifp->if_drv_flags & IFF_DRV_RUNNING)) em_init(adapter); VLAN_CAPABILITIES(ifp); break; } default: error = ether_ioctl(ifp, command, data); break; } return (error); } /********************************************************************* * Watchdog entry point * * This routine is called whenever hardware quits transmitting. * **********************************************************************/ static void em_watchdog(struct ifnet *ifp) { struct adapter *adapter = ifp->if_softc; EM_LOCK(adapter); /* If we are in this routine because of pause frames, then * don't reset the hardware. */ if (E1000_READ_REG(&adapter->hw, STATUS) & E1000_STATUS_TXOFF) { ifp->if_timer = EM_TX_TIMEOUT; EM_UNLOCK(adapter); return; } /* * Reclaim first as there is a possibility of losing Tx completion * interrupts. Possible cause of missing Tx completion interrupts * comes from Tx interrupt moderation mechanism(delayed interrupts) * or chipset bug. */ em_txeof(adapter); if (adapter->num_tx_desc_avail == adapter->num_tx_desc) { EM_UNLOCK(adapter); return; } if (em_check_for_link(&adapter->hw) == 0) device_printf(adapter->dev, "watchdog timeout -- resetting\n"); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; adapter->watchdog_events++; em_init_locked(adapter); EM_UNLOCK(adapter); } /********************************************************************* * Init entry point * * This routine is used in two ways. It is used by the stack as * init entry point in network interface structure. It is also used * by the driver as a hw/sw initialization routine to get to a * consistent state. * * return 0 on success, positive on failure **********************************************************************/ static void em_init_locked(struct adapter *adapter) { struct ifnet *ifp = adapter->ifp; device_t dev = adapter->dev; uint32_t pba; INIT_DEBUGOUT("em_init: begin"); EM_LOCK_ASSERT(adapter); em_stop(adapter); /* * Packet Buffer Allocation (PBA) * Writing PBA sets the receive portion of the buffer * the remainder is used for the transmit buffer. * * Devices before the 82547 had a Packet Buffer of 64K. * Default allocation: PBA=48K for Rx, leaving 16K for Tx. * After the 82547 the buffer was reduced to 40K. * Default allocation: PBA=30K for Rx, leaving 10K for Tx. * Note: default does not leave enough room for Jumbo Frame >10k. */ switch (adapter->hw.mac_type) { case em_82547: case em_82547_rev_2: /* 82547: Total Packet Buffer is 40K */ if (adapter->hw.max_frame_size > EM_RXBUFFER_8192) pba = E1000_PBA_22K; /* 22K for Rx, 18K for Tx */ else pba = E1000_PBA_30K; /* 30K for Rx, 10K for Tx */ adapter->tx_fifo_head = 0; adapter->tx_head_addr = pba << EM_TX_HEAD_ADDR_SHIFT; adapter->tx_fifo_size = (E1000_PBA_40K - pba) << EM_PBA_BYTES_SHIFT; break; case em_80003es2lan: /* 80003es2lan: Total Packet Buffer is 48K */ case em_82571: /* 82571: Total Packet Buffer is 48K */ case em_82572: /* 82572: Total Packet Buffer is 48K */ pba = E1000_PBA_32K; /* 32K for Rx, 16K for Tx */ break; case em_82573: /* 82573: Total Packet Buffer is 32K */ /* Jumbo frames not supported */ pba = E1000_PBA_12K; /* 12K for Rx, 20K for Tx */ break; case em_ich8lan: pba = E1000_PBA_8K; break; default: /* Devices before 82547 had a Packet Buffer of 64K. */ if(adapter->hw.max_frame_size > EM_RXBUFFER_8192) pba = E1000_PBA_40K; /* 40K for Rx, 24K for Tx */ else pba = E1000_PBA_48K; /* 48K for Rx, 16K for Tx */ } INIT_DEBUGOUT1("em_init: pba=%dK",pba); E1000_WRITE_REG(&adapter->hw, PBA, pba); /* Get the latest mac address, User can use a LAA */ bcopy(IF_LLADDR(adapter->ifp), adapter->hw.mac_addr, ETHER_ADDR_LEN); /* Initialize the hardware */ if (em_hardware_init(adapter)) { device_printf(dev, "Unable to initialize the hardware\n"); return; } em_update_link_status(adapter); if (ifp->if_capenable & IFCAP_VLAN_HWTAGGING) em_enable_vlans(adapter); /* Prepare transmit descriptors and buffers */ if (em_setup_transmit_structures(adapter)) { device_printf(dev, "Could not setup transmit structures\n"); em_stop(adapter); return; } em_initialize_transmit_unit(adapter); /* Setup Multicast table */ em_set_multi(adapter); /* Prepare receive descriptors and buffers */ if (em_setup_receive_structures(adapter)) { device_printf(dev, "Could not setup receive structures\n"); em_stop(adapter); return; } em_initialize_receive_unit(adapter); /* Don't lose promiscuous settings */ em_set_promisc(adapter); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; ifp->if_hwassist = 0; if (adapter->hw.mac_type >= em_82543) { if (ifp->if_capenable & IFCAP_TXCSUM) ifp->if_hwassist = EM_CHECKSUM_FEATURES; if (ifp->if_capenable & IFCAP_TSO) ifp->if_hwassist |= EM_TCPSEG_FEATURES; } callout_reset(&adapter->timer, hz, em_local_timer, adapter); em_clear_hw_cntrs(&adapter->hw); #ifdef DEVICE_POLLING /* * Only enable interrupts if we are not polling, make sure * they are off otherwise. */ if (ifp->if_capenable & IFCAP_POLLING) em_disable_intr(adapter); else #endif /* DEVICE_POLLING */ em_enable_intr(adapter); /* Don't reset the phy next time init gets called */ adapter->hw.phy_reset_disable = TRUE; } static void em_init(void *arg) { struct adapter *adapter = arg; EM_LOCK(adapter); em_init_locked(adapter); EM_UNLOCK(adapter); } #ifdef DEVICE_POLLING /********************************************************************* * * Legacy polling routine * *********************************************************************/ static void em_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct adapter *adapter = ifp->if_softc; uint32_t reg_icr; EM_LOCK(adapter); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { EM_UNLOCK(adapter); return; } if (cmd == POLL_AND_CHECK_STATUS) { reg_icr = E1000_READ_REG(&adapter->hw, ICR); if (reg_icr & (E1000_ICR_RXSEQ | E1000_ICR_LSC)) { callout_stop(&adapter->timer); adapter->hw.get_link_status = 1; em_check_for_link(&adapter->hw); em_update_link_status(adapter); callout_reset(&adapter->timer, hz, em_local_timer, adapter); } } em_rxeof(adapter, count); em_txeof(adapter); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) em_start_locked(ifp); EM_UNLOCK(adapter); } /********************************************************************* * * Legacy Interrupt Service routine * *********************************************************************/ static void em_intr(void *arg) { struct adapter *adapter = arg; struct ifnet *ifp; uint32_t reg_icr; EM_LOCK(adapter); ifp = adapter->ifp; if (ifp->if_capenable & IFCAP_POLLING) { EM_UNLOCK(adapter); return; } for (;;) { reg_icr = E1000_READ_REG(&adapter->hw, ICR); if (adapter->hw.mac_type >= em_82571 && (reg_icr & E1000_ICR_INT_ASSERTED) == 0) break; else if (reg_icr == 0) break; /* * XXX: some laptops trigger several spurious interrupts * on em(4) when in the resume cycle. The ICR register * reports all-ones value in this case. Processing such * interrupts would lead to a freeze. I don't know why. */ if (reg_icr == 0xffffffff) break; if (ifp->if_drv_flags & IFF_DRV_RUNNING) { em_rxeof(adapter, -1); em_txeof(adapter); } /* Link status change */ if (reg_icr & (E1000_ICR_RXSEQ | E1000_ICR_LSC)) { callout_stop(&adapter->timer); adapter->hw.get_link_status = 1; em_check_for_link(&adapter->hw); em_update_link_status(adapter); callout_reset(&adapter->timer, hz, em_local_timer, adapter); } if (reg_icr & E1000_ICR_RXO) adapter->rx_overruns++; } if (ifp->if_drv_flags & IFF_DRV_RUNNING && !IFQ_DRV_IS_EMPTY(&ifp->if_snd)) em_start_locked(ifp); EM_UNLOCK(adapter); } #else /* if not DEVICE_POLLING, then fast interrupt routines only */ static void em_handle_link(void *context, int pending) { struct adapter *adapter = context; struct ifnet *ifp; ifp = adapter->ifp; EM_LOCK(adapter); callout_stop(&adapter->timer); adapter->hw.get_link_status = 1; em_check_for_link(&adapter->hw); em_update_link_status(adapter); callout_reset(&adapter->timer, hz, em_local_timer, adapter); EM_UNLOCK(adapter); } static void em_handle_rxtx(void *context, int pending) { struct adapter *adapter = context; struct ifnet *ifp; NET_LOCK_GIANT(); ifp = adapter->ifp; /* * TODO: * It should be possible to run the tx clean loop without the lock. */ if (ifp->if_drv_flags & IFF_DRV_RUNNING) { if (em_rxeof(adapter, adapter->rx_process_limit) != 0) taskqueue_enqueue(adapter->tq, &adapter->rxtx_task); EM_LOCK(adapter); em_txeof(adapter); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) em_start_locked(ifp); EM_UNLOCK(adapter); } em_enable_intr(adapter); NET_UNLOCK_GIANT(); } /********************************************************************* * * Fast Interrupt Service routine * *********************************************************************/ static void em_intr_fast(void *arg) { struct adapter *adapter = arg; struct ifnet *ifp; uint32_t reg_icr; ifp = adapter->ifp; reg_icr = E1000_READ_REG(&adapter->hw, ICR); /* Hot eject? */ if (reg_icr == 0xffffffff) return; /* Definitely not our interrupt. */ if (reg_icr == 0x0) return; /* * Starting with the 82571 chip, bit 31 should be used to * determine whether the interrupt belongs to us. */ if (adapter->hw.mac_type >= em_82571 && (reg_icr & E1000_ICR_INT_ASSERTED) == 0) return; /* * Mask interrupts until the taskqueue is finished running. This is * cheap, just assume that it is needed. This also works around the * MSI message reordering errata on certain systems. */ em_disable_intr(adapter); taskqueue_enqueue(adapter->tq, &adapter->rxtx_task); /* Link status change */ if (reg_icr & (E1000_ICR_RXSEQ | E1000_ICR_LSC)) taskqueue_enqueue(taskqueue_fast, &adapter->link_task); if (reg_icr & E1000_ICR_RXO) adapter->rx_overruns++; } #endif /* ! DEVICE_POLLING */ /********************************************************************* * * Media Ioctl callback * * This routine is called whenever the user queries the status of * the interface using ifconfig. * **********************************************************************/ static void em_media_status(struct ifnet *ifp, struct ifmediareq *ifmr) { struct adapter *adapter = ifp->if_softc; INIT_DEBUGOUT("em_media_status: begin"); EM_LOCK(adapter); em_check_for_link(&adapter->hw); em_update_link_status(adapter); ifmr->ifm_status = IFM_AVALID; ifmr->ifm_active = IFM_ETHER; if (!adapter->link_active) { EM_UNLOCK(adapter); return; } ifmr->ifm_status |= IFM_ACTIVE; if ((adapter->hw.media_type == em_media_type_fiber) || (adapter->hw.media_type == em_media_type_internal_serdes)) { if (adapter->hw.mac_type == em_82545) ifmr->ifm_active |= IFM_1000_LX | IFM_FDX; else ifmr->ifm_active |= IFM_1000_SX | IFM_FDX; } else { switch (adapter->link_speed) { case 10: ifmr->ifm_active |= IFM_10_T; break; case 100: ifmr->ifm_active |= IFM_100_TX; break; case 1000: ifmr->ifm_active |= IFM_1000_T; break; } if (adapter->link_duplex == FULL_DUPLEX) ifmr->ifm_active |= IFM_FDX; else ifmr->ifm_active |= IFM_HDX; } EM_UNLOCK(adapter); } /********************************************************************* * * Media Ioctl callback * * This routine is called when the user changes speed/duplex using * media/mediopt option with ifconfig. * **********************************************************************/ static int em_media_change(struct ifnet *ifp) { struct adapter *adapter = ifp->if_softc; struct ifmedia *ifm = &adapter->media; INIT_DEBUGOUT("em_media_change: begin"); if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER) return (EINVAL); EM_LOCK(adapter); switch (IFM_SUBTYPE(ifm->ifm_media)) { case IFM_AUTO: adapter->hw.autoneg = DO_AUTO_NEG; adapter->hw.autoneg_advertised = AUTONEG_ADV_DEFAULT; break; case IFM_1000_LX: case IFM_1000_SX: case IFM_1000_T: adapter->hw.autoneg = DO_AUTO_NEG; adapter->hw.autoneg_advertised = ADVERTISE_1000_FULL; break; case IFM_100_TX: adapter->hw.autoneg = FALSE; adapter->hw.autoneg_advertised = 0; if ((ifm->ifm_media & IFM_GMASK) == IFM_FDX) adapter->hw.forced_speed_duplex = em_100_full; else adapter->hw.forced_speed_duplex = em_100_half; break; case IFM_10_T: adapter->hw.autoneg = FALSE; adapter->hw.autoneg_advertised = 0; if ((ifm->ifm_media & IFM_GMASK) == IFM_FDX) adapter->hw.forced_speed_duplex = em_10_full; else adapter->hw.forced_speed_duplex = em_10_half; break; default: device_printf(adapter->dev, "Unsupported media type\n"); } /* As the speed/duplex settings my have changed we need to * reset the PHY. */ adapter->hw.phy_reset_disable = FALSE; em_init_locked(adapter); EM_UNLOCK(adapter); return (0); } /********************************************************************* * * This routine maps the mbufs to tx descriptors. * * return 0 on success, positive on failure **********************************************************************/ static int em_encap(struct adapter *adapter, struct mbuf **m_headp) { struct ifnet *ifp = adapter->ifp; bus_dma_segment_t segs[EM_MAX_SCATTER]; bus_dmamap_t map; struct em_buffer *tx_buffer, *tx_buffer_last; struct em_tx_desc *current_tx_desc; struct mbuf *m_head; - struct m_tag *mtag; uint32_t txd_upper, txd_lower, txd_used, txd_saved; int nsegs, i, j; int error, do_tso, tso_desc = 0; m_head = *m_headp; current_tx_desc = NULL; txd_upper = txd_lower = txd_used = txd_saved = 0; do_tso = ((m_head->m_pkthdr.csum_flags & CSUM_TSO) != 0); /* * Force a cleanup if number of TX descriptors * available hits the threshold. */ if (adapter->num_tx_desc_avail <= EM_TX_CLEANUP_THRESHOLD) { em_txeof(adapter); if (adapter->num_tx_desc_avail <= EM_TX_CLEANUP_THRESHOLD) { adapter->no_tx_desc_avail1++; return (ENOBUFS); } } - /* Find out if we are in vlan mode. */ - mtag = VLAN_OUTPUT_TAG(ifp, m_head); - /* * When operating in promiscuous mode, hardware encapsulation for * packets is disabled. This means we have to add the vlan * encapsulation in the driver, since it will have come down from the * VLAN layer with a tag instead of a VLAN header. */ - if (mtag != NULL && adapter->em_insert_vlan_header) { + if ((m_head->m_flags & M_VLANTAG) && adapter->em_insert_vlan_header) { struct ether_vlan_header *evl; struct ether_header eh; m_head = m_pullup(m_head, sizeof(eh)); if (m_head == NULL) { *m_headp = NULL; return (ENOBUFS); } eh = *mtod(m_head, struct ether_header *); M_PREPEND(m_head, sizeof(*evl), M_DONTWAIT); if (m_head == NULL) { *m_headp = NULL; return (ENOBUFS); } m_head = m_pullup(m_head, sizeof(*evl)); if (m_head == NULL) { *m_headp = NULL; return (ENOBUFS); } evl = mtod(m_head, struct ether_vlan_header *); bcopy(&eh, evl, sizeof(*evl)); evl->evl_proto = evl->evl_encap_proto; evl->evl_encap_proto = htons(ETHERTYPE_VLAN); - evl->evl_tag = htons(VLAN_TAG_VALUE(mtag)); - m_tag_delete(m_head, mtag); - mtag = NULL; + evl->evl_tag = htons(m_head->m_pkthdr.ether_vtag); *m_headp = m_head; } /* * TSO workaround: * If an mbuf is only header we need * to pull 4 bytes of data into it. */ if (do_tso && (m_head->m_len <= M_TSO_LEN)) { m_head = m_pullup(m_head, M_TSO_LEN + 4); *m_headp = m_head; if (m_head == NULL) { return (ENOBUFS); } } /* * Map the packet for DMA. */ tx_buffer = &adapter->tx_buffer_area[adapter->next_avail_tx_desc]; tx_buffer_last = tx_buffer; map = tx_buffer->map; error = bus_dmamap_load_mbuf_sg(adapter->txtag, map, *m_headp, segs, &nsegs, BUS_DMA_NOWAIT); if (error == EFBIG) { struct mbuf *m; m = m_defrag(*m_headp, M_DONTWAIT); if (m == NULL) { /* Assume m_defrag(9) used only m_get(9). */ adapter->mbuf_alloc_failed++; m_freem(*m_headp); *m_headp = NULL; return (ENOBUFS); } *m_headp = m; error = bus_dmamap_load_mbuf_sg(adapter->txtag, map, *m_headp, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { adapter->no_tx_dma_setup++; m_freem(*m_headp); *m_headp = NULL; return (error); } } else if (error != 0) { adapter->no_tx_dma_setup++; return (error); } if (nsegs == 0) { m_freem(*m_headp); *m_headp = NULL; return (EIO); } /* * TSO Hardware workaround, if this packet is not * TSO, and is only a single descriptor long, and * it follows a TSO burst, then we need to add a * sentinel descriptor to prevent premature writeback. */ if ((do_tso == 0) && (adapter->tx_tso == TRUE)) { if (nsegs == 1) tso_desc = TRUE; adapter->tx_tso = FALSE; } if (nsegs > adapter->num_tx_desc_avail - 2) { adapter->no_tx_desc_avail2++; bus_dmamap_unload(adapter->txtag, map); return (ENOBUFS); } /* Do hardware assists */ m_head = *m_headp; if (ifp->if_hwassist > 0) { if (em_tso_setup(adapter, m_head, &txd_upper, &txd_lower)) { /* we need to make a final sentinel transmit desc */ tso_desc = TRUE; } else em_transmit_checksum_setup(adapter, m_head, &txd_upper, &txd_lower); } i = adapter->next_avail_tx_desc; if (adapter->pcix_82544) txd_saved = i; for (j = 0; j < nsegs; j++) { bus_size_t seg_len; bus_addr_t seg_addr; /* If adapter is 82544 and on PCIX bus. */ if(adapter->pcix_82544) { DESC_ARRAY desc_array; uint32_t array_elements, counter; /* * Check the Address and Length combination and * split the data accordingly */ array_elements = em_fill_descriptors(segs[j].ds_addr, segs[j].ds_len, &desc_array); for (counter = 0; counter < array_elements; counter++) { if (txd_used == adapter->num_tx_desc_avail) { adapter->next_avail_tx_desc = txd_saved; adapter->no_tx_desc_avail2++; bus_dmamap_unload(adapter->txtag, map); return (ENOBUFS); } tx_buffer = &adapter->tx_buffer_area[i]; current_tx_desc = &adapter->tx_desc_base[i]; current_tx_desc->buffer_addr = htole64( desc_array.descriptor[counter].address); current_tx_desc->lower.data = htole32( (adapter->txd_cmd | txd_lower | (uint16_t)desc_array.descriptor[counter].length)); current_tx_desc->upper.data = htole32((txd_upper)); if (++i == adapter->num_tx_desc) i = 0; tx_buffer->m_head = NULL; txd_used++; } } else { tx_buffer = &adapter->tx_buffer_area[i]; current_tx_desc = &adapter->tx_desc_base[i]; seg_addr = htole64(segs[j].ds_addr); seg_len = segs[j].ds_len; /* ** TSO Workaround: ** If this is the last descriptor, we want to ** split it so we have a small final sentinel */ if (tso_desc && (j == (nsegs -1)) && (seg_len > 8)) { seg_len -= 4; current_tx_desc->buffer_addr = seg_addr; current_tx_desc->lower.data = htole32( adapter->txd_cmd | txd_lower | seg_len); current_tx_desc->upper.data = htole32(txd_upper); if (++i == adapter->num_tx_desc) i = 0; /* Now make the sentinel */ ++txd_used; /* using an extra txd */ current_tx_desc = &adapter->tx_desc_base[i]; tx_buffer = &adapter->tx_buffer_area[i]; current_tx_desc->buffer_addr = seg_addr + seg_len; current_tx_desc->lower.data = htole32( adapter->txd_cmd | txd_lower | 4); current_tx_desc->upper.data = htole32(txd_upper); if (++i == adapter->num_tx_desc) i = 0; } else { current_tx_desc->buffer_addr = seg_addr; current_tx_desc->lower.data = htole32( adapter->txd_cmd | txd_lower | seg_len); current_tx_desc->upper.data = htole32(txd_upper); if (++i == adapter->num_tx_desc) i = 0; } tx_buffer->m_head = NULL; } } adapter->next_avail_tx_desc = i; if (adapter->pcix_82544) adapter->num_tx_desc_avail -= txd_used; else { adapter->num_tx_desc_avail -= nsegs; if (tso_desc) /* TSO used an extra for sentinel */ adapter->num_tx_desc_avail -= txd_used; } - if (mtag != NULL) { + if (m_head->m_flags & M_VLANTAG) { /* Set the vlan id. */ current_tx_desc->upper.fields.special = - htole16(VLAN_TAG_VALUE(mtag)); + htole16(m_head->m_pkthdr.ether_vtag); /* Tell hardware to add tag. */ current_tx_desc->lower.data |= htole32(E1000_TXD_CMD_VLE); } tx_buffer->m_head = m_head; tx_buffer_last->map = tx_buffer->map; tx_buffer->map = map; bus_dmamap_sync(adapter->txtag, map, BUS_DMASYNC_PREWRITE); /* * Last Descriptor of Packet needs End Of Packet (EOP). */ current_tx_desc->lower.data |= htole32(E1000_TXD_CMD_EOP); /* * Advance the Transmit Descriptor Tail (Tdt), this tells the E1000 * that this frame is available to transmit. */ bus_dmamap_sync(adapter->txdma.dma_tag, adapter->txdma.dma_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); if (adapter->hw.mac_type == em_82547 && adapter->link_duplex == HALF_DUPLEX) em_82547_move_tail_locked(adapter); else { E1000_WRITE_REG(&adapter->hw, TDT, i); if (adapter->hw.mac_type == em_82547) em_82547_update_fifo_head(adapter, m_head->m_pkthdr.len); } return (0); } /********************************************************************* * * 82547 workaround to avoid controller hang in half-duplex environment. * The workaround is to avoid queuing a large packet that would span * the internal Tx FIFO ring boundary. We need to reset the FIFO pointers * in this case. We do that only when FIFO is quiescent. * **********************************************************************/ static void em_82547_move_tail_locked(struct adapter *adapter) { uint16_t hw_tdt; uint16_t sw_tdt; struct em_tx_desc *tx_desc; uint16_t length = 0; boolean_t eop = 0; EM_LOCK_ASSERT(adapter); hw_tdt = E1000_READ_REG(&adapter->hw, TDT); sw_tdt = adapter->next_avail_tx_desc; while (hw_tdt != sw_tdt) { tx_desc = &adapter->tx_desc_base[hw_tdt]; length += tx_desc->lower.flags.length; eop = tx_desc->lower.data & E1000_TXD_CMD_EOP; if(++hw_tdt == adapter->num_tx_desc) hw_tdt = 0; if (eop) { if (em_82547_fifo_workaround(adapter, length)) { adapter->tx_fifo_wrk_cnt++; callout_reset(&adapter->tx_fifo_timer, 1, em_82547_move_tail, adapter); break; } E1000_WRITE_REG(&adapter->hw, TDT, hw_tdt); em_82547_update_fifo_head(adapter, length); length = 0; } } } static void em_82547_move_tail(void *arg) { struct adapter *adapter = arg; EM_LOCK(adapter); em_82547_move_tail_locked(adapter); EM_UNLOCK(adapter); } static int em_82547_fifo_workaround(struct adapter *adapter, int len) { int fifo_space, fifo_pkt_len; fifo_pkt_len = roundup2(len + EM_FIFO_HDR, EM_FIFO_HDR); if (adapter->link_duplex == HALF_DUPLEX) { fifo_space = adapter->tx_fifo_size - adapter->tx_fifo_head; if (fifo_pkt_len >= (EM_82547_PKT_THRESH + fifo_space)) { if (em_82547_tx_fifo_reset(adapter)) return (0); else return (1); } } return (0); } static void em_82547_update_fifo_head(struct adapter *adapter, int len) { int fifo_pkt_len = roundup2(len + EM_FIFO_HDR, EM_FIFO_HDR); /* tx_fifo_head is always 16 byte aligned */ adapter->tx_fifo_head += fifo_pkt_len; if (adapter->tx_fifo_head >= adapter->tx_fifo_size) { adapter->tx_fifo_head -= adapter->tx_fifo_size; } } static int em_82547_tx_fifo_reset(struct adapter *adapter) { uint32_t tctl; if ((E1000_READ_REG(&adapter->hw, TDT) == E1000_READ_REG(&adapter->hw, TDH)) && (E1000_READ_REG(&adapter->hw, TDFT) == E1000_READ_REG(&adapter->hw, TDFH)) && (E1000_READ_REG(&adapter->hw, TDFTS) == E1000_READ_REG(&adapter->hw, TDFHS))&& (E1000_READ_REG(&adapter->hw, TDFPC) == 0)) { /* Disable TX unit */ tctl = E1000_READ_REG(&adapter->hw, TCTL); E1000_WRITE_REG(&adapter->hw, TCTL, tctl & ~E1000_TCTL_EN); /* Reset FIFO pointers */ E1000_WRITE_REG(&adapter->hw, TDFT, adapter->tx_head_addr); E1000_WRITE_REG(&adapter->hw, TDFH, adapter->tx_head_addr); E1000_WRITE_REG(&adapter->hw, TDFTS, adapter->tx_head_addr); E1000_WRITE_REG(&adapter->hw, TDFHS, adapter->tx_head_addr); /* Re-enable TX unit */ E1000_WRITE_REG(&adapter->hw, TCTL, tctl); E1000_WRITE_FLUSH(&adapter->hw); adapter->tx_fifo_head = 0; adapter->tx_fifo_reset_cnt++; return (TRUE); } else { return (FALSE); } } static void em_set_promisc(struct adapter *adapter) { struct ifnet *ifp = adapter->ifp; uint32_t reg_rctl; reg_rctl = E1000_READ_REG(&adapter->hw, RCTL); if (ifp->if_flags & IFF_PROMISC) { reg_rctl |= (E1000_RCTL_UPE | E1000_RCTL_MPE); E1000_WRITE_REG(&adapter->hw, RCTL, reg_rctl); /* Disable VLAN stripping in promiscous mode * This enables bridging of vlan tagged frames to occur * and also allows vlan tags to be seen in tcpdump */ if (ifp->if_capenable & IFCAP_VLAN_HWTAGGING) em_disable_vlans(adapter); adapter->em_insert_vlan_header = 1; } else if (ifp->if_flags & IFF_ALLMULTI) { reg_rctl |= E1000_RCTL_MPE; reg_rctl &= ~E1000_RCTL_UPE; E1000_WRITE_REG(&adapter->hw, RCTL, reg_rctl); adapter->em_insert_vlan_header = 0; } else adapter->em_insert_vlan_header = 0; } static void em_disable_promisc(struct adapter *adapter) { struct ifnet *ifp = adapter->ifp; uint32_t reg_rctl; reg_rctl = E1000_READ_REG(&adapter->hw, RCTL); reg_rctl &= (~E1000_RCTL_UPE); reg_rctl &= (~E1000_RCTL_MPE); E1000_WRITE_REG(&adapter->hw, RCTL, reg_rctl); if (ifp->if_capenable & IFCAP_VLAN_HWTAGGING) em_enable_vlans(adapter); adapter->em_insert_vlan_header = 0; } /********************************************************************* * Multicast Update * * This routine is called whenever multicast address list is updated. * **********************************************************************/ static void em_set_multi(struct adapter *adapter) { struct ifnet *ifp = adapter->ifp; struct ifmultiaddr *ifma; uint32_t reg_rctl = 0; uint8_t mta[MAX_NUM_MULTICAST_ADDRESSES * ETH_LENGTH_OF_ADDRESS]; int mcnt = 0; IOCTL_DEBUGOUT("em_set_multi: begin"); if (adapter->hw.mac_type == em_82542_rev2_0) { reg_rctl = E1000_READ_REG(&adapter->hw, RCTL); if (adapter->hw.pci_cmd_word & CMD_MEM_WRT_INVALIDATE) em_pci_clear_mwi(&adapter->hw); reg_rctl |= E1000_RCTL_RST; E1000_WRITE_REG(&adapter->hw, RCTL, reg_rctl); msec_delay(5); } IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; if (mcnt == MAX_NUM_MULTICAST_ADDRESSES) break; bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr), &mta[mcnt*ETH_LENGTH_OF_ADDRESS], ETH_LENGTH_OF_ADDRESS); mcnt++; } IF_ADDR_UNLOCK(ifp); if (mcnt >= MAX_NUM_MULTICAST_ADDRESSES) { reg_rctl = E1000_READ_REG(&adapter->hw, RCTL); reg_rctl |= E1000_RCTL_MPE; E1000_WRITE_REG(&adapter->hw, RCTL, reg_rctl); } else em_mc_addr_list_update(&adapter->hw, mta, mcnt, 0, 1); if (adapter->hw.mac_type == em_82542_rev2_0) { reg_rctl = E1000_READ_REG(&adapter->hw, RCTL); reg_rctl &= ~E1000_RCTL_RST; E1000_WRITE_REG(&adapter->hw, RCTL, reg_rctl); msec_delay(5); if (adapter->hw.pci_cmd_word & CMD_MEM_WRT_INVALIDATE) em_pci_set_mwi(&adapter->hw); } } /********************************************************************* * Timer routine * * This routine checks for link status and updates statistics. * **********************************************************************/ static void em_local_timer(void *arg) { struct adapter *adapter = arg; struct ifnet *ifp = adapter->ifp; EM_LOCK(adapter); em_check_for_link(&adapter->hw); em_update_link_status(adapter); em_update_stats_counters(adapter); if (em_display_debug_stats && ifp->if_drv_flags & IFF_DRV_RUNNING) em_print_hw_stats(adapter); em_smartspeed(adapter); callout_reset(&adapter->timer, hz, em_local_timer, adapter); EM_UNLOCK(adapter); } static void em_update_link_status(struct adapter *adapter) { struct ifnet *ifp = adapter->ifp; device_t dev = adapter->dev; if (E1000_READ_REG(&adapter->hw, STATUS) & E1000_STATUS_LU) { if (adapter->link_active == 0) { em_get_speed_and_duplex(&adapter->hw, &adapter->link_speed, &adapter->link_duplex); /* Check if we may set SPEED_MODE bit on PCI-E */ if ((adapter->link_speed == SPEED_1000) && ((adapter->hw.mac_type == em_82571) || (adapter->hw.mac_type == em_82572))) { int tarc0; tarc0 = E1000_READ_REG(&adapter->hw, TARC0); tarc0 |= SPEED_MODE_BIT; E1000_WRITE_REG(&adapter->hw, TARC0, tarc0); } if (bootverbose) device_printf(dev, "Link is up %d Mbps %s\n", adapter->link_speed, ((adapter->link_duplex == FULL_DUPLEX) ? "Full Duplex" : "Half Duplex")); adapter->link_active = 1; adapter->smartspeed = 0; ifp->if_baudrate = adapter->link_speed * 1000000; if_link_state_change(ifp, LINK_STATE_UP); } } else { if (adapter->link_active == 1) { ifp->if_baudrate = adapter->link_speed = 0; adapter->link_duplex = 0; if (bootverbose) device_printf(dev, "Link is Down\n"); adapter->link_active = 0; if_link_state_change(ifp, LINK_STATE_DOWN); } } } /********************************************************************* * * This routine disables all traffic on the adapter by issuing a * global reset on the MAC and deallocates TX/RX buffers. * **********************************************************************/ static void em_stop(void *arg) { struct adapter *adapter = arg; struct ifnet *ifp = adapter->ifp; EM_LOCK_ASSERT(adapter); INIT_DEBUGOUT("em_stop: begin"); em_disable_intr(adapter); em_reset_hw(&adapter->hw); callout_stop(&adapter->timer); callout_stop(&adapter->tx_fifo_timer); em_free_transmit_structures(adapter); em_free_receive_structures(adapter); /* Tell the stack that the interface is no longer active */ ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); } /******************************************************************** * * Determine hardware revision. * **********************************************************************/ static void em_identify_hardware(struct adapter *adapter) { device_t dev = adapter->dev; /* Make sure our PCI config space has the necessary stuff set */ pci_enable_busmaster(dev); pci_enable_io(dev, SYS_RES_MEMORY); adapter->hw.pci_cmd_word = pci_read_config(dev, PCIR_COMMAND, 2); /* Save off the information about this board */ adapter->hw.vendor_id = pci_get_vendor(dev); adapter->hw.device_id = pci_get_device(dev); adapter->hw.revision_id = pci_get_revid(dev); adapter->hw.subsystem_vendor_id = pci_get_subvendor(dev); adapter->hw.subsystem_id = pci_get_subdevice(dev); /* Identify the MAC */ if (em_set_mac_type(&adapter->hw)) device_printf(dev, "Unknown MAC Type\n"); if(adapter->hw.mac_type == em_82541 || adapter->hw.mac_type == em_82541_rev_2 || adapter->hw.mac_type == em_82547 || adapter->hw.mac_type == em_82547_rev_2) adapter->hw.phy_init_script = TRUE; } static int em_allocate_pci_resources(struct adapter *adapter) { device_t dev = adapter->dev; int val, rid; rid = PCIR_BAR(0); adapter->res_memory = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (adapter->res_memory == NULL) { device_printf(dev, "Unable to allocate bus resource: memory\n"); return (ENXIO); } adapter->osdep.mem_bus_space_tag = rman_get_bustag(adapter->res_memory); adapter->osdep.mem_bus_space_handle = rman_get_bushandle(adapter->res_memory); adapter->hw.hw_addr = (uint8_t *)&adapter->osdep.mem_bus_space_handle; if (adapter->hw.mac_type > em_82543) { /* Figure our where our IO BAR is ? */ for (rid = PCIR_BAR(0); rid < PCIR_CIS;) { val = pci_read_config(dev, rid, 4); if (E1000_BAR_TYPE(val) == E1000_BAR_TYPE_IO) { adapter->io_rid = rid; break; } rid += 4; /* check for 64bit BAR */ if (E1000_BAR_MEM_TYPE(val) == E1000_BAR_MEM_TYPE_64BIT) rid += 4; } if (rid >= PCIR_CIS) { device_printf(dev, "Unable to locate IO BAR\n"); return (ENXIO); } adapter->res_ioport = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &adapter->io_rid, RF_ACTIVE); if (adapter->res_ioport == NULL) { device_printf(dev, "Unable to allocate bus resource: " "ioport\n"); return (ENXIO); } adapter->hw.io_base = 0; adapter->osdep.io_bus_space_tag = rman_get_bustag(adapter->res_ioport); adapter->osdep.io_bus_space_handle = rman_get_bushandle(adapter->res_ioport); } /* For ICH8 we need to find the flash memory. */ if (adapter->hw.mac_type == em_ich8lan) { rid = EM_FLASH; adapter->flash_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); adapter->osdep.flash_bus_space_tag = rman_get_bustag(adapter->flash_mem); adapter->osdep.flash_bus_space_handle = rman_get_bushandle(adapter->flash_mem); } rid = 0x0; adapter->res_interrupt = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (adapter->res_interrupt == NULL) { device_printf(dev, "Unable to allocate bus resource: " "interrupt\n"); return (ENXIO); } adapter->hw.back = &adapter->osdep; return (0); } int em_allocate_intr(struct adapter *adapter) { device_t dev = adapter->dev; int error; /* Manually turn off all interrupts */ E1000_WRITE_REG(&adapter->hw, IMC, 0xffffffff); #ifdef DEVICE_POLLING if (adapter->int_handler_tag == NULL && (error = bus_setup_intr(dev, adapter->res_interrupt, INTR_TYPE_NET | INTR_MPSAFE, em_intr, adapter, &adapter->int_handler_tag)) != 0) { device_printf(dev, "Failed to register interrupt handler"); return (error); } #else /* * Try allocating a fast interrupt and the associated deferred * processing contexts. */ TASK_INIT(&adapter->rxtx_task, 0, em_handle_rxtx, adapter); TASK_INIT(&adapter->link_task, 0, em_handle_link, adapter); adapter->tq = taskqueue_create_fast("em_taskq", M_NOWAIT, taskqueue_thread_enqueue, &adapter->tq); taskqueue_start_threads(&adapter->tq, 1, PI_NET, "%s taskq", device_get_nameunit(adapter->dev)); if ((error = bus_setup_intr(dev, adapter->res_interrupt, INTR_TYPE_NET | INTR_FAST, em_intr_fast, adapter, &adapter->int_handler_tag)) != 0) { device_printf(dev, "Failed to register fast interrupt " "handler: %d\n", error); taskqueue_free(adapter->tq); adapter->tq = NULL; return (error); } #endif em_enable_intr(adapter); return (0); } static void em_free_intr(struct adapter *adapter) { device_t dev = adapter->dev; if (adapter->int_handler_tag != NULL) { bus_teardown_intr(dev, adapter->res_interrupt, adapter->int_handler_tag); adapter->int_handler_tag = NULL; } if (adapter->tq != NULL) { taskqueue_drain(adapter->tq, &adapter->rxtx_task); taskqueue_drain(taskqueue_fast, &adapter->link_task); taskqueue_free(adapter->tq); adapter->tq = NULL; } } static void em_free_pci_resources(struct adapter *adapter) { device_t dev = adapter->dev; if (adapter->res_interrupt != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, adapter->res_interrupt); if (adapter->res_memory != NULL) bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(0), adapter->res_memory); if (adapter->flash_mem != NULL) bus_release_resource(dev, SYS_RES_MEMORY, EM_FLASH, adapter->flash_mem); if (adapter->res_ioport != NULL) bus_release_resource(dev, SYS_RES_IOPORT, adapter->io_rid, adapter->res_ioport); } /********************************************************************* * * Initialize the hardware to a configuration as specified by the * adapter structure. The controller is reset, the EEPROM is * verified, the MAC address is set, then the shared initialization * routines are called. * **********************************************************************/ static int em_hardware_init(struct adapter *adapter) { device_t dev = adapter->dev; uint16_t rx_buffer_size; INIT_DEBUGOUT("em_hardware_init: begin"); /* Issue a global reset */ em_reset_hw(&adapter->hw); /* When hardware is reset, fifo_head is also reset */ adapter->tx_fifo_head = 0; /* Make sure we have a good EEPROM before we read from it */ if (em_validate_eeprom_checksum(&adapter->hw) < 0) { device_printf(dev, "The EEPROM Checksum Is Not Valid\n"); return (EIO); } if (em_read_part_num(&adapter->hw, &(adapter->part_num)) < 0) { device_printf(dev, "EEPROM read error while reading part " "number\n"); return (EIO); } /* Set up smart power down as default off on newer adapters. */ if (!em_smart_pwr_down && (adapter->hw.mac_type == em_82571 || adapter->hw.mac_type == em_82572)) { uint16_t phy_tmp = 0; /* Speed up time to link by disabling smart power down. */ em_read_phy_reg(&adapter->hw, IGP02E1000_PHY_POWER_MGMT, &phy_tmp); phy_tmp &= ~IGP02E1000_PM_SPD; em_write_phy_reg(&adapter->hw, IGP02E1000_PHY_POWER_MGMT, phy_tmp); } /* * These parameters control the automatic generation (Tx) and * response (Rx) to Ethernet PAUSE frames. * - High water mark should allow for at least two frames to be * received after sending an XOFF. * - Low water mark works best when it is very near the high water mark. * This allows the receiver to restart by sending XON when it has * drained a bit. Here we use an arbitary value of 1500 which will * restart after one full frame is pulled from the buffer. There * could be several smaller frames in the buffer and if so they will * not trigger the XON until their total number reduces the buffer * by 1500. * - The pause time is fairly large at 1000 x 512ns = 512 usec. */ rx_buffer_size = ((E1000_READ_REG(&adapter->hw, PBA) & 0xffff) << 10 ); adapter->hw.fc_high_water = rx_buffer_size - roundup2(adapter->hw.max_frame_size, 1024); adapter->hw.fc_low_water = adapter->hw.fc_high_water - 1500; if (adapter->hw.mac_type == em_80003es2lan) adapter->hw.fc_pause_time = 0xFFFF; else adapter->hw.fc_pause_time = 0x1000; adapter->hw.fc_send_xon = TRUE; adapter->hw.fc = em_fc_full; if (em_init_hw(&adapter->hw) < 0) { device_printf(dev, "Hardware Initialization Failed"); return (EIO); } em_check_for_link(&adapter->hw); return (0); } /********************************************************************* * * Setup networking device structure and register an interface. * **********************************************************************/ static void em_setup_interface(device_t dev, struct adapter *adapter) { struct ifnet *ifp; INIT_DEBUGOUT("em_setup_interface: begin"); ifp = adapter->ifp = if_alloc(IFT_ETHER); if (ifp == NULL) panic("%s: can not if_alloc()", device_get_nameunit(dev)); if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_mtu = ETHERMTU; ifp->if_init = em_init; ifp->if_softc = adapter; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = em_ioctl; ifp->if_start = em_start; ifp->if_watchdog = em_watchdog; IFQ_SET_MAXLEN(&ifp->if_snd, adapter->num_tx_desc - 1); ifp->if_snd.ifq_drv_maxlen = adapter->num_tx_desc - 1; IFQ_SET_READY(&ifp->if_snd); ether_ifattach(ifp, adapter->hw.mac_addr); ifp->if_capabilities = ifp->if_capenable = 0; if (adapter->hw.mac_type >= em_82543) { ifp->if_capabilities |= IFCAP_HWCSUM | IFCAP_VLAN_HWCSUM; ifp->if_capenable |= IFCAP_HWCSUM | IFCAP_VLAN_HWCSUM; } /* Enable TSO if available */ if ((adapter->hw.mac_type > em_82544) && (adapter->hw.mac_type != em_82547)) { ifp->if_capabilities |= IFCAP_TSO; ifp->if_capenable |= IFCAP_TSO; } /* * Tell the upper layer(s) we support long frames. */ ifp->if_data.ifi_hdrlen = sizeof(struct ether_vlan_header); ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_MTU; ifp->if_capenable |= IFCAP_VLAN_MTU; #ifdef DEVICE_POLLING ifp->if_capabilities |= IFCAP_POLLING; #endif /* * Specify the media types supported by this adapter and register * callbacks to update media and link information */ ifmedia_init(&adapter->media, IFM_IMASK, em_media_change, em_media_status); if ((adapter->hw.media_type == em_media_type_fiber) || (adapter->hw.media_type == em_media_type_internal_serdes)) { u_char fiber_type = IFM_1000_SX; /* default type; */ if (adapter->hw.mac_type == em_82545) fiber_type = IFM_1000_LX; ifmedia_add(&adapter->media, IFM_ETHER | fiber_type | IFM_FDX, 0, NULL); ifmedia_add(&adapter->media, IFM_ETHER | fiber_type, 0, NULL); } else { ifmedia_add(&adapter->media, IFM_ETHER | IFM_10_T, 0, NULL); ifmedia_add(&adapter->media, IFM_ETHER | IFM_10_T | IFM_FDX, 0, NULL); ifmedia_add(&adapter->media, IFM_ETHER | IFM_100_TX, 0, NULL); ifmedia_add(&adapter->media, IFM_ETHER | IFM_100_TX | IFM_FDX, 0, NULL); if (adapter->hw.phy_type != em_phy_ife) { ifmedia_add(&adapter->media, IFM_ETHER | IFM_1000_T | IFM_FDX, 0, NULL); ifmedia_add(&adapter->media, IFM_ETHER | IFM_1000_T, 0, NULL); } } ifmedia_add(&adapter->media, IFM_ETHER | IFM_AUTO, 0, NULL); ifmedia_set(&adapter->media, IFM_ETHER | IFM_AUTO); } /********************************************************************* * * Workaround for SmartSpeed on 82541 and 82547 controllers * **********************************************************************/ static void em_smartspeed(struct adapter *adapter) { uint16_t phy_tmp; if (adapter->link_active || (adapter->hw.phy_type != em_phy_igp) || adapter->hw.autoneg == 0 || (adapter->hw.autoneg_advertised & ADVERTISE_1000_FULL) == 0) return; if (adapter->smartspeed == 0) { /* If Master/Slave config fault is asserted twice, * we assume back-to-back */ em_read_phy_reg(&adapter->hw, PHY_1000T_STATUS, &phy_tmp); if (!(phy_tmp & SR_1000T_MS_CONFIG_FAULT)) return; em_read_phy_reg(&adapter->hw, PHY_1000T_STATUS, &phy_tmp); if (phy_tmp & SR_1000T_MS_CONFIG_FAULT) { em_read_phy_reg(&adapter->hw, PHY_1000T_CTRL, &phy_tmp); if(phy_tmp & CR_1000T_MS_ENABLE) { phy_tmp &= ~CR_1000T_MS_ENABLE; em_write_phy_reg(&adapter->hw, PHY_1000T_CTRL, phy_tmp); adapter->smartspeed++; if(adapter->hw.autoneg && !em_phy_setup_autoneg(&adapter->hw) && !em_read_phy_reg(&adapter->hw, PHY_CTRL, &phy_tmp)) { phy_tmp |= (MII_CR_AUTO_NEG_EN | MII_CR_RESTART_AUTO_NEG); em_write_phy_reg(&adapter->hw, PHY_CTRL, phy_tmp); } } } return; } else if(adapter->smartspeed == EM_SMARTSPEED_DOWNSHIFT) { /* If still no link, perhaps using 2/3 pair cable */ em_read_phy_reg(&adapter->hw, PHY_1000T_CTRL, &phy_tmp); phy_tmp |= CR_1000T_MS_ENABLE; em_write_phy_reg(&adapter->hw, PHY_1000T_CTRL, phy_tmp); if(adapter->hw.autoneg && !em_phy_setup_autoneg(&adapter->hw) && !em_read_phy_reg(&adapter->hw, PHY_CTRL, &phy_tmp)) { phy_tmp |= (MII_CR_AUTO_NEG_EN | MII_CR_RESTART_AUTO_NEG); em_write_phy_reg(&adapter->hw, PHY_CTRL, phy_tmp); } } /* Restart process after EM_SMARTSPEED_MAX iterations */ if(adapter->smartspeed++ == EM_SMARTSPEED_MAX) adapter->smartspeed = 0; } /* * Manage DMA'able memory. */ static void em_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { if (error) return; *(bus_addr_t *) arg = segs[0].ds_addr; } static int em_dma_malloc(struct adapter *adapter, bus_size_t size, struct em_dma_alloc *dma, int mapflags) { int error; error = bus_dma_tag_create(bus_get_dma_tag(adapter->dev), /* parent */ EM_DBA_ALIGN, 0, /* alignment, bounds */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ size, /* maxsize */ 1, /* nsegments */ size, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &dma->dma_tag); if (error) { device_printf(adapter->dev, "%s: bus_dma_tag_create failed: %d\n", __func__, error); goto fail_0; } error = bus_dmamem_alloc(dma->dma_tag, (void**) &dma->dma_vaddr, BUS_DMA_NOWAIT, &dma->dma_map); if (error) { device_printf(adapter->dev, "%s: bus_dmamem_alloc(%ju) failed: %d\n", __func__, (uintmax_t)size, error); goto fail_2; } dma->dma_paddr = 0; error = bus_dmamap_load(dma->dma_tag, dma->dma_map, dma->dma_vaddr, size, em_dmamap_cb, &dma->dma_paddr, mapflags | BUS_DMA_NOWAIT); if (error || dma->dma_paddr == 0) { device_printf(adapter->dev, "%s: bus_dmamap_load failed: %d\n", __func__, error); goto fail_3; } return (0); fail_3: bus_dmamap_unload(dma->dma_tag, dma->dma_map); fail_2: bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map); bus_dma_tag_destroy(dma->dma_tag); fail_0: dma->dma_map = NULL; dma->dma_tag = NULL; return (error); } static void em_dma_free(struct adapter *adapter, struct em_dma_alloc *dma) { if (dma->dma_tag == NULL) return; if (dma->dma_map != NULL) { bus_dmamap_sync(dma->dma_tag, dma->dma_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(dma->dma_tag, dma->dma_map); bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map); dma->dma_map = NULL; } bus_dma_tag_destroy(dma->dma_tag); dma->dma_tag = NULL; } /********************************************************************* * * Allocate memory for tx_buffer structures. The tx_buffer stores all * the information needed to transmit a packet on the wire. * **********************************************************************/ static int em_allocate_transmit_structures(struct adapter *adapter) { adapter->tx_buffer_area = malloc(sizeof(struct em_buffer) * adapter->num_tx_desc, M_DEVBUF, M_NOWAIT); if (adapter->tx_buffer_area == NULL) { device_printf(adapter->dev, "Unable to allocate tx_buffer memory\n"); return (ENOMEM); } bzero(adapter->tx_buffer_area, sizeof(struct em_buffer) * adapter->num_tx_desc); return (0); } /********************************************************************* * * Allocate and initialize transmit structures. * **********************************************************************/ static int em_setup_transmit_structures(struct adapter *adapter) { struct ifnet *ifp = adapter->ifp; device_t dev = adapter->dev; struct em_buffer *tx_buffer; bus_size_t size, segsize; int error, i; /* * Setup DMA descriptor areas. */ segsize = size = roundup2(adapter->hw.max_frame_size, MCLBYTES); /* Overrides for TSO - want large sizes */ if (ifp->if_hwassist & EM_TCPSEG_FEATURES) { size = EM_TSO_SIZE; segsize = PAGE_SIZE; } if ((error = bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */ 1, 0, /* alignment, bounds */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ size, /* maxsize */ EM_MAX_SCATTER, /* nsegments */ segsize, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &adapter->txtag)) != 0) { device_printf(dev, "Unable to allocate TX DMA tag\n"); goto fail; } if ((error = em_allocate_transmit_structures(adapter)) != 0) goto fail; bzero(adapter->tx_desc_base, (sizeof(struct em_tx_desc)) * adapter->num_tx_desc); tx_buffer = adapter->tx_buffer_area; for (i = 0; i < adapter->num_tx_desc; i++) { error = bus_dmamap_create(adapter->txtag, 0, &tx_buffer->map); if (error != 0) { device_printf(dev, "Unable to create TX DMA map\n"); goto fail; } tx_buffer++; } adapter->next_avail_tx_desc = 0; adapter->oldest_used_tx_desc = 0; /* Set number of descriptors available */ adapter->num_tx_desc_avail = adapter->num_tx_desc; /* Set checksum context */ adapter->active_checksum_context = OFFLOAD_NONE; bus_dmamap_sync(adapter->txdma.dma_tag, adapter->txdma.dma_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); fail: em_free_transmit_structures(adapter); return (error); } /********************************************************************* * * Enable transmit unit. * **********************************************************************/ static void em_initialize_transmit_unit(struct adapter *adapter) { uint32_t reg_tctl, reg_tarc; uint32_t reg_tipg = 0; uint64_t bus_addr; INIT_DEBUGOUT("em_initialize_transmit_unit: begin"); /* Setup the Base and Length of the Tx Descriptor Ring */ bus_addr = adapter->txdma.dma_paddr; E1000_WRITE_REG(&adapter->hw, TDLEN, adapter->num_tx_desc * sizeof(struct em_tx_desc)); E1000_WRITE_REG(&adapter->hw, TDBAH, (uint32_t)(bus_addr >> 32)); E1000_WRITE_REG(&adapter->hw, TDBAL, (uint32_t)bus_addr); /* Setup the HW Tx Head and Tail descriptor pointers */ E1000_WRITE_REG(&adapter->hw, TDT, 0); E1000_WRITE_REG(&adapter->hw, TDH, 0); HW_DEBUGOUT2("Base = %x, Length = %x\n", E1000_READ_REG(&adapter->hw, TDBAL), E1000_READ_REG(&adapter->hw, TDLEN)); /* Set the default values for the Tx Inter Packet Gap timer */ switch (adapter->hw.mac_type) { case em_82542_rev2_0: case em_82542_rev2_1: reg_tipg = DEFAULT_82542_TIPG_IPGT; reg_tipg |= DEFAULT_82542_TIPG_IPGR1 << E1000_TIPG_IPGR1_SHIFT; reg_tipg |= DEFAULT_82542_TIPG_IPGR2 << E1000_TIPG_IPGR2_SHIFT; break; case em_80003es2lan: reg_tipg = DEFAULT_82543_TIPG_IPGR1; reg_tipg |= DEFAULT_80003ES2LAN_TIPG_IPGR2 << E1000_TIPG_IPGR2_SHIFT; break; default: if ((adapter->hw.media_type == em_media_type_fiber) || (adapter->hw.media_type == em_media_type_internal_serdes)) reg_tipg = DEFAULT_82543_TIPG_IPGT_FIBER; else reg_tipg = DEFAULT_82543_TIPG_IPGT_COPPER; reg_tipg |= DEFAULT_82543_TIPG_IPGR1 << E1000_TIPG_IPGR1_SHIFT; reg_tipg |= DEFAULT_82543_TIPG_IPGR2 << E1000_TIPG_IPGR2_SHIFT; } E1000_WRITE_REG(&adapter->hw, TIPG, reg_tipg); E1000_WRITE_REG(&adapter->hw, TIDV, adapter->tx_int_delay.value); if(adapter->hw.mac_type >= em_82540) E1000_WRITE_REG(&adapter->hw, TADV, adapter->tx_abs_int_delay.value); /* Do adapter specific tweaks before we enable the transmitter. */ if (adapter->hw.mac_type == em_82571 || adapter->hw.mac_type == em_82572) { reg_tarc = E1000_READ_REG(&adapter->hw, TARC0); reg_tarc |= (1 << 25); E1000_WRITE_REG(&adapter->hw, TARC0, reg_tarc); reg_tarc = E1000_READ_REG(&adapter->hw, TARC1); reg_tarc |= (1 << 25); reg_tarc &= ~(1 << 28); E1000_WRITE_REG(&adapter->hw, TARC1, reg_tarc); } else if (adapter->hw.mac_type == em_80003es2lan) { reg_tarc = E1000_READ_REG(&adapter->hw, TARC0); reg_tarc |= 1; E1000_WRITE_REG(&adapter->hw, TARC0, reg_tarc); reg_tarc = E1000_READ_REG(&adapter->hw, TARC1); reg_tarc |= 1; E1000_WRITE_REG(&adapter->hw, TARC1, reg_tarc); } /* Program the Transmit Control Register */ reg_tctl = E1000_TCTL_PSP | E1000_TCTL_EN | (E1000_COLLISION_THRESHOLD << E1000_CT_SHIFT); if (adapter->hw.mac_type >= em_82571) reg_tctl |= E1000_TCTL_MULR; if (adapter->link_duplex == FULL_DUPLEX) { reg_tctl |= E1000_FDX_COLLISION_DISTANCE << E1000_COLD_SHIFT; } else { reg_tctl |= E1000_HDX_COLLISION_DISTANCE << E1000_COLD_SHIFT; } /* This write will effectively turn on the transmit unit. */ E1000_WRITE_REG(&adapter->hw, TCTL, reg_tctl); /* Setup Transmit Descriptor Settings for this adapter */ adapter->txd_cmd = E1000_TXD_CMD_IFCS | E1000_TXD_CMD_RS; if (adapter->tx_int_delay.value > 0) adapter->txd_cmd |= E1000_TXD_CMD_IDE; } /********************************************************************* * * Free all transmit related data structures. * **********************************************************************/ static void em_free_transmit_structures(struct adapter *adapter) { struct em_buffer *tx_buffer; int i; INIT_DEBUGOUT("free_transmit_structures: begin"); if (adapter->tx_buffer_area != NULL) { tx_buffer = adapter->tx_buffer_area; for (i = 0; i < adapter->num_tx_desc; i++, tx_buffer++) { if (tx_buffer->m_head != NULL) { bus_dmamap_sync(adapter->txtag, tx_buffer->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(adapter->txtag, tx_buffer->map); m_freem(tx_buffer->m_head); tx_buffer->m_head = NULL; } else if (tx_buffer->map != NULL) bus_dmamap_unload(adapter->txtag, tx_buffer->map); if (tx_buffer->map != NULL) { bus_dmamap_destroy(adapter->txtag, tx_buffer->map); tx_buffer->map = NULL; } } } if (adapter->tx_buffer_area != NULL) { free(adapter->tx_buffer_area, M_DEVBUF); adapter->tx_buffer_area = NULL; } if (adapter->txtag != NULL) { bus_dma_tag_destroy(adapter->txtag); adapter->txtag = NULL; } } /********************************************************************* * * The offload context needs to be set when we transfer the first * packet of a particular protocol (TCP/UDP). We change the * context only if the protocol type changes. * **********************************************************************/ static void em_transmit_checksum_setup(struct adapter *adapter, struct mbuf *mp, uint32_t *txd_upper, uint32_t *txd_lower) { struct em_context_desc *TXD; struct em_buffer *tx_buffer; int curr_txd; if (mp->m_pkthdr.csum_flags) { if (mp->m_pkthdr.csum_flags & CSUM_TCP) { *txd_upper = E1000_TXD_POPTS_TXSM << 8; *txd_lower = E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D; if (adapter->active_checksum_context == OFFLOAD_TCP_IP) return; else adapter->active_checksum_context = OFFLOAD_TCP_IP; } else if (mp->m_pkthdr.csum_flags & CSUM_UDP) { *txd_upper = E1000_TXD_POPTS_TXSM << 8; *txd_lower = E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D; if (adapter->active_checksum_context == OFFLOAD_UDP_IP) return; else adapter->active_checksum_context = OFFLOAD_UDP_IP; } else { *txd_upper = 0; *txd_lower = 0; return; } } else { *txd_upper = 0; *txd_lower = 0; return; } /* If we reach this point, the checksum offload context * needs to be reset. */ curr_txd = adapter->next_avail_tx_desc; tx_buffer = &adapter->tx_buffer_area[curr_txd]; TXD = (struct em_context_desc *) &adapter->tx_desc_base[curr_txd]; TXD->lower_setup.ip_fields.ipcss = ETHER_HDR_LEN; TXD->lower_setup.ip_fields.ipcso = ETHER_HDR_LEN + offsetof(struct ip, ip_sum); TXD->lower_setup.ip_fields.ipcse = htole16(ETHER_HDR_LEN + sizeof(struct ip) - 1); TXD->upper_setup.tcp_fields.tucss = ETHER_HDR_LEN + sizeof(struct ip); TXD->upper_setup.tcp_fields.tucse = htole16(0); if (adapter->active_checksum_context == OFFLOAD_TCP_IP) { TXD->upper_setup.tcp_fields.tucso = ETHER_HDR_LEN + sizeof(struct ip) + offsetof(struct tcphdr, th_sum); } else if (adapter->active_checksum_context == OFFLOAD_UDP_IP) { TXD->upper_setup.tcp_fields.tucso = ETHER_HDR_LEN + sizeof(struct ip) + offsetof(struct udphdr, uh_sum); } TXD->tcp_seg_setup.data = htole32(0); TXD->cmd_and_length = htole32(adapter->txd_cmd | E1000_TXD_CMD_DEXT); tx_buffer->m_head = NULL; if (++curr_txd == adapter->num_tx_desc) curr_txd = 0; adapter->num_tx_desc_avail--; adapter->next_avail_tx_desc = curr_txd; } /********************************************************************** * * Setup work for hardware segmentation offload (TSO) * **********************************************************************/ static boolean_t em_tso_setup(struct adapter *adapter, struct mbuf *mp, uint32_t *txd_upper, uint32_t *txd_lower) { struct em_context_desc *TXD; struct em_buffer *tx_buffer; struct ip *ip; struct tcphdr *th; int curr_txd, hdr_len, ip_hlen, tcp_hlen; if (((mp->m_pkthdr.csum_flags & CSUM_TSO) == 0) || (mp->m_pkthdr.len <= E1000_TX_BUFFER_SIZE)) { return FALSE; } *txd_lower = (E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D | E1000_TXD_CMD_TSE); *txd_upper = (E1000_TXD_POPTS_IXSM | E1000_TXD_POPTS_TXSM) << 8; curr_txd = adapter->next_avail_tx_desc; tx_buffer = &adapter->tx_buffer_area[curr_txd]; TXD = (struct em_context_desc *) &adapter->tx_desc_base[curr_txd]; mp->m_data += sizeof(struct ether_header); ip = mtod(mp, struct ip *); ip->ip_len = 0; ip->ip_sum = 0; ip_hlen = ip->ip_hl << 2 ; th = (struct tcphdr *)((caddr_t)ip + ip_hlen); tcp_hlen = th->th_off << 2; hdr_len = ETHER_HDR_LEN + ip_hlen + tcp_hlen; th->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr, htons(IPPROTO_TCP)); mp->m_data -= sizeof(struct ether_header); TXD->lower_setup.ip_fields.ipcss = ETHER_HDR_LEN; TXD->lower_setup.ip_fields.ipcso = ETHER_HDR_LEN + offsetof(struct ip, ip_sum); TXD->lower_setup.ip_fields.ipcse = htole16(ETHER_HDR_LEN + ip_hlen - 1); TXD->upper_setup.tcp_fields.tucss = ETHER_HDR_LEN + ip_hlen; TXD->upper_setup.tcp_fields.tucse = 0; TXD->upper_setup.tcp_fields.tucso = ETHER_HDR_LEN + ip_hlen + offsetof(struct tcphdr, th_sum); TXD->tcp_seg_setup.fields.mss = htole16(mp->m_pkthdr.tso_segsz); TXD->tcp_seg_setup.fields.hdr_len = hdr_len; TXD->cmd_and_length = htole32(adapter->txd_cmd | E1000_TXD_CMD_DEXT | E1000_TXD_CMD_TSE | E1000_TXD_CMD_IP | E1000_TXD_CMD_TCP | (mp->m_pkthdr.len - (hdr_len))); tx_buffer->m_head = NULL; if (++curr_txd == adapter->num_tx_desc) curr_txd = 0; adapter->num_tx_desc_avail--; adapter->next_avail_tx_desc = curr_txd; adapter->tx_tso = TRUE; return TRUE; } /********************************************************************** * * Examine each tx_buffer in the used queue. If the hardware is done * processing the packet then free associated resources. The * tx_buffer is put back on the free queue. * **********************************************************************/ static void em_txeof(struct adapter *adapter) { int i, num_avail; struct em_buffer *tx_buffer; struct em_tx_desc *tx_desc; struct ifnet *ifp = adapter->ifp; EM_LOCK_ASSERT(adapter); if (adapter->num_tx_desc_avail == adapter->num_tx_desc) return; num_avail = adapter->num_tx_desc_avail; i = adapter->oldest_used_tx_desc; tx_buffer = &adapter->tx_buffer_area[i]; tx_desc = &adapter->tx_desc_base[i]; bus_dmamap_sync(adapter->txdma.dma_tag, adapter->txdma.dma_map, BUS_DMASYNC_POSTREAD); while (tx_desc->upper.fields.status & E1000_TXD_STAT_DD) { tx_desc->upper.data = 0; num_avail++; if (tx_buffer->m_head) { ifp->if_opackets++; bus_dmamap_sync(adapter->txtag, tx_buffer->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(adapter->txtag, tx_buffer->map); m_freem(tx_buffer->m_head); tx_buffer->m_head = NULL; } if (++i == adapter->num_tx_desc) i = 0; tx_buffer = &adapter->tx_buffer_area[i]; tx_desc = &adapter->tx_desc_base[i]; } bus_dmamap_sync(adapter->txdma.dma_tag, adapter->txdma.dma_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); adapter->oldest_used_tx_desc = i; /* * If we have enough room, clear IFF_DRV_OACTIVE to tell the stack * that it is OK to send packets. * If there are no pending descriptors, clear the timeout. Otherwise, * if some descriptors have been freed, restart the timeout. */ if (num_avail > EM_TX_CLEANUP_THRESHOLD) { ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if (num_avail == adapter->num_tx_desc) ifp->if_timer = 0; else if (num_avail != adapter->num_tx_desc_avail) ifp->if_timer = EM_TX_TIMEOUT; } adapter->num_tx_desc_avail = num_avail; } /********************************************************************* * * Get a buffer from system mbuf buffer pool. * **********************************************************************/ static int em_get_buf(struct adapter *adapter, int i) { struct mbuf *m; bus_dma_segment_t segs[1]; bus_dmamap_t map; struct em_buffer *rx_buffer; int error, nsegs; m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { adapter->mbuf_cluster_failed++; return (ENOBUFS); } m->m_len = m->m_pkthdr.len = MCLBYTES; if (adapter->hw.max_frame_size <= (MCLBYTES - ETHER_ALIGN)) m_adj(m, ETHER_ALIGN); /* * Using memory from the mbuf cluster pool, invoke the * bus_dma machinery to arrange the memory mapping. */ error = bus_dmamap_load_mbuf_sg(adapter->rxtag, adapter->rx_sparemap, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { m_free(m); return (error); } /* If nsegs is wrong then the stack is corrupt. */ KASSERT(nsegs == 1, ("Too many segments returned!")); rx_buffer = &adapter->rx_buffer_area[i]; if (rx_buffer->m_head != NULL) bus_dmamap_unload(adapter->rxtag, rx_buffer->map); map = rx_buffer->map; rx_buffer->map = adapter->rx_sparemap; adapter->rx_sparemap = map; bus_dmamap_sync(adapter->rxtag, rx_buffer->map, BUS_DMASYNC_PREREAD); rx_buffer->m_head = m; adapter->rx_desc_base[i].buffer_addr = htole64(segs[0].ds_addr); return (0); } /********************************************************************* * * Allocate memory for rx_buffer structures. Since we use one * rx_buffer per received packet, the maximum number of rx_buffer's * that we'll need is equal to the number of receive descriptors * that we've allocated. * **********************************************************************/ static int em_allocate_receive_structures(struct adapter *adapter) { device_t dev = adapter->dev; struct em_buffer *rx_buffer; int i, error; adapter->rx_buffer_area = malloc(sizeof(struct em_buffer) * adapter->num_rx_desc, M_DEVBUF, M_NOWAIT); if (adapter->rx_buffer_area == NULL) { device_printf(dev, "Unable to allocate rx_buffer memory\n"); return (ENOMEM); } bzero(adapter->rx_buffer_area, sizeof(struct em_buffer) * adapter->num_rx_desc); error = bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */ 1, 0, /* alignment, bounds */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &adapter->rxtag); if (error) { device_printf(dev, "%s: bus_dma_tag_create failed %d\n", __func__, error); goto fail; } error = bus_dmamap_create(adapter->rxtag, BUS_DMA_NOWAIT, &adapter->rx_sparemap); if (error) { device_printf(dev, "%s: bus_dmamap_create failed: %d\n", __func__, error); goto fail; } rx_buffer = adapter->rx_buffer_area; for (i = 0; i < adapter->num_rx_desc; i++, rx_buffer++) { error = bus_dmamap_create(adapter->rxtag, BUS_DMA_NOWAIT, &rx_buffer->map); if (error) { device_printf(dev, "%s: bus_dmamap_create failed: %d\n", __func__, error); goto fail; } } for (i = 0; i < adapter->num_rx_desc; i++) { error = em_get_buf(adapter, i); if (error) goto fail; } bus_dmamap_sync(adapter->rxdma.dma_tag, adapter->rxdma.dma_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); fail: em_free_receive_structures(adapter); return (error); } /********************************************************************* * * Allocate and initialize receive structures. * **********************************************************************/ static int em_setup_receive_structures(struct adapter *adapter) { int error; bzero(adapter->rx_desc_base, (sizeof(struct em_rx_desc)) * adapter->num_rx_desc); if ((error = em_allocate_receive_structures(adapter)) != 0) return (error); /* Setup our descriptor pointers */ adapter->next_rx_desc_to_check = 0; return (0); } /********************************************************************* * * Enable receive unit. * **********************************************************************/ static void em_initialize_receive_unit(struct adapter *adapter) { struct ifnet *ifp = adapter->ifp; uint64_t bus_addr; uint32_t reg_rctl; uint32_t reg_rxcsum; INIT_DEBUGOUT("em_initialize_receive_unit: begin"); /* * Make sure receives are disabled while setting * up the descriptor ring */ E1000_WRITE_REG(&adapter->hw, RCTL, 0); /* Set the Receive Delay Timer Register */ E1000_WRITE_REG(&adapter->hw, RDTR, adapter->rx_int_delay.value | E1000_RDT_FPDB); if(adapter->hw.mac_type >= em_82540) { E1000_WRITE_REG(&adapter->hw, RADV, adapter->rx_abs_int_delay.value); /* * Set the interrupt throttling rate. Value is calculated * as DEFAULT_ITR = 1/(MAX_INTS_PER_SEC * 256ns) */ #define MAX_INTS_PER_SEC 8000 #define DEFAULT_ITR 1000000000/(MAX_INTS_PER_SEC * 256) E1000_WRITE_REG(&adapter->hw, ITR, DEFAULT_ITR); } /* Setup the Base and Length of the Rx Descriptor Ring */ bus_addr = adapter->rxdma.dma_paddr; E1000_WRITE_REG(&adapter->hw, RDLEN, adapter->num_rx_desc * sizeof(struct em_rx_desc)); E1000_WRITE_REG(&adapter->hw, RDBAH, (uint32_t)(bus_addr >> 32)); E1000_WRITE_REG(&adapter->hw, RDBAL, (uint32_t)bus_addr); /* Setup the HW Rx Head and Tail Descriptor Pointers */ E1000_WRITE_REG(&adapter->hw, RDT, adapter->num_rx_desc - 1); E1000_WRITE_REG(&adapter->hw, RDH, 0); /* Setup the Receive Control Register */ reg_rctl = E1000_RCTL_EN | E1000_RCTL_BAM | E1000_RCTL_LBM_NO | E1000_RCTL_RDMTS_HALF | (adapter->hw.mc_filter_type << E1000_RCTL_MO_SHIFT); if (adapter->hw.tbi_compatibility_on == TRUE) reg_rctl |= E1000_RCTL_SBP; switch (adapter->rx_buffer_len) { default: case EM_RXBUFFER_2048: reg_rctl |= E1000_RCTL_SZ_2048; break; case EM_RXBUFFER_4096: reg_rctl |= E1000_RCTL_SZ_4096 | E1000_RCTL_BSEX | E1000_RCTL_LPE; break; case EM_RXBUFFER_8192: reg_rctl |= E1000_RCTL_SZ_8192 | E1000_RCTL_BSEX | E1000_RCTL_LPE; break; case EM_RXBUFFER_16384: reg_rctl |= E1000_RCTL_SZ_16384 | E1000_RCTL_BSEX | E1000_RCTL_LPE; break; } if (ifp->if_mtu > ETHERMTU) reg_rctl |= E1000_RCTL_LPE; /* Enable 82543 Receive Checksum Offload for TCP and UDP */ if ((adapter->hw.mac_type >= em_82543) && (ifp->if_capenable & IFCAP_RXCSUM)) { reg_rxcsum = E1000_READ_REG(&adapter->hw, RXCSUM); reg_rxcsum |= (E1000_RXCSUM_IPOFL | E1000_RXCSUM_TUOFL); E1000_WRITE_REG(&adapter->hw, RXCSUM, reg_rxcsum); } /* Enable Receives */ E1000_WRITE_REG(&adapter->hw, RCTL, reg_rctl); } /********************************************************************* * * Free receive related data structures. * **********************************************************************/ static void em_free_receive_structures(struct adapter *adapter) { struct em_buffer *rx_buffer; int i; INIT_DEBUGOUT("free_receive_structures: begin"); if (adapter->rx_sparemap) { bus_dmamap_destroy(adapter->rxtag, adapter->rx_sparemap); adapter->rx_sparemap = NULL; } if (adapter->rx_buffer_area != NULL) { rx_buffer = adapter->rx_buffer_area; for (i = 0; i < adapter->num_rx_desc; i++, rx_buffer++) { if (rx_buffer->m_head != NULL) { bus_dmamap_sync(adapter->rxtag, rx_buffer->map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(adapter->rxtag, rx_buffer->map); m_freem(rx_buffer->m_head); rx_buffer->m_head = NULL; } else if (rx_buffer->map != NULL) bus_dmamap_unload(adapter->rxtag, rx_buffer->map); if (rx_buffer->map != NULL) { bus_dmamap_destroy(adapter->rxtag, rx_buffer->map); rx_buffer->map = NULL; } } } if (adapter->rx_buffer_area != NULL) { free(adapter->rx_buffer_area, M_DEVBUF); adapter->rx_buffer_area = NULL; } if (adapter->rxtag != NULL) { bus_dma_tag_destroy(adapter->rxtag); adapter->rxtag = NULL; } } /********************************************************************* * * This routine executes in interrupt context. It replenishes * the mbufs in the descriptor and sends data which has been * dma'ed into host memory to upper layer. * * We loop at most count times if count is > 0, or until done if * count < 0. * *********************************************************************/ static int em_rxeof(struct adapter *adapter, int count) { struct ifnet *ifp; struct mbuf *mp; uint8_t accept_frame = 0; uint8_t eop = 0; uint16_t len, desc_len, prev_len_adj; int i; /* Pointer to the receive descriptor being examined. */ struct em_rx_desc *current_desc; uint8_t status; ifp = adapter->ifp; i = adapter->next_rx_desc_to_check; current_desc = &adapter->rx_desc_base[i]; bus_dmamap_sync(adapter->rxdma.dma_tag, adapter->rxdma.dma_map, BUS_DMASYNC_POSTREAD); if (!((current_desc->status) & E1000_RXD_STAT_DD)) return (0); while ((current_desc->status & E1000_RXD_STAT_DD) && (count != 0) && (ifp->if_drv_flags & IFF_DRV_RUNNING)) { struct mbuf *m = NULL; mp = adapter->rx_buffer_area[i].m_head; /* * Can't defer bus_dmamap_sync(9) because TBI_ACCEPT * needs to access the last received byte in the mbuf. */ bus_dmamap_sync(adapter->rxtag, adapter->rx_buffer_area[i].map, BUS_DMASYNC_POSTREAD); accept_frame = 1; prev_len_adj = 0; desc_len = le16toh(current_desc->length); status = current_desc->status; if (status & E1000_RXD_STAT_EOP) { count--; eop = 1; if (desc_len < ETHER_CRC_LEN) { len = 0; prev_len_adj = ETHER_CRC_LEN - desc_len; } else len = desc_len - ETHER_CRC_LEN; } else { eop = 0; len = desc_len; } if (current_desc->errors & E1000_RXD_ERR_FRAME_ERR_MASK) { uint8_t last_byte; uint32_t pkt_len = desc_len; if (adapter->fmp != NULL) pkt_len += adapter->fmp->m_pkthdr.len; last_byte = *(mtod(mp, caddr_t) + desc_len - 1); if (TBI_ACCEPT(&adapter->hw, status, current_desc->errors, pkt_len, last_byte)) { em_tbi_adjust_stats(&adapter->hw, &adapter->stats, pkt_len, adapter->hw.mac_addr); if (len > 0) len--; } else accept_frame = 0; } if (accept_frame) { if (em_get_buf(adapter, i) != 0) { ifp->if_iqdrops++; goto discard; } /* Assign correct length to the current fragment */ mp->m_len = len; if (adapter->fmp == NULL) { mp->m_pkthdr.len = len; adapter->fmp = mp; /* Store the first mbuf */ adapter->lmp = mp; } else { /* Chain mbuf's together */ mp->m_flags &= ~M_PKTHDR; /* * Adjust length of previous mbuf in chain if * we received less than 4 bytes in the last * descriptor. */ if (prev_len_adj > 0) { adapter->lmp->m_len -= prev_len_adj; adapter->fmp->m_pkthdr.len -= prev_len_adj; } adapter->lmp->m_next = mp; adapter->lmp = adapter->lmp->m_next; adapter->fmp->m_pkthdr.len += len; } if (eop) { adapter->fmp->m_pkthdr.rcvif = ifp; ifp->if_ipackets++; em_receive_checksum(adapter, current_desc, adapter->fmp); #ifndef __NO_STRICT_ALIGNMENT if (adapter->hw.max_frame_size > (MCLBYTES - ETHER_ALIGN) && em_fixup_rx(adapter) != 0) goto skip; #endif if (status & E1000_RXD_STAT_VP) - VLAN_INPUT_TAG(ifp, adapter->fmp, + adapter->fmp->m_pkthdr.ether_vtag = (le16toh(current_desc->special) & - E1000_RXD_SPC_VLAN_MASK)); + E1000_RXD_SPC_VLAN_MASK); + adapter->fmp->m_flags |= M_VLANTAG; #ifndef __NO_STRICT_ALIGNMENT skip: #endif m = adapter->fmp; adapter->fmp = NULL; adapter->lmp = NULL; } } else { ifp->if_ierrors++; discard: /* Reuse loaded DMA map and just update mbuf chain */ mp = adapter->rx_buffer_area[i].m_head; mp->m_len = mp->m_pkthdr.len = MCLBYTES; mp->m_data = mp->m_ext.ext_buf; mp->m_next = NULL; if (adapter->hw.max_frame_size <= (MCLBYTES - ETHER_ALIGN)) m_adj(mp, ETHER_ALIGN); if (adapter->fmp != NULL) { m_freem(adapter->fmp); adapter->fmp = NULL; adapter->lmp = NULL; } m = NULL; } /* Zero out the receive descriptors status. */ current_desc->status = 0; bus_dmamap_sync(adapter->rxdma.dma_tag, adapter->rxdma.dma_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* Advance our pointers to the next descriptor. */ if (++i == adapter->num_rx_desc) i = 0; if (m != NULL) { adapter->next_rx_desc_to_check = i; #ifdef DEVICE_POLLING EM_UNLOCK(adapter); (*ifp->if_input)(ifp, m); EM_LOCK(adapter); #else (*ifp->if_input)(ifp, m); #endif i = adapter->next_rx_desc_to_check; } current_desc = &adapter->rx_desc_base[i]; } adapter->next_rx_desc_to_check = i; /* Advance the E1000's Receive Queue #0 "Tail Pointer". */ if (--i < 0) i = adapter->num_rx_desc - 1; E1000_WRITE_REG(&adapter->hw, RDT, i); if (!((current_desc->status) & E1000_RXD_STAT_DD)) return (0); return (1); } #ifndef __NO_STRICT_ALIGNMENT /* * When jumbo frames are enabled we should realign entire payload on * architecures with strict alignment. This is serious design mistake of 8254x * as it nullifies DMA operations. 8254x just allows RX buffer size to be * 2048/4096/8192/16384. What we really want is 2048 - ETHER_ALIGN to align its * payload. On architecures without strict alignment restrictions 8254x still * performs unaligned memory access which would reduce the performance too. * To avoid copying over an entire frame to align, we allocate a new mbuf and * copy ethernet header to the new mbuf. The new mbuf is prepended into the * existing mbuf chain. * * Be aware, best performance of the 8254x is achived only when jumbo frame is * not used at all on architectures with strict alignment. */ static int em_fixup_rx(struct adapter *adapter) { struct mbuf *m, *n; int error; error = 0; m = adapter->fmp; if (m->m_len <= (MCLBYTES - ETHER_HDR_LEN)) { bcopy(m->m_data, m->m_data + ETHER_HDR_LEN, m->m_len); m->m_data += ETHER_HDR_LEN; } else { MGETHDR(n, M_DONTWAIT, MT_DATA); if (n != NULL) { bcopy(m->m_data, n->m_data, ETHER_HDR_LEN); m->m_data += ETHER_HDR_LEN; m->m_len -= ETHER_HDR_LEN; n->m_len = ETHER_HDR_LEN; M_MOVE_PKTHDR(n, m); n->m_next = m; adapter->fmp = n; } else { adapter->ifp->if_iqdrops++; adapter->mbuf_alloc_failed++; m_freem(adapter->fmp); adapter->fmp = NULL; adapter->lmp = NULL; error = ENOBUFS; } } return (error); } #endif /********************************************************************* * * Verify that the hardware indicated that the checksum is valid. * Inform the stack about the status of checksum so that stack * doesn't spend time verifying the checksum. * *********************************************************************/ static void em_receive_checksum(struct adapter *adapter, struct em_rx_desc *rx_desc, struct mbuf *mp) { /* 82543 or newer only */ if ((adapter->hw.mac_type < em_82543) || /* Ignore Checksum bit is set */ (rx_desc->status & E1000_RXD_STAT_IXSM)) { mp->m_pkthdr.csum_flags = 0; return; } if (rx_desc->status & E1000_RXD_STAT_IPCS) { /* Did it pass? */ if (!(rx_desc->errors & E1000_RXD_ERR_IPE)) { /* IP Checksum Good */ mp->m_pkthdr.csum_flags = CSUM_IP_CHECKED; mp->m_pkthdr.csum_flags |= CSUM_IP_VALID; } else { mp->m_pkthdr.csum_flags = 0; } } if (rx_desc->status & E1000_RXD_STAT_TCPCS) { /* Did it pass? */ if (!(rx_desc->errors & E1000_RXD_ERR_TCPE)) { mp->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | CSUM_PSEUDO_HDR); mp->m_pkthdr.csum_data = htons(0xffff); } } } static void em_enable_vlans(struct adapter *adapter) { uint32_t ctrl; E1000_WRITE_REG(&adapter->hw, VET, ETHERTYPE_VLAN); ctrl = E1000_READ_REG(&adapter->hw, CTRL); ctrl |= E1000_CTRL_VME; E1000_WRITE_REG(&adapter->hw, CTRL, ctrl); } static void em_disable_vlans(struct adapter *adapter) { uint32_t ctrl; ctrl = E1000_READ_REG(&adapter->hw, CTRL); ctrl &= ~E1000_CTRL_VME; E1000_WRITE_REG(&adapter->hw, CTRL, ctrl); } static void em_enable_intr(struct adapter *adapter) { E1000_WRITE_REG(&adapter->hw, IMS, (IMS_ENABLE_MASK)); } static void em_disable_intr(struct adapter *adapter) { /* * The first version of 82542 had an errata where when link was forced * it would stay up even up even if the cable was disconnected. * Sequence errors were used to detect the disconnect and then the * driver would unforce the link. This code in the in the ISR. For this * to work correctly the Sequence error interrupt had to be enabled * all the time. */ if (adapter->hw.mac_type == em_82542_rev2_0) E1000_WRITE_REG(&adapter->hw, IMC, (0xffffffff & ~E1000_IMC_RXSEQ)); else E1000_WRITE_REG(&adapter->hw, IMC, 0xffffffff); } static int em_is_valid_ether_addr(uint8_t *addr) { char zero_addr[6] = { 0, 0, 0, 0, 0, 0 }; if ((addr[0] & 1) || (!bcmp(addr, zero_addr, ETHER_ADDR_LEN))) { return (FALSE); } return (TRUE); } void em_write_pci_cfg(struct em_hw *hw, uint32_t reg, uint16_t *value) { pci_write_config(((struct em_osdep *)hw->back)->dev, reg, *value, 2); } void em_read_pci_cfg(struct em_hw *hw, uint32_t reg, uint16_t *value) { *value = pci_read_config(((struct em_osdep *)hw->back)->dev, reg, 2); } void em_pci_set_mwi(struct em_hw *hw) { pci_write_config(((struct em_osdep *)hw->back)->dev, PCIR_COMMAND, (hw->pci_cmd_word | CMD_MEM_WRT_INVALIDATE), 2); } void em_pci_clear_mwi(struct em_hw *hw) { pci_write_config(((struct em_osdep *)hw->back)->dev, PCIR_COMMAND, (hw->pci_cmd_word & ~CMD_MEM_WRT_INVALIDATE), 2); } /********************************************************************* * 82544 Coexistence issue workaround. * There are 2 issues. * 1. Transmit Hang issue. * To detect this issue, following equation can be used... * SIZE[3:0] + ADDR[2:0] = SUM[3:0]. * If SUM[3:0] is in between 1 to 4, we will have this issue. * * 2. DAC issue. * To detect this issue, following equation can be used... * SIZE[3:0] + ADDR[2:0] = SUM[3:0]. * If SUM[3:0] is in between 9 to c, we will have this issue. * * * WORKAROUND: * Make sure we do not have ending address as 1,2,3,4(Hang) or 9,a,b,c (DAC) * *** *********************************************************************/ static uint32_t em_fill_descriptors (bus_addr_t address, uint32_t length, PDESC_ARRAY desc_array) { /* Since issue is sensitive to length and address.*/ /* Let us first check the address...*/ uint32_t safe_terminator; if (length <= 4) { desc_array->descriptor[0].address = address; desc_array->descriptor[0].length = length; desc_array->elements = 1; return (desc_array->elements); } safe_terminator = (uint32_t)((((uint32_t)address & 0x7) + (length & 0xF)) & 0xF); /* if it does not fall between 0x1 to 0x4 and 0x9 to 0xC then return */ if (safe_terminator == 0 || (safe_terminator > 4 && safe_terminator < 9) || (safe_terminator > 0xC && safe_terminator <= 0xF)) { desc_array->descriptor[0].address = address; desc_array->descriptor[0].length = length; desc_array->elements = 1; return (desc_array->elements); } desc_array->descriptor[0].address = address; desc_array->descriptor[0].length = length - 4; desc_array->descriptor[1].address = address + (length - 4); desc_array->descriptor[1].length = 4; desc_array->elements = 2; return (desc_array->elements); } /********************************************************************** * * Update the board statistics counters. * **********************************************************************/ static void em_update_stats_counters(struct adapter *adapter) { struct ifnet *ifp; if(adapter->hw.media_type == em_media_type_copper || (E1000_READ_REG(&adapter->hw, STATUS) & E1000_STATUS_LU)) { adapter->stats.symerrs += E1000_READ_REG(&adapter->hw, SYMERRS); adapter->stats.sec += E1000_READ_REG(&adapter->hw, SEC); } adapter->stats.crcerrs += E1000_READ_REG(&adapter->hw, CRCERRS); adapter->stats.mpc += E1000_READ_REG(&adapter->hw, MPC); adapter->stats.scc += E1000_READ_REG(&adapter->hw, SCC); adapter->stats.ecol += E1000_READ_REG(&adapter->hw, ECOL); adapter->stats.mcc += E1000_READ_REG(&adapter->hw, MCC); adapter->stats.latecol += E1000_READ_REG(&adapter->hw, LATECOL); adapter->stats.colc += E1000_READ_REG(&adapter->hw, COLC); adapter->stats.dc += E1000_READ_REG(&adapter->hw, DC); adapter->stats.rlec += E1000_READ_REG(&adapter->hw, RLEC); adapter->stats.xonrxc += E1000_READ_REG(&adapter->hw, XONRXC); adapter->stats.xontxc += E1000_READ_REG(&adapter->hw, XONTXC); adapter->stats.xoffrxc += E1000_READ_REG(&adapter->hw, XOFFRXC); adapter->stats.xofftxc += E1000_READ_REG(&adapter->hw, XOFFTXC); adapter->stats.fcruc += E1000_READ_REG(&adapter->hw, FCRUC); adapter->stats.prc64 += E1000_READ_REG(&adapter->hw, PRC64); adapter->stats.prc127 += E1000_READ_REG(&adapter->hw, PRC127); adapter->stats.prc255 += E1000_READ_REG(&adapter->hw, PRC255); adapter->stats.prc511 += E1000_READ_REG(&adapter->hw, PRC511); adapter->stats.prc1023 += E1000_READ_REG(&adapter->hw, PRC1023); adapter->stats.prc1522 += E1000_READ_REG(&adapter->hw, PRC1522); adapter->stats.gprc += E1000_READ_REG(&adapter->hw, GPRC); adapter->stats.bprc += E1000_READ_REG(&adapter->hw, BPRC); adapter->stats.mprc += E1000_READ_REG(&adapter->hw, MPRC); adapter->stats.gptc += E1000_READ_REG(&adapter->hw, GPTC); /* For the 64-bit byte counters the low dword must be read first. */ /* Both registers clear on the read of the high dword */ adapter->stats.gorcl += E1000_READ_REG(&adapter->hw, GORCL); adapter->stats.gorch += E1000_READ_REG(&adapter->hw, GORCH); adapter->stats.gotcl += E1000_READ_REG(&adapter->hw, GOTCL); adapter->stats.gotch += E1000_READ_REG(&adapter->hw, GOTCH); adapter->stats.rnbc += E1000_READ_REG(&adapter->hw, RNBC); adapter->stats.ruc += E1000_READ_REG(&adapter->hw, RUC); adapter->stats.rfc += E1000_READ_REG(&adapter->hw, RFC); adapter->stats.roc += E1000_READ_REG(&adapter->hw, ROC); adapter->stats.rjc += E1000_READ_REG(&adapter->hw, RJC); adapter->stats.torl += E1000_READ_REG(&adapter->hw, TORL); adapter->stats.torh += E1000_READ_REG(&adapter->hw, TORH); adapter->stats.totl += E1000_READ_REG(&adapter->hw, TOTL); adapter->stats.toth += E1000_READ_REG(&adapter->hw, TOTH); adapter->stats.tpr += E1000_READ_REG(&adapter->hw, TPR); adapter->stats.tpt += E1000_READ_REG(&adapter->hw, TPT); adapter->stats.ptc64 += E1000_READ_REG(&adapter->hw, PTC64); adapter->stats.ptc127 += E1000_READ_REG(&adapter->hw, PTC127); adapter->stats.ptc255 += E1000_READ_REG(&adapter->hw, PTC255); adapter->stats.ptc511 += E1000_READ_REG(&adapter->hw, PTC511); adapter->stats.ptc1023 += E1000_READ_REG(&adapter->hw, PTC1023); adapter->stats.ptc1522 += E1000_READ_REG(&adapter->hw, PTC1522); adapter->stats.mptc += E1000_READ_REG(&adapter->hw, MPTC); adapter->stats.bptc += E1000_READ_REG(&adapter->hw, BPTC); if (adapter->hw.mac_type >= em_82543) { adapter->stats.algnerrc += E1000_READ_REG(&adapter->hw, ALGNERRC); adapter->stats.rxerrc += E1000_READ_REG(&adapter->hw, RXERRC); adapter->stats.tncrs += E1000_READ_REG(&adapter->hw, TNCRS); adapter->stats.cexterr += E1000_READ_REG(&adapter->hw, CEXTERR); adapter->stats.tsctc += E1000_READ_REG(&adapter->hw, TSCTC); adapter->stats.tsctfc += E1000_READ_REG(&adapter->hw, TSCTFC); } ifp = adapter->ifp; ifp->if_collisions = adapter->stats.colc; /* Rx Errors */ ifp->if_ierrors = adapter->stats.rxerrc + adapter->stats.crcerrs + adapter->stats.algnerrc + adapter->stats.ruc + adapter->stats.roc + adapter->stats.mpc + adapter->stats.cexterr; /* Tx Errors */ ifp->if_oerrors = adapter->stats.ecol + adapter->stats.latecol + adapter->watchdog_events; } /********************************************************************** * * This routine is called only when em_display_debug_stats is enabled. * This routine provides a way to take a look at important statistics * maintained by the driver and hardware. * **********************************************************************/ static void em_print_debug_info(struct adapter *adapter) { device_t dev = adapter->dev; uint8_t *hw_addr = adapter->hw.hw_addr; device_printf(dev, "Adapter hardware address = %p \n", hw_addr); device_printf(dev, "CTRL = 0x%x RCTL = 0x%x \n", E1000_READ_REG(&adapter->hw, CTRL), E1000_READ_REG(&adapter->hw, RCTL)); device_printf(dev, "Packet buffer = Tx=%dk Rx=%dk \n", ((E1000_READ_REG(&adapter->hw, PBA) & 0xffff0000) >> 16),\ (E1000_READ_REG(&adapter->hw, PBA) & 0xffff) ); device_printf(dev, "Flow control watermarks high = %d low = %d\n", adapter->hw.fc_high_water, adapter->hw.fc_low_water); device_printf(dev, "tx_int_delay = %d, tx_abs_int_delay = %d\n", E1000_READ_REG(&adapter->hw, TIDV), E1000_READ_REG(&adapter->hw, TADV)); device_printf(dev, "rx_int_delay = %d, rx_abs_int_delay = %d\n", E1000_READ_REG(&adapter->hw, RDTR), E1000_READ_REG(&adapter->hw, RADV)); device_printf(dev, "fifo workaround = %lld, fifo_reset_count = %lld\n", (long long)adapter->tx_fifo_wrk_cnt, (long long)adapter->tx_fifo_reset_cnt); device_printf(dev, "hw tdh = %d, hw tdt = %d\n", E1000_READ_REG(&adapter->hw, TDH), E1000_READ_REG(&adapter->hw, TDT)); device_printf(dev, "Num Tx descriptors avail = %d\n", adapter->num_tx_desc_avail); device_printf(dev, "Tx Descriptors not avail1 = %ld\n", adapter->no_tx_desc_avail1); device_printf(dev, "Tx Descriptors not avail2 = %ld\n", adapter->no_tx_desc_avail2); device_printf(dev, "Std mbuf failed = %ld\n", adapter->mbuf_alloc_failed); device_printf(dev, "Std mbuf cluster failed = %ld\n", adapter->mbuf_cluster_failed); } static void em_print_hw_stats(struct adapter *adapter) { device_t dev = adapter->dev; device_printf(dev, "Excessive collisions = %lld\n", (long long)adapter->stats.ecol); device_printf(dev, "Symbol errors = %lld\n", (long long)adapter->stats.symerrs); device_printf(dev, "Sequence errors = %lld\n", (long long)adapter->stats.sec); device_printf(dev, "Defer count = %lld\n", (long long)adapter->stats.dc); device_printf(dev, "Missed Packets = %lld\n", (long long)adapter->stats.mpc); device_printf(dev, "Receive No Buffers = %lld\n", (long long)adapter->stats.rnbc); /* RLEC is inaccurate on some hardware, calculate our own. */ device_printf(dev, "Receive Length Errors = %lld\n", ((long long)adapter->stats.roc + (long long)adapter->stats.ruc)); device_printf(dev, "Receive errors = %lld\n", (long long)adapter->stats.rxerrc); device_printf(dev, "Crc errors = %lld\n", (long long)adapter->stats.crcerrs); device_printf(dev, "Alignment errors = %lld\n", (long long)adapter->stats.algnerrc); device_printf(dev, "Carrier extension errors = %lld\n", (long long)adapter->stats.cexterr); device_printf(dev, "RX overruns = %ld\n", adapter->rx_overruns); device_printf(dev, "watchdog timeouts = %ld\n", adapter->watchdog_events); device_printf(dev, "XON Rcvd = %lld\n", (long long)adapter->stats.xonrxc); device_printf(dev, "XON Xmtd = %lld\n", (long long)adapter->stats.xontxc); device_printf(dev, "XOFF Rcvd = %lld\n", (long long)adapter->stats.xoffrxc); device_printf(dev, "XOFF Xmtd = %lld\n", (long long)adapter->stats.xofftxc); device_printf(dev, "Good Packets Rcvd = %lld\n", (long long)adapter->stats.gprc); device_printf(dev, "Good Packets Xmtd = %lld\n", (long long)adapter->stats.gptc); device_printf(dev, "TSO Contexts Xmtd = %lld\n", (long long)adapter->stats.tsctc); device_printf(dev, "TSO Contexts Failed = %lld\n", (long long)adapter->stats.tsctfc); } static int em_sysctl_debug_info(SYSCTL_HANDLER_ARGS) { struct adapter *adapter; int error; int result; result = -1; error = sysctl_handle_int(oidp, &result, 0, req); if (error || !req->newptr) return (error); if (result == 1) { adapter = (struct adapter *)arg1; em_print_debug_info(adapter); } return (error); } static int em_sysctl_stats(SYSCTL_HANDLER_ARGS) { struct adapter *adapter; int error; int result; result = -1; error = sysctl_handle_int(oidp, &result, 0, req); if (error || !req->newptr) return (error); if (result == 1) { adapter = (struct adapter *)arg1; em_print_hw_stats(adapter); } return (error); } static int em_sysctl_int_delay(SYSCTL_HANDLER_ARGS) { struct em_int_delay_info *info; struct adapter *adapter; uint32_t regval; int error; int usecs; int ticks; info = (struct em_int_delay_info *)arg1; usecs = info->value; error = sysctl_handle_int(oidp, &usecs, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (usecs < 0 || usecs > E1000_TICKS_TO_USECS(65535)) return (EINVAL); info->value = usecs; ticks = E1000_USECS_TO_TICKS(usecs); adapter = info->adapter; EM_LOCK(adapter); regval = E1000_READ_OFFSET(&adapter->hw, info->offset); regval = (regval & ~0xffff) | (ticks & 0xffff); /* Handle a few special cases. */ switch (info->offset) { case E1000_RDTR: case E1000_82542_RDTR: regval |= E1000_RDT_FPDB; break; case E1000_TIDV: case E1000_82542_TIDV: if (ticks == 0) { adapter->txd_cmd &= ~E1000_TXD_CMD_IDE; /* Don't write 0 into the TIDV register. */ regval++; } else adapter->txd_cmd |= E1000_TXD_CMD_IDE; break; } E1000_WRITE_OFFSET(&adapter->hw, info->offset, regval); EM_UNLOCK(adapter); return (0); } static void em_add_int_delay_sysctl(struct adapter *adapter, const char *name, const char *description, struct em_int_delay_info *info, int offset, int value) { info->adapter = adapter; info->offset = offset; info->value = value; SYSCTL_ADD_PROC(device_get_sysctl_ctx(adapter->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(adapter->dev)), OID_AUTO, name, CTLTYPE_INT|CTLFLAG_RW, info, 0, em_sysctl_int_delay, "I", description); } #ifndef DEVICE_POLLING static void em_add_int_process_limit(struct adapter *adapter, const char *name, const char *description, int *limit, int value) { *limit = value; SYSCTL_ADD_INT(device_get_sysctl_ctx(adapter->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(adapter->dev)), OID_AUTO, name, CTLTYPE_INT|CTLFLAG_RW, limit, value, description); } #endif Index: head/sys/dev/ixgb/if_ixgb.c =================================================================== --- head/sys/dev/ixgb/if_ixgb.c (revision 162374) +++ head/sys/dev/ixgb/if_ixgb.c (revision 162375) @@ -1,2517 +1,2526 @@ /******************************************************************************* Copyright (c) 2001-2004, Intel Corporation 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. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the Intel Corporation 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. ***************************************************************************/ /*$FreeBSD$*/ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_device_polling.h" #endif #include /********************************************************************* * Set this to one to display debug statistics *********************************************************************/ int ixgb_display_debug_stats = 0; /********************************************************************* * Linked list of board private structures for all NICs found *********************************************************************/ struct adapter *ixgb_adapter_list = NULL; /********************************************************************* * Driver version *********************************************************************/ char ixgb_driver_version[] = "1.0.6"; char ixgb_copyright[] = "Copyright (c) 2001-2004 Intel Corporation."; /********************************************************************* * PCI Device ID Table * * Used by probe to select devices to load on * Last field stores an index into ixgb_strings * Last entry must be all 0s * * { Vendor ID, Device ID, SubVendor ID, SubDevice ID, String Index } *********************************************************************/ static ixgb_vendor_info_t ixgb_vendor_info_array[] = { /* Intel(R) PRO/10000 Network Connection */ {INTEL_VENDOR_ID, IXGB_DEVICE_ID_82597EX, PCI_ANY_ID, PCI_ANY_ID, 0}, {INTEL_VENDOR_ID, IXGB_DEVICE_ID_82597EX_SR, PCI_ANY_ID, PCI_ANY_ID, 0}, /* required last entry */ {0, 0, 0, 0, 0} }; /********************************************************************* * Table of branding strings for all supported NICs. *********************************************************************/ static char *ixgb_strings[] = { "Intel(R) PRO/10GbE Network Driver" }; /********************************************************************* * Function prototypes *********************************************************************/ static int ixgb_probe(device_t); static int ixgb_attach(device_t); static int ixgb_detach(device_t); static int ixgb_shutdown(device_t); static void ixgb_intr(void *); static void ixgb_start(struct ifnet *); static void ixgb_start_locked(struct ifnet *); static int ixgb_ioctl(struct ifnet *, IOCTL_CMD_TYPE, caddr_t); static void ixgb_watchdog(struct ifnet *); static void ixgb_init(void *); static void ixgb_init_locked(struct adapter *); static void ixgb_stop(void *); static void ixgb_media_status(struct ifnet *, struct ifmediareq *); static int ixgb_media_change(struct ifnet *); static void ixgb_identify_hardware(struct adapter *); static int ixgb_allocate_pci_resources(struct adapter *); static void ixgb_free_pci_resources(struct adapter *); static void ixgb_local_timer(void *); static int ixgb_hardware_init(struct adapter *); static void ixgb_setup_interface(device_t, struct adapter *); static int ixgb_setup_transmit_structures(struct adapter *); static void ixgb_initialize_transmit_unit(struct adapter *); static int ixgb_setup_receive_structures(struct adapter *); static void ixgb_initialize_receive_unit(struct adapter *); static void ixgb_enable_intr(struct adapter *); static void ixgb_disable_intr(struct adapter *); static void ixgb_free_transmit_structures(struct adapter *); static void ixgb_free_receive_structures(struct adapter *); static void ixgb_update_stats_counters(struct adapter *); static void ixgb_clean_transmit_interrupts(struct adapter *); static int ixgb_allocate_receive_structures(struct adapter *); static int ixgb_allocate_transmit_structures(struct adapter *); static void ixgb_process_receive_interrupts(struct adapter *, int); static void ixgb_receive_checksum(struct adapter *, struct ixgb_rx_desc * rx_desc, struct mbuf *); static void ixgb_transmit_checksum_setup(struct adapter *, struct mbuf *, u_int8_t *); static void ixgb_set_promisc(struct adapter *); static void ixgb_disable_promisc(struct adapter *); static void ixgb_set_multi(struct adapter *); static void ixgb_print_hw_stats(struct adapter *); static void ixgb_print_link_status(struct adapter *); static int ixgb_get_buf(int i, struct adapter *, struct mbuf *); static void ixgb_enable_vlans(struct adapter * adapter); static int ixgb_encap(struct adapter * adapter, struct mbuf * m_head); static int ixgb_sysctl_stats(SYSCTL_HANDLER_ARGS); static int ixgb_dma_malloc(struct adapter *, bus_size_t, struct ixgb_dma_alloc *, int); static void ixgb_dma_free(struct adapter *, struct ixgb_dma_alloc *); #ifdef DEVICE_POLLING static poll_handler_t ixgb_poll; #endif /********************************************************************* * FreeBSD Device Interface Entry Points *********************************************************************/ static device_method_t ixgb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ixgb_probe), DEVMETHOD(device_attach, ixgb_attach), DEVMETHOD(device_detach, ixgb_detach), DEVMETHOD(device_shutdown, ixgb_shutdown), {0, 0} }; static driver_t ixgb_driver = { "ixgb", ixgb_methods, sizeof(struct adapter), }; static devclass_t ixgb_devclass; DRIVER_MODULE(if_ixgb, pci, ixgb_driver, ixgb_devclass, 0, 0); MODULE_DEPEND(if_ixgb, pci, 1, 1, 1); MODULE_DEPEND(if_ixgb, ether, 1, 1, 1); /* some defines for controlling descriptor fetches in h/w */ #define RXDCTL_PTHRESH_DEFAULT 128 /* chip considers prefech below this */ #define RXDCTL_HTHRESH_DEFAULT 16 /* chip will only prefetch if tail is * pushed this many descriptors from * head */ #define RXDCTL_WTHRESH_DEFAULT 0 /* chip writes back at this many or RXT0 */ /********************************************************************* * Device identification routine * * ixgb_probe determines if the driver should be loaded on * adapter based on PCI vendor/device id of the adapter. * * return 0 on success, positive on failure *********************************************************************/ static int ixgb_probe(device_t dev) { ixgb_vendor_info_t *ent; u_int16_t pci_vendor_id = 0; u_int16_t pci_device_id = 0; u_int16_t pci_subvendor_id = 0; u_int16_t pci_subdevice_id = 0; char adapter_name[60]; INIT_DEBUGOUT("ixgb_probe: begin"); pci_vendor_id = pci_get_vendor(dev); if (pci_vendor_id != IXGB_VENDOR_ID) return (ENXIO); pci_device_id = pci_get_device(dev); pci_subvendor_id = pci_get_subvendor(dev); pci_subdevice_id = pci_get_subdevice(dev); ent = ixgb_vendor_info_array; while (ent->vendor_id != 0) { if ((pci_vendor_id == ent->vendor_id) && (pci_device_id == ent->device_id) && ((pci_subvendor_id == ent->subvendor_id) || (ent->subvendor_id == PCI_ANY_ID)) && ((pci_subdevice_id == ent->subdevice_id) || (ent->subdevice_id == PCI_ANY_ID))) { sprintf(adapter_name, "%s, Version - %s", ixgb_strings[ent->index], ixgb_driver_version); device_set_desc_copy(dev, adapter_name); return (BUS_PROBE_DEFAULT); } ent++; } return (ENXIO); } /********************************************************************* * Device initialization routine * * The attach entry point is called when the driver is being loaded. * This routine identifies the type of hardware, allocates all resources * and initializes the hardware. * * return 0 on success, positive on failure *********************************************************************/ static int ixgb_attach(device_t dev) { struct adapter *adapter; int tsize, rsize; int error = 0; printf("ixgb%d: %s\n", device_get_unit(dev), ixgb_copyright); INIT_DEBUGOUT("ixgb_attach: begin"); /* Allocate, clear, and link in our adapter structure */ if (!(adapter = device_get_softc(dev))) { printf("ixgb: adapter structure allocation failed\n"); return (ENOMEM); } bzero(adapter, sizeof(struct adapter)); adapter->dev = dev; adapter->osdep.dev = dev; adapter->unit = device_get_unit(dev); IXGB_LOCK_INIT(adapter, device_get_nameunit(dev)); if (ixgb_adapter_list != NULL) ixgb_adapter_list->prev = adapter; adapter->next = ixgb_adapter_list; ixgb_adapter_list = adapter; /* SYSCTL APIs */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "stats", CTLTYPE_INT | CTLFLAG_RW, (void *)adapter, 0, ixgb_sysctl_stats, "I", "Statistics"); callout_init(&adapter->timer, CALLOUT_MPSAFE); /* Determine hardware revision */ ixgb_identify_hardware(adapter); /* Parameters (to be read from user) */ adapter->num_tx_desc = IXGB_MAX_TXD; adapter->num_rx_desc = IXGB_MAX_RXD; adapter->tx_int_delay = TIDV; adapter->rx_int_delay = RDTR; adapter->rx_buffer_len = IXGB_RXBUFFER_2048; adapter->hw.fc.high_water = FCRTH; adapter->hw.fc.low_water = FCRTL; adapter->hw.fc.pause_time = FCPAUSE; adapter->hw.fc.send_xon = TRUE; adapter->hw.fc.type = FLOW_CONTROL; /* Set the max frame size assuming standard ethernet sized frames */ adapter->hw.max_frame_size = ETHERMTU + ETHER_HDR_LEN + ETHER_CRC_LEN; if (ixgb_allocate_pci_resources(adapter)) { printf("ixgb%d: Allocation of PCI resources failed\n", adapter->unit); error = ENXIO; goto err_pci; } tsize = IXGB_ROUNDUP(adapter->num_tx_desc * sizeof(struct ixgb_tx_desc), 4096); /* Allocate Transmit Descriptor ring */ if (ixgb_dma_malloc(adapter, tsize, &adapter->txdma, BUS_DMA_NOWAIT)) { printf("ixgb%d: Unable to allocate TxDescriptor memory\n", adapter->unit); error = ENOMEM; goto err_tx_desc; } adapter->tx_desc_base = (struct ixgb_tx_desc *) adapter->txdma.dma_vaddr; rsize = IXGB_ROUNDUP(adapter->num_rx_desc * sizeof(struct ixgb_rx_desc), 4096); /* Allocate Receive Descriptor ring */ if (ixgb_dma_malloc(adapter, rsize, &adapter->rxdma, BUS_DMA_NOWAIT)) { printf("ixgb%d: Unable to allocate rx_desc memory\n", adapter->unit); error = ENOMEM; goto err_rx_desc; } adapter->rx_desc_base = (struct ixgb_rx_desc *) adapter->rxdma.dma_vaddr; /* Initialize the hardware */ if (ixgb_hardware_init(adapter)) { printf("ixgb%d: Unable to initialize the hardware\n", adapter->unit); error = EIO; goto err_hw_init; } /* Setup OS specific network interface */ ixgb_setup_interface(dev, adapter); /* Initialize statistics */ ixgb_clear_hw_cntrs(&adapter->hw); ixgb_update_stats_counters(adapter); INIT_DEBUGOUT("ixgb_attach: end"); return (0); err_hw_init: ixgb_dma_free(adapter, &adapter->rxdma); err_rx_desc: ixgb_dma_free(adapter, &adapter->txdma); err_tx_desc: err_pci: ixgb_free_pci_resources(adapter); sysctl_ctx_free(&adapter->sysctl_ctx); return (error); } /********************************************************************* * Device removal routine * * The detach entry point is called when the driver is being removed. * This routine stops the adapter and deallocates all the resources * that were allocated for driver operation. * * return 0 on success, positive on failure *********************************************************************/ static int ixgb_detach(device_t dev) { struct adapter *adapter = device_get_softc(dev); struct ifnet *ifp = adapter->ifp; INIT_DEBUGOUT("ixgb_detach: begin"); #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) ether_poll_deregister(ifp); #endif IXGB_LOCK(adapter); adapter->in_detach = 1; ixgb_stop(adapter); IXGB_UNLOCK(adapter); #if __FreeBSD_version < 500000 ether_ifdetach(adapter->ifp, ETHER_BPF_SUPPORTED); #else ether_ifdetach(adapter->ifp); #endif ixgb_free_pci_resources(adapter); #if __FreeBSD_version >= 500000 if_free(adapter->ifp); #endif /* Free Transmit Descriptor ring */ if (adapter->tx_desc_base) { ixgb_dma_free(adapter, &adapter->txdma); adapter->tx_desc_base = NULL; } /* Free Receive Descriptor ring */ if (adapter->rx_desc_base) { ixgb_dma_free(adapter, &adapter->rxdma); adapter->rx_desc_base = NULL; } /* Remove from the adapter list */ if (ixgb_adapter_list == adapter) ixgb_adapter_list = adapter->next; if (adapter->next != NULL) adapter->next->prev = adapter->prev; if (adapter->prev != NULL) adapter->prev->next = adapter->next; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); ifp->if_timer = 0; IXGB_LOCK_DESTROY(adapter); return (0); } /********************************************************************* * * Shutdown entry point * **********************************************************************/ static int ixgb_shutdown(device_t dev) { struct adapter *adapter = device_get_softc(dev); IXGB_LOCK(adapter); ixgb_stop(adapter); IXGB_UNLOCK(adapter); return (0); } /********************************************************************* * Transmit entry point * * ixgb_start is called by the stack to initiate a transmit. * The driver will remain in this routine as long as there are * packets to transmit and transmit resources are available. * In case resources are not available stack is notified and * the packet is requeued. **********************************************************************/ static void ixgb_start_locked(struct ifnet * ifp) { struct mbuf *m_head; struct adapter *adapter = ifp->if_softc; IXGB_LOCK_ASSERT(adapter); if (!adapter->link_active) return; while (ifp->if_snd.ifq_head != NULL) { IF_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; if (ixgb_encap(adapter, m_head)) { ifp->if_drv_flags |= IFF_DRV_OACTIVE; IF_PREPEND(&ifp->if_snd, m_head); break; } /* Send a copy of the frame to the BPF listener */ #if __FreeBSD_version < 500000 if (ifp->if_bpf) bpf_mtap(ifp, m_head); #else BPF_MTAP(ifp, m_head); #endif /* Set timeout in case hardware has problems transmitting */ ifp->if_timer = IXGB_TX_TIMEOUT; } return; } static void ixgb_start(struct ifnet *ifp) { struct adapter *adapter = ifp->if_softc; IXGB_LOCK(adapter); ixgb_start_locked(ifp); IXGB_UNLOCK(adapter); return; } /********************************************************************* * Ioctl entry point * * ixgb_ioctl is called when the user wants to configure the * interface. * * return 0 on success, positive on failure **********************************************************************/ static int ixgb_ioctl(struct ifnet * ifp, IOCTL_CMD_TYPE command, caddr_t data) { int mask, error = 0; struct ifreq *ifr = (struct ifreq *) data; struct adapter *adapter = ifp->if_softc; if (adapter->in_detach) goto out; switch (command) { case SIOCSIFADDR: case SIOCGIFADDR: IOCTL_DEBUGOUT("ioctl rcv'd: SIOCxIFADDR (Get/Set Interface Addr)"); ether_ioctl(ifp, command, data); break; case SIOCSIFMTU: IOCTL_DEBUGOUT("ioctl rcv'd: SIOCSIFMTU (Set Interface MTU)"); if (ifr->ifr_mtu > IXGB_MAX_JUMBO_FRAME_SIZE - ETHER_HDR_LEN) { error = EINVAL; } else { IXGB_LOCK(adapter); ifp->if_mtu = ifr->ifr_mtu; adapter->hw.max_frame_size = ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN; ixgb_init_locked(adapter); IXGB_UNLOCK(adapter); } break; case SIOCSIFFLAGS: IOCTL_DEBUGOUT("ioctl rcv'd: SIOCSIFFLAGS (Set Interface Flags)"); IXGB_LOCK(adapter); if (ifp->if_flags & IFF_UP) { if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { ixgb_init_locked(adapter); } ixgb_disable_promisc(adapter); ixgb_set_promisc(adapter); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { ixgb_stop(adapter); } } IXGB_UNLOCK(adapter); break; case SIOCADDMULTI: case SIOCDELMULTI: IOCTL_DEBUGOUT("ioctl rcv'd: SIOC(ADD|DEL)MULTI"); if (ifp->if_drv_flags & IFF_DRV_RUNNING) { IXGB_LOCK(adapter); ixgb_disable_intr(adapter); ixgb_set_multi(adapter); ixgb_enable_intr(adapter); IXGB_UNLOCK(adapter); } break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: IOCTL_DEBUGOUT("ioctl rcv'd: SIOCxIFMEDIA (Get/Set Interface Media)"); error = ifmedia_ioctl(ifp, ifr, &adapter->media, command); break; case SIOCSIFCAP: IOCTL_DEBUGOUT("ioctl rcv'd: SIOCSIFCAP (Set Capabilities)"); mask = ifr->ifr_reqcap ^ ifp->if_capenable; #ifdef DEVICE_POLLING if (mask & IFCAP_POLLING) { if (ifr->ifr_reqcap & IFCAP_POLLING) { error = ether_poll_register(ixgb_poll, ifp); if (error) return(error); IXGB_LOCK(adapter); ixgb_disable_intr(adapter); ifp->if_capenable |= IFCAP_POLLING; IXGB_UNLOCK(adapter); } else { error = ether_poll_deregister(ifp); /* Enable interrupt even in error case */ IXGB_LOCK(adapter); ixgb_enable_intr(adapter); ifp->if_capenable &= ~IFCAP_POLLING; IXGB_UNLOCK(adapter); } } #endif /* DEVICE_POLLING */ if (mask & IFCAP_HWCSUM) { if (IFCAP_HWCSUM & ifp->if_capenable) ifp->if_capenable &= ~IFCAP_HWCSUM; else ifp->if_capenable |= IFCAP_HWCSUM; if (ifp->if_drv_flags & IFF_DRV_RUNNING) ixgb_init(adapter); } break; default: IOCTL_DEBUGOUT1("ioctl received: UNKNOWN (0x%X)\n", (int)command); error = EINVAL; } out: return (error); } /********************************************************************* * Watchdog entry point * * This routine is called whenever hardware quits transmitting. * **********************************************************************/ static void ixgb_watchdog(struct ifnet * ifp) { struct adapter *adapter; adapter = ifp->if_softc; /* * If we are in this routine because of pause frames, then don't * reset the hardware. */ if (IXGB_READ_REG(&adapter->hw, STATUS) & IXGB_STATUS_TXOFF) { ifp->if_timer = IXGB_TX_TIMEOUT; return; } printf("ixgb%d: watchdog timeout -- resetting\n", adapter->unit); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; ixgb_stop(adapter); ixgb_init(adapter); ifp->if_oerrors++; return; } /********************************************************************* * Init entry point * * This routine is used in two ways. It is used by the stack as * init entry point in network interface structure. It is also used * by the driver as a hw/sw initialization routine to get to a * consistent state. * * return 0 on success, positive on failure **********************************************************************/ static void ixgb_init_locked(struct adapter *adapter) { struct ifnet *ifp; INIT_DEBUGOUT("ixgb_init: begin"); IXGB_LOCK_ASSERT(adapter); ixgb_stop(adapter); /* Get the latest mac address, User can use a LAA */ bcopy(IF_LLADDR(adapter->ifp), adapter->hw.curr_mac_addr, IXGB_ETH_LENGTH_OF_ADDRESS); /* Initialize the hardware */ if (ixgb_hardware_init(adapter)) { printf("ixgb%d: Unable to initialize the hardware\n", adapter->unit); return; } ixgb_enable_vlans(adapter); /* Prepare transmit descriptors and buffers */ if (ixgb_setup_transmit_structures(adapter)) { printf("ixgb%d: Could not setup transmit structures\n", adapter->unit); ixgb_stop(adapter); return; } ixgb_initialize_transmit_unit(adapter); /* Setup Multicast table */ ixgb_set_multi(adapter); /* Prepare receive descriptors and buffers */ if (ixgb_setup_receive_structures(adapter)) { printf("ixgb%d: Could not setup receive structures\n", adapter->unit); ixgb_stop(adapter); return; } ixgb_initialize_receive_unit(adapter); /* Don't lose promiscuous settings */ ixgb_set_promisc(adapter); ifp = adapter->ifp; ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if (ifp->if_capenable & IFCAP_TXCSUM) ifp->if_hwassist = IXGB_CHECKSUM_FEATURES; else ifp->if_hwassist = 0; /* Enable jumbo frames */ if (ifp->if_mtu > ETHERMTU) { uint32_t temp_reg; IXGB_WRITE_REG(&adapter->hw, MFS, adapter->hw.max_frame_size << IXGB_MFS_SHIFT); temp_reg = IXGB_READ_REG(&adapter->hw, CTRL0); temp_reg |= IXGB_CTRL0_JFE; IXGB_WRITE_REG(&adapter->hw, CTRL0, temp_reg); } callout_reset(&adapter->timer, 2 * hz, ixgb_local_timer, adapter); ixgb_clear_hw_cntrs(&adapter->hw); #ifdef DEVICE_POLLING /* * Only disable interrupts if we are polling, make sure they are on * otherwise. */ if (ifp->if_capenable & IFCAP_POLLING) ixgb_disable_intr(adapter); else #endif ixgb_enable_intr(adapter); return; } static void ixgb_init(void *arg) { struct adapter *adapter = arg; IXGB_LOCK(adapter); ixgb_init_locked(adapter); IXGB_UNLOCK(adapter); return; } #ifdef DEVICE_POLLING static void ixgb_poll_locked(struct ifnet * ifp, enum poll_cmd cmd, int count) { struct adapter *adapter = ifp->if_softc; u_int32_t reg_icr; IXGB_LOCK_ASSERT(adapter); if (cmd == POLL_AND_CHECK_STATUS) { reg_icr = IXGB_READ_REG(&adapter->hw, ICR); if (reg_icr & (IXGB_INT_RXSEQ | IXGB_INT_LSC)) { callout_stop(&adapter->timer); ixgb_check_for_link(&adapter->hw); ixgb_print_link_status(adapter); callout_reset(&adapter->timer, 2 * hz, ixgb_local_timer, adapter); } } ixgb_process_receive_interrupts(adapter, count); ixgb_clean_transmit_interrupts(adapter); if (ifp->if_snd.ifq_head != NULL) ixgb_start_locked(ifp); } static void ixgb_poll(struct ifnet * ifp, enum poll_cmd cmd, int count) { struct adapter *adapter = ifp->if_softc; IXGB_LOCK(adapter); if (ifp->if_drv_flags & IFF_DRV_RUNNING) ixgb_poll_locked(ifp, cmd, count); IXGB_UNLOCK(adapter); } #endif /* DEVICE_POLLING */ /********************************************************************* * * Interrupt Service routine * **********************************************************************/ static void ixgb_intr(void *arg) { u_int32_t loop_cnt = IXGB_MAX_INTR; u_int32_t reg_icr; struct ifnet *ifp; struct adapter *adapter = arg; boolean_t rxdmt0 = FALSE; IXGB_LOCK(adapter); ifp = adapter->ifp; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { IXGB_UNLOCK(adapter); return; } #endif reg_icr = IXGB_READ_REG(&adapter->hw, ICR); if (reg_icr == 0) { IXGB_UNLOCK(adapter); return; } if (reg_icr & IXGB_INT_RXDMT0) rxdmt0 = TRUE; #ifdef _SV_ if (reg_icr & IXGB_INT_RXDMT0) adapter->sv_stats.icr_rxdmt0++; if (reg_icr & IXGB_INT_RXO) adapter->sv_stats.icr_rxo++; if (reg_icr & IXGB_INT_RXT0) adapter->sv_stats.icr_rxt0++; if (reg_icr & IXGB_INT_TXDW) adapter->sv_stats.icr_TXDW++; #endif /* _SV_ */ /* Link status change */ if (reg_icr & (IXGB_INT_RXSEQ | IXGB_INT_LSC)) { callout_stop(&adapter->timer); ixgb_check_for_link(&adapter->hw); ixgb_print_link_status(adapter); callout_reset(&adapter->timer, 2 * hz, ixgb_local_timer, adapter); } while (loop_cnt > 0) { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { ixgb_process_receive_interrupts(adapter, -1); ixgb_clean_transmit_interrupts(adapter); } loop_cnt--; } if (rxdmt0 && adapter->raidc) { IXGB_WRITE_REG(&adapter->hw, IMC, IXGB_INT_RXDMT0); IXGB_WRITE_REG(&adapter->hw, IMS, IXGB_INT_RXDMT0); } if (ifp->if_drv_flags & IFF_DRV_RUNNING && ifp->if_snd.ifq_head != NULL) ixgb_start_locked(ifp); IXGB_UNLOCK(adapter); return; } /********************************************************************* * * Media Ioctl callback * * This routine is called whenever the user queries the status of * the interface using ifconfig. * **********************************************************************/ static void ixgb_media_status(struct ifnet * ifp, struct ifmediareq * ifmr) { struct adapter *adapter = ifp->if_softc; INIT_DEBUGOUT("ixgb_media_status: begin"); ixgb_check_for_link(&adapter->hw); ixgb_print_link_status(adapter); ifmr->ifm_status = IFM_AVALID; ifmr->ifm_active = IFM_ETHER; if (!adapter->hw.link_up) return; ifmr->ifm_status |= IFM_ACTIVE; ifmr->ifm_active |= IFM_1000_SX | IFM_FDX; return; } /********************************************************************* * * Media Ioctl callback * * This routine is called when the user changes speed/duplex using * media/mediopt option with ifconfig. * **********************************************************************/ static int ixgb_media_change(struct ifnet * ifp) { struct adapter *adapter = ifp->if_softc; struct ifmedia *ifm = &adapter->media; INIT_DEBUGOUT("ixgb_media_change: begin"); if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER) return (EINVAL); return (0); } /********************************************************************* * * This routine maps the mbufs to tx descriptors. * * return 0 on success, positive on failure **********************************************************************/ static int ixgb_encap(struct adapter * adapter, struct mbuf * m_head) { u_int8_t txd_popts; int i, j, error, nsegs; #if __FreeBSD_version < 500000 struct ifvlan *ifv = NULL; -#else - struct m_tag *mtag; #endif bus_dma_segment_t segs[IXGB_MAX_SCATTER]; bus_dmamap_t map; struct ixgb_buffer *tx_buffer = NULL; struct ixgb_tx_desc *current_tx_desc = NULL; struct ifnet *ifp = adapter->ifp; /* * Force a cleanup if number of TX descriptors available hits the * threshold */ if (adapter->num_tx_desc_avail <= IXGB_TX_CLEANUP_THRESHOLD) { ixgb_clean_transmit_interrupts(adapter); } if (adapter->num_tx_desc_avail <= IXGB_TX_CLEANUP_THRESHOLD) { adapter->no_tx_desc_avail1++; return (ENOBUFS); } /* * Map the packet for DMA. */ if (bus_dmamap_create(adapter->txtag, BUS_DMA_NOWAIT, &map)) { adapter->no_tx_map_avail++; return (ENOMEM); } error = bus_dmamap_load_mbuf_sg(adapter->txtag, map, m_head, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { adapter->no_tx_dma_setup++; printf("ixgb%d: ixgb_encap: bus_dmamap_load_mbuf failed; " "error %u\n", adapter->unit, error); bus_dmamap_destroy(adapter->txtag, map); return (error); } KASSERT(nsegs != 0, ("ixgb_encap: empty packet")); if (nsegs > adapter->num_tx_desc_avail) { adapter->no_tx_desc_avail2++; bus_dmamap_destroy(adapter->txtag, map); return (ENOBUFS); } if (ifp->if_hwassist > 0) { ixgb_transmit_checksum_setup(adapter, m_head, &txd_popts); } else txd_popts = 0; /* Find out if we are in vlan mode */ #if __FreeBSD_version < 500000 if ((m_head->m_flags & (M_PROTO1 | M_PKTHDR)) == (M_PROTO1 | M_PKTHDR) && m_head->m_pkthdr.rcvif != NULL && m_head->m_pkthdr.rcvif->if_type == IFT_L2VLAN) ifv = m_head->m_pkthdr.rcvif->if_softc; -#else +#elseif __FreeBSD_version < 700000 mtag = VLAN_OUTPUT_TAG(ifp, m_head); #endif i = adapter->next_avail_tx_desc; for (j = 0; j < nsegs; j++) { tx_buffer = &adapter->tx_buffer_area[i]; current_tx_desc = &adapter->tx_desc_base[i]; current_tx_desc->buff_addr = htole64(segs[j].ds_addr); current_tx_desc->cmd_type_len = (adapter->txd_cmd | segs[j].ds_len); current_tx_desc->popts = txd_popts; if (++i == adapter->num_tx_desc) i = 0; tx_buffer->m_head = NULL; } adapter->num_tx_desc_avail -= nsegs; adapter->next_avail_tx_desc = i; #if __FreeBSD_version < 500000 if (ifv != NULL) { /* Set the vlan id */ current_tx_desc->vlan = ifv->ifv_tag; -#else +#elseif __FreeBSD_version < 700000 if (mtag != NULL) { /* Set the vlan id */ current_tx_desc->vlan = VLAN_TAG_VALUE(mtag); +#else + if (m_head->m_flags & M_VLANTAG) { + current_tx_desc->vlan = m_head->m_pkthdr.ether_vtag; #endif /* Tell hardware to add tag */ current_tx_desc->cmd_type_len |= IXGB_TX_DESC_CMD_VLE; } tx_buffer->m_head = m_head; tx_buffer->map = map; bus_dmamap_sync(adapter->txtag, map, BUS_DMASYNC_PREWRITE); /* * Last Descriptor of Packet needs End Of Packet (EOP) */ current_tx_desc->cmd_type_len |= (IXGB_TX_DESC_CMD_EOP); /* * Advance the Transmit Descriptor Tail (Tdt), this tells the E1000 * that this frame is available to transmit. */ IXGB_WRITE_REG(&adapter->hw, TDT, i); return (0); } static void ixgb_set_promisc(struct adapter * adapter) { u_int32_t reg_rctl; struct ifnet *ifp = adapter->ifp; reg_rctl = IXGB_READ_REG(&adapter->hw, RCTL); if (ifp->if_flags & IFF_PROMISC) { reg_rctl |= (IXGB_RCTL_UPE | IXGB_RCTL_MPE); IXGB_WRITE_REG(&adapter->hw, RCTL, reg_rctl); } else if (ifp->if_flags & IFF_ALLMULTI) { reg_rctl |= IXGB_RCTL_MPE; reg_rctl &= ~IXGB_RCTL_UPE; IXGB_WRITE_REG(&adapter->hw, RCTL, reg_rctl); } return; } static void ixgb_disable_promisc(struct adapter * adapter) { u_int32_t reg_rctl; reg_rctl = IXGB_READ_REG(&adapter->hw, RCTL); reg_rctl &= (~IXGB_RCTL_UPE); reg_rctl &= (~IXGB_RCTL_MPE); IXGB_WRITE_REG(&adapter->hw, RCTL, reg_rctl); return; } /********************************************************************* * Multicast Update * * This routine is called whenever multicast address list is updated. * **********************************************************************/ static void ixgb_set_multi(struct adapter * adapter) { u_int32_t reg_rctl = 0; u_int8_t mta[MAX_NUM_MULTICAST_ADDRESSES * IXGB_ETH_LENGTH_OF_ADDRESS]; struct ifmultiaddr *ifma; int mcnt = 0; struct ifnet *ifp = adapter->ifp; IOCTL_DEBUGOUT("ixgb_set_multi: begin"); IF_ADDR_LOCK(ifp); #if __FreeBSD_version < 500000 LIST_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { #else TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { #endif if (ifma->ifma_addr->sa_family != AF_LINK) continue; bcopy(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), &mta[mcnt * IXGB_ETH_LENGTH_OF_ADDRESS], IXGB_ETH_LENGTH_OF_ADDRESS); mcnt++; } IF_ADDR_UNLOCK(ifp); if (mcnt > MAX_NUM_MULTICAST_ADDRESSES) { reg_rctl = IXGB_READ_REG(&adapter->hw, RCTL); reg_rctl |= IXGB_RCTL_MPE; IXGB_WRITE_REG(&adapter->hw, RCTL, reg_rctl); } else ixgb_mc_addr_list_update(&adapter->hw, mta, mcnt, 0); return; } /********************************************************************* * Timer routine * * This routine checks for link status and updates statistics. * **********************************************************************/ static void ixgb_local_timer(void *arg) { struct ifnet *ifp; struct adapter *adapter = arg; ifp = adapter->ifp; IXGB_LOCK(adapter); ixgb_check_for_link(&adapter->hw); ixgb_print_link_status(adapter); ixgb_update_stats_counters(adapter); if (ixgb_display_debug_stats && ifp->if_drv_flags & IFF_DRV_RUNNING) { ixgb_print_hw_stats(adapter); } callout_reset(&adapter->timer, 2 * hz, ixgb_local_timer, adapter); IXGB_UNLOCK(adapter); return; } static void ixgb_print_link_status(struct adapter * adapter) { if (adapter->hw.link_up) { if (!adapter->link_active) { printf("ixgb%d: Link is up %d Mbps %s \n", adapter->unit, 10000, "Full Duplex"); adapter->link_active = 1; } } else { if (adapter->link_active) { printf("ixgb%d: Link is Down \n", adapter->unit); adapter->link_active = 0; } } return; } /********************************************************************* * * This routine disables all traffic on the adapter by issuing a * global reset on the MAC and deallocates TX/RX buffers. * **********************************************************************/ static void ixgb_stop(void *arg) { struct ifnet *ifp; struct adapter *adapter = arg; ifp = adapter->ifp; IXGB_LOCK_ASSERT(adapter); INIT_DEBUGOUT("ixgb_stop: begin\n"); ixgb_disable_intr(adapter); adapter->hw.adapter_stopped = FALSE; ixgb_adapter_stop(&adapter->hw); callout_stop(&adapter->timer); ixgb_free_transmit_structures(adapter); ixgb_free_receive_structures(adapter); /* Tell the stack that the interface is no longer active */ ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); return; } /********************************************************************* * * Determine hardware revision. * **********************************************************************/ static void ixgb_identify_hardware(struct adapter * adapter) { device_t dev = adapter->dev; /* Make sure our PCI config space has the necessary stuff set */ adapter->hw.pci_cmd_word = pci_read_config(dev, PCIR_COMMAND, 2); if (!((adapter->hw.pci_cmd_word & PCIM_CMD_BUSMASTEREN) && (adapter->hw.pci_cmd_word & PCIM_CMD_MEMEN))) { printf("ixgb%d: Memory Access and/or Bus Master bits were not set!\n", adapter->unit); adapter->hw.pci_cmd_word |= (PCIM_CMD_BUSMASTEREN | PCIM_CMD_MEMEN); pci_write_config(dev, PCIR_COMMAND, adapter->hw.pci_cmd_word, 2); } /* Save off the information about this board */ adapter->hw.vendor_id = pci_get_vendor(dev); adapter->hw.device_id = pci_get_device(dev); adapter->hw.revision_id = pci_read_config(dev, PCIR_REVID, 1); adapter->hw.subsystem_vendor_id = pci_read_config(dev, PCIR_SUBVEND_0, 2); adapter->hw.subsystem_id = pci_read_config(dev, PCIR_SUBDEV_0, 2); /* Set MacType, etc. based on this PCI info */ switch (adapter->hw.device_id) { case IXGB_DEVICE_ID_82597EX: case IXGB_DEVICE_ID_82597EX_SR: adapter->hw.mac_type = ixgb_82597; break; default: INIT_DEBUGOUT1("Unknown device if 0x%x", adapter->hw.device_id); printf("ixgb%d: unsupported device id 0x%x\n", adapter->unit, adapter->hw.device_id); } return; } static int ixgb_allocate_pci_resources(struct adapter * adapter) { int rid; device_t dev = adapter->dev; rid = IXGB_MMBA; adapter->res_memory = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, 0, ~0, 1, RF_ACTIVE); if (!(adapter->res_memory)) { printf("ixgb%d: Unable to allocate bus resource: memory\n", adapter->unit); return (ENXIO); } adapter->osdep.mem_bus_space_tag = rman_get_bustag(adapter->res_memory); adapter->osdep.mem_bus_space_handle = rman_get_bushandle(adapter->res_memory); adapter->hw.hw_addr = (uint8_t *) & adapter->osdep.mem_bus_space_handle; rid = 0x0; adapter->res_interrupt = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); if (!(adapter->res_interrupt)) { printf("ixgb%d: Unable to allocate bus resource: interrupt\n", adapter->unit); return (ENXIO); } if (bus_setup_intr(dev, adapter->res_interrupt, INTR_TYPE_NET | INTR_MPSAFE, (void (*) (void *))ixgb_intr, adapter, &adapter->int_handler_tag)) { printf("ixgb%d: Error registering interrupt handler!\n", adapter->unit); return (ENXIO); } adapter->hw.back = &adapter->osdep; return (0); } static void ixgb_free_pci_resources(struct adapter * adapter) { device_t dev = adapter->dev; if (adapter->res_interrupt != NULL) { bus_teardown_intr(dev, adapter->res_interrupt, adapter->int_handler_tag); bus_release_resource(dev, SYS_RES_IRQ, 0, adapter->res_interrupt); } if (adapter->res_memory != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, IXGB_MMBA, adapter->res_memory); } if (adapter->res_ioport != NULL) { bus_release_resource(dev, SYS_RES_IOPORT, adapter->io_rid, adapter->res_ioport); } return; } /********************************************************************* * * Initialize the hardware to a configuration as specified by the * adapter structure. The controller is reset, the EEPROM is * verified, the MAC address is set, then the shared initialization * routines are called. * **********************************************************************/ static int ixgb_hardware_init(struct adapter * adapter) { /* Issue a global reset */ adapter->hw.adapter_stopped = FALSE; ixgb_adapter_stop(&adapter->hw); /* Make sure we have a good EEPROM before we read from it */ if (!ixgb_validate_eeprom_checksum(&adapter->hw)) { printf("ixgb%d: The EEPROM Checksum Is Not Valid\n", adapter->unit); return (EIO); } if (!ixgb_init_hw(&adapter->hw)) { printf("ixgb%d: Hardware Initialization Failed", adapter->unit); return (EIO); } return (0); } /********************************************************************* * * Setup networking device structure and register an interface. * **********************************************************************/ static void ixgb_setup_interface(device_t dev, struct adapter * adapter) { struct ifnet *ifp; INIT_DEBUGOUT("ixgb_setup_interface: begin"); ifp = adapter->ifp = if_alloc(IFT_ETHER); if (ifp == NULL) panic("%s: can not if_alloc()\n", device_get_nameunit(dev)); #if __FreeBSD_version >= 502000 if_initname(ifp, device_get_name(dev), device_get_unit(dev)); #else ifp->if_unit = adapter->unit; ifp->if_name = "ixgb"; #endif ifp->if_mtu = ETHERMTU; ifp->if_baudrate = 1000000000; ifp->if_init = ixgb_init; ifp->if_softc = adapter; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = ixgb_ioctl; ifp->if_start = ixgb_start; ifp->if_watchdog = ixgb_watchdog; ifp->if_snd.ifq_maxlen = adapter->num_tx_desc - 1; #if __FreeBSD_version < 500000 ether_ifattach(ifp, ETHER_BPF_SUPPORTED); #else ether_ifattach(ifp, adapter->hw.curr_mac_addr); #endif ifp->if_capabilities = IFCAP_HWCSUM; /* * Tell the upper layer(s) we support long frames. */ ifp->if_data.ifi_hdrlen = sizeof(struct ether_vlan_header); #if __FreeBSD_version >= 500000 ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_MTU; #endif ifp->if_capenable = ifp->if_capabilities; #ifdef DEVICE_POLLING ifp->if_capabilities |= IFCAP_POLLING; #endif /* * Specify the media types supported by this adapter and register * callbacks to update media and link information */ ifmedia_init(&adapter->media, IFM_IMASK, ixgb_media_change, ixgb_media_status); ifmedia_add(&adapter->media, IFM_ETHER | IFM_1000_SX | IFM_FDX, 0, NULL); ifmedia_add(&adapter->media, IFM_ETHER | IFM_1000_SX, 0, NULL); ifmedia_add(&adapter->media, IFM_ETHER | IFM_AUTO, 0, NULL); ifmedia_set(&adapter->media, IFM_ETHER | IFM_AUTO); return; } /******************************************************************** * Manage DMA'able memory. *******************************************************************/ static void ixgb_dmamap_cb(void *arg, bus_dma_segment_t * segs, int nseg, int error) { if (error) return; *(bus_addr_t *) arg = segs->ds_addr; return; } static int ixgb_dma_malloc(struct adapter * adapter, bus_size_t size, struct ixgb_dma_alloc * dma, int mapflags) { int r; r = bus_dma_tag_create(NULL, /* parent */ PAGE_SIZE, 0, /* alignment, bounds */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ size, /* maxsize */ 1, /* nsegments */ size, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ #if __FreeBSD_version >= 502000 NULL, /* lockfunc */ NULL, /* lockfuncarg */ #endif &dma->dma_tag); if (r != 0) { printf("ixgb%d: ixgb_dma_malloc: bus_dma_tag_create failed; " "error %u\n", adapter->unit, r); goto fail_0; } r = bus_dmamem_alloc(dma->dma_tag, (void **)&dma->dma_vaddr, BUS_DMA_NOWAIT, &dma->dma_map); if (r != 0) { printf("ixgb%d: ixgb_dma_malloc: bus_dmamem_alloc failed; " "error %u\n", adapter->unit, r); goto fail_1; } r = bus_dmamap_load(dma->dma_tag, dma->dma_map, dma->dma_vaddr, size, ixgb_dmamap_cb, &dma->dma_paddr, mapflags | BUS_DMA_NOWAIT); if (r != 0) { printf("ixgb%d: ixgb_dma_malloc: bus_dmamap_load failed; " "error %u\n", adapter->unit, r); goto fail_2; } dma->dma_size = size; return (0); fail_2: bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map); fail_1: bus_dma_tag_destroy(dma->dma_tag); fail_0: dma->dma_map = NULL; dma->dma_tag = NULL; return (r); } static void ixgb_dma_free(struct adapter * adapter, struct ixgb_dma_alloc * dma) { bus_dmamap_unload(dma->dma_tag, dma->dma_map); bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map); bus_dma_tag_destroy(dma->dma_tag); } /********************************************************************* * * Allocate memory for tx_buffer structures. The tx_buffer stores all * the information needed to transmit a packet on the wire. * **********************************************************************/ static int ixgb_allocate_transmit_structures(struct adapter * adapter) { if (!(adapter->tx_buffer_area = (struct ixgb_buffer *) malloc(sizeof(struct ixgb_buffer) * adapter->num_tx_desc, M_DEVBUF, M_NOWAIT | M_ZERO))) { printf("ixgb%d: Unable to allocate tx_buffer memory\n", adapter->unit); return ENOMEM; } bzero(adapter->tx_buffer_area, sizeof(struct ixgb_buffer) * adapter->num_tx_desc); return 0; } /********************************************************************* * * Allocate and initialize transmit structures. * **********************************************************************/ static int ixgb_setup_transmit_structures(struct adapter * adapter) { /* * Setup DMA descriptor areas. */ if (bus_dma_tag_create(NULL, /* parent */ PAGE_SIZE, 0, /* alignment, bounds */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES * IXGB_MAX_SCATTER, /* maxsize */ IXGB_MAX_SCATTER, /* nsegments */ MCLBYTES, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ #if __FreeBSD_version >= 502000 NULL, /* lockfunc */ NULL, /* lockfuncarg */ #endif &adapter->txtag)) { printf("ixgb%d: Unable to allocate TX DMA tag\n", adapter->unit); return (ENOMEM); } if (ixgb_allocate_transmit_structures(adapter)) return ENOMEM; bzero((void *)adapter->tx_desc_base, (sizeof(struct ixgb_tx_desc)) * adapter->num_tx_desc); adapter->next_avail_tx_desc = 0; adapter->oldest_used_tx_desc = 0; /* Set number of descriptors available */ adapter->num_tx_desc_avail = adapter->num_tx_desc; /* Set checksum context */ adapter->active_checksum_context = OFFLOAD_NONE; return 0; } /********************************************************************* * * Enable transmit unit. * **********************************************************************/ static void ixgb_initialize_transmit_unit(struct adapter * adapter) { u_int32_t reg_tctl; u_int64_t tdba = adapter->txdma.dma_paddr; /* Setup the Base and Length of the Tx Descriptor Ring */ IXGB_WRITE_REG(&adapter->hw, TDBAL, (tdba & 0x00000000ffffffffULL)); IXGB_WRITE_REG(&adapter->hw, TDBAH, (tdba >> 32)); IXGB_WRITE_REG(&adapter->hw, TDLEN, adapter->num_tx_desc * sizeof(struct ixgb_tx_desc)); /* Setup the HW Tx Head and Tail descriptor pointers */ IXGB_WRITE_REG(&adapter->hw, TDH, 0); IXGB_WRITE_REG(&adapter->hw, TDT, 0); HW_DEBUGOUT2("Base = %x, Length = %x\n", IXGB_READ_REG(&adapter->hw, TDBAL), IXGB_READ_REG(&adapter->hw, TDLEN)); IXGB_WRITE_REG(&adapter->hw, TIDV, adapter->tx_int_delay); /* Program the Transmit Control Register */ reg_tctl = IXGB_READ_REG(&adapter->hw, TCTL); reg_tctl = IXGB_TCTL_TCE | IXGB_TCTL_TXEN | IXGB_TCTL_TPDE; IXGB_WRITE_REG(&adapter->hw, TCTL, reg_tctl); /* Setup Transmit Descriptor Settings for this adapter */ adapter->txd_cmd = IXGB_TX_DESC_TYPE | IXGB_TX_DESC_CMD_RS; if (adapter->tx_int_delay > 0) adapter->txd_cmd |= IXGB_TX_DESC_CMD_IDE; return; } /********************************************************************* * * Free all transmit related data structures. * **********************************************************************/ static void ixgb_free_transmit_structures(struct adapter * adapter) { struct ixgb_buffer *tx_buffer; int i; INIT_DEBUGOUT("free_transmit_structures: begin"); if (adapter->tx_buffer_area != NULL) { tx_buffer = adapter->tx_buffer_area; for (i = 0; i < adapter->num_tx_desc; i++, tx_buffer++) { if (tx_buffer->m_head != NULL) { bus_dmamap_unload(adapter->txtag, tx_buffer->map); bus_dmamap_destroy(adapter->txtag, tx_buffer->map); m_freem(tx_buffer->m_head); } tx_buffer->m_head = NULL; } } if (adapter->tx_buffer_area != NULL) { free(adapter->tx_buffer_area, M_DEVBUF); adapter->tx_buffer_area = NULL; } if (adapter->txtag != NULL) { bus_dma_tag_destroy(adapter->txtag); adapter->txtag = NULL; } return; } /********************************************************************* * * The offload context needs to be set when we transfer the first * packet of a particular protocol (TCP/UDP). We change the * context only if the protocol type changes. * **********************************************************************/ static void ixgb_transmit_checksum_setup(struct adapter * adapter, struct mbuf * mp, u_int8_t * txd_popts) { struct ixgb_context_desc *TXD; struct ixgb_buffer *tx_buffer; int curr_txd; if (mp->m_pkthdr.csum_flags) { if (mp->m_pkthdr.csum_flags & CSUM_TCP) { *txd_popts = IXGB_TX_DESC_POPTS_TXSM; if (adapter->active_checksum_context == OFFLOAD_TCP_IP) return; else adapter->active_checksum_context = OFFLOAD_TCP_IP; } else if (mp->m_pkthdr.csum_flags & CSUM_UDP) { *txd_popts = IXGB_TX_DESC_POPTS_TXSM; if (adapter->active_checksum_context == OFFLOAD_UDP_IP) return; else adapter->active_checksum_context = OFFLOAD_UDP_IP; } else { *txd_popts = 0; return; } } else { *txd_popts = 0; return; } /* * If we reach this point, the checksum offload context needs to be * reset. */ curr_txd = adapter->next_avail_tx_desc; tx_buffer = &adapter->tx_buffer_area[curr_txd]; TXD = (struct ixgb_context_desc *) & adapter->tx_desc_base[curr_txd]; TXD->tucss = ENET_HEADER_SIZE + sizeof(struct ip); TXD->tucse = 0; TXD->mss = 0; if (adapter->active_checksum_context == OFFLOAD_TCP_IP) { TXD->tucso = ENET_HEADER_SIZE + sizeof(struct ip) + offsetof(struct tcphdr, th_sum); } else if (adapter->active_checksum_context == OFFLOAD_UDP_IP) { TXD->tucso = ENET_HEADER_SIZE + sizeof(struct ip) + offsetof(struct udphdr, uh_sum); } TXD->cmd_type_len = IXGB_CONTEXT_DESC_CMD_TCP | IXGB_TX_DESC_CMD_RS | IXGB_CONTEXT_DESC_CMD_IDE; tx_buffer->m_head = NULL; if (++curr_txd == adapter->num_tx_desc) curr_txd = 0; adapter->num_tx_desc_avail--; adapter->next_avail_tx_desc = curr_txd; return; } /********************************************************************** * * Examine each tx_buffer in the used queue. If the hardware is done * processing the packet then free associated resources. The * tx_buffer is put back on the free queue. * **********************************************************************/ static void ixgb_clean_transmit_interrupts(struct adapter * adapter) { int i, num_avail; struct ixgb_buffer *tx_buffer; struct ixgb_tx_desc *tx_desc; IXGB_LOCK_ASSERT(adapter); if (adapter->num_tx_desc_avail == adapter->num_tx_desc) return; #ifdef _SV_ adapter->clean_tx_interrupts++; #endif num_avail = adapter->num_tx_desc_avail; i = adapter->oldest_used_tx_desc; tx_buffer = &adapter->tx_buffer_area[i]; tx_desc = &adapter->tx_desc_base[i]; while (tx_desc->status & IXGB_TX_DESC_STATUS_DD) { tx_desc->status = 0; num_avail++; if (tx_buffer->m_head) { bus_dmamap_sync(adapter->txtag, tx_buffer->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(adapter->txtag, tx_buffer->map); bus_dmamap_destroy(adapter->txtag, tx_buffer->map); m_freem(tx_buffer->m_head); tx_buffer->m_head = NULL; } if (++i == adapter->num_tx_desc) i = 0; tx_buffer = &adapter->tx_buffer_area[i]; tx_desc = &adapter->tx_desc_base[i]; } adapter->oldest_used_tx_desc = i; /* * If we have enough room, clear IFF_DRV_OACTIVE to tell the stack that * it is OK to send packets. If there are no pending descriptors, * clear the timeout. Otherwise, if some descriptors have been freed, * restart the timeout. */ if (num_avail > IXGB_TX_CLEANUP_THRESHOLD) { struct ifnet *ifp = adapter->ifp; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if (num_avail == adapter->num_tx_desc) ifp->if_timer = 0; else if (num_avail == adapter->num_tx_desc_avail) ifp->if_timer = IXGB_TX_TIMEOUT; } adapter->num_tx_desc_avail = num_avail; return; } /********************************************************************* * * Get a buffer from system mbuf buffer pool. * **********************************************************************/ static int ixgb_get_buf(int i, struct adapter * adapter, struct mbuf * nmp) { register struct mbuf *mp = nmp; struct ixgb_buffer *rx_buffer; struct ifnet *ifp; bus_addr_t paddr; int error; ifp = adapter->ifp; if (mp == NULL) { mp = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (mp == NULL) { adapter->mbuf_alloc_failed++; return (ENOBUFS); } mp->m_len = mp->m_pkthdr.len = MCLBYTES; } else { mp->m_len = mp->m_pkthdr.len = MCLBYTES; mp->m_data = mp->m_ext.ext_buf; mp->m_next = NULL; } if (ifp->if_mtu <= ETHERMTU) { m_adj(mp, ETHER_ALIGN); } rx_buffer = &adapter->rx_buffer_area[i]; /* * Using memory from the mbuf cluster pool, invoke the bus_dma * machinery to arrange the memory mapping. */ error = bus_dmamap_load(adapter->rxtag, rx_buffer->map, mtod(mp, void *), mp->m_len, ixgb_dmamap_cb, &paddr, 0); if (error) { m_free(mp); return (error); } rx_buffer->m_head = mp; adapter->rx_desc_base[i].buff_addr = htole64(paddr); bus_dmamap_sync(adapter->rxtag, rx_buffer->map, BUS_DMASYNC_PREREAD); return (0); } /********************************************************************* * * Allocate memory for rx_buffer structures. Since we use one * rx_buffer per received packet, the maximum number of rx_buffer's * that we'll need is equal to the number of receive descriptors * that we've allocated. * **********************************************************************/ static int ixgb_allocate_receive_structures(struct adapter * adapter) { int i, error; struct ixgb_buffer *rx_buffer; if (!(adapter->rx_buffer_area = (struct ixgb_buffer *) malloc(sizeof(struct ixgb_buffer) * adapter->num_rx_desc, M_DEVBUF, M_NOWAIT | M_ZERO))) { printf("ixgb%d: Unable to allocate rx_buffer memory\n", adapter->unit); return (ENOMEM); } bzero(adapter->rx_buffer_area, sizeof(struct ixgb_buffer) * adapter->num_rx_desc); error = bus_dma_tag_create(NULL, /* parent */ PAGE_SIZE, 0, /* alignment, bounds */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ #if __FreeBSD_version >= 502000 NULL, /* lockfunc */ NULL, /* lockfuncarg */ #endif &adapter->rxtag); if (error != 0) { printf("ixgb%d: ixgb_allocate_receive_structures: " "bus_dma_tag_create failed; error %u\n", adapter->unit, error); goto fail_0; } rx_buffer = adapter->rx_buffer_area; for (i = 0; i < adapter->num_rx_desc; i++, rx_buffer++) { error = bus_dmamap_create(adapter->rxtag, BUS_DMA_NOWAIT, &rx_buffer->map); if (error != 0) { printf("ixgb%d: ixgb_allocate_receive_structures: " "bus_dmamap_create failed; error %u\n", adapter->unit, error); goto fail_1; } } for (i = 0; i < adapter->num_rx_desc; i++) { if (ixgb_get_buf(i, adapter, NULL) == ENOBUFS) { adapter->rx_buffer_area[i].m_head = NULL; adapter->rx_desc_base[i].buff_addr = 0; return (ENOBUFS); } } return (0); fail_1: bus_dma_tag_destroy(adapter->rxtag); fail_0: adapter->rxtag = NULL; free(adapter->rx_buffer_area, M_DEVBUF); adapter->rx_buffer_area = NULL; return (error); } /********************************************************************* * * Allocate and initialize receive structures. * **********************************************************************/ static int ixgb_setup_receive_structures(struct adapter * adapter) { bzero((void *)adapter->rx_desc_base, (sizeof(struct ixgb_rx_desc)) * adapter->num_rx_desc); if (ixgb_allocate_receive_structures(adapter)) return ENOMEM; /* Setup our descriptor pointers */ adapter->next_rx_desc_to_check = 0; adapter->next_rx_desc_to_use = 0; return (0); } /********************************************************************* * * Enable receive unit. * **********************************************************************/ static void ixgb_initialize_receive_unit(struct adapter * adapter) { u_int32_t reg_rctl; u_int32_t reg_rxcsum; u_int32_t reg_rxdctl; struct ifnet *ifp; u_int64_t rdba = adapter->rxdma.dma_paddr; ifp = adapter->ifp; /* * Make sure receives are disabled while setting up the descriptor * ring */ reg_rctl = IXGB_READ_REG(&adapter->hw, RCTL); IXGB_WRITE_REG(&adapter->hw, RCTL, reg_rctl & ~IXGB_RCTL_RXEN); /* Set the Receive Delay Timer Register */ IXGB_WRITE_REG(&adapter->hw, RDTR, adapter->rx_int_delay); /* Setup the Base and Length of the Rx Descriptor Ring */ IXGB_WRITE_REG(&adapter->hw, RDBAL, (rdba & 0x00000000ffffffffULL)); IXGB_WRITE_REG(&adapter->hw, RDBAH, (rdba >> 32)); IXGB_WRITE_REG(&adapter->hw, RDLEN, adapter->num_rx_desc * sizeof(struct ixgb_rx_desc)); /* Setup the HW Rx Head and Tail Descriptor Pointers */ IXGB_WRITE_REG(&adapter->hw, RDH, 0); IXGB_WRITE_REG(&adapter->hw, RDT, adapter->num_rx_desc - 1); reg_rxdctl = RXDCTL_WTHRESH_DEFAULT << IXGB_RXDCTL_WTHRESH_SHIFT | RXDCTL_HTHRESH_DEFAULT << IXGB_RXDCTL_HTHRESH_SHIFT | RXDCTL_PTHRESH_DEFAULT << IXGB_RXDCTL_PTHRESH_SHIFT; IXGB_WRITE_REG(&adapter->hw, RXDCTL, reg_rxdctl); adapter->raidc = 1; if (adapter->raidc) { uint32_t raidc; uint8_t poll_threshold; #define IXGB_RAIDC_POLL_DEFAULT 120 poll_threshold = ((adapter->num_rx_desc - 1) >> 3); poll_threshold >>= 1; poll_threshold &= 0x3F; raidc = IXGB_RAIDC_EN | IXGB_RAIDC_RXT_GATE | (IXGB_RAIDC_POLL_DEFAULT << IXGB_RAIDC_POLL_SHIFT) | (adapter->rx_int_delay << IXGB_RAIDC_DELAY_SHIFT) | poll_threshold; IXGB_WRITE_REG(&adapter->hw, RAIDC, raidc); } /* Enable Receive Checksum Offload for TCP and UDP ? */ if (ifp->if_capenable & IFCAP_RXCSUM) { reg_rxcsum = IXGB_READ_REG(&adapter->hw, RXCSUM); reg_rxcsum |= IXGB_RXCSUM_TUOFL; IXGB_WRITE_REG(&adapter->hw, RXCSUM, reg_rxcsum); } /* Setup the Receive Control Register */ reg_rctl = IXGB_READ_REG(&adapter->hw, RCTL); reg_rctl &= ~(3 << IXGB_RCTL_MO_SHIFT); reg_rctl |= IXGB_RCTL_BAM | IXGB_RCTL_RDMTS_1_2 | IXGB_RCTL_SECRC | IXGB_RCTL_CFF | (adapter->hw.mc_filter_type << IXGB_RCTL_MO_SHIFT); switch (adapter->rx_buffer_len) { default: case IXGB_RXBUFFER_2048: reg_rctl |= IXGB_RCTL_BSIZE_2048; break; case IXGB_RXBUFFER_4096: reg_rctl |= IXGB_RCTL_BSIZE_4096; break; case IXGB_RXBUFFER_8192: reg_rctl |= IXGB_RCTL_BSIZE_8192; break; case IXGB_RXBUFFER_16384: reg_rctl |= IXGB_RCTL_BSIZE_16384; break; } reg_rctl |= IXGB_RCTL_RXEN; /* Enable Receives */ IXGB_WRITE_REG(&adapter->hw, RCTL, reg_rctl); return; } /********************************************************************* * * Free receive related data structures. * **********************************************************************/ static void ixgb_free_receive_structures(struct adapter * adapter) { struct ixgb_buffer *rx_buffer; int i; INIT_DEBUGOUT("free_receive_structures: begin"); if (adapter->rx_buffer_area != NULL) { rx_buffer = adapter->rx_buffer_area; for (i = 0; i < adapter->num_rx_desc; i++, rx_buffer++) { if (rx_buffer->map != NULL) { bus_dmamap_unload(adapter->rxtag, rx_buffer->map); bus_dmamap_destroy(adapter->rxtag, rx_buffer->map); } if (rx_buffer->m_head != NULL) m_freem(rx_buffer->m_head); rx_buffer->m_head = NULL; } } if (adapter->rx_buffer_area != NULL) { free(adapter->rx_buffer_area, M_DEVBUF); adapter->rx_buffer_area = NULL; } if (adapter->rxtag != NULL) { bus_dma_tag_destroy(adapter->rxtag); adapter->rxtag = NULL; } return; } /********************************************************************* * * This routine executes in interrupt context. It replenishes * the mbufs in the descriptor and sends data which has been * dma'ed into host memory to upper layer. * * We loop at most count times if count is > 0, or until done if * count < 0. * *********************************************************************/ static void ixgb_process_receive_interrupts(struct adapter * adapter, int count) { struct ifnet *ifp; struct mbuf *mp; #if __FreeBSD_version < 500000 struct ether_header *eh; #endif int eop = 0; int len; u_int8_t accept_frame = 0; int i; int next_to_use = 0; int eop_desc; /* Pointer to the receive descriptor being examined. */ struct ixgb_rx_desc *current_desc; IXGB_LOCK_ASSERT(adapter); ifp = adapter->ifp; i = adapter->next_rx_desc_to_check; next_to_use = adapter->next_rx_desc_to_use; eop_desc = adapter->next_rx_desc_to_check; current_desc = &adapter->rx_desc_base[i]; if (!((current_desc->status) & IXGB_RX_DESC_STATUS_DD)) { #ifdef _SV_ adapter->no_pkts_avail++; #endif return; } while ((current_desc->status & IXGB_RX_DESC_STATUS_DD) && (count != 0)) { mp = adapter->rx_buffer_area[i].m_head; bus_dmamap_sync(adapter->rxtag, adapter->rx_buffer_area[i].map, BUS_DMASYNC_POSTREAD); accept_frame = 1; if (current_desc->status & IXGB_RX_DESC_STATUS_EOP) { count--; eop = 1; } else { eop = 0; } len = current_desc->length; if (current_desc->errors & (IXGB_RX_DESC_ERRORS_CE | IXGB_RX_DESC_ERRORS_SE | IXGB_RX_DESC_ERRORS_P | IXGB_RX_DESC_ERRORS_RXE)) { accept_frame = 0; } if (accept_frame) { /* Assign correct length to the current fragment */ mp->m_len = len; if (adapter->fmp == NULL) { mp->m_pkthdr.len = len; adapter->fmp = mp; /* Store the first mbuf */ adapter->lmp = mp; } else { /* Chain mbuf's together */ mp->m_flags &= ~M_PKTHDR; adapter->lmp->m_next = mp; adapter->lmp = adapter->lmp->m_next; adapter->fmp->m_pkthdr.len += len; } if (eop) { eop_desc = i; adapter->fmp->m_pkthdr.rcvif = ifp; #if __FreeBSD_version < 500000 eh = mtod(adapter->fmp, struct ether_header *); /* Remove ethernet header from mbuf */ m_adj(adapter->fmp, sizeof(struct ether_header)); ixgb_receive_checksum(adapter, current_desc, adapter->fmp); if (current_desc->status & IXGB_RX_DESC_STATUS_VP) VLAN_INPUT_TAG(eh, adapter->fmp, current_desc->special); else ether_input(ifp, eh, adapter->fmp); #else ixgb_receive_checksum(adapter, current_desc, adapter->fmp); +#if __FreeBSD_version < 700000 if (current_desc->status & IXGB_RX_DESC_STATUS_VP) VLAN_INPUT_TAG(ifp, adapter->fmp, current_desc->special); +#else + if (current_desc->status & IXGB_RX_DESC_STATUS_VP) { + adapter->fmp->m_pkthdr.ether_vtag = + current_desc->special; + adapter->fmp->m_flags |= M_VLANTAG; + } +#endif if (adapter->fmp != NULL) { IXGB_UNLOCK(adapter); (*ifp->if_input) (ifp, adapter->fmp); IXGB_LOCK(adapter); } #endif adapter->fmp = NULL; adapter->lmp = NULL; } adapter->rx_buffer_area[i].m_head = NULL; } else { adapter->dropped_pkts++; if (adapter->fmp != NULL) m_freem(adapter->fmp); adapter->fmp = NULL; adapter->lmp = NULL; } /* Zero out the receive descriptors status */ current_desc->status = 0; /* Advance our pointers to the next descriptor */ if (++i == adapter->num_rx_desc) { i = 0; current_desc = adapter->rx_desc_base; } else current_desc++; } adapter->next_rx_desc_to_check = i; if (--i < 0) i = (adapter->num_rx_desc - 1); /* * 82597EX: Workaround for redundent write back in receive descriptor ring (causes * memory corruption). Avoid using and re-submitting the most recently received RX * descriptor back to hardware. * * if(Last written back descriptor == EOP bit set descriptor) * then avoid re-submitting the most recently received RX descriptor * back to hardware. * if(Last written back descriptor != EOP bit set descriptor) * then avoid re-submitting the most recently received RX descriptors * till last EOP bit set descriptor. */ if (eop_desc != i) { if (++eop_desc == adapter->num_rx_desc) eop_desc = 0; i = eop_desc; } /* Replenish the descriptors with new mbufs till last EOP bit set descriptor */ while (next_to_use != i) { current_desc = &adapter->rx_desc_base[next_to_use]; if ((current_desc->errors & (IXGB_RX_DESC_ERRORS_CE | IXGB_RX_DESC_ERRORS_SE | IXGB_RX_DESC_ERRORS_P | IXGB_RX_DESC_ERRORS_RXE))) { mp = adapter->rx_buffer_area[next_to_use].m_head; ixgb_get_buf(next_to_use, adapter, mp); } else { if (ixgb_get_buf(next_to_use, adapter, NULL) == ENOBUFS) break; } /* Advance our pointers to the next descriptor */ if (++next_to_use == adapter->num_rx_desc) { next_to_use = 0; current_desc = adapter->rx_desc_base; } else current_desc++; } adapter->next_rx_desc_to_use = next_to_use; if (--next_to_use < 0) next_to_use = (adapter->num_rx_desc - 1); /* Advance the IXGB's Receive Queue #0 "Tail Pointer" */ IXGB_WRITE_REG(&adapter->hw, RDT, next_to_use); return; } /********************************************************************* * * Verify that the hardware indicated that the checksum is valid. * Inform the stack about the status of checksum so that stack * doesn't spend time verifying the checksum. * *********************************************************************/ static void ixgb_receive_checksum(struct adapter * adapter, struct ixgb_rx_desc * rx_desc, struct mbuf * mp) { if (rx_desc->status & IXGB_RX_DESC_STATUS_IXSM) { mp->m_pkthdr.csum_flags = 0; return; } if (rx_desc->status & IXGB_RX_DESC_STATUS_IPCS) { /* Did it pass? */ if (!(rx_desc->errors & IXGB_RX_DESC_ERRORS_IPE)) { /* IP Checksum Good */ mp->m_pkthdr.csum_flags = CSUM_IP_CHECKED; mp->m_pkthdr.csum_flags |= CSUM_IP_VALID; } else { mp->m_pkthdr.csum_flags = 0; } } if (rx_desc->status & IXGB_RX_DESC_STATUS_TCPCS) { /* Did it pass? */ if (!(rx_desc->errors & IXGB_RX_DESC_ERRORS_TCPE)) { mp->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | CSUM_PSEUDO_HDR); mp->m_pkthdr.csum_data = htons(0xffff); } } return; } static void ixgb_enable_vlans(struct adapter * adapter) { uint32_t ctrl; ctrl = IXGB_READ_REG(&adapter->hw, CTRL0); ctrl |= IXGB_CTRL0_VME; IXGB_WRITE_REG(&adapter->hw, CTRL0, ctrl); return; } static void ixgb_enable_intr(struct adapter * adapter) { IXGB_WRITE_REG(&adapter->hw, IMS, (IXGB_INT_RXT0 | IXGB_INT_TXDW | IXGB_INT_RXDMT0 | IXGB_INT_LSC | IXGB_INT_RXO)); return; } static void ixgb_disable_intr(struct adapter * adapter) { IXGB_WRITE_REG(&adapter->hw, IMC, ~0); return; } void ixgb_write_pci_cfg(struct ixgb_hw * hw, uint32_t reg, uint16_t * value) { pci_write_config(((struct ixgb_osdep *) hw->back)->dev, reg, *value, 2); } /********************************************************************** * * Update the board statistics counters. * **********************************************************************/ static void ixgb_update_stats_counters(struct adapter * adapter) { struct ifnet *ifp; adapter->stats.crcerrs += IXGB_READ_REG(&adapter->hw, CRCERRS); adapter->stats.gprcl += IXGB_READ_REG(&adapter->hw, GPRCL); adapter->stats.gprch += IXGB_READ_REG(&adapter->hw, GPRCH); adapter->stats.gorcl += IXGB_READ_REG(&adapter->hw, GORCL); adapter->stats.gorch += IXGB_READ_REG(&adapter->hw, GORCH); adapter->stats.bprcl += IXGB_READ_REG(&adapter->hw, BPRCL); adapter->stats.bprch += IXGB_READ_REG(&adapter->hw, BPRCH); adapter->stats.mprcl += IXGB_READ_REG(&adapter->hw, MPRCL); adapter->stats.mprch += IXGB_READ_REG(&adapter->hw, MPRCH); adapter->stats.roc += IXGB_READ_REG(&adapter->hw, ROC); adapter->stats.mpc += IXGB_READ_REG(&adapter->hw, MPC); adapter->stats.dc += IXGB_READ_REG(&adapter->hw, DC); adapter->stats.rlec += IXGB_READ_REG(&adapter->hw, RLEC); adapter->stats.xonrxc += IXGB_READ_REG(&adapter->hw, XONRXC); adapter->stats.xontxc += IXGB_READ_REG(&adapter->hw, XONTXC); adapter->stats.xoffrxc += IXGB_READ_REG(&adapter->hw, XOFFRXC); adapter->stats.xofftxc += IXGB_READ_REG(&adapter->hw, XOFFTXC); adapter->stats.gptcl += IXGB_READ_REG(&adapter->hw, GPTCL); adapter->stats.gptch += IXGB_READ_REG(&adapter->hw, GPTCH); adapter->stats.gotcl += IXGB_READ_REG(&adapter->hw, GOTCL); adapter->stats.gotch += IXGB_READ_REG(&adapter->hw, GOTCH); adapter->stats.ruc += IXGB_READ_REG(&adapter->hw, RUC); adapter->stats.rfc += IXGB_READ_REG(&adapter->hw, RFC); adapter->stats.rjc += IXGB_READ_REG(&adapter->hw, RJC); adapter->stats.torl += IXGB_READ_REG(&adapter->hw, TORL); adapter->stats.torh += IXGB_READ_REG(&adapter->hw, TORH); adapter->stats.totl += IXGB_READ_REG(&adapter->hw, TOTL); adapter->stats.toth += IXGB_READ_REG(&adapter->hw, TOTH); adapter->stats.tprl += IXGB_READ_REG(&adapter->hw, TPRL); adapter->stats.tprh += IXGB_READ_REG(&adapter->hw, TPRH); adapter->stats.tptl += IXGB_READ_REG(&adapter->hw, TPTL); adapter->stats.tpth += IXGB_READ_REG(&adapter->hw, TPTH); adapter->stats.plt64c += IXGB_READ_REG(&adapter->hw, PLT64C); adapter->stats.mptcl += IXGB_READ_REG(&adapter->hw, MPTCL); adapter->stats.mptch += IXGB_READ_REG(&adapter->hw, MPTCH); adapter->stats.bptcl += IXGB_READ_REG(&adapter->hw, BPTCL); adapter->stats.bptch += IXGB_READ_REG(&adapter->hw, BPTCH); adapter->stats.uprcl += IXGB_READ_REG(&adapter->hw, UPRCL); adapter->stats.uprch += IXGB_READ_REG(&adapter->hw, UPRCH); adapter->stats.vprcl += IXGB_READ_REG(&adapter->hw, VPRCL); adapter->stats.vprch += IXGB_READ_REG(&adapter->hw, VPRCH); adapter->stats.jprcl += IXGB_READ_REG(&adapter->hw, JPRCL); adapter->stats.jprch += IXGB_READ_REG(&adapter->hw, JPRCH); adapter->stats.rnbc += IXGB_READ_REG(&adapter->hw, RNBC); adapter->stats.icbc += IXGB_READ_REG(&adapter->hw, ICBC); adapter->stats.ecbc += IXGB_READ_REG(&adapter->hw, ECBC); adapter->stats.uptcl += IXGB_READ_REG(&adapter->hw, UPTCL); adapter->stats.uptch += IXGB_READ_REG(&adapter->hw, UPTCH); adapter->stats.vptcl += IXGB_READ_REG(&adapter->hw, VPTCL); adapter->stats.vptch += IXGB_READ_REG(&adapter->hw, VPTCH); adapter->stats.jptcl += IXGB_READ_REG(&adapter->hw, JPTCL); adapter->stats.jptch += IXGB_READ_REG(&adapter->hw, JPTCH); adapter->stats.tsctc += IXGB_READ_REG(&adapter->hw, TSCTC); adapter->stats.tsctfc += IXGB_READ_REG(&adapter->hw, TSCTFC); adapter->stats.ibic += IXGB_READ_REG(&adapter->hw, IBIC); adapter->stats.lfc += IXGB_READ_REG(&adapter->hw, LFC); adapter->stats.pfrc += IXGB_READ_REG(&adapter->hw, PFRC); adapter->stats.pftc += IXGB_READ_REG(&adapter->hw, PFTC); adapter->stats.mcfrc += IXGB_READ_REG(&adapter->hw, MCFRC); ifp = adapter->ifp; /* Fill out the OS statistics structure */ ifp->if_ipackets = adapter->stats.gprcl; ifp->if_opackets = adapter->stats.gptcl; ifp->if_ibytes = adapter->stats.gorcl; ifp->if_obytes = adapter->stats.gotcl; ifp->if_imcasts = adapter->stats.mprcl; ifp->if_collisions = 0; /* Rx Errors */ ifp->if_ierrors = adapter->dropped_pkts + adapter->stats.crcerrs + adapter->stats.rnbc + adapter->stats.mpc + adapter->stats.rlec; } /********************************************************************** * * This routine is called only when ixgb_display_debug_stats is enabled. * This routine provides a way to take a look at important statistics * maintained by the driver and hardware. * **********************************************************************/ static void ixgb_print_hw_stats(struct adapter * adapter) { char buf_speed[100], buf_type[100]; ixgb_bus_speed bus_speed; ixgb_bus_type bus_type; int unit = adapter->unit; #ifdef _SV_ printf("ixgb%d: Packets not Avail = %ld\n", unit, adapter->no_pkts_avail); printf("ixgb%d: CleanTxInterrupts = %ld\n", unit, adapter->clean_tx_interrupts); printf("ixgb%d: ICR RXDMT0 = %lld\n", unit, (long long)adapter->sv_stats.icr_rxdmt0); printf("ixgb%d: ICR RXO = %lld\n", unit, (long long)adapter->sv_stats.icr_rxo); printf("ixgb%d: ICR RXT0 = %lld\n", unit, (long long)adapter->sv_stats.icr_rxt0); printf("ixgb%d: ICR TXDW = %lld\n", unit, (long long)adapter->sv_stats.icr_TXDW); #endif /* _SV_ */ bus_speed = adapter->hw.bus.speed; bus_type = adapter->hw.bus.type; sprintf(buf_speed, bus_speed == ixgb_bus_speed_33 ? "33MHz" : bus_speed == ixgb_bus_speed_66 ? "66MHz" : bus_speed == ixgb_bus_speed_100 ? "100MHz" : bus_speed == ixgb_bus_speed_133 ? "133MHz" : "UNKNOWN"); printf("ixgb%d: PCI_Bus_Speed = %s\n", unit, buf_speed); sprintf(buf_type, bus_type == ixgb_bus_type_pci ? "PCI" : bus_type == ixgb_bus_type_pcix ? "PCI-X" : "UNKNOWN"); printf("ixgb%d: PCI_Bus_Type = %s\n", unit, buf_type); printf("ixgb%d: Tx Descriptors not Avail1 = %ld\n", unit, adapter->no_tx_desc_avail1); printf("ixgb%d: Tx Descriptors not Avail2 = %ld\n", unit, adapter->no_tx_desc_avail2); printf("ixgb%d: Std Mbuf Failed = %ld\n", unit, adapter->mbuf_alloc_failed); printf("ixgb%d: Std Cluster Failed = %ld\n", unit, adapter->mbuf_cluster_failed); printf("ixgb%d: Defer count = %lld\n", unit, (long long)adapter->stats.dc); printf("ixgb%d: Missed Packets = %lld\n", unit, (long long)adapter->stats.mpc); printf("ixgb%d: Receive No Buffers = %lld\n", unit, (long long)adapter->stats.rnbc); printf("ixgb%d: Receive length errors = %lld\n", unit, (long long)adapter->stats.rlec); printf("ixgb%d: Crc errors = %lld\n", unit, (long long)adapter->stats.crcerrs); printf("ixgb%d: Driver dropped packets = %ld\n", unit, adapter->dropped_pkts); printf("ixgb%d: XON Rcvd = %lld\n", unit, (long long)adapter->stats.xonrxc); printf("ixgb%d: XON Xmtd = %lld\n", unit, (long long)adapter->stats.xontxc); printf("ixgb%d: XOFF Rcvd = %lld\n", unit, (long long)adapter->stats.xoffrxc); printf("ixgb%d: XOFF Xmtd = %lld\n", unit, (long long)adapter->stats.xofftxc); printf("ixgb%d: Good Packets Rcvd = %lld\n", unit, (long long)adapter->stats.gprcl); printf("ixgb%d: Good Packets Xmtd = %lld\n", unit, (long long)adapter->stats.gptcl); printf("ixgb%d: Jumbo frames recvd = %lld\n", unit, (long long)adapter->stats.jprcl); printf("ixgb%d: Jumbo frames Xmtd = %lld\n", unit, (long long)adapter->stats.jptcl); return; } static int ixgb_sysctl_stats(SYSCTL_HANDLER_ARGS) { int error; int result; struct adapter *adapter; result = -1; error = sysctl_handle_int(oidp, &result, 0, req); if (error || !req->newptr) return (error); if (result == 1) { adapter = (struct adapter *) arg1; ixgb_print_hw_stats(adapter); } return error; } Index: head/sys/dev/nfe/if_nfe.c =================================================================== --- head/sys/dev/nfe/if_nfe.c (revision 162374) +++ head/sys/dev/nfe/if_nfe.c (revision 162375) @@ -1,2155 +1,2144 @@ /* $OpenBSD: if_nfe.c,v 1.54 2006/04/07 12:38:12 jsg Exp $ */ /*- * Copyright (c) 2006 Shigeaki Tagashira * Copyright (c) 2006 Damien Bergamini * Copyright (c) 2005, 2006 Jonathan Gray * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* Driver for NVIDIA nForce MCP Fast Ethernet and Gigabit Ethernet */ #include __FBSDID("$FreeBSD$"); /* Uncomment the following line to enable polling. */ /* #define DEVICE_POLLING */ #define NFE_NO_JUMBO #define NFE_CSUM #define NFE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) #define NVLAN 0 #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_device_polling.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_DEPEND(nfe, pci, 1, 1, 1); MODULE_DEPEND(nfe, ether, 1, 1, 1); MODULE_DEPEND(nfe, miibus, 1, 1, 1); #include "miibus_if.h" static int nfe_probe (device_t); static int nfe_attach (device_t); static int nfe_detach (device_t); static void nfe_shutdown(device_t); static int nfe_miibus_readreg (device_t, int, int); static int nfe_miibus_writereg (device_t, int, int, int); static void nfe_miibus_statchg (device_t); static int nfe_ioctl(struct ifnet *, u_long, caddr_t); static void nfe_intr(void *); static void nfe_txdesc32_sync(struct nfe_softc *, struct nfe_desc32 *, int); static void nfe_txdesc64_sync(struct nfe_softc *, struct nfe_desc64 *, int); static void nfe_txdesc32_rsync(struct nfe_softc *, int, int, int); static void nfe_txdesc64_rsync(struct nfe_softc *, int, int, int); static void nfe_rxdesc32_sync(struct nfe_softc *, struct nfe_desc32 *, int); static void nfe_rxdesc64_sync(struct nfe_softc *, struct nfe_desc64 *, int); static void nfe_rxeof(struct nfe_softc *); static void nfe_txeof(struct nfe_softc *); static int nfe_encap(struct nfe_softc *, struct mbuf *); static struct nfe_jbuf *nfe_jalloc(struct nfe_softc *); static void nfe_jfree(void *, void *); static int nfe_jpool_alloc(struct nfe_softc *); static void nfe_jpool_free(struct nfe_softc *); static void nfe_setmulti(struct nfe_softc *); static void nfe_start(struct ifnet *); static void nfe_start_locked(struct ifnet *); static void nfe_watchdog(struct ifnet *); static void nfe_init(void *); static void nfe_init_locked(void *); static void nfe_stop(struct ifnet *, int); static int nfe_alloc_rx_ring(struct nfe_softc *, struct nfe_rx_ring *); static void nfe_reset_rx_ring(struct nfe_softc *, struct nfe_rx_ring *); static void nfe_free_rx_ring(struct nfe_softc *, struct nfe_rx_ring *); static int nfe_alloc_tx_ring(struct nfe_softc *, struct nfe_tx_ring *); static void nfe_reset_tx_ring(struct nfe_softc *, struct nfe_tx_ring *); static void nfe_free_tx_ring(struct nfe_softc *, struct nfe_tx_ring *); static int nfe_ifmedia_upd(struct ifnet *); static int nfe_ifmedia_upd_locked(struct ifnet *); static void nfe_ifmedia_sts(struct ifnet *, struct ifmediareq *); static void nfe_tick(void *); static void nfe_tick_locked(struct nfe_softc *); static void nfe_get_macaddr(struct nfe_softc *, u_char *); static void nfe_set_macaddr(struct nfe_softc *, u_char *); static void nfe_dma_map_segs (void *, bus_dma_segment_t *, int, int); #ifdef DEVICE_POLLING static void nfe_poll_locked(struct ifnet *, enum poll_cmd, int); #endif #ifdef NFE_DEBUG int nfedebug = 0; #define DPRINTF(x) do { if (nfedebug) printf x; } while (0) #define DPRINTFN(n,x) do { if (nfedebug >= (n)) printf x; } while (0) #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif #define NFE_LOCK(_sc) mtx_lock(&(_sc)->nfe_mtx) #define NFE_UNLOCK(_sc) mtx_unlock(&(_sc)->nfe_mtx) #define NFE_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->nfe_mtx, MA_OWNED) #define letoh16(x) le16toh(x) #define NV_RID 0x10 static device_method_t nfe_methods[] = { /* Device interface */ DEVMETHOD(device_probe, nfe_probe), DEVMETHOD(device_attach, nfe_attach), DEVMETHOD(device_detach, nfe_detach), DEVMETHOD(device_shutdown, nfe_shutdown), /* bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, nfe_miibus_readreg), DEVMETHOD(miibus_writereg, nfe_miibus_writereg), DEVMETHOD(miibus_statchg, nfe_miibus_statchg), { 0, 0 } }; static driver_t nfe_driver = { "nfe", nfe_methods, sizeof(struct nfe_softc) }; static devclass_t nfe_devclass; DRIVER_MODULE(nfe, pci, nfe_driver, nfe_devclass, 0, 0); DRIVER_MODULE(miibus, nfe, miibus_driver, miibus_devclass, 0, 0); static struct nfe_type nfe_devs[] = { {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE_LAN, "NVIDIA nForce Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE2_LAN, "NVIDIA nForce2 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE3_LAN1, "NVIDIA nForce3 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE2_400_LAN1, "NVIDIA nForce2 400 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE2_400_LAN2, "NVIDIA nForce2 400 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE3_250_LAN, "NVIDIA nForce3 250 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE3_LAN4, "NVIDIA nForce3 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE4_LAN1, "NVIDIA nForce4 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE4_LAN2, "NVIDIA nForce4 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP04_LAN1, "NVIDIA nForce MCP04 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP04_LAN2, "NVIDIA nForce MCP04 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE430_LAN1, "NVIDIA nForce 430 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE430_LAN2, "NVIDIA nForce 430 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP55_LAN1, "NVIDIA nForce MCP55 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP55_LAN2, "NVIDIA nForce MCP55 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP61_LAN1, "NVIDIA nForce MCP61 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP61_LAN2, "NVIDIA nForce MCP61 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP61_LAN3, "NVIDIA nForce MCP61 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP61_LAN2, "NVIDIA nForce MCP61 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP65_LAN1, "NVIDIA nForce MCP65 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP65_LAN2, "NVIDIA nForce MCP65 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP65_LAN3, "NVIDIA nForce MCP65 Networking Adapter"}, {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP65_LAN2, "NVIDIA nForce MCP65 Networking Adapter"}, {0, 0, NULL} }; /* Probe for supported hardware ID's */ static int nfe_probe(device_t dev) { struct nfe_type *t; t = nfe_devs; /* Check for matching PCI DEVICE ID's */ while (t->name != NULL) { if ((pci_get_vendor(dev) == t->vid_id) && (pci_get_device(dev) == t->dev_id)) { device_set_desc(dev, t->name); return (0); } t++; } return (ENXIO); } static int nfe_attach(device_t dev) { struct nfe_softc *sc; struct ifnet *ifp; int unit, error = 0, rid; sc = device_get_softc(dev); unit = device_get_unit(dev); sc->nfe_dev = dev; sc->nfe_unit = unit; mtx_init(&sc->nfe_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF | MTX_RECURSE); callout_init_mtx(&sc->nfe_stat_ch, &sc->nfe_mtx, 0); pci_enable_busmaster(dev); rid = NV_RID; sc->nfe_res = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, 0, ~0, 1, RF_ACTIVE); if (sc->nfe_res == NULL) { printf ("nfe%d: couldn't map ports/memory\n", unit); error = ENXIO; goto fail; } sc->nfe_memt = rman_get_bustag(sc->nfe_res); sc->nfe_memh = rman_get_bushandle(sc->nfe_res); /* Allocate interrupt */ rid = 0; sc->nfe_irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); if (sc->nfe_irq == NULL) { printf("nfe%d: couldn't map interrupt\n", unit); error = ENXIO; goto fail; } nfe_get_macaddr(sc, sc->eaddr); sc->nfe_flags = 0; switch (pci_get_device(dev)) { case PCI_PRODUCT_NVIDIA_NFORCE3_LAN2: case PCI_PRODUCT_NVIDIA_NFORCE3_LAN3: case PCI_PRODUCT_NVIDIA_NFORCE3_LAN4: case PCI_PRODUCT_NVIDIA_NFORCE3_LAN5: sc->nfe_flags |= NFE_JUMBO_SUP | NFE_HW_CSUM; break; case PCI_PRODUCT_NVIDIA_MCP51_LAN1: case PCI_PRODUCT_NVIDIA_MCP51_LAN2: sc->nfe_flags |= NFE_40BIT_ADDR; break; case PCI_PRODUCT_NVIDIA_CK804_LAN1: case PCI_PRODUCT_NVIDIA_CK804_LAN2: case PCI_PRODUCT_NVIDIA_MCP04_LAN1: case PCI_PRODUCT_NVIDIA_MCP04_LAN2: sc->nfe_flags |= NFE_JUMBO_SUP | NFE_40BIT_ADDR | NFE_HW_CSUM; break; case PCI_PRODUCT_NVIDIA_MCP55_LAN1: case PCI_PRODUCT_NVIDIA_MCP55_LAN2: sc->nfe_flags |= NFE_JUMBO_SUP | NFE_40BIT_ADDR | NFE_HW_CSUM | NFE_HW_VLAN; break; case PCI_PRODUCT_NVIDIA_MCP61_LAN1: case PCI_PRODUCT_NVIDIA_MCP61_LAN2: case PCI_PRODUCT_NVIDIA_MCP61_LAN3: case PCI_PRODUCT_NVIDIA_MCP61_LAN4: sc->nfe_flags |= NFE_40BIT_ADDR; break; case PCI_PRODUCT_NVIDIA_MCP65_LAN1: case PCI_PRODUCT_NVIDIA_MCP65_LAN2: case PCI_PRODUCT_NVIDIA_MCP65_LAN3: case PCI_PRODUCT_NVIDIA_MCP65_LAN4: sc->nfe_flags |= NFE_JUMBO_SUP | NFE_40BIT_ADDR | NFE_HW_CSUM; break; } #ifndef NFE_NO_JUMBO /* enable jumbo frames for adapters that support it */ if (sc->nfe_flags & NFE_JUMBO_SUP) sc->nfe_flags |= NFE_USE_JUMBO; #endif /* * Allocate the parent bus DMA tag appropriate for PCI. */ #define NFE_NSEG_NEW 32 error = bus_dma_tag_create(NULL, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MAXBSIZE, NFE_NSEG_NEW, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT,/* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->nfe_parent_tag); if (error) goto fail; /* * Allocate Tx and Rx rings. */ if (nfe_alloc_tx_ring(sc, &sc->txq) != 0) { printf("nfe%d: could not allocate Tx ring\n", unit); error = ENXIO; goto fail; } if (nfe_alloc_rx_ring(sc, &sc->rxq) != 0) { printf("nfe%d: could not allocate Rx ring\n", unit); nfe_free_tx_ring(sc, &sc->txq); error = ENXIO; goto fail; } ifp = sc->nfe_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { printf("nfe%d: can not if_alloc()\n", unit); error = ENOSPC; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_mtu = ETHERMTU; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = nfe_ioctl; ifp->if_start = nfe_start; /* ifp->if_hwassist = NFE_CSUM_FEATURES; */ ifp->if_watchdog = nfe_watchdog; ifp->if_init = nfe_init; ifp->if_baudrate = IF_Gbps(1); ifp->if_snd.ifq_maxlen = NFE_IFQ_MAXLEN; ifp->if_capabilities = IFCAP_VLAN_MTU; #if NVLAN > 0 if (sc->nfe_flags & NFE_HW_VLAN) ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING; #endif #ifdef NFE_CSUM if (sc->nfe_flags & NFE_HW_CSUM) { ifp->if_capabilities |= IFCAP_HWCSUM; } #endif ifp->if_capenable = ifp->if_capabilities; #ifdef DEVICE_POLLING ifp->if_capabilities |= IFCAP_POLLING; #endif /* Do MII setup */ if (mii_phy_probe(dev, &sc->nfe_miibus, nfe_ifmedia_upd, nfe_ifmedia_sts)) { printf("nfe%d: MII without any phy!\n", unit); error = ENXIO; goto fail; } ether_ifattach(ifp, sc->eaddr); error = bus_setup_intr(dev, sc->nfe_irq, INTR_TYPE_NET|INTR_MPSAFE, nfe_intr, sc, &sc->nfe_intrhand); if (error) { printf("nfe%d: couldn't set up irq\n", unit); ether_ifdetach(ifp); goto fail; } fail: if (error) nfe_detach(dev); return (error); } static int nfe_detach(device_t dev) { struct nfe_softc *sc; struct ifnet *ifp; u_char eaddr[ETHER_ADDR_LEN]; int i; sc = device_get_softc(dev); KASSERT(mtx_initialized(&sc->nfe_mtx), ("nfe mutex not initialized")); ifp = sc->nfe_ifp; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) ether_poll_deregister(ifp); #endif for (i = 0; i < ETHER_ADDR_LEN; i++) { eaddr[i] = sc->eaddr[5 - i]; } nfe_set_macaddr(sc, eaddr); if (device_is_attached(dev)) { nfe_stop(ifp, 1); ifp->if_flags &= ~IFF_UP; callout_drain(&sc->nfe_stat_ch); ether_ifdetach(ifp); } if (ifp) if_free(ifp); if (sc->nfe_miibus) device_delete_child(dev, sc->nfe_miibus); bus_generic_detach(dev); if (sc->nfe_intrhand) bus_teardown_intr(dev, sc->nfe_irq, sc->nfe_intrhand); if (sc->nfe_irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->nfe_irq); if (sc->nfe_res) bus_release_resource(dev, SYS_RES_MEMORY, NV_RID, sc->nfe_res); nfe_free_tx_ring(sc, &sc->txq); nfe_free_rx_ring(sc, &sc->rxq); if (sc->nfe_parent_tag) bus_dma_tag_destroy(sc->nfe_parent_tag); mtx_destroy(&sc->nfe_mtx); return (0); } static void nfe_miibus_statchg(device_t dev) { struct nfe_softc *sc; struct mii_data *mii; u_int32_t phy, seed, misc = NFE_MISC1_MAGIC, link = NFE_MEDIA_SET; sc = device_get_softc(dev); mii = device_get_softc(sc->nfe_miibus); phy = NFE_READ(sc, NFE_PHY_IFACE); phy &= ~(NFE_PHY_HDX | NFE_PHY_100TX | NFE_PHY_1000T); seed = NFE_READ(sc, NFE_RNDSEED); seed &= ~NFE_SEED_MASK; if ((mii->mii_media_active & IFM_GMASK) == IFM_HDX) { phy |= NFE_PHY_HDX; /* half-duplex */ misc |= NFE_MISC1_HDX; } switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_1000_T: /* full-duplex only */ link |= NFE_MEDIA_1000T; seed |= NFE_SEED_1000T; phy |= NFE_PHY_1000T; break; case IFM_100_TX: link |= NFE_MEDIA_100TX; seed |= NFE_SEED_100TX; phy |= NFE_PHY_100TX; break; case IFM_10_T: link |= NFE_MEDIA_10T; seed |= NFE_SEED_10T; break; } NFE_WRITE(sc, NFE_RNDSEED, seed); /* XXX: gigabit NICs only? */ NFE_WRITE(sc, NFE_PHY_IFACE, phy); NFE_WRITE(sc, NFE_MISC1, misc); NFE_WRITE(sc, NFE_LINKSPEED, link); } static int nfe_miibus_readreg(device_t dev, int phy, int reg) { struct nfe_softc *sc = device_get_softc(dev); u_int32_t val; int ntries; NFE_WRITE(sc, NFE_PHY_STATUS, 0xf); if (NFE_READ(sc, NFE_PHY_CTL) & NFE_PHY_BUSY) { NFE_WRITE(sc, NFE_PHY_CTL, NFE_PHY_BUSY); DELAY(100); } NFE_WRITE(sc, NFE_PHY_CTL, (phy << NFE_PHYADD_SHIFT) | reg); for (ntries = 0; ntries < 1000; ntries++) { DELAY(100); if (!(NFE_READ(sc, NFE_PHY_CTL) & NFE_PHY_BUSY)) break; } if (ntries == 1000) { DPRINTFN(2, ("nfe%d: timeout waiting for PHY\n", sc->nfe_unit)); return 0; } if (NFE_READ(sc, NFE_PHY_STATUS) & NFE_PHY_ERROR) { DPRINTFN(2, ("nfe%d: could not read PHY\n", sc->nfe_unit)); return 0; } val = NFE_READ(sc, NFE_PHY_DATA); if (val != 0xffffffff && val != 0) sc->mii_phyaddr = phy; DPRINTFN(2, ("nfe%d: mii read phy %d reg 0x%x ret 0x%x\n", sc->nfe_unit, phy, reg, val)); return val; } static int nfe_miibus_writereg(device_t dev, int phy, int reg, int val) { struct nfe_softc *sc = device_get_softc(dev); u_int32_t ctl; int ntries; NFE_WRITE(sc, NFE_PHY_STATUS, 0xf); if (NFE_READ(sc, NFE_PHY_CTL) & NFE_PHY_BUSY) { NFE_WRITE(sc, NFE_PHY_CTL, NFE_PHY_BUSY); DELAY(100); } NFE_WRITE(sc, NFE_PHY_DATA, val); ctl = NFE_PHY_WRITE | (phy << NFE_PHYADD_SHIFT) | reg; NFE_WRITE(sc, NFE_PHY_CTL, ctl); for (ntries = 0; ntries < 1000; ntries++) { DELAY(100); if (!(NFE_READ(sc, NFE_PHY_CTL) & NFE_PHY_BUSY)) break; } #ifdef NFE_DEBUG if (nfedebug >= 2 && ntries == 1000) printf("could not write to PHY\n"); #endif return 0; } static int nfe_alloc_rx_ring(struct nfe_softc *sc, struct nfe_rx_ring *ring) { struct nfe_desc32 *desc32; struct nfe_desc64 *desc64; struct nfe_rx_data *data; struct nfe_jbuf *jbuf; void **desc; bus_addr_t physaddr; int i, error, descsize; if (sc->nfe_flags & NFE_40BIT_ADDR) { desc = (void **)&ring->desc64; descsize = sizeof (struct nfe_desc64); } else { desc = (void **)&ring->desc32; descsize = sizeof (struct nfe_desc32); } ring->cur = ring->next = 0; ring->bufsz = MCLBYTES; error = bus_dma_tag_create(sc->nfe_parent_tag, PAGE_SIZE, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ NFE_RX_RING_COUNT * descsize, 1, /* maxsize, nsegments */ NFE_RX_RING_COUNT * descsize, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &ring->rx_desc_tag); if (error != 0) { printf("nfe%d: could not create desc DMA tag\n", sc->nfe_unit); goto fail; } /* allocate memory to desc */ error = bus_dmamem_alloc(ring->rx_desc_tag, (void **)desc, BUS_DMA_NOWAIT, &ring->rx_desc_map); if (error != 0) { printf("nfe%d: could not create desc DMA map\n", sc->nfe_unit); goto fail; } /* map desc to device visible address space */ error = bus_dmamap_load(ring->rx_desc_tag, ring->rx_desc_map, *desc, NFE_RX_RING_COUNT * descsize, nfe_dma_map_segs, &ring->rx_desc_segs, BUS_DMA_NOWAIT); if (error != 0) { printf("nfe%d: could not load desc DMA map\n", sc->nfe_unit); goto fail; } bzero(*desc, NFE_RX_RING_COUNT * descsize); ring->rx_desc_addr = ring->rx_desc_segs.ds_addr; ring->physaddr = ring->rx_desc_addr; if (sc->nfe_flags & NFE_USE_JUMBO) { ring->bufsz = NFE_JBYTES; if ((error = nfe_jpool_alloc(sc)) != 0) { printf("nfe%d: could not allocate jumbo frames\n", sc->nfe_unit); goto fail; } } /* * Pre-allocate Rx buffers and populate Rx ring. */ for (i = 0; i < NFE_RX_RING_COUNT; i++) { data = &sc->rxq.data[i]; MGETHDR(data->m, M_DONTWAIT, MT_DATA); if (data->m == NULL) { printf("nfe%d: could not allocate rx mbuf\n", sc->nfe_unit); error = ENOMEM; goto fail; } if (sc->nfe_flags & NFE_USE_JUMBO) { if ((jbuf = nfe_jalloc(sc)) == NULL) { printf("nfe%d: could not allocate jumbo buffer\n", sc->nfe_unit); goto fail; } data->m->m_data = (void *)jbuf->buf; data->m->m_len = data->m->m_pkthdr.len = NFE_JBYTES; MEXTADD(data->m, jbuf->buf, NFE_JBYTES, nfe_jfree, (struct nfe_softc *)sc, 0, EXT_NET_DRV); /* m_adj(data->m, ETHER_ALIGN); */ physaddr = jbuf->physaddr; } else { error = bus_dma_tag_create(sc->nfe_parent_tag, ETHER_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, 1, /* maxsize, nsegments */ MCLBYTES, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &data->rx_data_tag); if (error != 0) { printf("nfe%d: could not create DMA map\n", sc->nfe_unit); goto fail; } error = bus_dmamap_create(data->rx_data_tag, 0, &data->rx_data_map); if (error != 0) { printf("nfe%d: could not allocate mbuf cluster\n", sc->nfe_unit); goto fail; } MCLGET(data->m, M_DONTWAIT); if (!(data->m->m_flags & M_EXT)) { error = ENOMEM; goto fail; } error = bus_dmamap_load(data->rx_data_tag, data->rx_data_map, mtod(data->m, void *), MCLBYTES, nfe_dma_map_segs, &data->rx_data_segs, BUS_DMA_NOWAIT); if (error != 0) { printf("nfe%d: could not load rx buf DMA map\n", sc->nfe_unit); goto fail; } data->rx_data_addr = data->rx_data_segs.ds_addr; physaddr = data->rx_data_addr; } if (sc->nfe_flags & NFE_40BIT_ADDR) { desc64 = &sc->rxq.desc64[i]; #if defined(__LP64__) desc64->physaddr[0] = htole32(physaddr >> 32); #endif desc64->physaddr[1] = htole32(physaddr & 0xffffffff); desc64->length = htole16(sc->rxq.bufsz); desc64->flags = htole16(NFE_RX_READY); } else { desc32 = &sc->rxq.desc32[i]; desc32->physaddr = htole32(physaddr); desc32->length = htole16(sc->rxq.bufsz); desc32->flags = htole16(NFE_RX_READY); } } bus_dmamap_sync(ring->rx_desc_tag, ring->rx_desc_map, BUS_DMASYNC_PREWRITE); return 0; fail: nfe_free_rx_ring(sc, ring); return error; } static int nfe_jpool_alloc(struct nfe_softc *sc) { struct nfe_rx_ring *ring = &sc->rxq; struct nfe_jbuf *jbuf; bus_addr_t physaddr; caddr_t buf; int i, error; /* * Allocate a big chunk of DMA'able memory. */ error = bus_dma_tag_create(sc->nfe_parent_tag, PAGE_SIZE, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ NFE_JPOOL_SIZE, 1, /* maxsize, nsegments */ NFE_JPOOL_SIZE, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &ring->rx_jumbo_tag); if (error != 0) { printf("nfe%d: could not create jumbo DMA tag\n", sc->nfe_unit); goto fail; } error = bus_dmamem_alloc(ring->rx_jumbo_tag, (void **)&ring->jpool, BUS_DMA_NOWAIT, &ring->rx_jumbo_map); if (error != 0) { printf("nfe%d: could not create jumbo DMA memory\n", sc->nfe_unit); goto fail; } error = bus_dmamap_load(ring->rx_jumbo_tag, ring->rx_jumbo_map, ring->jpool, NFE_JPOOL_SIZE, nfe_dma_map_segs, &ring->rx_jumbo_segs, BUS_DMA_NOWAIT); if (error != 0) { printf("nfe%d: could not load jumbo DMA map\n", sc->nfe_unit); goto fail; } /* ..and split it into 9KB chunks */ SLIST_INIT(&ring->jfreelist); buf = ring->jpool; ring->rx_jumbo_addr = ring->rx_jumbo_segs.ds_addr; physaddr = ring->rx_jumbo_addr; for (i = 0; i < NFE_JPOOL_COUNT; i++) { jbuf = &ring->jbuf[i]; jbuf->buf = buf; jbuf->physaddr = physaddr; SLIST_INSERT_HEAD(&ring->jfreelist, jbuf, jnext); buf += NFE_JBYTES; physaddr += NFE_JBYTES; } return 0; fail: nfe_jpool_free(sc); return error; } static void nfe_jpool_free(struct nfe_softc *sc) { struct nfe_rx_ring *ring = &sc->rxq; if (ring->jpool != NULL) { #if 0 bus_dmamem_unmap(ring->rx_jumbo_tag, ring->jpool, NFE_JPOOL_SIZE); #endif bus_dmamem_free(ring->rx_jumbo_tag, &ring->rx_jumbo_segs, ring->rx_jumbo_map); } if (ring->rx_jumbo_map != NULL) { bus_dmamap_sync(ring->rx_jumbo_tag, ring->rx_jumbo_map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->rx_jumbo_tag, ring->rx_jumbo_map); bus_dmamap_destroy(ring->rx_jumbo_tag, ring->rx_jumbo_map); } } static struct nfe_jbuf * nfe_jalloc(struct nfe_softc *sc) { struct nfe_jbuf *jbuf; jbuf = SLIST_FIRST(&sc->rxq.jfreelist); if (jbuf == NULL) return NULL; SLIST_REMOVE_HEAD(&sc->rxq.jfreelist, jnext); return jbuf; } /* * This is called automatically by the network stack when the mbuf is freed. * Caution must be taken that the NIC might be reset by the time the mbuf is * freed. */ static void nfe_jfree(void *buf, void *arg) { struct nfe_softc *sc = arg; struct nfe_jbuf *jbuf; int i; /* find the jbuf from the base pointer */ i = ((vm_offset_t)buf - (vm_offset_t)sc->rxq.jpool) / NFE_JBYTES; if (i < 0 || i >= NFE_JPOOL_COUNT) { printf("nfe%d: request to free a buffer (%p) not managed by us\n", sc->nfe_unit, buf); return; } jbuf = &sc->rxq.jbuf[i]; /* ..and put it back in the free list */ SLIST_INSERT_HEAD(&sc->rxq.jfreelist, jbuf, jnext); } static void nfe_reset_rx_ring(struct nfe_softc *sc, struct nfe_rx_ring *ring) { int i; for (i = 0; i < NFE_RX_RING_COUNT; i++) { if (sc->nfe_flags & NFE_40BIT_ADDR) { ring->desc64[i].length = htole16(ring->bufsz); ring->desc64[i].flags = htole16(NFE_RX_READY); } else { ring->desc32[i].length = htole16(ring->bufsz); ring->desc32[i].flags = htole16(NFE_RX_READY); } } bus_dmamap_sync(ring->rx_desc_tag, ring->rx_desc_map, BUS_DMASYNC_PREWRITE); ring->cur = ring->next = 0; } static void nfe_free_rx_ring(struct nfe_softc *sc, struct nfe_rx_ring *ring) { struct nfe_rx_data *data; void *desc; int i, descsize; if (sc->nfe_flags & NFE_40BIT_ADDR) { desc = ring->desc64; descsize = sizeof (struct nfe_desc64); } else { desc = ring->desc32; descsize = sizeof (struct nfe_desc32); } if (desc != NULL) { bus_dmamap_sync(ring->rx_desc_tag, ring->rx_desc_map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->rx_desc_tag, ring->rx_desc_map); bus_dmamem_free(ring->rx_desc_tag, desc, ring->rx_desc_map); bus_dma_tag_destroy(ring->rx_desc_tag); } if (sc->nfe_flags & NFE_USE_JUMBO) { nfe_jpool_free(sc); } else { for (i = 0; i < NFE_RX_RING_COUNT; i++) { data = &ring->data[i]; if (data->rx_data_map != NULL) { bus_dmamap_sync(data->rx_data_tag, data->rx_data_map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(data->rx_data_tag, data->rx_data_map); bus_dmamap_destroy(data->rx_data_tag, data->rx_data_map); bus_dma_tag_destroy(data->rx_data_tag); } if (data->m != NULL) m_freem(data->m); } } } static int nfe_alloc_tx_ring(struct nfe_softc *sc, struct nfe_tx_ring *ring) { int i, error; void **desc; int descsize; if (sc->nfe_flags & NFE_40BIT_ADDR) { desc = (void **)&ring->desc64; descsize = sizeof (struct nfe_desc64); } else { desc = (void **)&ring->desc32; descsize = sizeof (struct nfe_desc32); } ring->queued = 0; ring->cur = ring->next = 0; error = bus_dma_tag_create(sc->nfe_parent_tag, PAGE_SIZE, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ NFE_TX_RING_COUNT * descsize, 1, /* maxsize, nsegments */ NFE_TX_RING_COUNT * descsize, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &ring->tx_desc_tag); if (error != 0) { printf("nfe%d: could not create desc DMA tag\n", sc->nfe_unit); goto fail; } error = bus_dmamem_alloc(ring->tx_desc_tag, (void **)desc, BUS_DMA_NOWAIT, &ring->tx_desc_map); if (error != 0) { printf("nfe%d: could not create desc DMA map\n", sc->nfe_unit); goto fail; } error = bus_dmamap_load(ring->tx_desc_tag, ring->tx_desc_map, *desc, NFE_TX_RING_COUNT * descsize, nfe_dma_map_segs, &ring->tx_desc_segs, BUS_DMA_NOWAIT); if (error != 0) { printf("nfe%d: could not load desc DMA map\n", sc->nfe_unit); goto fail; } bzero(*desc, NFE_TX_RING_COUNT * descsize); ring->tx_desc_addr = ring->tx_desc_segs.ds_addr; ring->physaddr = ring->tx_desc_addr; error = bus_dma_tag_create(sc->nfe_parent_tag, ETHER_ALIGN, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, NFE_JBYTES, NFE_MAX_SCATTER, NFE_JBYTES, BUS_DMA_ALLOCNOW, NULL, NULL, &ring->tx_data_tag); if (error != 0) { printf("nfe%d: could not create DMA tag\n", sc->nfe_unit); goto fail; } for (i = 0; i < NFE_TX_RING_COUNT; i++) { error = bus_dmamap_create(ring->tx_data_tag, 0, &ring->data[i].tx_data_map); if (error != 0) { printf("nfe%d: could not create DMA map\n", sc->nfe_unit); goto fail; } } return 0; fail: nfe_free_tx_ring(sc, ring); return error; } static void nfe_reset_tx_ring(struct nfe_softc *sc, struct nfe_tx_ring *ring) { struct nfe_tx_data *data; int i; for (i = 0; i < NFE_TX_RING_COUNT; i++) { if (sc->nfe_flags & NFE_40BIT_ADDR) ring->desc64[i].flags = 0; else ring->desc32[i].flags = 0; data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->tx_data_tag, data->active, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->tx_data_tag, data->active); m_freem(data->m); data->m = NULL; } } bus_dmamap_sync(ring->tx_desc_tag, ring->tx_desc_map, BUS_DMASYNC_PREWRITE); ring->queued = 0; ring->cur = ring->next = 0; } static void nfe_free_tx_ring(struct nfe_softc *sc, struct nfe_tx_ring *ring) { struct nfe_tx_data *data; void *desc; int i, descsize; if (sc->nfe_flags & NFE_40BIT_ADDR) { desc = ring->desc64; descsize = sizeof (struct nfe_desc64); } else { desc = ring->desc32; descsize = sizeof (struct nfe_desc32); } if (desc != NULL) { bus_dmamap_sync(ring->tx_desc_tag, ring->tx_desc_map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->tx_desc_tag, ring->tx_desc_map); bus_dmamem_free(ring->tx_desc_tag, desc, ring->tx_desc_map); bus_dma_tag_destroy(ring->tx_desc_tag); } for (i = 0; i < NFE_TX_RING_COUNT; i++) { data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->tx_data_tag, data->active, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->tx_data_tag, data->active); m_freem(data->m); } } /* ..and now actually destroy the DMA mappings */ for (i = 0; i < NFE_TX_RING_COUNT; i++) { data = &ring->data[i]; if (data->tx_data_map == NULL) continue; bus_dmamap_destroy(ring->tx_data_tag, data->tx_data_map); } bus_dma_tag_destroy(ring->tx_data_tag); } #ifdef DEVICE_POLLING static poll_handler_t nfe_poll; static void nfe_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct nfe_softc *sc = ifp->if_softc; NFE_LOCK(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) nfe_poll_locked(ifp, cmd, count); NFE_UNLOCK(sc); } static void nfe_poll_locked(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct nfe_softc *sc = ifp->if_softc; u_int32_t r; NFE_LOCK_ASSERT(sc); if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { return; } sc->rxcycles = count; nfe_rxeof(sc); nfe_txeof(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) nfe_start_locked(ifp); if (cmd == POLL_AND_CHECK_STATUS) { if ((r = NFE_READ(sc, NFE_IRQ_STATUS)) == 0) { return; } NFE_WRITE(sc, NFE_IRQ_STATUS, r); if (r & NFE_IRQ_LINK) { NFE_READ(sc, NFE_PHY_STATUS); NFE_WRITE(sc, NFE_PHY_STATUS, 0xf); DPRINTF(("nfe%d: link state changed\n", sc->nfe_unit)); } } } #endif /* DEVICE_POLLING */ static int nfe_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { int error = 0; struct nfe_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; struct mii_data *mii; switch (cmd) { case SIOCSIFMTU: if (ifr->ifr_mtu < ETHERMIN || ((sc->nfe_flags & NFE_USE_JUMBO) && ifr->ifr_mtu > ETHERMTU_JUMBO) || (!(sc->nfe_flags & NFE_USE_JUMBO) && ifr->ifr_mtu > ETHERMTU)) error = EINVAL; else if (ifp->if_mtu != ifr->ifr_mtu) { ifp->if_mtu = ifr->ifr_mtu; ifp->if_drv_flags &= ~IFF_DRV_RUNNING; nfe_init(sc); } break; case SIOCSIFFLAGS: NFE_LOCK(sc); if (ifp->if_flags & IFF_UP) { /* * If only the PROMISC or ALLMULTI flag changes, then * don't do a full re-init of the chip, just update * the Rx filter. */ if ((ifp->if_drv_flags & IFF_DRV_RUNNING) && ((ifp->if_flags ^ sc->nfe_if_flags) & (IFF_ALLMULTI | IFF_PROMISC)) != 0) nfe_setmulti(sc); else nfe_init_locked(sc); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) nfe_stop(ifp, 1); } sc->nfe_if_flags = ifp->if_flags; NFE_UNLOCK(sc); error = 0; break; case SIOCADDMULTI: case SIOCDELMULTI: if (ifp->if_drv_flags & IFF_DRV_RUNNING) { NFE_LOCK(sc); nfe_setmulti(sc); NFE_UNLOCK(sc); error = 0; } break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = device_get_softc(sc->nfe_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: { int mask = ifr->ifr_reqcap ^ ifp->if_capenable; #ifdef DEVICE_POLLING if (mask & IFCAP_POLLING) { if (ifr->ifr_reqcap & IFCAP_POLLING) { error = ether_poll_register(nfe_poll, ifp); if (error) return(error); NFE_LOCK(sc); NFE_WRITE(sc, NFE_IRQ_MASK, 0); ifp->if_capenable |= IFCAP_POLLING; NFE_UNLOCK(sc); } else { error = ether_poll_deregister(ifp); /* Enable interrupt even in error case */ NFE_LOCK(sc); NFE_WRITE(sc, NFE_IRQ_MASK, NFE_IRQ_WANTED); ifp->if_capenable &= ~IFCAP_POLLING; NFE_UNLOCK(sc); } } #endif if (mask & IFCAP_HWCSUM) { ifp->if_capenable ^= IFCAP_HWCSUM; if (IFCAP_HWCSUM & ifp->if_capenable && IFCAP_HWCSUM & ifp->if_capabilities) ifp->if_hwassist = NFE_CSUM_FEATURES; else ifp->if_hwassist = 0; } } break; default: error = ether_ioctl(ifp, cmd, data); break; } return error; } static void nfe_intr(void *arg) { struct nfe_softc *sc = arg; struct ifnet *ifp = sc->nfe_ifp; u_int32_t r; NFE_LOCK(sc); #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { NFE_UNLOCK(sc); return; } #endif if ((r = NFE_READ(sc, NFE_IRQ_STATUS)) == 0) { NFE_UNLOCK(sc); return; /* not for us */ } NFE_WRITE(sc, NFE_IRQ_STATUS, r); DPRINTFN(5, ("nfe_intr: interrupt register %x\n", r)); NFE_WRITE(sc, NFE_IRQ_MASK, 0); if (r & NFE_IRQ_LINK) { NFE_READ(sc, NFE_PHY_STATUS); NFE_WRITE(sc, NFE_PHY_STATUS, 0xf); DPRINTF(("nfe%d: link state changed\n", sc->nfe_unit)); } if (ifp->if_drv_flags & IFF_DRV_RUNNING) { /* check Rx ring */ nfe_rxeof(sc); /* check Tx ring */ nfe_txeof(sc); } NFE_WRITE(sc, NFE_IRQ_MASK, NFE_IRQ_WANTED); if (ifp->if_drv_flags & IFF_DRV_RUNNING && !IFQ_DRV_IS_EMPTY(&ifp->if_snd)) nfe_start_locked(ifp); NFE_UNLOCK(sc); return; } static void nfe_txdesc32_sync(struct nfe_softc *sc, struct nfe_desc32 *desc32, int ops) { bus_dmamap_sync(sc->txq.tx_desc_tag, sc->txq.tx_desc_map, ops); } static void nfe_txdesc64_sync(struct nfe_softc *sc, struct nfe_desc64 *desc64, int ops) { bus_dmamap_sync(sc->txq.tx_desc_tag, sc->txq.tx_desc_map, ops); } static void nfe_txdesc32_rsync(struct nfe_softc *sc, int start, int end, int ops) { bus_dmamap_sync(sc->txq.tx_desc_tag, sc->txq.tx_desc_map, ops); } static void nfe_txdesc64_rsync(struct nfe_softc *sc, int start, int end, int ops) { bus_dmamap_sync(sc->txq.tx_desc_tag, sc->txq.tx_desc_map, ops); } static void nfe_rxdesc32_sync(struct nfe_softc *sc, struct nfe_desc32 *desc32, int ops) { bus_dmamap_sync(sc->rxq.rx_desc_tag, sc->rxq.rx_desc_map, ops); } static void nfe_rxdesc64_sync(struct nfe_softc *sc, struct nfe_desc64 *desc64, int ops) { bus_dmamap_sync(sc->rxq.rx_desc_tag, sc->rxq.rx_desc_map, ops); } static void nfe_rxeof(struct nfe_softc *sc) { struct ifnet *ifp = sc->nfe_ifp; struct nfe_desc32 *desc32=NULL; struct nfe_desc64 *desc64=NULL; struct nfe_rx_data *data; struct nfe_jbuf *jbuf; struct mbuf *m, *mnew; bus_addr_t physaddr; u_int16_t flags; int error, len; #if NVLAN > 1 u_int16_t vlan_tag = 0; int have_tag = 0; #endif NFE_LOCK_ASSERT(sc); for (;;) { #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { if (sc->rxcycles <= 0) break; sc->rxcycles--; } #endif data = &sc->rxq.data[sc->rxq.cur]; if (sc->nfe_flags & NFE_40BIT_ADDR) { desc64 = &sc->rxq.desc64[sc->rxq.cur]; nfe_rxdesc64_sync(sc, desc64, BUS_DMASYNC_POSTREAD); flags = letoh16(desc64->flags); len = letoh16(desc64->length) & 0x3fff; #if NVLAN > 1 if (flags & NFE_TX_VLAN_TAG) { have_tag = 1; vlan_tag = desc64->vtag; } #endif } else { desc32 = &sc->rxq.desc32[sc->rxq.cur]; nfe_rxdesc32_sync(sc, desc32, BUS_DMASYNC_POSTREAD); flags = letoh16(desc32->flags); len = letoh16(desc32->length) & 0x3fff; } if (flags & NFE_RX_READY) break; if ((sc->nfe_flags & (NFE_JUMBO_SUP | NFE_40BIT_ADDR)) == 0) { if (!(flags & NFE_RX_VALID_V1)) goto skip; if ((flags & NFE_RX_FIXME_V1) == NFE_RX_FIXME_V1) { flags &= ~NFE_RX_ERROR; len--; /* fix buffer length */ } } else { if (!(flags & NFE_RX_VALID_V2)) goto skip; if ((flags & NFE_RX_FIXME_V2) == NFE_RX_FIXME_V2) { flags &= ~NFE_RX_ERROR; len--; /* fix buffer length */ } } if (flags & NFE_RX_ERROR) { ifp->if_ierrors++; goto skip; } /* * Try to allocate a new mbuf for this ring element and load * it before processing the current mbuf. If the ring element * cannot be loaded, drop the received packet and reuse the * old mbuf. In the unlikely case that the old mbuf can't be * reloaded either, explicitly panic. */ MGETHDR(mnew, M_DONTWAIT, MT_DATA); if (mnew == NULL) { ifp->if_ierrors++; goto skip; } if (sc->nfe_flags & NFE_USE_JUMBO) { if ((jbuf = nfe_jalloc(sc)) == NULL) { m_freem(mnew); ifp->if_ierrors++; goto skip; } mnew->m_data = (void *)jbuf->buf; mnew->m_len = mnew->m_pkthdr.len = NFE_JBYTES; MEXTADD(mnew, jbuf->buf, NFE_JBYTES, nfe_jfree, (struct nfe_softc *)sc, 0 , EXT_NET_DRV); bus_dmamap_sync(sc->rxq.rx_jumbo_tag, sc->rxq.rx_jumbo_map, BUS_DMASYNC_POSTREAD); physaddr = jbuf->physaddr; } else { MCLGET(mnew, M_DONTWAIT); if (!(mnew->m_flags & M_EXT)) { m_freem(mnew); ifp->if_ierrors++; goto skip; } bus_dmamap_sync(data->rx_data_tag, data->rx_data_map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(data->rx_data_tag, data->rx_data_map); error = bus_dmamap_load(data->rx_data_tag, data->rx_data_map, mtod(mnew, void *), MCLBYTES, nfe_dma_map_segs, &data->rx_data_segs, BUS_DMA_NOWAIT); if (error != 0) { m_freem(mnew); /* try to reload the old mbuf */ error = bus_dmamap_load(data->rx_data_tag, data->rx_data_map, mtod(data->m, void *), MCLBYTES, nfe_dma_map_segs, &data->rx_data_segs, BUS_DMA_NOWAIT); if (error != 0) { /* very unlikely that it will fail.. */ panic("nfe%d: could not load old rx mbuf", sc->nfe_unit); } ifp->if_ierrors++; goto skip; } data->rx_data_addr = data->rx_data_segs.ds_addr; physaddr = data->rx_data_addr; } /* * New mbuf successfully loaded, update Rx ring and continue * processing. */ m = data->m; data->m = mnew; /* finalize mbuf */ m->m_pkthdr.len = m->m_len = len; m->m_pkthdr.rcvif = ifp; #if defined(NFE_CSUM) if ((sc->nfe_flags & NFE_HW_CSUM) && (flags & NFE_RX_CSUMOK)) { m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; if (flags & NFE_RX_IP_CSUMOK_V2) { m->m_pkthdr.csum_flags |= CSUM_IP_VALID; } if (flags & NFE_RX_UDP_CSUMOK_V2 || flags & NFE_RX_TCP_CSUMOK_V2) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID|CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } #endif #if NVLAN > 1 if (have_tag) { - VLAN_INPUT_TAG_NEW(ifp, m, vlan_tag); - if (m == NULL) - continue; + m->m_pkthdr.ether_vtag = vlan_tag; + m->m_flags |= M_VLANTAG; } #endif ifp->if_ipackets++; NFE_UNLOCK(sc); (*ifp->if_input)(ifp, m); NFE_LOCK(sc); /* update mapping address in h/w descriptor */ if (sc->nfe_flags & NFE_40BIT_ADDR) { #if defined(__LP64__) desc64->physaddr[0] = htole32(physaddr >> 32); #endif desc64->physaddr[1] = htole32(physaddr & 0xffffffff); } else { desc32->physaddr = htole32(physaddr); } skip: if (sc->nfe_flags & NFE_40BIT_ADDR) { desc64->length = htole16(sc->rxq.bufsz); desc64->flags = htole16(NFE_RX_READY); nfe_rxdesc64_sync(sc, desc64, BUS_DMASYNC_PREWRITE); } else { desc32->length = htole16(sc->rxq.bufsz); desc32->flags = htole16(NFE_RX_READY); nfe_rxdesc32_sync(sc, desc32, BUS_DMASYNC_PREWRITE); } sc->rxq.cur = (sc->rxq.cur + 1) % NFE_RX_RING_COUNT; } } static void nfe_txeof(struct nfe_softc *sc) { struct ifnet *ifp = sc->nfe_ifp; struct nfe_desc32 *desc32; struct nfe_desc64 *desc64; struct nfe_tx_data *data = NULL; u_int16_t flags; NFE_LOCK_ASSERT(sc); while (sc->txq.next != sc->txq.cur) { if (sc->nfe_flags & NFE_40BIT_ADDR) { desc64 = &sc->txq.desc64[sc->txq.next]; nfe_txdesc64_sync(sc, desc64, BUS_DMASYNC_POSTREAD); flags = letoh16(desc64->flags); } else { desc32 = &sc->txq.desc32[sc->txq.next]; nfe_txdesc32_sync(sc, desc32, BUS_DMASYNC_POSTREAD); flags = letoh16(desc32->flags); } if (flags & NFE_TX_VALID) break; data = &sc->txq.data[sc->txq.next]; if ((sc->nfe_flags & (NFE_JUMBO_SUP | NFE_40BIT_ADDR)) == 0) { if (!(flags & NFE_TX_LASTFRAG_V1) && data->m == NULL) goto skip; if ((flags & NFE_TX_ERROR_V1) != 0) { printf("nfe%d: tx v1 error 0x%4b\n", sc->nfe_unit, flags, NFE_V1_TXERR); ifp->if_oerrors++; } else ifp->if_opackets++; } else { if (!(flags & NFE_TX_LASTFRAG_V2) && data->m == NULL) goto skip; if ((flags & NFE_TX_ERROR_V2) != 0) { printf("nfe%d: tx v1 error 0x%4b\n", sc->nfe_unit, flags, NFE_V2_TXERR); ifp->if_oerrors++; } else ifp->if_opackets++; } if (data->m == NULL) { /* should not get there */ printf("nfe%d: last fragment bit w/o associated mbuf!\n", sc->nfe_unit); goto skip; } /* last fragment of the mbuf chain transmitted */ bus_dmamap_sync(sc->txq.tx_data_tag, data->active, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->txq.tx_data_tag, data->active); m_freem(data->m); data->m = NULL; ifp->if_timer = 0; skip: sc->txq.queued--; sc->txq.next = (sc->txq.next + 1) % NFE_TX_RING_COUNT; } if (data != NULL) { /* at least one slot freed */ ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; nfe_start_locked(ifp); } } static int nfe_encap(struct nfe_softc *sc, struct mbuf *m0) { struct nfe_desc32 *desc32=NULL; struct nfe_desc64 *desc64=NULL; struct nfe_tx_data *data=NULL; bus_dmamap_t map; u_int16_t flags = NFE_TX_VALID; -#if NVLAN > 0 - struct m_tag *vtag; -#endif bus_dma_segment_t segs[NFE_MAX_SCATTER]; int nsegs; int error, i; map = sc->txq.data[sc->txq.cur].tx_data_map; error = bus_dmamap_load_mbuf_sg(sc->txq.tx_data_tag, map, m0, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { printf("nfe%d: could not map mbuf (error %d)\n", sc->nfe_unit, error); return error; } if (sc->txq.queued + nsegs >= NFE_TX_RING_COUNT - 1) { bus_dmamap_unload(sc->txq.tx_data_tag, map); return ENOBUFS; } -#if NVLAN > 0 - /* setup h/w VLAN tagging */ - vtag = VLAN_OUTPUT_TAG(sc->nfe_ifp, m0); -#endif - #ifdef NFE_CSUM if (m0->m_pkthdr.csum_flags & CSUM_IP) flags |= NFE_TX_IP_CSUM; if (m0->m_pkthdr.csum_flags & CSUM_TCP) flags |= NFE_TX_TCP_CSUM; if (m0->m_pkthdr.csum_flags & CSUM_UDP) flags |= NFE_TX_TCP_CSUM; #endif for (i = 0; i < nsegs; i++) { data = &sc->txq.data[sc->txq.cur]; if (sc->nfe_flags & NFE_40BIT_ADDR) { desc64 = &sc->txq.desc64[sc->txq.cur]; #if defined(__LP64__) desc64->physaddr[0] = htole32(segs[i].ds_addr >> 32); #endif desc64->physaddr[1] = htole32(segs[i].ds_addr & 0xffffffff); desc64->length = htole16(segs[i].ds_len - 1); desc64->flags = htole16(flags); #if NVLAN > 0 - desc64->vtag = htole32(NFE_TX_VTAG | - VLAN_TAG_VALUE(vtag)); + if (m0->m_flags & M_VLANTAG) + desc64->vtag = htole32(NFE_TX_VTAG | + m0->m_pkthdr.ether_vtag); #endif } else { desc32 = &sc->txq.desc32[sc->txq.cur]; desc32->physaddr = htole32(segs[i].ds_addr); desc32->length = htole16(segs[i].ds_len - 1); desc32->flags = htole16(flags); } /* csum flags and vtag belong to the first fragment only */ if (nsegs > 1) { flags &= ~(NFE_TX_IP_CSUM | NFE_TX_TCP_CSUM); -#if NVLAN > 0 - vtag = 0; -#endif } sc->txq.queued++; sc->txq.cur = (sc->txq.cur + 1) % NFE_TX_RING_COUNT; } /* the whole mbuf chain has been DMA mapped, fix last descriptor */ if (sc->nfe_flags & NFE_40BIT_ADDR) { flags |= NFE_TX_LASTFRAG_V2; desc64->flags = htole16(flags); } else { if (sc->nfe_flags & NFE_JUMBO_SUP) flags |= NFE_TX_LASTFRAG_V2; else flags |= NFE_TX_LASTFRAG_V1; desc32->flags = htole16(flags); } data->m = m0; data->active = map; data->nsegs = nsegs; bus_dmamap_sync(sc->txq.tx_data_tag, map, BUS_DMASYNC_PREWRITE); return 0; } static void nfe_setmulti(struct nfe_softc *sc) { struct ifnet *ifp = sc->nfe_ifp; struct ifmultiaddr *ifma; u_int8_t addr[ETHER_ADDR_LEN], mask[ETHER_ADDR_LEN]; u_int32_t filter = NFE_RXFILTER_MAGIC; u_int8_t etherbroadcastaddr[ETHER_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; int i; NFE_LOCK_ASSERT(sc); if ((ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC)) != 0) { bzero(addr, ETHER_ADDR_LEN); bzero(mask, ETHER_ADDR_LEN); goto done; } bcopy(etherbroadcastaddr, addr, ETHER_ADDR_LEN); bcopy(etherbroadcastaddr, mask, ETHER_ADDR_LEN); IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { u_char *addrp; if (ifma->ifma_addr->sa_family != AF_LINK) continue; addrp = LLADDR((struct sockaddr_dl *) ifma->ifma_addr); for (i = 0; i < ETHER_ADDR_LEN; i++) { u_int8_t mcaddr = addrp[i]; addr[i] &= mcaddr; mask[i] &= ~mcaddr; } } IF_ADDR_UNLOCK(ifp); for (i = 0; i < ETHER_ADDR_LEN; i++) { mask[i] |= addr[i]; } done: addr[0] |= 0x01; /* make sure multicast bit is set */ NFE_WRITE(sc, NFE_MULTIADDR_HI, addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]); NFE_WRITE(sc, NFE_MULTIADDR_LO, addr[5] << 8 | addr[4]); NFE_WRITE(sc, NFE_MULTIMASK_HI, mask[3] << 24 | mask[2] << 16 | mask[1] << 8 | mask[0]); NFE_WRITE(sc, NFE_MULTIMASK_LO, mask[5] << 8 | mask[4]); filter |= (ifp->if_flags & IFF_PROMISC) ? NFE_PROMISC : NFE_U2M; NFE_WRITE(sc, NFE_RXFILTER, filter); } static void nfe_start(struct ifnet *ifp) { struct nfe_softc *sc; sc = ifp->if_softc; NFE_LOCK(sc); nfe_start_locked(ifp); NFE_UNLOCK(sc); } static void nfe_start_locked(struct ifnet *ifp) { struct nfe_softc *sc = ifp->if_softc; int old = sc->txq.cur; struct mbuf *m0; if (!sc->nfe_link || ifp->if_drv_flags & IFF_DRV_OACTIVE) { return; } for (;;) { IFQ_POLL(&ifp->if_snd, m0); if (m0 == NULL) break; if (nfe_encap(sc, m0) != 0) { ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } /* packet put in h/w queue, remove from s/w queue */ IFQ_DEQUEUE(&ifp->if_snd, m0); BPF_MTAP(ifp, m0); } if (sc->txq.cur == old) { /* nothing sent */ return; } if (sc->nfe_flags & NFE_40BIT_ADDR) nfe_txdesc64_rsync(sc, old, sc->txq.cur, BUS_DMASYNC_PREWRITE); else nfe_txdesc32_rsync(sc, old, sc->txq.cur, BUS_DMASYNC_PREWRITE); /* kick Tx */ NFE_WRITE(sc, NFE_RXTX_CTL, NFE_RXTX_KICKTX | sc->rxtxctl); /* * Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; return; } static void nfe_watchdog(struct ifnet *ifp) { struct nfe_softc *sc = ifp->if_softc; printf("nfe%d: watchdog timeout\n", sc->nfe_unit); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; nfe_init(sc); ifp->if_oerrors++; return; } static void nfe_init(void *xsc) { struct nfe_softc *sc = xsc; NFE_LOCK(sc); nfe_init_locked(sc); NFE_UNLOCK(sc); return; } static void nfe_init_locked(void *xsc) { struct nfe_softc *sc = xsc; struct ifnet *ifp = sc->nfe_ifp; struct mii_data *mii; u_int32_t tmp; NFE_LOCK_ASSERT(sc); mii = device_get_softc(sc->nfe_miibus); if (ifp->if_drv_flags & IFF_DRV_RUNNING) { return; } nfe_stop(ifp, 0); NFE_WRITE(sc, NFE_TX_UNK, 0); NFE_WRITE(sc, NFE_STATUS, 0); sc->rxtxctl = NFE_RXTX_BIT2; if (sc->nfe_flags & NFE_40BIT_ADDR) sc->rxtxctl |= NFE_RXTX_V3MAGIC; else if (sc->nfe_flags & NFE_JUMBO_SUP) sc->rxtxctl |= NFE_RXTX_V2MAGIC; #ifdef NFE_CSUM if (sc->nfe_flags & NFE_HW_CSUM) sc->rxtxctl |= NFE_RXTX_RXCSUM; #endif #if NVLAN > 0 /* * Although the adapter is capable of stripping VLAN tags from received * frames (NFE_RXTX_VTAG_STRIP), we do not enable this functionality on * purpose. This will be done in software by our network stack. */ if (sc->nfe_flags & NFE_HW_VLAN) sc->rxtxctl |= NFE_RXTX_VTAG_INSERT; #endif NFE_WRITE(sc, NFE_RXTX_CTL, NFE_RXTX_RESET | sc->rxtxctl); DELAY(10); NFE_WRITE(sc, NFE_RXTX_CTL, sc->rxtxctl); #if NVLAN if (sc->nfe_flags & NFE_HW_VLAN) NFE_WRITE(sc, NFE_VTAG_CTL, NFE_VTAG_ENABLE); #endif NFE_WRITE(sc, NFE_SETUP_R6, 0); /* set MAC address */ nfe_set_macaddr(sc, sc->eaddr); /* tell MAC where rings are in memory */ #ifdef __LP64__ NFE_WRITE(sc, NFE_RX_RING_ADDR_HI, sc->rxq.physaddr >> 32); #endif NFE_WRITE(sc, NFE_RX_RING_ADDR_LO, sc->rxq.physaddr & 0xffffffff); #ifdef __LP64__ NFE_WRITE(sc, NFE_TX_RING_ADDR_HI, sc->txq.physaddr >> 32); #endif NFE_WRITE(sc, NFE_TX_RING_ADDR_LO, sc->txq.physaddr & 0xffffffff); NFE_WRITE(sc, NFE_RING_SIZE, (NFE_RX_RING_COUNT - 1) << 16 | (NFE_TX_RING_COUNT - 1)); NFE_WRITE(sc, NFE_RXBUFSZ, sc->rxq.bufsz); /* force MAC to wakeup */ tmp = NFE_READ(sc, NFE_PWR_STATE); NFE_WRITE(sc, NFE_PWR_STATE, tmp | NFE_PWR_WAKEUP); DELAY(10); tmp = NFE_READ(sc, NFE_PWR_STATE); NFE_WRITE(sc, NFE_PWR_STATE, tmp | NFE_PWR_VALID); #if 1 /* configure interrupts coalescing/mitigation */ NFE_WRITE(sc, NFE_IMTIMER, NFE_IM_DEFAULT); #else /* no interrupt mitigation: one interrupt per packet */ NFE_WRITE(sc, NFE_IMTIMER, 970); #endif NFE_WRITE(sc, NFE_SETUP_R1, NFE_R1_MAGIC); NFE_WRITE(sc, NFE_SETUP_R2, NFE_R2_MAGIC); NFE_WRITE(sc, NFE_SETUP_R6, NFE_R6_MAGIC); /* update MAC knowledge of PHY; generates a NFE_IRQ_LINK interrupt */ NFE_WRITE(sc, NFE_STATUS, sc->mii_phyaddr << 24 | NFE_STATUS_MAGIC); NFE_WRITE(sc, NFE_SETUP_R4, NFE_R4_MAGIC); NFE_WRITE(sc, NFE_WOL_CTL, NFE_WOL_MAGIC); sc->rxtxctl &= ~NFE_RXTX_BIT2; NFE_WRITE(sc, NFE_RXTX_CTL, sc->rxtxctl); DELAY(10); NFE_WRITE(sc, NFE_RXTX_CTL, NFE_RXTX_BIT1 | sc->rxtxctl); /* set Rx filter */ nfe_setmulti(sc); nfe_ifmedia_upd(ifp); nfe_tick_locked(sc); /* enable Rx */ NFE_WRITE(sc, NFE_RX_CTL, NFE_RX_START); /* enable Tx */ NFE_WRITE(sc, NFE_TX_CTL, NFE_TX_START); NFE_WRITE(sc, NFE_PHY_STATUS, 0xf); #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) NFE_WRITE(sc, NFE_IRQ_MASK, 0); else #endif NFE_WRITE(sc, NFE_IRQ_MASK, NFE_IRQ_WANTED); /* enable interrupts */ ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->nfe_link = 0; return; } static void nfe_stop(struct ifnet *ifp, int disable) { struct nfe_softc *sc = ifp->if_softc; struct mii_data *mii; NFE_LOCK_ASSERT(sc); ifp->if_timer = 0; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); mii = device_get_softc(sc->nfe_miibus); callout_stop(&sc->nfe_stat_ch); /* abort Tx */ NFE_WRITE(sc, NFE_TX_CTL, 0); /* disable Rx */ NFE_WRITE(sc, NFE_RX_CTL, 0); /* disable interrupts */ NFE_WRITE(sc, NFE_IRQ_MASK, 0); sc->nfe_link = 0; /* reset Tx and Rx rings */ nfe_reset_tx_ring(sc, &sc->txq); nfe_reset_rx_ring(sc, &sc->rxq); return; } static int nfe_ifmedia_upd(struct ifnet *ifp) { struct nfe_softc *sc = ifp->if_softc; NFE_LOCK(sc); nfe_ifmedia_upd_locked(ifp); NFE_UNLOCK(sc); return (0); } static int nfe_ifmedia_upd_locked(struct ifnet *ifp) { struct nfe_softc *sc = ifp->if_softc; struct mii_data *mii; NFE_LOCK_ASSERT(sc); mii = device_get_softc(sc->nfe_miibus); if (mii->mii_instance) { struct mii_softc *miisc; for (miisc = LIST_FIRST(&mii->mii_phys); miisc != NULL; miisc = LIST_NEXT(miisc, mii_list)) { mii_phy_reset(miisc); } } mii_mediachg(mii); return (0); } static void nfe_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct nfe_softc *sc; struct mii_data *mii; sc = ifp->if_softc; NFE_LOCK(sc); mii = device_get_softc(sc->nfe_miibus); mii_pollstat(mii); NFE_UNLOCK(sc); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; return; } static void nfe_tick(void *xsc) { struct nfe_softc *sc; sc = xsc; NFE_LOCK(sc); nfe_tick_locked(sc); NFE_UNLOCK(sc); } void nfe_tick_locked(struct nfe_softc *arg) { struct nfe_softc *sc; struct mii_data *mii; struct ifnet *ifp; sc = arg; NFE_LOCK_ASSERT(sc); ifp = sc->nfe_ifp; mii = device_get_softc(sc->nfe_miibus); mii_tick(mii); if (!sc->nfe_link) { if (mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->nfe_link++; if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T && bootverbose) if_printf(sc->nfe_ifp, "gigabit link up\n"); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) nfe_start_locked(ifp); } } callout_reset(&sc->nfe_stat_ch, hz, nfe_tick, sc); return; } static void nfe_shutdown(device_t dev) { struct nfe_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); NFE_LOCK(sc); ifp = sc->nfe_ifp; nfe_stop(ifp,0); /* nfe_reset(sc); */ NFE_UNLOCK(sc); return; } static void nfe_get_macaddr(struct nfe_softc *sc, u_char *addr) { uint32_t tmp; tmp = NFE_READ(sc, NFE_MACADDR_LO); addr[0] = (tmp >> 8) & 0xff; addr[1] = (tmp & 0xff); tmp = NFE_READ(sc, NFE_MACADDR_HI); addr[2] = (tmp >> 24) & 0xff; addr[3] = (tmp >> 16) & 0xff; addr[4] = (tmp >> 8) & 0xff; addr[5] = (tmp & 0xff); } static void nfe_set_macaddr(struct nfe_softc *sc, u_char *addr) { NFE_WRITE(sc, NFE_MACADDR_LO, addr[5] << 8 | addr[4]); NFE_WRITE(sc, NFE_MACADDR_HI, addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]); } /* * Map a single buffer address. */ static void nfe_dma_map_segs(arg, segs, nseg, error) void *arg; bus_dma_segment_t *segs; int error, nseg; { if (error) return; KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg)); *(bus_dma_segment_t *)arg = *segs; return; } Index: head/sys/dev/nge/if_nge.c =================================================================== --- head/sys/dev/nge/if_nge.c (revision 162374) +++ head/sys/dev/nge/if_nge.c (revision 162375) @@ -1,2181 +1,2178 @@ /*- * Copyright (c) 2001 Wind River Systems * Copyright (c) 1997, 1998, 1999, 2000, 2001 * Bill Paul . 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * National Semiconductor DP83820/DP83821 gigabit ethernet driver * for FreeBSD. Datasheets are available from: * * http://www.national.com/ds/DP/DP83820.pdf * http://www.national.com/ds/DP/DP83821.pdf * * These chips are used on several low cost gigabit ethernet NICs * sold by D-Link, Addtron, SMC and Asante. Both parts are * virtually the same, except the 83820 is a 64-bit/32-bit part, * while the 83821 is 32-bit only. * * Many cards also use National gigE transceivers, such as the * DP83891, DP83861 and DP83862 gigPHYTER parts. The DP83861 datasheet * contains a full register description that applies to all of these * components: * * http://www.national.com/ds/DP/DP83861.pdf * * Written by Bill Paul * BSDi Open Source Solutions */ /* * The NatSemi DP83820 and 83821 controllers are enhanced versions * of the NatSemi MacPHYTER 10/100 devices. They support 10, 100 * and 1000Mbps speeds with 1000baseX (ten bit interface), MII and GMII * ports. Other features include 8K TX FIFO and 32K RX FIFO, TCP/IP * hardware checksum offload (IPv4 only), VLAN tagging and filtering, * priority TX and RX queues, a 2048 bit multicast hash filter, 4 RX pattern * matching buffers, one perfect address filter buffer and interrupt * moderation. The 83820 supports both 64-bit and 32-bit addressing * and data transfers: the 64-bit support can be toggled on or off * via software. This affects the size of certain fields in the DMA * descriptors. * * There are two bugs/misfeatures in the 83820/83821 that I have * discovered so far: * * - Receive buffers must be aligned on 64-bit boundaries, which means * you must resort to copying data in order to fix up the payload * alignment. * * - In order to transmit jumbo frames larger than 8170 bytes, you have * to turn off transmit checksum offloading, because the chip can't * compute the checksum on an outgoing frame unless it fits entirely * within the TX FIFO, which is only 8192 bytes in size. If you have * TX checksum offload enabled and you transmit attempt to transmit a * frame larger than 8170 bytes, the transmitter will wedge. * * To work around the latter problem, TX checksum offload is disabled * if the user selects an MTU larger than 8152 (8170 - 18). */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_device_polling.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for vtophys */ #include /* for vtophys */ #include #include #include #include #include #include #include #include #define NGE_USEIOSPACE #include MODULE_DEPEND(nge, pci, 1, 1, 1); MODULE_DEPEND(nge, ether, 1, 1, 1); MODULE_DEPEND(nge, miibus, 1, 1, 1); /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" #define NGE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) /* * Various supported device vendors/types and their names. */ static struct nge_type nge_devs[] = { { NGE_VENDORID, NGE_DEVICEID, "National Semiconductor Gigabit Ethernet" }, { 0, 0, NULL } }; static int nge_probe(device_t); static int nge_attach(device_t); static int nge_detach(device_t); static int nge_newbuf(struct nge_softc *, struct nge_desc *, struct mbuf *); static int nge_encap(struct nge_softc *, struct mbuf *, u_int32_t *); #ifdef NGE_FIXUP_RX static __inline void nge_fixup_rx (struct mbuf *); #endif static void nge_rxeof(struct nge_softc *); static void nge_txeof(struct nge_softc *); static void nge_intr(void *); static void nge_tick(void *); static void nge_start(struct ifnet *); static void nge_start_locked(struct ifnet *); static int nge_ioctl(struct ifnet *, u_long, caddr_t); static void nge_init(void *); static void nge_init_locked(struct nge_softc *); static void nge_stop(struct nge_softc *); static void nge_watchdog(struct ifnet *); static void nge_shutdown(device_t); static int nge_ifmedia_upd(struct ifnet *); static void nge_ifmedia_upd_locked(struct ifnet *); static void nge_ifmedia_sts(struct ifnet *, struct ifmediareq *); static void nge_delay(struct nge_softc *); static void nge_eeprom_idle(struct nge_softc *); static void nge_eeprom_putbyte(struct nge_softc *, int); static void nge_eeprom_getword(struct nge_softc *, int, u_int16_t *); static void nge_read_eeprom(struct nge_softc *, caddr_t, int, int, int); static void nge_mii_sync(struct nge_softc *); static void nge_mii_send(struct nge_softc *, u_int32_t, int); static int nge_mii_readreg(struct nge_softc *, struct nge_mii_frame *); static int nge_mii_writereg(struct nge_softc *, struct nge_mii_frame *); static int nge_miibus_readreg(device_t, int, int); static int nge_miibus_writereg(device_t, int, int, int); static void nge_miibus_statchg(device_t); static void nge_setmulti(struct nge_softc *); static void nge_reset(struct nge_softc *); static int nge_list_rx_init(struct nge_softc *); static int nge_list_tx_init(struct nge_softc *); #ifdef NGE_USEIOSPACE #define NGE_RES SYS_RES_IOPORT #define NGE_RID NGE_PCI_LOIO #else #define NGE_RES SYS_RES_MEMORY #define NGE_RID NGE_PCI_LOMEM #endif static device_method_t nge_methods[] = { /* Device interface */ DEVMETHOD(device_probe, nge_probe), DEVMETHOD(device_attach, nge_attach), DEVMETHOD(device_detach, nge_detach), DEVMETHOD(device_shutdown, nge_shutdown), /* bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, nge_miibus_readreg), DEVMETHOD(miibus_writereg, nge_miibus_writereg), DEVMETHOD(miibus_statchg, nge_miibus_statchg), { 0, 0 } }; static driver_t nge_driver = { "nge", nge_methods, sizeof(struct nge_softc) }; static devclass_t nge_devclass; DRIVER_MODULE(nge, pci, nge_driver, nge_devclass, 0, 0); DRIVER_MODULE(miibus, nge, miibus_driver, miibus_devclass, 0, 0); #define NGE_SETBIT(sc, reg, x) \ CSR_WRITE_4(sc, reg, \ CSR_READ_4(sc, reg) | (x)) #define NGE_CLRBIT(sc, reg, x) \ CSR_WRITE_4(sc, reg, \ CSR_READ_4(sc, reg) & ~(x)) #define SIO_SET(x) \ CSR_WRITE_4(sc, NGE_MEAR, CSR_READ_4(sc, NGE_MEAR) | (x)) #define SIO_CLR(x) \ CSR_WRITE_4(sc, NGE_MEAR, CSR_READ_4(sc, NGE_MEAR) & ~(x)) static void nge_delay(sc) struct nge_softc *sc; { int idx; for (idx = (300 / 33) + 1; idx > 0; idx--) CSR_READ_4(sc, NGE_CSR); return; } static void nge_eeprom_idle(sc) struct nge_softc *sc; { register int i; SIO_SET(NGE_MEAR_EE_CSEL); nge_delay(sc); SIO_SET(NGE_MEAR_EE_CLK); nge_delay(sc); for (i = 0; i < 25; i++) { SIO_CLR(NGE_MEAR_EE_CLK); nge_delay(sc); SIO_SET(NGE_MEAR_EE_CLK); nge_delay(sc); } SIO_CLR(NGE_MEAR_EE_CLK); nge_delay(sc); SIO_CLR(NGE_MEAR_EE_CSEL); nge_delay(sc); CSR_WRITE_4(sc, NGE_MEAR, 0x00000000); return; } /* * Send a read command and address to the EEPROM, check for ACK. */ static void nge_eeprom_putbyte(sc, addr) struct nge_softc *sc; int addr; { register int d, i; d = addr | NGE_EECMD_READ; /* * Feed in each bit and stobe the clock. */ for (i = 0x400; i; i >>= 1) { if (d & i) { SIO_SET(NGE_MEAR_EE_DIN); } else { SIO_CLR(NGE_MEAR_EE_DIN); } nge_delay(sc); SIO_SET(NGE_MEAR_EE_CLK); nge_delay(sc); SIO_CLR(NGE_MEAR_EE_CLK); nge_delay(sc); } return; } /* * Read a word of data stored in the EEPROM at address 'addr.' */ static void nge_eeprom_getword(sc, addr, dest) struct nge_softc *sc; int addr; u_int16_t *dest; { register int i; u_int16_t word = 0; /* Force EEPROM to idle state. */ nge_eeprom_idle(sc); /* Enter EEPROM access mode. */ nge_delay(sc); SIO_CLR(NGE_MEAR_EE_CLK); nge_delay(sc); SIO_SET(NGE_MEAR_EE_CSEL); nge_delay(sc); /* * Send address of word we want to read. */ nge_eeprom_putbyte(sc, addr); /* * Start reading bits from EEPROM. */ for (i = 0x8000; i; i >>= 1) { SIO_SET(NGE_MEAR_EE_CLK); nge_delay(sc); if (CSR_READ_4(sc, NGE_MEAR) & NGE_MEAR_EE_DOUT) word |= i; nge_delay(sc); SIO_CLR(NGE_MEAR_EE_CLK); nge_delay(sc); } /* Turn off EEPROM access mode. */ nge_eeprom_idle(sc); *dest = word; return; } /* * Read a sequence of words from the EEPROM. */ static void nge_read_eeprom(sc, dest, off, cnt, swap) struct nge_softc *sc; caddr_t dest; int off; int cnt; int swap; { int i; u_int16_t word = 0, *ptr; for (i = 0; i < cnt; i++) { nge_eeprom_getword(sc, off + i, &word); ptr = (u_int16_t *)(dest + (i * 2)); if (swap) *ptr = ntohs(word); else *ptr = word; } return; } /* * Sync the PHYs by setting data bit and strobing the clock 32 times. */ static void nge_mii_sync(sc) struct nge_softc *sc; { register int i; SIO_SET(NGE_MEAR_MII_DIR|NGE_MEAR_MII_DATA); for (i = 0; i < 32; i++) { SIO_SET(NGE_MEAR_MII_CLK); DELAY(1); SIO_CLR(NGE_MEAR_MII_CLK); DELAY(1); } return; } /* * Clock a series of bits through the MII. */ static void nge_mii_send(sc, bits, cnt) struct nge_softc *sc; u_int32_t bits; int cnt; { int i; SIO_CLR(NGE_MEAR_MII_CLK); for (i = (0x1 << (cnt - 1)); i; i >>= 1) { if (bits & i) { SIO_SET(NGE_MEAR_MII_DATA); } else { SIO_CLR(NGE_MEAR_MII_DATA); } DELAY(1); SIO_CLR(NGE_MEAR_MII_CLK); DELAY(1); SIO_SET(NGE_MEAR_MII_CLK); } } /* * Read an PHY register through the MII. */ static int nge_mii_readreg(sc, frame) struct nge_softc *sc; struct nge_mii_frame *frame; { int i, ack; /* * Set up frame for RX. */ frame->mii_stdelim = NGE_MII_STARTDELIM; frame->mii_opcode = NGE_MII_READOP; frame->mii_turnaround = 0; frame->mii_data = 0; CSR_WRITE_4(sc, NGE_MEAR, 0); /* * Turn on data xmit. */ SIO_SET(NGE_MEAR_MII_DIR); nge_mii_sync(sc); /* * Send command/address info. */ nge_mii_send(sc, frame->mii_stdelim, 2); nge_mii_send(sc, frame->mii_opcode, 2); nge_mii_send(sc, frame->mii_phyaddr, 5); nge_mii_send(sc, frame->mii_regaddr, 5); /* Idle bit */ SIO_CLR((NGE_MEAR_MII_CLK|NGE_MEAR_MII_DATA)); DELAY(1); SIO_SET(NGE_MEAR_MII_CLK); DELAY(1); /* Turn off xmit. */ SIO_CLR(NGE_MEAR_MII_DIR); /* Check for ack */ SIO_CLR(NGE_MEAR_MII_CLK); DELAY(1); ack = CSR_READ_4(sc, NGE_MEAR) & NGE_MEAR_MII_DATA; SIO_SET(NGE_MEAR_MII_CLK); DELAY(1); /* * Now try reading data bits. If the ack failed, we still * need to clock through 16 cycles to keep the PHY(s) in sync. */ if (ack) { for(i = 0; i < 16; i++) { SIO_CLR(NGE_MEAR_MII_CLK); DELAY(1); SIO_SET(NGE_MEAR_MII_CLK); DELAY(1); } goto fail; } for (i = 0x8000; i; i >>= 1) { SIO_CLR(NGE_MEAR_MII_CLK); DELAY(1); if (!ack) { if (CSR_READ_4(sc, NGE_MEAR) & NGE_MEAR_MII_DATA) frame->mii_data |= i; DELAY(1); } SIO_SET(NGE_MEAR_MII_CLK); DELAY(1); } fail: SIO_CLR(NGE_MEAR_MII_CLK); DELAY(1); SIO_SET(NGE_MEAR_MII_CLK); DELAY(1); if (ack) return(1); return(0); } /* * Write to a PHY register through the MII. */ static int nge_mii_writereg(sc, frame) struct nge_softc *sc; struct nge_mii_frame *frame; { /* * Set up frame for TX. */ frame->mii_stdelim = NGE_MII_STARTDELIM; frame->mii_opcode = NGE_MII_WRITEOP; frame->mii_turnaround = NGE_MII_TURNAROUND; /* * Turn on data output. */ SIO_SET(NGE_MEAR_MII_DIR); nge_mii_sync(sc); nge_mii_send(sc, frame->mii_stdelim, 2); nge_mii_send(sc, frame->mii_opcode, 2); nge_mii_send(sc, frame->mii_phyaddr, 5); nge_mii_send(sc, frame->mii_regaddr, 5); nge_mii_send(sc, frame->mii_turnaround, 2); nge_mii_send(sc, frame->mii_data, 16); /* Idle bit. */ SIO_SET(NGE_MEAR_MII_CLK); DELAY(1); SIO_CLR(NGE_MEAR_MII_CLK); DELAY(1); /* * Turn off xmit. */ SIO_CLR(NGE_MEAR_MII_DIR); return(0); } static int nge_miibus_readreg(dev, phy, reg) device_t dev; int phy, reg; { struct nge_softc *sc; struct nge_mii_frame frame; sc = device_get_softc(dev); bzero((char *)&frame, sizeof(frame)); frame.mii_phyaddr = phy; frame.mii_regaddr = reg; nge_mii_readreg(sc, &frame); return(frame.mii_data); } static int nge_miibus_writereg(dev, phy, reg, data) device_t dev; int phy, reg, data; { struct nge_softc *sc; struct nge_mii_frame frame; sc = device_get_softc(dev); bzero((char *)&frame, sizeof(frame)); frame.mii_phyaddr = phy; frame.mii_regaddr = reg; frame.mii_data = data; nge_mii_writereg(sc, &frame); return(0); } static void nge_miibus_statchg(dev) device_t dev; { int status; struct nge_softc *sc; struct mii_data *mii; sc = device_get_softc(dev); if (sc->nge_tbi) { if (IFM_SUBTYPE(sc->nge_ifmedia.ifm_cur->ifm_media) == IFM_AUTO) { status = CSR_READ_4(sc, NGE_TBI_ANLPAR); if (status == 0 || status & NGE_TBIANAR_FDX) { NGE_SETBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_SETBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); } else { NGE_CLRBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_CLRBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); } } else if ((sc->nge_ifmedia.ifm_cur->ifm_media & IFM_GMASK) != IFM_FDX) { NGE_CLRBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_CLRBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); } else { NGE_SETBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_SETBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); } } else { mii = device_get_softc(sc->nge_miibus); if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) { NGE_SETBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_SETBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); } else { NGE_CLRBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_CLRBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); } /* If we have a 1000Mbps link, set the mode_1000 bit. */ if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T || IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_SX) { NGE_SETBIT(sc, NGE_CFG, NGE_CFG_MODE_1000); } else { NGE_CLRBIT(sc, NGE_CFG, NGE_CFG_MODE_1000); } } return; } static void nge_setmulti(sc) struct nge_softc *sc; { struct ifnet *ifp; struct ifmultiaddr *ifma; u_int32_t h = 0, i, filtsave; int bit, index; NGE_LOCK_ASSERT(sc); ifp = sc->nge_ifp; if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { NGE_CLRBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_MCHASH|NGE_RXFILTCTL_UCHASH); NGE_SETBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_ALLMULTI); return; } /* * We have to explicitly enable the multicast hash table * on the NatSemi chip if we want to use it, which we do. * We also have to tell it that we don't want to use the * hash table for matching unicast addresses. */ NGE_SETBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_MCHASH); NGE_CLRBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_ALLMULTI|NGE_RXFILTCTL_UCHASH); filtsave = CSR_READ_4(sc, NGE_RXFILT_CTL); /* first, zot all the existing hash bits */ for (i = 0; i < NGE_MCAST_FILTER_LEN; i += 2) { CSR_WRITE_4(sc, NGE_RXFILT_CTL, NGE_FILTADDR_MCAST_LO + i); CSR_WRITE_4(sc, NGE_RXFILT_DATA, 0); } /* * From the 11 bits returned by the crc routine, the top 7 * bits represent the 16-bit word in the mcast hash table * that needs to be updated, and the lower 4 bits represent * which bit within that byte needs to be set. */ IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; h = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN) >> 21; index = (h >> 4) & 0x7F; bit = h & 0xF; CSR_WRITE_4(sc, NGE_RXFILT_CTL, NGE_FILTADDR_MCAST_LO + (index * 2)); NGE_SETBIT(sc, NGE_RXFILT_DATA, (1 << bit)); } IF_ADDR_UNLOCK(ifp); CSR_WRITE_4(sc, NGE_RXFILT_CTL, filtsave); return; } static void nge_reset(sc) struct nge_softc *sc; { register int i; NGE_SETBIT(sc, NGE_CSR, NGE_CSR_RESET); for (i = 0; i < NGE_TIMEOUT; i++) { if (!(CSR_READ_4(sc, NGE_CSR) & NGE_CSR_RESET)) break; } if (i == NGE_TIMEOUT) device_printf(sc->nge_dev, "reset never completed\n"); /* Wait a little while for the chip to get its brains in order. */ DELAY(1000); /* * If this is a NetSemi chip, make sure to clear * PME mode. */ CSR_WRITE_4(sc, NGE_CLKRUN, NGE_CLKRUN_PMESTS); CSR_WRITE_4(sc, NGE_CLKRUN, 0); return; } /* * Probe for a NatSemi chip. Check the PCI vendor and device * IDs against our list and return a device name if we find a match. */ static int nge_probe(dev) device_t dev; { struct nge_type *t; t = nge_devs; while(t->nge_name != NULL) { if ((pci_get_vendor(dev) == t->nge_vid) && (pci_get_device(dev) == t->nge_did)) { device_set_desc(dev, t->nge_name); return(BUS_PROBE_DEFAULT); } t++; } return(ENXIO); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int nge_attach(dev) device_t dev; { u_char eaddr[ETHER_ADDR_LEN]; struct nge_softc *sc; struct ifnet *ifp = NULL; int error = 0, rid; sc = device_get_softc(dev); sc->nge_dev = dev; NGE_LOCK_INIT(sc, device_get_nameunit(dev)); callout_init_mtx(&sc->nge_stat_ch, &sc->nge_mtx, 0); /* * Map control/status registers. */ pci_enable_busmaster(dev); rid = NGE_RID; sc->nge_res = bus_alloc_resource_any(dev, NGE_RES, &rid, RF_ACTIVE); if (sc->nge_res == NULL) { device_printf(dev, "couldn't map ports/memory\n"); error = ENXIO; goto fail; } sc->nge_btag = rman_get_bustag(sc->nge_res); sc->nge_bhandle = rman_get_bushandle(sc->nge_res); /* Allocate interrupt */ rid = 0; sc->nge_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->nge_irq == NULL) { device_printf(dev, "couldn't map interrupt\n"); error = ENXIO; goto fail; } /* Reset the adapter. */ nge_reset(sc); /* * Get station address from the EEPROM. */ nge_read_eeprom(sc, (caddr_t)&eaddr[4], NGE_EE_NODEADDR, 1, 0); nge_read_eeprom(sc, (caddr_t)&eaddr[2], NGE_EE_NODEADDR + 1, 1, 0); nge_read_eeprom(sc, (caddr_t)&eaddr[0], NGE_EE_NODEADDR + 2, 1, 0); sc->nge_ldata = contigmalloc(sizeof(struct nge_list_data), M_DEVBUF, M_NOWAIT|M_ZERO, 0, 0xffffffff, PAGE_SIZE, 0); if (sc->nge_ldata == NULL) { device_printf(dev, "no memory for list buffers!\n"); error = ENXIO; goto fail; } ifp = sc->nge_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "can not if_alloc()\n"); error = ENOSPC; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_mtu = ETHERMTU; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = nge_ioctl; ifp->if_start = nge_start; ifp->if_watchdog = nge_watchdog; ifp->if_init = nge_init; ifp->if_snd.ifq_maxlen = NGE_TX_LIST_CNT - 1; ifp->if_hwassist = NGE_CSUM_FEATURES; ifp->if_capabilities = IFCAP_HWCSUM | IFCAP_VLAN_HWTAGGING; ifp->if_capenable = ifp->if_capabilities; #ifdef DEVICE_POLLING ifp->if_capabilities |= IFCAP_POLLING; #endif /* * Do MII setup. */ /* XXX: leaked on error */ if (mii_phy_probe(dev, &sc->nge_miibus, nge_ifmedia_upd, nge_ifmedia_sts)) { if (CSR_READ_4(sc, NGE_CFG) & NGE_CFG_TBI_EN) { sc->nge_tbi = 1; device_printf(dev, "Using TBI\n"); sc->nge_miibus = dev; ifmedia_init(&sc->nge_ifmedia, 0, nge_ifmedia_upd, nge_ifmedia_sts); #define ADD(m, c) ifmedia_add(&sc->nge_ifmedia, (m), (c), NULL) ADD(IFM_MAKEWORD(IFM_ETHER, IFM_NONE, 0, 0), 0); ADD(IFM_MAKEWORD(IFM_ETHER, IFM_1000_SX, 0, 0), 0); ADD(IFM_MAKEWORD(IFM_ETHER, IFM_1000_SX, IFM_FDX, 0),0); ADD(IFM_MAKEWORD(IFM_ETHER, IFM_AUTO, 0, 0), 0); #undef ADD device_printf(dev, " 1000baseSX, 1000baseSX-FDX, auto\n"); ifmedia_set(&sc->nge_ifmedia, IFM_MAKEWORD(IFM_ETHER, IFM_AUTO, 0, 0)); CSR_WRITE_4(sc, NGE_GPIO, CSR_READ_4(sc, NGE_GPIO) | NGE_GPIO_GP4_OUT | NGE_GPIO_GP1_OUTENB | NGE_GPIO_GP2_OUTENB | NGE_GPIO_GP3_OUTENB | NGE_GPIO_GP3_IN | NGE_GPIO_GP4_IN); } else { device_printf(dev, "MII without any PHY!\n"); error = ENXIO; goto fail; } } /* * Call MI attach routine. */ ether_ifattach(ifp, eaddr); /* * Hookup IRQ last. */ error = bus_setup_intr(dev, sc->nge_irq, INTR_TYPE_NET | INTR_MPSAFE, nge_intr, sc, &sc->nge_intrhand); if (error) { device_printf(dev, "couldn't set up irq\n"); goto fail; } return (0); fail: if (sc->nge_ldata) contigfree(sc->nge_ldata, sizeof(struct nge_list_data), M_DEVBUF); if (ifp) if_free(ifp); if (sc->nge_irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->nge_irq); if (sc->nge_res) bus_release_resource(dev, NGE_RES, NGE_RID, sc->nge_res); NGE_LOCK_DESTROY(sc); return(error); } static int nge_detach(dev) device_t dev; { struct nge_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); ifp = sc->nge_ifp; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) ether_poll_deregister(ifp); #endif NGE_LOCK(sc); nge_reset(sc); nge_stop(sc); NGE_UNLOCK(sc); callout_drain(&sc->nge_stat_ch); ether_ifdetach(ifp); bus_generic_detach(dev); if (!sc->nge_tbi) { device_delete_child(dev, sc->nge_miibus); } bus_teardown_intr(dev, sc->nge_irq, sc->nge_intrhand); bus_release_resource(dev, SYS_RES_IRQ, 0, sc->nge_irq); bus_release_resource(dev, NGE_RES, NGE_RID, sc->nge_res); contigfree(sc->nge_ldata, sizeof(struct nge_list_data), M_DEVBUF); if_free(ifp); NGE_LOCK_DESTROY(sc); return(0); } /* * Initialize the transmit descriptors. */ static int nge_list_tx_init(sc) struct nge_softc *sc; { struct nge_list_data *ld; struct nge_ring_data *cd; int i; cd = &sc->nge_cdata; ld = sc->nge_ldata; for (i = 0; i < NGE_TX_LIST_CNT; i++) { if (i == (NGE_TX_LIST_CNT - 1)) { ld->nge_tx_list[i].nge_nextdesc = &ld->nge_tx_list[0]; ld->nge_tx_list[i].nge_next = vtophys(&ld->nge_tx_list[0]); } else { ld->nge_tx_list[i].nge_nextdesc = &ld->nge_tx_list[i + 1]; ld->nge_tx_list[i].nge_next = vtophys(&ld->nge_tx_list[i + 1]); } ld->nge_tx_list[i].nge_mbuf = NULL; ld->nge_tx_list[i].nge_ptr = 0; ld->nge_tx_list[i].nge_ctl = 0; } cd->nge_tx_prod = cd->nge_tx_cons = cd->nge_tx_cnt = 0; return(0); } /* * Initialize the RX descriptors and allocate mbufs for them. Note that * we arrange the descriptors in a closed ring, so that the last descriptor * points back to the first. */ static int nge_list_rx_init(sc) struct nge_softc *sc; { struct nge_list_data *ld; struct nge_ring_data *cd; int i; ld = sc->nge_ldata; cd = &sc->nge_cdata; for (i = 0; i < NGE_RX_LIST_CNT; i++) { if (nge_newbuf(sc, &ld->nge_rx_list[i], NULL) == ENOBUFS) return(ENOBUFS); if (i == (NGE_RX_LIST_CNT - 1)) { ld->nge_rx_list[i].nge_nextdesc = &ld->nge_rx_list[0]; ld->nge_rx_list[i].nge_next = vtophys(&ld->nge_rx_list[0]); } else { ld->nge_rx_list[i].nge_nextdesc = &ld->nge_rx_list[i + 1]; ld->nge_rx_list[i].nge_next = vtophys(&ld->nge_rx_list[i + 1]); } } cd->nge_rx_prod = 0; sc->nge_head = sc->nge_tail = NULL; return(0); } /* * Initialize an RX descriptor and attach an MBUF cluster. */ static int nge_newbuf(sc, c, m) struct nge_softc *sc; struct nge_desc *c; struct mbuf *m; { if (m == NULL) { m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); } else m->m_data = m->m_ext.ext_buf; m->m_len = m->m_pkthdr.len = MCLBYTES; m_adj(m, sizeof(u_int64_t)); c->nge_mbuf = m; c->nge_ptr = vtophys(mtod(m, caddr_t)); c->nge_ctl = m->m_len; c->nge_extsts = 0; return(0); } #ifdef NGE_FIXUP_RX static __inline void nge_fixup_rx(m) struct mbuf *m; { int i; uint16_t *src, *dst; src = mtod(m, uint16_t *); dst = src - 1; for (i = 0; i < (m->m_len / sizeof(uint16_t) + 1); i++) *dst++ = *src++; m->m_data -= ETHER_ALIGN; return; } #endif /* * A frame has been uploaded: pass the resulting mbuf chain up to * the higher level protocols. */ static void nge_rxeof(sc) struct nge_softc *sc; { struct mbuf *m; struct ifnet *ifp; struct nge_desc *cur_rx; int i, total_len = 0; u_int32_t rxstat; NGE_LOCK_ASSERT(sc); ifp = sc->nge_ifp; i = sc->nge_cdata.nge_rx_prod; while(NGE_OWNDESC(&sc->nge_ldata->nge_rx_list[i])) { u_int32_t extsts; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { if (sc->rxcycles <= 0) break; sc->rxcycles--; } #endif cur_rx = &sc->nge_ldata->nge_rx_list[i]; rxstat = cur_rx->nge_rxstat; extsts = cur_rx->nge_extsts; m = cur_rx->nge_mbuf; cur_rx->nge_mbuf = NULL; total_len = NGE_RXBYTES(cur_rx); NGE_INC(i, NGE_RX_LIST_CNT); if (rxstat & NGE_CMDSTS_MORE) { m->m_len = total_len; if (sc->nge_head == NULL) { m->m_pkthdr.len = total_len; sc->nge_head = sc->nge_tail = m; } else { m->m_flags &= ~M_PKTHDR; sc->nge_head->m_pkthdr.len += total_len; sc->nge_tail->m_next = m; sc->nge_tail = m; } nge_newbuf(sc, cur_rx, NULL); continue; } /* * If an error occurs, update stats, clear the * status word and leave the mbuf cluster in place: * it should simply get re-used next time this descriptor * comes up in the ring. */ if (!(rxstat & NGE_CMDSTS_PKT_OK)) { ifp->if_ierrors++; if (sc->nge_head != NULL) { m_freem(sc->nge_head); sc->nge_head = sc->nge_tail = NULL; } nge_newbuf(sc, cur_rx, m); continue; } /* Try conjure up a replacement mbuf. */ if (nge_newbuf(sc, cur_rx, NULL)) { ifp->if_ierrors++; if (sc->nge_head != NULL) { m_freem(sc->nge_head); sc->nge_head = sc->nge_tail = NULL; } nge_newbuf(sc, cur_rx, m); continue; } if (sc->nge_head != NULL) { m->m_len = total_len; m->m_flags &= ~M_PKTHDR; sc->nge_tail->m_next = m; m = sc->nge_head; m->m_pkthdr.len += total_len; sc->nge_head = sc->nge_tail = NULL; } else m->m_pkthdr.len = m->m_len = total_len; /* * Ok. NatSemi really screwed up here. This is the * only gigE chip I know of with alignment constraints * on receive buffers. RX buffers must be 64-bit aligned. */ /* * By popular demand, ignore the alignment problems * on the Intel x86 platform. The performance hit * incurred due to unaligned accesses is much smaller * than the hit produced by forcing buffer copies all * the time, especially with jumbo frames. We still * need to fix up the alignment everywhere else though. */ #ifdef NGE_FIXUP_RX nge_fixup_rx(m); #endif ifp->if_ipackets++; m->m_pkthdr.rcvif = ifp; /* Do IP checksum checking. */ if (extsts & NGE_RXEXTSTS_IPPKT) m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; if (!(extsts & NGE_RXEXTSTS_IPCSUMERR)) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; if ((extsts & NGE_RXEXTSTS_TCPPKT && !(extsts & NGE_RXEXTSTS_TCPCSUMERR)) || (extsts & NGE_RXEXTSTS_UDPPKT && !(extsts & NGE_RXEXTSTS_UDPCSUMERR))) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID|CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } /* * If we received a packet with a vlan tag, pass it * to vlan_input() instead of ether_input(). */ if (extsts & NGE_RXEXTSTS_VLANPKT) { - VLAN_INPUT_TAG(ifp, m, - ntohs(extsts & NGE_RXEXTSTS_VTCI)); - if (m == NULL) - continue; + m->m_pkthdr.ether_vtag = + ntohs(extsts & NGE_RXEXTSTS_VTCI); + m->m_flags |= M_VLANTAG; } NGE_UNLOCK(sc); (*ifp->if_input)(ifp, m); NGE_LOCK(sc); } sc->nge_cdata.nge_rx_prod = i; return; } /* * A frame was downloaded to the chip. It's safe for us to clean up * the list buffers. */ static void nge_txeof(sc) struct nge_softc *sc; { struct nge_desc *cur_tx; struct ifnet *ifp; u_int32_t idx; NGE_LOCK_ASSERT(sc); ifp = sc->nge_ifp; /* * Go through our tx list and free mbufs for those * frames that have been transmitted. */ idx = sc->nge_cdata.nge_tx_cons; while (idx != sc->nge_cdata.nge_tx_prod) { cur_tx = &sc->nge_ldata->nge_tx_list[idx]; if (NGE_OWNDESC(cur_tx)) break; if (cur_tx->nge_ctl & NGE_CMDSTS_MORE) { sc->nge_cdata.nge_tx_cnt--; NGE_INC(idx, NGE_TX_LIST_CNT); continue; } if (!(cur_tx->nge_ctl & NGE_CMDSTS_PKT_OK)) { ifp->if_oerrors++; if (cur_tx->nge_txstat & NGE_TXSTAT_EXCESSCOLLS) ifp->if_collisions++; if (cur_tx->nge_txstat & NGE_TXSTAT_OUTOFWINCOLL) ifp->if_collisions++; } ifp->if_collisions += (cur_tx->nge_txstat & NGE_TXSTAT_COLLCNT) >> 16; ifp->if_opackets++; if (cur_tx->nge_mbuf != NULL) { m_freem(cur_tx->nge_mbuf); cur_tx->nge_mbuf = NULL; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; } sc->nge_cdata.nge_tx_cnt--; NGE_INC(idx, NGE_TX_LIST_CNT); } sc->nge_cdata.nge_tx_cons = idx; if (idx == sc->nge_cdata.nge_tx_prod) ifp->if_timer = 0; return; } static void nge_tick(xsc) void *xsc; { struct nge_softc *sc; struct mii_data *mii; struct ifnet *ifp; sc = xsc; NGE_LOCK_ASSERT(sc); ifp = sc->nge_ifp; if (sc->nge_tbi) { if (!sc->nge_link) { if (CSR_READ_4(sc, NGE_TBI_BMSR) & NGE_TBIBMSR_ANEG_DONE) { if (bootverbose) device_printf(sc->nge_dev, "gigabit link up\n"); nge_miibus_statchg(sc->nge_miibus); sc->nge_link++; if (ifp->if_snd.ifq_head != NULL) nge_start_locked(ifp); } } } else { mii = device_get_softc(sc->nge_miibus); mii_tick(mii); if (!sc->nge_link) { if (mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->nge_link++; if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T && bootverbose) device_printf(sc->nge_dev, "gigabit link up\n"); if (ifp->if_snd.ifq_head != NULL) nge_start_locked(ifp); } } } callout_reset(&sc->nge_stat_ch, hz, nge_tick, sc); return; } #ifdef DEVICE_POLLING static poll_handler_t nge_poll; static void nge_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct nge_softc *sc = ifp->if_softc; NGE_LOCK(sc); if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { NGE_UNLOCK(sc); return; } /* * On the nge, reading the status register also clears it. * So before returning to intr mode we must make sure that all * possible pending sources of interrupts have been served. * In practice this means run to completion the *eof routines, * and then call the interrupt routine */ sc->rxcycles = count; nge_rxeof(sc); nge_txeof(sc); if (ifp->if_snd.ifq_head != NULL) nge_start_locked(ifp); if (sc->rxcycles > 0 || cmd == POLL_AND_CHECK_STATUS) { u_int32_t status; /* Reading the ISR register clears all interrupts. */ status = CSR_READ_4(sc, NGE_ISR); if (status & (NGE_ISR_RX_ERR|NGE_ISR_RX_OFLOW)) nge_rxeof(sc); if (status & (NGE_ISR_RX_IDLE)) NGE_SETBIT(sc, NGE_CSR, NGE_CSR_RX_ENABLE); if (status & NGE_ISR_SYSERR) { nge_reset(sc); nge_init_locked(sc); } } NGE_UNLOCK(sc); } #endif /* DEVICE_POLLING */ static void nge_intr(arg) void *arg; { struct nge_softc *sc; struct ifnet *ifp; u_int32_t status; sc = arg; ifp = sc->nge_ifp; NGE_LOCK(sc); #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { NGE_UNLOCK(sc); return; } #endif /* Supress unwanted interrupts */ if (!(ifp->if_flags & IFF_UP)) { nge_stop(sc); NGE_UNLOCK(sc); return; } /* Disable interrupts. */ CSR_WRITE_4(sc, NGE_IER, 0); /* Data LED on for TBI mode */ if(sc->nge_tbi) CSR_WRITE_4(sc, NGE_GPIO, CSR_READ_4(sc, NGE_GPIO) | NGE_GPIO_GP3_OUT); for (;;) { /* Reading the ISR register clears all interrupts. */ status = CSR_READ_4(sc, NGE_ISR); if ((status & NGE_INTRS) == 0) break; if ((status & NGE_ISR_TX_DESC_OK) || (status & NGE_ISR_TX_ERR) || (status & NGE_ISR_TX_OK) || (status & NGE_ISR_TX_IDLE)) nge_txeof(sc); if ((status & NGE_ISR_RX_DESC_OK) || (status & NGE_ISR_RX_ERR) || (status & NGE_ISR_RX_OFLOW) || (status & NGE_ISR_RX_FIFO_OFLOW) || (status & NGE_ISR_RX_IDLE) || (status & NGE_ISR_RX_OK)) nge_rxeof(sc); if ((status & NGE_ISR_RX_IDLE)) NGE_SETBIT(sc, NGE_CSR, NGE_CSR_RX_ENABLE); if (status & NGE_ISR_SYSERR) { nge_reset(sc); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; nge_init_locked(sc); } #if 0 /* * XXX: nge_tick() is not ready to be called this way * it screws up the aneg timeout because mii_tick() is * only to be called once per second. */ if (status & NGE_IMR_PHY_INTR) { sc->nge_link = 0; nge_tick(sc); } #endif } /* Re-enable interrupts. */ CSR_WRITE_4(sc, NGE_IER, 1); if (ifp->if_snd.ifq_head != NULL) nge_start_locked(ifp); /* Data LED off for TBI mode */ if(sc->nge_tbi) CSR_WRITE_4(sc, NGE_GPIO, CSR_READ_4(sc, NGE_GPIO) & ~NGE_GPIO_GP3_OUT); NGE_UNLOCK(sc); return; } /* * Encapsulate an mbuf chain in a descriptor by coupling the mbuf data * pointers to the fragment pointers. */ static int nge_encap(sc, m_head, txidx) struct nge_softc *sc; struct mbuf *m_head; u_int32_t *txidx; { struct nge_desc *f = NULL; struct mbuf *m; int frag, cur, cnt = 0; - struct m_tag *mtag; /* * Start packing the mbufs in this chain into * the fragment pointers. Stop when we run out * of fragments or hit the end of the mbuf chain. */ m = m_head; cur = frag = *txidx; for (m = m_head; m != NULL; m = m->m_next) { if (m->m_len != 0) { if ((NGE_TX_LIST_CNT - (sc->nge_cdata.nge_tx_cnt + cnt)) < 2) return(ENOBUFS); f = &sc->nge_ldata->nge_tx_list[frag]; f->nge_ctl = NGE_CMDSTS_MORE | m->m_len; f->nge_ptr = vtophys(mtod(m, vm_offset_t)); if (cnt != 0) f->nge_ctl |= NGE_CMDSTS_OWN; cur = frag; NGE_INC(frag, NGE_TX_LIST_CNT); cnt++; } } if (m != NULL) return(ENOBUFS); sc->nge_ldata->nge_tx_list[*txidx].nge_extsts = 0; if (m_head->m_pkthdr.csum_flags) { if (m_head->m_pkthdr.csum_flags & CSUM_IP) sc->nge_ldata->nge_tx_list[*txidx].nge_extsts |= NGE_TXEXTSTS_IPCSUM; if (m_head->m_pkthdr.csum_flags & CSUM_TCP) sc->nge_ldata->nge_tx_list[*txidx].nge_extsts |= NGE_TXEXTSTS_TCPCSUM; if (m_head->m_pkthdr.csum_flags & CSUM_UDP) sc->nge_ldata->nge_tx_list[*txidx].nge_extsts |= NGE_TXEXTSTS_UDPCSUM; } - mtag = VLAN_OUTPUT_TAG(sc->nge_ifp, m_head); - if (mtag != NULL) { + if (m_head->m_flags & M_VLANTAG) { sc->nge_ldata->nge_tx_list[cur].nge_extsts |= - (NGE_TXEXTSTS_VLANPKT|htons(VLAN_TAG_VALUE(mtag))); + (NGE_TXEXTSTS_VLANPKT|htons(m_head->m_pkthdr.ether_vtag)); } sc->nge_ldata->nge_tx_list[cur].nge_mbuf = m_head; sc->nge_ldata->nge_tx_list[cur].nge_ctl &= ~NGE_CMDSTS_MORE; sc->nge_ldata->nge_tx_list[*txidx].nge_ctl |= NGE_CMDSTS_OWN; sc->nge_cdata.nge_tx_cnt += cnt; *txidx = frag; return(0); } /* * Main transmit routine. To avoid having to do mbuf copies, we put pointers * to the mbuf data regions directly in the transmit lists. We also save a * copy of the pointers since the transmit list fragment pointers are * physical addresses. */ static void nge_start(ifp) struct ifnet *ifp; { struct nge_softc *sc; sc = ifp->if_softc; NGE_LOCK(sc); nge_start_locked(ifp); NGE_UNLOCK(sc); } static void nge_start_locked(ifp) struct ifnet *ifp; { struct nge_softc *sc; struct mbuf *m_head = NULL; u_int32_t idx; sc = ifp->if_softc; if (!sc->nge_link) return; idx = sc->nge_cdata.nge_tx_prod; if (ifp->if_drv_flags & IFF_DRV_OACTIVE) return; while(sc->nge_ldata->nge_tx_list[idx].nge_mbuf == NULL) { IF_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; if (nge_encap(sc, m_head, &idx)) { IF_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } /* * If there's a BPF listener, bounce a copy of this frame * to him. */ BPF_MTAP(ifp, m_head); } /* Transmit */ sc->nge_cdata.nge_tx_prod = idx; NGE_SETBIT(sc, NGE_CSR, NGE_CSR_TX_ENABLE); /* * Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; return; } static void nge_init(xsc) void *xsc; { struct nge_softc *sc = xsc; NGE_LOCK(sc); nge_init_locked(sc); NGE_UNLOCK(sc); } static void nge_init_locked(sc) struct nge_softc *sc; { struct ifnet *ifp = sc->nge_ifp; struct mii_data *mii; NGE_LOCK_ASSERT(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) return; /* * Cancel pending I/O and free all RX/TX buffers. */ nge_stop(sc); if (sc->nge_tbi) { mii = NULL; } else { mii = device_get_softc(sc->nge_miibus); } /* Set MAC address */ CSR_WRITE_4(sc, NGE_RXFILT_CTL, NGE_FILTADDR_PAR0); CSR_WRITE_4(sc, NGE_RXFILT_DATA, ((u_int16_t *)IF_LLADDR(sc->nge_ifp))[0]); CSR_WRITE_4(sc, NGE_RXFILT_CTL, NGE_FILTADDR_PAR1); CSR_WRITE_4(sc, NGE_RXFILT_DATA, ((u_int16_t *)IF_LLADDR(sc->nge_ifp))[1]); CSR_WRITE_4(sc, NGE_RXFILT_CTL, NGE_FILTADDR_PAR2); CSR_WRITE_4(sc, NGE_RXFILT_DATA, ((u_int16_t *)IF_LLADDR(sc->nge_ifp))[2]); /* Init circular RX list. */ if (nge_list_rx_init(sc) == ENOBUFS) { device_printf(sc->nge_dev, "initialization failed: no " "memory for rx buffers\n"); nge_stop(sc); return; } /* * Init tx descriptors. */ nge_list_tx_init(sc); /* * For the NatSemi chip, we have to explicitly enable the * reception of ARP frames, as well as turn on the 'perfect * match' filter where we store the station address, otherwise * we won't receive unicasts meant for this host. */ NGE_SETBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_ARP); NGE_SETBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_PERFECT); /* If we want promiscuous mode, set the allframes bit. */ if (ifp->if_flags & IFF_PROMISC) { NGE_SETBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_ALLPHYS); } else { NGE_CLRBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_ALLPHYS); } /* * Set the capture broadcast bit to capture broadcast frames. */ if (ifp->if_flags & IFF_BROADCAST) { NGE_SETBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_BROAD); } else { NGE_CLRBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_BROAD); } /* * Load the multicast filter. */ nge_setmulti(sc); /* Turn the receive filter on */ NGE_SETBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_ENABLE); /* * Load the address of the RX and TX lists. */ CSR_WRITE_4(sc, NGE_RX_LISTPTR, vtophys(&sc->nge_ldata->nge_rx_list[0])); CSR_WRITE_4(sc, NGE_TX_LISTPTR, vtophys(&sc->nge_ldata->nge_tx_list[0])); /* Set RX configuration */ CSR_WRITE_4(sc, NGE_RX_CFG, NGE_RXCFG); /* * Enable hardware checksum validation for all IPv4 * packets, do not reject packets with bad checksums. */ CSR_WRITE_4(sc, NGE_VLAN_IP_RXCTL, NGE_VIPRXCTL_IPCSUM_ENB); /* * Tell the chip to detect and strip VLAN tag info from * received frames. The tag will be provided in the extsts * field in the RX descriptors. */ NGE_SETBIT(sc, NGE_VLAN_IP_RXCTL, NGE_VIPRXCTL_TAG_DETECT_ENB|NGE_VIPRXCTL_TAG_STRIP_ENB); /* Set TX configuration */ CSR_WRITE_4(sc, NGE_TX_CFG, NGE_TXCFG); /* * Enable TX IPv4 checksumming on a per-packet basis. */ CSR_WRITE_4(sc, NGE_VLAN_IP_TXCTL, NGE_VIPTXCTL_CSUM_PER_PKT); /* * Tell the chip to insert VLAN tags on a per-packet basis as * dictated by the code in the frame encapsulation routine. */ NGE_SETBIT(sc, NGE_VLAN_IP_TXCTL, NGE_VIPTXCTL_TAG_PER_PKT); /* Set full/half duplex mode. */ if (sc->nge_tbi) { if ((sc->nge_ifmedia.ifm_cur->ifm_media & IFM_GMASK) == IFM_FDX) { NGE_SETBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_SETBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); } else { NGE_CLRBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_CLRBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); } } else { if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) { NGE_SETBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_SETBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); } else { NGE_CLRBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_CLRBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); } } nge_tick(sc); /* * Enable the delivery of PHY interrupts based on * link/speed/duplex status changes. Also enable the * extsts field in the DMA descriptors (needed for * TCP/IP checksum offload on transmit). */ NGE_SETBIT(sc, NGE_CFG, NGE_CFG_PHYINTR_SPD| NGE_CFG_PHYINTR_LNK|NGE_CFG_PHYINTR_DUP|NGE_CFG_EXTSTS_ENB); /* * Configure interrupt holdoff (moderation). We can * have the chip delay interrupt delivery for a certain * period. Units are in 100us, and the max setting * is 25500us (0xFF x 100us). Default is a 100us holdoff. */ CSR_WRITE_4(sc, NGE_IHR, 0x01); /* * Enable interrupts. */ CSR_WRITE_4(sc, NGE_IMR, NGE_INTRS); #ifdef DEVICE_POLLING /* * ... only enable interrupts if we are not polling, make sure * they are off otherwise. */ if (ifp->if_capenable & IFCAP_POLLING) CSR_WRITE_4(sc, NGE_IER, 0); else #endif CSR_WRITE_4(sc, NGE_IER, 1); /* Enable receiver and transmitter. */ NGE_CLRBIT(sc, NGE_CSR, NGE_CSR_TX_DISABLE|NGE_CSR_RX_DISABLE); NGE_SETBIT(sc, NGE_CSR, NGE_CSR_RX_ENABLE); nge_ifmedia_upd_locked(ifp); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; return; } /* * Set media options. */ static int nge_ifmedia_upd(ifp) struct ifnet *ifp; { struct nge_softc *sc; sc = ifp->if_softc; NGE_LOCK(sc); nge_ifmedia_upd_locked(ifp); NGE_UNLOCK(sc); return (0); } static void nge_ifmedia_upd_locked(ifp) struct ifnet *ifp; { struct nge_softc *sc; struct mii_data *mii; sc = ifp->if_softc; NGE_LOCK_ASSERT(sc); if (sc->nge_tbi) { if (IFM_SUBTYPE(sc->nge_ifmedia.ifm_cur->ifm_media) == IFM_AUTO) { CSR_WRITE_4(sc, NGE_TBI_ANAR, CSR_READ_4(sc, NGE_TBI_ANAR) | NGE_TBIANAR_HDX | NGE_TBIANAR_FDX | NGE_TBIANAR_PS1 | NGE_TBIANAR_PS2); CSR_WRITE_4(sc, NGE_TBI_BMCR, NGE_TBIBMCR_ENABLE_ANEG | NGE_TBIBMCR_RESTART_ANEG); CSR_WRITE_4(sc, NGE_TBI_BMCR, NGE_TBIBMCR_ENABLE_ANEG); } else if ((sc->nge_ifmedia.ifm_cur->ifm_media & IFM_GMASK) == IFM_FDX) { NGE_SETBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_SETBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); CSR_WRITE_4(sc, NGE_TBI_ANAR, 0); CSR_WRITE_4(sc, NGE_TBI_BMCR, 0); } else { NGE_CLRBIT(sc, NGE_TX_CFG, (NGE_TXCFG_IGN_HBEAT|NGE_TXCFG_IGN_CARR)); NGE_CLRBIT(sc, NGE_RX_CFG, NGE_RXCFG_RX_FDX); CSR_WRITE_4(sc, NGE_TBI_ANAR, 0); CSR_WRITE_4(sc, NGE_TBI_BMCR, 0); } CSR_WRITE_4(sc, NGE_GPIO, CSR_READ_4(sc, NGE_GPIO) & ~NGE_GPIO_GP3_OUT); } else { mii = device_get_softc(sc->nge_miibus); sc->nge_link = 0; if (mii->mii_instance) { struct mii_softc *miisc; LIST_FOREACH(miisc, &mii->mii_phys, mii_list) mii_phy_reset(miisc); } mii_mediachg(mii); } } /* * Report current media status. */ static void nge_ifmedia_sts(ifp, ifmr) struct ifnet *ifp; struct ifmediareq *ifmr; { struct nge_softc *sc; struct mii_data *mii; sc = ifp->if_softc; NGE_LOCK(sc); if (sc->nge_tbi) { ifmr->ifm_status = IFM_AVALID; ifmr->ifm_active = IFM_ETHER; if (CSR_READ_4(sc, NGE_TBI_BMSR) & NGE_TBIBMSR_ANEG_DONE) { ifmr->ifm_status |= IFM_ACTIVE; } if (CSR_READ_4(sc, NGE_TBI_BMCR) & NGE_TBIBMCR_LOOPBACK) ifmr->ifm_active |= IFM_LOOP; if (!CSR_READ_4(sc, NGE_TBI_BMSR) & NGE_TBIBMSR_ANEG_DONE) { ifmr->ifm_active |= IFM_NONE; ifmr->ifm_status = 0; NGE_UNLOCK(sc); return; } ifmr->ifm_active |= IFM_1000_SX; if (IFM_SUBTYPE(sc->nge_ifmedia.ifm_cur->ifm_media) == IFM_AUTO) { ifmr->ifm_active |= IFM_AUTO; if (CSR_READ_4(sc, NGE_TBI_ANLPAR) & NGE_TBIANAR_FDX) { ifmr->ifm_active |= IFM_FDX; }else if (CSR_READ_4(sc, NGE_TBI_ANLPAR) & NGE_TBIANAR_HDX) { ifmr->ifm_active |= IFM_HDX; } } else if ((sc->nge_ifmedia.ifm_cur->ifm_media & IFM_GMASK) == IFM_FDX) ifmr->ifm_active |= IFM_FDX; else ifmr->ifm_active |= IFM_HDX; } else { mii = device_get_softc(sc->nge_miibus); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } NGE_UNLOCK(sc); return; } static int nge_ioctl(ifp, command, data) struct ifnet *ifp; u_long command; caddr_t data; { struct nge_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; struct mii_data *mii; int error = 0; switch(command) { case SIOCSIFMTU: if (ifr->ifr_mtu > NGE_JUMBO_MTU) error = EINVAL; else { NGE_LOCK(sc); ifp->if_mtu = ifr->ifr_mtu; /* * Workaround: if the MTU is larger than * 8152 (TX FIFO size minus 64 minus 18), turn off * TX checksum offloading. */ if (ifr->ifr_mtu >= 8152) { ifp->if_capenable &= ~IFCAP_TXCSUM; ifp->if_hwassist = 0; } else { ifp->if_capenable |= IFCAP_TXCSUM; ifp->if_hwassist = NGE_CSUM_FEATURES; } NGE_UNLOCK(sc); } break; case SIOCSIFFLAGS: NGE_LOCK(sc); if (ifp->if_flags & IFF_UP) { if (ifp->if_drv_flags & IFF_DRV_RUNNING && ifp->if_flags & IFF_PROMISC && !(sc->nge_if_flags & IFF_PROMISC)) { NGE_SETBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_ALLPHYS| NGE_RXFILTCTL_ALLMULTI); } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && !(ifp->if_flags & IFF_PROMISC) && sc->nge_if_flags & IFF_PROMISC) { NGE_CLRBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_ALLPHYS); if (!(ifp->if_flags & IFF_ALLMULTI)) NGE_CLRBIT(sc, NGE_RXFILT_CTL, NGE_RXFILTCTL_ALLMULTI); } else { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; nge_init_locked(sc); } } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) nge_stop(sc); } sc->nge_if_flags = ifp->if_flags; NGE_UNLOCK(sc); error = 0; break; case SIOCADDMULTI: case SIOCDELMULTI: NGE_LOCK(sc); nge_setmulti(sc); NGE_UNLOCK(sc); error = 0; break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: if (sc->nge_tbi) { error = ifmedia_ioctl(ifp, ifr, &sc->nge_ifmedia, command); } else { mii = device_get_softc(sc->nge_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); } break; case SIOCSIFCAP: #ifdef DEVICE_POLLING if (ifr->ifr_reqcap & IFCAP_POLLING && !(ifp->if_capenable & IFCAP_POLLING)) { error = ether_poll_register(nge_poll, ifp); if (error) return(error); NGE_LOCK(sc); /* Disable interrupts */ CSR_WRITE_4(sc, NGE_IER, 0); ifp->if_capenable |= IFCAP_POLLING; NGE_UNLOCK(sc); return (error); } if (!(ifr->ifr_reqcap & IFCAP_POLLING) && ifp->if_capenable & IFCAP_POLLING) { error = ether_poll_deregister(ifp); /* Enable interrupts. */ NGE_LOCK(sc); CSR_WRITE_4(sc, NGE_IER, 1); ifp->if_capenable &= ~IFCAP_POLLING; NGE_UNLOCK(sc); return (error); } #endif /* DEVICE_POLLING */ break; default: error = ether_ioctl(ifp, command, data); break; } return(error); } static void nge_watchdog(ifp) struct ifnet *ifp; { struct nge_softc *sc; sc = ifp->if_softc; ifp->if_oerrors++; if_printf(ifp, "watchdog timeout\n"); NGE_LOCK(sc); nge_stop(sc); nge_reset(sc); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; nge_init_locked(sc); if (ifp->if_snd.ifq_head != NULL) nge_start_locked(ifp); NGE_UNLOCK(sc); return; } /* * Stop the adapter and free any mbufs allocated to the * RX and TX lists. */ static void nge_stop(sc) struct nge_softc *sc; { register int i; struct ifnet *ifp; struct mii_data *mii; NGE_LOCK_ASSERT(sc); ifp = sc->nge_ifp; ifp->if_timer = 0; if (sc->nge_tbi) { mii = NULL; } else { mii = device_get_softc(sc->nge_miibus); } callout_stop(&sc->nge_stat_ch); CSR_WRITE_4(sc, NGE_IER, 0); CSR_WRITE_4(sc, NGE_IMR, 0); NGE_SETBIT(sc, NGE_CSR, NGE_CSR_TX_DISABLE|NGE_CSR_RX_DISABLE); DELAY(1000); CSR_WRITE_4(sc, NGE_TX_LISTPTR, 0); CSR_WRITE_4(sc, NGE_RX_LISTPTR, 0); if (!sc->nge_tbi) mii_down(mii); sc->nge_link = 0; /* * Free data in the RX lists. */ for (i = 0; i < NGE_RX_LIST_CNT; i++) { if (sc->nge_ldata->nge_rx_list[i].nge_mbuf != NULL) { m_freem(sc->nge_ldata->nge_rx_list[i].nge_mbuf); sc->nge_ldata->nge_rx_list[i].nge_mbuf = NULL; } } bzero((char *)&sc->nge_ldata->nge_rx_list, sizeof(sc->nge_ldata->nge_rx_list)); /* * Free the TX list buffers. */ for (i = 0; i < NGE_TX_LIST_CNT; i++) { if (sc->nge_ldata->nge_tx_list[i].nge_mbuf != NULL) { m_freem(sc->nge_ldata->nge_tx_list[i].nge_mbuf); sc->nge_ldata->nge_tx_list[i].nge_mbuf = NULL; } } bzero((char *)&sc->nge_ldata->nge_tx_list, sizeof(sc->nge_ldata->nge_tx_list)); ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); return; } /* * Stop all chip I/O so that the kernel's probe routines don't * get confused by errant DMAs when rebooting. */ static void nge_shutdown(dev) device_t dev; { struct nge_softc *sc; sc = device_get_softc(dev); NGE_LOCK(sc); nge_reset(sc); nge_stop(sc); NGE_UNLOCK(sc); return; } Index: head/sys/dev/re/if_re.c =================================================================== --- head/sys/dev/re/if_re.c (revision 162374) +++ head/sys/dev/re/if_re.c (revision 162375) @@ -1,2697 +1,2694 @@ /*- * Copyright (c) 1997, 1998-2003 * Bill Paul . 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * RealTek 8139C+/8169/8169S/8110S/8168/8111/8101E PCI NIC driver * * Written by Bill Paul * Senior Networking Software Engineer * Wind River Systems */ /* * This driver is designed to support RealTek's next generation of * 10/100 and 10/100/1000 PCI ethernet controllers. There are currently * seven devices in this family: the RTL8139C+, the RTL8169, the RTL8169S, * RTL8110S, the RTL8168, the RTL8111 and the RTL8101E. * * The 8139C+ is a 10/100 ethernet chip. It is backwards compatible * with the older 8139 family, however it also supports a special * C+ mode of operation that provides several new performance enhancing * features. These include: * * o Descriptor based DMA mechanism. Each descriptor represents * a single packet fragment. Data buffers may be aligned on * any byte boundary. * * o 64-bit DMA * * o TCP/IP checksum offload for both RX and TX * * o High and normal priority transmit DMA rings * * o VLAN tag insertion and extraction * * o TCP large send (segmentation offload) * * Like the 8139, the 8139C+ also has a built-in 10/100 PHY. The C+ * programming API is fairly straightforward. The RX filtering, EEPROM * access and PHY access is the same as it is on the older 8139 series * chips. * * The 8169 is a 64-bit 10/100/1000 gigabit ethernet MAC. It has almost the * same programming API and feature set as the 8139C+ with the following * differences and additions: * * o 1000Mbps mode * * o Jumbo frames * * o GMII and TBI ports/registers for interfacing with copper * or fiber PHYs * * o RX and TX DMA rings can have up to 1024 descriptors * (the 8139C+ allows a maximum of 64) * * o Slight differences in register layout from the 8139C+ * * The TX start and timer interrupt registers are at different locations * on the 8169 than they are on the 8139C+. Also, the status word in the * RX descriptor has a slightly different bit layout. The 8169 does not * have a built-in PHY. Most reference boards use a Marvell 88E1000 'Alaska' * copper gigE PHY. * * The 8169S/8110S 10/100/1000 devices have built-in copper gigE PHYs * (the 'S' stands for 'single-chip'). These devices have the same * programming API as the older 8169, but also have some vendor-specific * registers for the on-board PHY. The 8110S is a LAN-on-motherboard * part designed to be pin-compatible with the RealTek 8100 10/100 chip. * * This driver takes advantage of the RX and TX checksum offload and * VLAN tag insertion/extraction features. It also implements TX * interrupt moderation using the timer interrupt registers, which * significantly reduces TX interrupt load. There is also support * for jumbo frames, however the 8169/8169S/8110S can not transmit * jumbo frames larger than 7440, so the max MTU possible with this * driver is 7422 bytes. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_device_polling.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_DEPEND(re, pci, 1, 1, 1); MODULE_DEPEND(re, ether, 1, 1, 1); MODULE_DEPEND(re, miibus, 1, 1, 1); /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" /* * Default to using PIO access for this driver. */ #define RE_USEIOSPACE #include #define RE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) /* * Various supported device vendors/types and their names. */ static struct rl_type re_devs[] = { { DLINK_VENDORID, DLINK_DEVICEID_528T, RL_HWREV_8169S, "D-Link DGE-528(T) Gigabit Ethernet Adapter" }, { RT_VENDORID, RT_DEVICEID_8139, RL_HWREV_8139CPLUS, "RealTek 8139C+ 10/100BaseTX" }, { RT_VENDORID, RT_DEVICEID_8101E, RL_HWREV_8101E, "RealTek 8101E PCIe 10/100baseTX" }, { RT_VENDORID, RT_DEVICEID_8168, RL_HWREV_8168_SPIN1, "RealTek 8168/8111B PCIe Gigabit Ethernet" }, { RT_VENDORID, RT_DEVICEID_8168, RL_HWREV_8168_SPIN2, "RealTek 8168/8111B PCIe Gigabit Ethernet" }, { RT_VENDORID, RT_DEVICEID_8169, RL_HWREV_8169, "RealTek 8169 Gigabit Ethernet" }, { RT_VENDORID, RT_DEVICEID_8169, RL_HWREV_8169S, "RealTek 8169S Single-chip Gigabit Ethernet" }, { RT_VENDORID, RT_DEVICEID_8169, RL_HWREV_8169_8110SB, "RealTek 8169SB/8110SB Single-chip Gigabit Ethernet" }, { RT_VENDORID, RT_DEVICEID_8169SC, RL_HWREV_8169_8110SC, "RealTek 8169SC/8110SC Single-chip Gigabit Ethernet" }, { RT_VENDORID, RT_DEVICEID_8169, RL_HWREV_8110S, "RealTek 8110S Single-chip Gigabit Ethernet" }, { COREGA_VENDORID, COREGA_DEVICEID_CGLAPCIGT, RL_HWREV_8169S, "Corega CG-LAPCIGT (RTL8169S) Gigabit Ethernet" }, { LINKSYS_VENDORID, LINKSYS_DEVICEID_EG1032, RL_HWREV_8169S, "Linksys EG1032 (RTL8169S) Gigabit Ethernet" }, { USR_VENDORID, USR_DEVICEID_997902, RL_HWREV_8169S, "US Robotics 997902 (RTL8169S) Gigabit Ethernet" }, { 0, 0, 0, NULL } }; static struct rl_hwrev re_hwrevs[] = { { RL_HWREV_8139, RL_8139, "" }, { RL_HWREV_8139A, RL_8139, "A" }, { RL_HWREV_8139AG, RL_8139, "A-G" }, { RL_HWREV_8139B, RL_8139, "B" }, { RL_HWREV_8130, RL_8139, "8130" }, { RL_HWREV_8139C, RL_8139, "C" }, { RL_HWREV_8139D, RL_8139, "8139D/8100B/8100C" }, { RL_HWREV_8139CPLUS, RL_8139CPLUS, "C+"}, { RL_HWREV_8168_SPIN1, RL_8169, "8168"}, { RL_HWREV_8169, RL_8169, "8169"}, { RL_HWREV_8169S, RL_8169, "8169S"}, { RL_HWREV_8110S, RL_8169, "8110S"}, { RL_HWREV_8169_8110SB, RL_8169, "8169SB"}, { RL_HWREV_8169_8110SC, RL_8169, "8169SC"}, { RL_HWREV_8100, RL_8139, "8100"}, { RL_HWREV_8101, RL_8139, "8101"}, { RL_HWREV_8100E, RL_8169, "8100E"}, { RL_HWREV_8101E, RL_8169, "8101E"}, { RL_HWREV_8168_SPIN2, RL_8169, "8168"}, { 0, 0, NULL } }; static int re_probe (device_t); static int re_attach (device_t); static int re_detach (device_t); static int re_encap (struct rl_softc *, struct mbuf **, int *); static void re_dma_map_addr (void *, bus_dma_segment_t *, int, int); static void re_dma_map_desc (void *, bus_dma_segment_t *, int, bus_size_t, int); static int re_allocmem (device_t, struct rl_softc *); static int re_newbuf (struct rl_softc *, int, struct mbuf *); static int re_rx_list_init (struct rl_softc *); static int re_tx_list_init (struct rl_softc *); #ifdef RE_FIXUP_RX static __inline void re_fixup_rx (struct mbuf *); #endif static int re_rxeof (struct rl_softc *); static void re_txeof (struct rl_softc *); #ifdef DEVICE_POLLING static void re_poll (struct ifnet *, enum poll_cmd, int); static void re_poll_locked (struct ifnet *, enum poll_cmd, int); #endif static void re_intr (void *); static void re_tick (void *); static void re_tx_task (void *, int); static void re_int_task (void *, int); static void re_start (struct ifnet *); static int re_ioctl (struct ifnet *, u_long, caddr_t); static void re_init (void *); static void re_init_locked (struct rl_softc *); static void re_stop (struct rl_softc *); static void re_watchdog (struct ifnet *); static int re_suspend (device_t); static int re_resume (device_t); static void re_shutdown (device_t); static int re_ifmedia_upd (struct ifnet *); static void re_ifmedia_sts (struct ifnet *, struct ifmediareq *); static void re_eeprom_putbyte (struct rl_softc *, int); static void re_eeprom_getword (struct rl_softc *, int, u_int16_t *); static void re_read_eeprom (struct rl_softc *, caddr_t, int, int); static int re_gmii_readreg (device_t, int, int); static int re_gmii_writereg (device_t, int, int, int); static int re_miibus_readreg (device_t, int, int); static int re_miibus_writereg (device_t, int, int, int); static void re_miibus_statchg (device_t); static void re_setmulti (struct rl_softc *); static void re_reset (struct rl_softc *); #ifdef RE_DIAG static int re_diag (struct rl_softc *); #endif #ifdef RE_USEIOSPACE #define RL_RES SYS_RES_IOPORT #define RL_RID RL_PCI_LOIO #else #define RL_RES SYS_RES_MEMORY #define RL_RID RL_PCI_LOMEM #endif static device_method_t re_methods[] = { /* Device interface */ DEVMETHOD(device_probe, re_probe), DEVMETHOD(device_attach, re_attach), DEVMETHOD(device_detach, re_detach), DEVMETHOD(device_suspend, re_suspend), DEVMETHOD(device_resume, re_resume), DEVMETHOD(device_shutdown, re_shutdown), /* bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, re_miibus_readreg), DEVMETHOD(miibus_writereg, re_miibus_writereg), DEVMETHOD(miibus_statchg, re_miibus_statchg), { 0, 0 } }; static driver_t re_driver = { "re", re_methods, sizeof(struct rl_softc) }; static devclass_t re_devclass; DRIVER_MODULE(re, pci, re_driver, re_devclass, 0, 0); DRIVER_MODULE(re, cardbus, re_driver, re_devclass, 0, 0); DRIVER_MODULE(miibus, re, miibus_driver, miibus_devclass, 0, 0); #define EE_SET(x) \ CSR_WRITE_1(sc, RL_EECMD, \ CSR_READ_1(sc, RL_EECMD) | x) #define EE_CLR(x) \ CSR_WRITE_1(sc, RL_EECMD, \ CSR_READ_1(sc, RL_EECMD) & ~x) /* * Send a read command and address to the EEPROM, check for ACK. */ static void re_eeprom_putbyte(sc, addr) struct rl_softc *sc; int addr; { register int d, i; d = addr | (RL_9346_READ << sc->rl_eewidth); /* * Feed in each bit and strobe the clock. */ for (i = 1 << (sc->rl_eewidth + 3); i; i >>= 1) { if (d & i) { EE_SET(RL_EE_DATAIN); } else { EE_CLR(RL_EE_DATAIN); } DELAY(100); EE_SET(RL_EE_CLK); DELAY(150); EE_CLR(RL_EE_CLK); DELAY(100); } return; } /* * Read a word of data stored in the EEPROM at address 'addr.' */ static void re_eeprom_getword(sc, addr, dest) struct rl_softc *sc; int addr; u_int16_t *dest; { register int i; u_int16_t word = 0; /* * Send address of word we want to read. */ re_eeprom_putbyte(sc, addr); /* * Start reading bits from EEPROM. */ for (i = 0x8000; i; i >>= 1) { EE_SET(RL_EE_CLK); DELAY(100); if (CSR_READ_1(sc, RL_EECMD) & RL_EE_DATAOUT) word |= i; EE_CLR(RL_EE_CLK); DELAY(100); } *dest = word; return; } /* * Read a sequence of words from the EEPROM. */ static void re_read_eeprom(sc, dest, off, cnt) struct rl_softc *sc; caddr_t dest; int off; int cnt; { int i; u_int16_t word = 0, *ptr; CSR_SETBIT_1(sc, RL_EECMD, RL_EEMODE_PROGRAM); DELAY(100); for (i = 0; i < cnt; i++) { CSR_SETBIT_1(sc, RL_EECMD, RL_EE_SEL); re_eeprom_getword(sc, off + i, &word); CSR_CLRBIT_1(sc, RL_EECMD, RL_EE_SEL); ptr = (u_int16_t *)(dest + (i * 2)); *ptr = word; } CSR_CLRBIT_1(sc, RL_EECMD, RL_EEMODE_PROGRAM); return; } static int re_gmii_readreg(dev, phy, reg) device_t dev; int phy, reg; { struct rl_softc *sc; u_int32_t rval; int i; if (phy != 1) return (0); sc = device_get_softc(dev); /* Let the rgephy driver read the GMEDIASTAT register */ if (reg == RL_GMEDIASTAT) { rval = CSR_READ_1(sc, RL_GMEDIASTAT); return (rval); } CSR_WRITE_4(sc, RL_PHYAR, reg << 16); DELAY(1000); for (i = 0; i < RL_TIMEOUT; i++) { rval = CSR_READ_4(sc, RL_PHYAR); if (rval & RL_PHYAR_BUSY) break; DELAY(100); } if (i == RL_TIMEOUT) { device_printf(sc->rl_dev, "PHY read failed\n"); return (0); } return (rval & RL_PHYAR_PHYDATA); } static int re_gmii_writereg(dev, phy, reg, data) device_t dev; int phy, reg, data; { struct rl_softc *sc; u_int32_t rval; int i; sc = device_get_softc(dev); CSR_WRITE_4(sc, RL_PHYAR, (reg << 16) | (data & RL_PHYAR_PHYDATA) | RL_PHYAR_BUSY); DELAY(1000); for (i = 0; i < RL_TIMEOUT; i++) { rval = CSR_READ_4(sc, RL_PHYAR); if (!(rval & RL_PHYAR_BUSY)) break; DELAY(100); } if (i == RL_TIMEOUT) { device_printf(sc->rl_dev, "PHY write failed\n"); return (0); } return (0); } static int re_miibus_readreg(dev, phy, reg) device_t dev; int phy, reg; { struct rl_softc *sc; u_int16_t rval = 0; u_int16_t re8139_reg = 0; sc = device_get_softc(dev); if (sc->rl_type == RL_8169) { rval = re_gmii_readreg(dev, phy, reg); return (rval); } /* Pretend the internal PHY is only at address 0 */ if (phy) { return (0); } switch (reg) { case MII_BMCR: re8139_reg = RL_BMCR; break; case MII_BMSR: re8139_reg = RL_BMSR; break; case MII_ANAR: re8139_reg = RL_ANAR; break; case MII_ANER: re8139_reg = RL_ANER; break; case MII_ANLPAR: re8139_reg = RL_LPAR; break; case MII_PHYIDR1: case MII_PHYIDR2: return (0); /* * Allow the rlphy driver to read the media status * register. If we have a link partner which does not * support NWAY, this is the register which will tell * us the results of parallel detection. */ case RL_MEDIASTAT: rval = CSR_READ_1(sc, RL_MEDIASTAT); return (rval); default: device_printf(sc->rl_dev, "bad phy register\n"); return (0); } rval = CSR_READ_2(sc, re8139_reg); if (sc->rl_type == RL_8139CPLUS && re8139_reg == RL_BMCR) { /* 8139C+ has different bit layout. */ rval &= ~(BMCR_LOOP | BMCR_ISO); } return (rval); } static int re_miibus_writereg(dev, phy, reg, data) device_t dev; int phy, reg, data; { struct rl_softc *sc; u_int16_t re8139_reg = 0; int rval = 0; sc = device_get_softc(dev); if (sc->rl_type == RL_8169) { rval = re_gmii_writereg(dev, phy, reg, data); return (rval); } /* Pretend the internal PHY is only at address 0 */ if (phy) return (0); switch (reg) { case MII_BMCR: re8139_reg = RL_BMCR; if (sc->rl_type == RL_8139CPLUS) { /* 8139C+ has different bit layout. */ data &= ~(BMCR_LOOP | BMCR_ISO); } break; case MII_BMSR: re8139_reg = RL_BMSR; break; case MII_ANAR: re8139_reg = RL_ANAR; break; case MII_ANER: re8139_reg = RL_ANER; break; case MII_ANLPAR: re8139_reg = RL_LPAR; break; case MII_PHYIDR1: case MII_PHYIDR2: return (0); break; default: device_printf(sc->rl_dev, "bad phy register\n"); return (0); } CSR_WRITE_2(sc, re8139_reg, data); return (0); } static void re_miibus_statchg(dev) device_t dev; { } /* * Program the 64-bit multicast hash filter. */ static void re_setmulti(sc) struct rl_softc *sc; { struct ifnet *ifp; int h = 0; u_int32_t hashes[2] = { 0, 0 }; struct ifmultiaddr *ifma; u_int32_t rxfilt; int mcnt = 0; RL_LOCK_ASSERT(sc); ifp = sc->rl_ifp; rxfilt = CSR_READ_4(sc, RL_RXCFG); if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { rxfilt |= RL_RXCFG_RX_MULTI; CSR_WRITE_4(sc, RL_RXCFG, rxfilt); CSR_WRITE_4(sc, RL_MAR0, 0xFFFFFFFF); CSR_WRITE_4(sc, RL_MAR4, 0xFFFFFFFF); return; } /* first, zot all the existing hash bits */ CSR_WRITE_4(sc, RL_MAR0, 0); CSR_WRITE_4(sc, RL_MAR4, 0); /* now program new ones */ IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; h = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; if (h < 32) hashes[0] |= (1 << h); else hashes[1] |= (1 << (h - 32)); mcnt++; } IF_ADDR_UNLOCK(ifp); if (mcnt) rxfilt |= RL_RXCFG_RX_MULTI; else rxfilt &= ~RL_RXCFG_RX_MULTI; CSR_WRITE_4(sc, RL_RXCFG, rxfilt); CSR_WRITE_4(sc, RL_MAR0, hashes[0]); CSR_WRITE_4(sc, RL_MAR4, hashes[1]); } static void re_reset(sc) struct rl_softc *sc; { register int i; RL_LOCK_ASSERT(sc); CSR_WRITE_1(sc, RL_COMMAND, RL_CMD_RESET); for (i = 0; i < RL_TIMEOUT; i++) { DELAY(10); if (!(CSR_READ_1(sc, RL_COMMAND) & RL_CMD_RESET)) break; } if (i == RL_TIMEOUT) device_printf(sc->rl_dev, "reset never completed!\n"); CSR_WRITE_1(sc, 0x82, 1); } #ifdef RE_DIAG /* * The following routine is designed to test for a defect on some * 32-bit 8169 cards. Some of these NICs have the REQ64# and ACK64# * lines connected to the bus, however for a 32-bit only card, they * should be pulled high. The result of this defect is that the * NIC will not work right if you plug it into a 64-bit slot: DMA * operations will be done with 64-bit transfers, which will fail * because the 64-bit data lines aren't connected. * * There's no way to work around this (short of talking a soldering * iron to the board), however we can detect it. The method we use * here is to put the NIC into digital loopback mode, set the receiver * to promiscuous mode, and then try to send a frame. We then compare * the frame data we sent to what was received. If the data matches, * then the NIC is working correctly, otherwise we know the user has * a defective NIC which has been mistakenly plugged into a 64-bit PCI * slot. In the latter case, there's no way the NIC can work correctly, * so we print out a message on the console and abort the device attach. */ static int re_diag(sc) struct rl_softc *sc; { struct ifnet *ifp = sc->rl_ifp; struct mbuf *m0; struct ether_header *eh; struct rl_desc *cur_rx; u_int16_t status; u_int32_t rxstat; int total_len, i, error = 0, phyaddr; u_int8_t dst[] = { 0x00, 'h', 'e', 'l', 'l', 'o' }; u_int8_t src[] = { 0x00, 'w', 'o', 'r', 'l', 'd' }; /* Allocate a single mbuf */ MGETHDR(m0, M_DONTWAIT, MT_DATA); if (m0 == NULL) return (ENOBUFS); RL_LOCK(sc); /* * Initialize the NIC in test mode. This sets the chip up * so that it can send and receive frames, but performs the * following special functions: * - Puts receiver in promiscuous mode * - Enables digital loopback mode * - Leaves interrupts turned off */ ifp->if_flags |= IFF_PROMISC; sc->rl_testmode = 1; re_reset(sc); re_init_locked(sc); sc->rl_link = 1; if (sc->rl_type == RL_8169) phyaddr = 1; else phyaddr = 0; re_miibus_writereg(sc->rl_dev, phyaddr, MII_BMCR, BMCR_RESET); for (i = 0; i < RL_TIMEOUT; i++) { status = re_miibus_readreg(sc->rl_dev, phyaddr, MII_BMCR); if (!(status & BMCR_RESET)) break; } re_miibus_writereg(sc->rl_dev, phyaddr, MII_BMCR, BMCR_LOOP); CSR_WRITE_2(sc, RL_ISR, RL_INTRS); DELAY(100000); /* Put some data in the mbuf */ eh = mtod(m0, struct ether_header *); bcopy ((char *)&dst, eh->ether_dhost, ETHER_ADDR_LEN); bcopy ((char *)&src, eh->ether_shost, ETHER_ADDR_LEN); eh->ether_type = htons(ETHERTYPE_IP); m0->m_pkthdr.len = m0->m_len = ETHER_MIN_LEN - ETHER_CRC_LEN; /* * Queue the packet, start transmission. * Note: IF_HANDOFF() ultimately calls re_start() for us. */ CSR_WRITE_2(sc, RL_ISR, 0xFFFF); RL_UNLOCK(sc); /* XXX: re_diag must not be called when in ALTQ mode */ IF_HANDOFF(&ifp->if_snd, m0, ifp); RL_LOCK(sc); m0 = NULL; /* Wait for it to propagate through the chip */ DELAY(100000); for (i = 0; i < RL_TIMEOUT; i++) { status = CSR_READ_2(sc, RL_ISR); CSR_WRITE_2(sc, RL_ISR, status); if ((status & (RL_ISR_TIMEOUT_EXPIRED|RL_ISR_RX_OK)) == (RL_ISR_TIMEOUT_EXPIRED|RL_ISR_RX_OK)) break; DELAY(10); } if (i == RL_TIMEOUT) { device_printf(sc->rl_dev, "diagnostic failed, failed to receive packet in" " loopback mode\n"); error = EIO; goto done; } /* * The packet should have been dumped into the first * entry in the RX DMA ring. Grab it from there. */ bus_dmamap_sync(sc->rl_ldata.rl_rx_list_tag, sc->rl_ldata.rl_rx_list_map, BUS_DMASYNC_POSTREAD); bus_dmamap_sync(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_rx_dmamap[0], BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_rx_dmamap[0]); m0 = sc->rl_ldata.rl_rx_mbuf[0]; sc->rl_ldata.rl_rx_mbuf[0] = NULL; eh = mtod(m0, struct ether_header *); cur_rx = &sc->rl_ldata.rl_rx_list[0]; total_len = RL_RXBYTES(cur_rx); rxstat = le32toh(cur_rx->rl_cmdstat); if (total_len != ETHER_MIN_LEN) { device_printf(sc->rl_dev, "diagnostic failed, received short packet\n"); error = EIO; goto done; } /* Test that the received packet data matches what we sent. */ if (bcmp((char *)&eh->ether_dhost, (char *)&dst, ETHER_ADDR_LEN) || bcmp((char *)&eh->ether_shost, (char *)&src, ETHER_ADDR_LEN) || ntohs(eh->ether_type) != ETHERTYPE_IP) { device_printf(sc->rl_dev, "WARNING, DMA FAILURE!\n"); device_printf(sc->rl_dev, "expected TX data: %6D/%6D/0x%x\n", dst, ":", src, ":", ETHERTYPE_IP); device_printf(sc->rl_dev, "received RX data: %6D/%6D/0x%x\n", eh->ether_dhost, ":", eh->ether_shost, ":", ntohs(eh->ether_type)); device_printf(sc->rl_dev, "You may have a defective 32-bit " "NIC plugged into a 64-bit PCI slot.\n"); device_printf(sc->rl_dev, "Please re-install the NIC in a " "32-bit slot for proper operation.\n"); device_printf(sc->rl_dev, "Read the re(4) man page for more " "details.\n"); error = EIO; } done: /* Turn interface off, release resources */ sc->rl_testmode = 0; sc->rl_link = 0; ifp->if_flags &= ~IFF_PROMISC; re_stop(sc); if (m0 != NULL) m_freem(m0); RL_UNLOCK(sc); return (error); } #endif /* * Probe for a RealTek 8139C+/8169/8110 chip. Check the PCI vendor and device * IDs against our list and return a device name if we find a match. */ static int re_probe(dev) device_t dev; { struct rl_type *t; struct rl_softc *sc; int rid; u_int32_t hwrev; t = re_devs; sc = device_get_softc(dev); while (t->rl_name != NULL) { if ((pci_get_vendor(dev) == t->rl_vid) && (pci_get_device(dev) == t->rl_did)) { /* * Only attach to rev. 3 of the Linksys EG1032 adapter. * Rev. 2 i supported by sk(4). */ if ((t->rl_vid == LINKSYS_VENDORID) && (t->rl_did == LINKSYS_DEVICEID_EG1032) && (pci_get_subdevice(dev) != LINKSYS_SUBDEVICE_EG1032_REV3)) { t++; continue; } /* * Temporarily map the I/O space * so we can read the chip ID register. */ rid = RL_RID; sc->rl_res = bus_alloc_resource_any(dev, RL_RES, &rid, RF_ACTIVE); if (sc->rl_res == NULL) { device_printf(dev, "couldn't map ports/memory\n"); return (ENXIO); } sc->rl_btag = rman_get_bustag(sc->rl_res); sc->rl_bhandle = rman_get_bushandle(sc->rl_res); hwrev = CSR_READ_4(sc, RL_TXCFG) & RL_TXCFG_HWREV; bus_release_resource(dev, RL_RES, RL_RID, sc->rl_res); if (t->rl_basetype == hwrev) { device_set_desc(dev, t->rl_name); return (BUS_PROBE_DEFAULT); } } t++; } return (ENXIO); } /* * This routine takes the segment list provided as the result of * a bus_dma_map_load() operation and assigns the addresses/lengths * to RealTek DMA descriptors. This can be called either by the RX * code or the TX code. In the RX case, we'll probably wind up mapping * at most one segment. For the TX case, there could be any number of * segments since TX packets may span multiple mbufs. In either case, * if the number of segments is larger than the rl_maxsegs limit * specified by the caller, we abort the mapping operation. Sadly, * whoever designed the buffer mapping API did not provide a way to * return an error from here, so we have to fake it a bit. */ static void re_dma_map_desc(arg, segs, nseg, mapsize, error) void *arg; bus_dma_segment_t *segs; int nseg; bus_size_t mapsize; int error; { struct rl_dmaload_arg *ctx; struct rl_desc *d = NULL; int i = 0, idx; u_int32_t cmdstat; int totlen = 0; if (error) return; ctx = arg; /* Signal error to caller if there's too many segments */ if (nseg > ctx->rl_maxsegs) { ctx->rl_maxsegs = 0; return; } /* * Map the segment array into descriptors. Note that we set the * start-of-frame and end-of-frame markers for either TX or RX, but * they really only have meaning in the TX case. (In the RX case, * it's the chip that tells us where packets begin and end.) * We also keep track of the end of the ring and set the * end-of-ring bits as needed, and we set the ownership bits * in all except the very first descriptor. (The caller will * set this descriptor later when it start transmission or * reception.) */ idx = ctx->rl_idx; for (;;) { d = &ctx->rl_ring[idx]; if (le32toh(d->rl_cmdstat) & RL_RDESC_STAT_OWN) { ctx->rl_maxsegs = 0; return; } cmdstat = segs[i].ds_len; totlen += segs[i].ds_len; d->rl_bufaddr_lo = htole32(RL_ADDR_LO(segs[i].ds_addr)); d->rl_bufaddr_hi = htole32(RL_ADDR_HI(segs[i].ds_addr)); if (i == 0) cmdstat |= RL_TDESC_CMD_SOF; else cmdstat |= RL_TDESC_CMD_OWN; if (idx == (RL_RX_DESC_CNT - 1)) cmdstat |= RL_TDESC_CMD_EOR; d->rl_cmdstat = htole32(cmdstat | ctx->rl_flags); i++; if (i == nseg) break; RL_DESC_INC(idx); } d->rl_cmdstat |= htole32(RL_TDESC_CMD_EOF); ctx->rl_maxsegs = nseg; ctx->rl_idx = idx; } /* * Map a single buffer address. */ static void re_dma_map_addr(arg, segs, nseg, error) void *arg; bus_dma_segment_t *segs; int nseg; int error; { bus_addr_t *addr; if (error) return; KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg)); addr = arg; *addr = segs->ds_addr; } static int re_allocmem(dev, sc) device_t dev; struct rl_softc *sc; { int error; int nseg; int i; /* * Allocate map for RX mbufs. */ nseg = 32; error = bus_dma_tag_create(sc->rl_parent_tag, ETHER_ALIGN, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES * nseg, nseg, MCLBYTES, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->rl_ldata.rl_mtag); if (error) { device_printf(dev, "could not allocate dma tag\n"); return (ENOMEM); } /* * Allocate map for TX descriptor list. */ error = bus_dma_tag_create(sc->rl_parent_tag, RL_RING_ALIGN, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, RL_TX_LIST_SZ, 1, RL_TX_LIST_SZ, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->rl_ldata.rl_tx_list_tag); if (error) { device_printf(dev, "could not allocate dma tag\n"); return (ENOMEM); } /* Allocate DMA'able memory for the TX ring */ error = bus_dmamem_alloc(sc->rl_ldata.rl_tx_list_tag, (void **)&sc->rl_ldata.rl_tx_list, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &sc->rl_ldata.rl_tx_list_map); if (error) return (ENOMEM); /* Load the map for the TX ring. */ error = bus_dmamap_load(sc->rl_ldata.rl_tx_list_tag, sc->rl_ldata.rl_tx_list_map, sc->rl_ldata.rl_tx_list, RL_TX_LIST_SZ, re_dma_map_addr, &sc->rl_ldata.rl_tx_list_addr, BUS_DMA_NOWAIT); /* Create DMA maps for TX buffers */ for (i = 0; i < RL_TX_DESC_CNT; i++) { error = bus_dmamap_create(sc->rl_ldata.rl_mtag, 0, &sc->rl_ldata.rl_tx_dmamap[i]); if (error) { device_printf(dev, "can't create DMA map for TX\n"); return (ENOMEM); } } /* * Allocate map for RX descriptor list. */ error = bus_dma_tag_create(sc->rl_parent_tag, RL_RING_ALIGN, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, RL_RX_LIST_SZ, 1, RL_RX_LIST_SZ, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->rl_ldata.rl_rx_list_tag); if (error) { device_printf(dev, "could not allocate dma tag\n"); return (ENOMEM); } /* Allocate DMA'able memory for the RX ring */ error = bus_dmamem_alloc(sc->rl_ldata.rl_rx_list_tag, (void **)&sc->rl_ldata.rl_rx_list, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &sc->rl_ldata.rl_rx_list_map); if (error) return (ENOMEM); /* Load the map for the RX ring. */ error = bus_dmamap_load(sc->rl_ldata.rl_rx_list_tag, sc->rl_ldata.rl_rx_list_map, sc->rl_ldata.rl_rx_list, RL_RX_LIST_SZ, re_dma_map_addr, &sc->rl_ldata.rl_rx_list_addr, BUS_DMA_NOWAIT); /* Create DMA maps for RX buffers */ for (i = 0; i < RL_RX_DESC_CNT; i++) { error = bus_dmamap_create(sc->rl_ldata.rl_mtag, 0, &sc->rl_ldata.rl_rx_dmamap[i]); if (error) { device_printf(dev, "can't create DMA map for RX\n"); return (ENOMEM); } } return (0); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int re_attach(dev) device_t dev; { u_char eaddr[ETHER_ADDR_LEN]; u_int16_t as[ETHER_ADDR_LEN / 2]; struct rl_softc *sc; struct ifnet *ifp; struct rl_hwrev *hw_rev; int hwrev; u_int16_t re_did = 0; int error = 0, rid, i; sc = device_get_softc(dev); sc->rl_dev = dev; mtx_init(&sc->rl_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->rl_stat_callout, &sc->rl_mtx, 0); /* * Map control/status registers. */ pci_enable_busmaster(dev); rid = RL_RID; sc->rl_res = bus_alloc_resource_any(dev, RL_RES, &rid, RF_ACTIVE); if (sc->rl_res == NULL) { device_printf(dev, "couldn't map ports/memory\n"); error = ENXIO; goto fail; } sc->rl_btag = rman_get_bustag(sc->rl_res); sc->rl_bhandle = rman_get_bushandle(sc->rl_res); /* Allocate interrupt */ rid = 0; sc->rl_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->rl_irq == NULL) { device_printf(dev, "couldn't map interrupt\n"); error = ENXIO; goto fail; } /* Reset the adapter. */ RL_LOCK(sc); re_reset(sc); RL_UNLOCK(sc); hw_rev = re_hwrevs; hwrev = CSR_READ_4(sc, RL_TXCFG) & RL_TXCFG_HWREV; while (hw_rev->rl_desc != NULL) { if (hw_rev->rl_rev == hwrev) { sc->rl_type = hw_rev->rl_type; break; } hw_rev++; } sc->rl_eewidth = 6; re_read_eeprom(sc, (caddr_t)&re_did, 0, 1); if (re_did != 0x8129) sc->rl_eewidth = 8; /* * Get station address from the EEPROM. */ re_read_eeprom(sc, (caddr_t)as, RL_EE_EADDR, 3); for (i = 0; i < ETHER_ADDR_LEN / 2; i++) as[i] = le16toh(as[i]); bcopy(as, eaddr, sizeof(eaddr)); if (sc->rl_type == RL_8169) { /* Set RX length mask */ sc->rl_rxlenmask = RL_RDESC_STAT_GFRAGLEN; sc->rl_txstart = RL_GTXSTART; } else { /* Set RX length mask */ sc->rl_rxlenmask = RL_RDESC_STAT_FRAGLEN; sc->rl_txstart = RL_TXSTART; } /* * Allocate the parent bus DMA tag appropriate for PCI. */ #define RL_NSEG_NEW 32 error = bus_dma_tag_create(NULL, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MAXBSIZE, RL_NSEG_NEW, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT,/* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->rl_parent_tag); if (error) goto fail; error = re_allocmem(dev, sc); if (error) goto fail; ifp = sc->rl_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "can not if_alloc()\n"); error = ENOSPC; goto fail; } /* Do MII setup */ if (mii_phy_probe(dev, &sc->rl_miibus, re_ifmedia_upd, re_ifmedia_sts)) { device_printf(dev, "MII without any phy!\n"); error = ENXIO; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_mtu = ETHERMTU; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = re_ioctl; ifp->if_capabilities = IFCAP_VLAN_MTU; ifp->if_start = re_start; ifp->if_hwassist = RE_CSUM_FEATURES; ifp->if_capabilities |= IFCAP_HWCSUM|IFCAP_VLAN_HWTAGGING; ifp->if_capenable = ifp->if_capabilities; #ifdef DEVICE_POLLING ifp->if_capabilities |= IFCAP_POLLING; #endif ifp->if_watchdog = re_watchdog; ifp->if_init = re_init; IFQ_SET_MAXLEN(&ifp->if_snd, RL_IFQ_MAXLEN); ifp->if_snd.ifq_drv_maxlen = RL_IFQ_MAXLEN; IFQ_SET_READY(&ifp->if_snd); TASK_INIT(&sc->rl_txtask, 1, re_tx_task, ifp); TASK_INIT(&sc->rl_inttask, 0, re_int_task, sc); /* * Call MI attach routine. */ ether_ifattach(ifp, eaddr); #ifdef RE_DIAG /* * Perform hardware diagnostic on the original RTL8169. * Some 32-bit cards were incorrectly wired and would * malfunction if plugged into a 64-bit slot. */ if (hwrev == RL_HWREV_8169) { error = re_diag(sc); if (error) { device_printf(dev, "attach aborted due to hardware diag failure\n"); ether_ifdetach(ifp); goto fail; } } #endif /* Hook interrupt last to avoid having to lock softc */ error = bus_setup_intr(dev, sc->rl_irq, INTR_TYPE_NET | INTR_MPSAFE | INTR_FAST, re_intr, sc, &sc->rl_intrhand); if (error) { device_printf(dev, "couldn't set up irq\n"); ether_ifdetach(ifp); } fail: if (error) re_detach(dev); return (error); } /* * Shutdown hardware and free up resources. This can be called any * time after the mutex has been initialized. It is called in both * the error case in attach and the normal detach case so it needs * to be careful about only freeing resources that have actually been * allocated. */ static int re_detach(dev) device_t dev; { struct rl_softc *sc; struct ifnet *ifp; int i; sc = device_get_softc(dev); ifp = sc->rl_ifp; KASSERT(mtx_initialized(&sc->rl_mtx), ("re mutex not initialized")); #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) ether_poll_deregister(ifp); #endif /* These should only be active if attach succeeded */ if (device_is_attached(dev)) { RL_LOCK(sc); #if 0 sc->suspended = 1; #endif re_stop(sc); RL_UNLOCK(sc); callout_drain(&sc->rl_stat_callout); /* * Force off the IFF_UP flag here, in case someone * still had a BPF descriptor attached to this * interface. If they do, ether_ifdetach() will cause * the BPF code to try and clear the promisc mode * flag, which will bubble down to re_ioctl(), * which will try to call re_init() again. This will * turn the NIC back on and restart the MII ticker, * which will panic the system when the kernel tries * to invoke the re_tick() function that isn't there * anymore. */ ifp->if_flags &= ~IFF_UP; ether_ifdetach(ifp); } if (sc->rl_miibus) device_delete_child(dev, sc->rl_miibus); bus_generic_detach(dev); /* * The rest is resource deallocation, so we should already be * stopped here. */ if (sc->rl_intrhand) bus_teardown_intr(dev, sc->rl_irq, sc->rl_intrhand); if (ifp != NULL) if_free(ifp); if (sc->rl_irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->rl_irq); if (sc->rl_res) bus_release_resource(dev, RL_RES, RL_RID, sc->rl_res); /* Yield the CPU long enough for any tasks to drain */ tsleep(sc, PPAUSE, "rewait", hz); /* Unload and free the RX DMA ring memory and map */ if (sc->rl_ldata.rl_rx_list_tag) { bus_dmamap_unload(sc->rl_ldata.rl_rx_list_tag, sc->rl_ldata.rl_rx_list_map); bus_dmamem_free(sc->rl_ldata.rl_rx_list_tag, sc->rl_ldata.rl_rx_list, sc->rl_ldata.rl_rx_list_map); bus_dma_tag_destroy(sc->rl_ldata.rl_rx_list_tag); } /* Unload and free the TX DMA ring memory and map */ if (sc->rl_ldata.rl_tx_list_tag) { bus_dmamap_unload(sc->rl_ldata.rl_tx_list_tag, sc->rl_ldata.rl_tx_list_map); bus_dmamem_free(sc->rl_ldata.rl_tx_list_tag, sc->rl_ldata.rl_tx_list, sc->rl_ldata.rl_tx_list_map); bus_dma_tag_destroy(sc->rl_ldata.rl_tx_list_tag); } /* Destroy all the RX and TX buffer maps */ if (sc->rl_ldata.rl_mtag) { for (i = 0; i < RL_TX_DESC_CNT; i++) bus_dmamap_destroy(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_tx_dmamap[i]); for (i = 0; i < RL_RX_DESC_CNT; i++) bus_dmamap_destroy(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_rx_dmamap[i]); bus_dma_tag_destroy(sc->rl_ldata.rl_mtag); } /* Unload and free the stats buffer and map */ if (sc->rl_ldata.rl_stag) { bus_dmamap_unload(sc->rl_ldata.rl_stag, sc->rl_ldata.rl_rx_list_map); bus_dmamem_free(sc->rl_ldata.rl_stag, sc->rl_ldata.rl_stats, sc->rl_ldata.rl_smap); bus_dma_tag_destroy(sc->rl_ldata.rl_stag); } if (sc->rl_parent_tag) bus_dma_tag_destroy(sc->rl_parent_tag); mtx_destroy(&sc->rl_mtx); return (0); } static int re_newbuf(sc, idx, m) struct rl_softc *sc; int idx; struct mbuf *m; { struct rl_dmaload_arg arg; struct mbuf *n = NULL; int error; if (m == NULL) { n = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (n == NULL) return (ENOBUFS); m = n; } else m->m_data = m->m_ext.ext_buf; m->m_len = m->m_pkthdr.len = MCLBYTES; #ifdef RE_FIXUP_RX /* * This is part of an evil trick to deal with non-x86 platforms. * The RealTek chip requires RX buffers to be aligned on 64-bit * boundaries, but that will hose non-x86 machines. To get around * this, we leave some empty space at the start of each buffer * and for non-x86 hosts, we copy the buffer back six bytes * to achieve word alignment. This is slightly more efficient * than allocating a new buffer, copying the contents, and * discarding the old buffer. */ m_adj(m, RE_ETHER_ALIGN); #endif arg.sc = sc; arg.rl_idx = idx; arg.rl_maxsegs = 1; arg.rl_flags = 0; arg.rl_ring = sc->rl_ldata.rl_rx_list; error = bus_dmamap_load_mbuf(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_rx_dmamap[idx], m, re_dma_map_desc, &arg, BUS_DMA_NOWAIT); if (error || arg.rl_maxsegs != 1) { if (n != NULL) m_freem(n); return (ENOMEM); } sc->rl_ldata.rl_rx_list[idx].rl_cmdstat |= htole32(RL_RDESC_CMD_OWN); sc->rl_ldata.rl_rx_mbuf[idx] = m; bus_dmamap_sync(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_rx_dmamap[idx], BUS_DMASYNC_PREREAD); return (0); } #ifdef RE_FIXUP_RX static __inline void re_fixup_rx(m) struct mbuf *m; { int i; uint16_t *src, *dst; src = mtod(m, uint16_t *); dst = src - (RE_ETHER_ALIGN - ETHER_ALIGN) / sizeof *src; for (i = 0; i < (m->m_len / sizeof(uint16_t) + 1); i++) *dst++ = *src++; m->m_data -= RE_ETHER_ALIGN - ETHER_ALIGN; return; } #endif static int re_tx_list_init(sc) struct rl_softc *sc; { RL_LOCK_ASSERT(sc); bzero ((char *)sc->rl_ldata.rl_tx_list, RL_TX_LIST_SZ); bzero ((char *)&sc->rl_ldata.rl_tx_mbuf, (RL_TX_DESC_CNT * sizeof(struct mbuf *))); bus_dmamap_sync(sc->rl_ldata.rl_tx_list_tag, sc->rl_ldata.rl_tx_list_map, BUS_DMASYNC_PREWRITE); sc->rl_ldata.rl_tx_prodidx = 0; sc->rl_ldata.rl_tx_considx = 0; sc->rl_ldata.rl_tx_free = RL_TX_DESC_CNT; return (0); } static int re_rx_list_init(sc) struct rl_softc *sc; { int i; bzero ((char *)sc->rl_ldata.rl_rx_list, RL_RX_LIST_SZ); bzero ((char *)&sc->rl_ldata.rl_rx_mbuf, (RL_RX_DESC_CNT * sizeof(struct mbuf *))); for (i = 0; i < RL_RX_DESC_CNT; i++) { if (re_newbuf(sc, i, NULL) == ENOBUFS) return (ENOBUFS); } /* Flush the RX descriptors */ bus_dmamap_sync(sc->rl_ldata.rl_rx_list_tag, sc->rl_ldata.rl_rx_list_map, BUS_DMASYNC_PREWRITE|BUS_DMASYNC_PREREAD); sc->rl_ldata.rl_rx_prodidx = 0; sc->rl_head = sc->rl_tail = NULL; return (0); } /* * RX handler for C+ and 8169. For the gigE chips, we support * the reception of jumbo frames that have been fragmented * across multiple 2K mbuf cluster buffers. */ static int re_rxeof(sc) struct rl_softc *sc; { struct mbuf *m; struct ifnet *ifp; int i, total_len; struct rl_desc *cur_rx; u_int32_t rxstat, rxvlan; int maxpkt = 16; RL_LOCK_ASSERT(sc); ifp = sc->rl_ifp; i = sc->rl_ldata.rl_rx_prodidx; /* Invalidate the descriptor memory */ bus_dmamap_sync(sc->rl_ldata.rl_rx_list_tag, sc->rl_ldata.rl_rx_list_map, BUS_DMASYNC_POSTREAD); while (!RL_OWN(&sc->rl_ldata.rl_rx_list[i]) && maxpkt) { cur_rx = &sc->rl_ldata.rl_rx_list[i]; m = sc->rl_ldata.rl_rx_mbuf[i]; total_len = RL_RXBYTES(cur_rx); rxstat = le32toh(cur_rx->rl_cmdstat); rxvlan = le32toh(cur_rx->rl_vlanctl); /* Invalidate the RX mbuf and unload its map */ bus_dmamap_sync(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_rx_dmamap[i], BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_rx_dmamap[i]); if (!(rxstat & RL_RDESC_STAT_EOF)) { m->m_len = RE_RX_DESC_BUFLEN; if (sc->rl_head == NULL) sc->rl_head = sc->rl_tail = m; else { m->m_flags &= ~M_PKTHDR; sc->rl_tail->m_next = m; sc->rl_tail = m; } re_newbuf(sc, i, NULL); RL_DESC_INC(i); continue; } /* * NOTE: for the 8139C+, the frame length field * is always 12 bits in size, but for the gigE chips, * it is 13 bits (since the max RX frame length is 16K). * Unfortunately, all 32 bits in the status word * were already used, so to make room for the extra * length bit, RealTek took out the 'frame alignment * error' bit and shifted the other status bits * over one slot. The OWN, EOR, FS and LS bits are * still in the same places. We have already extracted * the frame length and checked the OWN bit, so rather * than using an alternate bit mapping, we shift the * status bits one space to the right so we can evaluate * them using the 8169 status as though it was in the * same format as that of the 8139C+. */ if (sc->rl_type == RL_8169) rxstat >>= 1; /* * if total_len > 2^13-1, both _RXERRSUM and _GIANT will be * set, but if CRC is clear, it will still be a valid frame. */ if (rxstat & RL_RDESC_STAT_RXERRSUM && !(total_len > 8191 && (rxstat & RL_RDESC_STAT_ERRS) == RL_RDESC_STAT_GIANT)) { ifp->if_ierrors++; /* * If this is part of a multi-fragment packet, * discard all the pieces. */ if (sc->rl_head != NULL) { m_freem(sc->rl_head); sc->rl_head = sc->rl_tail = NULL; } re_newbuf(sc, i, m); RL_DESC_INC(i); continue; } /* * If allocating a replacement mbuf fails, * reload the current one. */ if (re_newbuf(sc, i, NULL)) { ifp->if_ierrors++; if (sc->rl_head != NULL) { m_freem(sc->rl_head); sc->rl_head = sc->rl_tail = NULL; } re_newbuf(sc, i, m); RL_DESC_INC(i); continue; } RL_DESC_INC(i); if (sc->rl_head != NULL) { m->m_len = total_len % RE_RX_DESC_BUFLEN; if (m->m_len == 0) m->m_len = RE_RX_DESC_BUFLEN; /* * Special case: if there's 4 bytes or less * in this buffer, the mbuf can be discarded: * the last 4 bytes is the CRC, which we don't * care about anyway. */ if (m->m_len <= ETHER_CRC_LEN) { sc->rl_tail->m_len -= (ETHER_CRC_LEN - m->m_len); m_freem(m); } else { m->m_len -= ETHER_CRC_LEN; m->m_flags &= ~M_PKTHDR; sc->rl_tail->m_next = m; } m = sc->rl_head; sc->rl_head = sc->rl_tail = NULL; m->m_pkthdr.len = total_len - ETHER_CRC_LEN; } else m->m_pkthdr.len = m->m_len = (total_len - ETHER_CRC_LEN); #ifdef RE_FIXUP_RX re_fixup_rx(m); #endif ifp->if_ipackets++; m->m_pkthdr.rcvif = ifp; /* Do RX checksumming if enabled */ if (ifp->if_capenable & IFCAP_RXCSUM) { /* Check IP header checksum */ if (rxstat & RL_RDESC_STAT_PROTOID) m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; if (!(rxstat & RL_RDESC_STAT_IPSUMBAD)) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; /* Check TCP/UDP checksum */ if ((RL_TCPPKT(rxstat) && !(rxstat & RL_RDESC_STAT_TCPSUMBAD)) || (RL_UDPPKT(rxstat) && !(rxstat & RL_RDESC_STAT_UDPSUMBAD))) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID|CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } maxpkt--; if (rxvlan & RL_RDESC_VLANCTL_TAG) { - VLAN_INPUT_TAG(ifp, m, - ntohs((rxvlan & RL_RDESC_VLANCTL_DATA))); - if (m == NULL) - continue; + m->m_pkthdr.ether_vtag = + ntohs((rxvlan & RL_RDESC_VLANCTL_DATA)); + m->m_flags |= M_VLANTAG; } RL_UNLOCK(sc); (*ifp->if_input)(ifp, m); RL_LOCK(sc); } /* Flush the RX DMA ring */ bus_dmamap_sync(sc->rl_ldata.rl_rx_list_tag, sc->rl_ldata.rl_rx_list_map, BUS_DMASYNC_PREWRITE|BUS_DMASYNC_PREREAD); sc->rl_ldata.rl_rx_prodidx = i; if (maxpkt) return(EAGAIN); return(0); } static void re_txeof(sc) struct rl_softc *sc; { struct ifnet *ifp; u_int32_t txstat; int idx; ifp = sc->rl_ifp; idx = sc->rl_ldata.rl_tx_considx; /* Invalidate the TX descriptor list */ bus_dmamap_sync(sc->rl_ldata.rl_tx_list_tag, sc->rl_ldata.rl_tx_list_map, BUS_DMASYNC_POSTREAD); while (sc->rl_ldata.rl_tx_free < RL_TX_DESC_CNT) { txstat = le32toh(sc->rl_ldata.rl_tx_list[idx].rl_cmdstat); if (txstat & RL_TDESC_CMD_OWN) break; sc->rl_ldata.rl_tx_list[idx].rl_bufaddr_lo = 0; /* * We only stash mbufs in the last descriptor * in a fragment chain, which also happens to * be the only place where the TX status bits * are valid. */ if (txstat & RL_TDESC_CMD_EOF) { m_freem(sc->rl_ldata.rl_tx_mbuf[idx]); sc->rl_ldata.rl_tx_mbuf[idx] = NULL; bus_dmamap_unload(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_tx_dmamap[idx]); if (txstat & (RL_TDESC_STAT_EXCESSCOL| RL_TDESC_STAT_COLCNT)) ifp->if_collisions++; if (txstat & RL_TDESC_STAT_TXERRSUM) ifp->if_oerrors++; else ifp->if_opackets++; } sc->rl_ldata.rl_tx_free++; RL_DESC_INC(idx); } /* No changes made to the TX ring, so no flush needed */ if (sc->rl_ldata.rl_tx_free) { sc->rl_ldata.rl_tx_considx = idx; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; ifp->if_timer = 0; } /* * Some chips will ignore a second TX request issued while an * existing transmission is in progress. If the transmitter goes * idle but there are still packets waiting to be sent, we need * to restart the channel here to flush them out. This only seems * to be required with the PCIe devices. */ if (sc->rl_ldata.rl_tx_free < RL_TX_DESC_CNT) CSR_WRITE_1(sc, sc->rl_txstart, RL_TXSTART_START); #ifdef RE_TX_MODERATION /* * If not all descriptors have been released reaped yet, * reload the timer so that we will eventually get another * interrupt that will cause us to re-enter this routine. * This is done in case the transmitter has gone idle. */ if (sc->rl_ldata.rl_tx_free != RL_TX_DESC_CNT) CSR_WRITE_4(sc, RL_TIMERCNT, 1); #endif } static void re_tick(xsc) void *xsc; { struct rl_softc *sc; struct mii_data *mii; struct ifnet *ifp; sc = xsc; ifp = sc->rl_ifp; RL_LOCK_ASSERT(sc); mii = device_get_softc(sc->rl_miibus); mii_tick(mii); if (sc->rl_link) { if (!(mii->mii_media_status & IFM_ACTIVE)) sc->rl_link = 0; } else { if (mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->rl_link = 1; if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) taskqueue_enqueue_fast(taskqueue_fast, &sc->rl_txtask); } } callout_reset(&sc->rl_stat_callout, hz, re_tick, sc); } #ifdef DEVICE_POLLING static void re_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct rl_softc *sc = ifp->if_softc; RL_LOCK(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) re_poll_locked(ifp, cmd, count); RL_UNLOCK(sc); } static void re_poll_locked(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct rl_softc *sc = ifp->if_softc; RL_LOCK_ASSERT(sc); sc->rxcycles = count; re_rxeof(sc); re_txeof(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) taskqueue_enqueue_fast(taskqueue_fast, &sc->rl_txtask); if (cmd == POLL_AND_CHECK_STATUS) { /* also check status register */ u_int16_t status; status = CSR_READ_2(sc, RL_ISR); if (status == 0xffff) return; if (status) CSR_WRITE_2(sc, RL_ISR, status); /* * XXX check behaviour on receiver stalls. */ if (status & RL_ISR_SYSTEM_ERR) { re_reset(sc); re_init_locked(sc); } } } #endif /* DEVICE_POLLING */ static void re_intr(arg) void *arg; { struct rl_softc *sc; struct ifnet *ifp; uint16_t status; sc = arg; ifp = sc->rl_ifp; status = CSR_READ_2(sc, RL_ISR); if (status == 0xFFFF || (status & RL_INTRS_CPLUS) == 0) return; CSR_WRITE_2(sc, RL_IMR, 0); taskqueue_enqueue_fast(taskqueue_fast, &sc->rl_inttask); return; } static void re_int_task(arg, npending) void *arg; int npending; { struct rl_softc *sc; struct ifnet *ifp; u_int16_t status; int rval = 0; sc = arg; ifp = sc->rl_ifp; RL_LOCK(sc); status = CSR_READ_2(sc, RL_ISR); CSR_WRITE_2(sc, RL_ISR, status); if (sc->suspended || !(ifp->if_flags & IFF_UP)) { RL_UNLOCK(sc); return; } #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { RL_UNLOCK(sc); return; } #endif if (status & (RL_ISR_RX_OK|RL_ISR_RX_ERR|RL_ISR_FIFO_OFLOW)) rval = re_rxeof(sc); #ifdef RE_TX_MODERATION if (status & (RL_ISR_TIMEOUT_EXPIRED| #else if (status & (RL_ISR_TX_OK| #endif RL_ISR_TX_ERR|RL_ISR_TX_DESC_UNAVAIL)) re_txeof(sc); if (status & RL_ISR_SYSTEM_ERR) { re_reset(sc); re_init_locked(sc); } if (status & RL_ISR_LINKCHG) { callout_stop(&sc->rl_stat_callout); re_tick(sc); } if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) taskqueue_enqueue_fast(taskqueue_fast, &sc->rl_txtask); RL_UNLOCK(sc); if ((CSR_READ_2(sc, RL_ISR) & RL_INTRS_CPLUS) || rval) { taskqueue_enqueue_fast(taskqueue_fast, &sc->rl_inttask); return; } CSR_WRITE_2(sc, RL_IMR, RL_INTRS_CPLUS); return; } static int re_encap(sc, m_head, idx) struct rl_softc *sc; struct mbuf **m_head; int *idx; { struct mbuf *m_new = NULL; struct rl_dmaload_arg arg; bus_dmamap_t map; int error; - struct m_tag *mtag; RL_LOCK_ASSERT(sc); if (sc->rl_ldata.rl_tx_free <= 4) return (EFBIG); /* * Set up checksum offload. Note: checksum offload bits must * appear in all descriptors of a multi-descriptor transmit * attempt. This is according to testing done with an 8169 * chip. This is a requirement. */ arg.rl_flags = 0; if ((*m_head)->m_pkthdr.csum_flags & CSUM_IP) arg.rl_flags |= RL_TDESC_CMD_IPCSUM; if ((*m_head)->m_pkthdr.csum_flags & CSUM_TCP) arg.rl_flags |= RL_TDESC_CMD_TCPCSUM; if ((*m_head)->m_pkthdr.csum_flags & CSUM_UDP) arg.rl_flags |= RL_TDESC_CMD_UDPCSUM; arg.sc = sc; arg.rl_idx = *idx; arg.rl_maxsegs = sc->rl_ldata.rl_tx_free; if (arg.rl_maxsegs > 4) arg.rl_maxsegs -= 4; arg.rl_ring = sc->rl_ldata.rl_tx_list; map = sc->rl_ldata.rl_tx_dmamap[*idx]; /* * With some of the RealTek chips, using the checksum offload * support in conjunction with the autopadding feature results * in the transmission of corrupt frames. For example, if we * need to send a really small IP fragment that's less than 60 * bytes in size, and IP header checksumming is enabled, the * resulting ethernet frame that appears on the wire will * have garbled payload. To work around this, if TX checksum * offload is enabled, we always manually pad short frames out * to the minimum ethernet frame size. We do this by pretending * the mbuf chain has too many fragments so the coalescing code * below can assemble the packet into a single buffer that's * padded out to the mininum frame size. */ if (arg.rl_flags && (*m_head)->m_pkthdr.len < RL_MIN_FRAMELEN) error = EFBIG; else error = bus_dmamap_load_mbuf(sc->rl_ldata.rl_mtag, map, *m_head, re_dma_map_desc, &arg, BUS_DMA_NOWAIT); if (error && error != EFBIG) { device_printf(sc->rl_dev, "can't map mbuf (error %d)\n", error); return (ENOBUFS); } /* Too many segments to map, coalesce into a single mbuf */ if (error || arg.rl_maxsegs == 0) { m_new = m_defrag(*m_head, M_DONTWAIT); if (m_new == NULL) return (ENOBUFS); else *m_head = m_new; /* * Manually pad short frames, and zero the pad space * to avoid leaking data. */ if (m_new->m_pkthdr.len < RL_MIN_FRAMELEN) { bzero(mtod(m_new, char *) + m_new->m_pkthdr.len, RL_MIN_FRAMELEN - m_new->m_pkthdr.len); m_new->m_pkthdr.len += RL_MIN_FRAMELEN - m_new->m_pkthdr.len; m_new->m_len = m_new->m_pkthdr.len; } arg.sc = sc; arg.rl_idx = *idx; arg.rl_maxsegs = sc->rl_ldata.rl_tx_free; arg.rl_ring = sc->rl_ldata.rl_tx_list; error = bus_dmamap_load_mbuf(sc->rl_ldata.rl_mtag, map, *m_head, re_dma_map_desc, &arg, BUS_DMA_NOWAIT); if (error) { device_printf(sc->rl_dev, "can't map mbuf (error %d)\n", error); return (EFBIG); } } /* * Insure that the map for this transmission * is placed at the array index of the last descriptor * in this chain. (Swap last and first dmamaps.) */ sc->rl_ldata.rl_tx_dmamap[*idx] = sc->rl_ldata.rl_tx_dmamap[arg.rl_idx]; sc->rl_ldata.rl_tx_dmamap[arg.rl_idx] = map; sc->rl_ldata.rl_tx_mbuf[arg.rl_idx] = *m_head; sc->rl_ldata.rl_tx_free -= arg.rl_maxsegs; /* * Set up hardware VLAN tagging. Note: vlan tag info must * appear in the first descriptor of a multi-descriptor * transmission attempt. */ - - mtag = VLAN_OUTPUT_TAG(sc->rl_ifp, *m_head); - if (mtag != NULL) + if ((*m_head)->m_flags & M_VLANTAG) sc->rl_ldata.rl_tx_list[*idx].rl_vlanctl = - htole32(htons(VLAN_TAG_VALUE(mtag)) | RL_TDESC_VLANCTL_TAG); + htole32(htons((*m_head)->m_pkthdr.ether_vtag) | + RL_TDESC_VLANCTL_TAG); /* Transfer ownership of packet to the chip. */ sc->rl_ldata.rl_tx_list[arg.rl_idx].rl_cmdstat |= htole32(RL_TDESC_CMD_OWN); if (*idx != arg.rl_idx) sc->rl_ldata.rl_tx_list[*idx].rl_cmdstat |= htole32(RL_TDESC_CMD_OWN); RL_DESC_INC(arg.rl_idx); *idx = arg.rl_idx; return (0); } static void re_tx_task(arg, npending) void *arg; int npending; { struct ifnet *ifp; ifp = arg; re_start(ifp); return; } /* * Main transmit routine for C+ and gigE NICs. */ static void re_start(ifp) struct ifnet *ifp; { struct rl_softc *sc; struct mbuf *m_head = NULL; int idx, queued = 0; sc = ifp->if_softc; RL_LOCK(sc); if (!sc->rl_link || ifp->if_drv_flags & IFF_DRV_OACTIVE) { RL_UNLOCK(sc); return; } idx = sc->rl_ldata.rl_tx_prodidx; while (sc->rl_ldata.rl_tx_mbuf[idx] == NULL) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; if (re_encap(sc, &m_head, &idx)) { IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } /* * If there's a BPF listener, bounce a copy of this frame * to him. */ BPF_MTAP(ifp, m_head); queued++; } if (queued == 0) { #ifdef RE_TX_MODERATION if (sc->rl_ldata.rl_tx_free != RL_TX_DESC_CNT) CSR_WRITE_4(sc, RL_TIMERCNT, 1); #endif RL_UNLOCK(sc); return; } /* Flush the TX descriptors */ bus_dmamap_sync(sc->rl_ldata.rl_tx_list_tag, sc->rl_ldata.rl_tx_list_map, BUS_DMASYNC_PREWRITE|BUS_DMASYNC_PREREAD); sc->rl_ldata.rl_tx_prodidx = idx; /* * RealTek put the TX poll request register in a different * location on the 8169 gigE chip. I don't know why. */ CSR_WRITE_1(sc, sc->rl_txstart, RL_TXSTART_START); #ifdef RE_TX_MODERATION /* * Use the countdown timer for interrupt moderation. * 'TX done' interrupts are disabled. Instead, we reset the * countdown timer, which will begin counting until it hits * the value in the TIMERINT register, and then trigger an * interrupt. Each time we write to the TIMERCNT register, * the timer count is reset to 0. */ CSR_WRITE_4(sc, RL_TIMERCNT, 1); #endif /* * Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; RL_UNLOCK(sc); return; } static void re_init(xsc) void *xsc; { struct rl_softc *sc = xsc; RL_LOCK(sc); re_init_locked(sc); RL_UNLOCK(sc); } static void re_init_locked(sc) struct rl_softc *sc; { struct ifnet *ifp = sc->rl_ifp; struct mii_data *mii; u_int32_t rxcfg = 0; union { uint32_t align_dummy; u_char eaddr[ETHER_ADDR_LEN]; } eaddr; RL_LOCK_ASSERT(sc); mii = device_get_softc(sc->rl_miibus); /* * Cancel pending I/O and free all RX/TX buffers. */ re_stop(sc); /* * Enable C+ RX and TX mode, as well as VLAN stripping and * RX checksum offload. We must configure the C+ register * before all others. */ CSR_WRITE_2(sc, RL_CPLUS_CMD, RL_CPLUSCMD_RXENB| RL_CPLUSCMD_TXENB|RL_CPLUSCMD_PCI_MRW| RL_CPLUSCMD_VLANSTRIP|RL_CPLUSCMD_RXCSUM_ENB); /* * Init our MAC address. Even though the chipset * documentation doesn't mention it, we need to enter "Config * register write enable" mode to modify the ID registers. */ /* Copy MAC address on stack to align. */ bcopy(IF_LLADDR(ifp), eaddr.eaddr, ETHER_ADDR_LEN); CSR_WRITE_1(sc, RL_EECMD, RL_EEMODE_WRITECFG); CSR_WRITE_4(sc, RL_IDR0, htole32(*(u_int32_t *)(&eaddr.eaddr[0]))); CSR_WRITE_4(sc, RL_IDR4, htole32(*(u_int32_t *)(&eaddr.eaddr[4]))); CSR_WRITE_1(sc, RL_EECMD, RL_EEMODE_OFF); /* * For C+ mode, initialize the RX descriptors and mbufs. */ re_rx_list_init(sc); re_tx_list_init(sc); /* * Enable transmit and receive. */ CSR_WRITE_1(sc, RL_COMMAND, RL_CMD_TX_ENB|RL_CMD_RX_ENB); /* * Set the initial TX and RX configuration. */ if (sc->rl_testmode) { if (sc->rl_type == RL_8169) CSR_WRITE_4(sc, RL_TXCFG, RL_TXCFG_CONFIG|RL_LOOPTEST_ON); else CSR_WRITE_4(sc, RL_TXCFG, RL_TXCFG_CONFIG|RL_LOOPTEST_ON_CPLUS); } else CSR_WRITE_4(sc, RL_TXCFG, RL_TXCFG_CONFIG); CSR_WRITE_4(sc, RL_RXCFG, RL_RXCFG_CONFIG); /* Set the individual bit to receive frames for this host only. */ rxcfg = CSR_READ_4(sc, RL_RXCFG); rxcfg |= RL_RXCFG_RX_INDIV; /* If we want promiscuous mode, set the allframes bit. */ if (ifp->if_flags & IFF_PROMISC) rxcfg |= RL_RXCFG_RX_ALLPHYS; else rxcfg &= ~RL_RXCFG_RX_ALLPHYS; CSR_WRITE_4(sc, RL_RXCFG, rxcfg); /* * Set capture broadcast bit to capture broadcast frames. */ if (ifp->if_flags & IFF_BROADCAST) rxcfg |= RL_RXCFG_RX_BROAD; else rxcfg &= ~RL_RXCFG_RX_BROAD; CSR_WRITE_4(sc, RL_RXCFG, rxcfg); /* * Program the multicast filter, if necessary. */ re_setmulti(sc); #ifdef DEVICE_POLLING /* * Disable interrupts if we are polling. */ if (ifp->if_capenable & IFCAP_POLLING) CSR_WRITE_2(sc, RL_IMR, 0); else /* otherwise ... */ #endif /* * Enable interrupts. */ if (sc->rl_testmode) CSR_WRITE_2(sc, RL_IMR, 0); else CSR_WRITE_2(sc, RL_IMR, RL_INTRS_CPLUS); CSR_WRITE_2(sc, RL_ISR, RL_INTRS_CPLUS); /* Set initial TX threshold */ sc->rl_txthresh = RL_TX_THRESH_INIT; /* Start RX/TX process. */ CSR_WRITE_4(sc, RL_MISSEDPKT, 0); #ifdef notdef /* Enable receiver and transmitter. */ CSR_WRITE_1(sc, RL_COMMAND, RL_CMD_TX_ENB|RL_CMD_RX_ENB); #endif /* * Load the addresses of the RX and TX lists into the chip. */ CSR_WRITE_4(sc, RL_RXLIST_ADDR_HI, RL_ADDR_HI(sc->rl_ldata.rl_rx_list_addr)); CSR_WRITE_4(sc, RL_RXLIST_ADDR_LO, RL_ADDR_LO(sc->rl_ldata.rl_rx_list_addr)); CSR_WRITE_4(sc, RL_TXLIST_ADDR_HI, RL_ADDR_HI(sc->rl_ldata.rl_tx_list_addr)); CSR_WRITE_4(sc, RL_TXLIST_ADDR_LO, RL_ADDR_LO(sc->rl_ldata.rl_tx_list_addr)); CSR_WRITE_1(sc, RL_EARLY_TX_THRESH, 16); #ifdef RE_TX_MODERATION /* * Initialize the timer interrupt register so that * a timer interrupt will be generated once the timer * reaches a certain number of ticks. The timer is * reloaded on each transmit. This gives us TX interrupt * moderation, which dramatically improves TX frame rate. */ if (sc->rl_type == RL_8169) CSR_WRITE_4(sc, RL_TIMERINT_8169, 0x800); else CSR_WRITE_4(sc, RL_TIMERINT, 0x400); #endif /* * For 8169 gigE NICs, set the max allowed RX packet * size so we can receive jumbo frames. */ if (sc->rl_type == RL_8169) CSR_WRITE_2(sc, RL_MAXRXPKTLEN, 16383); if (sc->rl_testmode) return; mii_mediachg(mii); CSR_WRITE_1(sc, RL_CFG1, RL_CFG1_DRVLOAD|RL_CFG1_FULLDUPLEX); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->rl_link = 0; callout_reset(&sc->rl_stat_callout, hz, re_tick, sc); } /* * Set media options. */ static int re_ifmedia_upd(ifp) struct ifnet *ifp; { struct rl_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = device_get_softc(sc->rl_miibus); RL_LOCK(sc); mii_mediachg(mii); RL_UNLOCK(sc); return (0); } /* * Report current media status. */ static void re_ifmedia_sts(ifp, ifmr) struct ifnet *ifp; struct ifmediareq *ifmr; { struct rl_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = device_get_softc(sc->rl_miibus); RL_LOCK(sc); mii_pollstat(mii); RL_UNLOCK(sc); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int re_ioctl(ifp, command, data) struct ifnet *ifp; u_long command; caddr_t data; { struct rl_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; struct mii_data *mii; int error = 0; switch (command) { case SIOCSIFMTU: RL_LOCK(sc); if (ifr->ifr_mtu > RL_JUMBO_MTU) error = EINVAL; ifp->if_mtu = ifr->ifr_mtu; RL_UNLOCK(sc); break; case SIOCSIFFLAGS: RL_LOCK(sc); if (ifp->if_flags & IFF_UP) re_init_locked(sc); else if (ifp->if_drv_flags & IFF_DRV_RUNNING) re_stop(sc); RL_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: RL_LOCK(sc); re_setmulti(sc); RL_UNLOCK(sc); break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: mii = device_get_softc(sc->rl_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); break; case SIOCSIFCAP: { int mask, reinit; mask = ifr->ifr_reqcap ^ ifp->if_capenable; reinit = 0; #ifdef DEVICE_POLLING if (mask & IFCAP_POLLING) { if (ifr->ifr_reqcap & IFCAP_POLLING) { error = ether_poll_register(re_poll, ifp); if (error) return(error); RL_LOCK(sc); /* Disable interrupts */ CSR_WRITE_2(sc, RL_IMR, 0x0000); ifp->if_capenable |= IFCAP_POLLING; RL_UNLOCK(sc); } else { error = ether_poll_deregister(ifp); /* Enable interrupts. */ RL_LOCK(sc); CSR_WRITE_2(sc, RL_IMR, RL_INTRS_CPLUS); ifp->if_capenable &= ~IFCAP_POLLING; RL_UNLOCK(sc); } } #endif /* DEVICE_POLLING */ if (mask & IFCAP_HWCSUM) { ifp->if_capenable ^= IFCAP_HWCSUM; if (ifp->if_capenable & IFCAP_TXCSUM) ifp->if_hwassist = RE_CSUM_FEATURES; else ifp->if_hwassist = 0; reinit = 1; } if (mask & IFCAP_VLAN_HWTAGGING) { ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING; reinit = 1; } if (reinit && ifp->if_drv_flags & IFF_DRV_RUNNING) re_init(sc); } break; default: error = ether_ioctl(ifp, command, data); break; } return (error); } static void re_watchdog(ifp) struct ifnet *ifp; { struct rl_softc *sc; sc = ifp->if_softc; RL_LOCK(sc); if_printf(ifp, "watchdog timeout\n"); ifp->if_oerrors++; re_txeof(sc); re_rxeof(sc); re_init_locked(sc); RL_UNLOCK(sc); } /* * Stop the adapter and free any mbufs allocated to the * RX and TX lists. */ static void re_stop(sc) struct rl_softc *sc; { register int i; struct ifnet *ifp; RL_LOCK_ASSERT(sc); ifp = sc->rl_ifp; ifp->if_timer = 0; callout_stop(&sc->rl_stat_callout); ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); CSR_WRITE_1(sc, RL_COMMAND, 0x00); CSR_WRITE_2(sc, RL_IMR, 0x0000); CSR_WRITE_2(sc, RL_ISR, 0xFFFF); if (sc->rl_head != NULL) { m_freem(sc->rl_head); sc->rl_head = sc->rl_tail = NULL; } /* Free the TX list buffers. */ for (i = 0; i < RL_TX_DESC_CNT; i++) { if (sc->rl_ldata.rl_tx_mbuf[i] != NULL) { bus_dmamap_unload(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_tx_dmamap[i]); m_freem(sc->rl_ldata.rl_tx_mbuf[i]); sc->rl_ldata.rl_tx_mbuf[i] = NULL; } } /* Free the RX list buffers. */ for (i = 0; i < RL_RX_DESC_CNT; i++) { if (sc->rl_ldata.rl_rx_mbuf[i] != NULL) { bus_dmamap_unload(sc->rl_ldata.rl_mtag, sc->rl_ldata.rl_rx_dmamap[i]); m_freem(sc->rl_ldata.rl_rx_mbuf[i]); sc->rl_ldata.rl_rx_mbuf[i] = NULL; } } } /* * Device suspend routine. Stop the interface and save some PCI * settings in case the BIOS doesn't restore them properly on * resume. */ static int re_suspend(dev) device_t dev; { struct rl_softc *sc; sc = device_get_softc(dev); RL_LOCK(sc); re_stop(sc); sc->suspended = 1; RL_UNLOCK(sc); return (0); } /* * Device resume routine. Restore some PCI settings in case the BIOS * doesn't, re-enable busmastering, and restart the interface if * appropriate. */ static int re_resume(dev) device_t dev; { struct rl_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); RL_LOCK(sc); ifp = sc->rl_ifp; /* reinitialize interface if necessary */ if (ifp->if_flags & IFF_UP) re_init_locked(sc); sc->suspended = 0; RL_UNLOCK(sc); return (0); } /* * Stop all chip I/O so that the kernel's probe routines don't * get confused by errant DMAs when rebooting. */ static void re_shutdown(dev) device_t dev; { struct rl_softc *sc; sc = device_get_softc(dev); RL_LOCK(sc); re_stop(sc); /* * Mark interface as down since otherwise we will panic if * interrupt comes in later on, which can happen in some * cases. */ sc->rl_ifp->if_flags &= ~IFF_UP; RL_UNLOCK(sc); } Index: head/sys/dev/stge/if_stge.c =================================================================== --- head/sys/dev/stge/if_stge.c (revision 162374) +++ head/sys/dev/stge/if_stge.c (revision 162375) @@ -1,2698 +1,2698 @@ /* $NetBSD: if_stge.c,v 1.32 2005/12/11 12:22:49 christos Exp $ */ /*- * Copyright (c) 2001 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jason R. Thorpe. * * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * Device driver for the Sundance Tech. TC9021 10/100/1000 * Ethernet controller. */ #include __FBSDID("$FreeBSD$"); #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_device_polling.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define STGE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) MODULE_DEPEND(stge, pci, 1, 1, 1); MODULE_DEPEND(stge, ether, 1, 1, 1); MODULE_DEPEND(stge, miibus, 1, 1, 1); /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" /* * Devices supported by this driver. */ static struct stge_product { uint16_t stge_vendorid; uint16_t stge_deviceid; const char *stge_name; } stge_products[] = { { VENDOR_SUNDANCETI, DEVICEID_SUNDANCETI_ST1023, "Sundance ST-1023 Gigabit Ethernet" }, { VENDOR_SUNDANCETI, DEVICEID_SUNDANCETI_ST2021, "Sundance ST-2021 Gigabit Ethernet" }, { VENDOR_TAMARACK, DEVICEID_TAMARACK_TC9021, "Tamarack TC9021 Gigabit Ethernet" }, { VENDOR_TAMARACK, DEVICEID_TAMARACK_TC9021_ALT, "Tamarack TC9021 Gigabit Ethernet" }, /* * The Sundance sample boards use the Sundance vendor ID, * but the Tamarack product ID. */ { VENDOR_SUNDANCETI, DEVICEID_TAMARACK_TC9021, "Sundance TC9021 Gigabit Ethernet" }, { VENDOR_SUNDANCETI, DEVICEID_TAMARACK_TC9021_ALT, "Sundance TC9021 Gigabit Ethernet" }, { VENDOR_DLINK, DEVICEID_DLINK_DL4000, "D-Link DL-4000 Gigabit Ethernet" }, { VENDOR_ANTARES, DEVICEID_ANTARES_TC9021, "Antares Gigabit Ethernet" } }; static int stge_probe(device_t); static int stge_attach(device_t); static int stge_detach(device_t); static void stge_shutdown(device_t); static int stge_suspend(device_t); static int stge_resume(device_t); static int stge_encap(struct stge_softc *, struct mbuf **); static void stge_start(struct ifnet *); static void stge_start_locked(struct ifnet *); static void stge_watchdog(struct ifnet *); static int stge_ioctl(struct ifnet *, u_long, caddr_t); static void stge_init(void *); static void stge_init_locked(struct stge_softc *); static void stge_vlan_setup(struct stge_softc *); static void stge_stop(struct stge_softc *); static void stge_start_tx(struct stge_softc *); static void stge_start_rx(struct stge_softc *); static void stge_stop_tx(struct stge_softc *); static void stge_stop_rx(struct stge_softc *); static void stge_reset(struct stge_softc *, uint32_t); static int stge_eeprom_wait(struct stge_softc *); static void stge_read_eeprom(struct stge_softc *, int, uint16_t *); static void stge_tick(void *); static void stge_stats_update(struct stge_softc *); static void stge_set_filter(struct stge_softc *); static void stge_set_multi(struct stge_softc *); static void stge_link_task(void *, int); static void stge_intr(void *); static __inline int stge_tx_error(struct stge_softc *); static void stge_txeof(struct stge_softc *); static void stge_rxeof(struct stge_softc *); static __inline void stge_discard_rxbuf(struct stge_softc *, int); static int stge_newbuf(struct stge_softc *, int); #ifndef __NO_STRICT_ALIGNMENT static __inline struct mbuf *stge_fixup_rx(struct stge_softc *, struct mbuf *); #endif static void stge_mii_sync(struct stge_softc *); static void stge_mii_send(struct stge_softc *, uint32_t, int); static int stge_mii_readreg(struct stge_softc *, struct stge_mii_frame *); static int stge_mii_writereg(struct stge_softc *, struct stge_mii_frame *); static int stge_miibus_readreg(device_t, int, int); static int stge_miibus_writereg(device_t, int, int, int); static void stge_miibus_statchg(device_t); static int stge_mediachange(struct ifnet *); static void stge_mediastatus(struct ifnet *, struct ifmediareq *); static void stge_dmamap_cb(void *, bus_dma_segment_t *, int, int); static int stge_dma_alloc(struct stge_softc *); static void stge_dma_free(struct stge_softc *); static void stge_dma_wait(struct stge_softc *); static void stge_init_tx_ring(struct stge_softc *); static int stge_init_rx_ring(struct stge_softc *); #ifdef DEVICE_POLLING static void stge_poll(struct ifnet *, enum poll_cmd, int); #endif static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int); static int sysctl_hw_stge_rxint_nframe(SYSCTL_HANDLER_ARGS); static int sysctl_hw_stge_rxint_dmawait(SYSCTL_HANDLER_ARGS); static device_method_t stge_methods[] = { /* Device interface */ DEVMETHOD(device_probe, stge_probe), DEVMETHOD(device_attach, stge_attach), DEVMETHOD(device_detach, stge_detach), DEVMETHOD(device_shutdown, stge_shutdown), DEVMETHOD(device_suspend, stge_suspend), DEVMETHOD(device_resume, stge_resume), /* MII interface */ DEVMETHOD(miibus_readreg, stge_miibus_readreg), DEVMETHOD(miibus_writereg, stge_miibus_writereg), DEVMETHOD(miibus_statchg, stge_miibus_statchg), { 0, 0 } }; static driver_t stge_driver = { "stge", stge_methods, sizeof(struct stge_softc) }; static devclass_t stge_devclass; DRIVER_MODULE(stge, pci, stge_driver, stge_devclass, 0, 0); DRIVER_MODULE(miibus, stge, miibus_driver, miibus_devclass, 0, 0); static struct resource_spec stge_res_spec_io[] = { { SYS_RES_IOPORT, PCIR_BAR(0), RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; static struct resource_spec stge_res_spec_mem[] = { { SYS_RES_MEMORY, PCIR_BAR(1), RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; #define MII_SET(x) \ CSR_WRITE_1(sc, STGE_PhyCtrl, CSR_READ_1(sc, STGE_PhyCtrl) | (x)) #define MII_CLR(x) \ CSR_WRITE_1(sc, STGE_PhyCtrl, CSR_READ_1(sc, STGE_PhyCtrl) & ~(x)) /* * Sync the PHYs by setting data bit and strobing the clock 32 times. */ static void stge_mii_sync(struct stge_softc *sc) { int i; MII_SET(PC_MgmtDir | PC_MgmtData); for (i = 0; i < 32; i++) { MII_SET(PC_MgmtClk); DELAY(1); MII_CLR(PC_MgmtClk); DELAY(1); } } /* * Clock a series of bits through the MII. */ static void stge_mii_send(struct stge_softc *sc, uint32_t bits, int cnt) { int i; MII_CLR(PC_MgmtClk); for (i = (0x1 << (cnt - 1)); i; i >>= 1) { if (bits & i) MII_SET(PC_MgmtData); else MII_CLR(PC_MgmtData); DELAY(1); MII_CLR(PC_MgmtClk); DELAY(1); MII_SET(PC_MgmtClk); } } /* * Read an PHY register through the MII. */ static int stge_mii_readreg(struct stge_softc *sc, struct stge_mii_frame *frame) { int i, ack; /* * Set up frame for RX. */ frame->mii_stdelim = STGE_MII_STARTDELIM; frame->mii_opcode = STGE_MII_READOP; frame->mii_turnaround = 0; frame->mii_data = 0; CSR_WRITE_1(sc, STGE_PhyCtrl, 0 | sc->sc_PhyCtrl); /* * Turn on data xmit. */ MII_SET(PC_MgmtDir); stge_mii_sync(sc); /* * Send command/address info. */ stge_mii_send(sc, frame->mii_stdelim, 2); stge_mii_send(sc, frame->mii_opcode, 2); stge_mii_send(sc, frame->mii_phyaddr, 5); stge_mii_send(sc, frame->mii_regaddr, 5); /* Turn off xmit. */ MII_CLR(PC_MgmtDir); /* Idle bit */ MII_CLR((PC_MgmtClk | PC_MgmtData)); DELAY(1); MII_SET(PC_MgmtClk); DELAY(1); /* Check for ack */ MII_CLR(PC_MgmtClk); DELAY(1); ack = CSR_READ_1(sc, STGE_PhyCtrl) & PC_MgmtData; MII_SET(PC_MgmtClk); DELAY(1); /* * Now try reading data bits. If the ack failed, we still * need to clock through 16 cycles to keep the PHY(s) in sync. */ if (ack) { for(i = 0; i < 16; i++) { MII_CLR(PC_MgmtClk); DELAY(1); MII_SET(PC_MgmtClk); DELAY(1); } goto fail; } for (i = 0x8000; i; i >>= 1) { MII_CLR(PC_MgmtClk); DELAY(1); if (!ack) { if (CSR_READ_1(sc, STGE_PhyCtrl) & PC_MgmtData) frame->mii_data |= i; DELAY(1); } MII_SET(PC_MgmtClk); DELAY(1); } fail: MII_CLR(PC_MgmtClk); DELAY(1); MII_SET(PC_MgmtClk); DELAY(1); if (ack) return(1); return(0); } /* * Write to a PHY register through the MII. */ static int stge_mii_writereg(struct stge_softc *sc, struct stge_mii_frame *frame) { /* * Set up frame for TX. */ frame->mii_stdelim = STGE_MII_STARTDELIM; frame->mii_opcode = STGE_MII_WRITEOP; frame->mii_turnaround = STGE_MII_TURNAROUND; /* * Turn on data output. */ MII_SET(PC_MgmtDir); stge_mii_sync(sc); stge_mii_send(sc, frame->mii_stdelim, 2); stge_mii_send(sc, frame->mii_opcode, 2); stge_mii_send(sc, frame->mii_phyaddr, 5); stge_mii_send(sc, frame->mii_regaddr, 5); stge_mii_send(sc, frame->mii_turnaround, 2); stge_mii_send(sc, frame->mii_data, 16); /* Idle bit. */ MII_SET(PC_MgmtClk); DELAY(1); MII_CLR(PC_MgmtClk); DELAY(1); /* * Turn off xmit. */ MII_CLR(PC_MgmtDir); return(0); } /* * sc_miibus_readreg: [mii interface function] * * Read a PHY register on the MII of the TC9021. */ static int stge_miibus_readreg(device_t dev, int phy, int reg) { struct stge_softc *sc; struct stge_mii_frame frame; int error; sc = device_get_softc(dev); if (reg == STGE_PhyCtrl) { /* XXX allow ip1000phy read STGE_PhyCtrl register. */ STGE_MII_LOCK(sc); error = CSR_READ_1(sc, STGE_PhyCtrl); STGE_MII_UNLOCK(sc); return (error); } bzero(&frame, sizeof(frame)); frame.mii_phyaddr = phy; frame.mii_regaddr = reg; STGE_MII_LOCK(sc); error = stge_mii_readreg(sc, &frame); STGE_MII_UNLOCK(sc); if (error != 0) { /* Don't show errors for PHY probe request */ if (reg != 1) device_printf(sc->sc_dev, "phy read fail\n"); return (0); } return (frame.mii_data); } /* * stge_miibus_writereg: [mii interface function] * * Write a PHY register on the MII of the TC9021. */ static int stge_miibus_writereg(device_t dev, int phy, int reg, int val) { struct stge_softc *sc; struct stge_mii_frame frame; int error; sc = device_get_softc(dev); bzero(&frame, sizeof(frame)); frame.mii_phyaddr = phy; frame.mii_regaddr = reg; frame.mii_data = val; STGE_MII_LOCK(sc); error = stge_mii_writereg(sc, &frame); STGE_MII_UNLOCK(sc); if (error != 0) device_printf(sc->sc_dev, "phy write fail\n"); return (0); } /* * stge_miibus_statchg: [mii interface function] * * Callback from MII layer when media changes. */ static void stge_miibus_statchg(device_t dev) { struct stge_softc *sc; struct mii_data *mii; sc = device_get_softc(dev); mii = device_get_softc(sc->sc_miibus); STGE_MII_LOCK(sc); if (IFM_SUBTYPE(mii->mii_media_active) == IFM_NONE) { STGE_MII_UNLOCK(sc); return; } sc->sc_MACCtrl = 0; if (((mii->mii_media_active & IFM_GMASK) & IFM_FDX) != 0) sc->sc_MACCtrl |= MC_DuplexSelect; if (((mii->mii_media_active & IFM_GMASK) & IFM_FLAG0) != 0) sc->sc_MACCtrl |= MC_RxFlowControlEnable; if (((mii->mii_media_active & IFM_GMASK) & IFM_FLAG1) != 0) sc->sc_MACCtrl |= MC_TxFlowControlEnable; /* * We can't access STGE_MACCtrl register in this context due to * the races between MII layer and driver which accesses this * register to program MAC. In order to solve the race, we defer * STGE_MACCtrl programming until we know we are out of MII. */ taskqueue_enqueue(taskqueue_swi, &sc->sc_link_task); STGE_MII_UNLOCK(sc); } /* * stge_mediastatus: [ifmedia interface function] * * Get the current interface media status. */ static void stge_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr) { struct stge_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = device_get_softc(sc->sc_miibus); mii_pollstat(mii); ifmr->ifm_status = mii->mii_media_status; ifmr->ifm_active = mii->mii_media_active; } /* * stge_mediachange: [ifmedia interface function] * * Set hardware to newly-selected media. */ static int stge_mediachange(struct ifnet *ifp) { struct stge_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = device_get_softc(sc->sc_miibus); mii_mediachg(mii); return (0); } static int stge_eeprom_wait(struct stge_softc *sc) { int i; for (i = 0; i < STGE_TIMEOUT; i++) { DELAY(1000); if ((CSR_READ_2(sc, STGE_EepromCtrl) & EC_EepromBusy) == 0) return (0); } return (1); } /* * stge_read_eeprom: * * Read data from the serial EEPROM. */ static void stge_read_eeprom(struct stge_softc *sc, int offset, uint16_t *data) { if (stge_eeprom_wait(sc)) device_printf(sc->sc_dev, "EEPROM failed to come ready\n"); CSR_WRITE_2(sc, STGE_EepromCtrl, EC_EepromAddress(offset) | EC_EepromOpcode(EC_OP_RR)); if (stge_eeprom_wait(sc)) device_printf(sc->sc_dev, "EEPROM read timed out\n"); *data = CSR_READ_2(sc, STGE_EepromData); } static int stge_probe(device_t dev) { struct stge_product *sp; int i; uint16_t vendor, devid; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); sp = stge_products; for (i = 0; i < sizeof(stge_products)/sizeof(stge_products[0]); i++, sp++) { if (vendor == sp->stge_vendorid && devid == sp->stge_deviceid) { device_set_desc(dev, sp->stge_name); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int stge_attach(device_t dev) { struct stge_softc *sc; struct ifnet *ifp; uint8_t enaddr[ETHER_ADDR_LEN]; int error, i; uint16_t cmd; uint32_t val; error = 0; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); mtx_init(&sc->sc_mii_mtx, "stge_mii_mutex", NULL, MTX_DEF); callout_init_mtx(&sc->sc_tick_ch, &sc->sc_mtx, 0); TASK_INIT(&sc->sc_link_task, 0, stge_link_task, sc); /* * Map the device. */ pci_enable_busmaster(dev); cmd = pci_read_config(dev, PCIR_COMMAND, 2); val = pci_read_config(dev, PCIR_BAR(1), 4); if ((val & 0x01) != 0) sc->sc_spec = stge_res_spec_mem; else { val = pci_read_config(dev, PCIR_BAR(0), 4); if ((val & 0x01) == 0) { device_printf(sc->sc_dev, "couldn't locate IO BAR\n"); error = ENXIO; goto fail; } sc->sc_spec = stge_res_spec_io; } error = bus_alloc_resources(dev, sc->sc_spec, sc->sc_res); if (error != 0) { device_printf(dev, "couldn't allocate %s resources\n", sc->sc_spec == stge_res_spec_mem ? "memory" : "I/O"); goto fail; } sc->sc_rev = pci_get_revid(dev); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rxint_nframe", CTLTYPE_INT|CTLFLAG_RW, &sc->sc_rxint_nframe, 0, sysctl_hw_stge_rxint_nframe, "I", "stge rx interrupt nframe"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rxint_dmawait", CTLTYPE_INT|CTLFLAG_RW, &sc->sc_rxint_dmawait, 0, sysctl_hw_stge_rxint_dmawait, "I", "stge rx interrupt dmawait"); /* Pull in device tunables. */ sc->sc_rxint_nframe = STGE_RXINT_NFRAME_DEFAULT; error = resource_int_value(device_get_name(dev), device_get_unit(dev), "rxint_nframe", &sc->sc_rxint_nframe); if (error == 0) { if (sc->sc_rxint_nframe < STGE_RXINT_NFRAME_MIN || sc->sc_rxint_nframe > STGE_RXINT_NFRAME_MAX) { device_printf(dev, "rxint_nframe value out of range; " "using default: %d\n", STGE_RXINT_NFRAME_DEFAULT); sc->sc_rxint_nframe = STGE_RXINT_NFRAME_DEFAULT; } } sc->sc_rxint_dmawait = STGE_RXINT_DMAWAIT_DEFAULT; error = resource_int_value(device_get_name(dev), device_get_unit(dev), "rxint_dmawait", &sc->sc_rxint_dmawait); if (error == 0) { if (sc->sc_rxint_dmawait < STGE_RXINT_DMAWAIT_MIN || sc->sc_rxint_dmawait > STGE_RXINT_DMAWAIT_MAX) { device_printf(dev, "rxint_dmawait value out of range; " "using default: %d\n", STGE_RXINT_DMAWAIT_DEFAULT); sc->sc_rxint_dmawait = STGE_RXINT_DMAWAIT_DEFAULT; } } if ((error = stge_dma_alloc(sc) != 0)) goto fail; /* * Determine if we're copper or fiber. It affects how we * reset the card. */ if (CSR_READ_4(sc, STGE_AsicCtrl) & AC_PhyMedia) sc->sc_usefiber = 1; else sc->sc_usefiber = 0; /* Load LED configuration from EEPROM. */ stge_read_eeprom(sc, STGE_EEPROM_LEDMode, &sc->sc_led); /* * Reset the chip to a known state. */ STGE_LOCK(sc); stge_reset(sc, STGE_RESET_FULL); STGE_UNLOCK(sc); /* * Reading the station address from the EEPROM doesn't seem * to work, at least on my sample boards. Instead, since * the reset sequence does AutoInit, read it from the station * address registers. For Sundance 1023 you can only read it * from EEPROM. */ if (pci_get_device(dev) != DEVICEID_SUNDANCETI_ST1023) { uint16_t v; v = CSR_READ_2(sc, STGE_StationAddress0); enaddr[0] = v & 0xff; enaddr[1] = v >> 8; v = CSR_READ_2(sc, STGE_StationAddress1); enaddr[2] = v & 0xff; enaddr[3] = v >> 8; v = CSR_READ_2(sc, STGE_StationAddress2); enaddr[4] = v & 0xff; enaddr[5] = v >> 8; sc->sc_stge1023 = 0; } else { uint16_t myaddr[ETHER_ADDR_LEN / 2]; for (i = 0; i sc_stge1023 = 1; } ifp = sc->sc_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(sc->sc_dev, "failed to if_alloc()\n"); error = ENXIO; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = stge_ioctl; ifp->if_start = stge_start; ifp->if_watchdog = stge_watchdog; ifp->if_init = stge_init; ifp->if_mtu = ETHERMTU; ifp->if_snd.ifq_drv_maxlen = STGE_TX_RING_CNT - 1; IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); IFQ_SET_READY(&ifp->if_snd); /* Revision B3 and earlier chips have checksum bug. */ if (sc->sc_rev >= 0x0c) { ifp->if_hwassist = STGE_CSUM_FEATURES; ifp->if_capabilities = IFCAP_HWCSUM; } else { ifp->if_hwassist = 0; ifp->if_capabilities = 0; } ifp->if_capenable = ifp->if_capabilities; /* * Read some important bits from the PhyCtrl register. */ sc->sc_PhyCtrl = CSR_READ_1(sc, STGE_PhyCtrl) & (PC_PhyDuplexPolarity | PC_PhyLnkPolarity); /* Set up MII bus. */ if ((error = mii_phy_probe(sc->sc_dev, &sc->sc_miibus, stge_mediachange, stge_mediastatus)) != 0) { device_printf(sc->sc_dev, "no PHY found!\n"); goto fail; } ether_ifattach(ifp, enaddr); /* VLAN capability setup */ ifp->if_capabilities |= IFCAP_VLAN_MTU | IFCAP_VLAN_HWTAGGING; if (sc->sc_rev >= 0x0c) ifp->if_capabilities |= IFCAP_VLAN_HWCSUM; ifp->if_capenable = ifp->if_capabilities; #ifdef DEVICE_POLLING ifp->if_capabilities |= IFCAP_POLLING; #endif /* * Tell the upper layer(s) we support long frames. * Must appear after the call to ether_ifattach() because * ether_ifattach() sets ifi_hdrlen to the default value. */ ifp->if_data.ifi_hdrlen = sizeof(struct ether_vlan_header); /* * The manual recommends disabling early transmit, so we * do. It's disabled anyway, if using IP checksumming, * since the entire packet must be in the FIFO in order * for the chip to perform the checksum. */ sc->sc_txthresh = 0x0fff; /* * Disable MWI if the PCI layer tells us to. */ sc->sc_DMACtrl = 0; if ((cmd & PCIM_CMD_MWRICEN) == 0) sc->sc_DMACtrl |= DMAC_MWIDisable; /* * Hookup IRQ */ error = bus_setup_intr(dev, sc->sc_res[1], INTR_TYPE_NET | INTR_MPSAFE, stge_intr, sc, &sc->sc_ih); if (error != 0) { ether_ifdetach(ifp); device_printf(sc->sc_dev, "couldn't set up IRQ\n"); sc->sc_ifp = NULL; goto fail; } fail: if (error != 0) stge_detach(dev); return (error); } static int stge_detach(device_t dev) { struct stge_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); ifp = sc->sc_ifp; #ifdef DEVICE_POLLING if (ifp && ifp->if_capenable & IFCAP_POLLING) ether_poll_deregister(ifp); #endif if (device_is_attached(dev)) { STGE_LOCK(sc); /* XXX */ sc->sc_detach = 1; stge_stop(sc); STGE_UNLOCK(sc); callout_drain(&sc->sc_tick_ch); taskqueue_drain(taskqueue_swi, &sc->sc_link_task); ether_ifdetach(ifp); } if (sc->sc_miibus != NULL) { device_delete_child(dev, sc->sc_miibus); sc->sc_miibus = NULL; } bus_generic_detach(dev); stge_dma_free(sc); if (ifp != NULL) { if_free(ifp); sc->sc_ifp = NULL; } if (sc->sc_ih) { bus_teardown_intr(dev, sc->sc_res[1], sc->sc_ih); sc->sc_ih = NULL; } bus_release_resources(dev, sc->sc_spec, sc->sc_res); mtx_destroy(&sc->sc_mii_mtx); mtx_destroy(&sc->sc_mtx); return (0); } struct stge_dmamap_arg { bus_addr_t stge_busaddr; }; static void stge_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct stge_dmamap_arg *ctx; if (error != 0) return; ctx = (struct stge_dmamap_arg *)arg; ctx->stge_busaddr = segs[0].ds_addr; } static int stge_dma_alloc(struct stge_softc *sc) { struct stge_dmamap_arg ctx; struct stge_txdesc *txd; struct stge_rxdesc *rxd; int error, i; /* create parent tag. */ error = bus_dma_tag_create(NULL, /* parent */ 1, 0, /* algnmnt, boundary */ STGE_DMA_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_cdata.stge_parent_tag); if (error != 0) { device_printf(sc->sc_dev, "failed to create parent DMA tag\n"); goto fail; } /* create tag for Tx ring. */ error = bus_dma_tag_create(sc->sc_cdata.stge_parent_tag,/* parent */ STGE_RING_ALIGN, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ STGE_TX_RING_SZ, /* maxsize */ 1, /* nsegments */ STGE_TX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_cdata.stge_tx_ring_tag); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate Tx ring DMA tag\n"); goto fail; } /* create tag for Rx ring. */ error = bus_dma_tag_create(sc->sc_cdata.stge_parent_tag,/* parent */ STGE_RING_ALIGN, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ STGE_RX_RING_SZ, /* maxsize */ 1, /* nsegments */ STGE_RX_RING_SZ, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_cdata.stge_rx_ring_tag); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate Rx ring DMA tag\n"); goto fail; } /* create tag for Tx buffers. */ error = bus_dma_tag_create(sc->sc_cdata.stge_parent_tag,/* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES * STGE_MAXTXSEGS, /* maxsize */ STGE_MAXTXSEGS, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_cdata.stge_tx_tag); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate Tx DMA tag\n"); goto fail; } /* create tag for Rx buffers. */ error = bus_dma_tag_create(sc->sc_cdata.stge_parent_tag,/* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_cdata.stge_rx_tag); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate Rx DMA tag\n"); goto fail; } /* allocate DMA'able memory and load the DMA map for Tx ring. */ error = bus_dmamem_alloc(sc->sc_cdata.stge_tx_ring_tag, (void **)&sc->sc_rdata.stge_tx_ring, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &sc->sc_cdata.stge_tx_ring_map); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate DMA'able memory for Tx ring\n"); goto fail; } ctx.stge_busaddr = 0; error = bus_dmamap_load(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map, sc->sc_rdata.stge_tx_ring, STGE_TX_RING_SZ, stge_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0 || ctx.stge_busaddr == 0) { device_printf(sc->sc_dev, "failed to load DMA'able memory for Tx ring\n"); goto fail; } sc->sc_rdata.stge_tx_ring_paddr = ctx.stge_busaddr; /* allocate DMA'able memory and load the DMA map for Rx ring. */ error = bus_dmamem_alloc(sc->sc_cdata.stge_rx_ring_tag, (void **)&sc->sc_rdata.stge_rx_ring, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &sc->sc_cdata.stge_rx_ring_map); if (error != 0) { device_printf(sc->sc_dev, "failed to allocate DMA'able memory for Rx ring\n"); goto fail; } ctx.stge_busaddr = 0; error = bus_dmamap_load(sc->sc_cdata.stge_rx_ring_tag, sc->sc_cdata.stge_rx_ring_map, sc->sc_rdata.stge_rx_ring, STGE_RX_RING_SZ, stge_dmamap_cb, &ctx, BUS_DMA_NOWAIT); if (error != 0 || ctx.stge_busaddr == 0) { device_printf(sc->sc_dev, "failed to load DMA'able memory for Rx ring\n"); goto fail; } sc->sc_rdata.stge_rx_ring_paddr = ctx.stge_busaddr; /* create DMA maps for Tx buffers. */ for (i = 0; i < STGE_TX_RING_CNT; i++) { txd = &sc->sc_cdata.stge_txdesc[i]; txd->tx_m = NULL; txd->tx_dmamap = 0; error = bus_dmamap_create(sc->sc_cdata.stge_tx_tag, 0, &txd->tx_dmamap); if (error != 0) { device_printf(sc->sc_dev, "failed to create Tx dmamap\n"); goto fail; } } /* create DMA maps for Rx buffers. */ if ((error = bus_dmamap_create(sc->sc_cdata.stge_rx_tag, 0, &sc->sc_cdata.stge_rx_sparemap)) != 0) { device_printf(sc->sc_dev, "failed to create spare Rx dmamap\n"); goto fail; } for (i = 0; i < STGE_RX_RING_CNT; i++) { rxd = &sc->sc_cdata.stge_rxdesc[i]; rxd->rx_m = NULL; rxd->rx_dmamap = 0; error = bus_dmamap_create(sc->sc_cdata.stge_rx_tag, 0, &rxd->rx_dmamap); if (error != 0) { device_printf(sc->sc_dev, "failed to create Rx dmamap\n"); goto fail; } } fail: return (error); } static void stge_dma_free(struct stge_softc *sc) { struct stge_txdesc *txd; struct stge_rxdesc *rxd; int i; /* Tx ring */ if (sc->sc_cdata.stge_tx_ring_tag) { if (sc->sc_cdata.stge_tx_ring_map) bus_dmamap_unload(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map); if (sc->sc_cdata.stge_tx_ring_map && sc->sc_rdata.stge_tx_ring) bus_dmamem_free(sc->sc_cdata.stge_tx_ring_tag, sc->sc_rdata.stge_tx_ring, sc->sc_cdata.stge_tx_ring_map); sc->sc_rdata.stge_tx_ring = NULL; sc->sc_cdata.stge_tx_ring_map = 0; bus_dma_tag_destroy(sc->sc_cdata.stge_tx_ring_tag); sc->sc_cdata.stge_tx_ring_tag = NULL; } /* Rx ring */ if (sc->sc_cdata.stge_rx_ring_tag) { if (sc->sc_cdata.stge_rx_ring_map) bus_dmamap_unload(sc->sc_cdata.stge_rx_ring_tag, sc->sc_cdata.stge_rx_ring_map); if (sc->sc_cdata.stge_rx_ring_map && sc->sc_rdata.stge_rx_ring) bus_dmamem_free(sc->sc_cdata.stge_rx_ring_tag, sc->sc_rdata.stge_rx_ring, sc->sc_cdata.stge_rx_ring_map); sc->sc_rdata.stge_rx_ring = NULL; sc->sc_cdata.stge_rx_ring_map = 0; bus_dma_tag_destroy(sc->sc_cdata.stge_rx_ring_tag); sc->sc_cdata.stge_rx_ring_tag = NULL; } /* Tx buffers */ if (sc->sc_cdata.stge_tx_tag) { for (i = 0; i < STGE_TX_RING_CNT; i++) { txd = &sc->sc_cdata.stge_txdesc[i]; if (txd->tx_dmamap) { bus_dmamap_destroy(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap); txd->tx_dmamap = 0; } } bus_dma_tag_destroy(sc->sc_cdata.stge_tx_tag); sc->sc_cdata.stge_tx_tag = NULL; } /* Rx buffers */ if (sc->sc_cdata.stge_rx_tag) { for (i = 0; i < STGE_RX_RING_CNT; i++) { rxd = &sc->sc_cdata.stge_rxdesc[i]; if (rxd->rx_dmamap) { bus_dmamap_destroy(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap); rxd->rx_dmamap = 0; } } if (sc->sc_cdata.stge_rx_sparemap) { bus_dmamap_destroy(sc->sc_cdata.stge_rx_tag, sc->sc_cdata.stge_rx_sparemap); sc->sc_cdata.stge_rx_sparemap = 0; } bus_dma_tag_destroy(sc->sc_cdata.stge_rx_tag); sc->sc_cdata.stge_rx_tag = NULL; } if (sc->sc_cdata.stge_parent_tag) { bus_dma_tag_destroy(sc->sc_cdata.stge_parent_tag); sc->sc_cdata.stge_parent_tag = NULL; } } /* * stge_shutdown: * * Make sure the interface is stopped at reboot time. */ static void stge_shutdown(device_t dev) { struct stge_softc *sc; sc = device_get_softc(dev); STGE_LOCK(sc); stge_stop(sc); STGE_UNLOCK(sc); } static int stge_suspend(device_t dev) { struct stge_softc *sc; sc = device_get_softc(dev); STGE_LOCK(sc); stge_stop(sc); sc->sc_suspended = 1; STGE_UNLOCK(sc); return (0); } static int stge_resume(device_t dev) { struct stge_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); STGE_LOCK(sc); ifp = sc->sc_ifp; if (ifp->if_flags & IFF_UP) stge_init_locked(sc); sc->sc_suspended = 0; STGE_UNLOCK(sc); return (0); } static void stge_dma_wait(struct stge_softc *sc) { int i; for (i = 0; i < STGE_TIMEOUT; i++) { DELAY(2); if ((CSR_READ_4(sc, STGE_DMACtrl) & DMAC_TxDMAInProg) == 0) break; } if (i == STGE_TIMEOUT) device_printf(sc->sc_dev, "DMA wait timed out\n"); } static int stge_encap(struct stge_softc *sc, struct mbuf **m_head) { struct stge_txdesc *txd; struct stge_tfd *tfd; struct mbuf *m; - struct m_tag *mtag; bus_dma_segment_t txsegs[STGE_MAXTXSEGS]; int error, i, nsegs, si; uint64_t csum_flags, tfc; STGE_LOCK_ASSERT(sc); if ((txd = STAILQ_FIRST(&sc->sc_cdata.stge_txfreeq)) == NULL) return (ENOBUFS); error = bus_dmamap_load_mbuf_sg(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap, *m_head, txsegs, &nsegs, 0); if (error == EFBIG) { m = m_defrag(*m_head, M_DONTWAIT); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (ENOMEM); } *m_head = m; error = bus_dmamap_load_mbuf_sg(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap, *m_head, txsegs, &nsegs, 0); if (error != 0) { m_freem(*m_head); *m_head = NULL; return (error); } } else if (error != 0) return (error); if (nsegs == 0) { m_freem(*m_head); *m_head = NULL; return (EIO); } m = *m_head; csum_flags = 0; if ((m->m_pkthdr.csum_flags & STGE_CSUM_FEATURES) != 0) { if (m->m_pkthdr.csum_flags & CSUM_IP) csum_flags |= TFD_IPChecksumEnable; if (m->m_pkthdr.csum_flags & CSUM_TCP) csum_flags |= TFD_TCPChecksumEnable; else if (m->m_pkthdr.csum_flags & CSUM_UDP) csum_flags |= TFD_UDPChecksumEnable; } si = sc->sc_cdata.stge_tx_prod; tfd = &sc->sc_rdata.stge_tx_ring[si]; for (i = 0; i < nsegs; i++) tfd->tfd_frags[i].frag_word0 = htole64(FRAG_ADDR(txsegs[i].ds_addr) | FRAG_LEN(txsegs[i].ds_len)); sc->sc_cdata.stge_tx_cnt++; tfc = TFD_FrameId(si) | TFD_WordAlign(TFD_WordAlign_disable) | TFD_FragCount(nsegs) | csum_flags; if (sc->sc_cdata.stge_tx_cnt >= STGE_TX_HIWAT) tfc |= TFD_TxDMAIndicate; /* Update producer index. */ sc->sc_cdata.stge_tx_prod = (si + 1) % STGE_TX_RING_CNT; /* Check if we have a VLAN tag to insert. */ - mtag = VLAN_OUTPUT_TAG(sc->sc_ifp, m); - if (mtag != NULL) - tfc |= TFD_VLANTagInsert | TFD_VID(VLAN_TAG_VALUE(mtag)); + if (m->m_flags & M_VLANTAG) + tfc |= (TFD_VLANTagInsert | TFD_VID(m->m_pkthdr.ether_vtag)); tfd->tfd_control = htole64(tfc); /* Update Tx Queue. */ STAILQ_REMOVE_HEAD(&sc->sc_cdata.stge_txfreeq, tx_q); STAILQ_INSERT_TAIL(&sc->sc_cdata.stge_txbusyq, txd, tx_q); txd->tx_m = m; /* Sync descriptors. */ bus_dmamap_sync(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } /* * stge_start: [ifnet interface function] * * Start packet transmission on the interface. */ static void stge_start(struct ifnet *ifp) { struct stge_softc *sc; sc = ifp->if_softc; STGE_LOCK(sc); stge_start_locked(ifp); STGE_UNLOCK(sc); } static void stge_start_locked(struct ifnet *ifp) { struct stge_softc *sc; struct mbuf *m_head; int enq; sc = ifp->if_softc; STGE_LOCK_ASSERT(sc); if ((ifp->if_drv_flags & (IFF_DRV_RUNNING|IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) return; for (enq = 0; !IFQ_DRV_IS_EMPTY(&ifp->if_snd); ) { if (sc->sc_cdata.stge_tx_cnt >= STGE_TX_HIWAT) { ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * Pack the data into the transmit ring. If we * don't have room, set the OACTIVE flag and wait * for the NIC to drain the ring. */ if (stge_encap(sc, &m_head)) { if (m_head == NULL) break; IFQ_DRV_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } enq++; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ BPF_MTAP(ifp, m_head); } if (enq > 0) { /* Transmit */ CSR_WRITE_4(sc, STGE_DMACtrl, DMAC_TxDMAPollNow); /* Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; } } /* * stge_watchdog: [ifnet interface function] * * Watchdog timer handler. */ static void stge_watchdog(struct ifnet *ifp) { struct stge_softc *sc; sc = ifp->if_softc; STGE_LOCK(sc); if_printf(sc->sc_ifp, "device timeout\n"); ifp->if_oerrors++; stge_init_locked(sc); STGE_UNLOCK(sc); } /* * stge_ioctl: [ifnet interface function] * * Handle control requests from the operator. */ static int stge_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct stge_softc *sc; struct ifreq *ifr; struct mii_data *mii; int error, mask; sc = ifp->if_softc; ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFMTU: if (ifr->ifr_mtu < ETHERMIN || ifr->ifr_mtu > STGE_JUMBO_MTU) error = EINVAL; else if (ifp->if_mtu != ifr->ifr_mtu) { ifp->if_mtu = ifr->ifr_mtu; STGE_LOCK(sc); stge_init_locked(sc); STGE_UNLOCK(sc); } break; case SIOCSIFFLAGS: STGE_LOCK(sc); if ((ifp->if_flags & IFF_UP) != 0) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { if (((ifp->if_flags ^ sc->sc_if_flags) & IFF_PROMISC) != 0) stge_set_filter(sc); } else { if (sc->sc_detach == 0) stge_init_locked(sc); } } else { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) stge_stop(sc); } sc->sc_if_flags = ifp->if_flags; STGE_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: STGE_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) stge_set_multi(sc); STGE_UNLOCK(sc); break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: mii = device_get_softc(sc->sc_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: mask = ifr->ifr_reqcap ^ ifp->if_capenable; #ifdef DEVICE_POLLING if ((mask & IFCAP_POLLING) != 0) { if ((ifr->ifr_reqcap & IFCAP_POLLING) != 0) { error = ether_poll_register(stge_poll, ifp); if (error != 0) break; STGE_LOCK(sc); CSR_WRITE_2(sc, STGE_IntEnable, 0); ifp->if_capenable |= IFCAP_POLLING; STGE_UNLOCK(sc); } else { error = ether_poll_deregister(ifp); if (error != 0) break; STGE_LOCK(sc); CSR_WRITE_2(sc, STGE_IntEnable, sc->sc_IntEnable); ifp->if_capenable &= ~IFCAP_POLLING; STGE_UNLOCK(sc); } } #endif if ((mask & IFCAP_HWCSUM) != 0) { ifp->if_capenable ^= IFCAP_HWCSUM; if ((IFCAP_HWCSUM & ifp->if_capenable) != 0 && (IFCAP_HWCSUM & ifp->if_capabilities) != 0) ifp->if_hwassist = STGE_CSUM_FEATURES; else ifp->if_hwassist = 0; } if ((mask & IFCAP_VLAN_HWTAGGING) != 0) { ifp->if_capenable ^= IFCAP_VLAN_HWTAGGING; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { STGE_LOCK(sc); stge_vlan_setup(sc); STGE_UNLOCK(sc); } } VLAN_CAPABILITIES(ifp); break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static void stge_link_task(void *arg, int pending) { struct stge_softc *sc; uint32_t v, ac; int i; sc = (struct stge_softc *)arg; STGE_LOCK(sc); /* * Update STGE_MACCtrl register depending on link status. * (duplex, flow control etc) */ v = ac = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; v &= ~(MC_DuplexSelect|MC_RxFlowControlEnable|MC_TxFlowControlEnable); v |= sc->sc_MACCtrl; CSR_WRITE_4(sc, STGE_MACCtrl, v); if (((ac ^ sc->sc_MACCtrl) & MC_DuplexSelect) != 0) { /* Duplex setting changed, reset Tx/Rx functions. */ ac = CSR_READ_4(sc, STGE_AsicCtrl); ac |= AC_TxReset | AC_RxReset; CSR_WRITE_4(sc, STGE_AsicCtrl, ac); for (i = 0; i < STGE_TIMEOUT; i++) { DELAY(100); if ((CSR_READ_4(sc, STGE_AsicCtrl) & AC_ResetBusy) == 0) break; } if (i == STGE_TIMEOUT) device_printf(sc->sc_dev, "reset failed to complete\n"); } STGE_UNLOCK(sc); } static __inline int stge_tx_error(struct stge_softc *sc) { uint32_t txstat; int error; for (error = 0;;) { txstat = CSR_READ_4(sc, STGE_TxStatus); if ((txstat & TS_TxComplete) == 0) break; /* Tx underrun */ if ((txstat & TS_TxUnderrun) != 0) { /* * XXX * There should be a more better way to recover * from Tx underrun instead of a full reset. */ if (sc->sc_nerr++ < STGE_MAXERR) device_printf(sc->sc_dev, "Tx underrun, " "resetting...\n"); if (sc->sc_nerr == STGE_MAXERR) device_printf(sc->sc_dev, "too many errors; " "not reporting any more\n"); error = -1; break; } /* Maximum/Late collisions, Re-enable Tx MAC. */ if ((txstat & (TS_MaxCollisions|TS_LateCollision)) != 0) CSR_WRITE_4(sc, STGE_MACCtrl, (CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK) | MC_TxEnable); } return (error); } /* * stge_intr: * * Interrupt service routine. */ static void stge_intr(void *arg) { struct stge_softc *sc; struct ifnet *ifp; int reinit; uint16_t status; sc = (struct stge_softc *)arg; ifp = sc->sc_ifp; STGE_LOCK(sc); #ifdef DEVICE_POLLING if ((ifp->if_capenable & IFCAP_POLLING) != 0) goto done_locked; #endif status = CSR_READ_2(sc, STGE_IntStatus); if (sc->sc_suspended || (status & IS_InterruptStatus) == 0) goto done_locked; /* Disable interrupts. */ for (reinit = 0;;) { status = CSR_READ_2(sc, STGE_IntStatusAck); status &= sc->sc_IntEnable; if (status == 0) break; /* Host interface errors. */ if ((status & IS_HostError) != 0) { device_printf(sc->sc_dev, "Host interface error, resetting...\n"); reinit = 1; goto force_init; } /* Receive interrupts. */ if ((status & IS_RxDMAComplete) != 0) { stge_rxeof(sc); if ((status & IS_RFDListEnd) != 0) CSR_WRITE_4(sc, STGE_DMACtrl, DMAC_RxDMAPollNow); } /* Transmit interrupts. */ if ((status & (IS_TxDMAComplete | IS_TxComplete)) != 0) stge_txeof(sc); /* Transmission errors.*/ if ((status & IS_TxComplete) != 0) { if ((reinit = stge_tx_error(sc)) != 0) break; } } force_init: if (reinit != 0) stge_init_locked(sc); /* Re-enable interrupts. */ CSR_WRITE_2(sc, STGE_IntEnable, sc->sc_IntEnable); /* Try to get more packets going. */ if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) stge_start_locked(ifp); done_locked: STGE_UNLOCK(sc); } /* * stge_txeof: * * Helper; handle transmit interrupts. */ static void stge_txeof(struct stge_softc *sc) { struct ifnet *ifp; struct stge_txdesc *txd; uint64_t control; int cons; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; txd = STAILQ_FIRST(&sc->sc_cdata.stge_txbusyq); if (txd == NULL) return; bus_dmamap_sync(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map, BUS_DMASYNC_POSTREAD); /* * Go through our Tx list and free mbufs for those * frames which have been transmitted. */ for (cons = sc->sc_cdata.stge_tx_cons;; cons = (cons + 1) % STGE_TX_RING_CNT) { if (sc->sc_cdata.stge_tx_cnt <= 0) break; control = le64toh(sc->sc_rdata.stge_tx_ring[cons].tfd_control); if ((control & TFD_TFDDone) == 0) break; sc->sc_cdata.stge_tx_cnt--; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; bus_dmamap_sync(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap); /* Output counter is updated with statistics register */ m_freem(txd->tx_m); txd->tx_m = NULL; STAILQ_REMOVE_HEAD(&sc->sc_cdata.stge_txbusyq, tx_q); STAILQ_INSERT_TAIL(&sc->sc_cdata.stge_txfreeq, txd, tx_q); txd = STAILQ_FIRST(&sc->sc_cdata.stge_txbusyq); } sc->sc_cdata.stge_tx_cons = cons; if (sc->sc_cdata.stge_tx_cnt == 0) ifp->if_timer = 0; bus_dmamap_sync(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static __inline void stge_discard_rxbuf(struct stge_softc *sc, int idx) { struct stge_rfd *rfd; rfd = &sc->sc_rdata.stge_rx_ring[idx]; rfd->rfd_status = 0; } #ifndef __NO_STRICT_ALIGNMENT /* * It seems that TC9021's DMA engine has alignment restrictions in * DMA scatter operations. The first DMA segment has no address * alignment restrictins but the rest should be aligned on 4(?) bytes * boundary. Otherwise it would corrupt random memory. Since we don't * know which one is used for the first segment in advance we simply * don't align at all. * To avoid copying over an entire frame to align, we allocate a new * mbuf and copy ethernet header to the new mbuf. The new mbuf is * prepended into the existing mbuf chain. */ static __inline struct mbuf * stge_fixup_rx(struct stge_softc *sc, struct mbuf *m) { struct mbuf *n; n = NULL; if (m->m_len <= (MCLBYTES - ETHER_HDR_LEN)) { bcopy(m->m_data, m->m_data + ETHER_HDR_LEN, m->m_len); m->m_data += ETHER_HDR_LEN; n = m; } else { MGETHDR(n, M_DONTWAIT, MT_DATA); if (n != NULL) { bcopy(m->m_data, n->m_data, ETHER_HDR_LEN); m->m_data += ETHER_HDR_LEN; m->m_len -= ETHER_HDR_LEN; n->m_len = ETHER_HDR_LEN; M_MOVE_PKTHDR(n, m); n->m_next = m; } else m_freem(m); } return (n); } #endif /* * stge_rxeof: * * Helper; handle receive interrupts. */ static void stge_rxeof(struct stge_softc *sc) { struct ifnet *ifp; struct stge_rxdesc *rxd; struct mbuf *mp, *m; uint64_t status64; uint32_t status; int cons, prog; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; bus_dmamap_sync(sc->sc_cdata.stge_rx_ring_tag, sc->sc_cdata.stge_rx_ring_map, BUS_DMASYNC_POSTREAD); prog = 0; for (cons = sc->sc_cdata.stge_rx_cons; prog < STGE_RX_RING_CNT; prog++, cons = (cons + 1) % STGE_RX_RING_CNT) { status64 = le64toh(sc->sc_rdata.stge_rx_ring[cons].rfd_status); status = RFD_RxStatus(status64); if ((status & RFD_RFDDone) == 0) break; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { if (sc->sc_cdata.stge_rxcycles <= 0) break; sc->sc_cdata.stge_rxcycles--; } #endif prog++; rxd = &sc->sc_cdata.stge_rxdesc[cons]; mp = rxd->rx_m; /* * If the packet had an error, drop it. Note we count * the error later in the periodic stats update. */ if ((status & RFD_FrameEnd) != 0 && (status & (RFD_RxFIFOOverrun | RFD_RxRuntFrame | RFD_RxAlignmentError | RFD_RxFCSError | RFD_RxLengthError)) != 0) { stge_discard_rxbuf(sc, cons); if (sc->sc_cdata.stge_rxhead != NULL) { m_freem(sc->sc_cdata.stge_rxhead); STGE_RXCHAIN_RESET(sc); } continue; } /* * Add a new receive buffer to the ring. */ if (stge_newbuf(sc, cons) != 0) { ifp->if_iqdrops++; stge_discard_rxbuf(sc, cons); if (sc->sc_cdata.stge_rxhead != NULL) { m_freem(sc->sc_cdata.stge_rxhead); STGE_RXCHAIN_RESET(sc); } continue; } if ((status & RFD_FrameEnd) != 0) mp->m_len = RFD_RxDMAFrameLen(status) - sc->sc_cdata.stge_rxlen; sc->sc_cdata.stge_rxlen += mp->m_len; /* Chain mbufs. */ if (sc->sc_cdata.stge_rxhead == NULL) { sc->sc_cdata.stge_rxhead = mp; sc->sc_cdata.stge_rxtail = mp; } else { mp->m_flags &= ~M_PKTHDR; sc->sc_cdata.stge_rxtail->m_next = mp; sc->sc_cdata.stge_rxtail = mp; } if ((status & RFD_FrameEnd) != 0) { m = sc->sc_cdata.stge_rxhead; m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = sc->sc_cdata.stge_rxlen; if (m->m_pkthdr.len > sc->sc_if_framesize) { m_freem(m); STGE_RXCHAIN_RESET(sc); continue; } /* * Set the incoming checksum information for * the packet. */ if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) { if ((status & RFD_IPDetected) != 0) { m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; if ((status & RFD_IPError) == 0) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; } if (((status & RFD_TCPDetected) != 0 && (status & RFD_TCPError) == 0) || ((status & RFD_UDPDetected) != 0 && (status & RFD_UDPError) == 0)) { m->m_pkthdr.csum_flags |= (CSUM_DATA_VALID | CSUM_PSEUDO_HDR); m->m_pkthdr.csum_data = 0xffff; } } #ifndef __NO_STRICT_ALIGNMENT if (sc->sc_if_framesize > (MCLBYTES - ETHER_ALIGN)) { if ((m = stge_fixup_rx(sc, m)) == NULL) { STGE_RXCHAIN_RESET(sc); continue; } } #endif /* Check for VLAN tagged packets. */ if ((status & RFD_VLANDetected) != 0 && - (ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) - VLAN_INPUT_TAG(ifp, m, RFD_TCI(status64)); + (ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) { + m->m_pkthdr.ether_vtag = RFD_TCI(status64); + m->m_flags |= M_VLANTAG; + } STGE_UNLOCK(sc); /* Pass it on. */ (*ifp->if_input)(ifp, m); STGE_LOCK(sc); STGE_RXCHAIN_RESET(sc); } } if (prog > 0) { /* Update the consumer index. */ sc->sc_cdata.stge_rx_cons = cons; bus_dmamap_sync(sc->sc_cdata.stge_rx_ring_tag, sc->sc_cdata.stge_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } } #ifdef DEVICE_POLLING static void stge_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) { struct stge_softc *sc; uint16_t status; sc = ifp->if_softc; STGE_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { STGE_UNLOCK(sc); return; } sc->sc_cdata.stge_rxcycles = count; stge_rxeof(sc); stge_txeof(sc); if (cmd == POLL_AND_CHECK_STATUS) { status = CSR_READ_2(sc, STGE_IntStatus); status &= sc->sc_IntEnable; if (status != 0) { if ((status & IS_HostError) != 0) { device_printf(sc->sc_dev, "Host interface error, resetting...\n"); stge_init_locked(sc); } if ((status & IS_TxComplete) != 0) { if (stge_tx_error(sc) != 0) stge_init_locked(sc); } } } if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) stge_start_locked(ifp); STGE_UNLOCK(sc); } #endif /* DEVICE_POLLING */ /* * stge_tick: * * One second timer, used to tick the MII. */ static void stge_tick(void *arg) { struct stge_softc *sc; struct mii_data *mii; sc = (struct stge_softc *)arg; STGE_LOCK_ASSERT(sc); mii = device_get_softc(sc->sc_miibus); mii_tick(mii); /* Update statistics counters. */ stge_stats_update(sc); /* * Relcaim any pending Tx descriptors to release mbufs in a * timely manner as we don't generate Tx completion interrupts * for every frame. This limits the delay to a maximum of one * second. */ if (sc->sc_cdata.stge_tx_cnt != 0) stge_txeof(sc); callout_reset(&sc->sc_tick_ch, hz, stge_tick, sc); } /* * stge_stats_update: * * Read the TC9021 statistics counters. */ static void stge_stats_update(struct stge_softc *sc) { struct ifnet *ifp; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; CSR_READ_4(sc,STGE_OctetRcvOk); ifp->if_ipackets += CSR_READ_4(sc, STGE_FramesRcvdOk); ifp->if_ierrors += CSR_READ_2(sc, STGE_FramesLostRxErrors); CSR_READ_4(sc, STGE_OctetXmtdOk); ifp->if_opackets += CSR_READ_4(sc, STGE_FramesXmtdOk); ifp->if_collisions += CSR_READ_4(sc, STGE_LateCollisions) + CSR_READ_4(sc, STGE_MultiColFrames) + CSR_READ_4(sc, STGE_SingleColFrames); ifp->if_oerrors += CSR_READ_2(sc, STGE_FramesAbortXSColls) + CSR_READ_2(sc, STGE_FramesWEXDeferal); } /* * stge_reset: * * Perform a soft reset on the TC9021. */ static void stge_reset(struct stge_softc *sc, uint32_t how) { uint32_t ac; uint8_t v; int i, dv; STGE_LOCK_ASSERT(sc); dv = 5000; ac = CSR_READ_4(sc, STGE_AsicCtrl); switch (how) { case STGE_RESET_TX: ac |= AC_TxReset | AC_FIFO; dv = 100; break; case STGE_RESET_RX: ac |= AC_RxReset | AC_FIFO; dv = 100; break; case STGE_RESET_FULL: default: /* * Only assert RstOut if we're fiber. We need GMII clocks * to be present in order for the reset to complete on fiber * cards. */ ac |= AC_GlobalReset | AC_RxReset | AC_TxReset | AC_DMA | AC_FIFO | AC_Network | AC_Host | AC_AutoInit | (sc->sc_usefiber ? AC_RstOut : 0); break; } CSR_WRITE_4(sc, STGE_AsicCtrl, ac); /* Account for reset problem at 10Mbps. */ DELAY(dv); for (i = 0; i < STGE_TIMEOUT; i++) { if ((CSR_READ_4(sc, STGE_AsicCtrl) & AC_ResetBusy) == 0) break; DELAY(dv); } if (i == STGE_TIMEOUT) device_printf(sc->sc_dev, "reset failed to complete\n"); /* Set LED, from Linux IPG driver. */ ac = CSR_READ_4(sc, STGE_AsicCtrl); ac &= ~(AC_LEDMode | AC_LEDSpeed | AC_LEDModeBit1); if ((sc->sc_led & 0x01) != 0) ac |= AC_LEDMode; if ((sc->sc_led & 0x03) != 0) ac |= AC_LEDModeBit1; if ((sc->sc_led & 0x08) != 0) ac |= AC_LEDSpeed; CSR_WRITE_4(sc, STGE_AsicCtrl, ac); /* Set PHY, from Linux IPG driver */ v = CSR_READ_1(sc, STGE_PhySet); v &= ~(PS_MemLenb9b | PS_MemLen | PS_NonCompdet); v |= ((sc->sc_led & 0x70) >> 4); CSR_WRITE_1(sc, STGE_PhySet, v); } /* * stge_init: [ ifnet interface function ] * * Initialize the interface. */ static void stge_init(void *xsc) { struct stge_softc *sc; sc = (struct stge_softc *)xsc; STGE_LOCK(sc); stge_init_locked(sc); STGE_UNLOCK(sc); } static void stge_init_locked(struct stge_softc *sc) { struct ifnet *ifp; struct mii_data *mii; uint16_t eaddr[3]; uint32_t v; int error; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; mii = device_get_softc(sc->sc_miibus); /* * Cancel any pending I/O. */ stge_stop(sc); /* Init descriptors. */ error = stge_init_rx_ring(sc); if (error != 0) { device_printf(sc->sc_dev, "initialization failed: no memory for rx buffers\n"); stge_stop(sc); goto out; } stge_init_tx_ring(sc); /* Set the station address. */ bcopy(IF_LLADDR(ifp), eaddr, ETHER_ADDR_LEN); CSR_WRITE_2(sc, STGE_StationAddress0, htole16(eaddr[0])); CSR_WRITE_2(sc, STGE_StationAddress1, htole16(eaddr[1])); CSR_WRITE_2(sc, STGE_StationAddress2, htole16(eaddr[2])); /* * Set the statistics masks. Disable all the RMON stats, * and disable selected stats in the non-RMON stats registers. */ CSR_WRITE_4(sc, STGE_RMONStatisticsMask, 0xffffffff); CSR_WRITE_4(sc, STGE_StatisticsMask, (1U << 1) | (1U << 2) | (1U << 3) | (1U << 4) | (1U << 5) | (1U << 6) | (1U << 7) | (1U << 8) | (1U << 9) | (1U << 10) | (1U << 13) | (1U << 14) | (1U << 15) | (1U << 19) | (1U << 20) | (1U << 21)); /* Set up the receive filter. */ stge_set_filter(sc); /* Program multicast filter. */ stge_set_multi(sc); /* * Give the transmit and receive ring to the chip. */ CSR_WRITE_4(sc, STGE_TFDListPtrHi, STGE_ADDR_HI(STGE_TX_RING_ADDR(sc, 0))); CSR_WRITE_4(sc, STGE_TFDListPtrLo, STGE_ADDR_LO(STGE_TX_RING_ADDR(sc, 0))); CSR_WRITE_4(sc, STGE_RFDListPtrHi, STGE_ADDR_HI(STGE_RX_RING_ADDR(sc, 0))); CSR_WRITE_4(sc, STGE_RFDListPtrLo, STGE_ADDR_LO(STGE_RX_RING_ADDR(sc, 0))); /* * Initialize the Tx auto-poll period. It's OK to make this number * large (255 is the max, but we use 127) -- we explicitly kick the * transmit engine when there's actually a packet. */ CSR_WRITE_1(sc, STGE_TxDMAPollPeriod, 127); /* ..and the Rx auto-poll period. */ CSR_WRITE_1(sc, STGE_RxDMAPollPeriod, 1); /* Initialize the Tx start threshold. */ CSR_WRITE_2(sc, STGE_TxStartThresh, sc->sc_txthresh); /* Rx DMA thresholds, from Linux */ CSR_WRITE_1(sc, STGE_RxDMABurstThresh, 0x30); CSR_WRITE_1(sc, STGE_RxDMAUrgentThresh, 0x30); /* Rx early threhold, from Linux */ CSR_WRITE_2(sc, STGE_RxEarlyThresh, 0x7ff); /* Tx DMA thresholds, from Linux */ CSR_WRITE_1(sc, STGE_TxDMABurstThresh, 0x30); CSR_WRITE_1(sc, STGE_TxDMAUrgentThresh, 0x04); /* * Initialize the Rx DMA interrupt control register. We * request an interrupt after every incoming packet, but * defer it for sc_rxint_dmawait us. When the number of * interrupts pending reaches STGE_RXINT_NFRAME, we stop * deferring the interrupt, and signal it immediately. */ CSR_WRITE_4(sc, STGE_RxDMAIntCtrl, RDIC_RxFrameCount(sc->sc_rxint_nframe) | RDIC_RxDMAWaitTime(STGE_RXINT_USECS2TICK(sc->sc_rxint_dmawait))); /* * Initialize the interrupt mask. */ sc->sc_IntEnable = IS_HostError | IS_TxComplete | IS_TxDMAComplete | IS_RxDMAComplete | IS_RFDListEnd; #ifdef DEVICE_POLLING /* Disable interrupts if we are polling. */ if ((ifp->if_capenable & IFCAP_POLLING) != 0) CSR_WRITE_2(sc, STGE_IntEnable, 0); else #endif CSR_WRITE_2(sc, STGE_IntEnable, sc->sc_IntEnable); /* * Configure the DMA engine. * XXX Should auto-tune TxBurstLimit. */ CSR_WRITE_4(sc, STGE_DMACtrl, sc->sc_DMACtrl | DMAC_TxBurstLimit(3)); /* * Send a PAUSE frame when we reach 29,696 bytes in the Rx * FIFO, and send an un-PAUSE frame when we reach 3056 bytes * in the Rx FIFO. */ CSR_WRITE_2(sc, STGE_FlowOnTresh, 29696 / 16); CSR_WRITE_2(sc, STGE_FlowOffThresh, 3056 / 16); /* * Set the maximum frame size. */ sc->sc_if_framesize = ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN; CSR_WRITE_2(sc, STGE_MaxFrameSize, sc->sc_if_framesize); /* * Initialize MacCtrl -- do it before setting the media, * as setting the media will actually program the register. * * Note: We have to poke the IFS value before poking * anything else. */ /* Tx/Rx MAC should be disabled before programming IFS.*/ CSR_WRITE_4(sc, STGE_MACCtrl, MC_IFSSelect(MC_IFS96bit)); stge_vlan_setup(sc); if (sc->sc_rev >= 6) { /* >= B.2 */ /* Multi-frag frame bug work-around. */ CSR_WRITE_2(sc, STGE_DebugCtrl, CSR_READ_2(sc, STGE_DebugCtrl) | 0x0200); /* Tx Poll Now bug work-around. */ CSR_WRITE_2(sc, STGE_DebugCtrl, CSR_READ_2(sc, STGE_DebugCtrl) | 0x0010); /* Tx Poll Now bug work-around. */ CSR_WRITE_2(sc, STGE_DebugCtrl, CSR_READ_2(sc, STGE_DebugCtrl) | 0x0020); } v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; v |= MC_StatisticsEnable | MC_TxEnable | MC_RxEnable; CSR_WRITE_4(sc, STGE_MACCtrl, v); /* * It seems that transmitting frames without checking the state of * Rx/Tx MAC wedge the hardware. */ stge_start_tx(sc); stge_start_rx(sc); /* * Set the current media. */ mii_mediachg(mii); /* * Start the one second MII clock. */ callout_reset(&sc->sc_tick_ch, hz, stge_tick, sc); /* * ...all done! */ ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; out: if (error != 0) device_printf(sc->sc_dev, "interface not running\n"); } static void stge_vlan_setup(struct stge_softc *sc) { struct ifnet *ifp; uint32_t v; ifp = sc->sc_ifp; /* * The NIC always copy a VLAN tag regardless of STGE_MACCtrl * MC_AutoVLANuntagging bit. * MC_AutoVLANtagging bit selects which VLAN source to use * between STGE_VLANTag and TFC. However TFC TFD_VLANTagInsert * bit has priority over MC_AutoVLANtagging bit. So we always * use TFC instead of STGE_VLANTag register. */ v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((ifp->if_capenable & IFCAP_VLAN_HWTAGGING) != 0) v |= MC_AutoVLANuntagging; else v &= ~MC_AutoVLANuntagging; CSR_WRITE_4(sc, STGE_MACCtrl, v); } /* * Stop transmission on the interface. */ static void stge_stop(struct stge_softc *sc) { struct ifnet *ifp; struct stge_txdesc *txd; struct stge_rxdesc *rxd; uint32_t v; int i; STGE_LOCK_ASSERT(sc); /* * Stop the one second clock. */ callout_stop(&sc->sc_tick_ch); /* * Reset the chip to a known state. */ stge_reset(sc, STGE_RESET_FULL); /* * Disable interrupts. */ CSR_WRITE_2(sc, STGE_IntEnable, 0); /* * Stop receiver, transmitter, and stats update. */ stge_stop_rx(sc); stge_stop_tx(sc); v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; v |= MC_StatisticsDisable; CSR_WRITE_4(sc, STGE_MACCtrl, v); /* * Stop the transmit and receive DMA. */ stge_dma_wait(sc); CSR_WRITE_4(sc, STGE_TFDListPtrHi, 0); CSR_WRITE_4(sc, STGE_TFDListPtrLo, 0); CSR_WRITE_4(sc, STGE_RFDListPtrHi, 0); CSR_WRITE_4(sc, STGE_RFDListPtrLo, 0); /* * Free RX and TX mbufs still in the queues. */ for (i = 0; i < STGE_RX_RING_CNT; i++) { rxd = &sc->sc_cdata.stge_rxdesc[i]; if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap); m_freem(rxd->rx_m); rxd->rx_m = NULL; } } for (i = 0; i < STGE_TX_RING_CNT; i++) { txd = &sc->sc_cdata.stge_txdesc[i]; if (txd->tx_m != NULL) { bus_dmamap_sync(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_cdata.stge_tx_tag, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; } } /* * Mark the interface down and cancel the watchdog timer. */ ifp = sc->sc_ifp; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); ifp->if_timer = 0; } static void stge_start_tx(struct stge_softc *sc) { uint32_t v; int i; v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_TxEnabled) != 0) return; v |= MC_TxEnable; CSR_WRITE_4(sc, STGE_MACCtrl, v); CSR_WRITE_1(sc, STGE_TxDMAPollPeriod, 127); for (i = STGE_TIMEOUT; i > 0; i--) { DELAY(10); v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_TxEnabled) != 0) break; } if (i == 0) device_printf(sc->sc_dev, "Starting Tx MAC timed out\n"); } static void stge_start_rx(struct stge_softc *sc) { uint32_t v; int i; v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_RxEnabled) != 0) return; v |= MC_RxEnable; CSR_WRITE_4(sc, STGE_MACCtrl, v); CSR_WRITE_1(sc, STGE_RxDMAPollPeriod, 1); for (i = STGE_TIMEOUT; i > 0; i--) { DELAY(10); v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_RxEnabled) != 0) break; } if (i == 0) device_printf(sc->sc_dev, "Starting Rx MAC timed out\n"); } static void stge_stop_tx(struct stge_softc *sc) { uint32_t v; int i; v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_TxEnabled) == 0) return; v |= MC_TxDisable; CSR_WRITE_4(sc, STGE_MACCtrl, v); for (i = STGE_TIMEOUT; i > 0; i--) { DELAY(10); v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_TxEnabled) == 0) break; } if (i == 0) device_printf(sc->sc_dev, "Stopping Tx MAC timed out\n"); } static void stge_stop_rx(struct stge_softc *sc) { uint32_t v; int i; v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_RxEnabled) == 0) return; v |= MC_RxDisable; CSR_WRITE_4(sc, STGE_MACCtrl, v); for (i = STGE_TIMEOUT; i > 0; i--) { DELAY(10); v = CSR_READ_4(sc, STGE_MACCtrl) & MC_MASK; if ((v & MC_RxEnabled) == 0) break; } if (i == 0) device_printf(sc->sc_dev, "Stopping Rx MAC timed out\n"); } static void stge_init_tx_ring(struct stge_softc *sc) { struct stge_ring_data *rd; struct stge_txdesc *txd; bus_addr_t addr; int i; STAILQ_INIT(&sc->sc_cdata.stge_txfreeq); STAILQ_INIT(&sc->sc_cdata.stge_txbusyq); sc->sc_cdata.stge_tx_prod = 0; sc->sc_cdata.stge_tx_cons = 0; sc->sc_cdata.stge_tx_cnt = 0; rd = &sc->sc_rdata; bzero(rd->stge_tx_ring, STGE_TX_RING_SZ); for (i = 0; i < STGE_TX_RING_CNT; i++) { if (i == (STGE_TX_RING_CNT - 1)) addr = STGE_TX_RING_ADDR(sc, 0); else addr = STGE_TX_RING_ADDR(sc, i + 1); rd->stge_tx_ring[i].tfd_next = htole64(addr); rd->stge_tx_ring[i].tfd_control = htole64(TFD_TFDDone); txd = &sc->sc_cdata.stge_txdesc[i]; STAILQ_INSERT_TAIL(&sc->sc_cdata.stge_txfreeq, txd, tx_q); } bus_dmamap_sync(sc->sc_cdata.stge_tx_ring_tag, sc->sc_cdata.stge_tx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } static int stge_init_rx_ring(struct stge_softc *sc) { struct stge_ring_data *rd; bus_addr_t addr; int i; sc->sc_cdata.stge_rx_cons = 0; STGE_RXCHAIN_RESET(sc); rd = &sc->sc_rdata; bzero(rd->stge_rx_ring, STGE_RX_RING_SZ); for (i = 0; i < STGE_RX_RING_CNT; i++) { if (stge_newbuf(sc, i) != 0) return (ENOBUFS); if (i == (STGE_RX_RING_CNT - 1)) addr = STGE_RX_RING_ADDR(sc, 0); else addr = STGE_RX_RING_ADDR(sc, i + 1); rd->stge_rx_ring[i].rfd_next = htole64(addr); rd->stge_rx_ring[i].rfd_status = 0; } bus_dmamap_sync(sc->sc_cdata.stge_rx_ring_tag, sc->sc_cdata.stge_rx_ring_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return (0); } /* * stge_newbuf: * * Add a receive buffer to the indicated descriptor. */ static int stge_newbuf(struct stge_softc *sc, int idx) { struct stge_rxdesc *rxd; struct stge_rfd *rfd; struct mbuf *m; bus_dma_segment_t segs[1]; bus_dmamap_t map; int nsegs; m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); m->m_len = m->m_pkthdr.len = MCLBYTES; /* * The hardware requires 4bytes aligned DMA address when JUMBO * frame is used. */ if (sc->sc_if_framesize <= (MCLBYTES - ETHER_ALIGN)) m_adj(m, ETHER_ALIGN); if (bus_dmamap_load_mbuf_sg(sc->sc_cdata.stge_rx_tag, sc->sc_cdata.stge_rx_sparemap, m, segs, &nsegs, 0) != 0) { m_freem(m); return (ENOBUFS); } KASSERT(nsegs == 1, ("%s: %d segments returned!", __func__, nsegs)); rxd = &sc->sc_cdata.stge_rxdesc[idx]; if (rxd->rx_m != NULL) { bus_dmamap_sync(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap); } map = rxd->rx_dmamap; rxd->rx_dmamap = sc->sc_cdata.stge_rx_sparemap; sc->sc_cdata.stge_rx_sparemap = map; bus_dmamap_sync(sc->sc_cdata.stge_rx_tag, rxd->rx_dmamap, BUS_DMASYNC_PREREAD); rxd->rx_m = m; rfd = &sc->sc_rdata.stge_rx_ring[idx]; rfd->rfd_frag.frag_word0 = htole64(FRAG_ADDR(segs[0].ds_addr) | FRAG_LEN(segs[0].ds_len)); rfd->rfd_status = 0; return (0); } /* * stge_set_filter: * * Set up the receive filter. */ static void stge_set_filter(struct stge_softc *sc) { struct ifnet *ifp; uint16_t mode; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; mode = CSR_READ_2(sc, STGE_ReceiveMode); mode |= RM_ReceiveUnicast; if ((ifp->if_flags & IFF_BROADCAST) != 0) mode |= RM_ReceiveBroadcast; else mode &= ~RM_ReceiveBroadcast; if ((ifp->if_flags & IFF_PROMISC) != 0) mode |= RM_ReceiveAllFrames; else mode &= ~RM_ReceiveAllFrames; CSR_WRITE_2(sc, STGE_ReceiveMode, mode); } static void stge_set_multi(struct stge_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; uint32_t crc; uint32_t mchash[2]; uint16_t mode; int count; STGE_LOCK_ASSERT(sc); ifp = sc->sc_ifp; mode = CSR_READ_2(sc, STGE_ReceiveMode); if ((ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI)) != 0) { if ((ifp->if_flags & IFF_PROMISC) != 0) mode |= RM_ReceiveAllFrames; else if ((ifp->if_flags & IFF_ALLMULTI) != 0) mode |= RM_ReceiveMulticast; CSR_WRITE_2(sc, STGE_ReceiveMode, mode); return; } /* clear existing filters. */ CSR_WRITE_4(sc, STGE_HashTable0, 0); CSR_WRITE_4(sc, STGE_HashTable1, 0); /* * Set up the multicast address filter by passing all multicast * addresses through a CRC generator, and then using the low-order * 6 bits as an index into the 64 bit multicast hash table. The * high order bits select the register, while the rest of the bits * select the bit within the register. */ bzero(mchash, sizeof(mchash)); count = 0; IF_ADDR_LOCK(sc->sc_ifp); TAILQ_FOREACH(ifma, &sc->sc_ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; crc = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN); /* Just want the 6 least significant bits. */ crc &= 0x3f; /* Set the corresponding bit in the hash table. */ mchash[crc >> 5] |= 1 << (crc & 0x1f); count++; } IF_ADDR_UNLOCK(ifp); mode &= ~(RM_ReceiveMulticast | RM_ReceiveAllFrames); if (count > 0) mode |= RM_ReceiveMulticastHash; else mode &= ~RM_ReceiveMulticastHash; CSR_WRITE_4(sc, STGE_HashTable0, mchash[0]); CSR_WRITE_4(sc, STGE_HashTable1, mchash[1]); CSR_WRITE_2(sc, STGE_ReceiveMode, mode); } static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high) { int error, value; if (!arg1) return (EINVAL); value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if (error || !req->newptr) return (error); if (value < low || value > high) return (EINVAL); *(int *)arg1 = value; return (0); } static int sysctl_hw_stge_rxint_nframe(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, STGE_RXINT_NFRAME_MIN, STGE_RXINT_NFRAME_MAX)); } static int sysctl_hw_stge_rxint_dmawait(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, STGE_RXINT_DMAWAIT_MIN, STGE_RXINT_DMAWAIT_MAX)); } Index: head/sys/dev/ti/if_ti.c =================================================================== --- head/sys/dev/ti/if_ti.c (revision 162374) +++ head/sys/dev/ti/if_ti.c (revision 162375) @@ -1,3890 +1,3887 @@ /*- * Copyright (c) 1997, 1998, 1999 * Bill Paul . 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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. */ /* * Alteon Networks Tigon PCI gigabit ethernet driver for FreeBSD. * Manuals, sample driver and firmware source kits are available * from http://www.alteon.com/support/openkits. * * Written by Bill Paul * Electrical Engineering Department * Columbia University, New York City */ /* * The Alteon Networks Tigon chip contains an embedded R4000 CPU, * gigabit MAC, dual DMA channels and a PCI interface unit. NICs * using the Tigon may have anywhere from 512K to 2MB of SRAM. The * Tigon supports hardware IP, TCP and UCP checksumming, multicast * filtering and jumbo (9014 byte) frames. The hardware is largely * controlled by firmware, which must be loaded into the NIC during * initialization. * * The Tigon 2 contains 2 R4000 CPUs and requires a newer firmware * revision, which supports new features such as extended commands, * extended jumbo receive ring desciptors and a mini receive ring. * * Alteon Networks is to be commended for releasing such a vast amount * of development material for the Tigon NIC without requiring an NDA * (although they really should have done it a long time ago). With * any luck, the other vendors will finally wise up and follow Alteon's * stellar example. * * The firmware for the Tigon 1 and 2 NICs is compiled directly into * this driver by #including it as a C header file. This bloats the * driver somewhat, but it's the easiest method considering that the * driver code and firmware code need to be kept in sync. The source * for the firmware is not provided with the FreeBSD distribution since * compiling it requires a GNU toolchain targeted for mips-sgi-irix5.3. * * The following people deserve special thanks: * - Terry Murphy of 3Com, for providing a 3c985 Tigon 1 board * for testing * - Raymond Lee of Netgear, for providing a pair of Netgear * GA620 Tigon 2 boards for testing * - Ulf Zimmermann, for bringing the GA260 to my attention and * convincing me to write this driver. * - Andrew Gallatin for providing FreeBSD/Alpha support. */ #include __FBSDID("$FreeBSD$"); #include "opt_ti.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* #define TI_PRIVATE_JUMBOS */ #ifndef TI_PRIVATE_JUMBOS #include #include #endif #include #include #include #include #include #include #define TI_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP | CSUM_IP_FRAGS) /* * We can only turn on header splitting if we're using extended receive * BDs. */ #if defined(TI_JUMBO_HDRSPLIT) && defined(TI_PRIVATE_JUMBOS) #error "options TI_JUMBO_HDRSPLIT and TI_PRIVATE_JUMBOS are mutually exclusive" #endif /* TI_JUMBO_HDRSPLIT && TI_JUMBO_HDRSPLIT */ typedef enum { TI_SWAP_HTON, TI_SWAP_NTOH } ti_swap_type; /* * Various supported device vendors/types and their names. */ static struct ti_type ti_devs[] = { { ALT_VENDORID, ALT_DEVICEID_ACENIC, "Alteon AceNIC 1000baseSX Gigabit Ethernet" }, { ALT_VENDORID, ALT_DEVICEID_ACENIC_COPPER, "Alteon AceNIC 1000baseT Gigabit Ethernet" }, { TC_VENDORID, TC_DEVICEID_3C985, "3Com 3c985-SX Gigabit Ethernet" }, { NG_VENDORID, NG_DEVICEID_GA620, "Netgear GA620 1000baseSX Gigabit Ethernet" }, { NG_VENDORID, NG_DEVICEID_GA620T, "Netgear GA620 1000baseT Gigabit Ethernet" }, { SGI_VENDORID, SGI_DEVICEID_TIGON, "Silicon Graphics Gigabit Ethernet" }, { DEC_VENDORID, DEC_DEVICEID_FARALLON_PN9000SX, "Farallon PN9000SX Gigabit Ethernet" }, { 0, 0, NULL } }; static d_open_t ti_open; static d_close_t ti_close; static d_ioctl_t ti_ioctl2; static struct cdevsw ti_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_open = ti_open, .d_close = ti_close, .d_ioctl = ti_ioctl2, .d_name = "ti", }; static int ti_probe(device_t); static int ti_attach(device_t); static int ti_detach(device_t); static void ti_txeof(struct ti_softc *); static void ti_rxeof(struct ti_softc *); static void ti_stats_update(struct ti_softc *); static int ti_encap(struct ti_softc *, struct mbuf **); static void ti_intr(void *); static void ti_start(struct ifnet *); static void ti_start_locked(struct ifnet *); static int ti_ioctl(struct ifnet *, u_long, caddr_t); static void ti_init(void *); static void ti_init_locked(void *); static void ti_init2(struct ti_softc *); static void ti_stop(struct ti_softc *); static void ti_watchdog(struct ifnet *); static void ti_shutdown(device_t); static int ti_ifmedia_upd(struct ifnet *); static void ti_ifmedia_sts(struct ifnet *, struct ifmediareq *); static u_int32_t ti_eeprom_putbyte(struct ti_softc *, int); static u_int8_t ti_eeprom_getbyte(struct ti_softc *, int, u_int8_t *); static int ti_read_eeprom(struct ti_softc *, caddr_t, int, int); static void ti_add_mcast(struct ti_softc *, struct ether_addr *); static void ti_del_mcast(struct ti_softc *, struct ether_addr *); static void ti_setmulti(struct ti_softc *); static void ti_mem_read(struct ti_softc *, u_int32_t, u_int32_t, void *); static void ti_mem_write(struct ti_softc *, u_int32_t, u_int32_t, void *); static void ti_mem_zero(struct ti_softc *, u_int32_t, u_int32_t); static int ti_copy_mem(struct ti_softc *, u_int32_t, u_int32_t, caddr_t, int, int); static int ti_copy_scratch(struct ti_softc *, u_int32_t, u_int32_t, caddr_t, int, int, int); static int ti_bcopy_swap(const void *, void *, size_t, ti_swap_type); static void ti_loadfw(struct ti_softc *); static void ti_cmd(struct ti_softc *, struct ti_cmd_desc *); static void ti_cmd_ext(struct ti_softc *, struct ti_cmd_desc *, caddr_t, int); static void ti_handle_events(struct ti_softc *); static int ti_alloc_dmamaps(struct ti_softc *); static void ti_free_dmamaps(struct ti_softc *); static int ti_alloc_jumbo_mem(struct ti_softc *); #ifdef TI_PRIVATE_JUMBOS static void *ti_jalloc(struct ti_softc *); static void ti_jfree(void *, void *); #endif /* TI_PRIVATE_JUMBOS */ static int ti_newbuf_std(struct ti_softc *, int, struct mbuf *); static int ti_newbuf_mini(struct ti_softc *, int, struct mbuf *); static int ti_newbuf_jumbo(struct ti_softc *, int, struct mbuf *); static int ti_init_rx_ring_std(struct ti_softc *); static void ti_free_rx_ring_std(struct ti_softc *); static int ti_init_rx_ring_jumbo(struct ti_softc *); static void ti_free_rx_ring_jumbo(struct ti_softc *); static int ti_init_rx_ring_mini(struct ti_softc *); static void ti_free_rx_ring_mini(struct ti_softc *); static void ti_free_tx_ring(struct ti_softc *); static int ti_init_tx_ring(struct ti_softc *); static int ti_64bitslot_war(struct ti_softc *); static int ti_chipinit(struct ti_softc *); static int ti_gibinit(struct ti_softc *); #ifdef TI_JUMBO_HDRSPLIT static __inline void ti_hdr_split (struct mbuf *top, int hdr_len, int pkt_len, int idx); #endif /* TI_JUMBO_HDRSPLIT */ static device_method_t ti_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ti_probe), DEVMETHOD(device_attach, ti_attach), DEVMETHOD(device_detach, ti_detach), DEVMETHOD(device_shutdown, ti_shutdown), { 0, 0 } }; static driver_t ti_driver = { "ti", ti_methods, sizeof(struct ti_softc) }; static devclass_t ti_devclass; DRIVER_MODULE(ti, pci, ti_driver, ti_devclass, 0, 0); MODULE_DEPEND(ti, pci, 1, 1, 1); MODULE_DEPEND(ti, ether, 1, 1, 1); /* * Send an instruction or address to the EEPROM, check for ACK. */ static u_int32_t ti_eeprom_putbyte(sc, byte) struct ti_softc *sc; int byte; { int i, ack = 0; /* * Make sure we're in TX mode. */ TI_SETBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_TXEN); /* * Feed in each bit and stobe the clock. */ for (i = 0x80; i; i >>= 1) { if (byte & i) { TI_SETBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_DOUT); } else { TI_CLRBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_DOUT); } DELAY(1); TI_SETBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_CLK); DELAY(1); TI_CLRBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_CLK); } /* * Turn off TX mode. */ TI_CLRBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_TXEN); /* * Check for ack. */ TI_SETBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_CLK); ack = CSR_READ_4(sc, TI_MISC_LOCAL_CTL) & TI_MLC_EE_DIN; TI_CLRBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_CLK); return (ack); } /* * Read a byte of data stored in the EEPROM at address 'addr.' * We have to send two address bytes since the EEPROM can hold * more than 256 bytes of data. */ static u_int8_t ti_eeprom_getbyte(sc, addr, dest) struct ti_softc *sc; int addr; u_int8_t *dest; { int i; u_int8_t byte = 0; EEPROM_START; /* * Send write control code to EEPROM. */ if (ti_eeprom_putbyte(sc, EEPROM_CTL_WRITE)) { device_printf(sc->ti_dev, "failed to send write command, status: %x\n", CSR_READ_4(sc, TI_MISC_LOCAL_CTL)); return (1); } /* * Send first byte of address of byte we want to read. */ if (ti_eeprom_putbyte(sc, (addr >> 8) & 0xFF)) { device_printf(sc->ti_dev, "failed to send address, status: %x\n", CSR_READ_4(sc, TI_MISC_LOCAL_CTL)); return (1); } /* * Send second byte address of byte we want to read. */ if (ti_eeprom_putbyte(sc, addr & 0xFF)) { device_printf(sc->ti_dev, "failed to send address, status: %x\n", CSR_READ_4(sc, TI_MISC_LOCAL_CTL)); return (1); } EEPROM_STOP; EEPROM_START; /* * Send read control code to EEPROM. */ if (ti_eeprom_putbyte(sc, EEPROM_CTL_READ)) { device_printf(sc->ti_dev, "failed to send read command, status: %x\n", CSR_READ_4(sc, TI_MISC_LOCAL_CTL)); return (1); } /* * Start reading bits from EEPROM. */ TI_CLRBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_TXEN); for (i = 0x80; i; i >>= 1) { TI_SETBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_CLK); DELAY(1); if (CSR_READ_4(sc, TI_MISC_LOCAL_CTL) & TI_MLC_EE_DIN) byte |= i; TI_CLRBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_EE_CLK); DELAY(1); } EEPROM_STOP; /* * No ACK generated for read, so just return byte. */ *dest = byte; return (0); } /* * Read a sequence of bytes from the EEPROM. */ static int ti_read_eeprom(sc, dest, off, cnt) struct ti_softc *sc; caddr_t dest; int off; int cnt; { int err = 0, i; u_int8_t byte = 0; for (i = 0; i < cnt; i++) { err = ti_eeprom_getbyte(sc, off + i, &byte); if (err) break; *(dest + i) = byte; } return (err ? 1 : 0); } /* * NIC memory read function. * Can be used to copy data from NIC local memory. */ static void ti_mem_read(sc, addr, len, buf) struct ti_softc *sc; u_int32_t addr, len; void *buf; { int segptr, segsize, cnt; char *ptr; segptr = addr; cnt = len; ptr = buf; while (cnt) { if (cnt < TI_WINLEN) segsize = cnt; else segsize = TI_WINLEN - (segptr % TI_WINLEN); CSR_WRITE_4(sc, TI_WINBASE, (segptr & ~(TI_WINLEN - 1))); bus_space_read_region_4(sc->ti_btag, sc->ti_bhandle, TI_WINDOW + (segptr & (TI_WINLEN - 1)), (u_int32_t *)ptr, segsize / 4); ptr += segsize; segptr += segsize; cnt -= segsize; } } /* * NIC memory write function. * Can be used to copy data into NIC local memory. */ static void ti_mem_write(sc, addr, len, buf) struct ti_softc *sc; u_int32_t addr, len; void *buf; { int segptr, segsize, cnt; char *ptr; segptr = addr; cnt = len; ptr = buf; while (cnt) { if (cnt < TI_WINLEN) segsize = cnt; else segsize = TI_WINLEN - (segptr % TI_WINLEN); CSR_WRITE_4(sc, TI_WINBASE, (segptr & ~(TI_WINLEN - 1))); bus_space_write_region_4(sc->ti_btag, sc->ti_bhandle, TI_WINDOW + (segptr & (TI_WINLEN - 1)), (u_int32_t *)ptr, segsize / 4); ptr += segsize; segptr += segsize; cnt -= segsize; } } /* * NIC memory read function. * Can be used to clear a section of NIC local memory. */ static void ti_mem_zero(sc, addr, len) struct ti_softc *sc; u_int32_t addr, len; { int segptr, segsize, cnt; segptr = addr; cnt = len; while (cnt) { if (cnt < TI_WINLEN) segsize = cnt; else segsize = TI_WINLEN - (segptr % TI_WINLEN); CSR_WRITE_4(sc, TI_WINBASE, (segptr & ~(TI_WINLEN - 1))); bus_space_set_region_4(sc->ti_btag, sc->ti_bhandle, TI_WINDOW + (segptr & (TI_WINLEN - 1)), 0, segsize / 4); segptr += segsize; cnt -= segsize; } } static int ti_copy_mem(sc, tigon_addr, len, buf, useraddr, readdata) struct ti_softc *sc; u_int32_t tigon_addr, len; caddr_t buf; int useraddr, readdata; { int segptr, segsize, cnt; caddr_t ptr; u_int32_t origwin; u_int8_t tmparray[TI_WINLEN], tmparray2[TI_WINLEN]; int resid, segresid; int first_pass; TI_LOCK_ASSERT(sc); /* * At the moment, we don't handle non-aligned cases, we just bail. * If this proves to be a problem, it will be fixed. */ if ((readdata == 0) && (tigon_addr & 0x3)) { device_printf(sc->ti_dev, "%s: tigon address %#x isn't " "word-aligned\n", __func__, tigon_addr); device_printf(sc->ti_dev, "%s: unaligned writes aren't " "yet supported\n", __func__); return (EINVAL); } segptr = tigon_addr & ~0x3; segresid = tigon_addr - segptr; /* * This is the non-aligned amount left over that we'll need to * copy. */ resid = len & 0x3; /* Add in the left over amount at the front of the buffer */ resid += segresid; cnt = len & ~0x3; /* * If resid + segresid is >= 4, add multiples of 4 to the count and * decrease the residual by that much. */ cnt += resid & ~0x3; resid -= resid & ~0x3; ptr = buf; first_pass = 1; /* * Save the old window base value. */ origwin = CSR_READ_4(sc, TI_WINBASE); while (cnt) { bus_size_t ti_offset; if (cnt < TI_WINLEN) segsize = cnt; else segsize = TI_WINLEN - (segptr % TI_WINLEN); CSR_WRITE_4(sc, TI_WINBASE, (segptr & ~(TI_WINLEN - 1))); ti_offset = TI_WINDOW + (segptr & (TI_WINLEN -1)); if (readdata) { bus_space_read_region_4(sc->ti_btag, sc->ti_bhandle, ti_offset, (u_int32_t *)tmparray, segsize >> 2); if (useraddr) { /* * Yeah, this is a little on the kludgy * side, but at least this code is only * used for debugging. */ ti_bcopy_swap(tmparray, tmparray2, segsize, TI_SWAP_NTOH); TI_UNLOCK(sc); if (first_pass) { copyout(&tmparray2[segresid], ptr, segsize - segresid); first_pass = 0; } else copyout(tmparray2, ptr, segsize); TI_LOCK(sc); } else { if (first_pass) { ti_bcopy_swap(tmparray, tmparray2, segsize, TI_SWAP_NTOH); TI_UNLOCK(sc); bcopy(&tmparray2[segresid], ptr, segsize - segresid); TI_LOCK(sc); first_pass = 0; } else ti_bcopy_swap(tmparray, ptr, segsize, TI_SWAP_NTOH); } } else { if (useraddr) { TI_UNLOCK(sc); copyin(ptr, tmparray2, segsize); TI_LOCK(sc); ti_bcopy_swap(tmparray2, tmparray, segsize, TI_SWAP_HTON); } else ti_bcopy_swap(ptr, tmparray, segsize, TI_SWAP_HTON); bus_space_write_region_4(sc->ti_btag, sc->ti_bhandle, ti_offset, (u_int32_t *)tmparray, segsize >> 2); } segptr += segsize; ptr += segsize; cnt -= segsize; } /* * Handle leftover, non-word-aligned bytes. */ if (resid != 0) { u_int32_t tmpval, tmpval2; bus_size_t ti_offset; /* * Set the segment pointer. */ CSR_WRITE_4(sc, TI_WINBASE, (segptr & ~(TI_WINLEN - 1))); ti_offset = TI_WINDOW + (segptr & (TI_WINLEN - 1)); /* * First, grab whatever is in our source/destination. * We'll obviously need this for reads, but also for * writes, since we'll be doing read/modify/write. */ bus_space_read_region_4(sc->ti_btag, sc->ti_bhandle, ti_offset, &tmpval, 1); /* * Next, translate this from little-endian to big-endian * (at least on i386 boxes). */ tmpval2 = ntohl(tmpval); if (readdata) { /* * If we're reading, just copy the leftover number * of bytes from the host byte order buffer to * the user's buffer. */ if (useraddr) { TI_UNLOCK(sc); copyout(&tmpval2, ptr, resid); TI_LOCK(sc); } else bcopy(&tmpval2, ptr, resid); } else { /* * If we're writing, first copy the bytes to be * written into the network byte order buffer, * leaving the rest of the buffer with whatever was * originally in there. Then, swap the bytes * around into host order and write them out. * * XXX KDM the read side of this has been verified * to work, but the write side of it has not been * verified. So user beware. */ if (useraddr) { TI_UNLOCK(sc); copyin(ptr, &tmpval2, resid); TI_LOCK(sc); } else bcopy(ptr, &tmpval2, resid); tmpval = htonl(tmpval2); bus_space_write_region_4(sc->ti_btag, sc->ti_bhandle, ti_offset, &tmpval, 1); } } CSR_WRITE_4(sc, TI_WINBASE, origwin); return (0); } static int ti_copy_scratch(sc, tigon_addr, len, buf, useraddr, readdata, cpu) struct ti_softc *sc; u_int32_t tigon_addr, len; caddr_t buf; int useraddr, readdata; int cpu; { u_int32_t segptr; int cnt; u_int32_t tmpval, tmpval2; caddr_t ptr; TI_LOCK_ASSERT(sc); /* * At the moment, we don't handle non-aligned cases, we just bail. * If this proves to be a problem, it will be fixed. */ if (tigon_addr & 0x3) { device_printf(sc->ti_dev, "%s: tigon address %#x " "isn't word-aligned\n", __func__, tigon_addr); return (EINVAL); } if (len & 0x3) { device_printf(sc->ti_dev, "%s: transfer length %d " "isn't word-aligned\n", __func__, len); return (EINVAL); } segptr = tigon_addr; cnt = len; ptr = buf; while (cnt) { CSR_WRITE_4(sc, CPU_REG(TI_SRAM_ADDR, cpu), segptr); if (readdata) { tmpval2 = CSR_READ_4(sc, CPU_REG(TI_SRAM_DATA, cpu)); tmpval = ntohl(tmpval2); /* * Note: I've used this debugging interface * extensively with Alteon's 12.3.15 firmware, * compiled with GCC 2.7.2.1 and binutils 2.9.1. * * When you compile the firmware without * optimization, which is necessary sometimes in * order to properly step through it, you sometimes * read out a bogus value of 0xc0017c instead of * whatever was supposed to be in that scratchpad * location. That value is on the stack somewhere, * but I've never been able to figure out what was * causing the problem. * * The address seems to pop up in random places, * often not in the same place on two subsequent * reads. * * In any case, the underlying data doesn't seem * to be affected, just the value read out. * * KDM, 3/7/2000 */ if (tmpval2 == 0xc0017c) device_printf(sc->ti_dev, "found 0xc0017c at " "%#x (tmpval2)\n", segptr); if (tmpval == 0xc0017c) device_printf(sc->ti_dev, "found 0xc0017c at " "%#x (tmpval)\n", segptr); if (useraddr) copyout(&tmpval, ptr, 4); else bcopy(&tmpval, ptr, 4); } else { if (useraddr) copyin(ptr, &tmpval2, 4); else bcopy(ptr, &tmpval2, 4); tmpval = htonl(tmpval2); CSR_WRITE_4(sc, CPU_REG(TI_SRAM_DATA, cpu), tmpval); } cnt -= 4; segptr += 4; ptr += 4; } return (0); } static int ti_bcopy_swap(src, dst, len, swap_type) const void *src; void *dst; size_t len; ti_swap_type swap_type; { const u_int8_t *tmpsrc; u_int8_t *tmpdst; size_t tmplen; if (len & 0x3) { printf("ti_bcopy_swap: length %zd isn't 32-bit aligned\n", len); return (-1); } tmpsrc = src; tmpdst = dst; tmplen = len; while (tmplen) { if (swap_type == TI_SWAP_NTOH) *(u_int32_t *)tmpdst = ntohl(*(const u_int32_t *)tmpsrc); else *(u_int32_t *)tmpdst = htonl(*(const u_int32_t *)tmpsrc); tmpsrc += 4; tmpdst += 4; tmplen -= 4; } return (0); } /* * Load firmware image into the NIC. Check that the firmware revision * is acceptable and see if we want the firmware for the Tigon 1 or * Tigon 2. */ static void ti_loadfw(sc) struct ti_softc *sc; { TI_LOCK_ASSERT(sc); switch (sc->ti_hwrev) { case TI_HWREV_TIGON: if (tigonFwReleaseMajor != TI_FIRMWARE_MAJOR || tigonFwReleaseMinor != TI_FIRMWARE_MINOR || tigonFwReleaseFix != TI_FIRMWARE_FIX) { device_printf(sc->ti_dev, "firmware revision mismatch; " "want %d.%d.%d, got %d.%d.%d\n", TI_FIRMWARE_MAJOR, TI_FIRMWARE_MINOR, TI_FIRMWARE_FIX, tigonFwReleaseMajor, tigonFwReleaseMinor, tigonFwReleaseFix); return; } ti_mem_write(sc, tigonFwTextAddr, tigonFwTextLen, tigonFwText); ti_mem_write(sc, tigonFwDataAddr, tigonFwDataLen, tigonFwData); ti_mem_write(sc, tigonFwRodataAddr, tigonFwRodataLen, tigonFwRodata); ti_mem_zero(sc, tigonFwBssAddr, tigonFwBssLen); ti_mem_zero(sc, tigonFwSbssAddr, tigonFwSbssLen); CSR_WRITE_4(sc, TI_CPU_PROGRAM_COUNTER, tigonFwStartAddr); break; case TI_HWREV_TIGON_II: if (tigon2FwReleaseMajor != TI_FIRMWARE_MAJOR || tigon2FwReleaseMinor != TI_FIRMWARE_MINOR || tigon2FwReleaseFix != TI_FIRMWARE_FIX) { device_printf(sc->ti_dev, "firmware revision mismatch; " "want %d.%d.%d, got %d.%d.%d\n", TI_FIRMWARE_MAJOR, TI_FIRMWARE_MINOR, TI_FIRMWARE_FIX, tigon2FwReleaseMajor, tigon2FwReleaseMinor, tigon2FwReleaseFix); return; } ti_mem_write(sc, tigon2FwTextAddr, tigon2FwTextLen, tigon2FwText); ti_mem_write(sc, tigon2FwDataAddr, tigon2FwDataLen, tigon2FwData); ti_mem_write(sc, tigon2FwRodataAddr, tigon2FwRodataLen, tigon2FwRodata); ti_mem_zero(sc, tigon2FwBssAddr, tigon2FwBssLen); ti_mem_zero(sc, tigon2FwSbssAddr, tigon2FwSbssLen); CSR_WRITE_4(sc, TI_CPU_PROGRAM_COUNTER, tigon2FwStartAddr); break; default: device_printf(sc->ti_dev, "can't load firmware: unknown hardware rev\n"); break; } } /* * Send the NIC a command via the command ring. */ static void ti_cmd(sc, cmd) struct ti_softc *sc; struct ti_cmd_desc *cmd; { int index; index = sc->ti_cmd_saved_prodidx; CSR_WRITE_4(sc, TI_GCR_CMDRING + (index * 4), *(u_int32_t *)(cmd)); TI_INC(index, TI_CMD_RING_CNT); CSR_WRITE_4(sc, TI_MB_CMDPROD_IDX, index); sc->ti_cmd_saved_prodidx = index; } /* * Send the NIC an extended command. The 'len' parameter specifies the * number of command slots to include after the initial command. */ static void ti_cmd_ext(sc, cmd, arg, len) struct ti_softc *sc; struct ti_cmd_desc *cmd; caddr_t arg; int len; { int index; int i; index = sc->ti_cmd_saved_prodidx; CSR_WRITE_4(sc, TI_GCR_CMDRING + (index * 4), *(u_int32_t *)(cmd)); TI_INC(index, TI_CMD_RING_CNT); for (i = 0; i < len; i++) { CSR_WRITE_4(sc, TI_GCR_CMDRING + (index * 4), *(u_int32_t *)(&arg[i * 4])); TI_INC(index, TI_CMD_RING_CNT); } CSR_WRITE_4(sc, TI_MB_CMDPROD_IDX, index); sc->ti_cmd_saved_prodidx = index; } /* * Handle events that have triggered interrupts. */ static void ti_handle_events(sc) struct ti_softc *sc; { struct ti_event_desc *e; if (sc->ti_rdata->ti_event_ring == NULL) return; while (sc->ti_ev_saved_considx != sc->ti_ev_prodidx.ti_idx) { e = &sc->ti_rdata->ti_event_ring[sc->ti_ev_saved_considx]; switch (TI_EVENT_EVENT(e)) { case TI_EV_LINKSTAT_CHANGED: sc->ti_linkstat = TI_EVENT_CODE(e); if (sc->ti_linkstat == TI_EV_CODE_LINK_UP) device_printf(sc->ti_dev, "10/100 link up\n"); else if (sc->ti_linkstat == TI_EV_CODE_GIG_LINK_UP) device_printf(sc->ti_dev, "gigabit link up\n"); else if (sc->ti_linkstat == TI_EV_CODE_LINK_DOWN) device_printf(sc->ti_dev, "link down\n"); break; case TI_EV_ERROR: if (TI_EVENT_CODE(e) == TI_EV_CODE_ERR_INVAL_CMD) device_printf(sc->ti_dev, "invalid command\n"); else if (TI_EVENT_CODE(e) == TI_EV_CODE_ERR_UNIMP_CMD) device_printf(sc->ti_dev, "unknown command\n"); else if (TI_EVENT_CODE(e) == TI_EV_CODE_ERR_BADCFG) device_printf(sc->ti_dev, "bad config data\n"); break; case TI_EV_FIRMWARE_UP: ti_init2(sc); break; case TI_EV_STATS_UPDATED: ti_stats_update(sc); break; case TI_EV_RESET_JUMBO_RING: case TI_EV_MCAST_UPDATED: /* Who cares. */ break; default: device_printf(sc->ti_dev, "unknown event: %d\n", TI_EVENT_EVENT(e)); break; } /* Advance the consumer index. */ TI_INC(sc->ti_ev_saved_considx, TI_EVENT_RING_CNT); CSR_WRITE_4(sc, TI_GCR_EVENTCONS_IDX, sc->ti_ev_saved_considx); } } static int ti_alloc_dmamaps(struct ti_softc *sc) { int i; for (i = 0; i < TI_TX_RING_CNT; i++) { sc->ti_cdata.ti_txdesc[i].tx_m = NULL; sc->ti_cdata.ti_txdesc[i].tx_dmamap = 0; if (bus_dmamap_create(sc->ti_mbuftx_dmat, 0, &sc->ti_cdata.ti_txdesc[i].tx_dmamap)) return (ENOBUFS); } for (i = 0; i < TI_STD_RX_RING_CNT; i++) { if (bus_dmamap_create(sc->ti_mbufrx_dmat, 0, &sc->ti_cdata.ti_rx_std_maps[i])) return (ENOBUFS); } for (i = 0; i < TI_JUMBO_RX_RING_CNT; i++) { if (bus_dmamap_create(sc->ti_jumbo_dmat, 0, &sc->ti_cdata.ti_rx_jumbo_maps[i])) return (ENOBUFS); } for (i = 0; i < TI_MINI_RX_RING_CNT; i++) { if (bus_dmamap_create(sc->ti_mbufrx_dmat, 0, &sc->ti_cdata.ti_rx_mini_maps[i])) return (ENOBUFS); } return (0); } static void ti_free_dmamaps(struct ti_softc *sc) { int i; if (sc->ti_mbuftx_dmat) for (i = 0; i < TI_TX_RING_CNT; i++) if (sc->ti_cdata.ti_txdesc[i].tx_dmamap) { bus_dmamap_destroy(sc->ti_mbuftx_dmat, sc->ti_cdata.ti_txdesc[i].tx_dmamap); sc->ti_cdata.ti_txdesc[i].tx_dmamap = 0; } if (sc->ti_mbufrx_dmat) for (i = 0; i < TI_STD_RX_RING_CNT; i++) if (sc->ti_cdata.ti_rx_std_maps[i]) { bus_dmamap_destroy(sc->ti_mbufrx_dmat, sc->ti_cdata.ti_rx_std_maps[i]); sc->ti_cdata.ti_rx_std_maps[i] = 0; } if (sc->ti_jumbo_dmat) for (i = 0; i < TI_JUMBO_RX_RING_CNT; i++) if (sc->ti_cdata.ti_rx_jumbo_maps[i]) { bus_dmamap_destroy(sc->ti_jumbo_dmat, sc->ti_cdata.ti_rx_jumbo_maps[i]); sc->ti_cdata.ti_rx_jumbo_maps[i] = 0; } if (sc->ti_mbufrx_dmat) for (i = 0; i < TI_MINI_RX_RING_CNT; i++) if (sc->ti_cdata.ti_rx_mini_maps[i]) { bus_dmamap_destroy(sc->ti_mbufrx_dmat, sc->ti_cdata.ti_rx_mini_maps[i]); sc->ti_cdata.ti_rx_mini_maps[i] = 0; } } #ifdef TI_PRIVATE_JUMBOS /* * Memory management for the jumbo receive ring is a pain in the * butt. We need to allocate at least 9018 bytes of space per frame, * _and_ it has to be contiguous (unless you use the extended * jumbo descriptor format). Using malloc() all the time won't * work: malloc() allocates memory in powers of two, which means we * would end up wasting a considerable amount of space by allocating * 9K chunks. We don't have a jumbo mbuf cluster pool. Thus, we have * to do our own memory management. * * The driver needs to allocate a contiguous chunk of memory at boot * time. We then chop this up ourselves into 9K pieces and use them * as external mbuf storage. * * One issue here is how much memory to allocate. The jumbo ring has * 256 slots in it, but at 9K per slot than can consume over 2MB of * RAM. This is a bit much, especially considering we also need * RAM for the standard ring and mini ring (on the Tigon 2). To * save space, we only actually allocate enough memory for 64 slots * by default, which works out to between 500 and 600K. This can * be tuned by changing a #define in if_tireg.h. */ static int ti_alloc_jumbo_mem(sc) struct ti_softc *sc; { caddr_t ptr; int i; struct ti_jpool_entry *entry; /* * Grab a big chunk o' storage. Since we are chopping this pool up * into ~9k chunks, there doesn't appear to be a need to use page * alignment. */ if (bus_dma_tag_create(sc->ti_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ TI_JMEM, /* maxsize */ 1, /* nsegments */ TI_JMEM, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ti_jumbo_dmat) != 0) { device_printf(sc->ti_dev, "Failed to allocate jumbo dmat\n"); return (ENOBUFS); } if (bus_dmamem_alloc(sc->ti_jumbo_dmat, (void**)&sc->ti_cdata.ti_jumbo_buf, BUS_DMA_NOWAIT, &sc->ti_jumbo_dmamap) != 0) { device_printf(sc->ti_dev, "Failed to allocate jumbo memory\n"); return (ENOBUFS); } SLIST_INIT(&sc->ti_jfree_listhead); SLIST_INIT(&sc->ti_jinuse_listhead); /* * Now divide it up into 9K pieces and save the addresses * in an array. */ ptr = sc->ti_cdata.ti_jumbo_buf; for (i = 0; i < TI_JSLOTS; i++) { sc->ti_cdata.ti_jslots[i] = ptr; ptr += TI_JLEN; entry = malloc(sizeof(struct ti_jpool_entry), M_DEVBUF, M_NOWAIT); if (entry == NULL) { device_printf(sc->ti_dev, "no memory for jumbo " "buffer queue!\n"); return (ENOBUFS); } entry->slot = i; SLIST_INSERT_HEAD(&sc->ti_jfree_listhead, entry, jpool_entries); } return (0); } /* * Allocate a jumbo buffer. */ static void *ti_jalloc(sc) struct ti_softc *sc; { struct ti_jpool_entry *entry; entry = SLIST_FIRST(&sc->ti_jfree_listhead); if (entry == NULL) { device_printf(sc->ti_dev, "no free jumbo buffers\n"); return (NULL); } SLIST_REMOVE_HEAD(&sc->ti_jfree_listhead, jpool_entries); SLIST_INSERT_HEAD(&sc->ti_jinuse_listhead, entry, jpool_entries); return (sc->ti_cdata.ti_jslots[entry->slot]); } /* * Release a jumbo buffer. */ static void ti_jfree(buf, args) void *buf; void *args; { struct ti_softc *sc; int i; struct ti_jpool_entry *entry; /* Extract the softc struct pointer. */ sc = (struct ti_softc *)args; if (sc == NULL) panic("ti_jfree: didn't get softc pointer!"); /* calculate the slot this buffer belongs to */ i = ((vm_offset_t)buf - (vm_offset_t)sc->ti_cdata.ti_jumbo_buf) / TI_JLEN; if ((i < 0) || (i >= TI_JSLOTS)) panic("ti_jfree: asked to free buffer that we don't manage!"); entry = SLIST_FIRST(&sc->ti_jinuse_listhead); if (entry == NULL) panic("ti_jfree: buffer not in use!"); entry->slot = i; SLIST_REMOVE_HEAD(&sc->ti_jinuse_listhead, jpool_entries); SLIST_INSERT_HEAD(&sc->ti_jfree_listhead, entry, jpool_entries); } #else static int ti_alloc_jumbo_mem(sc) struct ti_softc *sc; { /* * The VM system will take care of providing aligned pages. Alignment * is set to 1 here so that busdma resources won't be wasted. */ if (bus_dma_tag_create(sc->ti_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ PAGE_SIZE * 4 /*XXX*/, /* maxsize */ 4, /* nsegments */ PAGE_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ti_jumbo_dmat) != 0) { device_printf(sc->ti_dev, "Failed to allocate jumbo dmat\n"); return (ENOBUFS); } return (0); } #endif /* TI_PRIVATE_JUMBOS */ /* * Intialize a standard receive ring descriptor. */ static int ti_newbuf_std(sc, i, m) struct ti_softc *sc; int i; struct mbuf *m; { bus_dmamap_t map; bus_dma_segment_t segs; struct mbuf *m_new = NULL; struct ti_rx_desc *r; int nsegs; nsegs = 0; if (m == NULL) { MGETHDR(m_new, M_DONTWAIT, MT_DATA); if (m_new == NULL) return (ENOBUFS); MCLGET(m_new, M_DONTWAIT); if (!(m_new->m_flags & M_EXT)) { m_freem(m_new); return (ENOBUFS); } m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; } else { m_new = m; m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; m_new->m_data = m_new->m_ext.ext_buf; } m_adj(m_new, ETHER_ALIGN); sc->ti_cdata.ti_rx_std_chain[i] = m_new; r = &sc->ti_rdata->ti_rx_std_ring[i]; map = sc->ti_cdata.ti_rx_std_maps[i]; if (bus_dmamap_load_mbuf_sg(sc->ti_mbufrx_dmat, map, m_new, &segs, &nsegs, 0)) return (ENOBUFS); if (nsegs != 1) return (ENOBUFS); ti_hostaddr64(&r->ti_addr, segs.ds_addr); r->ti_len = segs.ds_len; r->ti_type = TI_BDTYPE_RECV_BD; r->ti_flags = 0; if (sc->ti_ifp->if_hwassist) r->ti_flags |= TI_BDFLAG_TCP_UDP_CKSUM | TI_BDFLAG_IP_CKSUM; r->ti_idx = i; bus_dmamap_sync(sc->ti_mbufrx_dmat, map, BUS_DMASYNC_PREREAD); return (0); } /* * Intialize a mini receive ring descriptor. This only applies to * the Tigon 2. */ static int ti_newbuf_mini(sc, i, m) struct ti_softc *sc; int i; struct mbuf *m; { bus_dma_segment_t segs; bus_dmamap_t map; struct mbuf *m_new = NULL; struct ti_rx_desc *r; int nsegs; nsegs = 0; if (m == NULL) { MGETHDR(m_new, M_DONTWAIT, MT_DATA); if (m_new == NULL) { return (ENOBUFS); } m_new->m_len = m_new->m_pkthdr.len = MHLEN; } else { m_new = m; m_new->m_data = m_new->m_pktdat; m_new->m_len = m_new->m_pkthdr.len = MHLEN; } m_adj(m_new, ETHER_ALIGN); r = &sc->ti_rdata->ti_rx_mini_ring[i]; sc->ti_cdata.ti_rx_mini_chain[i] = m_new; map = sc->ti_cdata.ti_rx_mini_maps[i]; if (bus_dmamap_load_mbuf_sg(sc->ti_mbufrx_dmat, map, m_new, &segs, &nsegs, 0)) return (ENOBUFS); if (nsegs != 1) return (ENOBUFS); ti_hostaddr64(&r->ti_addr, segs.ds_addr); r->ti_len = segs.ds_len; r->ti_type = TI_BDTYPE_RECV_BD; r->ti_flags = TI_BDFLAG_MINI_RING; if (sc->ti_ifp->if_hwassist) r->ti_flags |= TI_BDFLAG_TCP_UDP_CKSUM | TI_BDFLAG_IP_CKSUM; r->ti_idx = i; bus_dmamap_sync(sc->ti_mbufrx_dmat, map, BUS_DMASYNC_PREREAD); return (0); } #ifdef TI_PRIVATE_JUMBOS /* * Initialize a jumbo receive ring descriptor. This allocates * a jumbo buffer from the pool managed internally by the driver. */ static int ti_newbuf_jumbo(sc, i, m) struct ti_softc *sc; int i; struct mbuf *m; { bus_dmamap_t map; struct mbuf *m_new = NULL; struct ti_rx_desc *r; int nsegs; bus_dma_segment_t segs; if (m == NULL) { caddr_t *buf = NULL; /* Allocate the mbuf. */ MGETHDR(m_new, M_DONTWAIT, MT_DATA); if (m_new == NULL) { return (ENOBUFS); } /* Allocate the jumbo buffer */ buf = ti_jalloc(sc); if (buf == NULL) { m_freem(m_new); device_printf(sc->ti_dev, "jumbo allocation failed " "-- packet dropped!\n"); return (ENOBUFS); } /* Attach the buffer to the mbuf. */ m_new->m_data = (void *) buf; m_new->m_len = m_new->m_pkthdr.len = TI_JUMBO_FRAMELEN; MEXTADD(m_new, buf, TI_JUMBO_FRAMELEN, ti_jfree, (struct ti_softc *)sc, 0, EXT_NET_DRV); } else { m_new = m; m_new->m_data = m_new->m_ext.ext_buf; m_new->m_ext.ext_size = TI_JUMBO_FRAMELEN; } m_adj(m_new, ETHER_ALIGN); /* Set up the descriptor. */ r = &sc->ti_rdata->ti_rx_jumbo_ring[i]; sc->ti_cdata.ti_rx_jumbo_chain[i] = m_new; map = sc->ti_cdata.ti_rx_jumbo_maps[i]; if (bus_dmamap_load_mbuf_sg(sc->ti_jumbo_dmat, map, m_new, &segs, &nsegs, 0)) return (ENOBUFS); if (nsegs != 1) return (ENOBUFS); ti_hostaddr64(&r->ti_addr, segs.ds_addr); r->ti_len = segs.ds_len; r->ti_type = TI_BDTYPE_RECV_JUMBO_BD; r->ti_flags = TI_BDFLAG_JUMBO_RING; if (sc->ti_ifp->if_hwassist) r->ti_flags |= TI_BDFLAG_TCP_UDP_CKSUM | TI_BDFLAG_IP_CKSUM; r->ti_idx = i; bus_dmamap_sync(sc->ti_jumbo_dmat, map, BUS_DMASYNC_PREREAD); return (0); } #else #if (PAGE_SIZE == 4096) #define NPAYLOAD 2 #else #define NPAYLOAD 1 #endif #define TCP_HDR_LEN (52 + sizeof(struct ether_header)) #define UDP_HDR_LEN (28 + sizeof(struct ether_header)) #define NFS_HDR_LEN (UDP_HDR_LEN) static int HDR_LEN = TCP_HDR_LEN; /* * Initialize a jumbo receive ring descriptor. This allocates * a jumbo buffer from the pool managed internally by the driver. */ static int ti_newbuf_jumbo(sc, idx, m_old) struct ti_softc *sc; int idx; struct mbuf *m_old; { bus_dmamap_t map; struct mbuf *cur, *m_new = NULL; struct mbuf *m[3] = {NULL, NULL, NULL}; struct ti_rx_desc_ext *r; vm_page_t frame; static int color; /* 1 extra buf to make nobufs easy*/ struct sf_buf *sf[3] = {NULL, NULL, NULL}; int i; bus_dma_segment_t segs[4]; int nsegs; if (m_old != NULL) { m_new = m_old; cur = m_old->m_next; for (i = 0; i <= NPAYLOAD; i++){ m[i] = cur; cur = cur->m_next; } } else { /* Allocate the mbufs. */ MGETHDR(m_new, M_DONTWAIT, MT_DATA); if (m_new == NULL) { device_printf(sc->ti_dev, "mbuf allocation failed " "-- packet dropped!\n"); goto nobufs; } MGET(m[NPAYLOAD], M_DONTWAIT, MT_DATA); if (m[NPAYLOAD] == NULL) { device_printf(sc->ti_dev, "cluster mbuf allocation " "failed -- packet dropped!\n"); goto nobufs; } MCLGET(m[NPAYLOAD], M_DONTWAIT); if ((m[NPAYLOAD]->m_flags & M_EXT) == 0) { device_printf(sc->ti_dev, "mbuf allocation failed " "-- packet dropped!\n"); goto nobufs; } m[NPAYLOAD]->m_len = MCLBYTES; for (i = 0; i < NPAYLOAD; i++){ MGET(m[i], M_DONTWAIT, MT_DATA); if (m[i] == NULL) { device_printf(sc->ti_dev, "mbuf allocation " "failed -- packet dropped!\n"); goto nobufs; } frame = vm_page_alloc(NULL, color++, VM_ALLOC_INTERRUPT | VM_ALLOC_NOOBJ | VM_ALLOC_WIRED); if (frame == NULL) { device_printf(sc->ti_dev, "buffer allocation " "failed -- packet dropped!\n"); printf(" index %d page %d\n", idx, i); goto nobufs; } sf[i] = sf_buf_alloc(frame, SFB_NOWAIT); if (sf[i] == NULL) { vm_page_lock_queues(); vm_page_unwire(frame, 0); vm_page_free(frame); vm_page_unlock_queues(); device_printf(sc->ti_dev, "buffer allocation " "failed -- packet dropped!\n"); printf(" index %d page %d\n", idx, i); goto nobufs; } } for (i = 0; i < NPAYLOAD; i++){ /* Attach the buffer to the mbuf. */ m[i]->m_data = (void *)sf_buf_kva(sf[i]); m[i]->m_len = PAGE_SIZE; MEXTADD(m[i], sf_buf_kva(sf[i]), PAGE_SIZE, sf_buf_mext, sf[i], 0, EXT_DISPOSABLE); m[i]->m_next = m[i+1]; } /* link the buffers to the header */ m_new->m_next = m[0]; m_new->m_data += ETHER_ALIGN; if (sc->ti_hdrsplit) m_new->m_len = MHLEN - ETHER_ALIGN; else m_new->m_len = HDR_LEN; m_new->m_pkthdr.len = NPAYLOAD * PAGE_SIZE + m_new->m_len; } /* Set up the descriptor. */ r = &sc->ti_rdata->ti_rx_jumbo_ring[idx]; sc->ti_cdata.ti_rx_jumbo_chain[idx] = m_new; map = sc->ti_cdata.ti_rx_jumbo_maps[i]; if (bus_dmamap_load_mbuf_sg(sc->ti_jumbo_dmat, map, m_new, segs, &nsegs, 0)) return (ENOBUFS); if ((nsegs < 1) || (nsegs > 4)) return (ENOBUFS); ti_hostaddr64(&r->ti_addr0, segs[0].ds_addr); r->ti_len0 = m_new->m_len; ti_hostaddr64(&r->ti_addr1, segs[1].ds_addr); r->ti_len1 = PAGE_SIZE; ti_hostaddr64(&r->ti_addr2, segs[2].ds_addr); r->ti_len2 = m[1]->m_ext.ext_size; /* could be PAGE_SIZE or MCLBYTES */ if (PAGE_SIZE == 4096) { ti_hostaddr64(&r->ti_addr3, segs[3].ds_addr); r->ti_len3 = MCLBYTES; } else { r->ti_len3 = 0; } r->ti_type = TI_BDTYPE_RECV_JUMBO_BD; r->ti_flags = TI_BDFLAG_JUMBO_RING|TI_RCB_FLAG_USE_EXT_RX_BD; if (sc->ti_ifp->if_hwassist) r->ti_flags |= TI_BDFLAG_TCP_UDP_CKSUM|TI_BDFLAG_IP_CKSUM; r->ti_idx = idx; bus_dmamap_sync(sc->ti_jumbo_dmat, map, BUS_DMASYNC_PREREAD); return (0); nobufs: /* * Warning! : * This can only be called before the mbufs are strung together. * If the mbufs are strung together, m_freem() will free the chain, * so that the later mbufs will be freed multiple times. */ if (m_new) m_freem(m_new); for (i = 0; i < 3; i++) { if (m[i]) m_freem(m[i]); if (sf[i]) sf_buf_mext((void *)sf_buf_kva(sf[i]), sf[i]); } return (ENOBUFS); } #endif /* * The standard receive ring has 512 entries in it. At 2K per mbuf cluster, * that's 1MB or memory, which is a lot. For now, we fill only the first * 256 ring entries and hope that our CPU is fast enough to keep up with * the NIC. */ static int ti_init_rx_ring_std(sc) struct ti_softc *sc; { int i; struct ti_cmd_desc cmd; for (i = 0; i < TI_SSLOTS; i++) { if (ti_newbuf_std(sc, i, NULL) == ENOBUFS) return (ENOBUFS); }; TI_UPDATE_STDPROD(sc, i - 1); sc->ti_std = i - 1; return (0); } static void ti_free_rx_ring_std(sc) struct ti_softc *sc; { bus_dmamap_t map; int i; for (i = 0; i < TI_STD_RX_RING_CNT; i++) { if (sc->ti_cdata.ti_rx_std_chain[i] != NULL) { map = sc->ti_cdata.ti_rx_std_maps[i]; bus_dmamap_sync(sc->ti_mbufrx_dmat, map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->ti_mbufrx_dmat, map); m_freem(sc->ti_cdata.ti_rx_std_chain[i]); sc->ti_cdata.ti_rx_std_chain[i] = NULL; } bzero((char *)&sc->ti_rdata->ti_rx_std_ring[i], sizeof(struct ti_rx_desc)); } } static int ti_init_rx_ring_jumbo(sc) struct ti_softc *sc; { int i; struct ti_cmd_desc cmd; for (i = 0; i < TI_JUMBO_RX_RING_CNT; i++) { if (ti_newbuf_jumbo(sc, i, NULL) == ENOBUFS) return (ENOBUFS); }; TI_UPDATE_JUMBOPROD(sc, i - 1); sc->ti_jumbo = i - 1; return (0); } static void ti_free_rx_ring_jumbo(sc) struct ti_softc *sc; { bus_dmamap_t map; int i; for (i = 0; i < TI_JUMBO_RX_RING_CNT; i++) { if (sc->ti_cdata.ti_rx_jumbo_chain[i] != NULL) { map = sc->ti_cdata.ti_rx_jumbo_maps[i]; bus_dmamap_sync(sc->ti_jumbo_dmat, map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->ti_jumbo_dmat, map); m_freem(sc->ti_cdata.ti_rx_jumbo_chain[i]); sc->ti_cdata.ti_rx_jumbo_chain[i] = NULL; } bzero((char *)&sc->ti_rdata->ti_rx_jumbo_ring[i], sizeof(struct ti_rx_desc)); } } static int ti_init_rx_ring_mini(sc) struct ti_softc *sc; { int i; for (i = 0; i < TI_MSLOTS; i++) { if (ti_newbuf_mini(sc, i, NULL) == ENOBUFS) return (ENOBUFS); }; TI_UPDATE_MINIPROD(sc, i - 1); sc->ti_mini = i - 1; return (0); } static void ti_free_rx_ring_mini(sc) struct ti_softc *sc; { bus_dmamap_t map; int i; for (i = 0; i < TI_MINI_RX_RING_CNT; i++) { if (sc->ti_cdata.ti_rx_mini_chain[i] != NULL) { map = sc->ti_cdata.ti_rx_mini_maps[i]; bus_dmamap_sync(sc->ti_mbufrx_dmat, map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->ti_mbufrx_dmat, map); m_freem(sc->ti_cdata.ti_rx_mini_chain[i]); sc->ti_cdata.ti_rx_mini_chain[i] = NULL; } bzero((char *)&sc->ti_rdata->ti_rx_mini_ring[i], sizeof(struct ti_rx_desc)); } } static void ti_free_tx_ring(sc) struct ti_softc *sc; { struct ti_txdesc *txd; int i; if (sc->ti_rdata->ti_tx_ring == NULL) return; for (i = 0; i < TI_TX_RING_CNT; i++) { txd = &sc->ti_cdata.ti_txdesc[i]; if (txd->tx_m != NULL) { bus_dmamap_sync(sc->ti_mbuftx_dmat, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->ti_mbuftx_dmat, txd->tx_dmamap); m_freem(txd->tx_m); txd->tx_m = NULL; } bzero((char *)&sc->ti_rdata->ti_tx_ring[i], sizeof(struct ti_tx_desc)); } } static int ti_init_tx_ring(sc) struct ti_softc *sc; { struct ti_txdesc *txd; int i; STAILQ_INIT(&sc->ti_cdata.ti_txfreeq); STAILQ_INIT(&sc->ti_cdata.ti_txbusyq); for (i = 0; i < TI_TX_RING_CNT; i++) { txd = &sc->ti_cdata.ti_txdesc[i]; STAILQ_INSERT_TAIL(&sc->ti_cdata.ti_txfreeq, txd, tx_q); } sc->ti_txcnt = 0; sc->ti_tx_saved_considx = 0; sc->ti_tx_saved_prodidx = 0; CSR_WRITE_4(sc, TI_MB_SENDPROD_IDX, 0); return (0); } /* * The Tigon 2 firmware has a new way to add/delete multicast addresses, * but we have to support the old way too so that Tigon 1 cards will * work. */ static void ti_add_mcast(sc, addr) struct ti_softc *sc; struct ether_addr *addr; { struct ti_cmd_desc cmd; u_int16_t *m; u_int32_t ext[2] = {0, 0}; m = (u_int16_t *)&addr->octet[0]; switch (sc->ti_hwrev) { case TI_HWREV_TIGON: CSR_WRITE_4(sc, TI_GCR_MAR0, htons(m[0])); CSR_WRITE_4(sc, TI_GCR_MAR1, (htons(m[1]) << 16) | htons(m[2])); TI_DO_CMD(TI_CMD_ADD_MCAST_ADDR, 0, 0); break; case TI_HWREV_TIGON_II: ext[0] = htons(m[0]); ext[1] = (htons(m[1]) << 16) | htons(m[2]); TI_DO_CMD_EXT(TI_CMD_EXT_ADD_MCAST, 0, 0, (caddr_t)&ext, 2); break; default: device_printf(sc->ti_dev, "unknown hwrev\n"); break; } } static void ti_del_mcast(sc, addr) struct ti_softc *sc; struct ether_addr *addr; { struct ti_cmd_desc cmd; u_int16_t *m; u_int32_t ext[2] = {0, 0}; m = (u_int16_t *)&addr->octet[0]; switch (sc->ti_hwrev) { case TI_HWREV_TIGON: CSR_WRITE_4(sc, TI_GCR_MAR0, htons(m[0])); CSR_WRITE_4(sc, TI_GCR_MAR1, (htons(m[1]) << 16) | htons(m[2])); TI_DO_CMD(TI_CMD_DEL_MCAST_ADDR, 0, 0); break; case TI_HWREV_TIGON_II: ext[0] = htons(m[0]); ext[1] = (htons(m[1]) << 16) | htons(m[2]); TI_DO_CMD_EXT(TI_CMD_EXT_DEL_MCAST, 0, 0, (caddr_t)&ext, 2); break; default: device_printf(sc->ti_dev, "unknown hwrev\n"); break; } } /* * Configure the Tigon's multicast address filter. * * The actual multicast table management is a bit of a pain, thanks to * slight brain damage on the part of both Alteon and us. With our * multicast code, we are only alerted when the multicast address table * changes and at that point we only have the current list of addresses: * we only know the current state, not the previous state, so we don't * actually know what addresses were removed or added. The firmware has * state, but we can't get our grubby mits on it, and there is no 'delete * all multicast addresses' command. Hence, we have to maintain our own * state so we know what addresses have been programmed into the NIC at * any given time. */ static void ti_setmulti(sc) struct ti_softc *sc; { struct ifnet *ifp; struct ifmultiaddr *ifma; struct ti_cmd_desc cmd; struct ti_mc_entry *mc; u_int32_t intrs; TI_LOCK_ASSERT(sc); ifp = sc->ti_ifp; if (ifp->if_flags & IFF_ALLMULTI) { TI_DO_CMD(TI_CMD_SET_ALLMULTI, TI_CMD_CODE_ALLMULTI_ENB, 0); return; } else { TI_DO_CMD(TI_CMD_SET_ALLMULTI, TI_CMD_CODE_ALLMULTI_DIS, 0); } /* Disable interrupts. */ intrs = CSR_READ_4(sc, TI_MB_HOSTINTR); CSR_WRITE_4(sc, TI_MB_HOSTINTR, 1); /* First, zot all the existing filters. */ while (SLIST_FIRST(&sc->ti_mc_listhead) != NULL) { mc = SLIST_FIRST(&sc->ti_mc_listhead); ti_del_mcast(sc, &mc->mc_addr); SLIST_REMOVE_HEAD(&sc->ti_mc_listhead, mc_entries); free(mc, M_DEVBUF); } /* Now program new ones. */ IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; mc = malloc(sizeof(struct ti_mc_entry), M_DEVBUF, M_NOWAIT); if (mc == NULL) { device_printf(sc->ti_dev, "no memory for mcast filter entry\n"); continue; } bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr), (char *)&mc->mc_addr, ETHER_ADDR_LEN); SLIST_INSERT_HEAD(&sc->ti_mc_listhead, mc, mc_entries); ti_add_mcast(sc, &mc->mc_addr); } IF_ADDR_UNLOCK(ifp); /* Re-enable interrupts. */ CSR_WRITE_4(sc, TI_MB_HOSTINTR, intrs); } /* * Check to see if the BIOS has configured us for a 64 bit slot when * we aren't actually in one. If we detect this condition, we can work * around it on the Tigon 2 by setting a bit in the PCI state register, * but for the Tigon 1 we must give up and abort the interface attach. */ static int ti_64bitslot_war(sc) struct ti_softc *sc; { if (!(CSR_READ_4(sc, TI_PCI_STATE) & TI_PCISTATE_32BIT_BUS)) { CSR_WRITE_4(sc, 0x600, 0); CSR_WRITE_4(sc, 0x604, 0); CSR_WRITE_4(sc, 0x600, 0x5555AAAA); if (CSR_READ_4(sc, 0x604) == 0x5555AAAA) { if (sc->ti_hwrev == TI_HWREV_TIGON) return (EINVAL); else { TI_SETBIT(sc, TI_PCI_STATE, TI_PCISTATE_32BIT_BUS); return (0); } } } return (0); } /* * Do endian, PCI and DMA initialization. Also check the on-board ROM * self-test results. */ static int ti_chipinit(sc) struct ti_softc *sc; { u_int32_t cacheline; u_int32_t pci_writemax = 0; u_int32_t hdrsplit; /* Initialize link to down state. */ sc->ti_linkstat = TI_EV_CODE_LINK_DOWN; if (sc->ti_ifp->if_capenable & IFCAP_HWCSUM) sc->ti_ifp->if_hwassist = TI_CSUM_FEATURES; else sc->ti_ifp->if_hwassist = 0; /* Set endianness before we access any non-PCI registers. */ #if 0 && BYTE_ORDER == BIG_ENDIAN CSR_WRITE_4(sc, TI_MISC_HOST_CTL, TI_MHC_BIGENDIAN_INIT | (TI_MHC_BIGENDIAN_INIT << 24)); #else CSR_WRITE_4(sc, TI_MISC_HOST_CTL, TI_MHC_LITTLEENDIAN_INIT | (TI_MHC_LITTLEENDIAN_INIT << 24)); #endif /* Check the ROM failed bit to see if self-tests passed. */ if (CSR_READ_4(sc, TI_CPU_STATE) & TI_CPUSTATE_ROMFAIL) { device_printf(sc->ti_dev, "board self-diagnostics failed!\n"); return (ENODEV); } /* Halt the CPU. */ TI_SETBIT(sc, TI_CPU_STATE, TI_CPUSTATE_HALT); /* Figure out the hardware revision. */ switch (CSR_READ_4(sc, TI_MISC_HOST_CTL) & TI_MHC_CHIP_REV_MASK) { case TI_REV_TIGON_I: sc->ti_hwrev = TI_HWREV_TIGON; break; case TI_REV_TIGON_II: sc->ti_hwrev = TI_HWREV_TIGON_II; break; default: device_printf(sc->ti_dev, "unsupported chip revision\n"); return (ENODEV); } /* Do special setup for Tigon 2. */ if (sc->ti_hwrev == TI_HWREV_TIGON_II) { TI_SETBIT(sc, TI_CPU_CTL_B, TI_CPUSTATE_HALT); TI_SETBIT(sc, TI_MISC_LOCAL_CTL, TI_MLC_SRAM_BANK_512K); TI_SETBIT(sc, TI_MISC_CONF, TI_MCR_SRAM_SYNCHRONOUS); } /* * We don't have firmware source for the Tigon 1, so Tigon 1 boards * can't do header splitting. */ #ifdef TI_JUMBO_HDRSPLIT if (sc->ti_hwrev != TI_HWREV_TIGON) sc->ti_hdrsplit = 1; else device_printf(sc->ti_dev, "can't do header splitting on a Tigon I board\n"); #endif /* TI_JUMBO_HDRSPLIT */ /* Set up the PCI state register. */ CSR_WRITE_4(sc, TI_PCI_STATE, TI_PCI_READ_CMD|TI_PCI_WRITE_CMD); if (sc->ti_hwrev == TI_HWREV_TIGON_II) { TI_SETBIT(sc, TI_PCI_STATE, TI_PCISTATE_USE_MEM_RD_MULT); } /* Clear the read/write max DMA parameters. */ TI_CLRBIT(sc, TI_PCI_STATE, (TI_PCISTATE_WRITE_MAXDMA| TI_PCISTATE_READ_MAXDMA)); /* Get cache line size. */ cacheline = CSR_READ_4(sc, TI_PCI_BIST) & 0xFF; /* * If the system has set enabled the PCI memory write * and invalidate command in the command register, set * the write max parameter accordingly. This is necessary * to use MWI with the Tigon 2. */ if (CSR_READ_4(sc, TI_PCI_CMDSTAT) & PCIM_CMD_MWIEN) { switch (cacheline) { case 1: case 4: case 8: case 16: case 32: case 64: break; default: /* Disable PCI memory write and invalidate. */ if (bootverbose) device_printf(sc->ti_dev, "cache line size %d" " not supported; disabling PCI MWI\n", cacheline); CSR_WRITE_4(sc, TI_PCI_CMDSTAT, CSR_READ_4(sc, TI_PCI_CMDSTAT) & ~PCIM_CMD_MWIEN); break; } } #ifdef __brokenalpha__ /* * From the Alteon sample driver: * Must insure that we do not cross an 8K (bytes) boundary * for DMA reads. Our highest limit is 1K bytes. This is a * restriction on some ALPHA platforms with early revision * 21174 PCI chipsets, such as the AlphaPC 164lx */ TI_SETBIT(sc, TI_PCI_STATE, pci_writemax|TI_PCI_READMAX_1024); #else TI_SETBIT(sc, TI_PCI_STATE, pci_writemax); #endif /* This sets the min dma param all the way up (0xff). */ TI_SETBIT(sc, TI_PCI_STATE, TI_PCISTATE_MINDMA); if (sc->ti_hdrsplit) hdrsplit = TI_OPMODE_JUMBO_HDRSPLIT; else hdrsplit = 0; /* Configure DMA variables. */ #if BYTE_ORDER == BIG_ENDIAN CSR_WRITE_4(sc, TI_GCR_OPMODE, TI_OPMODE_BYTESWAP_BD | TI_OPMODE_BYTESWAP_DATA | TI_OPMODE_WORDSWAP_BD | TI_OPMODE_WARN_ENB | TI_OPMODE_FATAL_ENB | TI_OPMODE_DONT_FRAG_JUMBO | hdrsplit); #else /* BYTE_ORDER */ CSR_WRITE_4(sc, TI_GCR_OPMODE, TI_OPMODE_BYTESWAP_DATA| TI_OPMODE_WORDSWAP_BD|TI_OPMODE_DONT_FRAG_JUMBO| TI_OPMODE_WARN_ENB|TI_OPMODE_FATAL_ENB | hdrsplit); #endif /* BYTE_ORDER */ /* * Only allow 1 DMA channel to be active at a time. * I don't think this is a good idea, but without it * the firmware racks up lots of nicDmaReadRingFull * errors. This is not compatible with hardware checksums. */ if (sc->ti_ifp->if_hwassist == 0) TI_SETBIT(sc, TI_GCR_OPMODE, TI_OPMODE_1_DMA_ACTIVE); /* Recommended settings from Tigon manual. */ CSR_WRITE_4(sc, TI_GCR_DMA_WRITECFG, TI_DMA_STATE_THRESH_8W); CSR_WRITE_4(sc, TI_GCR_DMA_READCFG, TI_DMA_STATE_THRESH_8W); if (ti_64bitslot_war(sc)) { device_printf(sc->ti_dev, "bios thinks we're in a 64 bit slot, " "but we aren't"); return (EINVAL); } return (0); } /* * Initialize the general information block and firmware, and * start the CPU(s) running. */ static int ti_gibinit(sc) struct ti_softc *sc; { struct ti_rcb *rcb; int i; struct ifnet *ifp; uint32_t rdphys; TI_LOCK_ASSERT(sc); ifp = sc->ti_ifp; rdphys = sc->ti_rdata_phys; /* Disable interrupts for now. */ CSR_WRITE_4(sc, TI_MB_HOSTINTR, 1); /* * Tell the chip where to find the general information block. * While this struct could go into >4GB memory, we allocate it in a * single slab with the other descriptors, and those don't seem to * support being located in a 64-bit region. */ CSR_WRITE_4(sc, TI_GCR_GENINFO_HI, 0); CSR_WRITE_4(sc, TI_GCR_GENINFO_LO, rdphys + TI_RD_OFF(ti_info)); /* Load the firmware into SRAM. */ ti_loadfw(sc); /* Set up the contents of the general info and ring control blocks. */ /* Set up the event ring and producer pointer. */ rcb = &sc->ti_rdata->ti_info.ti_ev_rcb; TI_HOSTADDR(rcb->ti_hostaddr) = rdphys + TI_RD_OFF(ti_event_ring); rcb->ti_flags = 0; TI_HOSTADDR(sc->ti_rdata->ti_info.ti_ev_prodidx_ptr) = rdphys + TI_RD_OFF(ti_ev_prodidx_r); sc->ti_ev_prodidx.ti_idx = 0; CSR_WRITE_4(sc, TI_GCR_EVENTCONS_IDX, 0); sc->ti_ev_saved_considx = 0; /* Set up the command ring and producer mailbox. */ rcb = &sc->ti_rdata->ti_info.ti_cmd_rcb; TI_HOSTADDR(rcb->ti_hostaddr) = TI_GCR_NIC_ADDR(TI_GCR_CMDRING); rcb->ti_flags = 0; rcb->ti_max_len = 0; for (i = 0; i < TI_CMD_RING_CNT; i++) { CSR_WRITE_4(sc, TI_GCR_CMDRING + (i * 4), 0); } CSR_WRITE_4(sc, TI_GCR_CMDCONS_IDX, 0); CSR_WRITE_4(sc, TI_MB_CMDPROD_IDX, 0); sc->ti_cmd_saved_prodidx = 0; /* * Assign the address of the stats refresh buffer. * We re-use the current stats buffer for this to * conserve memory. */ TI_HOSTADDR(sc->ti_rdata->ti_info.ti_refresh_stats_ptr) = rdphys + TI_RD_OFF(ti_info.ti_stats); /* Set up the standard receive ring. */ rcb = &sc->ti_rdata->ti_info.ti_std_rx_rcb; TI_HOSTADDR(rcb->ti_hostaddr) = rdphys + TI_RD_OFF(ti_rx_std_ring); rcb->ti_max_len = TI_FRAMELEN; rcb->ti_flags = 0; if (sc->ti_ifp->if_hwassist) rcb->ti_flags |= TI_RCB_FLAG_TCP_UDP_CKSUM | TI_RCB_FLAG_IP_CKSUM | TI_RCB_FLAG_NO_PHDR_CKSUM; rcb->ti_flags |= TI_RCB_FLAG_VLAN_ASSIST; /* Set up the jumbo receive ring. */ rcb = &sc->ti_rdata->ti_info.ti_jumbo_rx_rcb; TI_HOSTADDR(rcb->ti_hostaddr) = rdphys + TI_RD_OFF(ti_rx_jumbo_ring); #ifdef TI_PRIVATE_JUMBOS rcb->ti_max_len = TI_JUMBO_FRAMELEN; rcb->ti_flags = 0; #else rcb->ti_max_len = PAGE_SIZE; rcb->ti_flags = TI_RCB_FLAG_USE_EXT_RX_BD; #endif if (sc->ti_ifp->if_hwassist) rcb->ti_flags |= TI_RCB_FLAG_TCP_UDP_CKSUM | TI_RCB_FLAG_IP_CKSUM | TI_RCB_FLAG_NO_PHDR_CKSUM; rcb->ti_flags |= TI_RCB_FLAG_VLAN_ASSIST; /* * Set up the mini ring. Only activated on the * Tigon 2 but the slot in the config block is * still there on the Tigon 1. */ rcb = &sc->ti_rdata->ti_info.ti_mini_rx_rcb; TI_HOSTADDR(rcb->ti_hostaddr) = rdphys + TI_RD_OFF(ti_rx_mini_ring); rcb->ti_max_len = MHLEN - ETHER_ALIGN; if (sc->ti_hwrev == TI_HWREV_TIGON) rcb->ti_flags = TI_RCB_FLAG_RING_DISABLED; else rcb->ti_flags = 0; if (sc->ti_ifp->if_hwassist) rcb->ti_flags |= TI_RCB_FLAG_TCP_UDP_CKSUM | TI_RCB_FLAG_IP_CKSUM | TI_RCB_FLAG_NO_PHDR_CKSUM; rcb->ti_flags |= TI_RCB_FLAG_VLAN_ASSIST; /* * Set up the receive return ring. */ rcb = &sc->ti_rdata->ti_info.ti_return_rcb; TI_HOSTADDR(rcb->ti_hostaddr) = rdphys + TI_RD_OFF(ti_rx_return_ring); rcb->ti_flags = 0; rcb->ti_max_len = TI_RETURN_RING_CNT; TI_HOSTADDR(sc->ti_rdata->ti_info.ti_return_prodidx_ptr) = rdphys + TI_RD_OFF(ti_return_prodidx_r); /* * Set up the tx ring. Note: for the Tigon 2, we have the option * of putting the transmit ring in the host's address space and * letting the chip DMA it instead of leaving the ring in the NIC's * memory and accessing it through the shared memory region. We * do this for the Tigon 2, but it doesn't work on the Tigon 1, * so we have to revert to the shared memory scheme if we detect * a Tigon 1 chip. */ CSR_WRITE_4(sc, TI_WINBASE, TI_TX_RING_BASE); bzero((char *)sc->ti_rdata->ti_tx_ring, TI_TX_RING_CNT * sizeof(struct ti_tx_desc)); rcb = &sc->ti_rdata->ti_info.ti_tx_rcb; if (sc->ti_hwrev == TI_HWREV_TIGON) rcb->ti_flags = 0; else rcb->ti_flags = TI_RCB_FLAG_HOST_RING; rcb->ti_flags |= TI_RCB_FLAG_VLAN_ASSIST; if (sc->ti_ifp->if_hwassist) rcb->ti_flags |= TI_RCB_FLAG_TCP_UDP_CKSUM | TI_RCB_FLAG_IP_CKSUM | TI_RCB_FLAG_NO_PHDR_CKSUM; rcb->ti_max_len = TI_TX_RING_CNT; if (sc->ti_hwrev == TI_HWREV_TIGON) TI_HOSTADDR(rcb->ti_hostaddr) = TI_TX_RING_BASE; else TI_HOSTADDR(rcb->ti_hostaddr) = rdphys + TI_RD_OFF(ti_tx_ring); TI_HOSTADDR(sc->ti_rdata->ti_info.ti_tx_considx_ptr) = rdphys + TI_RD_OFF(ti_tx_considx_r); bus_dmamap_sync(sc->ti_rdata_dmat, sc->ti_rdata_dmamap, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); /* Set up tuneables */ #if 0 if (ifp->if_mtu > (ETHERMTU + ETHER_HDR_LEN + ETHER_CRC_LEN)) CSR_WRITE_4(sc, TI_GCR_RX_COAL_TICKS, (sc->ti_rx_coal_ticks / 10)); else #endif CSR_WRITE_4(sc, TI_GCR_RX_COAL_TICKS, sc->ti_rx_coal_ticks); CSR_WRITE_4(sc, TI_GCR_TX_COAL_TICKS, sc->ti_tx_coal_ticks); CSR_WRITE_4(sc, TI_GCR_STAT_TICKS, sc->ti_stat_ticks); CSR_WRITE_4(sc, TI_GCR_RX_MAX_COAL_BD, sc->ti_rx_max_coal_bds); CSR_WRITE_4(sc, TI_GCR_TX_MAX_COAL_BD, sc->ti_tx_max_coal_bds); CSR_WRITE_4(sc, TI_GCR_TX_BUFFER_RATIO, sc->ti_tx_buf_ratio); /* Turn interrupts on. */ CSR_WRITE_4(sc, TI_GCR_MASK_INTRS, 0); CSR_WRITE_4(sc, TI_MB_HOSTINTR, 0); /* Start CPU. */ TI_CLRBIT(sc, TI_CPU_STATE, (TI_CPUSTATE_HALT|TI_CPUSTATE_STEP)); return (0); } static void ti_rdata_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct ti_softc *sc; sc = arg; if (error || nseg != 1) return; /* * All of the Tigon data structures need to live at <4GB. This * cast is fine since busdma was told about this constraint. */ sc->ti_rdata_phys = segs[0].ds_addr; return; } /* * Probe for a Tigon chip. Check the PCI vendor and device IDs * against our list and return its name if we find a match. */ static int ti_probe(dev) device_t dev; { struct ti_type *t; t = ti_devs; while (t->ti_name != NULL) { if ((pci_get_vendor(dev) == t->ti_vid) && (pci_get_device(dev) == t->ti_did)) { device_set_desc(dev, t->ti_name); return (BUS_PROBE_DEFAULT); } t++; } return (ENXIO); } static int ti_attach(dev) device_t dev; { struct ifnet *ifp; struct ti_softc *sc; int error = 0, rid; u_char eaddr[6]; sc = device_get_softc(dev); sc->ti_unit = device_get_unit(dev); sc->ti_dev = dev; mtx_init(&sc->ti_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); ifmedia_init(&sc->ifmedia, IFM_IMASK, ti_ifmedia_upd, ti_ifmedia_sts); ifp = sc->ti_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "can not if_alloc()\n"); error = ENOSPC; goto fail; } sc->ti_ifp->if_capabilities = IFCAP_HWCSUM | IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_MTU; sc->ti_ifp->if_capenable = sc->ti_ifp->if_capabilities; /* * Map control/status registers. */ pci_enable_busmaster(dev); rid = TI_PCI_LOMEM; sc->ti_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE|PCI_RF_DENSE); if (sc->ti_res == NULL) { device_printf(dev, "couldn't map memory\n"); error = ENXIO; goto fail; } sc->ti_btag = rman_get_bustag(sc->ti_res); sc->ti_bhandle = rman_get_bushandle(sc->ti_res); /* Allocate interrupt */ rid = 0; sc->ti_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->ti_irq == NULL) { device_printf(dev, "couldn't map interrupt\n"); error = ENXIO; goto fail; } if (ti_chipinit(sc)) { device_printf(dev, "chip initialization failed\n"); error = ENXIO; goto fail; } /* Zero out the NIC's on-board SRAM. */ ti_mem_zero(sc, 0x2000, 0x100000 - 0x2000); /* Init again -- zeroing memory may have clobbered some registers. */ if (ti_chipinit(sc)) { device_printf(dev, "chip initialization failed\n"); error = ENXIO; goto fail; } /* * Get station address from the EEPROM. Note: the manual states * that the MAC address is at offset 0x8c, however the data is * stored as two longwords (since that's how it's loaded into * the NIC). This means the MAC address is actually preceded * by two zero bytes. We need to skip over those. */ if (ti_read_eeprom(sc, eaddr, TI_EE_MAC_OFFSET + 2, ETHER_ADDR_LEN)) { device_printf(dev, "failed to read station address\n"); error = ENXIO; goto fail; } /* Allocate the general information block and ring buffers. */ if (bus_dma_tag_create(NULL, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT,/* maxsize */ 0, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT,/* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ti_parent_dmat) != 0) { device_printf(dev, "Failed to allocate parent dmat\n"); error = ENOMEM; goto fail; } if (bus_dma_tag_create(sc->ti_parent_dmat, /* parent */ PAGE_SIZE, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sizeof(struct ti_ring_data), /* maxsize */ 1, /* nsegments */ sizeof(struct ti_ring_data), /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ti_rdata_dmat) != 0) { device_printf(dev, "Failed to allocate rdata dmat\n"); error = ENOMEM; goto fail; } if (bus_dmamem_alloc(sc->ti_rdata_dmat, (void**)&sc->ti_rdata, BUS_DMA_NOWAIT, &sc->ti_rdata_dmamap) != 0) { device_printf(dev, "Failed to allocate rdata memory\n"); error = ENOMEM; goto fail; } if (bus_dmamap_load(sc->ti_rdata_dmat, sc->ti_rdata_dmamap, sc->ti_rdata, sizeof(struct ti_ring_data), ti_rdata_cb, sc, BUS_DMA_NOWAIT) != 0) { device_printf(dev, "Failed to load rdata segments\n"); error = ENOMEM; goto fail; } bzero(sc->ti_rdata, sizeof(struct ti_ring_data)); /* Try to allocate memory for jumbo buffers. */ if (ti_alloc_jumbo_mem(sc)) { device_printf(dev, "jumbo buffer allocation failed\n"); error = ENXIO; goto fail; } if (bus_dma_tag_create(sc->ti_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES * TI_MAXTXSEGS,/* maxsize */ TI_MAXTXSEGS, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ti_mbuftx_dmat) != 0) { device_printf(dev, "Failed to allocate rdata dmat\n"); error = ENOMEM; goto fail; } if (bus_dma_tag_create(sc->ti_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, /* maxsize */ 1, /* nsegments */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ti_mbufrx_dmat) != 0) { device_printf(dev, "Failed to allocate rdata dmat\n"); error = ENOMEM; goto fail; } if (ti_alloc_dmamaps(sc)) { device_printf(dev, "dma map creation failed\n"); error = ENXIO; goto fail; } /* * We really need a better way to tell a 1000baseTX card * from a 1000baseSX one, since in theory there could be * OEMed 1000baseTX cards from lame vendors who aren't * clever enough to change the PCI ID. For the moment * though, the AceNIC is the only copper card available. */ if (pci_get_vendor(dev) == ALT_VENDORID && pci_get_device(dev) == ALT_DEVICEID_ACENIC_COPPER) sc->ti_copper = 1; /* Ok, it's not the only copper card available. */ if (pci_get_vendor(dev) == NG_VENDORID && pci_get_device(dev) == NG_DEVICEID_GA620T) sc->ti_copper = 1; /* Set default tuneable values. */ sc->ti_stat_ticks = 2 * TI_TICKS_PER_SEC; #if 0 sc->ti_rx_coal_ticks = TI_TICKS_PER_SEC / 5000; #endif sc->ti_rx_coal_ticks = 170; sc->ti_tx_coal_ticks = TI_TICKS_PER_SEC / 500; sc->ti_rx_max_coal_bds = 64; #if 0 sc->ti_tx_max_coal_bds = 128; #endif sc->ti_tx_max_coal_bds = 32; sc->ti_tx_buf_ratio = 21; /* Set up ifnet structure */ ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = ti_ioctl; ifp->if_start = ti_start; ifp->if_watchdog = ti_watchdog; ifp->if_init = ti_init; ifp->if_mtu = ETHERMTU; ifp->if_snd.ifq_maxlen = TI_TX_RING_CNT - 1; /* Set up ifmedia support. */ if (sc->ti_copper) { /* * Copper cards allow manual 10/100 mode selection, * but not manual 1000baseTX mode selection. Why? * Becuase currently there's no way to specify the * master/slave setting through the firmware interface, * so Alteon decided to just bag it and handle it * via autonegotiation. */ ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_10_T, 0, NULL); ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_10_T|IFM_FDX, 0, NULL); ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_100_TX, 0, NULL); ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_100_TX|IFM_FDX, 0, NULL); ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_1000_T, 0, NULL); ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_1000_T|IFM_FDX, 0, NULL); } else { /* Fiber cards don't support 10/100 modes. */ ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_1000_SX, 0, NULL); ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_1000_SX|IFM_FDX, 0, NULL); } ifmedia_add(&sc->ifmedia, IFM_ETHER|IFM_AUTO, 0, NULL); ifmedia_set(&sc->ifmedia, IFM_ETHER|IFM_AUTO); /* * We're assuming here that card initialization is a sequential * thing. If it isn't, multiple cards probing at the same time * could stomp on the list of softcs here. */ /* Register the device */ sc->dev = make_dev(&ti_cdevsw, sc->ti_unit, UID_ROOT, GID_OPERATOR, 0600, "ti%d", sc->ti_unit); sc->dev->si_drv1 = sc; /* * Call MI attach routine. */ ether_ifattach(ifp, eaddr); /* Hook interrupt last to avoid having to lock softc */ error = bus_setup_intr(dev, sc->ti_irq, INTR_TYPE_NET|INTR_MPSAFE, ti_intr, sc, &sc->ti_intrhand); if (error) { device_printf(dev, "couldn't set up irq\n"); goto fail; } fail: if (error) ti_detach(dev); return (error); } /* * Shutdown hardware and free up resources. This can be called any * time after the mutex has been initialized. It is called in both * the error case in attach and the normal detach case so it needs * to be careful about only freeing resources that have actually been * allocated. */ static int ti_detach(dev) device_t dev; { struct ti_softc *sc; struct ifnet *ifp; int attached; sc = device_get_softc(dev); if (sc->dev) destroy_dev(sc->dev); KASSERT(mtx_initialized(&sc->ti_mtx), ("ti mutex not initialized")); attached = device_is_attached(dev); TI_LOCK(sc); ifp = sc->ti_ifp; if (attached) ti_stop(sc); TI_UNLOCK(sc); if (attached) ether_ifdetach(ifp); /* These should only be active if attach succeeded */ if (attached) bus_generic_detach(dev); ti_free_dmamaps(sc); ifmedia_removeall(&sc->ifmedia); #ifdef TI_PRIVATE_JUMBOS if (sc->ti_cdata.ti_jumbo_buf) bus_dmamem_free(sc->ti_jumbo_dmat, sc->ti_cdata.ti_jumbo_buf, sc->ti_jumbo_dmamap); #endif if (sc->ti_jumbo_dmat) bus_dma_tag_destroy(sc->ti_jumbo_dmat); if (sc->ti_mbuftx_dmat) bus_dma_tag_destroy(sc->ti_mbuftx_dmat); if (sc->ti_mbufrx_dmat) bus_dma_tag_destroy(sc->ti_mbufrx_dmat); if (sc->ti_rdata) bus_dmamem_free(sc->ti_rdata_dmat, sc->ti_rdata, sc->ti_rdata_dmamap); if (sc->ti_rdata_dmat) bus_dma_tag_destroy(sc->ti_rdata_dmat); if (sc->ti_parent_dmat) bus_dma_tag_destroy(sc->ti_parent_dmat); if (sc->ti_intrhand) bus_teardown_intr(dev, sc->ti_irq, sc->ti_intrhand); if (sc->ti_irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->ti_irq); if (sc->ti_res) { bus_release_resource(dev, SYS_RES_MEMORY, TI_PCI_LOMEM, sc->ti_res); } if (ifp) if_free(ifp); mtx_destroy(&sc->ti_mtx); return (0); } #ifdef TI_JUMBO_HDRSPLIT /* * If hdr_len is 0, that means that header splitting wasn't done on * this packet for some reason. The two most likely reasons are that * the protocol isn't a supported protocol for splitting, or this * packet had a fragment offset that wasn't 0. * * The header length, if it is non-zero, will always be the length of * the headers on the packet, but that length could be longer than the * first mbuf. So we take the minimum of the two as the actual * length. */ static __inline void ti_hdr_split(struct mbuf *top, int hdr_len, int pkt_len, int idx) { int i = 0; int lengths[4] = {0, 0, 0, 0}; struct mbuf *m, *mp; if (hdr_len != 0) top->m_len = min(hdr_len, top->m_len); pkt_len -= top->m_len; lengths[i++] = top->m_len; mp = top; for (m = top->m_next; m && pkt_len; m = m->m_next) { m->m_len = m->m_ext.ext_size = min(m->m_len, pkt_len); pkt_len -= m->m_len; lengths[i++] = m->m_len; mp = m; } #if 0 if (hdr_len != 0) printf("got split packet: "); else printf("got non-split packet: "); printf("%d,%d,%d,%d = %d\n", lengths[0], lengths[1], lengths[2], lengths[3], lengths[0] + lengths[1] + lengths[2] + lengths[3]); #endif if (pkt_len) panic("header splitting didn't"); if (m) { m_freem(m); mp->m_next = NULL; } if (mp->m_next != NULL) panic("ti_hdr_split: last mbuf in chain should be null"); } #endif /* TI_JUMBO_HDRSPLIT */ /* * Frame reception handling. This is called if there's a frame * on the receive return list. * * Note: we have to be able to handle three possibilities here: * 1) the frame is from the mini receive ring (can only happen) * on Tigon 2 boards) * 2) the frame is from the jumbo recieve ring * 3) the frame is from the standard receive ring */ static void ti_rxeof(sc) struct ti_softc *sc; { bus_dmamap_t map; struct ifnet *ifp; struct ti_cmd_desc cmd; TI_LOCK_ASSERT(sc); ifp = sc->ti_ifp; while (sc->ti_rx_saved_considx != sc->ti_return_prodidx.ti_idx) { struct ti_rx_desc *cur_rx; u_int32_t rxidx; struct mbuf *m = NULL; u_int16_t vlan_tag = 0; int have_tag = 0; cur_rx = &sc->ti_rdata->ti_rx_return_ring[sc->ti_rx_saved_considx]; rxidx = cur_rx->ti_idx; TI_INC(sc->ti_rx_saved_considx, TI_RETURN_RING_CNT); if (cur_rx->ti_flags & TI_BDFLAG_VLAN_TAG) { have_tag = 1; vlan_tag = cur_rx->ti_vlan_tag & 0xfff; } if (cur_rx->ti_flags & TI_BDFLAG_JUMBO_RING) { TI_INC(sc->ti_jumbo, TI_JUMBO_RX_RING_CNT); m = sc->ti_cdata.ti_rx_jumbo_chain[rxidx]; sc->ti_cdata.ti_rx_jumbo_chain[rxidx] = NULL; map = sc->ti_cdata.ti_rx_jumbo_maps[rxidx]; bus_dmamap_sync(sc->ti_jumbo_dmat, map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->ti_jumbo_dmat, map); if (cur_rx->ti_flags & TI_BDFLAG_ERROR) { ifp->if_ierrors++; ti_newbuf_jumbo(sc, sc->ti_jumbo, m); continue; } if (ti_newbuf_jumbo(sc, sc->ti_jumbo, NULL) == ENOBUFS) { ifp->if_ierrors++; ti_newbuf_jumbo(sc, sc->ti_jumbo, m); continue; } #ifdef TI_PRIVATE_JUMBOS m->m_len = cur_rx->ti_len; #else /* TI_PRIVATE_JUMBOS */ #ifdef TI_JUMBO_HDRSPLIT if (sc->ti_hdrsplit) ti_hdr_split(m, TI_HOSTADDR(cur_rx->ti_addr), cur_rx->ti_len, rxidx); else #endif /* TI_JUMBO_HDRSPLIT */ m_adj(m, cur_rx->ti_len - m->m_pkthdr.len); #endif /* TI_PRIVATE_JUMBOS */ } else if (cur_rx->ti_flags & TI_BDFLAG_MINI_RING) { TI_INC(sc->ti_mini, TI_MINI_RX_RING_CNT); m = sc->ti_cdata.ti_rx_mini_chain[rxidx]; sc->ti_cdata.ti_rx_mini_chain[rxidx] = NULL; map = sc->ti_cdata.ti_rx_mini_maps[rxidx]; bus_dmamap_sync(sc->ti_mbufrx_dmat, map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->ti_mbufrx_dmat, map); if (cur_rx->ti_flags & TI_BDFLAG_ERROR) { ifp->if_ierrors++; ti_newbuf_mini(sc, sc->ti_mini, m); continue; } if (ti_newbuf_mini(sc, sc->ti_mini, NULL) == ENOBUFS) { ifp->if_ierrors++; ti_newbuf_mini(sc, sc->ti_mini, m); continue; } m->m_len = cur_rx->ti_len; } else { TI_INC(sc->ti_std, TI_STD_RX_RING_CNT); m = sc->ti_cdata.ti_rx_std_chain[rxidx]; sc->ti_cdata.ti_rx_std_chain[rxidx] = NULL; map = sc->ti_cdata.ti_rx_std_maps[rxidx]; bus_dmamap_sync(sc->ti_mbufrx_dmat, map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->ti_mbufrx_dmat, map); if (cur_rx->ti_flags & TI_BDFLAG_ERROR) { ifp->if_ierrors++; ti_newbuf_std(sc, sc->ti_std, m); continue; } if (ti_newbuf_std(sc, sc->ti_std, NULL) == ENOBUFS) { ifp->if_ierrors++; ti_newbuf_std(sc, sc->ti_std, m); continue; } m->m_len = cur_rx->ti_len; } m->m_pkthdr.len = cur_rx->ti_len; ifp->if_ipackets++; m->m_pkthdr.rcvif = ifp; if (ifp->if_hwassist) { m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED | CSUM_DATA_VALID; if ((cur_rx->ti_ip_cksum ^ 0xffff) == 0) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; m->m_pkthdr.csum_data = cur_rx->ti_tcp_udp_cksum; } /* * If we received a packet with a vlan tag, * tag it before passing the packet upward. */ if (have_tag) { - VLAN_INPUT_TAG(ifp, m, vlan_tag); - if (m == NULL) - continue; + m->m_pkthdr.ether_vtag = vlan_tag; + m->m_flags |= M_VLANTAG; } TI_UNLOCK(sc); (*ifp->if_input)(ifp, m); TI_LOCK(sc); } /* Only necessary on the Tigon 1. */ if (sc->ti_hwrev == TI_HWREV_TIGON) CSR_WRITE_4(sc, TI_GCR_RXRETURNCONS_IDX, sc->ti_rx_saved_considx); TI_UPDATE_STDPROD(sc, sc->ti_std); TI_UPDATE_MINIPROD(sc, sc->ti_mini); TI_UPDATE_JUMBOPROD(sc, sc->ti_jumbo); } static void ti_txeof(sc) struct ti_softc *sc; { struct ti_txdesc *txd; struct ti_tx_desc txdesc; struct ti_tx_desc *cur_tx = NULL; struct ifnet *ifp; int idx; ifp = sc->ti_ifp; txd = STAILQ_FIRST(&sc->ti_cdata.ti_txbusyq); if (txd == NULL) return; /* * Go through our tx ring and free mbufs for those * frames that have been sent. */ for (idx = sc->ti_tx_saved_considx; idx != sc->ti_tx_considx.ti_idx; TI_INC(idx, TI_TX_RING_CNT)) { if (sc->ti_hwrev == TI_HWREV_TIGON) { ti_mem_read(sc, TI_TX_RING_BASE + idx * sizeof(txdesc), sizeof(txdesc), &txdesc); cur_tx = &txdesc; } else cur_tx = &sc->ti_rdata->ti_tx_ring[idx]; sc->ti_txcnt--; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if ((cur_tx->ti_flags & TI_BDFLAG_END) == 0) continue; bus_dmamap_sync(sc->ti_mbuftx_dmat, txd->tx_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->ti_mbuftx_dmat, txd->tx_dmamap); ifp->if_opackets++; m_freem(txd->tx_m); txd->tx_m = NULL; STAILQ_REMOVE_HEAD(&sc->ti_cdata.ti_txbusyq, tx_q); STAILQ_INSERT_TAIL(&sc->ti_cdata.ti_txfreeq, txd, tx_q); txd = STAILQ_FIRST(&sc->ti_cdata.ti_txbusyq); } sc->ti_tx_saved_considx = idx; ifp->if_timer = sc->ti_txcnt > 0 ? 5 : 0; } static void ti_intr(xsc) void *xsc; { struct ti_softc *sc; struct ifnet *ifp; sc = xsc; TI_LOCK(sc); ifp = sc->ti_ifp; /*#ifdef notdef*/ /* Avoid this for now -- checking this register is expensive. */ /* Make sure this is really our interrupt. */ if (!(CSR_READ_4(sc, TI_MISC_HOST_CTL) & TI_MHC_INTSTATE)) { TI_UNLOCK(sc); return; } /*#endif*/ /* Ack interrupt and stop others from occuring. */ CSR_WRITE_4(sc, TI_MB_HOSTINTR, 1); if (ifp->if_drv_flags & IFF_DRV_RUNNING) { /* Check RX return ring producer/consumer */ ti_rxeof(sc); /* Check TX ring producer/consumer */ ti_txeof(sc); } ti_handle_events(sc); /* Re-enable interrupts. */ CSR_WRITE_4(sc, TI_MB_HOSTINTR, 0); if (ifp->if_drv_flags & IFF_DRV_RUNNING && ifp->if_snd.ifq_head != NULL) ti_start_locked(ifp); TI_UNLOCK(sc); } static void ti_stats_update(sc) struct ti_softc *sc; { struct ifnet *ifp; ifp = sc->ti_ifp; bus_dmamap_sync(sc->ti_rdata_dmat, sc->ti_rdata_dmamap, BUS_DMASYNC_POSTREAD); ifp->if_collisions += (sc->ti_rdata->ti_info.ti_stats.dot3StatsSingleCollisionFrames + sc->ti_rdata->ti_info.ti_stats.dot3StatsMultipleCollisionFrames + sc->ti_rdata->ti_info.ti_stats.dot3StatsExcessiveCollisions + sc->ti_rdata->ti_info.ti_stats.dot3StatsLateCollisions) - ifp->if_collisions; bus_dmamap_sync(sc->ti_rdata_dmat, sc->ti_rdata_dmamap, BUS_DMASYNC_PREREAD); } /* * Encapsulate an mbuf chain in the tx ring by coupling the mbuf data * pointers to descriptors. */ static int ti_encap(sc, m_head) struct ti_softc *sc; struct mbuf **m_head; { struct ti_txdesc *txd; struct ti_tx_desc *f; struct ti_tx_desc txdesc; struct mbuf *m; - struct m_tag *mtag; bus_dma_segment_t txsegs[TI_MAXTXSEGS]; u_int16_t csum_flags; int error, frag, i, nseg; if ((txd = STAILQ_FIRST(&sc->ti_cdata.ti_txfreeq)) == NULL) return (ENOBUFS); error = bus_dmamap_load_mbuf_sg(sc->ti_mbuftx_dmat, txd->tx_dmamap, *m_head, txsegs, &nseg, 0); if (error == EFBIG) { m = m_defrag(*m_head, M_DONTWAIT); if (m == NULL) { m_freem(*m_head); *m_head = NULL; return (ENOMEM); } *m_head = m; error = bus_dmamap_load_mbuf_sg(sc->ti_mbuftx_dmat, txd->tx_dmamap, *m_head, txsegs, &nseg, 0); if (error) { m_freem(*m_head); *m_head = NULL; return (error); } } else if (error != 0) return (error); if (nseg == 0) { m_freem(*m_head); *m_head = NULL; return (EIO); } if (sc->ti_txcnt + nseg >= TI_TX_RING_CNT) { bus_dmamap_unload(sc->ti_mbuftx_dmat, txd->tx_dmamap); return (ENOBUFS); } m = *m_head; csum_flags = 0; if (m->m_pkthdr.csum_flags) { if (m->m_pkthdr.csum_flags & CSUM_IP) csum_flags |= TI_BDFLAG_IP_CKSUM; if (m->m_pkthdr.csum_flags & (CSUM_TCP | CSUM_UDP)) csum_flags |= TI_BDFLAG_TCP_UDP_CKSUM; if (m->m_flags & M_LASTFRAG) csum_flags |= TI_BDFLAG_IP_FRAG_END; else if (m->m_flags & M_FRAG) csum_flags |= TI_BDFLAG_IP_FRAG; } bus_dmamap_sync(sc->ti_mbuftx_dmat, txd->tx_dmamap, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(sc->ti_rdata_dmat, sc->ti_rdata_dmamap, BUS_DMASYNC_PREWRITE); - mtag = VLAN_OUTPUT_TAG(sc->ti_ifp, m); frag = sc->ti_tx_saved_prodidx; for (i = 0; i < nseg; i++) { if (sc->ti_hwrev == TI_HWREV_TIGON) { bzero(&txdesc, sizeof(txdesc)); f = &txdesc; } else f = &sc->ti_rdata->ti_tx_ring[frag]; ti_hostaddr64(&f->ti_addr, txsegs[i].ds_addr); f->ti_len = txsegs[i].ds_len; f->ti_flags = csum_flags; - if (mtag != NULL) { + if (m->m_flags & M_VLANTAG) { f->ti_flags |= TI_BDFLAG_VLAN_TAG; - f->ti_vlan_tag = VLAN_TAG_VALUE(mtag) & 0xfff; + f->ti_vlan_tag = m->m_pkthdr.ether_vtag & 0xfff; } else { f->ti_vlan_tag = 0; } if (sc->ti_hwrev == TI_HWREV_TIGON) ti_mem_write(sc, TI_TX_RING_BASE + frag * sizeof(txdesc), sizeof(txdesc), &txdesc); TI_INC(frag, TI_TX_RING_CNT); } sc->ti_tx_saved_prodidx = frag; /* set TI_BDFLAG_END on the last descriptor */ frag = (frag + TI_TX_RING_CNT - 1) % TI_TX_RING_CNT; if (sc->ti_hwrev == TI_HWREV_TIGON) { txdesc.ti_flags |= TI_BDFLAG_END; ti_mem_write(sc, TI_TX_RING_BASE + frag * sizeof(txdesc), sizeof(txdesc), &txdesc); } else sc->ti_rdata->ti_tx_ring[frag].ti_flags |= TI_BDFLAG_END; STAILQ_REMOVE_HEAD(&sc->ti_cdata.ti_txfreeq, tx_q); STAILQ_INSERT_TAIL(&sc->ti_cdata.ti_txbusyq, txd, tx_q); txd->tx_m = m; sc->ti_txcnt += nseg; return (0); } static void ti_start(ifp) struct ifnet *ifp; { struct ti_softc *sc; sc = ifp->if_softc; TI_LOCK(sc); ti_start_locked(ifp); TI_UNLOCK(sc); } /* * Main transmit routine. To avoid having to do mbuf copies, we put pointers * to the mbuf data regions directly in the transmit descriptors. */ static void ti_start_locked(ifp) struct ifnet *ifp; { struct ti_softc *sc; struct mbuf *m_head = NULL; int enq = 0; sc = ifp->if_softc; for (; ifp->if_snd.ifq_head != NULL && sc->ti_txcnt < (TI_TX_RING_CNT - 16);) { IF_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) break; /* * XXX * safety overkill. If this is a fragmented packet chain * with delayed TCP/UDP checksums, then only encapsulate * it if we have enough descriptors to handle the entire * chain at once. * (paranoia -- may not actually be needed) */ if (m_head->m_flags & M_FIRSTFRAG && m_head->m_pkthdr.csum_flags & (CSUM_DELAY_DATA)) { if ((TI_TX_RING_CNT - sc->ti_txcnt) < m_head->m_pkthdr.csum_data + 16) { IF_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } } /* * Pack the data into the transmit ring. If we * don't have room, set the OACTIVE flag and wait * for the NIC to drain the ring. */ if (ti_encap(sc, &m_head)) { if (m_head == NULL) break; IF_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } enq++; /* * If there's a BPF listener, bounce a copy of this frame * to him. */ BPF_MTAP(ifp, m_head); } if (enq > 0) { /* Transmit */ CSR_WRITE_4(sc, TI_MB_SENDPROD_IDX, sc->ti_tx_saved_prodidx); /* * Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; } } static void ti_init(xsc) void *xsc; { struct ti_softc *sc; sc = xsc; TI_LOCK(sc); ti_init_locked(sc); TI_UNLOCK(sc); } static void ti_init_locked(xsc) void *xsc; { struct ti_softc *sc = xsc; /* Cancel pending I/O and flush buffers. */ ti_stop(sc); /* Init the gen info block, ring control blocks and firmware. */ if (ti_gibinit(sc)) { device_printf(sc->ti_dev, "initialization failure\n"); return; } } static void ti_init2(sc) struct ti_softc *sc; { struct ti_cmd_desc cmd; struct ifnet *ifp; u_int8_t *ea; struct ifmedia *ifm; int tmp; TI_LOCK_ASSERT(sc); ifp = sc->ti_ifp; /* Specify MTU and interface index. */ CSR_WRITE_4(sc, TI_GCR_IFINDEX, sc->ti_unit); CSR_WRITE_4(sc, TI_GCR_IFMTU, ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN + ETHER_VLAN_ENCAP_LEN); TI_DO_CMD(TI_CMD_UPDATE_GENCOM, 0, 0); /* Load our MAC address. */ ea = IF_LLADDR(sc->ti_ifp); CSR_WRITE_4(sc, TI_GCR_PAR0, (ea[0] << 8) | ea[1]); CSR_WRITE_4(sc, TI_GCR_PAR1, (ea[2] << 24) | (ea[3] << 16) | (ea[4] << 8) | ea[5]); TI_DO_CMD(TI_CMD_SET_MAC_ADDR, 0, 0); /* Enable or disable promiscuous mode as needed. */ if (ifp->if_flags & IFF_PROMISC) { TI_DO_CMD(TI_CMD_SET_PROMISC_MODE, TI_CMD_CODE_PROMISC_ENB, 0); } else { TI_DO_CMD(TI_CMD_SET_PROMISC_MODE, TI_CMD_CODE_PROMISC_DIS, 0); } /* Program multicast filter. */ ti_setmulti(sc); /* * If this is a Tigon 1, we should tell the * firmware to use software packet filtering. */ if (sc->ti_hwrev == TI_HWREV_TIGON) { TI_DO_CMD(TI_CMD_FDR_FILTERING, TI_CMD_CODE_FILT_ENB, 0); } /* Init RX ring. */ ti_init_rx_ring_std(sc); /* Init jumbo RX ring. */ if (ifp->if_mtu > (ETHERMTU + ETHER_HDR_LEN + ETHER_CRC_LEN)) ti_init_rx_ring_jumbo(sc); /* * If this is a Tigon 2, we can also configure the * mini ring. */ if (sc->ti_hwrev == TI_HWREV_TIGON_II) ti_init_rx_ring_mini(sc); CSR_WRITE_4(sc, TI_GCR_RXRETURNCONS_IDX, 0); sc->ti_rx_saved_considx = 0; /* Init TX ring. */ ti_init_tx_ring(sc); /* Tell firmware we're alive. */ TI_DO_CMD(TI_CMD_HOST_STATE, TI_CMD_CODE_STACK_UP, 0); /* Enable host interrupts. */ CSR_WRITE_4(sc, TI_MB_HOSTINTR, 0); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; /* * Make sure to set media properly. We have to do this * here since we have to issue commands in order to set * the link negotiation and we can't issue commands until * the firmware is running. */ ifm = &sc->ifmedia; tmp = ifm->ifm_media; ifm->ifm_media = ifm->ifm_cur->ifm_media; ti_ifmedia_upd(ifp); ifm->ifm_media = tmp; } /* * Set media options. */ static int ti_ifmedia_upd(ifp) struct ifnet *ifp; { struct ti_softc *sc; struct ifmedia *ifm; struct ti_cmd_desc cmd; u_int32_t flowctl; sc = ifp->if_softc; ifm = &sc->ifmedia; if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER) return (EINVAL); flowctl = 0; switch (IFM_SUBTYPE(ifm->ifm_media)) { case IFM_AUTO: /* * Transmit flow control doesn't work on the Tigon 1. */ flowctl = TI_GLNK_RX_FLOWCTL_Y; /* * Transmit flow control can also cause problems on the * Tigon 2, apparantly with both the copper and fiber * boards. The symptom is that the interface will just * hang. This was reproduced with Alteon 180 switches. */ #if 0 if (sc->ti_hwrev != TI_HWREV_TIGON) flowctl |= TI_GLNK_TX_FLOWCTL_Y; #endif CSR_WRITE_4(sc, TI_GCR_GLINK, TI_GLNK_PREF|TI_GLNK_1000MB| TI_GLNK_FULL_DUPLEX| flowctl | TI_GLNK_AUTONEGENB|TI_GLNK_ENB); flowctl = TI_LNK_RX_FLOWCTL_Y; #if 0 if (sc->ti_hwrev != TI_HWREV_TIGON) flowctl |= TI_LNK_TX_FLOWCTL_Y; #endif CSR_WRITE_4(sc, TI_GCR_LINK, TI_LNK_100MB|TI_LNK_10MB| TI_LNK_FULL_DUPLEX|TI_LNK_HALF_DUPLEX| flowctl | TI_LNK_AUTONEGENB|TI_LNK_ENB); TI_DO_CMD(TI_CMD_LINK_NEGOTIATION, TI_CMD_CODE_NEGOTIATE_BOTH, 0); break; case IFM_1000_SX: case IFM_1000_T: flowctl = TI_GLNK_RX_FLOWCTL_Y; #if 0 if (sc->ti_hwrev != TI_HWREV_TIGON) flowctl |= TI_GLNK_TX_FLOWCTL_Y; #endif CSR_WRITE_4(sc, TI_GCR_GLINK, TI_GLNK_PREF|TI_GLNK_1000MB| flowctl |TI_GLNK_ENB); CSR_WRITE_4(sc, TI_GCR_LINK, 0); if ((ifm->ifm_media & IFM_GMASK) == IFM_FDX) { TI_SETBIT(sc, TI_GCR_GLINK, TI_GLNK_FULL_DUPLEX); } TI_DO_CMD(TI_CMD_LINK_NEGOTIATION, TI_CMD_CODE_NEGOTIATE_GIGABIT, 0); break; case IFM_100_FX: case IFM_10_FL: case IFM_100_TX: case IFM_10_T: flowctl = TI_LNK_RX_FLOWCTL_Y; #if 0 if (sc->ti_hwrev != TI_HWREV_TIGON) flowctl |= TI_LNK_TX_FLOWCTL_Y; #endif CSR_WRITE_4(sc, TI_GCR_GLINK, 0); CSR_WRITE_4(sc, TI_GCR_LINK, TI_LNK_ENB|TI_LNK_PREF|flowctl); if (IFM_SUBTYPE(ifm->ifm_media) == IFM_100_FX || IFM_SUBTYPE(ifm->ifm_media) == IFM_100_TX) { TI_SETBIT(sc, TI_GCR_LINK, TI_LNK_100MB); } else { TI_SETBIT(sc, TI_GCR_LINK, TI_LNK_10MB); } if ((ifm->ifm_media & IFM_GMASK) == IFM_FDX) { TI_SETBIT(sc, TI_GCR_LINK, TI_LNK_FULL_DUPLEX); } else { TI_SETBIT(sc, TI_GCR_LINK, TI_LNK_HALF_DUPLEX); } TI_DO_CMD(TI_CMD_LINK_NEGOTIATION, TI_CMD_CODE_NEGOTIATE_10_100, 0); break; } return (0); } /* * Report current media status. */ static void ti_ifmedia_sts(ifp, ifmr) struct ifnet *ifp; struct ifmediareq *ifmr; { struct ti_softc *sc; u_int32_t media = 0; sc = ifp->if_softc; ifmr->ifm_status = IFM_AVALID; ifmr->ifm_active = IFM_ETHER; if (sc->ti_linkstat == TI_EV_CODE_LINK_DOWN) return; ifmr->ifm_status |= IFM_ACTIVE; if (sc->ti_linkstat == TI_EV_CODE_GIG_LINK_UP) { media = CSR_READ_4(sc, TI_GCR_GLINK_STAT); if (sc->ti_copper) ifmr->ifm_active |= IFM_1000_T; else ifmr->ifm_active |= IFM_1000_SX; if (media & TI_GLNK_FULL_DUPLEX) ifmr->ifm_active |= IFM_FDX; else ifmr->ifm_active |= IFM_HDX; } else if (sc->ti_linkstat == TI_EV_CODE_LINK_UP) { media = CSR_READ_4(sc, TI_GCR_LINK_STAT); if (sc->ti_copper) { if (media & TI_LNK_100MB) ifmr->ifm_active |= IFM_100_TX; if (media & TI_LNK_10MB) ifmr->ifm_active |= IFM_10_T; } else { if (media & TI_LNK_100MB) ifmr->ifm_active |= IFM_100_FX; if (media & TI_LNK_10MB) ifmr->ifm_active |= IFM_10_FL; } if (media & TI_LNK_FULL_DUPLEX) ifmr->ifm_active |= IFM_FDX; if (media & TI_LNK_HALF_DUPLEX) ifmr->ifm_active |= IFM_HDX; } } static int ti_ioctl(ifp, command, data) struct ifnet *ifp; u_long command; caddr_t data; { struct ti_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; int mask, error = 0; struct ti_cmd_desc cmd; switch (command) { case SIOCSIFMTU: TI_LOCK(sc); if (ifr->ifr_mtu > TI_JUMBO_MTU) error = EINVAL; else { ifp->if_mtu = ifr->ifr_mtu; ti_init_locked(sc); } TI_UNLOCK(sc); break; case SIOCSIFFLAGS: TI_LOCK(sc); if (ifp->if_flags & IFF_UP) { /* * If only the state of the PROMISC flag changed, * then just use the 'set promisc mode' command * instead of reinitializing the entire NIC. Doing * a full re-init means reloading the firmware and * waiting for it to start up, which may take a * second or two. */ if (ifp->if_drv_flags & IFF_DRV_RUNNING && ifp->if_flags & IFF_PROMISC && !(sc->ti_if_flags & IFF_PROMISC)) { TI_DO_CMD(TI_CMD_SET_PROMISC_MODE, TI_CMD_CODE_PROMISC_ENB, 0); } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && !(ifp->if_flags & IFF_PROMISC) && sc->ti_if_flags & IFF_PROMISC) { TI_DO_CMD(TI_CMD_SET_PROMISC_MODE, TI_CMD_CODE_PROMISC_DIS, 0); } else ti_init_locked(sc); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { ti_stop(sc); } } sc->ti_if_flags = ifp->if_flags; TI_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: TI_LOCK(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) ti_setmulti(sc); TI_UNLOCK(sc); break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &sc->ifmedia, command); break; case SIOCSIFCAP: TI_LOCK(sc); mask = ifr->ifr_reqcap ^ ifp->if_capenable; if (mask & IFCAP_HWCSUM) { if (IFCAP_HWCSUM & ifp->if_capenable) ifp->if_capenable &= ~IFCAP_HWCSUM; else ifp->if_capenable |= IFCAP_HWCSUM; if (ifp->if_drv_flags & IFF_DRV_RUNNING) ti_init_locked(sc); } TI_UNLOCK(sc); break; default: error = ether_ioctl(ifp, command, data); break; } return (error); } static int ti_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct ti_softc *sc; sc = dev->si_drv1; if (sc == NULL) return (ENODEV); TI_LOCK(sc); sc->ti_flags |= TI_FLAG_DEBUGING; TI_UNLOCK(sc); return (0); } static int ti_close(struct cdev *dev, int flag, int fmt, struct thread *td) { struct ti_softc *sc; sc = dev->si_drv1; if (sc == NULL) return (ENODEV); TI_LOCK(sc); sc->ti_flags &= ~TI_FLAG_DEBUGING; TI_UNLOCK(sc); return (0); } /* * This ioctl routine goes along with the Tigon character device. */ static int ti_ioctl2(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { int error; struct ti_softc *sc; sc = dev->si_drv1; if (sc == NULL) return (ENODEV); error = 0; switch (cmd) { case TIIOCGETSTATS: { struct ti_stats *outstats; outstats = (struct ti_stats *)addr; TI_LOCK(sc); bcopy(&sc->ti_rdata->ti_info.ti_stats, outstats, sizeof(struct ti_stats)); TI_UNLOCK(sc); break; } case TIIOCGETPARAMS: { struct ti_params *params; params = (struct ti_params *)addr; TI_LOCK(sc); params->ti_stat_ticks = sc->ti_stat_ticks; params->ti_rx_coal_ticks = sc->ti_rx_coal_ticks; params->ti_tx_coal_ticks = sc->ti_tx_coal_ticks; params->ti_rx_max_coal_bds = sc->ti_rx_max_coal_bds; params->ti_tx_max_coal_bds = sc->ti_tx_max_coal_bds; params->ti_tx_buf_ratio = sc->ti_tx_buf_ratio; params->param_mask = TI_PARAM_ALL; TI_UNLOCK(sc); error = 0; break; } case TIIOCSETPARAMS: { struct ti_params *params; params = (struct ti_params *)addr; TI_LOCK(sc); if (params->param_mask & TI_PARAM_STAT_TICKS) { sc->ti_stat_ticks = params->ti_stat_ticks; CSR_WRITE_4(sc, TI_GCR_STAT_TICKS, sc->ti_stat_ticks); } if (params->param_mask & TI_PARAM_RX_COAL_TICKS) { sc->ti_rx_coal_ticks = params->ti_rx_coal_ticks; CSR_WRITE_4(sc, TI_GCR_RX_COAL_TICKS, sc->ti_rx_coal_ticks); } if (params->param_mask & TI_PARAM_TX_COAL_TICKS) { sc->ti_tx_coal_ticks = params->ti_tx_coal_ticks; CSR_WRITE_4(sc, TI_GCR_TX_COAL_TICKS, sc->ti_tx_coal_ticks); } if (params->param_mask & TI_PARAM_RX_COAL_BDS) { sc->ti_rx_max_coal_bds = params->ti_rx_max_coal_bds; CSR_WRITE_4(sc, TI_GCR_RX_MAX_COAL_BD, sc->ti_rx_max_coal_bds); } if (params->param_mask & TI_PARAM_TX_COAL_BDS) { sc->ti_tx_max_coal_bds = params->ti_tx_max_coal_bds; CSR_WRITE_4(sc, TI_GCR_TX_MAX_COAL_BD, sc->ti_tx_max_coal_bds); } if (params->param_mask & TI_PARAM_TX_BUF_RATIO) { sc->ti_tx_buf_ratio = params->ti_tx_buf_ratio; CSR_WRITE_4(sc, TI_GCR_TX_BUFFER_RATIO, sc->ti_tx_buf_ratio); } TI_UNLOCK(sc); error = 0; break; } case TIIOCSETTRACE: { ti_trace_type trace_type; trace_type = *(ti_trace_type *)addr; /* * Set tracing to whatever the user asked for. Setting * this register to 0 should have the effect of disabling * tracing. */ CSR_WRITE_4(sc, TI_GCR_NIC_TRACING, trace_type); error = 0; break; } case TIIOCGETTRACE: { struct ti_trace_buf *trace_buf; u_int32_t trace_start, cur_trace_ptr, trace_len; trace_buf = (struct ti_trace_buf *)addr; TI_LOCK(sc); trace_start = CSR_READ_4(sc, TI_GCR_NICTRACE_START); cur_trace_ptr = CSR_READ_4(sc, TI_GCR_NICTRACE_PTR); trace_len = CSR_READ_4(sc, TI_GCR_NICTRACE_LEN); #if 0 if_printf(sc->ti_ifp, "trace_start = %#x, cur_trace_ptr = %#x, " "trace_len = %d\n", trace_start, cur_trace_ptr, trace_len); if_printf(sc->ti_ifp, "trace_buf->buf_len = %d\n", trace_buf->buf_len); #endif error = ti_copy_mem(sc, trace_start, min(trace_len, trace_buf->buf_len), (caddr_t)trace_buf->buf, 1, 1); if (error == 0) { trace_buf->fill_len = min(trace_len, trace_buf->buf_len); if (cur_trace_ptr < trace_start) trace_buf->cur_trace_ptr = trace_start - cur_trace_ptr; else trace_buf->cur_trace_ptr = cur_trace_ptr - trace_start; } else trace_buf->fill_len = 0; TI_UNLOCK(sc); break; } /* * For debugging, five ioctls are needed: * ALT_ATTACH * ALT_READ_TG_REG * ALT_WRITE_TG_REG * ALT_READ_TG_MEM * ALT_WRITE_TG_MEM */ case ALT_ATTACH: /* * From what I can tell, Alteon's Solaris Tigon driver * only has one character device, so you have to attach * to the Tigon board you're interested in. This seems * like a not-so-good way to do things, since unless you * subsequently specify the unit number of the device * you're interested in in every ioctl, you'll only be * able to debug one board at a time. */ error = 0; break; case ALT_READ_TG_MEM: case ALT_WRITE_TG_MEM: { struct tg_mem *mem_param; u_int32_t sram_end, scratch_end; mem_param = (struct tg_mem *)addr; if (sc->ti_hwrev == TI_HWREV_TIGON) { sram_end = TI_END_SRAM_I; scratch_end = TI_END_SCRATCH_I; } else { sram_end = TI_END_SRAM_II; scratch_end = TI_END_SCRATCH_II; } /* * For now, we'll only handle accessing regular SRAM, * nothing else. */ TI_LOCK(sc); if ((mem_param->tgAddr >= TI_BEG_SRAM) && ((mem_param->tgAddr + mem_param->len) <= sram_end)) { /* * In this instance, we always copy to/from user * space, so the user space argument is set to 1. */ error = ti_copy_mem(sc, mem_param->tgAddr, mem_param->len, mem_param->userAddr, 1, (cmd == ALT_READ_TG_MEM) ? 1 : 0); } else if ((mem_param->tgAddr >= TI_BEG_SCRATCH) && (mem_param->tgAddr <= scratch_end)) { error = ti_copy_scratch(sc, mem_param->tgAddr, mem_param->len, mem_param->userAddr, 1, (cmd == ALT_READ_TG_MEM) ? 1 : 0, TI_PROCESSOR_A); } else if ((mem_param->tgAddr >= TI_BEG_SCRATCH_B_DEBUG) && (mem_param->tgAddr <= TI_BEG_SCRATCH_B_DEBUG)) { if (sc->ti_hwrev == TI_HWREV_TIGON) { if_printf(sc->ti_ifp, "invalid memory range for Tigon I\n"); error = EINVAL; break; } error = ti_copy_scratch(sc, mem_param->tgAddr - TI_SCRATCH_DEBUG_OFF, mem_param->len, mem_param->userAddr, 1, (cmd == ALT_READ_TG_MEM) ? 1 : 0, TI_PROCESSOR_B); } else { if_printf(sc->ti_ifp, "memory address %#x len %d is " "out of supported range\n", mem_param->tgAddr, mem_param->len); error = EINVAL; } TI_UNLOCK(sc); break; } case ALT_READ_TG_REG: case ALT_WRITE_TG_REG: { struct tg_reg *regs; u_int32_t tmpval; regs = (struct tg_reg *)addr; /* * Make sure the address in question isn't out of range. */ if (regs->addr > TI_REG_MAX) { error = EINVAL; break; } TI_LOCK(sc); if (cmd == ALT_READ_TG_REG) { bus_space_read_region_4(sc->ti_btag, sc->ti_bhandle, regs->addr, &tmpval, 1); regs->data = ntohl(tmpval); #if 0 if ((regs->addr == TI_CPU_STATE) || (regs->addr == TI_CPU_CTL_B)) { if_printf(sc->ti_ifp, "register %#x = %#x\n", regs->addr, tmpval); } #endif } else { tmpval = htonl(regs->data); bus_space_write_region_4(sc->ti_btag, sc->ti_bhandle, regs->addr, &tmpval, 1); } TI_UNLOCK(sc); break; } default: error = ENOTTY; break; } return (error); } static void ti_watchdog(ifp) struct ifnet *ifp; { struct ti_softc *sc; sc = ifp->if_softc; TI_LOCK(sc); /* * When we're debugging, the chip is often stopped for long periods * of time, and that would normally cause the watchdog timer to fire. * Since that impedes debugging, we don't want to do that. */ if (sc->ti_flags & TI_FLAG_DEBUGING) { TI_UNLOCK(sc); return; } if_printf(ifp, "watchdog timeout -- resetting\n"); ti_stop(sc); ti_init_locked(sc); ifp->if_oerrors++; TI_UNLOCK(sc); } /* * Stop the adapter and free any mbufs allocated to the * RX and TX lists. */ static void ti_stop(sc) struct ti_softc *sc; { struct ifnet *ifp; struct ti_cmd_desc cmd; TI_LOCK_ASSERT(sc); ifp = sc->ti_ifp; /* Disable host interrupts. */ CSR_WRITE_4(sc, TI_MB_HOSTINTR, 1); /* * Tell firmware we're shutting down. */ TI_DO_CMD(TI_CMD_HOST_STATE, TI_CMD_CODE_STACK_DOWN, 0); /* Halt and reinitialize. */ if (ti_chipinit(sc) != 0) return; ti_mem_zero(sc, 0x2000, 0x100000 - 0x2000); if (ti_chipinit(sc) != 0) return; /* Free the RX lists. */ ti_free_rx_ring_std(sc); /* Free jumbo RX list. */ ti_free_rx_ring_jumbo(sc); /* Free mini RX list. */ ti_free_rx_ring_mini(sc); /* Free TX buffers. */ ti_free_tx_ring(sc); sc->ti_ev_prodidx.ti_idx = 0; sc->ti_return_prodidx.ti_idx = 0; sc->ti_tx_considx.ti_idx = 0; sc->ti_tx_saved_considx = TI_TXCONS_UNSET; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); } /* * Stop all chip I/O so that the kernel's probe routines don't * get confused by errant DMAs when rebooting. */ static void ti_shutdown(dev) device_t dev; { struct ti_softc *sc; sc = device_get_softc(dev); TI_LOCK(sc); ti_chipinit(sc); TI_UNLOCK(sc); } Index: head/sys/dev/txp/if_txp.c =================================================================== --- head/sys/dev/txp/if_txp.c (revision 162374) +++ head/sys/dev/txp/if_txp.c (revision 162375) @@ -1,1897 +1,1894 @@ /* $OpenBSD: if_txp.c,v 1.48 2001/06/27 06:34:50 kjc Exp $ */ /*- * Copyright (c) 2001 * Jason L. Wright , Theo de Raadt, and * Aaron Campbell . 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Jason L. Wright, * Theo de Raadt and Aaron Campbell. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * Driver for 3c990 (Typhoon) Ethernet ASIC */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for vtophys */ #include /* for vtophys */ #include #include #include #include #include #include #include #include #define TXP_USEIOSPACE #define __STRICT_ALIGNMENT #include #include #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* * Various supported device vendors/types and their names. */ static struct txp_type txp_devs[] = { { TXP_VENDORID_3COM, TXP_DEVICEID_3CR990_TX_95, "3Com 3cR990-TX-95 Etherlink with 3XP Processor" }, { TXP_VENDORID_3COM, TXP_DEVICEID_3CR990_TX_97, "3Com 3cR990-TX-97 Etherlink with 3XP Processor" }, { TXP_VENDORID_3COM, TXP_DEVICEID_3CR990B_TXM, "3Com 3cR990B-TXM Etherlink with 3XP Processor" }, { TXP_VENDORID_3COM, TXP_DEVICEID_3CR990_SRV_95, "3Com 3cR990-SRV-95 Etherlink Server with 3XP Processor" }, { TXP_VENDORID_3COM, TXP_DEVICEID_3CR990_SRV_97, "3Com 3cR990-SRV-97 Etherlink Server with 3XP Processor" }, { TXP_VENDORID_3COM, TXP_DEVICEID_3CR990B_SRV, "3Com 3cR990B-SRV Etherlink Server with 3XP Processor" }, { 0, 0, NULL } }; static int txp_probe(device_t); static int txp_attach(device_t); static int txp_detach(device_t); static void txp_intr(void *); static void txp_tick(void *); static int txp_shutdown(device_t); static int txp_ioctl(struct ifnet *, u_long, caddr_t); static void txp_start(struct ifnet *); static void txp_start_locked(struct ifnet *); static void txp_stop(struct txp_softc *); static void txp_init(void *); static void txp_init_locked(struct txp_softc *); static void txp_watchdog(struct ifnet *); static void txp_release_resources(struct txp_softc *); static int txp_chip_init(struct txp_softc *); static int txp_reset_adapter(struct txp_softc *); static int txp_download_fw(struct txp_softc *); static int txp_download_fw_wait(struct txp_softc *); static int txp_download_fw_section(struct txp_softc *, struct txp_fw_section_header *, int); static int txp_alloc_rings(struct txp_softc *); static int txp_rxring_fill(struct txp_softc *); static void txp_rxring_empty(struct txp_softc *); static void txp_set_filter(struct txp_softc *); static int txp_cmd_desc_numfree(struct txp_softc *); static int txp_command(struct txp_softc *, u_int16_t, u_int16_t, u_int32_t, u_int32_t, u_int16_t *, u_int32_t *, u_int32_t *, int); static int txp_command2(struct txp_softc *, u_int16_t, u_int16_t, u_int32_t, u_int32_t, struct txp_ext_desc *, u_int8_t, struct txp_rsp_desc **, int); static int txp_response(struct txp_softc *, u_int32_t, u_int16_t, u_int16_t, struct txp_rsp_desc **); static void txp_rsp_fixup(struct txp_softc *, struct txp_rsp_desc *, struct txp_rsp_desc *); static void txp_capabilities(struct txp_softc *); static void txp_ifmedia_sts(struct ifnet *, struct ifmediareq *); static int txp_ifmedia_upd(struct ifnet *); #ifdef TXP_DEBUG static void txp_show_descriptor(void *); #endif static void txp_tx_reclaim(struct txp_softc *, struct txp_tx_ring *); static void txp_rxbuf_reclaim(struct txp_softc *); static void txp_rx_reclaim(struct txp_softc *, struct txp_rx_ring *); #ifdef TXP_USEIOSPACE #define TXP_RES SYS_RES_IOPORT #define TXP_RID TXP_PCI_LOIO #else #define TXP_RES SYS_RES_MEMORY #define TXP_RID TXP_PCI_LOMEM #endif static device_method_t txp_methods[] = { /* Device interface */ DEVMETHOD(device_probe, txp_probe), DEVMETHOD(device_attach, txp_attach), DEVMETHOD(device_detach, txp_detach), DEVMETHOD(device_shutdown, txp_shutdown), { 0, 0 } }; static driver_t txp_driver = { "txp", txp_methods, sizeof(struct txp_softc) }; static devclass_t txp_devclass; DRIVER_MODULE(txp, pci, txp_driver, txp_devclass, 0, 0); MODULE_DEPEND(txp, pci, 1, 1, 1); MODULE_DEPEND(txp, ether, 1, 1, 1); static int txp_probe(dev) device_t dev; { struct txp_type *t; t = txp_devs; while(t->txp_name != NULL) { if ((pci_get_vendor(dev) == t->txp_vid) && (pci_get_device(dev) == t->txp_did)) { device_set_desc(dev, t->txp_name); return(BUS_PROBE_DEFAULT); } t++; } return(ENXIO); } static int txp_attach(dev) device_t dev; { struct txp_softc *sc; struct ifnet *ifp; u_int16_t p1; u_int32_t p2; int error = 0, rid; u_char eaddr[6]; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_cold = 1; mtx_init(&sc->sc_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->sc_tick, &sc->sc_mtx, 0); /* * Map control/status registers. */ pci_enable_busmaster(dev); rid = TXP_RID; sc->sc_res = bus_alloc_resource_any(dev, TXP_RES, &rid, RF_ACTIVE); if (sc->sc_res == NULL) { device_printf(dev, "couldn't map ports/memory\n"); error = ENXIO; goto fail; } sc->sc_bt = rman_get_bustag(sc->sc_res); sc->sc_bh = rman_get_bushandle(sc->sc_res); /* Allocate interrupt */ rid = 0; sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->sc_irq == NULL) { device_printf(dev, "couldn't map interrupt\n"); error = ENXIO; goto fail; } if (txp_chip_init(sc)) { error = ENXIO; goto fail; } sc->sc_fwbuf = contigmalloc(32768, M_DEVBUF, M_NOWAIT, 0, 0xffffffff, PAGE_SIZE, 0); error = txp_download_fw(sc); contigfree(sc->sc_fwbuf, 32768, M_DEVBUF); sc->sc_fwbuf = NULL; if (error) goto fail; sc->sc_ldata = contigmalloc(sizeof(struct txp_ldata), M_DEVBUF, M_NOWAIT, 0, 0xffffffff, PAGE_SIZE, 0); bzero(sc->sc_ldata, sizeof(struct txp_ldata)); if (txp_alloc_rings(sc)) { error = ENXIO; goto fail; } if (txp_command(sc, TXP_CMD_MAX_PKT_SIZE_WRITE, TXP_MAX_PKTLEN, 0, 0, NULL, NULL, NULL, 1)) { error = ENXIO; goto fail; } if (txp_command(sc, TXP_CMD_STATION_ADDRESS_READ, 0, 0, 0, &p1, &p2, NULL, 1)) { error = ENXIO; goto fail; } eaddr[0] = ((u_int8_t *)&p1)[1]; eaddr[1] = ((u_int8_t *)&p1)[0]; eaddr[2] = ((u_int8_t *)&p2)[3]; eaddr[3] = ((u_int8_t *)&p2)[2]; eaddr[4] = ((u_int8_t *)&p2)[1]; eaddr[5] = ((u_int8_t *)&p2)[0]; sc->sc_cold = 0; ifmedia_init(&sc->sc_ifmedia, 0, txp_ifmedia_upd, txp_ifmedia_sts); ifmedia_add(&sc->sc_ifmedia, IFM_ETHER|IFM_10_T, 0, NULL); ifmedia_add(&sc->sc_ifmedia, IFM_ETHER|IFM_10_T|IFM_HDX, 0, NULL); ifmedia_add(&sc->sc_ifmedia, IFM_ETHER|IFM_10_T|IFM_FDX, 0, NULL); ifmedia_add(&sc->sc_ifmedia, IFM_ETHER|IFM_100_TX, 0, NULL); ifmedia_add(&sc->sc_ifmedia, IFM_ETHER|IFM_100_TX|IFM_HDX, 0, NULL); ifmedia_add(&sc->sc_ifmedia, IFM_ETHER|IFM_100_TX|IFM_FDX, 0, NULL); ifmedia_add(&sc->sc_ifmedia, IFM_ETHER|IFM_AUTO, 0, NULL); sc->sc_xcvr = TXP_XCVR_AUTO; txp_command(sc, TXP_CMD_XCVR_SELECT, TXP_XCVR_AUTO, 0, 0, NULL, NULL, NULL, 0); ifmedia_set(&sc->sc_ifmedia, IFM_ETHER|IFM_AUTO); ifp = sc->sc_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "can not if_alloc()\n"); error = ENOSPC; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_mtu = ETHERMTU; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = txp_ioctl; ifp->if_start = txp_start; ifp->if_watchdog = txp_watchdog; ifp->if_init = txp_init; ifp->if_baudrate = 100000000; ifp->if_snd.ifq_maxlen = TX_ENTRIES; ifp->if_hwassist = 0; txp_capabilities(sc); /* * Attach us everywhere */ ether_ifattach(ifp, eaddr); error = bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_NET | INTR_MPSAFE, txp_intr, sc, &sc->sc_intrhand); if (error) { ether_ifdetach(ifp); device_printf(dev, "couldn't set up irq\n"); goto fail; } return(0); fail: txp_release_resources(sc); mtx_destroy(&sc->sc_mtx); return(error); } static int txp_detach(dev) device_t dev; { struct txp_softc *sc; struct ifnet *ifp; int i; sc = device_get_softc(dev); ifp = sc->sc_ifp; TXP_LOCK(sc); txp_stop(sc); TXP_UNLOCK(sc); txp_shutdown(dev); callout_drain(&sc->sc_tick); ifmedia_removeall(&sc->sc_ifmedia); ether_ifdetach(ifp); for (i = 0; i < RXBUF_ENTRIES; i++) free(sc->sc_rxbufs[i].rb_sd, M_DEVBUF); txp_release_resources(sc); mtx_destroy(&sc->sc_mtx); return(0); } static void txp_release_resources(sc) struct txp_softc *sc; { device_t dev; dev = sc->sc_dev; if (sc->sc_intrhand != NULL) bus_teardown_intr(dev, sc->sc_irq, sc->sc_intrhand); if (sc->sc_irq != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq); if (sc->sc_res != NULL) bus_release_resource(dev, TXP_RES, TXP_RID, sc->sc_res); if (sc->sc_ldata != NULL) contigfree(sc->sc_ldata, sizeof(struct txp_ldata), M_DEVBUF); if (sc->sc_ifp) if_free(sc->sc_ifp); return; } static int txp_chip_init(sc) struct txp_softc *sc; { /* disable interrupts */ WRITE_REG(sc, TXP_IER, 0); WRITE_REG(sc, TXP_IMR, TXP_INT_SELF | TXP_INT_PCI_TABORT | TXP_INT_PCI_MABORT | TXP_INT_DMA3 | TXP_INT_DMA2 | TXP_INT_DMA1 | TXP_INT_DMA0 | TXP_INT_LATCH); /* ack all interrupts */ WRITE_REG(sc, TXP_ISR, TXP_INT_RESERVED | TXP_INT_LATCH | TXP_INT_A2H_7 | TXP_INT_A2H_6 | TXP_INT_A2H_5 | TXP_INT_A2H_4 | TXP_INT_SELF | TXP_INT_PCI_TABORT | TXP_INT_PCI_MABORT | TXP_INT_DMA3 | TXP_INT_DMA2 | TXP_INT_DMA1 | TXP_INT_DMA0 | TXP_INT_A2H_3 | TXP_INT_A2H_2 | TXP_INT_A2H_1 | TXP_INT_A2H_0); if (txp_reset_adapter(sc)) return (-1); /* disable interrupts */ WRITE_REG(sc, TXP_IER, 0); WRITE_REG(sc, TXP_IMR, TXP_INT_SELF | TXP_INT_PCI_TABORT | TXP_INT_PCI_MABORT | TXP_INT_DMA3 | TXP_INT_DMA2 | TXP_INT_DMA1 | TXP_INT_DMA0 | TXP_INT_LATCH); /* ack all interrupts */ WRITE_REG(sc, TXP_ISR, TXP_INT_RESERVED | TXP_INT_LATCH | TXP_INT_A2H_7 | TXP_INT_A2H_6 | TXP_INT_A2H_5 | TXP_INT_A2H_4 | TXP_INT_SELF | TXP_INT_PCI_TABORT | TXP_INT_PCI_MABORT | TXP_INT_DMA3 | TXP_INT_DMA2 | TXP_INT_DMA1 | TXP_INT_DMA0 | TXP_INT_A2H_3 | TXP_INT_A2H_2 | TXP_INT_A2H_1 | TXP_INT_A2H_0); return (0); } static int txp_reset_adapter(sc) struct txp_softc *sc; { u_int32_t r; int i; r = 0; WRITE_REG(sc, TXP_SRR, TXP_SRR_ALL); DELAY(1000); WRITE_REG(sc, TXP_SRR, 0); /* Should wait max 6 seconds */ for (i = 0; i < 6000; i++) { r = READ_REG(sc, TXP_A2H_0); if (r == STAT_WAITING_FOR_HOST_REQUEST) break; DELAY(1000); } if (r != STAT_WAITING_FOR_HOST_REQUEST) { device_printf(sc->sc_dev, "reset hung\n"); return (-1); } return (0); } static int txp_download_fw(sc) struct txp_softc *sc; { struct txp_fw_file_header *fileheader; struct txp_fw_section_header *secthead; int sect; u_int32_t r, i, ier, imr; r = 0; ier = READ_REG(sc, TXP_IER); WRITE_REG(sc, TXP_IER, ier | TXP_INT_A2H_0); imr = READ_REG(sc, TXP_IMR); WRITE_REG(sc, TXP_IMR, imr | TXP_INT_A2H_0); for (i = 0; i < 10000; i++) { r = READ_REG(sc, TXP_A2H_0); if (r == STAT_WAITING_FOR_HOST_REQUEST) break; DELAY(50); } if (r != STAT_WAITING_FOR_HOST_REQUEST) { device_printf(sc->sc_dev, "not waiting for host request\n"); return (-1); } /* Ack the status */ WRITE_REG(sc, TXP_ISR, TXP_INT_A2H_0); fileheader = (struct txp_fw_file_header *)tc990image; if (bcmp("TYPHOON", fileheader->magicid, sizeof(fileheader->magicid))) { device_printf(sc->sc_dev, "fw invalid magic\n"); return (-1); } /* Tell boot firmware to get ready for image */ WRITE_REG(sc, TXP_H2A_1, fileheader->addr); WRITE_REG(sc, TXP_H2A_0, TXP_BOOTCMD_RUNTIME_IMAGE); if (txp_download_fw_wait(sc)) { device_printf(sc->sc_dev, "fw wait failed, initial\n"); return (-1); } secthead = (struct txp_fw_section_header *)(((u_int8_t *)tc990image) + sizeof(struct txp_fw_file_header)); for (sect = 0; sect < fileheader->nsections; sect++) { if (txp_download_fw_section(sc, secthead, sect)) return (-1); secthead = (struct txp_fw_section_header *) (((u_int8_t *)secthead) + secthead->nbytes + sizeof(*secthead)); } WRITE_REG(sc, TXP_H2A_0, TXP_BOOTCMD_DOWNLOAD_COMPLETE); for (i = 0; i < 10000; i++) { r = READ_REG(sc, TXP_A2H_0); if (r == STAT_WAITING_FOR_BOOT) break; DELAY(50); } if (r != STAT_WAITING_FOR_BOOT) { device_printf(sc->sc_dev, "not waiting for boot\n"); return (-1); } WRITE_REG(sc, TXP_IER, ier); WRITE_REG(sc, TXP_IMR, imr); return (0); } static int txp_download_fw_wait(sc) struct txp_softc *sc; { u_int32_t i, r; r = 0; for (i = 0; i < 10000; i++) { r = READ_REG(sc, TXP_ISR); if (r & TXP_INT_A2H_0) break; DELAY(50); } if (!(r & TXP_INT_A2H_0)) { device_printf(sc->sc_dev, "fw wait failed comm0\n"); return (-1); } WRITE_REG(sc, TXP_ISR, TXP_INT_A2H_0); r = READ_REG(sc, TXP_A2H_0); if (r != STAT_WAITING_FOR_SEGMENT) { device_printf(sc->sc_dev, "fw not waiting for segment\n"); return (-1); } return (0); } static int txp_download_fw_section(sc, sect, sectnum) struct txp_softc *sc; struct txp_fw_section_header *sect; int sectnum; { vm_offset_t dma; int rseg, err = 0; struct mbuf m; u_int16_t csum; /* Skip zero length sections */ if (sect->nbytes == 0) return (0); /* Make sure we aren't past the end of the image */ rseg = ((u_int8_t *)sect) - ((u_int8_t *)tc990image); if (rseg >= sizeof(tc990image)) { device_printf(sc->sc_dev, "fw invalid section address, " "section %d\n", sectnum); return (-1); } /* Make sure this section doesn't go past the end */ rseg += sect->nbytes; if (rseg >= sizeof(tc990image)) { device_printf(sc->sc_dev, "fw truncated section %d\n", sectnum); return (-1); } bcopy(((u_int8_t *)sect) + sizeof(*sect), sc->sc_fwbuf, sect->nbytes); dma = vtophys(sc->sc_fwbuf); /* * dummy up mbuf and verify section checksum */ m.m_type = MT_DATA; m.m_next = m.m_nextpkt = NULL; m.m_len = sect->nbytes; m.m_data = sc->sc_fwbuf; m.m_flags = 0; csum = in_cksum(&m, sect->nbytes); if (csum != sect->cksum) { device_printf(sc->sc_dev, "fw section %d, bad " "cksum (expected 0x%x got 0x%x)\n", sectnum, sect->cksum, csum); err = -1; goto bail; } WRITE_REG(sc, TXP_H2A_1, sect->nbytes); WRITE_REG(sc, TXP_H2A_2, sect->cksum); WRITE_REG(sc, TXP_H2A_3, sect->addr); WRITE_REG(sc, TXP_H2A_4, 0); WRITE_REG(sc, TXP_H2A_5, dma & 0xffffffff); WRITE_REG(sc, TXP_H2A_0, TXP_BOOTCMD_SEGMENT_AVAILABLE); if (txp_download_fw_wait(sc)) { device_printf(sc->sc_dev, "fw wait failed, " "section %d\n", sectnum); err = -1; } bail: return (err); } static void txp_intr(vsc) void *vsc; { struct txp_softc *sc = vsc; struct txp_hostvar *hv = sc->sc_hostvar; u_int32_t isr; /* mask all interrupts */ TXP_LOCK(sc); WRITE_REG(sc, TXP_IMR, TXP_INT_RESERVED | TXP_INT_SELF | TXP_INT_A2H_7 | TXP_INT_A2H_6 | TXP_INT_A2H_5 | TXP_INT_A2H_4 | TXP_INT_A2H_2 | TXP_INT_A2H_1 | TXP_INT_A2H_0 | TXP_INT_DMA3 | TXP_INT_DMA2 | TXP_INT_DMA1 | TXP_INT_DMA0 | TXP_INT_PCI_TABORT | TXP_INT_PCI_MABORT | TXP_INT_LATCH); isr = READ_REG(sc, TXP_ISR); while (isr) { WRITE_REG(sc, TXP_ISR, isr); if ((*sc->sc_rxhir.r_roff) != (*sc->sc_rxhir.r_woff)) txp_rx_reclaim(sc, &sc->sc_rxhir); if ((*sc->sc_rxlor.r_roff) != (*sc->sc_rxlor.r_woff)) txp_rx_reclaim(sc, &sc->sc_rxlor); if (hv->hv_rx_buf_write_idx == hv->hv_rx_buf_read_idx) txp_rxbuf_reclaim(sc); if (sc->sc_txhir.r_cnt && (sc->sc_txhir.r_cons != TXP_OFFSET2IDX(*(sc->sc_txhir.r_off)))) txp_tx_reclaim(sc, &sc->sc_txhir); if (sc->sc_txlor.r_cnt && (sc->sc_txlor.r_cons != TXP_OFFSET2IDX(*(sc->sc_txlor.r_off)))) txp_tx_reclaim(sc, &sc->sc_txlor); isr = READ_REG(sc, TXP_ISR); } /* unmask all interrupts */ WRITE_REG(sc, TXP_IMR, TXP_INT_A2H_3); txp_start_locked(sc->sc_ifp); TXP_UNLOCK(sc); return; } static void txp_rx_reclaim(sc, r) struct txp_softc *sc; struct txp_rx_ring *r; { struct ifnet *ifp = sc->sc_ifp; struct txp_rx_desc *rxd; struct mbuf *m; struct txp_swdesc *sd = NULL; u_int32_t roff, woff; TXP_LOCK_ASSERT(sc); roff = *r->r_roff; woff = *r->r_woff; rxd = r->r_desc + (roff / sizeof(struct txp_rx_desc)); while (roff != woff) { if (rxd->rx_flags & RX_FLAGS_ERROR) { device_printf(sc->sc_dev, "error 0x%x\n", rxd->rx_stat); ifp->if_ierrors++; goto next; } /* retrieve stashed pointer */ sd = rxd->rx_sd; m = sd->sd_mbuf; sd->sd_mbuf = NULL; m->m_pkthdr.len = m->m_len = rxd->rx_len; #ifdef __STRICT_ALIGNMENT { /* * XXX Nice chip, except it won't accept "off by 2" * buffers, so we're force to copy. Supposedly * this will be fixed in a newer firmware rev * and this will be temporary. */ struct mbuf *mnew; mnew = m_devget(mtod(m, caddr_t), rxd->rx_len, ETHER_ALIGN, ifp, NULL); m_freem(m); if (mnew == NULL) { ifp->if_ierrors++; goto next; } m = mnew; } #endif if (rxd->rx_stat & RX_STAT_IPCKSUMBAD) m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; else if (rxd->rx_stat & RX_STAT_IPCKSUMGOOD) m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED|CSUM_IP_VALID; if ((rxd->rx_stat & RX_STAT_TCPCKSUMGOOD) || (rxd->rx_stat & RX_STAT_UDPCKSUMGOOD)) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID|CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } if (rxd->rx_stat & RX_STAT_VLAN) { - VLAN_INPUT_TAG(ifp, m, htons(rxd->rx_vlan >> 16)); - if (m == NULL) - goto next; + m->m_pkthdr.ether_vtag = htons(rxd->rx_vlan >> 16); + m->m_flags |= M_VLANTAG; } TXP_UNLOCK(sc); (*ifp->if_input)(ifp, m); TXP_LOCK(sc); next: roff += sizeof(struct txp_rx_desc); if (roff == (RX_ENTRIES * sizeof(struct txp_rx_desc))) { roff = 0; rxd = r->r_desc; } else rxd++; woff = *r->r_woff; } *r->r_roff = woff; return; } static void txp_rxbuf_reclaim(sc) struct txp_softc *sc; { struct ifnet *ifp = sc->sc_ifp; struct txp_hostvar *hv = sc->sc_hostvar; struct txp_rxbuf_desc *rbd; struct txp_swdesc *sd; u_int32_t i; TXP_LOCK_ASSERT(sc); if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) return; i = sc->sc_rxbufprod; rbd = sc->sc_rxbufs + i; while (1) { sd = rbd->rb_sd; if (sd->sd_mbuf != NULL) break; sd->sd_mbuf = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (sd->sd_mbuf == NULL) return; sd->sd_mbuf->m_pkthdr.rcvif = ifp; sd->sd_mbuf->m_pkthdr.len = sd->sd_mbuf->m_len = MCLBYTES; rbd->rb_paddrlo = vtophys(mtod(sd->sd_mbuf, vm_offset_t)) & 0xffffffff; rbd->rb_paddrhi = 0; hv->hv_rx_buf_write_idx = TXP_IDX2OFFSET(i); if (++i == RXBUF_ENTRIES) { i = 0; rbd = sc->sc_rxbufs; } else rbd++; } sc->sc_rxbufprod = i; return; } /* * Reclaim mbufs and entries from a transmit ring. */ static void txp_tx_reclaim(sc, r) struct txp_softc *sc; struct txp_tx_ring *r; { struct ifnet *ifp = sc->sc_ifp; u_int32_t idx = TXP_OFFSET2IDX(*(r->r_off)); u_int32_t cons = r->r_cons, cnt = r->r_cnt; struct txp_tx_desc *txd = r->r_desc + cons; struct txp_swdesc *sd = sc->sc_txd + cons; struct mbuf *m; TXP_LOCK_ASSERT(sc); while (cons != idx) { if (cnt == 0) break; if ((txd->tx_flags & TX_FLAGS_TYPE_M) == TX_FLAGS_TYPE_DATA) { m = sd->sd_mbuf; if (m != NULL) { m_freem(m); txd->tx_addrlo = 0; txd->tx_addrhi = 0; ifp->if_opackets++; } } ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; if (++cons == TX_ENTRIES) { txd = r->r_desc; cons = 0; sd = sc->sc_txd; } else { txd++; sd++; } cnt--; } r->r_cons = cons; r->r_cnt = cnt; if (cnt == 0) ifp->if_timer = 0; } static int txp_shutdown(dev) device_t dev; { struct txp_softc *sc; sc = device_get_softc(dev); TXP_LOCK(sc); /* mask all interrupts */ WRITE_REG(sc, TXP_IMR, TXP_INT_SELF | TXP_INT_PCI_TABORT | TXP_INT_PCI_MABORT | TXP_INT_DMA3 | TXP_INT_DMA2 | TXP_INT_DMA1 | TXP_INT_DMA0 | TXP_INT_LATCH); txp_command(sc, TXP_CMD_TX_DISABLE, 0, 0, 0, NULL, NULL, NULL, 0); txp_command(sc, TXP_CMD_RX_DISABLE, 0, 0, 0, NULL, NULL, NULL, 0); txp_command(sc, TXP_CMD_HALT, 0, 0, 0, NULL, NULL, NULL, 0); TXP_UNLOCK(sc); return(0); } static int txp_alloc_rings(sc) struct txp_softc *sc; { struct txp_boot_record *boot; struct txp_ldata *ld; u_int32_t r; int i; r = 0; ld = sc->sc_ldata; boot = &ld->txp_boot; /* boot record */ sc->sc_boot = boot; /* host variables */ bzero(&ld->txp_hostvar, sizeof(struct txp_hostvar)); boot->br_hostvar_lo = vtophys(&ld->txp_hostvar); boot->br_hostvar_hi = 0; sc->sc_hostvar = (struct txp_hostvar *)&ld->txp_hostvar; /* hi priority tx ring */ boot->br_txhipri_lo = vtophys(&ld->txp_txhiring);; boot->br_txhipri_hi = 0; boot->br_txhipri_siz = TX_ENTRIES * sizeof(struct txp_tx_desc); sc->sc_txhir.r_reg = TXP_H2A_1; sc->sc_txhir.r_desc = (struct txp_tx_desc *)&ld->txp_txhiring; sc->sc_txhir.r_cons = sc->sc_txhir.r_prod = sc->sc_txhir.r_cnt = 0; sc->sc_txhir.r_off = &sc->sc_hostvar->hv_tx_hi_desc_read_idx; /* lo priority tx ring */ boot->br_txlopri_lo = vtophys(&ld->txp_txloring); boot->br_txlopri_hi = 0; boot->br_txlopri_siz = TX_ENTRIES * sizeof(struct txp_tx_desc); sc->sc_txlor.r_reg = TXP_H2A_3; sc->sc_txlor.r_desc = (struct txp_tx_desc *)&ld->txp_txloring; sc->sc_txlor.r_cons = sc->sc_txlor.r_prod = sc->sc_txlor.r_cnt = 0; sc->sc_txlor.r_off = &sc->sc_hostvar->hv_tx_lo_desc_read_idx; /* high priority rx ring */ boot->br_rxhipri_lo = vtophys(&ld->txp_rxhiring); boot->br_rxhipri_hi = 0; boot->br_rxhipri_siz = RX_ENTRIES * sizeof(struct txp_rx_desc); sc->sc_rxhir.r_desc = (struct txp_rx_desc *)&ld->txp_rxhiring; sc->sc_rxhir.r_roff = &sc->sc_hostvar->hv_rx_hi_read_idx; sc->sc_rxhir.r_woff = &sc->sc_hostvar->hv_rx_hi_write_idx; /* low priority rx ring */ boot->br_rxlopri_lo = vtophys(&ld->txp_rxloring); boot->br_rxlopri_hi = 0; boot->br_rxlopri_siz = RX_ENTRIES * sizeof(struct txp_rx_desc); sc->sc_rxlor.r_desc = (struct txp_rx_desc *)&ld->txp_rxloring; sc->sc_rxlor.r_roff = &sc->sc_hostvar->hv_rx_lo_read_idx; sc->sc_rxlor.r_woff = &sc->sc_hostvar->hv_rx_lo_write_idx; /* command ring */ bzero(&ld->txp_cmdring, sizeof(struct txp_cmd_desc) * CMD_ENTRIES); boot->br_cmd_lo = vtophys(&ld->txp_cmdring); boot->br_cmd_hi = 0; boot->br_cmd_siz = CMD_ENTRIES * sizeof(struct txp_cmd_desc); sc->sc_cmdring.base = (struct txp_cmd_desc *)&ld->txp_cmdring; sc->sc_cmdring.size = CMD_ENTRIES * sizeof(struct txp_cmd_desc); sc->sc_cmdring.lastwrite = 0; /* response ring */ bzero(&ld->txp_rspring, sizeof(struct txp_rsp_desc) * RSP_ENTRIES); boot->br_resp_lo = vtophys(&ld->txp_rspring); boot->br_resp_hi = 0; boot->br_resp_siz = CMD_ENTRIES * sizeof(struct txp_rsp_desc); sc->sc_rspring.base = (struct txp_rsp_desc *)&ld->txp_rspring; sc->sc_rspring.size = RSP_ENTRIES * sizeof(struct txp_rsp_desc); sc->sc_rspring.lastwrite = 0; /* receive buffer ring */ boot->br_rxbuf_lo = vtophys(&ld->txp_rxbufs); boot->br_rxbuf_hi = 0; boot->br_rxbuf_siz = RXBUF_ENTRIES * sizeof(struct txp_rxbuf_desc); sc->sc_rxbufs = (struct txp_rxbuf_desc *)&ld->txp_rxbufs; for (i = 0; i < RXBUF_ENTRIES; i++) { struct txp_swdesc *sd; if (sc->sc_rxbufs[i].rb_sd != NULL) continue; sc->sc_rxbufs[i].rb_sd = malloc(sizeof(struct txp_swdesc), M_DEVBUF, M_NOWAIT); if (sc->sc_rxbufs[i].rb_sd == NULL) return(ENOBUFS); sd = sc->sc_rxbufs[i].rb_sd; sd->sd_mbuf = NULL; } sc->sc_rxbufprod = 0; /* zero dma */ bzero(&ld->txp_zero, sizeof(u_int32_t)); boot->br_zero_lo = vtophys(&ld->txp_zero); boot->br_zero_hi = 0; /* See if it's waiting for boot, and try to boot it */ for (i = 0; i < 10000; i++) { r = READ_REG(sc, TXP_A2H_0); if (r == STAT_WAITING_FOR_BOOT) break; DELAY(50); } if (r != STAT_WAITING_FOR_BOOT) { device_printf(sc->sc_dev, "not waiting for boot\n"); return(ENXIO); } WRITE_REG(sc, TXP_H2A_2, 0); WRITE_REG(sc, TXP_H2A_1, vtophys(sc->sc_boot)); WRITE_REG(sc, TXP_H2A_0, TXP_BOOTCMD_REGISTER_BOOT_RECORD); /* See if it booted */ for (i = 0; i < 10000; i++) { r = READ_REG(sc, TXP_A2H_0); if (r == STAT_RUNNING) break; DELAY(50); } if (r != STAT_RUNNING) { device_printf(sc->sc_dev, "fw not running\n"); return(ENXIO); } /* Clear TX and CMD ring write registers */ WRITE_REG(sc, TXP_H2A_1, TXP_BOOTCMD_NULL); WRITE_REG(sc, TXP_H2A_2, TXP_BOOTCMD_NULL); WRITE_REG(sc, TXP_H2A_3, TXP_BOOTCMD_NULL); WRITE_REG(sc, TXP_H2A_0, TXP_BOOTCMD_NULL); return (0); } static int txp_ioctl(ifp, command, data) struct ifnet *ifp; u_long command; caddr_t data; { struct txp_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *)data; int error = 0; switch(command) { case SIOCSIFFLAGS: TXP_LOCK(sc); if (ifp->if_flags & IFF_UP) { txp_init_locked(sc); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) txp_stop(sc); } TXP_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: /* * Multicast list has changed; set the hardware * filter accordingly. */ TXP_LOCK(sc); txp_set_filter(sc); TXP_UNLOCK(sc); error = 0; break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &sc->sc_ifmedia, command); break; default: error = ether_ioctl(ifp, command, data); break; } return(error); } static int txp_rxring_fill(sc) struct txp_softc *sc; { int i; struct ifnet *ifp; struct txp_swdesc *sd; TXP_LOCK_ASSERT(sc); ifp = sc->sc_ifp; for (i = 0; i < RXBUF_ENTRIES; i++) { sd = sc->sc_rxbufs[i].rb_sd; sd->sd_mbuf = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (sd->sd_mbuf == NULL) return(ENOBUFS); sd->sd_mbuf->m_pkthdr.len = sd->sd_mbuf->m_len = MCLBYTES; sd->sd_mbuf->m_pkthdr.rcvif = ifp; sc->sc_rxbufs[i].rb_paddrlo = vtophys(mtod(sd->sd_mbuf, vm_offset_t)); sc->sc_rxbufs[i].rb_paddrhi = 0; } sc->sc_hostvar->hv_rx_buf_write_idx = (RXBUF_ENTRIES - 1) * sizeof(struct txp_rxbuf_desc); return(0); } static void txp_rxring_empty(sc) struct txp_softc *sc; { int i; struct txp_swdesc *sd; TXP_LOCK_ASSERT(sc); if (sc->sc_rxbufs == NULL) return; for (i = 0; i < RXBUF_ENTRIES; i++) { if (&sc->sc_rxbufs[i] == NULL) continue; sd = sc->sc_rxbufs[i].rb_sd; if (sd == NULL) continue; if (sd->sd_mbuf != NULL) { m_freem(sd->sd_mbuf); sd->sd_mbuf = NULL; } } return; } static void txp_init(xsc) void *xsc; { struct txp_softc *sc; sc = xsc; TXP_LOCK(sc); txp_init_locked(sc); TXP_UNLOCK(sc); } static void txp_init_locked(sc) struct txp_softc *sc; { struct ifnet *ifp; u_int16_t p1; u_int32_t p2; TXP_LOCK_ASSERT(sc); ifp = sc->sc_ifp; if (ifp->if_drv_flags & IFF_DRV_RUNNING) return; txp_stop(sc); txp_command(sc, TXP_CMD_MAX_PKT_SIZE_WRITE, TXP_MAX_PKTLEN, 0, 0, NULL, NULL, NULL, 1); /* Set station address. */ ((u_int8_t *)&p1)[1] = IF_LLADDR(sc->sc_ifp)[0]; ((u_int8_t *)&p1)[0] = IF_LLADDR(sc->sc_ifp)[1]; ((u_int8_t *)&p2)[3] = IF_LLADDR(sc->sc_ifp)[2]; ((u_int8_t *)&p2)[2] = IF_LLADDR(sc->sc_ifp)[3]; ((u_int8_t *)&p2)[1] = IF_LLADDR(sc->sc_ifp)[4]; ((u_int8_t *)&p2)[0] = IF_LLADDR(sc->sc_ifp)[5]; txp_command(sc, TXP_CMD_STATION_ADDRESS_WRITE, p1, p2, 0, NULL, NULL, NULL, 1); txp_set_filter(sc); txp_rxring_fill(sc); txp_command(sc, TXP_CMD_TX_ENABLE, 0, 0, 0, NULL, NULL, NULL, 1); txp_command(sc, TXP_CMD_RX_ENABLE, 0, 0, 0, NULL, NULL, NULL, 1); WRITE_REG(sc, TXP_IER, TXP_INT_RESERVED | TXP_INT_SELF | TXP_INT_A2H_7 | TXP_INT_A2H_6 | TXP_INT_A2H_5 | TXP_INT_A2H_4 | TXP_INT_A2H_2 | TXP_INT_A2H_1 | TXP_INT_A2H_0 | TXP_INT_DMA3 | TXP_INT_DMA2 | TXP_INT_DMA1 | TXP_INT_DMA0 | TXP_INT_PCI_TABORT | TXP_INT_PCI_MABORT | TXP_INT_LATCH); WRITE_REG(sc, TXP_IMR, TXP_INT_A2H_3); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; ifp->if_timer = 0; callout_reset(&sc->sc_tick, hz, txp_tick, sc); } static void txp_tick(vsc) void *vsc; { struct txp_softc *sc = vsc; struct ifnet *ifp = sc->sc_ifp; struct txp_rsp_desc *rsp = NULL; struct txp_ext_desc *ext; TXP_LOCK_ASSERT(sc); txp_rxbuf_reclaim(sc); if (txp_command2(sc, TXP_CMD_READ_STATISTICS, 0, 0, 0, NULL, 0, &rsp, 1)) goto out; if (rsp->rsp_numdesc != 6) goto out; if (txp_command(sc, TXP_CMD_CLEAR_STATISTICS, 0, 0, 0, NULL, NULL, NULL, 1)) goto out; ext = (struct txp_ext_desc *)(rsp + 1); ifp->if_ierrors += ext[3].ext_2 + ext[3].ext_3 + ext[3].ext_4 + ext[4].ext_1 + ext[4].ext_4; ifp->if_oerrors += ext[0].ext_1 + ext[1].ext_1 + ext[1].ext_4 + ext[2].ext_1; ifp->if_collisions += ext[0].ext_2 + ext[0].ext_3 + ext[1].ext_2 + ext[1].ext_3; ifp->if_opackets += rsp->rsp_par2; ifp->if_ipackets += ext[2].ext_3; out: if (rsp != NULL) free(rsp, M_DEVBUF); callout_reset(&sc->sc_tick, hz, txp_tick, sc); return; } static void txp_start(ifp) struct ifnet *ifp; { struct txp_softc *sc; sc = ifp->if_softc; TXP_LOCK(sc); txp_start_locked(ifp); TXP_UNLOCK(sc); } static void txp_start_locked(ifp) struct ifnet *ifp; { struct txp_softc *sc = ifp->if_softc; struct txp_tx_ring *r = &sc->sc_txhir; struct txp_tx_desc *txd; struct txp_frag_desc *fxd; struct mbuf *m, *m0; struct txp_swdesc *sd; u_int32_t firstprod, firstcnt, prod, cnt; - struct m_tag *mtag; TXP_LOCK_ASSERT(sc); if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) return; prod = r->r_prod; cnt = r->r_cnt; while (1) { IF_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; firstprod = prod; firstcnt = cnt; sd = sc->sc_txd + prod; sd->sd_mbuf = m; if ((TX_ENTRIES - cnt) < 4) goto oactive; txd = r->r_desc + prod; txd->tx_flags = TX_FLAGS_TYPE_DATA; txd->tx_numdesc = 0; txd->tx_addrlo = 0; txd->tx_addrhi = 0; txd->tx_totlen = 0; txd->tx_pflags = 0; if (++prod == TX_ENTRIES) prod = 0; if (++cnt >= (TX_ENTRIES - 4)) goto oactive; - mtag = VLAN_OUTPUT_TAG(ifp, m); - if (mtag != NULL) { + if (m->m_flags & M_VLANTAG) { txd->tx_pflags = TX_PFLAGS_VLAN | - (htons(VLAN_TAG_VALUE(mtag)) << TX_PFLAGS_VLANTAG_S); + (htons(m->m_pkthdr.ether_vtag) << TX_PFLAGS_VLANTAG_S); } if (m->m_pkthdr.csum_flags & CSUM_IP) txd->tx_pflags |= TX_PFLAGS_IPCKSUM; #if 0 if (m->m_pkthdr.csum_flags & CSUM_TCP) txd->tx_pflags |= TX_PFLAGS_TCPCKSUM; if (m->m_pkthdr.csum_flags & CSUM_UDP) txd->tx_pflags |= TX_PFLAGS_UDPCKSUM; #endif fxd = (struct txp_frag_desc *)(r->r_desc + prod); for (m0 = m; m0 != NULL; m0 = m0->m_next) { if (m0->m_len == 0) continue; if (++cnt >= (TX_ENTRIES - 4)) goto oactive; txd->tx_numdesc++; fxd->frag_flags = FRAG_FLAGS_TYPE_FRAG; fxd->frag_rsvd1 = 0; fxd->frag_len = m0->m_len; fxd->frag_addrlo = vtophys(mtod(m0, vm_offset_t)); fxd->frag_addrhi = 0; fxd->frag_rsvd2 = 0; if (++prod == TX_ENTRIES) { fxd = (struct txp_frag_desc *)r->r_desc; prod = 0; } else fxd++; } ifp->if_timer = 5; BPF_MTAP(ifp, m); WRITE_REG(sc, r->r_reg, TXP_IDX2OFFSET(prod)); } r->r_prod = prod; r->r_cnt = cnt; return; oactive: ifp->if_drv_flags |= IFF_DRV_OACTIVE; r->r_prod = firstprod; r->r_cnt = firstcnt; IF_PREPEND(&ifp->if_snd, m); return; } /* * Handle simple commands sent to the typhoon */ static int txp_command(sc, id, in1, in2, in3, out1, out2, out3, wait) struct txp_softc *sc; u_int16_t id, in1, *out1; u_int32_t in2, in3, *out2, *out3; int wait; { struct txp_rsp_desc *rsp = NULL; if (txp_command2(sc, id, in1, in2, in3, NULL, 0, &rsp, wait)) return (-1); if (!wait) return (0); if (out1 != NULL) *out1 = rsp->rsp_par1; if (out2 != NULL) *out2 = rsp->rsp_par2; if (out3 != NULL) *out3 = rsp->rsp_par3; free(rsp, M_DEVBUF); return (0); } static int txp_command2(sc, id, in1, in2, in3, in_extp, in_extn, rspp, wait) struct txp_softc *sc; u_int16_t id, in1; u_int32_t in2, in3; struct txp_ext_desc *in_extp; u_int8_t in_extn; struct txp_rsp_desc **rspp; int wait; { struct txp_hostvar *hv = sc->sc_hostvar; struct txp_cmd_desc *cmd; struct txp_ext_desc *ext; u_int32_t idx, i; u_int16_t seq; if (txp_cmd_desc_numfree(sc) < (in_extn + 1)) { device_printf(sc->sc_dev, "no free cmd descriptors\n"); return (-1); } idx = sc->sc_cmdring.lastwrite; cmd = (struct txp_cmd_desc *)(((u_int8_t *)sc->sc_cmdring.base) + idx); bzero(cmd, sizeof(*cmd)); cmd->cmd_numdesc = in_extn; cmd->cmd_seq = seq = sc->sc_seq++; cmd->cmd_id = id; cmd->cmd_par1 = in1; cmd->cmd_par2 = in2; cmd->cmd_par3 = in3; cmd->cmd_flags = CMD_FLAGS_TYPE_CMD | (wait ? CMD_FLAGS_RESP : 0) | CMD_FLAGS_VALID; idx += sizeof(struct txp_cmd_desc); if (idx == sc->sc_cmdring.size) idx = 0; for (i = 0; i < in_extn; i++) { ext = (struct txp_ext_desc *)(((u_int8_t *)sc->sc_cmdring.base) + idx); bcopy(in_extp, ext, sizeof(struct txp_ext_desc)); in_extp++; idx += sizeof(struct txp_cmd_desc); if (idx == sc->sc_cmdring.size) idx = 0; } sc->sc_cmdring.lastwrite = idx; WRITE_REG(sc, TXP_H2A_2, sc->sc_cmdring.lastwrite); if (!wait) return (0); for (i = 0; i < 10000; i++) { idx = hv->hv_resp_read_idx; if (idx != hv->hv_resp_write_idx) { *rspp = NULL; if (txp_response(sc, idx, id, seq, rspp)) return (-1); if (*rspp != NULL) break; } DELAY(50); } if (i == 1000 || (*rspp) == NULL) { device_printf(sc->sc_dev, "0x%x command failed\n", id); return (-1); } return (0); } static int txp_response(sc, ridx, id, seq, rspp) struct txp_softc *sc; u_int32_t ridx; u_int16_t id; u_int16_t seq; struct txp_rsp_desc **rspp; { struct txp_hostvar *hv = sc->sc_hostvar; struct txp_rsp_desc *rsp; while (ridx != hv->hv_resp_write_idx) { rsp = (struct txp_rsp_desc *)(((u_int8_t *)sc->sc_rspring.base) + ridx); if (id == rsp->rsp_id && rsp->rsp_seq == seq) { *rspp = (struct txp_rsp_desc *)malloc( sizeof(struct txp_rsp_desc) * (rsp->rsp_numdesc + 1), M_DEVBUF, M_NOWAIT); if ((*rspp) == NULL) return (-1); txp_rsp_fixup(sc, rsp, *rspp); return (0); } if (rsp->rsp_flags & RSP_FLAGS_ERROR) { device_printf(sc->sc_dev, "response error!\n"); txp_rsp_fixup(sc, rsp, NULL); ridx = hv->hv_resp_read_idx; continue; } switch (rsp->rsp_id) { case TXP_CMD_CYCLE_STATISTICS: case TXP_CMD_MEDIA_STATUS_READ: break; case TXP_CMD_HELLO_RESPONSE: device_printf(sc->sc_dev, "hello\n"); break; default: device_printf(sc->sc_dev, "unknown id(0x%x)\n", rsp->rsp_id); } txp_rsp_fixup(sc, rsp, NULL); ridx = hv->hv_resp_read_idx; hv->hv_resp_read_idx = ridx; } return (0); } static void txp_rsp_fixup(sc, rsp, dst) struct txp_softc *sc; struct txp_rsp_desc *rsp, *dst; { struct txp_rsp_desc *src = rsp; struct txp_hostvar *hv = sc->sc_hostvar; u_int32_t i, ridx; ridx = hv->hv_resp_read_idx; for (i = 0; i < rsp->rsp_numdesc + 1; i++) { if (dst != NULL) bcopy(src, dst++, sizeof(struct txp_rsp_desc)); ridx += sizeof(struct txp_rsp_desc); if (ridx == sc->sc_rspring.size) { src = sc->sc_rspring.base; ridx = 0; } else src++; sc->sc_rspring.lastwrite = hv->hv_resp_read_idx = ridx; } hv->hv_resp_read_idx = ridx; } static int txp_cmd_desc_numfree(sc) struct txp_softc *sc; { struct txp_hostvar *hv = sc->sc_hostvar; struct txp_boot_record *br = sc->sc_boot; u_int32_t widx, ridx, nfree; widx = sc->sc_cmdring.lastwrite; ridx = hv->hv_cmd_read_idx; if (widx == ridx) { /* Ring is completely free */ nfree = br->br_cmd_siz - sizeof(struct txp_cmd_desc); } else { if (widx > ridx) nfree = br->br_cmd_siz - (widx - ridx + sizeof(struct txp_cmd_desc)); else nfree = ridx - widx - sizeof(struct txp_cmd_desc); } return (nfree / sizeof(struct txp_cmd_desc)); } static void txp_stop(sc) struct txp_softc *sc; { struct ifnet *ifp; TXP_LOCK_ASSERT(sc); ifp = sc->sc_ifp; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); callout_stop(&sc->sc_tick); txp_command(sc, TXP_CMD_TX_DISABLE, 0, 0, 0, NULL, NULL, NULL, 1); txp_command(sc, TXP_CMD_RX_DISABLE, 0, 0, 0, NULL, NULL, NULL, 1); txp_rxring_empty(sc); return; } static void txp_watchdog(ifp) struct ifnet *ifp; { return; } static int txp_ifmedia_upd(ifp) struct ifnet *ifp; { struct txp_softc *sc = ifp->if_softc; struct ifmedia *ifm = &sc->sc_ifmedia; u_int16_t new_xcvr; TXP_LOCK(sc); if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER) { TXP_UNLOCK(sc); return (EINVAL); } if (IFM_SUBTYPE(ifm->ifm_media) == IFM_10_T) { if ((ifm->ifm_media & IFM_GMASK) == IFM_FDX) new_xcvr = TXP_XCVR_10_FDX; else new_xcvr = TXP_XCVR_10_HDX; } else if (IFM_SUBTYPE(ifm->ifm_media) == IFM_100_TX) { if ((ifm->ifm_media & IFM_GMASK) == IFM_FDX) new_xcvr = TXP_XCVR_100_FDX; else new_xcvr = TXP_XCVR_100_HDX; } else if (IFM_SUBTYPE(ifm->ifm_media) == IFM_AUTO) { new_xcvr = TXP_XCVR_AUTO; } else { TXP_UNLOCK(sc); return (EINVAL); } /* nothing to do */ if (sc->sc_xcvr == new_xcvr) { TXP_UNLOCK(sc); return (0); } txp_command(sc, TXP_CMD_XCVR_SELECT, new_xcvr, 0, 0, NULL, NULL, NULL, 0); sc->sc_xcvr = new_xcvr; TXP_UNLOCK(sc); return (0); } static void txp_ifmedia_sts(ifp, ifmr) struct ifnet *ifp; struct ifmediareq *ifmr; { struct txp_softc *sc = ifp->if_softc; struct ifmedia *ifm = &sc->sc_ifmedia; u_int16_t bmsr, bmcr, anlpar; ifmr->ifm_status = IFM_AVALID; ifmr->ifm_active = IFM_ETHER; TXP_LOCK(sc); if (txp_command(sc, TXP_CMD_PHY_MGMT_READ, 0, MII_BMSR, 0, &bmsr, NULL, NULL, 1)) goto bail; if (txp_command(sc, TXP_CMD_PHY_MGMT_READ, 0, MII_BMSR, 0, &bmsr, NULL, NULL, 1)) goto bail; if (txp_command(sc, TXP_CMD_PHY_MGMT_READ, 0, MII_BMCR, 0, &bmcr, NULL, NULL, 1)) goto bail; if (txp_command(sc, TXP_CMD_PHY_MGMT_READ, 0, MII_ANLPAR, 0, &anlpar, NULL, NULL, 1)) goto bail; TXP_UNLOCK(sc); if (bmsr & BMSR_LINK) ifmr->ifm_status |= IFM_ACTIVE; if (bmcr & BMCR_ISO) { ifmr->ifm_active |= IFM_NONE; ifmr->ifm_status = 0; return; } if (bmcr & BMCR_LOOP) ifmr->ifm_active |= IFM_LOOP; if (bmcr & BMCR_AUTOEN) { if ((bmsr & BMSR_ACOMP) == 0) { ifmr->ifm_active |= IFM_NONE; return; } if (anlpar & ANLPAR_T4) ifmr->ifm_active |= IFM_100_T4; else if (anlpar & ANLPAR_TX_FD) ifmr->ifm_active |= IFM_100_TX|IFM_FDX; else if (anlpar & ANLPAR_TX) ifmr->ifm_active |= IFM_100_TX; else if (anlpar & ANLPAR_10_FD) ifmr->ifm_active |= IFM_10_T|IFM_FDX; else if (anlpar & ANLPAR_10) ifmr->ifm_active |= IFM_10_T; else ifmr->ifm_active |= IFM_NONE; } else ifmr->ifm_active = ifm->ifm_cur->ifm_media; return; bail: TXP_UNLOCK(sc); ifmr->ifm_active |= IFM_NONE; ifmr->ifm_status &= ~IFM_AVALID; } #ifdef TXP_DEBUG static void txp_show_descriptor(d) void *d; { struct txp_cmd_desc *cmd = d; struct txp_rsp_desc *rsp = d; struct txp_tx_desc *txd = d; struct txp_frag_desc *frgd = d; switch (cmd->cmd_flags & CMD_FLAGS_TYPE_M) { case CMD_FLAGS_TYPE_CMD: /* command descriptor */ printf("[cmd flags 0x%x num %d id %d seq %d par1 0x%x par2 0x%x par3 0x%x]\n", cmd->cmd_flags, cmd->cmd_numdesc, cmd->cmd_id, cmd->cmd_seq, cmd->cmd_par1, cmd->cmd_par2, cmd->cmd_par3); break; case CMD_FLAGS_TYPE_RESP: /* response descriptor */ printf("[rsp flags 0x%x num %d id %d seq %d par1 0x%x par2 0x%x par3 0x%x]\n", rsp->rsp_flags, rsp->rsp_numdesc, rsp->rsp_id, rsp->rsp_seq, rsp->rsp_par1, rsp->rsp_par2, rsp->rsp_par3); break; case CMD_FLAGS_TYPE_DATA: /* data header (assuming tx for now) */ printf("[data flags 0x%x num %d totlen %d addr 0x%x/0x%x pflags 0x%x]", txd->tx_flags, txd->tx_numdesc, txd->tx_totlen, txd->tx_addrlo, txd->tx_addrhi, txd->tx_pflags); break; case CMD_FLAGS_TYPE_FRAG: /* fragment descriptor */ printf("[frag flags 0x%x rsvd1 0x%x len %d addr 0x%x/0x%x rsvd2 0x%x]", frgd->frag_flags, frgd->frag_rsvd1, frgd->frag_len, frgd->frag_addrlo, frgd->frag_addrhi, frgd->frag_rsvd2); break; default: printf("[unknown(%x) flags 0x%x num %d id %d seq %d par1 0x%x par2 0x%x par3 0x%x]\n", cmd->cmd_flags & CMD_FLAGS_TYPE_M, cmd->cmd_flags, cmd->cmd_numdesc, cmd->cmd_id, cmd->cmd_seq, cmd->cmd_par1, cmd->cmd_par2, cmd->cmd_par3); break; } } #endif static void txp_set_filter(sc) struct txp_softc *sc; { struct ifnet *ifp = sc->sc_ifp; u_int32_t crc, carry, hashbit, hash[2]; u_int16_t filter; u_int8_t octet; int i, j, mcnt = 0; struct ifmultiaddr *ifma; char *enm; if (ifp->if_flags & IFF_PROMISC) { filter = TXP_RXFILT_PROMISC; goto setit; } filter = TXP_RXFILT_DIRECT; if (ifp->if_flags & IFF_BROADCAST) filter |= TXP_RXFILT_BROADCAST; if (ifp->if_flags & IFF_ALLMULTI) filter |= TXP_RXFILT_ALLMULTI; else { hash[0] = hash[1] = 0; IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; enm = LLADDR((struct sockaddr_dl *)ifma->ifma_addr); mcnt++; crc = 0xffffffff; for (i = 0; i < ETHER_ADDR_LEN; i++) { octet = enm[i]; for (j = 0; j < 8; j++) { carry = ((crc & 0x80000000) ? 1 : 0) ^ (octet & 1); crc <<= 1; octet >>= 1; if (carry) crc = (crc ^ TXP_POLYNOMIAL) | carry; } } hashbit = (u_int16_t)(crc & (64 - 1)); hash[hashbit / 32] |= (1 << hashbit % 32); } IF_ADDR_UNLOCK(ifp); if (mcnt > 0) { filter |= TXP_RXFILT_HASHMULTI; txp_command(sc, TXP_CMD_MCAST_HASH_MASK_WRITE, 2, hash[0], hash[1], NULL, NULL, NULL, 0); } } setit: txp_command(sc, TXP_CMD_RX_FILTER_WRITE, filter, 0, 0, NULL, NULL, NULL, 1); return; } static void txp_capabilities(sc) struct txp_softc *sc; { struct ifnet *ifp = sc->sc_ifp; struct txp_rsp_desc *rsp = NULL; struct txp_ext_desc *ext; if (txp_command2(sc, TXP_CMD_OFFLOAD_READ, 0, 0, 0, NULL, 0, &rsp, 1)) goto out; if (rsp->rsp_numdesc != 1) goto out; ext = (struct txp_ext_desc *)(rsp + 1); sc->sc_tx_capability = ext->ext_1 & OFFLOAD_MASK; sc->sc_rx_capability = ext->ext_2 & OFFLOAD_MASK; ifp->if_capabilities = 0; if (rsp->rsp_par2 & rsp->rsp_par3 & OFFLOAD_VLAN) { sc->sc_tx_capability |= OFFLOAD_VLAN; sc->sc_rx_capability |= OFFLOAD_VLAN; ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING; } #if 0 /* not ready yet */ if (rsp->rsp_par2 & rsp->rsp_par3 & OFFLOAD_IPSEC) { sc->sc_tx_capability |= OFFLOAD_IPSEC; sc->sc_rx_capability |= OFFLOAD_IPSEC; ifp->if_capabilities |= IFCAP_IPSEC; } #endif if (rsp->rsp_par2 & rsp->rsp_par3 & OFFLOAD_IPCKSUM) { sc->sc_tx_capability |= OFFLOAD_IPCKSUM; sc->sc_rx_capability |= OFFLOAD_IPCKSUM; ifp->if_capabilities |= IFCAP_HWCSUM; ifp->if_hwassist |= CSUM_IP; } if (rsp->rsp_par2 & rsp->rsp_par3 & OFFLOAD_TCPCKSUM) { #if 0 sc->sc_tx_capability |= OFFLOAD_TCPCKSUM; #endif sc->sc_rx_capability |= OFFLOAD_TCPCKSUM; ifp->if_capabilities |= IFCAP_HWCSUM; } if (rsp->rsp_par2 & rsp->rsp_par3 & OFFLOAD_UDPCKSUM) { #if 0 sc->sc_tx_capability |= OFFLOAD_UDPCKSUM; #endif sc->sc_rx_capability |= OFFLOAD_UDPCKSUM; ifp->if_capabilities |= IFCAP_HWCSUM; } ifp->if_capenable = ifp->if_capabilities; if (txp_command(sc, TXP_CMD_OFFLOAD_WRITE, 0, sc->sc_tx_capability, sc->sc_rx_capability, NULL, NULL, NULL, 1)) goto out; out: if (rsp != NULL) free(rsp, M_DEVBUF); return; } Index: head/sys/dev/vge/if_vge.c =================================================================== --- head/sys/dev/vge/if_vge.c (revision 162374) +++ head/sys/dev/vge/if_vge.c (revision 162375) @@ -1,2443 +1,2440 @@ /*- * Copyright (c) 2004 * Bill Paul . 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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 __FBSDID("$FreeBSD$"); /* * VIA Networking Technologies VT612x PCI gigabit ethernet NIC driver. * * Written by Bill Paul * Senior Networking Software Engineer * Wind River Systems */ /* * The VIA Networking VT6122 is a 32bit, 33/66Mhz PCI device that * combines a tri-speed ethernet MAC and PHY, with the following * features: * * o Jumbo frame support up to 16K * o Transmit and receive flow control * o IPv4 checksum offload * o VLAN tag insertion and stripping * o TCP large send * o 64-bit multicast hash table filter * o 64 entry CAM filter * o 16K RX FIFO and 48K TX FIFO memory * o Interrupt moderation * * The VT6122 supports up to four transmit DMA queues. The descriptors * in the transmit ring can address up to 7 data fragments; frames which * span more than 7 data buffers must be coalesced, but in general the * BSD TCP/IP stack rarely generates frames more than 2 or 3 fragments * long. The receive descriptors address only a single buffer. * * There are two peculiar design issues with the VT6122. One is that * receive data buffers must be aligned on a 32-bit boundary. This is * not a problem where the VT6122 is used as a LOM device in x86-based * systems, but on architectures that generate unaligned access traps, we * have to do some copying. * * The other issue has to do with the way 64-bit addresses are handled. * The DMA descriptors only allow you to specify 48 bits of addressing * information. The remaining 16 bits are specified using one of the * I/O registers. If you only have a 32-bit system, then this isn't * an issue, but if you have a 64-bit system and more than 4GB of * memory, you must have to make sure your network data buffers reside * in the same 48-bit 'segment.' * * Special thanks to Ryan Fu at VIA Networking for providing documentation * and sample NICs for testing. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_device_polling.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_DEPEND(vge, pci, 1, 1, 1); MODULE_DEPEND(vge, ether, 1, 1, 1); MODULE_DEPEND(vge, miibus, 1, 1, 1); /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" #include #include #define VGE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) /* * Various supported device vendors/types and their names. */ static struct vge_type vge_devs[] = { { VIA_VENDORID, VIA_DEVICEID_61XX, "VIA Networking Gigabit Ethernet" }, { 0, 0, NULL } }; static int vge_probe (device_t); static int vge_attach (device_t); static int vge_detach (device_t); static int vge_encap (struct vge_softc *, struct mbuf *, int); static void vge_dma_map_addr (void *, bus_dma_segment_t *, int, int); static void vge_dma_map_rx_desc (void *, bus_dma_segment_t *, int, bus_size_t, int); static void vge_dma_map_tx_desc (void *, bus_dma_segment_t *, int, bus_size_t, int); static int vge_allocmem (device_t, struct vge_softc *); static int vge_newbuf (struct vge_softc *, int, struct mbuf *); static int vge_rx_list_init (struct vge_softc *); static int vge_tx_list_init (struct vge_softc *); #ifdef VGE_FIXUP_RX static __inline void vge_fixup_rx (struct mbuf *); #endif static void vge_rxeof (struct vge_softc *); static void vge_txeof (struct vge_softc *); static void vge_intr (void *); static void vge_tick (void *); static void vge_tx_task (void *, int); static void vge_start (struct ifnet *); static int vge_ioctl (struct ifnet *, u_long, caddr_t); static void vge_init (void *); static void vge_stop (struct vge_softc *); static void vge_watchdog (struct ifnet *); static int vge_suspend (device_t); static int vge_resume (device_t); static void vge_shutdown (device_t); static int vge_ifmedia_upd (struct ifnet *); static void vge_ifmedia_sts (struct ifnet *, struct ifmediareq *); #ifdef VGE_EEPROM static void vge_eeprom_getword (struct vge_softc *, int, u_int16_t *); #endif static void vge_read_eeprom (struct vge_softc *, caddr_t, int, int, int); static void vge_miipoll_start (struct vge_softc *); static void vge_miipoll_stop (struct vge_softc *); static int vge_miibus_readreg (device_t, int, int); static int vge_miibus_writereg (device_t, int, int, int); static void vge_miibus_statchg (device_t); static void vge_cam_clear (struct vge_softc *); static int vge_cam_set (struct vge_softc *, uint8_t *); #if __FreeBSD_version < 502113 static uint32_t vge_mchash (uint8_t *); #endif static void vge_setmulti (struct vge_softc *); static void vge_reset (struct vge_softc *); #define VGE_PCI_LOIO 0x10 #define VGE_PCI_LOMEM 0x14 static device_method_t vge_methods[] = { /* Device interface */ DEVMETHOD(device_probe, vge_probe), DEVMETHOD(device_attach, vge_attach), DEVMETHOD(device_detach, vge_detach), DEVMETHOD(device_suspend, vge_suspend), DEVMETHOD(device_resume, vge_resume), DEVMETHOD(device_shutdown, vge_shutdown), /* bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, vge_miibus_readreg), DEVMETHOD(miibus_writereg, vge_miibus_writereg), DEVMETHOD(miibus_statchg, vge_miibus_statchg), { 0, 0 } }; static driver_t vge_driver = { "vge", vge_methods, sizeof(struct vge_softc) }; static devclass_t vge_devclass; DRIVER_MODULE(vge, pci, vge_driver, vge_devclass, 0, 0); DRIVER_MODULE(vge, cardbus, vge_driver, vge_devclass, 0, 0); DRIVER_MODULE(miibus, vge, miibus_driver, miibus_devclass, 0, 0); #ifdef VGE_EEPROM /* * Read a word of data stored in the EEPROM at address 'addr.' */ static void vge_eeprom_getword(sc, addr, dest) struct vge_softc *sc; int addr; u_int16_t *dest; { register int i; u_int16_t word = 0; /* * Enter EEPROM embedded programming mode. In order to * access the EEPROM at all, we first have to set the * EELOAD bit in the CHIPCFG2 register. */ CSR_SETBIT_1(sc, VGE_CHIPCFG2, VGE_CHIPCFG2_EELOAD); CSR_SETBIT_1(sc, VGE_EECSR, VGE_EECSR_EMBP/*|VGE_EECSR_ECS*/); /* Select the address of the word we want to read */ CSR_WRITE_1(sc, VGE_EEADDR, addr); /* Issue read command */ CSR_SETBIT_1(sc, VGE_EECMD, VGE_EECMD_ERD); /* Wait for the done bit to be set. */ for (i = 0; i < VGE_TIMEOUT; i++) { if (CSR_READ_1(sc, VGE_EECMD) & VGE_EECMD_EDONE) break; } if (i == VGE_TIMEOUT) { device_printf(sc->vge_dev, "EEPROM read timed out\n"); *dest = 0; return; } /* Read the result */ word = CSR_READ_2(sc, VGE_EERDDAT); /* Turn off EEPROM access mode. */ CSR_CLRBIT_1(sc, VGE_EECSR, VGE_EECSR_EMBP/*|VGE_EECSR_ECS*/); CSR_CLRBIT_1(sc, VGE_CHIPCFG2, VGE_CHIPCFG2_EELOAD); *dest = word; return; } #endif /* * Read a sequence of words from the EEPROM. */ static void vge_read_eeprom(sc, dest, off, cnt, swap) struct vge_softc *sc; caddr_t dest; int off; int cnt; int swap; { int i; #ifdef VGE_EEPROM u_int16_t word = 0, *ptr; for (i = 0; i < cnt; i++) { vge_eeprom_getword(sc, off + i, &word); ptr = (u_int16_t *)(dest + (i * 2)); if (swap) *ptr = ntohs(word); else *ptr = word; } #else for (i = 0; i < ETHER_ADDR_LEN; i++) dest[i] = CSR_READ_1(sc, VGE_PAR0 + i); #endif } static void vge_miipoll_stop(sc) struct vge_softc *sc; { int i; CSR_WRITE_1(sc, VGE_MIICMD, 0); for (i = 0; i < VGE_TIMEOUT; i++) { DELAY(1); if (CSR_READ_1(sc, VGE_MIISTS) & VGE_MIISTS_IIDL) break; } if (i == VGE_TIMEOUT) device_printf(sc->vge_dev, "failed to idle MII autopoll\n"); return; } static void vge_miipoll_start(sc) struct vge_softc *sc; { int i; /* First, make sure we're idle. */ CSR_WRITE_1(sc, VGE_MIICMD, 0); CSR_WRITE_1(sc, VGE_MIIADDR, VGE_MIIADDR_SWMPL); for (i = 0; i < VGE_TIMEOUT; i++) { DELAY(1); if (CSR_READ_1(sc, VGE_MIISTS) & VGE_MIISTS_IIDL) break; } if (i == VGE_TIMEOUT) { device_printf(sc->vge_dev, "failed to idle MII autopoll\n"); return; } /* Now enable auto poll mode. */ CSR_WRITE_1(sc, VGE_MIICMD, VGE_MIICMD_MAUTO); /* And make sure it started. */ for (i = 0; i < VGE_TIMEOUT; i++) { DELAY(1); if ((CSR_READ_1(sc, VGE_MIISTS) & VGE_MIISTS_IIDL) == 0) break; } if (i == VGE_TIMEOUT) device_printf(sc->vge_dev, "failed to start MII autopoll\n"); return; } static int vge_miibus_readreg(dev, phy, reg) device_t dev; int phy, reg; { struct vge_softc *sc; int i; u_int16_t rval = 0; sc = device_get_softc(dev); if (phy != (CSR_READ_1(sc, VGE_MIICFG) & 0x1F)) return(0); VGE_LOCK(sc); vge_miipoll_stop(sc); /* Specify the register we want to read. */ CSR_WRITE_1(sc, VGE_MIIADDR, reg); /* Issue read command. */ CSR_SETBIT_1(sc, VGE_MIICMD, VGE_MIICMD_RCMD); /* Wait for the read command bit to self-clear. */ for (i = 0; i < VGE_TIMEOUT; i++) { DELAY(1); if ((CSR_READ_1(sc, VGE_MIICMD) & VGE_MIICMD_RCMD) == 0) break; } if (i == VGE_TIMEOUT) device_printf(sc->vge_dev, "MII read timed out\n"); else rval = CSR_READ_2(sc, VGE_MIIDATA); vge_miipoll_start(sc); VGE_UNLOCK(sc); return (rval); } static int vge_miibus_writereg(dev, phy, reg, data) device_t dev; int phy, reg, data; { struct vge_softc *sc; int i, rval = 0; sc = device_get_softc(dev); if (phy != (CSR_READ_1(sc, VGE_MIICFG) & 0x1F)) return(0); VGE_LOCK(sc); vge_miipoll_stop(sc); /* Specify the register we want to write. */ CSR_WRITE_1(sc, VGE_MIIADDR, reg); /* Specify the data we want to write. */ CSR_WRITE_2(sc, VGE_MIIDATA, data); /* Issue write command. */ CSR_SETBIT_1(sc, VGE_MIICMD, VGE_MIICMD_WCMD); /* Wait for the write command bit to self-clear. */ for (i = 0; i < VGE_TIMEOUT; i++) { DELAY(1); if ((CSR_READ_1(sc, VGE_MIICMD) & VGE_MIICMD_WCMD) == 0) break; } if (i == VGE_TIMEOUT) { device_printf(sc->vge_dev, "MII write timed out\n"); rval = EIO; } vge_miipoll_start(sc); VGE_UNLOCK(sc); return (rval); } static void vge_cam_clear(sc) struct vge_softc *sc; { int i; /* * Turn off all the mask bits. This tells the chip * that none of the entries in the CAM filter are valid. * desired entries will be enabled as we fill the filter in. */ CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL); CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_CAMMASK); CSR_WRITE_1(sc, VGE_CAMADDR, VGE_CAMADDR_ENABLE); for (i = 0; i < 8; i++) CSR_WRITE_1(sc, VGE_CAM0 + i, 0); /* Clear the VLAN filter too. */ CSR_WRITE_1(sc, VGE_CAMADDR, VGE_CAMADDR_ENABLE|VGE_CAMADDR_AVSEL|0); for (i = 0; i < 8; i++) CSR_WRITE_1(sc, VGE_CAM0 + i, 0); CSR_WRITE_1(sc, VGE_CAMADDR, 0); CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL); CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_MAR); sc->vge_camidx = 0; return; } static int vge_cam_set(sc, addr) struct vge_softc *sc; uint8_t *addr; { int i, error = 0; if (sc->vge_camidx == VGE_CAM_MAXADDRS) return(ENOSPC); /* Select the CAM data page. */ CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL); CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_CAMDATA); /* Set the filter entry we want to update and enable writing. */ CSR_WRITE_1(sc, VGE_CAMADDR, VGE_CAMADDR_ENABLE|sc->vge_camidx); /* Write the address to the CAM registers */ for (i = 0; i < ETHER_ADDR_LEN; i++) CSR_WRITE_1(sc, VGE_CAM0 + i, addr[i]); /* Issue a write command. */ CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_WRITE); /* Wake for it to clear. */ for (i = 0; i < VGE_TIMEOUT; i++) { DELAY(1); if ((CSR_READ_1(sc, VGE_CAMCTL) & VGE_CAMCTL_WRITE) == 0) break; } if (i == VGE_TIMEOUT) { device_printf(sc->vge_dev, "setting CAM filter failed\n"); error = EIO; goto fail; } /* Select the CAM mask page. */ CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL); CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_CAMMASK); /* Set the mask bit that enables this filter. */ CSR_SETBIT_1(sc, VGE_CAM0 + (sc->vge_camidx/8), 1<<(sc->vge_camidx & 7)); sc->vge_camidx++; fail: /* Turn off access to CAM. */ CSR_WRITE_1(sc, VGE_CAMADDR, 0); CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL); CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_MAR); return (error); } #if __FreeBSD_version < 502113 static uint32_t vge_mchash(addr) uint8_t *addr; { uint32_t crc, carry; int idx, bit; uint8_t data; /* Compute CRC for the address value. */ crc = 0xFFFFFFFF; /* initial value */ for (idx = 0; idx < 6; idx++) { for (data = *addr++, bit = 0; bit < 8; bit++, data >>= 1) { carry = ((crc & 0x80000000) ? 1 : 0) ^ (data & 0x01); crc <<= 1; if (carry) crc = (crc ^ 0x04c11db6) | carry; } } return(crc); } #endif /* * Program the multicast filter. We use the 64-entry CAM filter * for perfect filtering. If there's more than 64 multicast addresses, * we use the hash filter insted. */ static void vge_setmulti(sc) struct vge_softc *sc; { struct ifnet *ifp; int error = 0/*, h = 0*/; struct ifmultiaddr *ifma; u_int32_t h, hashes[2] = { 0, 0 }; ifp = sc->vge_ifp; /* First, zot all the multicast entries. */ vge_cam_clear(sc); CSR_WRITE_4(sc, VGE_MAR0, 0); CSR_WRITE_4(sc, VGE_MAR1, 0); /* * If the user wants allmulti or promisc mode, enable reception * of all multicast frames. */ if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { CSR_WRITE_4(sc, VGE_MAR0, 0xFFFFFFFF); CSR_WRITE_4(sc, VGE_MAR1, 0xFFFFFFFF); return; } /* Now program new ones */ IF_ADDR_LOCK(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; error = vge_cam_set(sc, LLADDR((struct sockaddr_dl *)ifma->ifma_addr)); if (error) break; } /* If there were too many addresses, use the hash filter. */ if (error) { vge_cam_clear(sc); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; #if __FreeBSD_version < 502113 h = vge_mchash(LLADDR((struct sockaddr_dl *) ifma->ifma_addr)) >> 26; #else h = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; #endif if (h < 32) hashes[0] |= (1 << h); else hashes[1] |= (1 << (h - 32)); } CSR_WRITE_4(sc, VGE_MAR0, hashes[0]); CSR_WRITE_4(sc, VGE_MAR1, hashes[1]); } IF_ADDR_UNLOCK(ifp); return; } static void vge_reset(sc) struct vge_softc *sc; { register int i; CSR_WRITE_1(sc, VGE_CRS1, VGE_CR1_SOFTRESET); for (i = 0; i < VGE_TIMEOUT; i++) { DELAY(5); if ((CSR_READ_1(sc, VGE_CRS1) & VGE_CR1_SOFTRESET) == 0) break; } if (i == VGE_TIMEOUT) { device_printf(sc->vge_dev, "soft reset timed out"); CSR_WRITE_1(sc, VGE_CRS3, VGE_CR3_STOP_FORCE); DELAY(2000); } DELAY(5000); CSR_SETBIT_1(sc, VGE_EECSR, VGE_EECSR_RELOAD); for (i = 0; i < VGE_TIMEOUT; i++) { DELAY(5); if ((CSR_READ_1(sc, VGE_EECSR) & VGE_EECSR_RELOAD) == 0) break; } if (i == VGE_TIMEOUT) { device_printf(sc->vge_dev, "EEPROM reload timed out\n"); return; } CSR_CLRBIT_1(sc, VGE_CHIPCFG0, VGE_CHIPCFG0_PACPI); return; } /* * Probe for a VIA gigabit chip. Check the PCI vendor and device * IDs against our list and return a device name if we find a match. */ static int vge_probe(dev) device_t dev; { struct vge_type *t; struct vge_softc *sc; t = vge_devs; sc = device_get_softc(dev); while (t->vge_name != NULL) { if ((pci_get_vendor(dev) == t->vge_vid) && (pci_get_device(dev) == t->vge_did)) { device_set_desc(dev, t->vge_name); return (BUS_PROBE_DEFAULT); } t++; } return (ENXIO); } static void vge_dma_map_rx_desc(arg, segs, nseg, mapsize, error) void *arg; bus_dma_segment_t *segs; int nseg; bus_size_t mapsize; int error; { struct vge_dmaload_arg *ctx; struct vge_rx_desc *d = NULL; if (error) return; ctx = arg; /* Signal error to caller if there's too many segments */ if (nseg > ctx->vge_maxsegs) { ctx->vge_maxsegs = 0; return; } /* * Map the segment array into descriptors. */ d = &ctx->sc->vge_ldata.vge_rx_list[ctx->vge_idx]; /* If this descriptor is still owned by the chip, bail. */ if (le32toh(d->vge_sts) & VGE_RDSTS_OWN) { device_printf(ctx->sc->vge_dev, "tried to map busy descriptor\n"); ctx->vge_maxsegs = 0; return; } d->vge_buflen = htole16(VGE_BUFLEN(segs[0].ds_len) | VGE_RXDESC_I); d->vge_addrlo = htole32(VGE_ADDR_LO(segs[0].ds_addr)); d->vge_addrhi = htole16(VGE_ADDR_HI(segs[0].ds_addr) & 0xFFFF); d->vge_sts = 0; d->vge_ctl = 0; ctx->vge_maxsegs = 1; return; } static void vge_dma_map_tx_desc(arg, segs, nseg, mapsize, error) void *arg; bus_dma_segment_t *segs; int nseg; bus_size_t mapsize; int error; { struct vge_dmaload_arg *ctx; struct vge_tx_desc *d = NULL; struct vge_tx_frag *f; int i = 0; if (error) return; ctx = arg; /* Signal error to caller if there's too many segments */ if (nseg > ctx->vge_maxsegs) { ctx->vge_maxsegs = 0; return; } /* Map the segment array into descriptors. */ d = &ctx->sc->vge_ldata.vge_tx_list[ctx->vge_idx]; /* If this descriptor is still owned by the chip, bail. */ if (le32toh(d->vge_sts) & VGE_TDSTS_OWN) { ctx->vge_maxsegs = 0; return; } for (i = 0; i < nseg; i++) { f = &d->vge_frag[i]; f->vge_buflen = htole16(VGE_BUFLEN(segs[i].ds_len)); f->vge_addrlo = htole32(VGE_ADDR_LO(segs[i].ds_addr)); f->vge_addrhi = htole16(VGE_ADDR_HI(segs[i].ds_addr) & 0xFFFF); } /* Argh. This chip does not autopad short frames */ if (ctx->vge_m0->m_pkthdr.len < VGE_MIN_FRAMELEN) { f = &d->vge_frag[i]; f->vge_buflen = htole16(VGE_BUFLEN(VGE_MIN_FRAMELEN - ctx->vge_m0->m_pkthdr.len)); f->vge_addrlo = htole32(VGE_ADDR_LO(segs[0].ds_addr)); f->vge_addrhi = htole16(VGE_ADDR_HI(segs[0].ds_addr) & 0xFFFF); ctx->vge_m0->m_pkthdr.len = VGE_MIN_FRAMELEN; i++; } /* * When telling the chip how many segments there are, we * must use nsegs + 1 instead of just nsegs. Darned if I * know why. */ i++; d->vge_sts = ctx->vge_m0->m_pkthdr.len << 16; d->vge_ctl = ctx->vge_flags|(i << 28)|VGE_TD_LS_NORM; if (ctx->vge_m0->m_pkthdr.len > ETHERMTU + ETHER_HDR_LEN) d->vge_ctl |= VGE_TDCTL_JUMBO; ctx->vge_maxsegs = nseg; return; } /* * Map a single buffer address. */ static void vge_dma_map_addr(arg, segs, nseg, error) void *arg; bus_dma_segment_t *segs; int nseg; int error; { bus_addr_t *addr; if (error) return; KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg)); addr = arg; *addr = segs->ds_addr; return; } static int vge_allocmem(dev, sc) device_t dev; struct vge_softc *sc; { int error; int nseg; int i; /* * Allocate map for RX mbufs. */ nseg = 32; error = bus_dma_tag_create(sc->vge_parent_tag, ETHER_ALIGN, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES * nseg, nseg, MCLBYTES, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->vge_ldata.vge_mtag); if (error) { device_printf(dev, "could not allocate dma tag\n"); return (ENOMEM); } /* * Allocate map for TX descriptor list. */ error = bus_dma_tag_create(sc->vge_parent_tag, VGE_RING_ALIGN, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, VGE_TX_LIST_SZ, 1, VGE_TX_LIST_SZ, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->vge_ldata.vge_tx_list_tag); if (error) { device_printf(dev, "could not allocate dma tag\n"); return (ENOMEM); } /* Allocate DMA'able memory for the TX ring */ error = bus_dmamem_alloc(sc->vge_ldata.vge_tx_list_tag, (void **)&sc->vge_ldata.vge_tx_list, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &sc->vge_ldata.vge_tx_list_map); if (error) return (ENOMEM); /* Load the map for the TX ring. */ error = bus_dmamap_load(sc->vge_ldata.vge_tx_list_tag, sc->vge_ldata.vge_tx_list_map, sc->vge_ldata.vge_tx_list, VGE_TX_LIST_SZ, vge_dma_map_addr, &sc->vge_ldata.vge_tx_list_addr, BUS_DMA_NOWAIT); /* Create DMA maps for TX buffers */ for (i = 0; i < VGE_TX_DESC_CNT; i++) { error = bus_dmamap_create(sc->vge_ldata.vge_mtag, 0, &sc->vge_ldata.vge_tx_dmamap[i]); if (error) { device_printf(dev, "can't create DMA map for TX\n"); return (ENOMEM); } } /* * Allocate map for RX descriptor list. */ error = bus_dma_tag_create(sc->vge_parent_tag, VGE_RING_ALIGN, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, VGE_TX_LIST_SZ, 1, VGE_TX_LIST_SZ, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->vge_ldata.vge_rx_list_tag); if (error) { device_printf(dev, "could not allocate dma tag\n"); return (ENOMEM); } /* Allocate DMA'able memory for the RX ring */ error = bus_dmamem_alloc(sc->vge_ldata.vge_rx_list_tag, (void **)&sc->vge_ldata.vge_rx_list, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &sc->vge_ldata.vge_rx_list_map); if (error) return (ENOMEM); /* Load the map for the RX ring. */ error = bus_dmamap_load(sc->vge_ldata.vge_rx_list_tag, sc->vge_ldata.vge_rx_list_map, sc->vge_ldata.vge_rx_list, VGE_TX_LIST_SZ, vge_dma_map_addr, &sc->vge_ldata.vge_rx_list_addr, BUS_DMA_NOWAIT); /* Create DMA maps for RX buffers */ for (i = 0; i < VGE_RX_DESC_CNT; i++) { error = bus_dmamap_create(sc->vge_ldata.vge_mtag, 0, &sc->vge_ldata.vge_rx_dmamap[i]); if (error) { device_printf(dev, "can't create DMA map for RX\n"); return (ENOMEM); } } return (0); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int vge_attach(dev) device_t dev; { u_char eaddr[ETHER_ADDR_LEN]; struct vge_softc *sc; struct ifnet *ifp; int unit, error = 0, rid; sc = device_get_softc(dev); unit = device_get_unit(dev); sc->vge_dev = dev; mtx_init(&sc->vge_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF | MTX_RECURSE); /* * Map control/status registers. */ pci_enable_busmaster(dev); rid = VGE_PCI_LOMEM; sc->vge_res = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, 0, ~0, 1, RF_ACTIVE); if (sc->vge_res == NULL) { printf ("vge%d: couldn't map ports/memory\n", unit); error = ENXIO; goto fail; } sc->vge_btag = rman_get_bustag(sc->vge_res); sc->vge_bhandle = rman_get_bushandle(sc->vge_res); /* Allocate interrupt */ rid = 0; sc->vge_irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); if (sc->vge_irq == NULL) { printf("vge%d: couldn't map interrupt\n", unit); error = ENXIO; goto fail; } /* Reset the adapter. */ vge_reset(sc); /* * Get station address from the EEPROM. */ vge_read_eeprom(sc, (caddr_t)eaddr, VGE_EE_EADDR, 3, 0); sc->vge_unit = unit; #if __FreeBSD_version < 502113 printf("vge%d: Ethernet address: %6D\n", unit, eaddr, ":"); #endif /* * Allocate the parent bus DMA tag appropriate for PCI. */ #define VGE_NSEG_NEW 32 error = bus_dma_tag_create(NULL, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MAXBSIZE, VGE_NSEG_NEW, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT,/* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->vge_parent_tag); if (error) goto fail; error = vge_allocmem(dev, sc); if (error) goto fail; ifp = sc->vge_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { printf("vge%d: can not if_alloc()\n", sc->vge_unit); error = ENOSPC; goto fail; } /* Do MII setup */ if (mii_phy_probe(dev, &sc->vge_miibus, vge_ifmedia_upd, vge_ifmedia_sts)) { printf("vge%d: MII without any phy!\n", sc->vge_unit); error = ENXIO; goto fail; } ifp->if_softc = sc; if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_mtu = ETHERMTU; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_ioctl = vge_ioctl; ifp->if_capabilities = IFCAP_VLAN_MTU; ifp->if_start = vge_start; ifp->if_hwassist = VGE_CSUM_FEATURES; ifp->if_capabilities |= IFCAP_HWCSUM|IFCAP_VLAN_HWTAGGING; ifp->if_capenable = ifp->if_capabilities; #ifdef DEVICE_POLLING ifp->if_capabilities |= IFCAP_POLLING; #endif ifp->if_watchdog = vge_watchdog; ifp->if_init = vge_init; ifp->if_snd.ifq_maxlen = VGE_IFQ_MAXLEN; TASK_INIT(&sc->vge_txtask, 0, vge_tx_task, ifp); /* * Call MI attach routine. */ ether_ifattach(ifp, eaddr); /* Hook interrupt last to avoid having to lock softc */ error = bus_setup_intr(dev, sc->vge_irq, INTR_TYPE_NET|INTR_MPSAFE, vge_intr, sc, &sc->vge_intrhand); if (error) { printf("vge%d: couldn't set up irq\n", unit); ether_ifdetach(ifp); goto fail; } fail: if (error) vge_detach(dev); return (error); } /* * Shutdown hardware and free up resources. This can be called any * time after the mutex has been initialized. It is called in both * the error case in attach and the normal detach case so it needs * to be careful about only freeing resources that have actually been * allocated. */ static int vge_detach(dev) device_t dev; { struct vge_softc *sc; struct ifnet *ifp; int i; sc = device_get_softc(dev); KASSERT(mtx_initialized(&sc->vge_mtx), ("vge mutex not initialized")); ifp = sc->vge_ifp; #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) ether_poll_deregister(ifp); #endif /* These should only be active if attach succeeded */ if (device_is_attached(dev)) { vge_stop(sc); /* * Force off the IFF_UP flag here, in case someone * still had a BPF descriptor attached to this * interface. If they do, ether_ifattach() will cause * the BPF code to try and clear the promisc mode * flag, which will bubble down to vge_ioctl(), * which will try to call vge_init() again. This will * turn the NIC back on and restart the MII ticker, * which will panic the system when the kernel tries * to invoke the vge_tick() function that isn't there * anymore. */ ifp->if_flags &= ~IFF_UP; ether_ifdetach(ifp); } if (sc->vge_miibus) device_delete_child(dev, sc->vge_miibus); bus_generic_detach(dev); if (sc->vge_intrhand) bus_teardown_intr(dev, sc->vge_irq, sc->vge_intrhand); if (sc->vge_irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->vge_irq); if (sc->vge_res) bus_release_resource(dev, SYS_RES_MEMORY, VGE_PCI_LOMEM, sc->vge_res); if (ifp) if_free(ifp); /* Unload and free the RX DMA ring memory and map */ if (sc->vge_ldata.vge_rx_list_tag) { bus_dmamap_unload(sc->vge_ldata.vge_rx_list_tag, sc->vge_ldata.vge_rx_list_map); bus_dmamem_free(sc->vge_ldata.vge_rx_list_tag, sc->vge_ldata.vge_rx_list, sc->vge_ldata.vge_rx_list_map); bus_dma_tag_destroy(sc->vge_ldata.vge_rx_list_tag); } /* Unload and free the TX DMA ring memory and map */ if (sc->vge_ldata.vge_tx_list_tag) { bus_dmamap_unload(sc->vge_ldata.vge_tx_list_tag, sc->vge_ldata.vge_tx_list_map); bus_dmamem_free(sc->vge_ldata.vge_tx_list_tag, sc->vge_ldata.vge_tx_list, sc->vge_ldata.vge_tx_list_map); bus_dma_tag_destroy(sc->vge_ldata.vge_tx_list_tag); } /* Destroy all the RX and TX buffer maps */ if (sc->vge_ldata.vge_mtag) { for (i = 0; i < VGE_TX_DESC_CNT; i++) bus_dmamap_destroy(sc->vge_ldata.vge_mtag, sc->vge_ldata.vge_tx_dmamap[i]); for (i = 0; i < VGE_RX_DESC_CNT; i++) bus_dmamap_destroy(sc->vge_ldata.vge_mtag, sc->vge_ldata.vge_rx_dmamap[i]); bus_dma_tag_destroy(sc->vge_ldata.vge_mtag); } if (sc->vge_parent_tag) bus_dma_tag_destroy(sc->vge_parent_tag); mtx_destroy(&sc->vge_mtx); return (0); } static int vge_newbuf(sc, idx, m) struct vge_softc *sc; int idx; struct mbuf *m; { struct vge_dmaload_arg arg; struct mbuf *n = NULL; int i, error; if (m == NULL) { n = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (n == NULL) return (ENOBUFS); m = n; } else m->m_data = m->m_ext.ext_buf; #ifdef VGE_FIXUP_RX /* * This is part of an evil trick to deal with non-x86 platforms. * The VIA chip requires RX buffers to be aligned on 32-bit * boundaries, but that will hose non-x86 machines. To get around * this, we leave some empty space at the start of each buffer * and for non-x86 hosts, we copy the buffer back two bytes * to achieve word alignment. This is slightly more efficient * than allocating a new buffer, copying the contents, and * discarding the old buffer. */ m->m_len = m->m_pkthdr.len = MCLBYTES - VGE_ETHER_ALIGN; m_adj(m, VGE_ETHER_ALIGN); #else m->m_len = m->m_pkthdr.len = MCLBYTES; #endif arg.sc = sc; arg.vge_idx = idx; arg.vge_maxsegs = 1; arg.vge_flags = 0; error = bus_dmamap_load_mbuf(sc->vge_ldata.vge_mtag, sc->vge_ldata.vge_rx_dmamap[idx], m, vge_dma_map_rx_desc, &arg, BUS_DMA_NOWAIT); if (error || arg.vge_maxsegs != 1) { if (n != NULL) m_freem(n); return (ENOMEM); } /* * Note: the manual fails to document the fact that for * proper opration, the driver needs to replentish the RX * DMA ring 4 descriptors at a time (rather than one at a * time, like most chips). We can allocate the new buffers * but we should not set the OWN bits until we're ready * to hand back 4 of them in one shot. */ #define VGE_RXCHUNK 4 sc->vge_rx_consumed++; if (sc->vge_rx_consumed == VGE_RXCHUNK) { for (i = idx; i != idx - sc->vge_rx_consumed; i--) sc->vge_ldata.vge_rx_list[i].vge_sts |= htole32(VGE_RDSTS_OWN); sc->vge_rx_consumed = 0; } sc->vge_ldata.vge_rx_mbuf[idx] = m; bus_dmamap_sync(sc->vge_ldata.vge_mtag, sc->vge_ldata.vge_rx_dmamap[idx], BUS_DMASYNC_PREREAD); return (0); } static int vge_tx_list_init(sc) struct vge_softc *sc; { bzero ((char *)sc->vge_ldata.vge_tx_list, VGE_TX_LIST_SZ); bzero ((char *)&sc->vge_ldata.vge_tx_mbuf, (VGE_TX_DESC_CNT * sizeof(struct mbuf *))); bus_dmamap_sync(sc->vge_ldata.vge_tx_list_tag, sc->vge_ldata.vge_tx_list_map, BUS_DMASYNC_PREWRITE); sc->vge_ldata.vge_tx_prodidx = 0; sc->vge_ldata.vge_tx_considx = 0; sc->vge_ldata.vge_tx_free = VGE_TX_DESC_CNT; return (0); } static int vge_rx_list_init(sc) struct vge_softc *sc; { int i; bzero ((char *)sc->vge_ldata.vge_rx_list, VGE_RX_LIST_SZ); bzero ((char *)&sc->vge_ldata.vge_rx_mbuf, (VGE_RX_DESC_CNT * sizeof(struct mbuf *))); sc->vge_rx_consumed = 0; for (i = 0; i < VGE_RX_DESC_CNT; i++) { if (vge_newbuf(sc, i, NULL) == ENOBUFS) return (ENOBUFS); } /* Flush the RX descriptors */ bus_dmamap_sync(sc->vge_ldata.vge_rx_list_tag, sc->vge_ldata.vge_rx_list_map, BUS_DMASYNC_PREWRITE|BUS_DMASYNC_PREREAD); sc->vge_ldata.vge_rx_prodidx = 0; sc->vge_rx_consumed = 0; sc->vge_head = sc->vge_tail = NULL; return (0); } #ifdef VGE_FIXUP_RX static __inline void vge_fixup_rx(m) struct mbuf *m; { int i; uint16_t *src, *dst; src = mtod(m, uint16_t *); dst = src - 1; for (i = 0; i < (m->m_len / sizeof(uint16_t) + 1); i++) *dst++ = *src++; m->m_data -= ETHER_ALIGN; return; } #endif /* * RX handler. We support the reception of jumbo frames that have * been fragmented across multiple 2K mbuf cluster buffers. */ static void vge_rxeof(sc) struct vge_softc *sc; { struct mbuf *m; struct ifnet *ifp; int i, total_len; int lim = 0; struct vge_rx_desc *cur_rx; u_int32_t rxstat, rxctl; VGE_LOCK_ASSERT(sc); ifp = sc->vge_ifp; i = sc->vge_ldata.vge_rx_prodidx; /* Invalidate the descriptor memory */ bus_dmamap_sync(sc->vge_ldata.vge_rx_list_tag, sc->vge_ldata.vge_rx_list_map, BUS_DMASYNC_POSTREAD); while (!VGE_OWN(&sc->vge_ldata.vge_rx_list[i])) { #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { if (sc->rxcycles <= 0) break; sc->rxcycles--; } #endif cur_rx = &sc->vge_ldata.vge_rx_list[i]; m = sc->vge_ldata.vge_rx_mbuf[i]; total_len = VGE_RXBYTES(cur_rx); rxstat = le32toh(cur_rx->vge_sts); rxctl = le32toh(cur_rx->vge_ctl); /* Invalidate the RX mbuf and unload its map */ bus_dmamap_sync(sc->vge_ldata.vge_mtag, sc->vge_ldata.vge_rx_dmamap[i], BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->vge_ldata.vge_mtag, sc->vge_ldata.vge_rx_dmamap[i]); /* * If the 'start of frame' bit is set, this indicates * either the first fragment in a multi-fragment receive, * or an intermediate fragment. Either way, we want to * accumulate the buffers. */ if (rxstat & VGE_RXPKT_SOF) { m->m_len = MCLBYTES - VGE_ETHER_ALIGN; if (sc->vge_head == NULL) sc->vge_head = sc->vge_tail = m; else { m->m_flags &= ~M_PKTHDR; sc->vge_tail->m_next = m; sc->vge_tail = m; } vge_newbuf(sc, i, NULL); VGE_RX_DESC_INC(i); continue; } /* * Bad/error frames will have the RXOK bit cleared. * However, there's one error case we want to allow: * if a VLAN tagged frame arrives and the chip can't * match it against the CAM filter, it considers this * a 'VLAN CAM filter miss' and clears the 'RXOK' bit. * We don't want to drop the frame though: our VLAN * filtering is done in software. */ if (!(rxstat & VGE_RDSTS_RXOK) && !(rxstat & VGE_RDSTS_VIDM) && !(rxstat & VGE_RDSTS_CSUMERR)) { ifp->if_ierrors++; /* * If this is part of a multi-fragment packet, * discard all the pieces. */ if (sc->vge_head != NULL) { m_freem(sc->vge_head); sc->vge_head = sc->vge_tail = NULL; } vge_newbuf(sc, i, m); VGE_RX_DESC_INC(i); continue; } /* * If allocating a replacement mbuf fails, * reload the current one. */ if (vge_newbuf(sc, i, NULL)) { ifp->if_ierrors++; if (sc->vge_head != NULL) { m_freem(sc->vge_head); sc->vge_head = sc->vge_tail = NULL; } vge_newbuf(sc, i, m); VGE_RX_DESC_INC(i); continue; } VGE_RX_DESC_INC(i); if (sc->vge_head != NULL) { m->m_len = total_len % (MCLBYTES - VGE_ETHER_ALIGN); /* * Special case: if there's 4 bytes or less * in this buffer, the mbuf can be discarded: * the last 4 bytes is the CRC, which we don't * care about anyway. */ if (m->m_len <= ETHER_CRC_LEN) { sc->vge_tail->m_len -= (ETHER_CRC_LEN - m->m_len); m_freem(m); } else { m->m_len -= ETHER_CRC_LEN; m->m_flags &= ~M_PKTHDR; sc->vge_tail->m_next = m; } m = sc->vge_head; sc->vge_head = sc->vge_tail = NULL; m->m_pkthdr.len = total_len - ETHER_CRC_LEN; } else m->m_pkthdr.len = m->m_len = (total_len - ETHER_CRC_LEN); #ifdef VGE_FIXUP_RX vge_fixup_rx(m); #endif ifp->if_ipackets++; m->m_pkthdr.rcvif = ifp; /* Do RX checksumming if enabled */ if (ifp->if_capenable & IFCAP_RXCSUM) { /* Check IP header checksum */ if (rxctl & VGE_RDCTL_IPPKT) m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; if (rxctl & VGE_RDCTL_IPCSUMOK) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; /* Check TCP/UDP checksum */ if (rxctl & (VGE_RDCTL_TCPPKT|VGE_RDCTL_UDPPKT) && rxctl & VGE_RDCTL_PROTOCSUMOK) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID|CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } if (rxstat & VGE_RDSTS_VTAG) { - VLAN_INPUT_TAG(ifp, m, - ntohs((rxctl & VGE_RDCTL_VLANID))); - if (m == NULL) - continue; + m->m_pkthdr.ether_vtag = + ntohs((rxctl & VGE_RDCTL_VLANID)); + m->m_flags |= M_VLANTAG; } VGE_UNLOCK(sc); (*ifp->if_input)(ifp, m); VGE_LOCK(sc); lim++; if (lim == VGE_RX_DESC_CNT) break; } /* Flush the RX DMA ring */ bus_dmamap_sync(sc->vge_ldata.vge_rx_list_tag, sc->vge_ldata.vge_rx_list_map, BUS_DMASYNC_PREWRITE|BUS_DMASYNC_PREREAD); sc->vge_ldata.vge_rx_prodidx = i; CSR_WRITE_2(sc, VGE_RXDESC_RESIDUECNT, lim); return; } static void vge_txeof(sc) struct vge_softc *sc; { struct ifnet *ifp; u_int32_t txstat; int idx; ifp = sc->vge_ifp; idx = sc->vge_ldata.vge_tx_considx; /* Invalidate the TX descriptor list */ bus_dmamap_sync(sc->vge_ldata.vge_tx_list_tag, sc->vge_ldata.vge_tx_list_map, BUS_DMASYNC_POSTREAD); while (idx != sc->vge_ldata.vge_tx_prodidx) { txstat = le32toh(sc->vge_ldata.vge_tx_list[idx].vge_sts); if (txstat & VGE_TDSTS_OWN) break; m_freem(sc->vge_ldata.vge_tx_mbuf[idx]); sc->vge_ldata.vge_tx_mbuf[idx] = NULL; bus_dmamap_unload(sc->vge_ldata.vge_mtag, sc->vge_ldata.vge_tx_dmamap[idx]); if (txstat & (VGE_TDSTS_EXCESSCOLL|VGE_TDSTS_COLL)) ifp->if_collisions++; if (txstat & VGE_TDSTS_TXERR) ifp->if_oerrors++; else ifp->if_opackets++; sc->vge_ldata.vge_tx_free++; VGE_TX_DESC_INC(idx); } /* No changes made to the TX ring, so no flush needed */ if (idx != sc->vge_ldata.vge_tx_considx) { sc->vge_ldata.vge_tx_considx = idx; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; ifp->if_timer = 0; } /* * If not all descriptors have been released reaped yet, * reload the timer so that we will eventually get another * interrupt that will cause us to re-enter this routine. * This is done in case the transmitter has gone idle. */ if (sc->vge_ldata.vge_tx_free != VGE_TX_DESC_CNT) { CSR_WRITE_1(sc, VGE_CRS1, VGE_CR1_TIMER0_ENABLE); } return; } static void vge_tick(xsc) void *xsc; { struct vge_softc *sc; struct ifnet *ifp; struct mii_data *mii; sc = xsc; ifp = sc->vge_ifp; VGE_LOCK(sc); mii = device_get_softc(sc->vge_miibus); mii_tick(mii); if (sc->vge_link) { if (!(mii->mii_media_status & IFM_ACTIVE)) { sc->vge_link = 0; if_link_state_change(sc->vge_ifp, LINK_STATE_DOWN); } } else { if (mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->vge_link = 1; if_link_state_change(sc->vge_ifp, LINK_STATE_UP); #if __FreeBSD_version < 502114 if (ifp->if_snd.ifq_head != NULL) #else if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) #endif taskqueue_enqueue(taskqueue_swi, &sc->vge_txtask); } } VGE_UNLOCK(sc); return; } #ifdef DEVICE_POLLING static void vge_poll (struct ifnet *ifp, enum poll_cmd cmd, int count) { struct vge_softc *sc = ifp->if_softc; VGE_LOCK(sc); if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) goto done; sc->rxcycles = count; vge_rxeof(sc); vge_txeof(sc); #if __FreeBSD_version < 502114 if (ifp->if_snd.ifq_head != NULL) #else if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) #endif taskqueue_enqueue(taskqueue_swi, &sc->vge_txtask); if (cmd == POLL_AND_CHECK_STATUS) { /* also check status register */ u_int32_t status; status = CSR_READ_4(sc, VGE_ISR); if (status == 0xFFFFFFFF) goto done; if (status) CSR_WRITE_4(sc, VGE_ISR, status); /* * XXX check behaviour on receiver stalls. */ if (status & VGE_ISR_TXDMA_STALL || status & VGE_ISR_RXDMA_STALL) vge_init(sc); if (status & (VGE_ISR_RXOFLOW|VGE_ISR_RXNODESC)) { vge_rxeof(sc); ifp->if_ierrors++; CSR_WRITE_1(sc, VGE_RXQCSRS, VGE_RXQCSR_RUN); CSR_WRITE_1(sc, VGE_RXQCSRS, VGE_RXQCSR_WAK); } } done: VGE_UNLOCK(sc); } #endif /* DEVICE_POLLING */ static void vge_intr(arg) void *arg; { struct vge_softc *sc; struct ifnet *ifp; u_int32_t status; sc = arg; if (sc->suspended) { return; } VGE_LOCK(sc); ifp = sc->vge_ifp; if (!(ifp->if_flags & IFF_UP)) { VGE_UNLOCK(sc); return; } #ifdef DEVICE_POLLING if (ifp->if_capenable & IFCAP_POLLING) { VGE_UNLOCK(sc); return; } #endif /* Disable interrupts */ CSR_WRITE_1(sc, VGE_CRC3, VGE_CR3_INT_GMSK); for (;;) { status = CSR_READ_4(sc, VGE_ISR); /* If the card has gone away the read returns 0xffff. */ if (status == 0xFFFFFFFF) break; if (status) CSR_WRITE_4(sc, VGE_ISR, status); if ((status & VGE_INTRS) == 0) break; if (status & (VGE_ISR_RXOK|VGE_ISR_RXOK_HIPRIO)) vge_rxeof(sc); if (status & (VGE_ISR_RXOFLOW|VGE_ISR_RXNODESC)) { vge_rxeof(sc); CSR_WRITE_1(sc, VGE_RXQCSRS, VGE_RXQCSR_RUN); CSR_WRITE_1(sc, VGE_RXQCSRS, VGE_RXQCSR_WAK); } if (status & (VGE_ISR_TXOK0|VGE_ISR_TIMER0)) vge_txeof(sc); if (status & (VGE_ISR_TXDMA_STALL|VGE_ISR_RXDMA_STALL)) vge_init(sc); if (status & VGE_ISR_LINKSTS) vge_tick(sc); } /* Re-enable interrupts */ CSR_WRITE_1(sc, VGE_CRS3, VGE_CR3_INT_GMSK); VGE_UNLOCK(sc); #if __FreeBSD_version < 502114 if (ifp->if_snd.ifq_head != NULL) #else if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) #endif taskqueue_enqueue(taskqueue_swi, &sc->vge_txtask); return; } static int vge_encap(sc, m_head, idx) struct vge_softc *sc; struct mbuf *m_head; int idx; { struct mbuf *m_new = NULL; struct vge_dmaload_arg arg; bus_dmamap_t map; int error; - struct m_tag *mtag; if (sc->vge_ldata.vge_tx_free <= 2) return (EFBIG); arg.vge_flags = 0; if (m_head->m_pkthdr.csum_flags & CSUM_IP) arg.vge_flags |= VGE_TDCTL_IPCSUM; if (m_head->m_pkthdr.csum_flags & CSUM_TCP) arg.vge_flags |= VGE_TDCTL_TCPCSUM; if (m_head->m_pkthdr.csum_flags & CSUM_UDP) arg.vge_flags |= VGE_TDCTL_UDPCSUM; arg.sc = sc; arg.vge_idx = idx; arg.vge_m0 = m_head; arg.vge_maxsegs = VGE_TX_FRAGS; map = sc->vge_ldata.vge_tx_dmamap[idx]; error = bus_dmamap_load_mbuf(sc->vge_ldata.vge_mtag, map, m_head, vge_dma_map_tx_desc, &arg, BUS_DMA_NOWAIT); if (error && error != EFBIG) { printf("vge%d: can't map mbuf (error %d)\n", sc->vge_unit, error); return (ENOBUFS); } /* Too many segments to map, coalesce into a single mbuf */ if (error || arg.vge_maxsegs == 0) { m_new = m_defrag(m_head, M_DONTWAIT); if (m_new == NULL) return (1); else m_head = m_new; arg.sc = sc; arg.vge_m0 = m_head; arg.vge_idx = idx; arg.vge_maxsegs = 1; error = bus_dmamap_load_mbuf(sc->vge_ldata.vge_mtag, map, m_head, vge_dma_map_tx_desc, &arg, BUS_DMA_NOWAIT); if (error) { printf("vge%d: can't map mbuf (error %d)\n", sc->vge_unit, error); return (EFBIG); } } sc->vge_ldata.vge_tx_mbuf[idx] = m_head; sc->vge_ldata.vge_tx_free--; /* * Set up hardware VLAN tagging. */ - mtag = VLAN_OUTPUT_TAG(sc->vge_ifp, m_head); - if (mtag != NULL) + if (m_head->m_flags & M_VLANTAG) sc->vge_ldata.vge_tx_list[idx].vge_ctl |= - htole32(htons(VLAN_TAG_VALUE(mtag)) | VGE_TDCTL_VTAG); + htole32(htons(m_head->m_pkthdr.ether_vtag) | VGE_TDCTL_VTAG); sc->vge_ldata.vge_tx_list[idx].vge_sts |= htole32(VGE_TDSTS_OWN); return (0); } static void vge_tx_task(arg, npending) void *arg; int npending; { struct ifnet *ifp; ifp = arg; vge_start(ifp); return; } /* * Main transmit routine. */ static void vge_start(ifp) struct ifnet *ifp; { struct vge_softc *sc; struct mbuf *m_head = NULL; int idx, pidx = 0; sc = ifp->if_softc; VGE_LOCK(sc); if (!sc->vge_link || ifp->if_drv_flags & IFF_DRV_OACTIVE) { VGE_UNLOCK(sc); return; } #if __FreeBSD_version < 502114 if (ifp->if_snd.ifq_head == NULL) { #else if (IFQ_DRV_IS_EMPTY(&ifp->if_snd)) { #endif VGE_UNLOCK(sc); return; } idx = sc->vge_ldata.vge_tx_prodidx; pidx = idx - 1; if (pidx < 0) pidx = VGE_TX_DESC_CNT - 1; while (sc->vge_ldata.vge_tx_mbuf[idx] == NULL) { #if __FreeBSD_version < 502114 IF_DEQUEUE(&ifp->if_snd, m_head); #else IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); #endif if (m_head == NULL) break; if (vge_encap(sc, m_head, idx)) { #if __FreeBSD_version >= 502114 IFQ_DRV_PREPEND(&ifp->if_snd, m_head); #else IF_PREPEND(&ifp->if_snd, m_head); #endif ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } sc->vge_ldata.vge_tx_list[pidx].vge_frag[0].vge_buflen |= htole16(VGE_TXDESC_Q); pidx = idx; VGE_TX_DESC_INC(idx); /* * If there's a BPF listener, bounce a copy of this frame * to him. */ BPF_MTAP(ifp, m_head); } if (idx == sc->vge_ldata.vge_tx_prodidx) { VGE_UNLOCK(sc); return; } /* Flush the TX descriptors */ bus_dmamap_sync(sc->vge_ldata.vge_tx_list_tag, sc->vge_ldata.vge_tx_list_map, BUS_DMASYNC_PREWRITE|BUS_DMASYNC_PREREAD); /* Issue a transmit command. */ CSR_WRITE_2(sc, VGE_TXQCSRS, VGE_TXQCSR_WAK0); sc->vge_ldata.vge_tx_prodidx = idx; /* * Use the countdown timer for interrupt moderation. * 'TX done' interrupts are disabled. Instead, we reset the * countdown timer, which will begin counting until it hits * the value in the SSTIMER register, and then trigger an * interrupt. Each time we set the TIMER0_ENABLE bit, the * the timer count is reloaded. Only when the transmitter * is idle will the timer hit 0 and an interrupt fire. */ CSR_WRITE_1(sc, VGE_CRS1, VGE_CR1_TIMER0_ENABLE); VGE_UNLOCK(sc); /* * Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; return; } static void vge_init(xsc) void *xsc; { struct vge_softc *sc = xsc; struct ifnet *ifp = sc->vge_ifp; struct mii_data *mii; int i; VGE_LOCK(sc); mii = device_get_softc(sc->vge_miibus); /* * Cancel pending I/O and free all RX/TX buffers. */ vge_stop(sc); vge_reset(sc); /* * Initialize the RX and TX descriptors and mbufs. */ vge_rx_list_init(sc); vge_tx_list_init(sc); /* Set our station address */ for (i = 0; i < ETHER_ADDR_LEN; i++) CSR_WRITE_1(sc, VGE_PAR0 + i, IF_LLADDR(sc->vge_ifp)[i]); /* * Set receive FIFO threshold. Also allow transmission and * reception of VLAN tagged frames. */ CSR_CLRBIT_1(sc, VGE_RXCFG, VGE_RXCFG_FIFO_THR|VGE_RXCFG_VTAGOPT); CSR_SETBIT_1(sc, VGE_RXCFG, VGE_RXFIFOTHR_128BYTES|VGE_VTAG_OPT2); /* Set DMA burst length */ CSR_CLRBIT_1(sc, VGE_DMACFG0, VGE_DMACFG0_BURSTLEN); CSR_SETBIT_1(sc, VGE_DMACFG0, VGE_DMABURST_128); CSR_SETBIT_1(sc, VGE_TXCFG, VGE_TXCFG_ARB_PRIO|VGE_TXCFG_NONBLK); /* Set collision backoff algorithm */ CSR_CLRBIT_1(sc, VGE_CHIPCFG1, VGE_CHIPCFG1_CRANDOM| VGE_CHIPCFG1_CAP|VGE_CHIPCFG1_MBA|VGE_CHIPCFG1_BAKOPT); CSR_SETBIT_1(sc, VGE_CHIPCFG1, VGE_CHIPCFG1_OFSET); /* Disable LPSEL field in priority resolution */ CSR_SETBIT_1(sc, VGE_DIAGCTL, VGE_DIAGCTL_LPSEL_DIS); /* * Load the addresses of the DMA queues into the chip. * Note that we only use one transmit queue. */ CSR_WRITE_4(sc, VGE_TXDESC_ADDR_LO0, VGE_ADDR_LO(sc->vge_ldata.vge_tx_list_addr)); CSR_WRITE_2(sc, VGE_TXDESCNUM, VGE_TX_DESC_CNT - 1); CSR_WRITE_4(sc, VGE_RXDESC_ADDR_LO, VGE_ADDR_LO(sc->vge_ldata.vge_rx_list_addr)); CSR_WRITE_2(sc, VGE_RXDESCNUM, VGE_RX_DESC_CNT - 1); CSR_WRITE_2(sc, VGE_RXDESC_RESIDUECNT, VGE_RX_DESC_CNT); /* Enable and wake up the RX descriptor queue */ CSR_WRITE_1(sc, VGE_RXQCSRS, VGE_RXQCSR_RUN); CSR_WRITE_1(sc, VGE_RXQCSRS, VGE_RXQCSR_WAK); /* Enable the TX descriptor queue */ CSR_WRITE_2(sc, VGE_TXQCSRS, VGE_TXQCSR_RUN0); /* Set up the receive filter -- allow large frames for VLANs. */ CSR_WRITE_1(sc, VGE_RXCTL, VGE_RXCTL_RX_UCAST|VGE_RXCTL_RX_GIANT); /* If we want promiscuous mode, set the allframes bit. */ if (ifp->if_flags & IFF_PROMISC) { CSR_SETBIT_1(sc, VGE_RXCTL, VGE_RXCTL_RX_PROMISC); } /* Set capture broadcast bit to capture broadcast frames. */ if (ifp->if_flags & IFF_BROADCAST) { CSR_SETBIT_1(sc, VGE_RXCTL, VGE_RXCTL_RX_BCAST); } /* Set multicast bit to capture multicast frames. */ if (ifp->if_flags & IFF_MULTICAST) { CSR_SETBIT_1(sc, VGE_RXCTL, VGE_RXCTL_RX_MCAST); } /* Init the cam filter. */ vge_cam_clear(sc); /* Init the multicast filter. */ vge_setmulti(sc); /* Enable flow control */ CSR_WRITE_1(sc, VGE_CRS2, 0x8B); /* Enable jumbo frame reception (if desired) */ /* Start the MAC. */ CSR_WRITE_1(sc, VGE_CRC0, VGE_CR0_STOP); CSR_WRITE_1(sc, VGE_CRS1, VGE_CR1_NOPOLL); CSR_WRITE_1(sc, VGE_CRS0, VGE_CR0_TX_ENABLE|VGE_CR0_RX_ENABLE|VGE_CR0_START); /* * Configure one-shot timer for microsecond * resulution and load it for 500 usecs. */ CSR_SETBIT_1(sc, VGE_DIAGCTL, VGE_DIAGCTL_TIMER0_RES); CSR_WRITE_2(sc, VGE_SSTIMER, 400); /* * Configure interrupt moderation for receive. Enable * the holdoff counter and load it, and set the RX * suppression count to the number of descriptors we * want to allow before triggering an interrupt. * The holdoff timer is in units of 20 usecs. */ #ifdef notyet CSR_WRITE_1(sc, VGE_INTCTL1, VGE_INTCTL_TXINTSUP_DISABLE); /* Select the interrupt holdoff timer page. */ CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL); CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_INTHLDOFF); CSR_WRITE_1(sc, VGE_INTHOLDOFF, 10); /* ~200 usecs */ /* Enable use of the holdoff timer. */ CSR_WRITE_1(sc, VGE_CRS3, VGE_CR3_INT_HOLDOFF); CSR_WRITE_1(sc, VGE_INTCTL1, VGE_INTCTL_SC_RELOAD); /* Select the RX suppression threshold page. */ CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL); CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_RXSUPPTHR); CSR_WRITE_1(sc, VGE_RXSUPPTHR, 64); /* interrupt after 64 packets */ /* Restore the page select bits. */ CSR_CLRBIT_1(sc, VGE_CAMCTL, VGE_CAMCTL_PAGESEL); CSR_SETBIT_1(sc, VGE_CAMCTL, VGE_PAGESEL_MAR); #endif #ifdef DEVICE_POLLING /* * Disable interrupts if we are polling. */ if (ifp->if_capenable & IFCAP_POLLING) { CSR_WRITE_4(sc, VGE_IMR, 0); CSR_WRITE_1(sc, VGE_CRC3, VGE_CR3_INT_GMSK); } else /* otherwise ... */ #endif { /* * Enable interrupts. */ CSR_WRITE_4(sc, VGE_IMR, VGE_INTRS); CSR_WRITE_4(sc, VGE_ISR, 0); CSR_WRITE_1(sc, VGE_CRS3, VGE_CR3_INT_GMSK); } mii_mediachg(mii); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->vge_if_flags = 0; sc->vge_link = 0; VGE_UNLOCK(sc); return; } /* * Set media options. */ static int vge_ifmedia_upd(ifp) struct ifnet *ifp; { struct vge_softc *sc; struct mii_data *mii; sc = ifp->if_softc; VGE_LOCK(sc); mii = device_get_softc(sc->vge_miibus); mii_mediachg(mii); VGE_UNLOCK(sc); return (0); } /* * Report current media status. */ static void vge_ifmedia_sts(ifp, ifmr) struct ifnet *ifp; struct ifmediareq *ifmr; { struct vge_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = device_get_softc(sc->vge_miibus); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; return; } static void vge_miibus_statchg(dev) device_t dev; { struct vge_softc *sc; struct mii_data *mii; struct ifmedia_entry *ife; sc = device_get_softc(dev); mii = device_get_softc(sc->vge_miibus); ife = mii->mii_media.ifm_cur; /* * If the user manually selects a media mode, we need to turn * on the forced MAC mode bit in the DIAGCTL register. If the * user happens to choose a full duplex mode, we also need to * set the 'force full duplex' bit. This applies only to * 10Mbps and 100Mbps speeds. In autoselect mode, forced MAC * mode is disabled, and in 1000baseT mode, full duplex is * always implied, so we turn on the forced mode bit but leave * the FDX bit cleared. */ switch (IFM_SUBTYPE(ife->ifm_media)) { case IFM_AUTO: CSR_CLRBIT_1(sc, VGE_DIAGCTL, VGE_DIAGCTL_MACFORCE); CSR_CLRBIT_1(sc, VGE_DIAGCTL, VGE_DIAGCTL_FDXFORCE); break; case IFM_1000_T: CSR_SETBIT_1(sc, VGE_DIAGCTL, VGE_DIAGCTL_MACFORCE); CSR_CLRBIT_1(sc, VGE_DIAGCTL, VGE_DIAGCTL_FDXFORCE); break; case IFM_100_TX: case IFM_10_T: CSR_SETBIT_1(sc, VGE_DIAGCTL, VGE_DIAGCTL_MACFORCE); if ((ife->ifm_media & IFM_GMASK) == IFM_FDX) { CSR_SETBIT_1(sc, VGE_DIAGCTL, VGE_DIAGCTL_FDXFORCE); } else { CSR_CLRBIT_1(sc, VGE_DIAGCTL, VGE_DIAGCTL_FDXFORCE); } break; default: device_printf(dev, "unknown media type: %x\n", IFM_SUBTYPE(ife->ifm_media)); break; } return; } static int vge_ioctl(ifp, command, data) struct ifnet *ifp; u_long command; caddr_t data; { struct vge_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *) data; struct mii_data *mii; int error = 0; switch (command) { case SIOCSIFMTU: if (ifr->ifr_mtu > VGE_JUMBO_MTU) error = EINVAL; ifp->if_mtu = ifr->ifr_mtu; break; case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) { if (ifp->if_drv_flags & IFF_DRV_RUNNING && ifp->if_flags & IFF_PROMISC && !(sc->vge_if_flags & IFF_PROMISC)) { CSR_SETBIT_1(sc, VGE_RXCTL, VGE_RXCTL_RX_PROMISC); vge_setmulti(sc); } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && !(ifp->if_flags & IFF_PROMISC) && sc->vge_if_flags & IFF_PROMISC) { CSR_CLRBIT_1(sc, VGE_RXCTL, VGE_RXCTL_RX_PROMISC); vge_setmulti(sc); } else vge_init(sc); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) vge_stop(sc); } sc->vge_if_flags = ifp->if_flags; break; case SIOCADDMULTI: case SIOCDELMULTI: vge_setmulti(sc); break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: mii = device_get_softc(sc->vge_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); break; case SIOCSIFCAP: { int mask = ifr->ifr_reqcap ^ ifp->if_capenable; #ifdef DEVICE_POLLING if (mask & IFCAP_POLLING) { if (ifr->ifr_reqcap & IFCAP_POLLING) { error = ether_poll_register(vge_poll, ifp); if (error) return(error); VGE_LOCK(sc); /* Disable interrupts */ CSR_WRITE_4(sc, VGE_IMR, 0); CSR_WRITE_1(sc, VGE_CRC3, VGE_CR3_INT_GMSK); ifp->if_capenable |= IFCAP_POLLING; VGE_UNLOCK(sc); } else { error = ether_poll_deregister(ifp); /* Enable interrupts. */ VGE_LOCK(sc); CSR_WRITE_4(sc, VGE_IMR, VGE_INTRS); CSR_WRITE_4(sc, VGE_ISR, 0xFFFFFFFF); CSR_WRITE_1(sc, VGE_CRS3, VGE_CR3_INT_GMSK); ifp->if_capenable &= ~IFCAP_POLLING; VGE_UNLOCK(sc); } } #endif /* DEVICE_POLLING */ if (mask & IFCAP_HWCSUM) { ifp->if_capenable |= ifr->ifr_reqcap & (IFCAP_HWCSUM); if (ifp->if_capenable & IFCAP_TXCSUM) ifp->if_hwassist = VGE_CSUM_FEATURES; else ifp->if_hwassist = 0; if (ifp->if_drv_flags & IFF_DRV_RUNNING) vge_init(sc); } } break; default: error = ether_ioctl(ifp, command, data); break; } return (error); } static void vge_watchdog(ifp) struct ifnet *ifp; { struct vge_softc *sc; sc = ifp->if_softc; VGE_LOCK(sc); printf("vge%d: watchdog timeout\n", sc->vge_unit); ifp->if_oerrors++; vge_txeof(sc); vge_rxeof(sc); vge_init(sc); VGE_UNLOCK(sc); return; } /* * Stop the adapter and free any mbufs allocated to the * RX and TX lists. */ static void vge_stop(sc) struct vge_softc *sc; { register int i; struct ifnet *ifp; VGE_LOCK(sc); ifp = sc->vge_ifp; ifp->if_timer = 0; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); CSR_WRITE_1(sc, VGE_CRC3, VGE_CR3_INT_GMSK); CSR_WRITE_1(sc, VGE_CRS0, VGE_CR0_STOP); CSR_WRITE_4(sc, VGE_ISR, 0xFFFFFFFF); CSR_WRITE_2(sc, VGE_TXQCSRC, 0xFFFF); CSR_WRITE_1(sc, VGE_RXQCSRC, 0xFF); CSR_WRITE_4(sc, VGE_RXDESC_ADDR_LO, 0); if (sc->vge_head != NULL) { m_freem(sc->vge_head); sc->vge_head = sc->vge_tail = NULL; } /* Free the TX list buffers. */ for (i = 0; i < VGE_TX_DESC_CNT; i++) { if (sc->vge_ldata.vge_tx_mbuf[i] != NULL) { bus_dmamap_unload(sc->vge_ldata.vge_mtag, sc->vge_ldata.vge_tx_dmamap[i]); m_freem(sc->vge_ldata.vge_tx_mbuf[i]); sc->vge_ldata.vge_tx_mbuf[i] = NULL; } } /* Free the RX list buffers. */ for (i = 0; i < VGE_RX_DESC_CNT; i++) { if (sc->vge_ldata.vge_rx_mbuf[i] != NULL) { bus_dmamap_unload(sc->vge_ldata.vge_mtag, sc->vge_ldata.vge_rx_dmamap[i]); m_freem(sc->vge_ldata.vge_rx_mbuf[i]); sc->vge_ldata.vge_rx_mbuf[i] = NULL; } } VGE_UNLOCK(sc); return; } /* * Device suspend routine. Stop the interface and save some PCI * settings in case the BIOS doesn't restore them properly on * resume. */ static int vge_suspend(dev) device_t dev; { struct vge_softc *sc; sc = device_get_softc(dev); vge_stop(sc); sc->suspended = 1; return (0); } /* * Device resume routine. Restore some PCI settings in case the BIOS * doesn't, re-enable busmastering, and restart the interface if * appropriate. */ static int vge_resume(dev) device_t dev; { struct vge_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); ifp = sc->vge_ifp; /* reenable busmastering */ pci_enable_busmaster(dev); pci_enable_io(dev, SYS_RES_MEMORY); /* reinitialize interface if necessary */ if (ifp->if_flags & IFF_UP) vge_init(sc); sc->suspended = 0; return (0); } /* * Stop all chip I/O so that the kernel's probe routines don't * get confused by errant DMAs when rebooting. */ static void vge_shutdown(dev) device_t dev; { struct vge_softc *sc; sc = device_get_softc(dev); vge_stop(sc); } Index: head/sys/net/if_vlan.c =================================================================== --- head/sys/net/if_vlan.c (revision 162374) +++ head/sys/net/if_vlan.c (revision 162375) @@ -1,1487 +1,1475 @@ /*- * Copyright 1998 Massachusetts Institute of Technology * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that both the above copyright notice and this * permission notice appear in all copies, that both the above * copyright notice and this permission notice appear in all * supporting documentation, and that the name of M.I.T. not be used * in advertising or publicity pertaining to distribution of the * software without specific, written prior permission. M.I.T. makes * no representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied * warranty. * * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT * SHALL M.I.T. 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. * * $FreeBSD$ */ /* * if_vlan.c - pseudo-device driver for IEEE 802.1Q virtual LANs. * Might be extended some day to also handle IEEE 802.1p priority * tagging. This is sort of sneaky in the implementation, since * we need to pretend to be enough of an Ethernet implementation * to make arp work. The way we do this is by telling everyone * that we are an Ethernet, and then catch the packets that * ether_output() left on our output queue when it calls * if_start(), rewrite them for use by the real outgoing interface, * and ask it to send them. */ #include "opt_inet.h" #include "opt_vlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #endif #define VLANNAME "vlan" #define VLAN_DEF_HWIDTH 4 #define VLAN_IFFLAGS (IFF_BROADCAST | IFF_MULTICAST) LIST_HEAD(ifvlanhead, ifvlan); struct ifvlantrunk { struct ifnet *parent; /* parent interface of this trunk */ struct rwlock rw; #ifdef VLAN_ARRAY #define VLAN_ARRAY_SIZE (EVL_VLID_MASK + 1) struct ifvlan *vlans[VLAN_ARRAY_SIZE]; /* static table */ #else struct ifvlanhead *hash; /* dynamic hash-list table */ uint16_t hmask; uint16_t hwidth; #endif int refcnt; LIST_ENTRY(ifvlantrunk) trunk_entry; }; static LIST_HEAD(, ifvlantrunk) trunk_list; struct vlan_mc_entry { struct ether_addr mc_addr; SLIST_ENTRY(vlan_mc_entry) mc_entries; }; struct ifvlan { struct ifvlantrunk *ifv_trunk; struct ifnet *ifv_ifp; #define TRUNK(ifv) ((ifv)->ifv_trunk) #define PARENT(ifv) ((ifv)->ifv_trunk->parent) int ifv_pflags; /* special flags we have set on parent */ struct ifv_linkmib { int ifvm_encaplen; /* encapsulation length */ int ifvm_mtufudge; /* MTU fudged by this much */ int ifvm_mintu; /* min transmission unit */ uint16_t ifvm_proto; /* encapsulation ethertype */ uint16_t ifvm_tag; /* tag to apply on packets leaving if */ } ifv_mib; SLIST_HEAD(, vlan_mc_entry) vlan_mc_listhead; LIST_ENTRY(ifvlan) ifv_list; }; #define ifv_proto ifv_mib.ifvm_proto #define ifv_tag ifv_mib.ifvm_tag #define ifv_encaplen ifv_mib.ifvm_encaplen #define ifv_mtufudge ifv_mib.ifvm_mtufudge #define ifv_mintu ifv_mib.ifvm_mintu /* Special flags we should propagate to parent. */ static struct { int flag; int (*func)(struct ifnet *, int); } vlan_pflags[] = { {IFF_PROMISC, ifpromisc}, {IFF_ALLMULTI, if_allmulti}, {0, NULL} }; SYSCTL_DECL(_net_link); SYSCTL_NODE(_net_link, IFT_L2VLAN, vlan, CTLFLAG_RW, 0, "IEEE 802.1Q VLAN"); SYSCTL_NODE(_net_link_vlan, PF_LINK, link, CTLFLAG_RW, 0, "for consistency"); static int soft_pad = 0; SYSCTL_INT(_net_link_vlan, OID_AUTO, soft_pad, CTLFLAG_RW, &soft_pad, 0, "pad short frames before tagging"); static MALLOC_DEFINE(M_VLAN, VLANNAME, "802.1Q Virtual LAN Interface"); static eventhandler_tag ifdetach_tag; /* * We have a global mutex, that is used to serialize configuration * changes and isn't used in normal packet delivery. * * We also have a per-trunk rwlock, that is locked shared on packet * processing and exclusive when configuration is changed. * * The VLAN_ARRAY substitutes the dynamic hash with a static array * with 4096 entries. In theory this can give a boost in processing, * however on practice it does not. Probably this is because array * is too big to fit into CPU cache. */ static struct mtx ifv_mtx; #define VLAN_LOCK_INIT() mtx_init(&ifv_mtx, "vlan_global", NULL, MTX_DEF) #define VLAN_LOCK_DESTROY() mtx_destroy(&ifv_mtx) #define VLAN_LOCK_ASSERT() mtx_assert(&ifv_mtx, MA_OWNED) #define VLAN_LOCK() mtx_lock(&ifv_mtx) #define VLAN_UNLOCK() mtx_unlock(&ifv_mtx) #define TRUNK_LOCK_INIT(trunk) rw_init(&(trunk)->rw, VLANNAME) #define TRUNK_LOCK_DESTROY(trunk) rw_destroy(&(trunk)->rw) #define TRUNK_LOCK(trunk) rw_wlock(&(trunk)->rw) #define TRUNK_UNLOCK(trunk) rw_wunlock(&(trunk)->rw) #define TRUNK_LOCK_ASSERT(trunk) rw_assert(&(trunk)->rw, RA_WLOCKED) #define TRUNK_RLOCK(trunk) rw_rlock(&(trunk)->rw) #define TRUNK_RUNLOCK(trunk) rw_runlock(&(trunk)->rw) #define TRUNK_LOCK_RASSERT(trunk) rw_assert(&(trunk)->rw, RA_RLOCKED) #ifndef VLAN_ARRAY static void vlan_inithash(struct ifvlantrunk *trunk); static void vlan_freehash(struct ifvlantrunk *trunk); static int vlan_inshash(struct ifvlantrunk *trunk, struct ifvlan *ifv); static int vlan_remhash(struct ifvlantrunk *trunk, struct ifvlan *ifv); static void vlan_growhash(struct ifvlantrunk *trunk, int howmuch); static __inline struct ifvlan * vlan_gethash(struct ifvlantrunk *trunk, uint16_t tag); #endif static void trunk_destroy(struct ifvlantrunk *trunk); static void vlan_start(struct ifnet *ifp); static void vlan_init(void *foo); static void vlan_input(struct ifnet *ifp, struct mbuf *m); static int vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t addr); static int vlan_setflag(struct ifnet *ifp, int flag, int status, int (*func)(struct ifnet *, int)); static int vlan_setflags(struct ifnet *ifp, int status); static int vlan_setmulti(struct ifnet *ifp); static int vlan_unconfig(struct ifnet *ifp); static int vlan_unconfig_locked(struct ifnet *ifp); static int vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t tag); static void vlan_link_state(struct ifnet *ifp, int link); static void vlan_capabilities(struct ifvlan *ifv); static void vlan_trunk_capabilities(struct ifnet *ifp); static struct ifnet *vlan_clone_match_ethertag(struct if_clone *, const char *, int *); static int vlan_clone_match(struct if_clone *, const char *); static int vlan_clone_create(struct if_clone *, char *, size_t, caddr_t); static int vlan_clone_destroy(struct if_clone *, struct ifnet *); static void vlan_ifdetach(void *arg, struct ifnet *ifp); static struct if_clone vlan_cloner = IFC_CLONE_INITIALIZER(VLANNAME, NULL, IF_MAXUNIT, NULL, vlan_clone_match, vlan_clone_create, vlan_clone_destroy); #ifndef VLAN_ARRAY #define HASH(n, m) ((((n) >> 8) ^ ((n) >> 4) ^ (n)) & (m)) static void vlan_inithash(struct ifvlantrunk *trunk) { int i, n; /* * The trunk must not be locked here since we call malloc(M_WAITOK). * It is OK in case this function is called before the trunk struct * gets hooked up and becomes visible from other threads. */ KASSERT(trunk->hwidth == 0 && trunk->hash == NULL, ("%s: hash already initialized", __func__)); trunk->hwidth = VLAN_DEF_HWIDTH; n = 1 << trunk->hwidth; trunk->hmask = n - 1; trunk->hash = malloc(sizeof(struct ifvlanhead) * n, M_VLAN, M_WAITOK); for (i = 0; i < n; i++) LIST_INIT(&trunk->hash[i]); } static void vlan_freehash(struct ifvlantrunk *trunk) { #ifdef INVARIANTS int i; KASSERT(trunk->hwidth > 0, ("%s: hwidth not positive", __func__)); for (i = 0; i < (1 << trunk->hwidth); i++) KASSERT(LIST_EMPTY(&trunk->hash[i]), ("%s: hash table not empty", __func__)); #endif free(trunk->hash, M_VLAN); trunk->hash = NULL; trunk->hwidth = trunk->hmask = 0; } static int vlan_inshash(struct ifvlantrunk *trunk, struct ifvlan *ifv) { int i, b; struct ifvlan *ifv2; TRUNK_LOCK_ASSERT(trunk); KASSERT(trunk->hwidth > 0, ("%s: hwidth not positive", __func__)); b = 1 << trunk->hwidth; i = HASH(ifv->ifv_tag, trunk->hmask); LIST_FOREACH(ifv2, &trunk->hash[i], ifv_list) if (ifv->ifv_tag == ifv2->ifv_tag) return (EEXIST); /* * Grow the hash when the number of vlans exceeds half of the number of * hash buckets squared. This will make the average linked-list length * buckets/2. */ if (trunk->refcnt > (b * b) / 2) { vlan_growhash(trunk, 1); i = HASH(ifv->ifv_tag, trunk->hmask); } LIST_INSERT_HEAD(&trunk->hash[i], ifv, ifv_list); trunk->refcnt++; return (0); } static int vlan_remhash(struct ifvlantrunk *trunk, struct ifvlan *ifv) { int i, b; struct ifvlan *ifv2; TRUNK_LOCK_ASSERT(trunk); KASSERT(trunk->hwidth > 0, ("%s: hwidth not positive", __func__)); b = 1 << trunk->hwidth; i = HASH(ifv->ifv_tag, trunk->hmask); LIST_FOREACH(ifv2, &trunk->hash[i], ifv_list) if (ifv2 == ifv) { trunk->refcnt--; LIST_REMOVE(ifv2, ifv_list); if (trunk->refcnt < (b * b) / 2) vlan_growhash(trunk, -1); return (0); } panic("%s: vlan not found\n", __func__); return (ENOENT); /*NOTREACHED*/ } /* * Grow the hash larger or smaller if memory permits. */ static void vlan_growhash(struct ifvlantrunk *trunk, int howmuch) { struct ifvlan *ifv; struct ifvlanhead *hash2; int hwidth2, i, j, n, n2; TRUNK_LOCK_ASSERT(trunk); KASSERT(trunk->hwidth > 0, ("%s: hwidth not positive", __func__)); if (howmuch == 0) { /* Harmless yet obvious coding error */ printf("%s: howmuch is 0\n", __func__); return; } hwidth2 = trunk->hwidth + howmuch; n = 1 << trunk->hwidth; n2 = 1 << hwidth2; /* Do not shrink the table below the default */ if (hwidth2 < VLAN_DEF_HWIDTH) return; /* M_NOWAIT because we're called with trunk mutex held */ hash2 = malloc(sizeof(struct ifvlanhead) * n2, M_VLAN, M_NOWAIT); if (hash2 == NULL) { printf("%s: out of memory -- hash size not changed\n", __func__); return; /* We can live with the old hash table */ } for (j = 0; j < n2; j++) LIST_INIT(&hash2[j]); for (i = 0; i < n; i++) while (!LIST_EMPTY(&trunk->hash[i])) { ifv = LIST_FIRST(&trunk->hash[i]); LIST_REMOVE(ifv, ifv_list); j = HASH(ifv->ifv_tag, n2 - 1); LIST_INSERT_HEAD(&hash2[j], ifv, ifv_list); } free(trunk->hash, M_VLAN); trunk->hash = hash2; trunk->hwidth = hwidth2; trunk->hmask = n2 - 1; } static __inline struct ifvlan * vlan_gethash(struct ifvlantrunk *trunk, uint16_t tag) { struct ifvlan *ifv; TRUNK_LOCK_RASSERT(trunk); LIST_FOREACH(ifv, &trunk->hash[HASH(tag, trunk->hmask)], ifv_list) if (ifv->ifv_tag == tag) return (ifv); return (NULL); } #if 0 /* Debugging code to view the hashtables. */ static void vlan_dumphash(struct ifvlantrunk *trunk) { int i; struct ifvlan *ifv; for (i = 0; i < (1 << trunk->hwidth); i++) { printf("%d: ", i); LIST_FOREACH(ifv, &trunk->hash[i], ifv_list) printf("%s ", ifv->ifv_ifp->if_xname); printf("\n"); } } #endif /* 0 */ #endif /* !VLAN_ARRAY */ static void trunk_destroy(struct ifvlantrunk *trunk) { VLAN_LOCK_ASSERT(); TRUNK_LOCK(trunk); #ifndef VLAN_ARRAY vlan_freehash(trunk); #endif trunk->parent->if_vlantrunk = NULL; LIST_REMOVE(trunk, trunk_entry); TRUNK_UNLOCK(trunk); TRUNK_LOCK_DESTROY(trunk); free(trunk, M_VLAN); } /* * Program our multicast filter. What we're actually doing is * programming the multicast filter of the parent. This has the * side effect of causing the parent interface to receive multicast * traffic that it doesn't really want, which ends up being discarded * later by the upper protocol layers. Unfortunately, there's no way * to avoid this: there really is only one physical interface. * * XXX: There is a possible race here if more than one thread is * modifying the multicast state of the vlan interface at the same time. */ static int vlan_setmulti(struct ifnet *ifp) { struct ifnet *ifp_p; struct ifmultiaddr *ifma, *rifma = NULL; struct ifvlan *sc; struct vlan_mc_entry *mc = NULL; struct sockaddr_dl sdl; int error; /*VLAN_LOCK_ASSERT();*/ /* Find the parent. */ sc = ifp->if_softc; ifp_p = PARENT(sc); bzero((char *)&sdl, sizeof(sdl)); sdl.sdl_len = sizeof(sdl); sdl.sdl_family = AF_LINK; sdl.sdl_index = ifp_p->if_index; sdl.sdl_type = IFT_ETHER; sdl.sdl_alen = ETHER_ADDR_LEN; /* First, remove any existing filter entries. */ while (SLIST_FIRST(&sc->vlan_mc_listhead) != NULL) { mc = SLIST_FIRST(&sc->vlan_mc_listhead); bcopy((char *)&mc->mc_addr, LLADDR(&sdl), ETHER_ADDR_LEN); error = if_delmulti(ifp_p, (struct sockaddr *)&sdl); if (error) return (error); SLIST_REMOVE_HEAD(&sc->vlan_mc_listhead, mc_entries); free(mc, M_VLAN); } /* Now program new ones. */ TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; mc = malloc(sizeof(struct vlan_mc_entry), M_VLAN, M_NOWAIT); if (mc == NULL) return (ENOMEM); bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr), (char *)&mc->mc_addr, ETHER_ADDR_LEN); SLIST_INSERT_HEAD(&sc->vlan_mc_listhead, mc, mc_entries); bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr), LLADDR(&sdl), ETHER_ADDR_LEN); error = if_addmulti(ifp_p, (struct sockaddr *)&sdl, &rifma); if (error) return (error); } return (0); } /* * A handler for network interface departure events. * Track departure of trunks here so that we don't access invalid * pointers or whatever if a trunk is ripped from under us, e.g., * by ejecting its hot-plug card. */ static void vlan_ifdetach(void *arg __unused, struct ifnet *ifp) { struct ifvlan *ifv; int i; /* * Check if it's a trunk interface first of all * to avoid needless locking. */ if (ifp->if_vlantrunk == NULL) return; VLAN_LOCK(); /* * OK, it's a trunk. Loop over and detach all vlan's on it. * Check trunk pointer after each vlan_unconfig() as it will * free it and set to NULL after the last vlan was detached. */ #ifdef VLAN_ARRAY for (i = 0; i < VLAN_ARRAY_SIZE; i++) if ((ifv = ifp->if_vlantrunk->vlans[i])) { vlan_unconfig_locked(ifv->ifv_ifp); if (ifp->if_vlantrunk == NULL) break; } #else /* VLAN_ARRAY */ restart: for (i = 0; i < (1 << ifp->if_vlantrunk->hwidth); i++) if ((ifv = LIST_FIRST(&ifp->if_vlantrunk->hash[i]))) { vlan_unconfig_locked(ifv->ifv_ifp); if (ifp->if_vlantrunk) goto restart; /* trunk->hwidth can change */ else break; } #endif /* VLAN_ARRAY */ /* Trunk should have been destroyed in vlan_unconfig(). */ KASSERT(ifp->if_vlantrunk == NULL, ("%s: purge failed", __func__)); VLAN_UNLOCK(); } /* * VLAN support can be loaded as a module. The only place in the * system that's intimately aware of this is ether_input. We hook * into this code through vlan_input_p which is defined there and * set here. Noone else in the system should be aware of this so * we use an explicit reference here. */ extern void (*vlan_input_p)(struct ifnet *, struct mbuf *); /* For if_link_state_change() eyes only... */ extern void (*vlan_link_state_p)(struct ifnet *, int); static int vlan_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: ifdetach_tag = EVENTHANDLER_REGISTER(ifnet_departure_event, vlan_ifdetach, NULL, EVENTHANDLER_PRI_ANY); if (ifdetach_tag == NULL) return (ENOMEM); LIST_INIT(&trunk_list); VLAN_LOCK_INIT(); vlan_input_p = vlan_input; vlan_link_state_p = vlan_link_state; vlan_trunk_cap_p = vlan_trunk_capabilities; if_clone_attach(&vlan_cloner); break; case MOD_UNLOAD: { struct ifvlantrunk *trunk, *trunk1; if_clone_detach(&vlan_cloner); EVENTHANDLER_DEREGISTER(ifnet_departure_event, ifdetach_tag); vlan_input_p = NULL; vlan_link_state_p = NULL; vlan_trunk_cap_p = NULL; VLAN_LOCK(); LIST_FOREACH_SAFE(trunk, &trunk_list, trunk_entry, trunk1) trunk_destroy(trunk); VLAN_UNLOCK(); VLAN_LOCK_DESTROY(); break; } default: return (EOPNOTSUPP); } return (0); } static moduledata_t vlan_mod = { "if_vlan", vlan_modevent, 0 }; DECLARE_MODULE(if_vlan, vlan_mod, SI_SUB_PSEUDO, SI_ORDER_ANY); MODULE_VERSION(if_vlan, 3); MODULE_DEPEND(if_vlan, miibus, 1, 1, 1); static struct ifnet * vlan_clone_match_ethertag(struct if_clone *ifc, const char *name, int *tag) { const char *cp; struct ifnet *ifp; int t = 0; /* Check for . style interface names. */ IFNET_RLOCK(); TAILQ_FOREACH(ifp, &ifnet, if_link) { if (ifp->if_type != IFT_ETHER) continue; if (strncmp(ifp->if_xname, name, strlen(ifp->if_xname)) != 0) continue; cp = name + strlen(ifp->if_xname); if (*cp != '.') continue; for(; *cp != '\0'; cp++) { if (*cp < '0' || *cp > '9') continue; t = (t * 10) + (*cp - '0'); } if (tag != NULL) *tag = t; break; } IFNET_RUNLOCK(); return (ifp); } static int vlan_clone_match(struct if_clone *ifc, const char *name) { const char *cp; if (vlan_clone_match_ethertag(ifc, name, NULL) != NULL) return (1); if (strncmp(VLANNAME, name, strlen(VLANNAME)) != 0) return (0); for (cp = name + 4; *cp != '\0'; cp++) { if (*cp < '0' || *cp > '9') return (0); } return (1); } static int vlan_clone_create(struct if_clone *ifc, char *name, size_t len, caddr_t params) { char *dp; int wildcard; int unit; int error; int tag; int ethertag; struct ifvlan *ifv; struct ifnet *ifp; struct ifnet *p; struct vlanreq vlr; static const u_char eaddr[6]; /* 00:00:00:00:00:00 */ /* * There are 3 (ugh) ways to specify the cloned device: * o pass a parameter block with the clone request. * o specify parameters in the text of the clone device name * o specify no parameters and get an unattached device that * must be configured separately. * The first technique is preferred; the latter two are * supported for backwards compatibilty. */ if (params) { error = copyin(params, &vlr, sizeof(vlr)); if (error) return error; p = ifunit(vlr.vlr_parent); if (p == NULL) return ENXIO; /* * Don't let the caller set up a VLAN tag with * anything except VLID bits. */ if (vlr.vlr_tag & ~EVL_VLID_MASK) return (EINVAL); error = ifc_name2unit(name, &unit); if (error != 0) return (error); ethertag = 1; tag = vlr.vlr_tag; wildcard = (unit < 0); } else if ((p = vlan_clone_match_ethertag(ifc, name, &tag)) != NULL) { ethertag = 1; unit = -1; wildcard = 0; /* * Don't let the caller set up a VLAN tag with * anything except VLID bits. */ if (tag & ~EVL_VLID_MASK) return (EINVAL); } else { ethertag = 0; error = ifc_name2unit(name, &unit); if (error != 0) return (error); wildcard = (unit < 0); } error = ifc_alloc_unit(ifc, &unit); if (error != 0) return (error); /* In the wildcard case, we need to update the name. */ if (wildcard) { for (dp = name; *dp != '\0'; dp++); if (snprintf(dp, len - (dp-name), "%d", unit) > len - (dp-name) - 1) { panic("%s: interface name too long", __func__); } } ifv = malloc(sizeof(struct ifvlan), M_VLAN, M_WAITOK | M_ZERO); ifp = ifv->ifv_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { ifc_free_unit(ifc, unit); free(ifv, M_VLAN); return (ENOSPC); } SLIST_INIT(&ifv->vlan_mc_listhead); ifp->if_softc = ifv; /* * Set the name manually rather than using if_initname because * we don't conform to the default naming convention for interfaces. */ strlcpy(ifp->if_xname, name, IFNAMSIZ); ifp->if_dname = ifc->ifc_name; ifp->if_dunit = unit; /* NB: flags are not set here */ ifp->if_linkmib = &ifv->ifv_mib; ifp->if_linkmiblen = sizeof(ifv->ifv_mib); /* NB: mtu is not set here */ ifp->if_init = vlan_init; ifp->if_start = vlan_start; ifp->if_ioctl = vlan_ioctl; ifp->if_snd.ifq_maxlen = ifqmaxlen; ifp->if_flags = VLAN_IFFLAGS; ether_ifattach(ifp, eaddr); /* Now undo some of the damage... */ ifp->if_baudrate = 0; ifp->if_type = IFT_L2VLAN; ifp->if_hdrlen = ETHER_VLAN_ENCAP_LEN; if (ethertag) { error = vlan_config(ifv, p, tag); if (error != 0) { /* * Since we've partialy failed, we need to back * out all the way, otherwise userland could get * confused. Thus, we destroy the interface. */ ether_ifdetach(ifp); vlan_unconfig(ifp); if_free_type(ifp, IFT_ETHER); free(ifv, M_VLAN); return (error); } /* Update flags on the parent, if necessary. */ vlan_setflags(ifp, 1); } return (0); } static int vlan_clone_destroy(struct if_clone *ifc, struct ifnet *ifp) { struct ifvlan *ifv = ifp->if_softc; int unit = ifp->if_dunit; ether_ifdetach(ifp); /* first, remove it from system-wide lists */ vlan_unconfig(ifp); /* now it can be unconfigured and freed */ if_free_type(ifp, IFT_ETHER); free(ifv, M_VLAN); ifc_free_unit(ifc, unit); return (0); } /* * The ifp->if_init entry point for vlan(4) is a no-op. */ static void vlan_init(void *foo __unused) { } /* * The if_start method for vlan(4) interface. It doesn't * raises the IFF_DRV_OACTIVE flag, since it is called * only from IFQ_HANDOFF() macro in ether_output_frame(). * If the interface queue is full, and vlan_start() is * not called, the queue would never get emptied and * interface would stall forever. */ static void vlan_start(struct ifnet *ifp) { struct ifvlan *ifv; struct ifnet *p; struct mbuf *m; int error; ifv = ifp->if_softc; p = PARENT(ifv); for (;;) { IF_DEQUEUE(&ifp->if_snd, m); if (m == 0) break; BPF_MTAP(ifp, m); /* * Do not run parent's if_start() if the parent is not up, * or parent's driver will cause a system crash. */ if (!((p->if_flags & IFF_UP) && (p->if_drv_flags & IFF_DRV_RUNNING))) { m_freem(m); ifp->if_collisions++; continue; } /* * Pad the frame to the minimum size allowed if told to. * This option is in accord with IEEE Std 802.1Q, 2003 Ed., * paragraph C.4.4.3.b. It can help to work around buggy * bridges that violate paragraph C.4.4.3.a from the same * document, i.e., fail to pad short frames after untagging. * E.g., a tagged frame 66 bytes long (incl. FCS) is OK, but * untagging it will produce a 62-byte frame, which is a runt * and requires padding. There are VLAN-enabled network * devices that just discard such runts instead or mishandle * them somehow. */ if (soft_pad) { static char pad[8]; /* just zeros */ int n; for (n = ETHERMIN + ETHER_HDR_LEN - m->m_pkthdr.len; n > 0; n -= sizeof(pad)) if (!m_append(m, min(n, sizeof(pad)), pad)) break; if (n > 0) { if_printf(ifp, "cannot pad short frame\n"); ifp->if_oerrors++; m_freem(m); continue; } } /* * If underlying interface can do VLAN tag insertion itself, * just pass the packet along. However, we need some way to * tell the interface where the packet came from so that it * knows how to find the VLAN tag to use, so we attach a * packet tag that holds it. */ if (p->if_capenable & IFCAP_VLAN_HWTAGGING) { - struct m_tag *mtag = (struct m_tag *) - uma_zalloc(zone_mtag_vlan, M_NOWAIT); - if (mtag == NULL) { - ifp->if_oerrors++; - m_freem(m); - continue; - } - VLAN_TAG_VALUE(mtag) = ifv->ifv_tag; - m_tag_prepend(m, mtag); + m->m_pkthdr.ether_vtag = ifv->ifv_tag; m->m_flags |= M_VLANTAG; } else { struct ether_vlan_header *evl; M_PREPEND(m, ifv->ifv_encaplen, M_DONTWAIT); if (m == NULL) { if_printf(ifp, "unable to prepend VLAN header\n"); ifp->if_oerrors++; continue; } /* M_PREPEND takes care of m_len, m_pkthdr.len for us */ if (m->m_len < sizeof(*evl)) { m = m_pullup(m, sizeof(*evl)); if (m == NULL) { if_printf(ifp, "cannot pullup VLAN header\n"); ifp->if_oerrors++; continue; } } /* * Transform the Ethernet header into an Ethernet header * with 802.1Q encapsulation. */ bcopy(mtod(m, char *) + ifv->ifv_encaplen, mtod(m, char *), ETHER_HDR_LEN); evl = mtod(m, struct ether_vlan_header *); evl->evl_proto = evl->evl_encap_proto; evl->evl_encap_proto = htons(ifv->ifv_proto); evl->evl_tag = htons(ifv->ifv_tag); #ifdef DEBUG printf("%s: %*D\n", __func__, (int)sizeof(*evl), (unsigned char *)evl, ":"); #endif } /* * Send it, precisely as ether_output() would have. * We are already running at splimp. */ IFQ_HANDOFF(p, m, error); if (!error) ifp->if_opackets++; else ifp->if_oerrors++; } } static void vlan_input(struct ifnet *ifp, struct mbuf *m) { struct ifvlantrunk *trunk = ifp->if_vlantrunk; struct ifvlan *ifv; - struct m_tag *mtag; + int inenc = 0; uint16_t tag; KASSERT(trunk != NULL, ("%s: no trunk", __func__)); if (m->m_flags & M_VLANTAG) { /* * Packet is tagged, but m contains a normal * Ethernet frame; the tag is stored out-of-band. */ - mtag = m_tag_locate(m, MTAG_VLAN, MTAG_VLAN_TAG, NULL); - KASSERT(mtag != NULL, - ("%s: M_VLANTAG without m_tag", __func__)); - tag = EVL_VLANOFTAG(VLAN_TAG_VALUE(mtag)); - m_tag_delete(m, mtag); + tag = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag); m->m_flags &= ~M_VLANTAG; } else { struct ether_vlan_header *evl; /* * Packet is tagged in-band as specified by 802.1q. */ - mtag = NULL; + inenc = 1; switch (ifp->if_type) { case IFT_ETHER: if (m->m_len < sizeof(*evl) && (m = m_pullup(m, sizeof(*evl))) == NULL) { if_printf(ifp, "cannot pullup VLAN header\n"); return; } evl = mtod(m, struct ether_vlan_header *); tag = EVL_VLANOFTAG(ntohs(evl->evl_tag)); /* * Restore the original ethertype. We'll remove * the encapsulation after we've found the vlan * interface corresponding to the tag. */ evl->evl_encap_proto = evl->evl_proto; break; default: #ifdef INVARIANTS panic("%s: %s has unsupported if_type %u", __func__, ifp->if_xname, ifp->if_type); #endif m_freem(m); ifp->if_noproto++; return; } } TRUNK_RLOCK(trunk); #ifdef VLAN_ARRAY ifv = trunk->vlans[tag]; #else ifv = vlan_gethash(trunk, tag); #endif if (ifv == NULL || (ifv->ifv_ifp->if_flags & IFF_UP) == 0) { TRUNK_RUNLOCK(trunk); m_freem(m); ifp->if_noproto++; return; } TRUNK_RUNLOCK(trunk); - if (mtag == NULL) { + if (inenc) { /* * Packet had an in-line encapsulation header; * remove it. The original header has already * been fixed up above. */ bcopy(mtod(m, caddr_t), mtod(m, caddr_t) + ETHER_VLAN_ENCAP_LEN, ETHER_HDR_LEN); m_adj(m, ETHER_VLAN_ENCAP_LEN); } m->m_pkthdr.rcvif = ifv->ifv_ifp; ifv->ifv_ifp->if_ipackets++; /* Pass it back through the parent's input routine. */ (*ifp->if_input)(ifv->ifv_ifp, m); } static int vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t tag) { struct ifvlantrunk *trunk; struct ifnet *ifp; int error = 0; /* VID numbers 0x0 and 0xFFF are reserved */ if (tag == 0 || tag == 0xFFF) return (EINVAL); if (p->if_type != IFT_ETHER) return (EPROTONOSUPPORT); if ((p->if_flags & VLAN_IFFLAGS) != VLAN_IFFLAGS) return (EPROTONOSUPPORT); if (ifv->ifv_trunk) return (EBUSY); if (p->if_vlantrunk == NULL) { trunk = malloc(sizeof(struct ifvlantrunk), M_VLAN, M_WAITOK | M_ZERO); #ifndef VLAN_ARRAY vlan_inithash(trunk); #endif VLAN_LOCK(); if (p->if_vlantrunk != NULL) { /* A race that that is very unlikely to be hit. */ #ifndef VLAN_ARRAY vlan_freehash(trunk); #endif free(trunk, M_VLAN); goto exists; } TRUNK_LOCK_INIT(trunk); LIST_INSERT_HEAD(&trunk_list, trunk, trunk_entry); TRUNK_LOCK(trunk); p->if_vlantrunk = trunk; trunk->parent = p; } else { VLAN_LOCK(); exists: trunk = p->if_vlantrunk; TRUNK_LOCK(trunk); } ifv->ifv_tag = tag; /* must set this before vlan_inshash() */ #ifdef VLAN_ARRAY if (trunk->vlans[tag] != NULL) { error = EEXIST; goto done; } trunk->vlans[tag] = ifv; trunk->refcnt++; #else error = vlan_inshash(trunk, ifv); if (error) goto done; #endif ifv->ifv_proto = ETHERTYPE_VLAN; ifv->ifv_encaplen = ETHER_VLAN_ENCAP_LEN; ifv->ifv_mintu = ETHERMIN; ifv->ifv_pflags = 0; /* * If the parent supports the VLAN_MTU capability, * i.e. can Tx/Rx larger than ETHER_MAX_LEN frames, * use it. */ if (p->if_capenable & IFCAP_VLAN_MTU) { /* * No need to fudge the MTU since the parent can * handle extended frames. */ ifv->ifv_mtufudge = 0; } else { /* * Fudge the MTU by the encapsulation size. This * makes us incompatible with strictly compliant * 802.1Q implementations, but allows us to use * the feature with other NetBSD implementations, * which might still be useful. */ ifv->ifv_mtufudge = ifv->ifv_encaplen; } ifv->ifv_trunk = trunk; ifp = ifv->ifv_ifp; ifp->if_mtu = p->if_mtu - ifv->ifv_mtufudge; ifp->if_baudrate = p->if_baudrate; /* * Copy only a selected subset of flags from the parent. * Other flags are none of our business. */ #define VLAN_COPY_FLAGS (IFF_SIMPLEX) ifp->if_flags &= ~VLAN_COPY_FLAGS; ifp->if_flags |= p->if_flags & VLAN_COPY_FLAGS; #undef VLAN_COPY_FLAGS ifp->if_link_state = p->if_link_state; vlan_capabilities(ifv); /* * Set up our ``Ethernet address'' to reflect the underlying * physical interface's. */ bcopy(IF_LLADDR(p), IF_LLADDR(ifp), ETHER_ADDR_LEN); /* * Configure multicast addresses that may already be * joined on the vlan device. */ (void)vlan_setmulti(ifp); /* XXX: VLAN lock held */ /* We are ready for operation now. */ ifp->if_drv_flags |= IFF_DRV_RUNNING; done: TRUNK_UNLOCK(trunk); VLAN_UNLOCK(); return (error); } static int vlan_unconfig(struct ifnet *ifp) { int ret; VLAN_LOCK(); ret = vlan_unconfig_locked(ifp); VLAN_UNLOCK(); return (ret); } static int vlan_unconfig_locked(struct ifnet *ifp) { struct ifvlantrunk *trunk; struct vlan_mc_entry *mc; struct ifvlan *ifv; int error; VLAN_LOCK_ASSERT(); ifv = ifp->if_softc; trunk = ifv->ifv_trunk; if (trunk) { struct sockaddr_dl sdl; struct ifnet *p = trunk->parent; TRUNK_LOCK(trunk); /* * Since the interface is being unconfigured, we need to * empty the list of multicast groups that we may have joined * while we were alive from the parent's list. */ bzero((char *)&sdl, sizeof(sdl)); sdl.sdl_len = sizeof(sdl); sdl.sdl_family = AF_LINK; sdl.sdl_index = p->if_index; sdl.sdl_type = IFT_ETHER; sdl.sdl_alen = ETHER_ADDR_LEN; while(SLIST_FIRST(&ifv->vlan_mc_listhead) != NULL) { mc = SLIST_FIRST(&ifv->vlan_mc_listhead); bcopy((char *)&mc->mc_addr, LLADDR(&sdl), ETHER_ADDR_LEN); error = if_delmulti(p, (struct sockaddr *)&sdl); if (error) return (error); SLIST_REMOVE_HEAD(&ifv->vlan_mc_listhead, mc_entries); free(mc, M_VLAN); } vlan_setflags(ifp, 0); /* clear special flags on parent */ #ifdef VLAN_ARRAY trunk->vlans[ifv->ifv_tag] = NULL; trunk->refcnt--; #else vlan_remhash(trunk, ifv); #endif ifv->ifv_trunk = NULL; /* * Check if we were the last. */ if (trunk->refcnt == 0) { trunk->parent->if_vlantrunk = NULL; /* * XXXGL: If some ithread has already entered * vlan_input() and is now blocked on the trunk * lock, then it should preempt us right after * unlock and finish its work. Then we will acquire * lock again in trunk_destroy(). */ TRUNK_UNLOCK(trunk); trunk_destroy(trunk); } else TRUNK_UNLOCK(trunk); } /* Disconnect from parent. */ if (ifv->ifv_pflags) if_printf(ifp, "%s: ifv_pflags unclean\n", __func__); ifp->if_mtu = ETHERMTU; ifp->if_link_state = LINK_STATE_UNKNOWN; ifp->if_drv_flags &= ~IFF_DRV_RUNNING; return (0); } /* Handle a reference counted flag that should be set on the parent as well */ static int vlan_setflag(struct ifnet *ifp, int flag, int status, int (*func)(struct ifnet *, int)) { struct ifvlan *ifv; int error; /* XXX VLAN_LOCK_ASSERT(); */ ifv = ifp->if_softc; status = status ? (ifp->if_flags & flag) : 0; /* Now "status" contains the flag value or 0 */ /* * See if recorded parent's status is different from what * we want it to be. If it is, flip it. We record parent's * status in ifv_pflags so that we won't clear parent's flag * we haven't set. In fact, we don't clear or set parent's * flags directly, but get or release references to them. * That's why we can be sure that recorded flags still are * in accord with actual parent's flags. */ if (status != (ifv->ifv_pflags & flag)) { error = (*func)(PARENT(ifv), status); if (error) return (error); ifv->ifv_pflags &= ~flag; ifv->ifv_pflags |= status; } return (0); } /* * Handle IFF_* flags that require certain changes on the parent: * if "status" is true, update parent's flags respective to our if_flags; * if "status" is false, forcedly clear the flags set on parent. */ static int vlan_setflags(struct ifnet *ifp, int status) { int error, i; for (i = 0; vlan_pflags[i].flag; i++) { error = vlan_setflag(ifp, vlan_pflags[i].flag, status, vlan_pflags[i].func); if (error) return (error); } return (0); } /* Inform all vlans that their parent has changed link state */ static void vlan_link_state(struct ifnet *ifp, int link) { struct ifvlantrunk *trunk = ifp->if_vlantrunk; struct ifvlan *ifv; int i; TRUNK_LOCK(trunk); #ifdef VLAN_ARRAY for (i = 0; i < VLAN_ARRAY_SIZE; i++) if (trunk->vlans[i] != NULL) { ifv = trunk->vlans[i]; #else for (i = 0; i < (1 << trunk->hwidth); i++) { LIST_FOREACH(ifv, &trunk->hash[i], ifv_list) #endif if_link_state_change(ifv->ifv_ifp, trunk->parent->if_link_state); } TRUNK_UNLOCK(trunk); } static void vlan_capabilities(struct ifvlan *ifv) { struct ifnet *p = PARENT(ifv); struct ifnet *ifp = ifv->ifv_ifp; TRUNK_LOCK_ASSERT(TRUNK(ifv)); /* * If the parent interface can do checksum offloading * on VLANs, then propagate its hardware-assisted * checksumming flags. Also assert that checksum * offloading requires hardware VLAN tagging. */ if (p->if_capabilities & IFCAP_VLAN_HWCSUM) ifp->if_capabilities = p->if_capabilities & IFCAP_HWCSUM; if (p->if_capenable & IFCAP_VLAN_HWCSUM && p->if_capenable & IFCAP_VLAN_HWTAGGING) { ifp->if_capenable = p->if_capenable & IFCAP_HWCSUM; ifp->if_hwassist = p->if_hwassist; } else { ifp->if_capenable = 0; ifp->if_hwassist = 0; } } static void vlan_trunk_capabilities(struct ifnet *ifp) { struct ifvlantrunk *trunk = ifp->if_vlantrunk; struct ifvlan *ifv; int i; TRUNK_LOCK(trunk); #ifdef VLAN_ARRAY for (i = 0; i < VLAN_ARRAY_SIZE; i++) if (trunk->vlans[i] != NULL) { ifv = trunk->vlans[i]; #else for (i = 0; i < (1 << trunk->hwidth); i++) { LIST_FOREACH(ifv, &trunk->hash[i], ifv_list) #endif vlan_capabilities(ifv); } TRUNK_UNLOCK(trunk); } static int vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct ifaddr *ifa; struct ifnet *p; struct ifreq *ifr; struct ifvlan *ifv; struct vlanreq vlr; int error = 0; ifr = (struct ifreq *)data; ifa = (struct ifaddr *)data; ifv = ifp->if_softc; switch (cmd) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: arp_ifinit(ifv->ifv_ifp, ifa); break; #endif default: break; } break; case SIOCGIFADDR: { struct sockaddr *sa; sa = (struct sockaddr *) &ifr->ifr_data; bcopy(IF_LLADDR(ifp), (caddr_t)sa->sa_data, ETHER_ADDR_LEN); } break; case SIOCGIFMEDIA: VLAN_LOCK(); if (TRUNK(ifv) != NULL) { error = (*PARENT(ifv)->if_ioctl)(PARENT(ifv), SIOCGIFMEDIA, data); VLAN_UNLOCK(); /* Limit the result to the parent's current config. */ if (error == 0) { struct ifmediareq *ifmr; ifmr = (struct ifmediareq *)data; if (ifmr->ifm_count >= 1 && ifmr->ifm_ulist) { ifmr->ifm_count = 1; error = copyout(&ifmr->ifm_current, ifmr->ifm_ulist, sizeof(int)); } } } else { VLAN_UNLOCK(); error = EINVAL; } break; case SIOCSIFMEDIA: error = EINVAL; break; case SIOCSIFMTU: /* * Set the interface MTU. */ VLAN_LOCK(); if (TRUNK(ifv) != NULL) { if (ifr->ifr_mtu > (PARENT(ifv)->if_mtu - ifv->ifv_mtufudge) || ifr->ifr_mtu < (ifv->ifv_mintu - ifv->ifv_mtufudge)) error = EINVAL; else ifp->if_mtu = ifr->ifr_mtu; } else error = EINVAL; VLAN_UNLOCK(); break; case SIOCSETVLAN: error = copyin(ifr->ifr_data, &vlr, sizeof(vlr)); if (error) break; if (vlr.vlr_parent[0] == '\0') { vlan_unconfig(ifp); break; } p = ifunit(vlr.vlr_parent); if (p == 0) { error = ENOENT; break; } /* * Don't let the caller set up a VLAN tag with * anything except VLID bits. */ if (vlr.vlr_tag & ~EVL_VLID_MASK) { error = EINVAL; break; } error = vlan_config(ifv, p, vlr.vlr_tag); if (error) break; /* Update flags on the parent, if necessary. */ vlan_setflags(ifp, 1); break; case SIOCGETVLAN: bzero(&vlr, sizeof(vlr)); VLAN_LOCK(); if (TRUNK(ifv) != NULL) { strlcpy(vlr.vlr_parent, PARENT(ifv)->if_xname, sizeof(vlr.vlr_parent)); vlr.vlr_tag = ifv->ifv_tag; } VLAN_UNLOCK(); error = copyout(&vlr, ifr->ifr_data, sizeof(vlr)); break; case SIOCSIFFLAGS: /* * We should propagate selected flags to the parent, * e.g., promiscuous mode. */ if (TRUNK(ifv) != NULL) error = vlan_setflags(ifp, 1); break; case SIOCADDMULTI: case SIOCDELMULTI: /* * If we don't have a parent, just remember the membership for * when we do. */ if (TRUNK(ifv) != NULL) error = vlan_setmulti(ifp); break; default: error = EINVAL; } return (error); } Index: head/sys/net/if_vlan_var.h =================================================================== --- head/sys/net/if_vlan_var.h (revision 162374) +++ head/sys/net/if_vlan_var.h (revision 162375) @@ -1,129 +1,106 @@ /*- * Copyright 1998 Massachusetts Institute of Technology * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that both the above copyright notice and this * permission notice appear in all copies, that both the above * copyright notice and this permission notice appear in all * supporting documentation, and that the name of M.I.T. not be used * in advertising or publicity pertaining to distribution of the * software without specific, written prior permission. M.I.T. makes * no representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied * warranty. * * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT * SHALL M.I.T. 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. * * $FreeBSD$ */ #ifndef _NET_IF_VLAN_VAR_H_ #define _NET_IF_VLAN_VAR_H_ 1 struct ether_vlan_header { u_char evl_dhost[ETHER_ADDR_LEN]; u_char evl_shost[ETHER_ADDR_LEN]; u_int16_t evl_encap_proto; u_int16_t evl_tag; u_int16_t evl_proto; }; #define EVL_VLID_MASK 0x0FFF #define EVL_VLANOFTAG(tag) ((tag) & EVL_VLID_MASK) #define EVL_PRIOFTAG(tag) (((tag) >> 13) & 7) /* sysctl(3) tags, for compatibility purposes */ #define VLANCTL_PROTO 1 #define VLANCTL_MAX 2 /* * Configuration structure for SIOCSETVLAN and SIOCGETVLAN ioctls. */ struct vlanreq { char vlr_parent[IFNAMSIZ]; u_short vlr_tag; }; #define SIOCSETVLAN SIOCSIFGENERIC #define SIOCGETVLAN SIOCGIFGENERIC #ifdef _KERNEL /* * Drivers that are capable of adding and removing the VLAN header * in hardware indicate they support this by marking IFCAP_VLAN_HWTAGGING * in if_capabilities. Drivers for hardware that is capable * of handling larger MTU's that may include a software-appended * VLAN header w/o lowering the normal MTU should mark IFCAP_VLAN_MTU * in if_capabilities; this notifies the VLAN code it can leave the * MTU on the vlan interface at the normal setting. */ /* - * Drivers that support hardware VLAN tagging pass a packet's tag - * up through the stack by appending a packet tag with this value. - * Output is handled likewise, the driver must locate the packet - * tag to extract the VLAN tag. The following macros are used to - * do this work. On input, do: + * VLAN tags are stored in host byte order. Byte swapping may be + * necessary. * - * VLAN_INPUT_TAG(ifp, m, tag,); + * Drivers that support hardware VLAN tag stripping fill in the + * received VLAN tag (containing both vlan and priority information) + * into the ether_vtag mbuf packet header field: + * + * m->m_pkthdr.ether_vtag = vlan_id; // ntohs()? + * m->m_flags |= M_VLANTAG; * - * to mark the packet m with the specified VLAN tag. The last - * parameter provides code to execute in case of an error. On - * output the driver should check mbuf to see if a VLAN tag is - * present and only then check for a tag; this is done with: + * to mark the packet m with the specified VLAN tag. * - * struct m_tag *mtag; - * mtag = VLAN_OUTPUT_TAG(ifp, m); - * if (mtag != NULL) { - * ... = VLAN_TAG_VALUE(mtag); + * On output the driver should check the mbuf for the M_VLANTAG + * flag to see if a VLAN tag is present and valid: + * + * if (m->m_flags & M_VLANTAG) { + * ... = m->m_pkthdr.ether_vtag; // htons()? * ... pass tag to hardware ... * } * * Note that a driver must indicate it supports hardware VLAN - * tagging by marking IFCAP_VLAN_HWTAGGING in if_capabilities. + * stripping/insertion by marking IFCAP_VLAN_HWTAGGING in + * if_capabilities. */ - -/* - * This macro must expand to a lvalue so that it can be used - * to set a tag with a simple assignment. - */ -#define VLAN_TAG_VALUE(_mt) (*(u_int *)((_mt) + 1)) - -#define VLAN_INPUT_TAG(_ifp, _m, _t) do { \ - struct m_tag *mtag = (struct m_tag *) \ - uma_zalloc(zone_mtag_vlan, M_NOWAIT); \ - if (mtag != NULL) { \ - VLAN_TAG_VALUE(mtag) = (_t); \ - m_tag_prepend((_m), mtag); \ - (_m)->m_flags |= M_VLANTAG; \ - } else { \ - (_ifp)->if_ierrors++; \ - m_freem(_m); \ - _m = NULL; \ - } \ -} while (0) - -#define VLAN_OUTPUT_TAG(_ifp, _m) \ - ((_m)->m_flags & M_VLANTAG ? \ - m_tag_locate((_m), MTAG_VLAN, MTAG_VLAN_TAG, NULL) : NULL) #define VLAN_CAPABILITIES(_ifp) do { \ if ((_ifp)->if_vlantrunk != NULL) \ (*vlan_trunk_cap_p)(_ifp); \ } while (0) extern void (*vlan_trunk_cap_p)(struct ifnet *); #endif /* _KERNEL */ #endif /* _NET_IF_VLAN_VAR_H_ */ Index: head/sys/net80211/ieee80211_input.c =================================================================== --- head/sys/net80211/ieee80211_input.c (revision 162374) +++ head/sys/net80211/ieee80211_input.c (revision 162375) @@ -1,2872 +1,2864 @@ /*- * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Alternatively, this software may be distributed under the terms of the * GNU General Public License ("GPL") version 2 as published by the Free * Software Foundation. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_DEBUG #include /* * Decide if a received management frame should be * printed when debugging is enabled. This filters some * of the less interesting frames that come frequently * (e.g. beacons). */ static __inline int doprint(struct ieee80211com *ic, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BEACON: return (ic->ic_flags & IEEE80211_F_SCAN); case IEEE80211_FC0_SUBTYPE_PROBE_REQ: return (ic->ic_opmode == IEEE80211_M_IBSS); } return 1; } /* * Emit a debug message about discarding a frame or information * element. One format is for extracting the mac address from * the frame header; the other is for when a header is not * available or otherwise appropriate. */ #define IEEE80211_DISCARD(_ic, _m, _wh, _type, _fmt, ...) do { \ if ((_ic)->ic_debug & (_m)) \ ieee80211_discard_frame(_ic, _wh, _type, _fmt, __VA_ARGS__);\ } while (0) #define IEEE80211_DISCARD_IE(_ic, _m, _wh, _type, _fmt, ...) do { \ if ((_ic)->ic_debug & (_m)) \ ieee80211_discard_ie(_ic, _wh, _type, _fmt, __VA_ARGS__);\ } while (0) #define IEEE80211_DISCARD_MAC(_ic, _m, _mac, _type, _fmt, ...) do { \ if ((_ic)->ic_debug & (_m)) \ ieee80211_discard_mac(_ic, _mac, _type, _fmt, __VA_ARGS__);\ } while (0) static const u_int8_t *ieee80211_getbssid(struct ieee80211com *, const struct ieee80211_frame *); static void ieee80211_discard_frame(struct ieee80211com *, const struct ieee80211_frame *, const char *type, const char *fmt, ...); static void ieee80211_discard_ie(struct ieee80211com *, const struct ieee80211_frame *, const char *type, const char *fmt, ...); static void ieee80211_discard_mac(struct ieee80211com *, const u_int8_t mac[IEEE80211_ADDR_LEN], const char *type, const char *fmt, ...); #else #define IEEE80211_DISCARD(_ic, _m, _wh, _type, _fmt, ...) #define IEEE80211_DISCARD_IE(_ic, _m, _wh, _type, _fmt, ...) #define IEEE80211_DISCARD_MAC(_ic, _m, _mac, _type, _fmt, ...) #endif /* IEEE80211_DEBUG */ static struct mbuf *ieee80211_defrag(struct ieee80211com *, struct ieee80211_node *, struct mbuf *, int); static struct mbuf *ieee80211_decap(struct ieee80211com *, struct mbuf *, int); static void ieee80211_send_error(struct ieee80211com *, struct ieee80211_node *, const u_int8_t *mac, int subtype, int arg); static void ieee80211_deliver_data(struct ieee80211com *, struct ieee80211_node *, struct mbuf *); static void ieee80211_node_pwrsave(struct ieee80211_node *, int enable); static void ieee80211_recv_pspoll(struct ieee80211com *, struct ieee80211_node *, struct mbuf *); /* * Process a received frame. The node associated with the sender * should be supplied. If nothing was found in the node table then * the caller is assumed to supply a reference to ic_bss instead. * The RSSI and a timestamp are also supplied. The RSSI data is used * during AP scanning to select a AP to associate with; it can have * any units so long as values have consistent units and higher values * mean ``better signal''. The receive timestamp is currently not used * by the 802.11 layer. */ int ieee80211_input(struct ieee80211com *ic, struct mbuf *m, struct ieee80211_node *ni, int rssi, u_int32_t rstamp) { #define SEQ_LEQ(a,b) ((int)((a)-(b)) <= 0) #define HAS_SEQ(type) ((type & 0x4) == 0) struct ifnet *ifp = ic->ic_ifp; struct ieee80211_frame *wh; struct ieee80211_key *key; struct ether_header *eh; int hdrspace; u_int8_t dir, type, subtype; u_int8_t *bssid; u_int16_t rxseq; KASSERT(ni != NULL, ("null node")); ni->ni_inact = ni->ni_inact_reload; /* trim CRC here so WEP can find its own CRC at the end of packet. */ if (m->m_flags & M_HASFCS) { m_adj(m, -IEEE80211_CRC_LEN); m->m_flags &= ~M_HASFCS; } type = -1; /* undefined */ /* * In monitor mode, send everything directly to bpf. * XXX may want to include the CRC */ if (ic->ic_opmode == IEEE80211_M_MONITOR) goto out; if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_min)) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (1): len %u", m->m_pkthdr.len); ic->ic_stats.is_rx_tooshort++; goto out; } /* * Bit of a cheat here, we use a pointer for a 3-address * frame format but don't reference fields past outside * ieee80211_frame_min w/o first validating the data is * present. */ wh = mtod(m, struct ieee80211_frame *); if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != IEEE80211_FC0_VERSION_0) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "wrong version %x", wh->i_fc[0]); ic->ic_stats.is_rx_badversion++; goto err; } dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { switch (ic->ic_opmode) { case IEEE80211_M_STA: bssid = wh->i_addr2; if (!IEEE80211_ADDR_EQ(bssid, ni->ni_bssid)) { /* not interested in */ IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, bssid, NULL, "%s", "not to bss"); ic->ic_stats.is_rx_wrongbss++; goto out; } break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: case IEEE80211_M_HOSTAP: if (dir != IEEE80211_FC1_DIR_NODS) bssid = wh->i_addr1; else if (type == IEEE80211_FC0_TYPE_CTL) bssid = wh->i_addr1; else { if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (2): len %u", m->m_pkthdr.len); ic->ic_stats.is_rx_tooshort++; goto out; } bssid = wh->i_addr3; } if (type != IEEE80211_FC0_TYPE_DATA) break; /* * Data frame, validate the bssid. */ if (!IEEE80211_ADDR_EQ(bssid, ic->ic_bss->ni_bssid) && !IEEE80211_ADDR_EQ(bssid, ifp->if_broadcastaddr)) { /* not interested in */ IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, bssid, NULL, "%s", "not to bss"); ic->ic_stats.is_rx_wrongbss++; goto out; } /* * For adhoc mode we cons up a node when it doesn't * exist. This should probably done after an ACL check. */ if (ni == ic->ic_bss && ic->ic_opmode != IEEE80211_M_HOSTAP && !IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { /* * Fake up a node for this newly * discovered member of the IBSS. */ ni = ieee80211_fakeup_adhoc_node(&ic->ic_sta, wh->i_addr2); if (ni == NULL) { /* NB: stat kept for alloc failure */ goto err; } } break; default: goto out; } ni->ni_rssi = rssi; ni->ni_rstamp = rstamp; if (HAS_SEQ(type)) { u_int8_t tid; if (IEEE80211_QOS_HAS_SEQ(wh)) { tid = ((struct ieee80211_qosframe *)wh)-> i_qos[0] & IEEE80211_QOS_TID; if (TID_TO_WME_AC(tid) >= WME_AC_VI) ic->ic_wme.wme_hipri_traffic++; tid++; } else tid = 0; rxseq = le16toh(*(u_int16_t *)wh->i_seq); if ((wh->i_fc[1] & IEEE80211_FC1_RETRY) && SEQ_LEQ(rxseq, ni->ni_rxseqs[tid])) { /* duplicate, discard */ IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, bssid, "duplicate", "seqno <%u,%u> fragno <%u,%u> tid %u", rxseq >> IEEE80211_SEQ_SEQ_SHIFT, ni->ni_rxseqs[tid] >> IEEE80211_SEQ_SEQ_SHIFT, rxseq & IEEE80211_SEQ_FRAG_MASK, ni->ni_rxseqs[tid] & IEEE80211_SEQ_FRAG_MASK, tid); ic->ic_stats.is_rx_dup++; IEEE80211_NODE_STAT(ni, rx_dup); goto out; } ni->ni_rxseqs[tid] = rxseq; } } switch (type) { case IEEE80211_FC0_TYPE_DATA: hdrspace = ieee80211_hdrspace(ic, wh); if (m->m_len < hdrspace && (m = m_pullup(m, hdrspace)) == NULL) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "data too short: expecting %u", hdrspace); ic->ic_stats.is_rx_tooshort++; goto out; /* XXX */ } switch (ic->ic_opmode) { case IEEE80211_M_STA: if (dir != IEEE80211_FC1_DIR_FROMDS) { IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, "data", "%s", "unknown dir 0x%x", dir); ic->ic_stats.is_rx_wrongdir++; goto out; } if ((ifp->if_flags & IFF_SIMPLEX) && IEEE80211_IS_MULTICAST(wh->i_addr1) && IEEE80211_ADDR_EQ(wh->i_addr3, ic->ic_myaddr)) { /* * In IEEE802.11 network, multicast packet * sent from me is broadcasted from AP. * It should be silently discarded for * SIMPLEX interface. */ IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, NULL, "%s", "multicast echo"); ic->ic_stats.is_rx_mcastecho++; goto out; } break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: if (dir != IEEE80211_FC1_DIR_NODS) { IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, "data", "%s", "unknown dir 0x%x", dir); ic->ic_stats.is_rx_wrongdir++; goto out; } /* XXX no power-save support */ break; case IEEE80211_M_HOSTAP: if (dir != IEEE80211_FC1_DIR_TODS) { IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, "data", "%s", "unknown dir 0x%x", dir); ic->ic_stats.is_rx_wrongdir++; goto out; } /* check if source STA is associated */ if (ni == ic->ic_bss) { IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, "data", "%s", "unknown src"); ieee80211_send_error(ic, ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_NOT_AUTHED); ic->ic_stats.is_rx_notassoc++; goto err; } if (ni->ni_associd == 0) { IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, "data", "%s", "unassoc src"); IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_NOT_ASSOCED); ic->ic_stats.is_rx_notassoc++; goto err; } /* * Check for power save state change. */ if (((wh->i_fc[1] & IEEE80211_FC1_PWR_MGT) ^ (ni->ni_flags & IEEE80211_NODE_PWR_MGT))) ieee80211_node_pwrsave(ni, wh->i_fc[1] & IEEE80211_FC1_PWR_MGT); break; default: /* XXX here to keep compiler happy */ goto out; } /* * Handle privacy requirements. Note that we * must not be preempted from here until after * we (potentially) call ieee80211_crypto_demic; * otherwise we may violate assumptions in the * crypto cipher modules used to do delayed update * of replay sequence numbers. */ if (wh->i_fc[1] & IEEE80211_FC1_WEP) { if ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, "WEP", "%s", "PRIVACY off"); ic->ic_stats.is_rx_noprivacy++; IEEE80211_NODE_STAT(ni, rx_noprivacy); goto out; } key = ieee80211_crypto_decap(ic, ni, m, hdrspace); if (key == NULL) { /* NB: stats+msgs handled in crypto_decap */ IEEE80211_NODE_STAT(ni, rx_wepfail); goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_WEP; } else { key = NULL; } /* * Next up, any fragmentation. */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { m = ieee80211_defrag(ic, ni, m, hdrspace); if (m == NULL) { /* Fragment dropped or frame not complete yet */ goto out; } } wh = NULL; /* no longer valid, catch any uses */ /* * Next strip any MSDU crypto bits. */ if (key != NULL && !ieee80211_crypto_demic(ic, key, m, 0)) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "demic error"); ic->ic_stats.is_rx_demicfail++; IEEE80211_NODE_STAT(ni, rx_demicfail); goto out; } /* copy to listener after decrypt */ if (bpf_peers_present(ic->ic_rawbpf)) bpf_mtap(ic->ic_rawbpf, m); /* * Finally, strip the 802.11 header. */ m = ieee80211_decap(ic, m, hdrspace); if (m == NULL) { /* don't count Null data frames as errors */ if (subtype == IEEE80211_FC0_SUBTYPE_NODATA) goto out; IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "decap error"); ic->ic_stats.is_rx_decap++; IEEE80211_NODE_STAT(ni, rx_decap); goto err; } eh = mtod(m, struct ether_header *); if (!ieee80211_node_is_authorized(ni)) { /* * Deny any non-PAE frames received prior to * authorization. For open/shared-key * authentication the port is mark authorized * after authentication completes. For 802.1x * the port is not marked authorized by the * authenticator until the handshake has completed. */ if (eh->ether_type != htons(ETHERTYPE_PAE)) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_INPUT, eh->ether_shost, "data", "unauthorized port: ether type 0x%x len %u", eh->ether_type, m->m_pkthdr.len); ic->ic_stats.is_rx_unauth++; IEEE80211_NODE_STAT(ni, rx_unauth); goto err; } } else { /* * When denying unencrypted frames, discard * any non-PAE frames received without encryption. */ if ((ic->ic_flags & IEEE80211_F_DROPUNENC) && key == NULL && eh->ether_type != htons(ETHERTYPE_PAE)) { /* * Drop unencrypted frames. */ ic->ic_stats.is_rx_unencrypted++; IEEE80211_NODE_STAT(ni, rx_unencrypted); goto out; } } ieee80211_deliver_data(ic, ni, m); return IEEE80211_FC0_TYPE_DATA; case IEEE80211_FC0_TYPE_MGT: ic->ic_stats.is_rx_mgmt++; IEEE80211_NODE_STAT(ni, rx_mgmt); if (dir != IEEE80211_FC1_DIR_NODS) { IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, "data", "%s", "unknown dir 0x%x", dir); ic->ic_stats.is_rx_wrongdir++; goto err; } if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_ANY, ni->ni_macaddr, "mgt", "too short: len %u", m->m_pkthdr.len); ic->ic_stats.is_rx_tooshort++; goto out; } #ifdef IEEE80211_DEBUG if ((ieee80211_msg_debug(ic) && doprint(ic, subtype)) || ieee80211_msg_dumppkts(ic)) { if_printf(ic->ic_ifp, "received %s from %s rssi %d\n", ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], ether_sprintf(wh->i_addr2), rssi); } #endif if (wh->i_fc[1] & IEEE80211_FC1_WEP) { if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) { /* * Only shared key auth frames with a challenge * should be encrypted, discard all others. */ IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], "%s", "WEP set but not permitted"); ic->ic_stats.is_rx_mgtdiscard++; /* XXX */ goto out; } if ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, "mgt", "%s", "WEP set but PRIVACY off"); ic->ic_stats.is_rx_noprivacy++; goto out; } hdrspace = ieee80211_hdrspace(ic, wh); key = ieee80211_crypto_decap(ic, ni, m, hdrspace); if (key == NULL) { /* NB: stats+msgs handled in crypto_decap */ goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_WEP; } if (bpf_peers_present(ic->ic_rawbpf)) bpf_mtap(ic->ic_rawbpf, m); (*ic->ic_recv_mgmt)(ic, m, ni, subtype, rssi, rstamp); m_freem(m); return type; case IEEE80211_FC0_TYPE_CTL: ic->ic_stats.is_rx_ctl++; IEEE80211_NODE_STAT(ni, rx_ctrl); if (ic->ic_opmode == IEEE80211_M_HOSTAP) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_PS_POLL: ieee80211_recv_pspoll(ic, ni, m); break; } } goto out; default: IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY, wh, NULL, "bad frame type 0x%x", type); /* should not come here */ break; } err: ifp->if_ierrors++; out: if (m != NULL) { if (bpf_peers_present(ic->ic_rawbpf)) bpf_mtap(ic->ic_rawbpf, m); m_freem(m); } return type; #undef SEQ_LEQ } /* * This function reassemble fragments. */ static struct mbuf * ieee80211_defrag(struct ieee80211com *ic, struct ieee80211_node *ni, struct mbuf *m, int hdrspace) { struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); struct ieee80211_frame *lwh; u_int16_t rxseq; u_int8_t fragno; u_int8_t more_frag = wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG; struct mbuf *mfrag; KASSERT(!IEEE80211_IS_MULTICAST(wh->i_addr1), ("multicast fragm?")); rxseq = le16toh(*(u_int16_t *)wh->i_seq); fragno = rxseq & IEEE80211_SEQ_FRAG_MASK; /* Quick way out, if there's nothing to defragment */ if (!more_frag && fragno == 0 && ni->ni_rxfrag[0] == NULL) return m; /* * Remove frag to insure it doesn't get reaped by timer. */ if (ni->ni_table == NULL) { /* * Should never happen. If the node is orphaned (not in * the table) then input packets should not reach here. * Otherwise, a concurrent request that yanks the table * should be blocked by other interlocking and/or by first * shutting the driver down. Regardless, be defensive * here and just bail */ /* XXX need msg+stat */ m_freem(m); return NULL; } IEEE80211_NODE_LOCK(ni->ni_table); mfrag = ni->ni_rxfrag[0]; ni->ni_rxfrag[0] = NULL; IEEE80211_NODE_UNLOCK(ni->ni_table); /* * Validate new fragment is in order and * related to the previous ones. */ if (mfrag != NULL) { u_int16_t last_rxseq; lwh = mtod(mfrag, struct ieee80211_frame *); last_rxseq = le16toh(*(u_int16_t *)lwh->i_seq); /* NB: check seq # and frag together */ if (rxseq != last_rxseq+1 || !IEEE80211_ADDR_EQ(wh->i_addr1, lwh->i_addr1) || !IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2)) { /* * Unrelated fragment or no space for it, * clear current fragments. */ m_freem(mfrag); mfrag = NULL; } } if (mfrag == NULL) { if (fragno != 0) { /* !first fragment, discard */ ic->ic_stats.is_rx_defrag++; IEEE80211_NODE_STAT(ni, rx_defrag); m_freem(m); return NULL; } mfrag = m; } else { /* concatenate */ m_adj(m, hdrspace); /* strip header */ m_cat(mfrag, m); /* NB: m_cat doesn't update the packet header */ mfrag->m_pkthdr.len += m->m_pkthdr.len; /* track last seqnum and fragno */ lwh = mtod(mfrag, struct ieee80211_frame *); *(u_int16_t *) lwh->i_seq = *(u_int16_t *) wh->i_seq; } if (more_frag) { /* more to come, save */ ni->ni_rxfragstamp = ticks; ni->ni_rxfrag[0] = mfrag; mfrag = NULL; } return mfrag; } static void ieee80211_deliver_data(struct ieee80211com *ic, struct ieee80211_node *ni, struct mbuf *m) { struct ether_header *eh = mtod(m, struct ether_header *); struct ifnet *ifp = ic->ic_ifp; /* * Do accounting. */ ifp->if_ipackets++; IEEE80211_NODE_STAT(ni, rx_data); IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len); if (ETHER_IS_MULTICAST(eh->ether_dhost)) { m->m_flags |= M_MCAST; /* XXX M_BCAST? */ IEEE80211_NODE_STAT(ni, rx_mcast); } else IEEE80211_NODE_STAT(ni, rx_ucast); /* perform as a bridge within the AP */ if (ic->ic_opmode == IEEE80211_M_HOSTAP && (ic->ic_flags & IEEE80211_F_NOBRIDGE) == 0) { struct mbuf *m1 = NULL; if (m->m_flags & M_MCAST) { m1 = m_dup(m, M_DONTWAIT); if (m1 == NULL) ifp->if_oerrors++; else m1->m_flags |= M_MCAST; } else { /* * Check if the destination is known; if so * and the port is authorized dispatch directly. */ struct ieee80211_node *sta = ieee80211_find_node(&ic->ic_sta, eh->ether_dhost); if (sta != NULL) { if (ieee80211_node_is_authorized(sta)) { /* * Beware of sending to ourself; this * needs to happen via the normal * input path. */ if (sta != ic->ic_bss) { m1 = m; m = NULL; } } else { ic->ic_stats.is_rx_unauth++; IEEE80211_NODE_STAT(sta, rx_unauth); } ieee80211_free_node(sta); } } if (m1 != NULL) IF_HANDOFF(&ifp->if_snd, m1, ifp); } if (m != NULL) { m->m_pkthdr.rcvif = ifp; if (ni->ni_vlan != 0) { /* attach vlan tag */ - VLAN_INPUT_TAG(ifp, m, ni->ni_vlan); - if (m == NULL) - goto out; /* XXX goto err? */ + m->m_pkthdr.ether_vtag = ni->ni_vlan; + m->m_flags |= M_VLANTAG; } (*ifp->if_input)(ifp, m); - } - return; - out: - if (m != NULL) { - if (bpf_peers_present(ic->ic_rawbpf)) - bpf_mtap(ic->ic_rawbpf, m); - m_freem(m); } } static struct mbuf * ieee80211_decap(struct ieee80211com *ic, struct mbuf *m, int hdrlen) { struct ieee80211_qosframe_addr4 wh; /* Max size address frames */ struct ether_header *eh; struct llc *llc; if (m->m_len < hdrlen + sizeof(*llc) && (m = m_pullup(m, hdrlen + sizeof(*llc))) == NULL) { /* XXX stat, msg */ return NULL; } memcpy(&wh, mtod(m, caddr_t), hdrlen); llc = (struct llc *)(mtod(m, caddr_t) + hdrlen); if (llc->llc_dsap == LLC_SNAP_LSAP && llc->llc_ssap == LLC_SNAP_LSAP && llc->llc_control == LLC_UI && llc->llc_snap.org_code[0] == 0 && llc->llc_snap.org_code[1] == 0 && llc->llc_snap.org_code[2] == 0) { m_adj(m, hdrlen + sizeof(struct llc) - sizeof(*eh)); llc = NULL; } else { m_adj(m, hdrlen - sizeof(*eh)); } eh = mtod(m, struct ether_header *); switch (wh.i_fc[1] & IEEE80211_FC1_DIR_MASK) { case IEEE80211_FC1_DIR_NODS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2); break; case IEEE80211_FC1_DIR_TODS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2); break; case IEEE80211_FC1_DIR_FROMDS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr3); break; case IEEE80211_FC1_DIR_DSTODS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr4); break; } #ifdef ALIGNED_POINTER if (!ALIGNED_POINTER(mtod(m, caddr_t) + sizeof(*eh), u_int32_t)) { struct mbuf *n, *n0, **np; caddr_t newdata; int off, pktlen; n0 = NULL; np = &n0; off = 0; pktlen = m->m_pkthdr.len; while (pktlen > off) { if (n0 == NULL) { MGETHDR(n, M_DONTWAIT, MT_DATA); if (n == NULL) { m_freem(m); return NULL; } M_MOVE_PKTHDR(n, m); n->m_len = MHLEN; } else { MGET(n, M_DONTWAIT, MT_DATA); if (n == NULL) { m_freem(m); m_freem(n0); return NULL; } n->m_len = MLEN; } if (pktlen - off >= MINCLSIZE) { MCLGET(n, M_DONTWAIT); if (n->m_flags & M_EXT) n->m_len = n->m_ext.ext_size; } if (n0 == NULL) { newdata = (caddr_t)ALIGN(n->m_data + sizeof(*eh)) - sizeof(*eh); n->m_len -= newdata - n->m_data; n->m_data = newdata; } if (n->m_len > pktlen - off) n->m_len = pktlen - off; m_copydata(m, off, n->m_len, mtod(n, caddr_t)); off += n->m_len; *np = n; np = &n->m_next; } m_freem(m); m = n0; } #endif /* ALIGNED_POINTER */ if (llc != NULL) { eh = mtod(m, struct ether_header *); eh->ether_type = htons(m->m_pkthdr.len - sizeof(*eh)); } return m; } /* * Install received rate set information in the node's state block. */ int ieee80211_setup_rates(struct ieee80211_node *ni, const u_int8_t *rates, const u_int8_t *xrates, int flags) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_rateset *rs = &ni->ni_rates; memset(rs, 0, sizeof(*rs)); rs->rs_nrates = rates[1]; memcpy(rs->rs_rates, rates + 2, rs->rs_nrates); if (xrates != NULL) { u_int8_t nxrates; /* * Tack on 11g extended supported rate element. */ nxrates = xrates[1]; if (rs->rs_nrates + nxrates > IEEE80211_RATE_MAXSIZE) { nxrates = IEEE80211_RATE_MAXSIZE - rs->rs_nrates; IEEE80211_DPRINTF(ic, IEEE80211_MSG_XRATE, "[%s] extended rate set too large;" " only using %u of %u rates\n", ether_sprintf(ni->ni_macaddr), nxrates, xrates[1]); ic->ic_stats.is_rx_rstoobig++; } memcpy(rs->rs_rates + rs->rs_nrates, xrates+2, nxrates); rs->rs_nrates += nxrates; } return ieee80211_fix_rate(ni, flags); } static void ieee80211_auth_open(struct ieee80211com *ic, struct ieee80211_frame *wh, struct ieee80211_node *ni, int rssi, u_int32_t rstamp, u_int16_t seq, u_int16_t status) { if (ni->ni_authmode == IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "open auth", "bad sta auth mode %u", ni->ni_authmode); ic->ic_stats.is_rx_bad_auth++; /* XXX */ if (ic->ic_opmode == IEEE80211_M_HOSTAP) { /* XXX hack to workaround calling convention */ ieee80211_send_error(ic, ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq + 1) | (IEEE80211_STATUS_ALG<<16)); } return; } switch (ic->ic_opmode) { case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: case IEEE80211_M_MONITOR: /* should not come here */ IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "open auth", "bad operating mode %u", ic->ic_opmode); break; case IEEE80211_M_HOSTAP: if (ic->ic_state != IEEE80211_S_RUN || seq != IEEE80211_AUTH_OPEN_REQUEST) { ic->ic_stats.is_rx_bad_auth++; return; } /* always accept open authentication requests */ if (ni == ic->ic_bss) { ni = ieee80211_dup_bss(&ic->ic_sta, wh->i_addr2); if (ni == NULL) return; } else if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0) (void) ieee80211_ref_node(ni); /* * Mark the node as referenced to reflect that it's * reference count has been bumped to insure it remains * after the transaction completes. */ ni->ni_flags |= IEEE80211_NODE_AREF; IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, "[%s] station authenticated (open)\n", ether_sprintf(ni->ni_macaddr)); /* * When 802.1x is not in use mark the port * authorized at this point so traffic can flow. */ if (ni->ni_authmode != IEEE80211_AUTH_8021X) ieee80211_node_authorize(ni); break; case IEEE80211_M_STA: if (ic->ic_state != IEEE80211_S_AUTH || seq != IEEE80211_AUTH_OPEN_RESPONSE) { ic->ic_stats.is_rx_bad_auth++; return; } if (status != 0) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, "[%s] open auth failed (reason %d)\n", ether_sprintf(ni->ni_macaddr), status); /* XXX can this happen? */ if (ni != ic->ic_bss) ni->ni_fails++; ic->ic_stats.is_rx_auth_fail++; ieee80211_new_state(ic, IEEE80211_S_SCAN, 0); } else ieee80211_new_state(ic, IEEE80211_S_ASSOC, wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); break; } } /* * Send a management frame error response to the specified * station. If ni is associated with the station then use * it; otherwise allocate a temporary node suitable for * transmitting the frame and then free the reference so * it will go away as soon as the frame has been transmitted. */ static void ieee80211_send_error(struct ieee80211com *ic, struct ieee80211_node *ni, const u_int8_t *mac, int subtype, int arg) { int istmp; if (ni == ic->ic_bss) { ni = ieee80211_tmp_node(ic, mac); if (ni == NULL) { /* XXX msg */ return; } istmp = 1; } else istmp = 0; IEEE80211_SEND_MGMT(ic, ni, subtype, arg); if (istmp) ieee80211_free_node(ni); } static int alloc_challenge(struct ieee80211com *ic, struct ieee80211_node *ni) { if (ni->ni_challenge == NULL) MALLOC(ni->ni_challenge, u_int32_t*, IEEE80211_CHALLENGE_LEN, M_DEVBUF, M_NOWAIT); if (ni->ni_challenge == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, "[%s] shared key challenge alloc failed\n", ether_sprintf(ni->ni_macaddr)); /* XXX statistic */ } return (ni->ni_challenge != NULL); } /* XXX TODO: add statistics */ static void ieee80211_auth_shared(struct ieee80211com *ic, struct ieee80211_frame *wh, u_int8_t *frm, u_int8_t *efrm, struct ieee80211_node *ni, int rssi, u_int32_t rstamp, u_int16_t seq, u_int16_t status) { u_int8_t *challenge; int allocbs, estatus; /* * NB: this can happen as we allow pre-shared key * authentication to be enabled w/o wep being turned * on so that configuration of these can be done * in any order. It may be better to enforce the * ordering in which case this check would just be * for sanity/consistency. */ if ((ic->ic_flags & IEEE80211_F_PRIVACY) == 0) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", " PRIVACY is disabled"); estatus = IEEE80211_STATUS_ALG; goto bad; } /* * Pre-shared key authentication is evil; accept * it only if explicitly configured (it is supported * mainly for compatibility with clients like OS X). */ if (ni->ni_authmode != IEEE80211_AUTH_AUTO && ni->ni_authmode != IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad sta auth mode %u", ni->ni_authmode); ic->ic_stats.is_rx_bad_auth++; /* XXX maybe a unique error? */ estatus = IEEE80211_STATUS_ALG; goto bad; } challenge = NULL; if (frm + 1 < efrm) { if ((frm[1] + 2) > (efrm - frm)) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "ie %d/%d too long", frm[0], (frm[1] + 2) - (efrm - frm)); ic->ic_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (*frm == IEEE80211_ELEMID_CHALLENGE) challenge = frm; frm += frm[1] + 2; } switch (seq) { case IEEE80211_AUTH_SHARED_CHALLENGE: case IEEE80211_AUTH_SHARED_RESPONSE: if (challenge == NULL) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", "no challenge"); ic->ic_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (challenge[1] != IEEE80211_CHALLENGE_LEN) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad challenge len %d", challenge[1]); ic->ic_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } default: break; } switch (ic->ic_opmode) { case IEEE80211_M_MONITOR: case IEEE80211_M_AHDEMO: case IEEE80211_M_IBSS: IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad operating mode %u", ic->ic_opmode); return; case IEEE80211_M_HOSTAP: if (ic->ic_state != IEEE80211_S_RUN) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad state %u", ic->ic_state); estatus = IEEE80211_STATUS_ALG; /* XXX */ goto bad; } switch (seq) { case IEEE80211_AUTH_SHARED_REQUEST: if (ni == ic->ic_bss) { ni = ieee80211_dup_bss(&ic->ic_sta, wh->i_addr2); if (ni == NULL) { /* NB: no way to return an error */ return; } allocbs = 1; } else { if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0) (void) ieee80211_ref_node(ni); allocbs = 0; } /* * Mark the node as referenced to reflect that it's * reference count has been bumped to insure it remains * after the transaction completes. */ ni->ni_flags |= IEEE80211_NODE_AREF; ni->ni_rssi = rssi; ni->ni_rstamp = rstamp; if (!alloc_challenge(ic, ni)) { /* NB: don't return error so they rexmit */ return; } get_random_bytes(ni->ni_challenge, IEEE80211_CHALLENGE_LEN); IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, "[%s] shared key %sauth request\n", ether_sprintf(ni->ni_macaddr), allocbs ? "" : "re"); break; case IEEE80211_AUTH_SHARED_RESPONSE: if (ni == ic->ic_bss) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key response", "%s", "unknown station"); /* NB: don't send a response */ return; } if (ni->ni_challenge == NULL) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key response", "%s", "no challenge recorded"); ic->ic_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (memcmp(ni->ni_challenge, &challenge[2], challenge[1]) != 0) { IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key response", "%s", "challenge mismatch"); ic->ic_stats.is_rx_auth_fail++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, "[%s] station authenticated (shared key)\n", ether_sprintf(ni->ni_macaddr)); ieee80211_node_authorize(ni); break; default: IEEE80211_DISCARD_MAC(ic, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad seq %d", seq); ic->ic_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_SEQUENCE; goto bad; } IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); break; case IEEE80211_M_STA: if (ic->ic_state != IEEE80211_S_AUTH) return; switch (seq) { case IEEE80211_AUTH_SHARED_PASS: if (ni->ni_challenge != NULL) { FREE(ni->ni_challenge, M_DEVBUF); ni->ni_challenge = NULL; } if (status != 0) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, "[%s] shared key auth failed (reason %d)\n", ether_sprintf(ieee80211_getbssid(ic, wh)), status); /* XXX can this happen? */ if (ni != ic->ic_bss) ni->ni_fails++; ic->ic_stats.is_rx_auth_fail++; return; } ieee80211_new_state(ic, IEEE80211_S_ASSOC, wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); break; case IEEE80211_AUTH_SHARED_CHALLENGE: if (!alloc_challenge(ic, ni)) return; /* XXX could optimize by passing recvd challenge */ memcpy(ni->ni_challenge, &challenge[2], challenge[1]); IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); break; default: IEEE80211_DISCARD(ic, IEEE80211_MSG_AUTH, wh, "shared key auth", "bad seq %d", seq); ic->ic_stats.is_rx_bad_auth++; return; } break; } return; bad: /* * Send an error response; but only when operating as an AP. */ if (ic->ic_opmode == IEEE80211_M_HOSTAP) { /* XXX hack to workaround calling convention */ ieee80211_send_error(ic, ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq + 1) | (estatus<<16)); } else if (ic->ic_opmode == IEEE80211_M_STA) { /* * Kick the state machine. This short-circuits * using the mgt frame timeout to trigger the * state transition. */ if (ic->ic_state == IEEE80211_S_AUTH) ieee80211_new_state(ic, IEEE80211_S_SCAN, 0); } } /* Verify the existence and length of __elem or get out. */ #define IEEE80211_VERIFY_ELEMENT(__elem, __maxlen) do { \ if ((__elem) == NULL) { \ IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID, \ wh, ieee80211_mgt_subtype_name[subtype >> \ IEEE80211_FC0_SUBTYPE_SHIFT], \ "%s", "no " #__elem ); \ ic->ic_stats.is_rx_elem_missing++; \ return; \ } \ if ((__elem)[1] > (__maxlen)) { \ IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID, \ wh, ieee80211_mgt_subtype_name[subtype >> \ IEEE80211_FC0_SUBTYPE_SHIFT], \ "bad " #__elem " len %d", (__elem)[1]); \ ic->ic_stats.is_rx_elem_toobig++; \ return; \ } \ } while (0) #define IEEE80211_VERIFY_LENGTH(_len, _minlen) do { \ if ((_len) < (_minlen)) { \ IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID, \ wh, ieee80211_mgt_subtype_name[subtype >> \ IEEE80211_FC0_SUBTYPE_SHIFT], \ "%s", "ie too short"); \ ic->ic_stats.is_rx_elem_toosmall++; \ return; \ } \ } while (0) #ifdef IEEE80211_DEBUG static void ieee80211_ssid_mismatch(struct ieee80211com *ic, const char *tag, u_int8_t mac[IEEE80211_ADDR_LEN], u_int8_t *ssid) { printf("[%s] discard %s frame, ssid mismatch: ", ether_sprintf(mac), tag); ieee80211_print_essid(ssid + 2, ssid[1]); printf("\n"); } #define IEEE80211_VERIFY_SSID(_ni, _ssid) do { \ if ((_ssid)[1] != 0 && \ ((_ssid)[1] != (_ni)->ni_esslen || \ memcmp((_ssid) + 2, (_ni)->ni_essid, (_ssid)[1]) != 0)) { \ if (ieee80211_msg_input(ic)) \ ieee80211_ssid_mismatch(ic, \ ieee80211_mgt_subtype_name[subtype >> \ IEEE80211_FC0_SUBTYPE_SHIFT], \ wh->i_addr2, _ssid); \ ic->ic_stats.is_rx_ssidmismatch++; \ return; \ } \ } while (0) #else /* !IEEE80211_DEBUG */ #define IEEE80211_VERIFY_SSID(_ni, _ssid) do { \ if ((_ssid)[1] != 0 && \ ((_ssid)[1] != (_ni)->ni_esslen || \ memcmp((_ssid) + 2, (_ni)->ni_essid, (_ssid)[1]) != 0)) { \ ic->ic_stats.is_rx_ssidmismatch++; \ return; \ } \ } while (0) #endif /* !IEEE80211_DEBUG */ /* unalligned little endian access */ #define LE_READ_2(p) \ ((u_int16_t) \ ((((const u_int8_t *)(p))[0] ) | \ (((const u_int8_t *)(p))[1] << 8))) #define LE_READ_4(p) \ ((u_int32_t) \ ((((const u_int8_t *)(p))[0] ) | \ (((const u_int8_t *)(p))[1] << 8) | \ (((const u_int8_t *)(p))[2] << 16) | \ (((const u_int8_t *)(p))[3] << 24))) static int __inline iswpaoui(const u_int8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((WPA_OUI_TYPE<<24)|WPA_OUI); } static int __inline iswmeoui(const u_int8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI); } static int __inline iswmeparam(const u_int8_t *frm) { return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) && frm[6] == WME_PARAM_OUI_SUBTYPE; } static int __inline iswmeinfo(const u_int8_t *frm) { return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) && frm[6] == WME_INFO_OUI_SUBTYPE; } static int __inline isatherosoui(const u_int8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((ATH_OUI_TYPE<<24)|ATH_OUI); } /* * Convert a WPA cipher selector OUI to an internal * cipher algorithm. Where appropriate we also * record any key length. */ static int wpa_cipher(u_int8_t *sel, u_int8_t *keylen) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case WPA_SEL(WPA_CSE_NULL): return IEEE80211_CIPHER_NONE; case WPA_SEL(WPA_CSE_WEP40): if (keylen) *keylen = 40 / NBBY; return IEEE80211_CIPHER_WEP; case WPA_SEL(WPA_CSE_WEP104): if (keylen) *keylen = 104 / NBBY; return IEEE80211_CIPHER_WEP; case WPA_SEL(WPA_CSE_TKIP): return IEEE80211_CIPHER_TKIP; case WPA_SEL(WPA_CSE_CCMP): return IEEE80211_CIPHER_AES_CCM; } return 32; /* NB: so 1<< is discarded */ #undef WPA_SEL } /* * Convert a WPA key management/authentication algorithm * to an internal code. */ static int wpa_keymgmt(u_int8_t *sel) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case WPA_SEL(WPA_ASE_8021X_UNSPEC): return WPA_ASE_8021X_UNSPEC; case WPA_SEL(WPA_ASE_8021X_PSK): return WPA_ASE_8021X_PSK; case WPA_SEL(WPA_ASE_NONE): return WPA_ASE_NONE; } return 0; /* NB: so is discarded */ #undef WPA_SEL } /* * Parse a WPA information element to collect parameters * and validate the parameters against what has been * configured for the system. */ static int ieee80211_parse_wpa(struct ieee80211com *ic, u_int8_t *frm, struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) { u_int8_t len = frm[1]; u_int32_t w; int n; /* * Check the length once for fixed parts: OUI, type, * version, mcast cipher, and 2 selector counts. * Other, variable-length data, must be checked separately. */ if ((ic->ic_flags & IEEE80211_F_WPA1) == 0) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "not WPA, flags 0x%x", ic->ic_flags); return IEEE80211_REASON_IE_INVALID; } if (len < 14) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "too short, len %u", len); return IEEE80211_REASON_IE_INVALID; } frm += 6, len -= 4; /* NB: len is payload only */ /* NB: iswapoui already validated the OUI and type */ w = LE_READ_2(frm); if (w != WPA_VERSION) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "bad version %u", w); return IEEE80211_REASON_IE_INVALID; } frm += 2, len -= 2; /* multicast/group cipher */ w = wpa_cipher(frm, &rsn->rsn_mcastkeylen); if (w != rsn->rsn_mcastcipher) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "mcast cipher mismatch; got %u, expected %u", w, rsn->rsn_mcastcipher); return IEEE80211_REASON_IE_INVALID; } frm += 4, len -= 4; /* unicast ciphers */ n = LE_READ_2(frm); frm += 2, len -= 2; if (len < n*4+2) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "ucast cipher data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= 1<rsn_ucastkeylen); frm += 4, len -= 4; } w &= rsn->rsn_ucastcipherset; if (w == 0) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "%s", "ucast cipher set empty"); return IEEE80211_REASON_IE_INVALID; } if (w & (1<rsn_ucastcipher = IEEE80211_CIPHER_TKIP; else rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; /* key management algorithms */ n = LE_READ_2(frm); frm += 2, len -= 2; if (len < n*4) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "key mgmt alg data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= wpa_keymgmt(frm); frm += 4, len -= 4; } w &= rsn->rsn_keymgmtset; if (w == 0) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "%s", "no acceptable key mgmt alg"); return IEEE80211_REASON_IE_INVALID; } if (w & WPA_ASE_8021X_UNSPEC) rsn->rsn_keymgmt = WPA_ASE_8021X_UNSPEC; else rsn->rsn_keymgmt = WPA_ASE_8021X_PSK; if (len > 2) /* optional capabilities */ rsn->rsn_caps = LE_READ_2(frm); return 0; } /* * Convert an RSN cipher selector OUI to an internal * cipher algorithm. Where appropriate we also * record any key length. */ static int rsn_cipher(u_int8_t *sel, u_int8_t *keylen) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case RSN_SEL(RSN_CSE_NULL): return IEEE80211_CIPHER_NONE; case RSN_SEL(RSN_CSE_WEP40): if (keylen) *keylen = 40 / NBBY; return IEEE80211_CIPHER_WEP; case RSN_SEL(RSN_CSE_WEP104): if (keylen) *keylen = 104 / NBBY; return IEEE80211_CIPHER_WEP; case RSN_SEL(RSN_CSE_TKIP): return IEEE80211_CIPHER_TKIP; case RSN_SEL(RSN_CSE_CCMP): return IEEE80211_CIPHER_AES_CCM; case RSN_SEL(RSN_CSE_WRAP): return IEEE80211_CIPHER_AES_OCB; } return 32; /* NB: so 1<< is discarded */ #undef WPA_SEL } /* * Convert an RSN key management/authentication algorithm * to an internal code. */ static int rsn_keymgmt(u_int8_t *sel) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case RSN_SEL(RSN_ASE_8021X_UNSPEC): return RSN_ASE_8021X_UNSPEC; case RSN_SEL(RSN_ASE_8021X_PSK): return RSN_ASE_8021X_PSK; case RSN_SEL(RSN_ASE_NONE): return RSN_ASE_NONE; } return 0; /* NB: so is discarded */ #undef RSN_SEL } /* * Parse a WPA/RSN information element to collect parameters * and validate the parameters against what has been * configured for the system. */ static int ieee80211_parse_rsn(struct ieee80211com *ic, u_int8_t *frm, struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) { u_int8_t len = frm[1]; u_int32_t w; int n; /* * Check the length once for fixed parts: * version, mcast cipher, and 2 selector counts. * Other, variable-length data, must be checked separately. */ if ((ic->ic_flags & IEEE80211_F_WPA2) == 0) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "not RSN, flags 0x%x", ic->ic_flags); return IEEE80211_REASON_IE_INVALID; } if (len < 10) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "too short, len %u", len); return IEEE80211_REASON_IE_INVALID; } frm += 2; w = LE_READ_2(frm); if (w != RSN_VERSION) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "bad version %u", w); return IEEE80211_REASON_IE_INVALID; } frm += 2, len -= 2; /* multicast/group cipher */ w = rsn_cipher(frm, &rsn->rsn_mcastkeylen); if (w != rsn->rsn_mcastcipher) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "mcast cipher mismatch; got %u, expected %u", w, rsn->rsn_mcastcipher); return IEEE80211_REASON_IE_INVALID; } frm += 4, len -= 4; /* unicast ciphers */ n = LE_READ_2(frm); frm += 2, len -= 2; if (len < n*4+2) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "ucast cipher data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= 1<rsn_ucastkeylen); frm += 4, len -= 4; } w &= rsn->rsn_ucastcipherset; if (w == 0) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "%s", "ucast cipher set empty"); return IEEE80211_REASON_IE_INVALID; } if (w & (1<rsn_ucastcipher = IEEE80211_CIPHER_TKIP; else rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; /* key management algorithms */ n = LE_READ_2(frm); frm += 2, len -= 2; if (len < n*4) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "key mgmt alg data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= rsn_keymgmt(frm); frm += 4, len -= 4; } w &= rsn->rsn_keymgmtset; if (w == 0) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "%s", "no acceptable key mgmt alg"); return IEEE80211_REASON_IE_INVALID; } if (w & RSN_ASE_8021X_UNSPEC) rsn->rsn_keymgmt = RSN_ASE_8021X_UNSPEC; else rsn->rsn_keymgmt = RSN_ASE_8021X_PSK; /* optional RSN capabilities */ if (len > 2) rsn->rsn_caps = LE_READ_2(frm); /* XXXPMKID */ return 0; } static int ieee80211_parse_wmeparams(struct ieee80211com *ic, u_int8_t *frm, const struct ieee80211_frame *wh) { #define MS(_v, _f) (((_v) & _f) >> _f##_S) struct ieee80211_wme_state *wme = &ic->ic_wme; u_int len = frm[1], qosinfo; int i; if (len < sizeof(struct ieee80211_wme_param)-2) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, wh, "WME", "too short, len %u", len); return -1; } qosinfo = frm[__offsetof(struct ieee80211_wme_param, param_qosInfo)]; qosinfo &= WME_QOSINFO_COUNT; /* XXX do proper check for wraparound */ if (qosinfo == wme->wme_wmeChanParams.cap_info) return 0; frm += __offsetof(struct ieee80211_wme_param, params_acParams); for (i = 0; i < WME_NUM_AC; i++) { struct wmeParams *wmep = &wme->wme_wmeChanParams.cap_wmeParams[i]; /* NB: ACI not used */ wmep->wmep_acm = MS(frm[0], WME_PARAM_ACM); wmep->wmep_aifsn = MS(frm[0], WME_PARAM_AIFSN); wmep->wmep_logcwmin = MS(frm[1], WME_PARAM_LOGCWMIN); wmep->wmep_logcwmax = MS(frm[1], WME_PARAM_LOGCWMAX); wmep->wmep_txopLimit = LE_READ_2(frm+2); frm += 4; } wme->wme_wmeChanParams.cap_info = qosinfo; return 1; #undef MS } void ieee80211_saveie(u_int8_t **iep, const u_int8_t *ie) { u_int ielen = ie[1]+2; /* * Record information element for later use. */ if (*iep == NULL || (*iep)[1] != ie[1]) { if (*iep != NULL) FREE(*iep, M_DEVBUF); MALLOC(*iep, void*, ielen, M_DEVBUF, M_NOWAIT); } if (*iep != NULL) memcpy(*iep, ie, ielen); /* XXX note failure */ } /* XXX find a better place for definition */ struct l2_update_frame { struct ether_header eh; u_int8_t dsap; u_int8_t ssap; u_int8_t control; u_int8_t xid[3]; } __packed; /* * Deliver a TGf L2UF frame on behalf of a station. * This primes any bridge when the station is roaming * between ap's on the same wired network. */ static void ieee80211_deliver_l2uf(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = ic->ic_ifp; struct mbuf *m; struct l2_update_frame *l2uf; struct ether_header *eh; m = m_gethdr(M_NOWAIT, MT_DATA); if (m == NULL) { IEEE80211_NOTE(ic, IEEE80211_MSG_ASSOC, ni, "%s", "no mbuf for l2uf frame"); ic->ic_stats.is_rx_nobuf++; /* XXX not right */ return; } l2uf = mtod(m, struct l2_update_frame *); eh = &l2uf->eh; /* dst: Broadcast address */ IEEE80211_ADDR_COPY(eh->ether_dhost, ifp->if_broadcastaddr); /* src: associated STA */ IEEE80211_ADDR_COPY(eh->ether_shost, ni->ni_macaddr); eh->ether_type = htons(sizeof(*l2uf) - sizeof(*eh)); l2uf->dsap = 0; l2uf->ssap = 0; l2uf->control = 0xf5; l2uf->xid[0] = 0x81; l2uf->xid[1] = 0x80; l2uf->xid[2] = 0x00; m->m_pkthdr.len = m->m_len = sizeof(*l2uf); ieee80211_deliver_data(ic, ni, m); } void ieee80211_recv_mgmt(struct ieee80211com *ic, struct mbuf *m0, struct ieee80211_node *ni, int subtype, int rssi, u_int32_t rstamp) { #define ISPROBE(_st) ((_st) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) #define ISREASSOC(_st) ((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP) struct ieee80211_frame *wh; u_int8_t *frm, *efrm; u_int8_t *ssid, *rates, *xrates, *wpa, *wme; int reassoc, resp, allocbs; u_int8_t rate; wh = mtod(m0, struct ieee80211_frame *); frm = (u_int8_t *)&wh[1]; efrm = mtod(m0, u_int8_t *) + m0->m_len; switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_BEACON: { struct ieee80211_scanparams scan; /* * We process beacon/probe response frames: * o when scanning, or * o station mode when associated (to collect state * updates such as 802.11g slot time), or * o adhoc mode (to discover neighbors) * Frames otherwise received are discarded. */ if (!((ic->ic_flags & IEEE80211_F_SCAN) || (ic->ic_opmode == IEEE80211_M_STA && ni->ni_associd) || ic->ic_opmode == IEEE80211_M_IBSS)) { ic->ic_stats.is_rx_mgtdiscard++; return; } /* * beacon/probe response frame format * [8] time stamp * [2] beacon interval * [2] capability information * [tlv] ssid * [tlv] supported rates * [tlv] country information * [tlv] parameter set (FH/DS) * [tlv] erp information * [tlv] extended supported rates * [tlv] WME * [tlv] WPA or RSN */ IEEE80211_VERIFY_LENGTH(efrm - frm, 12); memset(&scan, 0, sizeof(scan)); scan.tstamp = frm; frm += 8; scan.bintval = le16toh(*(u_int16_t *)frm); frm += 2; scan.capinfo = le16toh(*(u_int16_t *)frm); frm += 2; scan.bchan = ieee80211_chan2ieee(ic, ic->ic_curchan); scan.chan = scan.bchan; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2); switch (*frm) { case IEEE80211_ELEMID_SSID: scan.ssid = frm; break; case IEEE80211_ELEMID_RATES: scan.rates = frm; break; case IEEE80211_ELEMID_COUNTRY: scan.country = frm; break; case IEEE80211_ELEMID_FHPARMS: if (ic->ic_phytype == IEEE80211_T_FH) { scan.fhdwell = LE_READ_2(&frm[2]); scan.chan = IEEE80211_FH_CHAN(frm[4], frm[5]); scan.fhindex = frm[6]; } break; case IEEE80211_ELEMID_DSPARMS: /* * XXX hack this since depending on phytype * is problematic for multi-mode devices. */ if (ic->ic_phytype != IEEE80211_T_FH) scan.chan = frm[2]; break; case IEEE80211_ELEMID_TIM: /* XXX ATIM? */ scan.tim = frm; scan.timoff = frm - mtod(m0, u_int8_t *); break; case IEEE80211_ELEMID_IBSSPARMS: break; case IEEE80211_ELEMID_XRATES: scan.xrates = frm; break; case IEEE80211_ELEMID_ERP: if (frm[1] != 1) { IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID, wh, "ERP", "bad len %u", frm[1]); ic->ic_stats.is_rx_elem_toobig++; break; } scan.erp = frm[2]; break; case IEEE80211_ELEMID_RSN: scan.wpa = frm; break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(frm)) scan.wpa = frm; else if (iswmeparam(frm) || iswmeinfo(frm)) scan.wme = frm; /* XXX Atheros OUI support */ break; default: IEEE80211_DISCARD_IE(ic, IEEE80211_MSG_ELEMID, wh, "unhandled", "id %u, len %u", *frm, frm[1]); ic->ic_stats.is_rx_elem_unknown++; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(scan.rates, IEEE80211_RATE_MAXSIZE); IEEE80211_VERIFY_ELEMENT(scan.ssid, IEEE80211_NWID_LEN); if ( #if IEEE80211_CHAN_MAX < 255 scan.chan > IEEE80211_CHAN_MAX || #endif isclr(ic->ic_chan_active, scan.chan)) { IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], "invalid channel %u", scan.chan); ic->ic_stats.is_rx_badchan++; return; } if (scan.chan != scan.bchan && ic->ic_phytype != IEEE80211_T_FH) { /* * Frame was received on a channel different from the * one indicated in the DS params element id; * silently discard it. * * NB: this can happen due to signal leakage. * But we should take it for FH phy because * the rssi value should be correct even for * different hop pattern in FH. */ IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], "for off-channel %u", scan.chan); ic->ic_stats.is_rx_chanmismatch++; return; } if (!(IEEE80211_BINTVAL_MIN <= scan.bintval && scan.bintval <= IEEE80211_BINTVAL_MAX)) { IEEE80211_DISCARD(ic, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], "bogus beacon interval", scan.bintval); ic->ic_stats.is_rx_badbintval++; return; } /* * Count frame now that we know it's to be processed. */ if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { ic->ic_stats.is_rx_beacon++; /* XXX remove */ IEEE80211_NODE_STAT(ni, rx_beacons); } else IEEE80211_NODE_STAT(ni, rx_proberesp); /* * When operating in station mode, check for state updates. * Be careful to ignore beacons received while doing a * background scan. We consider only 11g/WMM stuff right now. */ if (ic->ic_opmode == IEEE80211_M_STA && ni->ni_associd != 0 && ((ic->ic_flags & IEEE80211_F_SCAN) == 0 || IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid))) { /* record tsf of last beacon */ memcpy(ni->ni_tstamp.data, scan.tstamp, sizeof(ni->ni_tstamp)); /* count beacon frame for s/w bmiss handling */ ic->ic_swbmiss_count++; ic->ic_bmiss_count = 0; if (ni->ni_erp != scan.erp) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, "[%s] erp change: was 0x%x, now 0x%x\n", ether_sprintf(wh->i_addr2), ni->ni_erp, scan.erp); if (ic->ic_curmode == IEEE80211_MODE_11G && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) ic->ic_flags |= IEEE80211_F_USEPROT; else ic->ic_flags &= ~IEEE80211_F_USEPROT; ni->ni_erp = scan.erp; /* XXX statistic */ } if ((ni->ni_capinfo ^ scan.capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, "[%s] capabilities change: before 0x%x," " now 0x%x\n", ether_sprintf(wh->i_addr2), ni->ni_capinfo, scan.capinfo); /* * NB: we assume short preamble doesn't * change dynamically */ ieee80211_set_shortslottime(ic, ic->ic_curmode == IEEE80211_MODE_11A || (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); ni->ni_capinfo = scan.capinfo; /* XXX statistic */ } if (scan.wme != NULL && (ni->ni_flags & IEEE80211_NODE_QOS) && ieee80211_parse_wmeparams(ic, scan.wme, wh) > 0) ieee80211_wme_updateparams(ic); if (scan.tim != NULL) { struct ieee80211_tim_ie *ie = (struct ieee80211_tim_ie *) scan.tim; ni->ni_dtim_count = ie->tim_count; ni->ni_dtim_period = ie->tim_period; } if (ic->ic_flags & IEEE80211_F_SCAN) ieee80211_add_scan(ic, &scan, wh, subtype, rssi, rstamp); return; } /* * If scanning, just pass information to the scan module. */ if (ic->ic_flags & IEEE80211_F_SCAN) { if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) { /* * Actively scanning a channel marked passive; * send a probe request now that we know there * is 802.11 traffic present. * * XXX check if the beacon we recv'd gives * us what we need and suppress the probe req */ ieee80211_probe_curchan(ic, 1); ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; } ieee80211_add_scan(ic, &scan, wh, subtype, rssi, rstamp); return; } if (scan.capinfo & IEEE80211_CAPINFO_IBSS) { if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { /* * Create a new entry in the neighbor table. */ ni = ieee80211_add_neighbor(ic, wh, &scan); } else if (ni->ni_capinfo == 0) { /* * Update faked node created on transmit. * Note this also updates the tsf. */ ieee80211_init_neighbor(ni, wh, &scan); } else { /* * Record tsf for potential resync. */ memcpy(ni->ni_tstamp.data, scan.tstamp, sizeof(ni->ni_tstamp)); } if (ni != NULL) { ni->ni_rssi = rssi; ni->ni_rstamp = rstamp; } } break; } case IEEE80211_FC0_SUBTYPE_PROBE_REQ: if (ic->ic_opmode == IEEE80211_M_STA || ic->ic_state != IEEE80211_S_RUN) { ic->ic_stats.is_rx_mgtdiscard++; return; } if (IEEE80211_IS_MULTICAST(wh->i_addr2)) { /* frame must be directed */ ic->ic_stats.is_rx_mgtdiscard++; /* XXX stat */ return; } /* * prreq frame format * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates */ ssid = rates = xrates = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2); switch (*frm) { case IEEE80211_ELEMID_SSID: ssid = frm; break; case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN); IEEE80211_VERIFY_SSID(ic->ic_bss, ssid); if ((ic->ic_flags & IEEE80211_F_HIDESSID) && ssid[1] == 0) { IEEE80211_DISCARD(ic, IEEE80211_MSG_INPUT, wh, ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], "%s", "no ssid with ssid suppression enabled"); ic->ic_stats.is_rx_ssidmismatch++; /*XXX*/ return; } allocbs = 0; if (ni == ic->ic_bss) { if (ic->ic_opmode != IEEE80211_M_IBSS) { ni = ieee80211_tmp_node(ic, wh->i_addr2); allocbs = 1; } else if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr)) { /* * XXX Cannot tell if the sender is operating * in ibss mode. But we need a new node to * send the response so blindly add them to the * neighbor table. */ ni = ieee80211_fakeup_adhoc_node(&ic->ic_sta, wh->i_addr2); } if (ni == NULL) return; } IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, "[%s] recv probe req\n", ether_sprintf(wh->i_addr2)); ni->ni_rssi = rssi; ni->ni_rstamp = rstamp; rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); if (rate & IEEE80211_RATE_BASIC) { IEEE80211_DISCARD(ic, IEEE80211_MSG_XRATE, wh, ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], "%s", "recv'd rate set invalid"); } else { IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_PROBE_RESP, 0); } if (allocbs) { /* * Temporary node created just to send a * response, reclaim immediately. */ ieee80211_free_node(ni); } break; case IEEE80211_FC0_SUBTYPE_AUTH: { u_int16_t algo, seq, status; /* * auth frame format * [2] algorithm * [2] sequence * [2] status * [tlv*] challenge */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6); algo = le16toh(*(u_int16_t *)frm); seq = le16toh(*(u_int16_t *)(frm + 2)); status = le16toh(*(u_int16_t *)(frm + 4)); IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH, "[%s] recv auth frame with algorithm %d seq %d\n", ether_sprintf(wh->i_addr2), algo, seq); /* * Consult the ACL policy module if setup. */ if (ic->ic_acl != NULL && !ic->ic_acl->iac_check(ic, wh->i_addr2)) { IEEE80211_DISCARD(ic, IEEE80211_MSG_ACL, wh, "auth", "%s", "disallowed by ACL"); ic->ic_stats.is_rx_acl++; if (ic->ic_opmode == IEEE80211_M_HOSTAP) { IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_AUTH, (seq+1) | (IEEE80211_STATUS_UNSPECIFIED<<16)); } return; } if (ic->ic_flags & IEEE80211_F_COUNTERM) { IEEE80211_DISCARD(ic, IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO, wh, "auth", "%s", "TKIP countermeasures enabled"); ic->ic_stats.is_rx_auth_countermeasures++; if (ic->ic_opmode == IEEE80211_M_HOSTAP) { IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_AUTH, IEEE80211_REASON_MIC_FAILURE); } return; } if (algo == IEEE80211_AUTH_ALG_SHARED) ieee80211_auth_shared(ic, wh, frm + 6, efrm, ni, rssi, rstamp, seq, status); else if (algo == IEEE80211_AUTH_ALG_OPEN) ieee80211_auth_open(ic, wh, ni, rssi, rstamp, seq, status); else { IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY, wh, "auth", "unsupported alg %d", algo); ic->ic_stats.is_rx_auth_unsupported++; if (ic->ic_opmode == IEEE80211_M_HOSTAP) { /* XXX not right */ IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_AUTH, (seq+1) | (IEEE80211_STATUS_ALG<<16)); } return; } break; } case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: { u_int16_t capinfo, lintval; struct ieee80211_rsnparms rsn; u_int8_t reason; if (ic->ic_opmode != IEEE80211_M_HOSTAP || ic->ic_state != IEEE80211_S_RUN) { ic->ic_stats.is_rx_mgtdiscard++; return; } if (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) { reassoc = 1; resp = IEEE80211_FC0_SUBTYPE_REASSOC_RESP; } else { reassoc = 0; resp = IEEE80211_FC0_SUBTYPE_ASSOC_RESP; } /* * asreq frame format * [2] capability information * [2] listen interval * [6*] current AP address (reassoc only) * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates * [tlv] WPA or RSN */ IEEE80211_VERIFY_LENGTH(efrm - frm, (reassoc ? 10 : 4)); if (!IEEE80211_ADDR_EQ(wh->i_addr3, ic->ic_bss->ni_bssid)) { IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY, wh, ieee80211_mgt_subtype_name[subtype >> IEEE80211_FC0_SUBTYPE_SHIFT], "%s", "wrong bssid"); ic->ic_stats.is_rx_assoc_bss++; return; } capinfo = le16toh(*(u_int16_t *)frm); frm += 2; lintval = le16toh(*(u_int16_t *)frm); frm += 2; if (reassoc) frm += 6; /* ignore current AP info */ ssid = rates = xrates = wpa = wme = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2); switch (*frm) { case IEEE80211_ELEMID_SSID: ssid = frm; break; case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; /* XXX verify only one of RSN and WPA ie's? */ case IEEE80211_ELEMID_RSN: wpa = frm; break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(frm)) wpa = frm; else if (iswmeinfo(frm)) wme = frm; /* XXX Atheros OUI support */ break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN); IEEE80211_VERIFY_SSID(ic->ic_bss, ssid); if (ni == ic->ic_bss) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, "[%s] deny %s request, sta not authenticated\n", ether_sprintf(wh->i_addr2), reassoc ? "reassoc" : "assoc"); ieee80211_send_error(ic, ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_ASSOC_NOT_AUTHED); ic->ic_stats.is_rx_assoc_notauth++; return; } /* assert right associstion security credentials */ if (wpa == NULL && (ic->ic_flags & IEEE80211_F_WPA)) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, "[%s] no WPA/RSN IE in association request\n", ether_sprintf(wh->i_addr2)); IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_RSN_REQUIRED); ieee80211_node_leave(ic, ni); /* XXX distinguish WPA/RSN? */ ic->ic_stats.is_rx_assoc_badwpaie++; return; } if (wpa != NULL) { /* * Parse WPA information element. Note that * we initialize the param block from the node * state so that information in the IE overrides * our defaults. The resulting parameters are * installed below after the association is assured. */ rsn = ni->ni_rsn; if (wpa[0] != IEEE80211_ELEMID_RSN) reason = ieee80211_parse_wpa(ic, wpa, &rsn, wh); else reason = ieee80211_parse_rsn(ic, wpa, &rsn, wh); if (reason != 0) { IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH, reason); ieee80211_node_leave(ic, ni); /* XXX distinguish WPA/RSN? */ ic->ic_stats.is_rx_assoc_badwpaie++; return; } IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, "[%s] %s ie: mc %u/%u uc %u/%u key %u caps 0x%x\n", ether_sprintf(wh->i_addr2), wpa[0] != IEEE80211_ELEMID_RSN ? "WPA" : "RSN", rsn.rsn_mcastcipher, rsn.rsn_mcastkeylen, rsn.rsn_ucastcipher, rsn.rsn_ucastkeylen, rsn.rsn_keymgmt, rsn.rsn_caps); } /* discard challenge after association */ if (ni->ni_challenge != NULL) { FREE(ni->ni_challenge, M_DEVBUF); ni->ni_challenge = NULL; } /* NB: 802.11 spec says to ignore station's privacy bit */ if ((capinfo & IEEE80211_CAPINFO_ESS) == 0) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, "[%s] deny %s request, capability mismatch 0x%x\n", ether_sprintf(wh->i_addr2), reassoc ? "reassoc" : "assoc", capinfo); IEEE80211_SEND_MGMT(ic, ni, resp, IEEE80211_STATUS_CAPINFO); ieee80211_node_leave(ic, ni); ic->ic_stats.is_rx_assoc_capmismatch++; return; } rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); /* * If constrained to 11g-only stations reject an * 11b-only station. We cheat a bit here by looking * at the max negotiated xmit rate and assuming anyone * with a best rate <24Mb/s is an 11b station. */ if ((rate & IEEE80211_RATE_BASIC) || ((ic->ic_flags & IEEE80211_F_PUREG) && rate < 48)) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, "[%s] deny %s request, rate set mismatch\n", ether_sprintf(wh->i_addr2), reassoc ? "reassoc" : "assoc"); IEEE80211_SEND_MGMT(ic, ni, resp, IEEE80211_STATUS_BASIC_RATE); ieee80211_node_leave(ic, ni); ic->ic_stats.is_rx_assoc_norate++; return; } ni->ni_rssi = rssi; ni->ni_rstamp = rstamp; ni->ni_intval = lintval; ni->ni_capinfo = capinfo; ni->ni_chan = ic->ic_bss->ni_chan; ni->ni_fhdwell = ic->ic_bss->ni_fhdwell; ni->ni_fhindex = ic->ic_bss->ni_fhindex; if (wpa != NULL) { /* * Record WPA/RSN parameters for station, mark * node as using WPA and record information element * for applications that require it. */ ni->ni_rsn = rsn; ieee80211_saveie(&ni->ni_wpa_ie, wpa); } else if (ni->ni_wpa_ie != NULL) { /* * Flush any state from a previous association. */ FREE(ni->ni_wpa_ie, M_DEVBUF); ni->ni_wpa_ie = NULL; } if (wme != NULL) { /* * Record WME parameters for station, mark node * as capable of QoS and record information * element for applications that require it. */ ieee80211_saveie(&ni->ni_wme_ie, wme); ni->ni_flags |= IEEE80211_NODE_QOS; } else if (ni->ni_wme_ie != NULL) { /* * Flush any state from a previous association. */ FREE(ni->ni_wme_ie, M_DEVBUF); ni->ni_wme_ie = NULL; ni->ni_flags &= ~IEEE80211_NODE_QOS; } ieee80211_deliver_l2uf(ni); ieee80211_node_join(ic, ni, resp); break; } case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: { u_int16_t capinfo, associd; u_int16_t status; if (ic->ic_opmode != IEEE80211_M_STA || ic->ic_state != IEEE80211_S_ASSOC) { ic->ic_stats.is_rx_mgtdiscard++; return; } /* * asresp frame format * [2] capability information * [2] status * [2] association ID * [tlv] supported rates * [tlv] extended supported rates * [tlv] WME */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6); ni = ic->ic_bss; capinfo = le16toh(*(u_int16_t *)frm); frm += 2; status = le16toh(*(u_int16_t *)frm); frm += 2; if (status != 0) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, "[%s] %sassoc failed (reason %d)\n", ether_sprintf(wh->i_addr2), ISREASSOC(subtype) ? "re" : "", status); if (ni != ic->ic_bss) /* XXX never true? */ ni->ni_fails++; ic->ic_stats.is_rx_auth_fail++; /* XXX */ return; } associd = le16toh(*(u_int16_t *)frm); frm += 2; rates = xrates = wpa = wme = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2); switch (*frm) { case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; case IEEE80211_ELEMID_VENDOR: if (iswmeoui(frm)) wme = frm; /* XXX Atheros OUI support */ break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE); rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); if (rate & IEEE80211_RATE_BASIC) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, "[%s] %sassoc failed (rate set mismatch)\n", ether_sprintf(wh->i_addr2), ISREASSOC(subtype) ? "re" : ""); if (ni != ic->ic_bss) /* XXX never true? */ ni->ni_fails++; ic->ic_stats.is_rx_assoc_norate++; ieee80211_new_state(ic, IEEE80211_S_SCAN, 0); return; } ni->ni_capinfo = capinfo; ni->ni_associd = associd; if (wme != NULL && ieee80211_parse_wmeparams(ic, wme, wh) >= 0) { ni->ni_flags |= IEEE80211_NODE_QOS; ieee80211_wme_updateparams(ic); } else ni->ni_flags &= ~IEEE80211_NODE_QOS; /* * Configure state now that we are associated. * * XXX may need different/additional driver callbacks? */ if (ic->ic_curmode == IEEE80211_MODE_11A || (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)) { ic->ic_flags |= IEEE80211_F_SHPREAMBLE; ic->ic_flags &= ~IEEE80211_F_USEBARKER; } else { ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE; ic->ic_flags |= IEEE80211_F_USEBARKER; } ieee80211_set_shortslottime(ic, ic->ic_curmode == IEEE80211_MODE_11A || (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); /* * Honor ERP protection. * * NB: ni_erp should zero for non-11g operation. * XXX check ic_curmode anyway? */ if (ic->ic_curmode == IEEE80211_MODE_11G && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) ic->ic_flags |= IEEE80211_F_USEPROT; else ic->ic_flags &= ~IEEE80211_F_USEPROT; IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, "[%s] %sassoc success: %s preamble, %s slot time%s%s\n", ether_sprintf(wh->i_addr2), ISREASSOC(subtype) ? "re" : "", ic->ic_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", ic->ic_flags&IEEE80211_F_SHSLOT ? "short" : "long", ic->ic_flags&IEEE80211_F_USEPROT ? ", protection" : "", ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "" ); ieee80211_new_state(ic, IEEE80211_S_RUN, subtype); break; } case IEEE80211_FC0_SUBTYPE_DEAUTH: { u_int16_t reason; if (ic->ic_state == IEEE80211_S_SCAN) { ic->ic_stats.is_rx_mgtdiscard++; return; } /* * deauth frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2); reason = le16toh(*(u_int16_t *)frm); ic->ic_stats.is_rx_deauth++; IEEE80211_NODE_STAT(ni, rx_deauth); IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH, "[%s] recv deauthenticate (reason %d)\n", ether_sprintf(ni->ni_macaddr), reason); switch (ic->ic_opmode) { case IEEE80211_M_STA: ieee80211_new_state(ic, IEEE80211_S_AUTH, wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); break; case IEEE80211_M_HOSTAP: if (ni != ic->ic_bss) ieee80211_node_leave(ic, ni); break; default: ic->ic_stats.is_rx_mgtdiscard++; break; } break; } case IEEE80211_FC0_SUBTYPE_DISASSOC: { u_int16_t reason; if (ic->ic_state != IEEE80211_S_RUN && ic->ic_state != IEEE80211_S_ASSOC && ic->ic_state != IEEE80211_S_AUTH) { ic->ic_stats.is_rx_mgtdiscard++; return; } /* * disassoc frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2); reason = le16toh(*(u_int16_t *)frm); ic->ic_stats.is_rx_disassoc++; IEEE80211_NODE_STAT(ni, rx_disassoc); IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, "[%s] recv disassociate (reason %d)\n", ether_sprintf(ni->ni_macaddr), reason); switch (ic->ic_opmode) { case IEEE80211_M_STA: ieee80211_new_state(ic, IEEE80211_S_ASSOC, wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK); break; case IEEE80211_M_HOSTAP: if (ni != ic->ic_bss) ieee80211_node_leave(ic, ni); break; default: ic->ic_stats.is_rx_mgtdiscard++; break; } break; } default: IEEE80211_DISCARD(ic, IEEE80211_MSG_ANY, wh, "mgt", "subtype 0x%x not handled", subtype); ic->ic_stats.is_rx_badsubtype++; break; } #undef ISREASSOC #undef ISPROBE } #undef IEEE80211_VERIFY_LENGTH #undef IEEE80211_VERIFY_ELEMENT /* * Handle station power-save state change. */ static void ieee80211_node_pwrsave(struct ieee80211_node *ni, int enable) { struct ieee80211com *ic = ni->ni_ic; struct mbuf *m; if (enable) { if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) == 0) ic->ic_ps_sta++; ni->ni_flags |= IEEE80211_NODE_PWR_MGT; IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, "[%s] power save mode on, %u sta's in ps mode\n", ether_sprintf(ni->ni_macaddr), ic->ic_ps_sta); return; } if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) ic->ic_ps_sta--; ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, "[%s] power save mode off, %u sta's in ps mode\n", ether_sprintf(ni->ni_macaddr), ic->ic_ps_sta); /* XXX if no stations in ps mode, flush mc frames */ /* * Flush queued unicast frames. */ if (IEEE80211_NODE_SAVEQ_QLEN(ni) == 0) { if (ic->ic_set_tim != NULL) ic->ic_set_tim(ni, 0); /* just in case */ return; } IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, "[%s] flush ps queue, %u packets queued\n", ether_sprintf(ni->ni_macaddr), IEEE80211_NODE_SAVEQ_QLEN(ni)); for (;;) { int qlen; IEEE80211_NODE_SAVEQ_DEQUEUE(ni, m, qlen); if (m == NULL) break; /* * If this is the last packet, turn off the TIM bit. * If there are more packets, set the more packets bit * in the mbuf so ieee80211_encap will mark the 802.11 * head to indicate more data frames will follow. */ if (qlen != 0) m->m_flags |= M_MORE_DATA; /* XXX need different driver interface */ /* XXX bypasses q max */ IF_ENQUEUE(&ic->ic_ifp->if_snd, m); } if (ic->ic_set_tim != NULL) ic->ic_set_tim(ni, 0); } /* * Process a received ps-poll frame. */ static void ieee80211_recv_pspoll(struct ieee80211com *ic, struct ieee80211_node *ni, struct mbuf *m0) { struct ieee80211_frame_min *wh; struct mbuf *m; u_int16_t aid; int qlen; wh = mtod(m0, struct ieee80211_frame_min *); if (ni->ni_associd == 0) { IEEE80211_DISCARD(ic, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, (struct ieee80211_frame *) wh, "ps-poll", "%s", "unassociated station"); ic->ic_stats.is_ps_unassoc++; IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_NOT_ASSOCED); return; } aid = le16toh(*(u_int16_t *)wh->i_dur); if (aid != ni->ni_associd) { IEEE80211_DISCARD(ic, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, (struct ieee80211_frame *) wh, "ps-poll", "aid mismatch: sta aid 0x%x poll aid 0x%x", ni->ni_associd, aid); ic->ic_stats.is_ps_badaid++; IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_NOT_ASSOCED); return; } /* Okay, take the first queued packet and put it out... */ IEEE80211_NODE_SAVEQ_DEQUEUE(ni, m, qlen); if (m == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, "[%s] recv ps-poll, but queue empty\n", ether_sprintf(wh->i_addr2)); ieee80211_send_nulldata(ieee80211_ref_node(ni)); ic->ic_stats.is_ps_qempty++; /* XXX node stat */ if (ic->ic_set_tim != NULL) ic->ic_set_tim(ni, 0); /* just in case */ return; } /* * If there are more packets, set the more packets bit * in the packet dispatched to the station; otherwise * turn off the TIM bit. */ if (qlen != 0) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, "[%s] recv ps-poll, send packet, %u still queued\n", ether_sprintf(ni->ni_macaddr), qlen); m->m_flags |= M_MORE_DATA; } else { IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, "[%s] recv ps-poll, send packet, queue empty\n", ether_sprintf(ni->ni_macaddr)); if (ic->ic_set_tim != NULL) ic->ic_set_tim(ni, 0); } m->m_flags |= M_PWR_SAV; /* bypass PS handling */ IF_ENQUEUE(&ic->ic_ifp->if_snd, m); } #ifdef IEEE80211_DEBUG /* * Debugging support. */ /* * Return the bssid of a frame. */ static const u_int8_t * ieee80211_getbssid(struct ieee80211com *ic, const struct ieee80211_frame *wh) { if (ic->ic_opmode == IEEE80211_M_STA) return wh->i_addr2; if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) != IEEE80211_FC1_DIR_NODS) return wh->i_addr1; if ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PS_POLL) return wh->i_addr1; return wh->i_addr3; } void ieee80211_note(struct ieee80211com *ic, const char *fmt, ...) { char buf[128]; /* XXX */ va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(ic->ic_ifp, "%s", buf); /* NB: no \n */ } void ieee80211_note_frame(struct ieee80211com *ic, const struct ieee80211_frame *wh, const char *fmt, ...) { char buf[128]; /* XXX */ va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(ic->ic_ifp, "[%s] %s\n", ether_sprintf(ieee80211_getbssid(ic, wh)), buf); } void ieee80211_note_mac(struct ieee80211com *ic, const u_int8_t mac[IEEE80211_ADDR_LEN], const char *fmt, ...) { char buf[128]; /* XXX */ va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(ic->ic_ifp, "[%s] %s\n", ether_sprintf(mac), buf); } static void ieee80211_discard_frame(struct ieee80211com *ic, const struct ieee80211_frame *wh, const char *type, const char *fmt, ...) { va_list ap; printf("[%s:%s] discard ", ic->ic_ifp->if_xname, ether_sprintf(ieee80211_getbssid(ic, wh))); if (type != NULL) printf("%s frame, ", type); else printf("frame, "); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } static void ieee80211_discard_ie(struct ieee80211com *ic, const struct ieee80211_frame *wh, const char *type, const char *fmt, ...) { va_list ap; printf("[%s:%s] discard ", ic->ic_ifp->if_xname, ether_sprintf(ieee80211_getbssid(ic, wh))); if (type != NULL) printf("%s information element, ", type); else printf("information element, "); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } static void ieee80211_discard_mac(struct ieee80211com *ic, const u_int8_t mac[IEEE80211_ADDR_LEN], const char *type, const char *fmt, ...) { va_list ap; printf("[%s:%s] discard ", ic->ic_ifp->if_xname, ether_sprintf(mac)); if (type != NULL) printf("%s frame, ", type); else printf("frame, "); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } #endif /* IEEE80211_DEBUG */ Index: head/sys/net80211/ieee80211_output.c =================================================================== --- head/sys/net80211/ieee80211_output.c (revision 162374) +++ head/sys/net80211/ieee80211_output.c (revision 162375) @@ -1,1843 +1,1842 @@ /*- * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Alternatively, this software may be distributed under the terms of the * GNU General Public License ("GPL") version 2 as published by the Free * Software Foundation. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #include #include #endif #ifdef IEEE80211_DEBUG /* * Decide if an outbound management frame should be * printed when debugging is enabled. This filters some * of the less interesting frames that come frequently * (e.g. beacons). */ static __inline int doprint(struct ieee80211com *ic, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: return (ic->ic_opmode == IEEE80211_M_IBSS); } return 1; } #endif /* * Set the direction field and address fields of an outgoing * non-QoS frame. Note this should be called early on in * constructing a frame as it sets i_fc[1]; other bits can * then be or'd in. */ static void ieee80211_send_setup(struct ieee80211com *ic, struct ieee80211_node *ni, struct ieee80211_frame *wh, int type, const u_int8_t sa[IEEE80211_ADDR_LEN], const u_int8_t da[IEEE80211_ADDR_LEN], const u_int8_t bssid[IEEE80211_ADDR_LEN]) { #define WH4(wh) ((struct ieee80211_frame_addr4 *)wh) wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | type; if ((type & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_DATA) { switch (ic->ic_opmode) { case IEEE80211_M_STA: wh->i_fc[1] = IEEE80211_FC1_DIR_TODS; IEEE80211_ADDR_COPY(wh->i_addr1, bssid); IEEE80211_ADDR_COPY(wh->i_addr2, sa); IEEE80211_ADDR_COPY(wh->i_addr3, da); break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, sa); IEEE80211_ADDR_COPY(wh->i_addr3, bssid); break; case IEEE80211_M_HOSTAP: wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, bssid); IEEE80211_ADDR_COPY(wh->i_addr3, sa); break; case IEEE80211_M_MONITOR: /* NB: to quiet compiler */ break; } } else { wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, sa); IEEE80211_ADDR_COPY(wh->i_addr3, bssid); } *(u_int16_t *)&wh->i_dur[0] = 0; /* NB: use non-QoS tid */ *(u_int16_t *)&wh->i_seq[0] = htole16(ni->ni_txseqs[0] << IEEE80211_SEQ_SEQ_SHIFT); ni->ni_txseqs[0]++; #undef WH4 } /* * Send a management frame to the specified node. The node pointer * must have a reference as the pointer will be passed to the driver * and potentially held for a long time. If the frame is successfully * dispatched to the driver, then it is responsible for freeing the * reference (and potentially free'ing up any associated storage). */ static int ieee80211_mgmt_output(struct ieee80211com *ic, struct ieee80211_node *ni, struct mbuf *m, int type, int timer) { struct ifnet *ifp = ic->ic_ifp; struct ieee80211_frame *wh; KASSERT(ni != NULL, ("null node")); /* * Yech, hack alert! We want to pass the node down to the * driver's start routine. If we don't do so then the start * routine must immediately look it up again and that can * cause a lock order reversal if, for example, this frame * is being sent because the station is being timedout and * the frame being sent is a DEAUTH message. We could stick * this in an m_tag and tack that on to the mbuf. However * that's rather expensive to do for every frame so instead * we stuff it in the rcvif field since outbound frames do * not (presently) use this. */ M_PREPEND(m, sizeof(struct ieee80211_frame), M_DONTWAIT); if (m == NULL) return ENOMEM; KASSERT(m->m_pkthdr.rcvif == NULL, ("rcvif not null")); m->m_pkthdr.rcvif = (void *)ni; wh = mtod(m, struct ieee80211_frame *); ieee80211_send_setup(ic, ni, wh, IEEE80211_FC0_TYPE_MGT | type, ic->ic_myaddr, ni->ni_macaddr, ni->ni_bssid); if ((m->m_flags & M_LINK0) != 0 && ni->ni_challenge != NULL) { m->m_flags &= ~M_LINK0; IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH, "[%s] encrypting frame (%s)\n", ether_sprintf(wh->i_addr1), __func__); wh->i_fc[1] |= IEEE80211_FC1_WEP; } #ifdef IEEE80211_DEBUG /* avoid printing too many frames */ if ((ieee80211_msg_debug(ic) && doprint(ic, type)) || ieee80211_msg_dumppkts(ic)) { printf("[%s] send %s on channel %u\n", ether_sprintf(wh->i_addr1), ieee80211_mgt_subtype_name[ (type & IEEE80211_FC0_SUBTYPE_MASK) >> IEEE80211_FC0_SUBTYPE_SHIFT], ieee80211_chan2ieee(ic, ic->ic_curchan)); } #endif IEEE80211_NODE_STAT(ni, tx_mgmt); IF_ENQUEUE(&ic->ic_mgtq, m); if (timer) { /* * Set the mgt frame timeout. */ ic->ic_mgt_timer = timer; ifp->if_timer = 1; } if_start(ifp); return 0; } /* * Raw packet transmit stub for legacy drivers. * Send the packet through the mgt q so we bypass * the normal encapsulation work. */ int ieee80211_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = ic->ic_ifp; m->m_pkthdr.rcvif = (void *) ni; IF_ENQUEUE(&ic->ic_mgtq, m); if_start(ifp); ifp->if_opackets++; return 0; } /* * 802.11 output routine. This is (currently) used only to * connect bpf write calls to the 802.11 layer for injecting * raw 802.11 frames. Note we locate the ieee80211com from * the ifnet using a spare field setup at attach time. This * will go away when the virtual ap support comes in. */ int ieee80211_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, struct rtentry *rt0) { #define senderr(e) do { error = (e); goto bad;} while (0) struct ieee80211com *ic = ifp->if_spare2; /* XXX */ struct ieee80211_node *ni = NULL; struct ieee80211_frame *wh; int error; /* * Hand to the 802.3 code if not tagged as * a raw 802.11 frame. */ if (dst->sa_family != AF_IEEE80211) return ether_output(ifp, m, dst, rt0); #ifdef MAC error = mac_check_ifnet_transmit(ifp, m); if (error) senderr(error); #endif if (ifp->if_flags & IFF_MONITOR) senderr(ENETDOWN); if ((ifp->if_flags & IFF_UP) == 0) senderr(ENETDOWN); /* XXX bypass bridge, pfil, carp, etc. */ if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_ack)) senderr(EIO); /* XXX */ wh = mtod(m, struct ieee80211_frame *); if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != IEEE80211_FC0_VERSION_0) senderr(EIO); /* XXX */ /* locate destination node */ switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) { case IEEE80211_FC1_DIR_NODS: case IEEE80211_FC1_DIR_FROMDS: ni = ieee80211_find_txnode(ic, wh->i_addr1); break; case IEEE80211_FC1_DIR_TODS: case IEEE80211_FC1_DIR_DSTODS: if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) senderr(EIO); /* XXX */ ni = ieee80211_find_txnode(ic, wh->i_addr3); break; default: senderr(EIO); /* XXX */ } if (ni == NULL) { /* * Permit packets w/ bpf params through regardless * (see below about sa_len). */ if (dst->sa_len == 0) senderr(EHOSTUNREACH); ni = ieee80211_ref_node(ic->ic_bss); } /* XXX ctrl frames should go through */ if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) && (m->m_flags & M_PWR_SAV) == 0) { /* * Station in power save mode; pass the frame * to the 802.11 layer and continue. We'll get * the frame back when the time is right. */ ieee80211_pwrsave(ic, ni, m); error = 0; goto reclaim; } /* calculate priority so drivers can find the tx queue */ /* XXX assumes an 802.3 frame */ if (ieee80211_classify(ic, m, ni)) senderr(EIO); /* XXX */ BPF_MTAP(ifp, m); /* * NB: DLT_IEEE802_11_RADIO identifies the parameters are * present by setting the sa_len field of the sockaddr (yes, * this is a hack). * NB: we assume sa_data is suitably aligned to cast. */ return ic->ic_raw_xmit(ni, m, (const struct ieee80211_bpf_params *) (dst->sa_len ? dst->sa_data : NULL)); bad: if (m != NULL) m_freem(m); reclaim: if (ni != NULL) ieee80211_free_node(ni); return error; #undef senderr } /* * Send a null data frame to the specified node. * * NB: the caller is assumed to have setup a node reference * for use; this is necessary to deal with a race condition * when probing for inactive stations. */ int ieee80211_send_nulldata(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = ic->ic_ifp; struct mbuf *m; struct ieee80211_frame *wh; MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { /* XXX debug msg */ ic->ic_stats.is_tx_nobuf++; ieee80211_unref_node(&ni); return ENOMEM; } m->m_pkthdr.rcvif = (void *) ni; wh = mtod(m, struct ieee80211_frame *); ieee80211_send_setup(ic, ni, wh, IEEE80211_FC0_TYPE_DATA | IEEE80211_FC0_SUBTYPE_NODATA, ic->ic_myaddr, ni->ni_macaddr, ni->ni_bssid); /* NB: power management bit is never sent by an AP */ if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) && ic->ic_opmode != IEEE80211_M_HOSTAP) wh->i_fc[1] |= IEEE80211_FC1_PWR_MGT; m->m_len = m->m_pkthdr.len = sizeof(struct ieee80211_frame); IEEE80211_NODE_STAT(ni, tx_data); IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, "[%s] send null data frame on channel %u, pwr mgt %s\n", ether_sprintf(ni->ni_macaddr), ieee80211_chan2ieee(ic, ic->ic_curchan), wh->i_fc[1] & IEEE80211_FC1_PWR_MGT ? "ena" : "dis"); IF_ENQUEUE(&ic->ic_mgtq, m); /* cheat */ if_start(ifp); return 0; } /* * Assign priority to a frame based on any vlan tag assigned * to the station and/or any Diffserv setting in an IP header. * Finally, if an ACM policy is setup (in station mode) it's * applied. */ int ieee80211_classify(struct ieee80211com *ic, struct mbuf *m, struct ieee80211_node *ni) { int v_wme_ac, d_wme_ac, ac; #ifdef INET struct ether_header *eh; #endif if ((ni->ni_flags & IEEE80211_NODE_QOS) == 0) { ac = WME_AC_BE; goto done; } /* * If node has a vlan tag then all traffic * to it must have a matching tag. */ v_wme_ac = 0; if (ni->ni_vlan != 0) { - struct m_tag *mtag = VLAN_OUTPUT_TAG(ic->ic_ifp, m); - if (mtag == NULL) { + if ((m->m_flags & M_VLANTAG) == 0) { IEEE80211_NODE_STAT(ni, tx_novlantag); return 1; } - if (EVL_VLANOFTAG(VLAN_TAG_VALUE(mtag)) != + if (EVL_VLANOFTAG(m->m_pkthdr.ether_vtag) != EVL_VLANOFTAG(ni->ni_vlan)) { IEEE80211_NODE_STAT(ni, tx_vlanmismatch); return 1; } /* map vlan priority to AC */ switch (EVL_PRIOFTAG(ni->ni_vlan)) { case 1: case 2: v_wme_ac = WME_AC_BK; break; case 0: case 3: v_wme_ac = WME_AC_BE; break; case 4: case 5: v_wme_ac = WME_AC_VI; break; case 6: case 7: v_wme_ac = WME_AC_VO; break; } } #ifdef INET eh = mtod(m, struct ether_header *); if (eh->ether_type == htons(ETHERTYPE_IP)) { const struct ip *ip = (struct ip *) (mtod(m, u_int8_t *) + sizeof (*eh)); /* * IP frame, map the TOS field. */ switch (ip->ip_tos) { case 0x08: case 0x20: d_wme_ac = WME_AC_BK; /* background */ break; case 0x28: case 0xa0: d_wme_ac = WME_AC_VI; /* video */ break; case 0x30: /* voice */ case 0xe0: case 0x88: /* XXX UPSD */ case 0xb8: d_wme_ac = WME_AC_VO; break; default: d_wme_ac = WME_AC_BE; break; } } else { #endif /* INET */ d_wme_ac = WME_AC_BE; #ifdef INET } #endif /* * Use highest priority AC. */ if (v_wme_ac > d_wme_ac) ac = v_wme_ac; else ac = d_wme_ac; /* * Apply ACM policy. */ if (ic->ic_opmode == IEEE80211_M_STA) { static const int acmap[4] = { WME_AC_BK, /* WME_AC_BE */ WME_AC_BK, /* WME_AC_BK */ WME_AC_BE, /* WME_AC_VI */ WME_AC_VI, /* WME_AC_VO */ }; while (ac != WME_AC_BK && ic->ic_wme.wme_wmeBssChanParams.cap_wmeParams[ac].wmep_acm) ac = acmap[ac]; } done: M_WME_SETAC(m, ac); return 0; } /* * Insure there is sufficient contiguous space to encapsulate the * 802.11 data frame. If room isn't already there, arrange for it. * Drivers and cipher modules assume we have done the necessary work * and fail rudely if they don't find the space they need. */ static struct mbuf * ieee80211_mbuf_adjust(struct ieee80211com *ic, int hdrsize, struct ieee80211_key *key, struct mbuf *m) { #define TO_BE_RECLAIMED (sizeof(struct ether_header) - sizeof(struct llc)) int needed_space = hdrsize; if (key != NULL) { /* XXX belongs in crypto code? */ needed_space += key->wk_cipher->ic_header; /* XXX frags */ /* * When crypto is being done in the host we must insure * the data are writable for the cipher routines; clone * a writable mbuf chain. * XXX handle SWMIC specially */ if (key->wk_flags & (IEEE80211_KEY_SWCRYPT|IEEE80211_KEY_SWMIC)) { m = m_unshare(m, M_NOWAIT); if (m == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_OUTPUT, "%s: cannot get writable mbuf\n", __func__); ic->ic_stats.is_tx_nobuf++; /* XXX new stat */ return NULL; } } } /* * We know we are called just before stripping an Ethernet * header and prepending an LLC header. This means we know * there will be * sizeof(struct ether_header) - sizeof(struct llc) * bytes recovered to which we need additional space for the * 802.11 header and any crypto header. */ /* XXX check trailing space and copy instead? */ if (M_LEADINGSPACE(m) < needed_space - TO_BE_RECLAIMED) { struct mbuf *n = m_gethdr(M_NOWAIT, m->m_type); if (n == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_OUTPUT, "%s: cannot expand storage\n", __func__); ic->ic_stats.is_tx_nobuf++; m_freem(m); return NULL; } KASSERT(needed_space <= MHLEN, ("not enough room, need %u got %zu\n", needed_space, MHLEN)); /* * Setup new mbuf to have leading space to prepend the * 802.11 header and any crypto header bits that are * required (the latter are added when the driver calls * back to ieee80211_crypto_encap to do crypto encapsulation). */ /* NB: must be first 'cuz it clobbers m_data */ m_move_pkthdr(n, m); n->m_len = 0; /* NB: m_gethdr does not set */ n->m_data += needed_space; /* * Pull up Ethernet header to create the expected layout. * We could use m_pullup but that's overkill (i.e. we don't * need the actual data) and it cannot fail so do it inline * for speed. */ /* NB: struct ether_header is known to be contiguous */ n->m_len += sizeof(struct ether_header); m->m_len -= sizeof(struct ether_header); m->m_data += sizeof(struct ether_header); /* * Replace the head of the chain. */ n->m_next = m; m = n; } return m; #undef TO_BE_RECLAIMED } #define KEY_UNDEFINED(k) ((k).wk_cipher == &ieee80211_cipher_none) /* * Return the transmit key to use in sending a unicast frame. * If a unicast key is set we use that. When no unicast key is set * we fall back to the default transmit key. */ static __inline struct ieee80211_key * ieee80211_crypto_getucastkey(struct ieee80211com *ic, struct ieee80211_node *ni) { if (KEY_UNDEFINED(ni->ni_ucastkey)) { if (ic->ic_def_txkey == IEEE80211_KEYIX_NONE || KEY_UNDEFINED(ic->ic_nw_keys[ic->ic_def_txkey])) return NULL; return &ic->ic_nw_keys[ic->ic_def_txkey]; } else { return &ni->ni_ucastkey; } } /* * Return the transmit key to use in sending a multicast frame. * Multicast traffic always uses the group key which is installed as * the default tx key. */ static __inline struct ieee80211_key * ieee80211_crypto_getmcastkey(struct ieee80211com *ic, struct ieee80211_node *ni) { if (ic->ic_def_txkey == IEEE80211_KEYIX_NONE || KEY_UNDEFINED(ic->ic_nw_keys[ic->ic_def_txkey])) return NULL; return &ic->ic_nw_keys[ic->ic_def_txkey]; } /* * Encapsulate an outbound data frame. The mbuf chain is updated. * If an error is encountered NULL is returned. The caller is required * to provide a node reference and pullup the ethernet header in the * first mbuf. */ struct mbuf * ieee80211_encap(struct ieee80211com *ic, struct mbuf *m, struct ieee80211_node *ni) { struct ether_header eh; struct ieee80211_frame *wh; struct ieee80211_key *key; struct llc *llc; int hdrsize, datalen, addqos; KASSERT(m->m_len >= sizeof(eh), ("no ethernet header!")); memcpy(&eh, mtod(m, caddr_t), sizeof(struct ether_header)); /* * Insure space for additional headers. First identify * transmit key to use in calculating any buffer adjustments * required. This is also used below to do privacy * encapsulation work. Then calculate the 802.11 header * size and any padding required by the driver. * * Note key may be NULL if we fall back to the default * transmit key and that is not set. In that case the * buffer may not be expanded as needed by the cipher * routines, but they will/should discard it. */ if (ic->ic_flags & IEEE80211_F_PRIVACY) { if (ic->ic_opmode == IEEE80211_M_STA || !IEEE80211_IS_MULTICAST(eh.ether_dhost)) key = ieee80211_crypto_getucastkey(ic, ni); else key = ieee80211_crypto_getmcastkey(ic, ni); if (key == NULL && eh.ether_type != htons(ETHERTYPE_PAE)) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_CRYPTO, "[%s] no default transmit key (%s) deftxkey %u\n", ether_sprintf(eh.ether_dhost), __func__, ic->ic_def_txkey); ic->ic_stats.is_tx_nodefkey++; } } else key = NULL; /* XXX 4-address format */ /* * XXX Some ap's don't handle QoS-encapsulated EAPOL * frames so suppress use. This may be an issue if other * ap's require all data frames to be QoS-encapsulated * once negotiated in which case we'll need to make this * configurable. */ addqos = (ni->ni_flags & IEEE80211_NODE_QOS) && eh.ether_type != htons(ETHERTYPE_PAE); if (addqos) hdrsize = sizeof(struct ieee80211_qosframe); else hdrsize = sizeof(struct ieee80211_frame); if (ic->ic_flags & IEEE80211_F_DATAPAD) hdrsize = roundup(hdrsize, sizeof(u_int32_t)); m = ieee80211_mbuf_adjust(ic, hdrsize, key, m); if (m == NULL) { /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ goto bad; } /* NB: this could be optimized because of ieee80211_mbuf_adjust */ m_adj(m, sizeof(struct ether_header) - sizeof(struct llc)); llc = mtod(m, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; llc->llc_snap.org_code[0] = 0; llc->llc_snap.org_code[1] = 0; llc->llc_snap.org_code[2] = 0; llc->llc_snap.ether_type = eh.ether_type; datalen = m->m_pkthdr.len; /* NB: w/o 802.11 header */ M_PREPEND(m, hdrsize, M_DONTWAIT); if (m == NULL) { ic->ic_stats.is_tx_nobuf++; goto bad; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_DATA; *(u_int16_t *)wh->i_dur = 0; switch (ic->ic_opmode) { case IEEE80211_M_STA: wh->i_fc[1] = IEEE80211_FC1_DIR_TODS; IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_bssid); IEEE80211_ADDR_COPY(wh->i_addr2, eh.ether_shost); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_dhost); break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, eh.ether_shost); /* * NB: always use the bssid from ic_bss as the * neighbor's may be stale after an ibss merge */ IEEE80211_ADDR_COPY(wh->i_addr3, ic->ic_bss->ni_bssid); break; case IEEE80211_M_HOSTAP: wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, ni->ni_bssid); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_shost); break; case IEEE80211_M_MONITOR: goto bad; } if (m->m_flags & M_MORE_DATA) wh->i_fc[1] |= IEEE80211_FC1_MORE_DATA; if (addqos) { struct ieee80211_qosframe *qwh = (struct ieee80211_qosframe *) wh; int ac, tid; ac = M_WME_GETAC(m); /* map from access class/queue to 11e header priorty value */ tid = WME_AC_TO_TID(ac); qwh->i_qos[0] = tid & IEEE80211_QOS_TID; if (ic->ic_wme.wme_wmeChanParams.cap_wmeParams[ac].wmep_noackPolicy) qwh->i_qos[0] |= 1 << IEEE80211_QOS_ACKPOLICY_S; qwh->i_qos[1] = 0; qwh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_QOS; *(u_int16_t *)wh->i_seq = htole16(ni->ni_txseqs[tid] << IEEE80211_SEQ_SEQ_SHIFT); ni->ni_txseqs[tid]++; } else { *(u_int16_t *)wh->i_seq = htole16(ni->ni_txseqs[0] << IEEE80211_SEQ_SEQ_SHIFT); ni->ni_txseqs[0]++; } if (key != NULL) { /* * IEEE 802.1X: send EAPOL frames always in the clear. * WPA/WPA2: encrypt EAPOL keys when pairwise keys are set. */ if (eh.ether_type != htons(ETHERTYPE_PAE) || ((ic->ic_flags & IEEE80211_F_WPA) && (ic->ic_opmode == IEEE80211_M_STA ? !KEY_UNDEFINED(*key) : !KEY_UNDEFINED(ni->ni_ucastkey)))) { wh->i_fc[1] |= IEEE80211_FC1_WEP; /* XXX do fragmentation */ if (!ieee80211_crypto_enmic(ic, key, m, 0)) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_OUTPUT, "[%s] enmic failed, discard frame\n", ether_sprintf(eh.ether_dhost)); ic->ic_stats.is_crypto_enmicfail++; goto bad; } } } IEEE80211_NODE_STAT(ni, tx_data); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) IEEE80211_NODE_STAT(ni, tx_mcast); else IEEE80211_NODE_STAT(ni, tx_ucast); IEEE80211_NODE_STAT_ADD(ni, tx_bytes, datalen); return m; bad: if (m != NULL) m_freem(m); return NULL; } /* * Add a supported rates element id to a frame. */ static u_int8_t * ieee80211_add_rates(u_int8_t *frm, const struct ieee80211_rateset *rs) { int nrates; *frm++ = IEEE80211_ELEMID_RATES; nrates = rs->rs_nrates; if (nrates > IEEE80211_RATE_SIZE) nrates = IEEE80211_RATE_SIZE; *frm++ = nrates; memcpy(frm, rs->rs_rates, nrates); return frm + nrates; } /* * Add an extended supported rates element id to a frame. */ static u_int8_t * ieee80211_add_xrates(u_int8_t *frm, const struct ieee80211_rateset *rs) { /* * Add an extended supported rates element if operating in 11g mode. */ if (rs->rs_nrates > IEEE80211_RATE_SIZE) { int nrates = rs->rs_nrates - IEEE80211_RATE_SIZE; *frm++ = IEEE80211_ELEMID_XRATES; *frm++ = nrates; memcpy(frm, rs->rs_rates + IEEE80211_RATE_SIZE, nrates); frm += nrates; } return frm; } /* * Add an ssid elemet to a frame. */ static u_int8_t * ieee80211_add_ssid(u_int8_t *frm, const u_int8_t *ssid, u_int len) { *frm++ = IEEE80211_ELEMID_SSID; *frm++ = len; memcpy(frm, ssid, len); return frm + len; } /* * Add an erp element to a frame. */ static u_int8_t * ieee80211_add_erp(u_int8_t *frm, struct ieee80211com *ic) { u_int8_t erp; *frm++ = IEEE80211_ELEMID_ERP; *frm++ = 1; erp = 0; if (ic->ic_nonerpsta != 0) erp |= IEEE80211_ERP_NON_ERP_PRESENT; if (ic->ic_flags & IEEE80211_F_USEPROT) erp |= IEEE80211_ERP_USE_PROTECTION; if (ic->ic_flags & IEEE80211_F_USEBARKER) erp |= IEEE80211_ERP_LONG_PREAMBLE; *frm++ = erp; return frm; } static u_int8_t * ieee80211_setup_wpa_ie(struct ieee80211com *ic, u_int8_t *ie) { #define WPA_OUI_BYTES 0x00, 0x50, 0xf2 #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) #define ADDSELECTOR(frm, sel) do { \ memcpy(frm, sel, 4); \ frm += 4; \ } while (0) static const u_int8_t oui[4] = { WPA_OUI_BYTES, WPA_OUI_TYPE }; static const u_int8_t cipher_suite[][4] = { { WPA_OUI_BYTES, WPA_CSE_WEP40 }, /* NB: 40-bit */ { WPA_OUI_BYTES, WPA_CSE_TKIP }, { 0x00, 0x00, 0x00, 0x00 }, /* XXX WRAP */ { WPA_OUI_BYTES, WPA_CSE_CCMP }, { 0x00, 0x00, 0x00, 0x00 }, /* XXX CKIP */ { WPA_OUI_BYTES, WPA_CSE_NULL }, }; static const u_int8_t wep104_suite[4] = { WPA_OUI_BYTES, WPA_CSE_WEP104 }; static const u_int8_t key_mgt_unspec[4] = { WPA_OUI_BYTES, WPA_ASE_8021X_UNSPEC }; static const u_int8_t key_mgt_psk[4] = { WPA_OUI_BYTES, WPA_ASE_8021X_PSK }; const struct ieee80211_rsnparms *rsn = &ic->ic_bss->ni_rsn; u_int8_t *frm = ie; u_int8_t *selcnt; *frm++ = IEEE80211_ELEMID_VENDOR; *frm++ = 0; /* length filled in below */ memcpy(frm, oui, sizeof(oui)); /* WPA OUI */ frm += sizeof(oui); ADDSHORT(frm, WPA_VERSION); /* XXX filter out CKIP */ /* multicast cipher */ if (rsn->rsn_mcastcipher == IEEE80211_CIPHER_WEP && rsn->rsn_mcastkeylen >= 13) ADDSELECTOR(frm, wep104_suite); else ADDSELECTOR(frm, cipher_suite[rsn->rsn_mcastcipher]); /* unicast cipher list */ selcnt = frm; ADDSHORT(frm, 0); /* selector count */ if (rsn->rsn_ucastcipherset & (1<rsn_ucastcipherset & (1<rsn_keymgmtset & WPA_ASE_8021X_UNSPEC) { selcnt[0]++; ADDSELECTOR(frm, key_mgt_unspec); } if (rsn->rsn_keymgmtset & WPA_ASE_8021X_PSK) { selcnt[0]++; ADDSELECTOR(frm, key_mgt_psk); } /* optional capabilities */ if (rsn->rsn_caps != 0 && rsn->rsn_caps != RSN_CAP_PREAUTH) ADDSHORT(frm, rsn->rsn_caps); /* calculate element length */ ie[1] = frm - ie - 2; KASSERT(ie[1]+2 <= sizeof(struct ieee80211_ie_wpa), ("WPA IE too big, %u > %zu", ie[1]+2, sizeof(struct ieee80211_ie_wpa))); return frm; #undef ADDSHORT #undef ADDSELECTOR #undef WPA_OUI_BYTES } static u_int8_t * ieee80211_setup_rsn_ie(struct ieee80211com *ic, u_int8_t *ie) { #define RSN_OUI_BYTES 0x00, 0x0f, 0xac #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) #define ADDSELECTOR(frm, sel) do { \ memcpy(frm, sel, 4); \ frm += 4; \ } while (0) static const u_int8_t cipher_suite[][4] = { { RSN_OUI_BYTES, RSN_CSE_WEP40 }, /* NB: 40-bit */ { RSN_OUI_BYTES, RSN_CSE_TKIP }, { RSN_OUI_BYTES, RSN_CSE_WRAP }, { RSN_OUI_BYTES, RSN_CSE_CCMP }, { 0x00, 0x00, 0x00, 0x00 }, /* XXX CKIP */ { RSN_OUI_BYTES, RSN_CSE_NULL }, }; static const u_int8_t wep104_suite[4] = { RSN_OUI_BYTES, RSN_CSE_WEP104 }; static const u_int8_t key_mgt_unspec[4] = { RSN_OUI_BYTES, RSN_ASE_8021X_UNSPEC }; static const u_int8_t key_mgt_psk[4] = { RSN_OUI_BYTES, RSN_ASE_8021X_PSK }; const struct ieee80211_rsnparms *rsn = &ic->ic_bss->ni_rsn; u_int8_t *frm = ie; u_int8_t *selcnt; *frm++ = IEEE80211_ELEMID_RSN; *frm++ = 0; /* length filled in below */ ADDSHORT(frm, RSN_VERSION); /* XXX filter out CKIP */ /* multicast cipher */ if (rsn->rsn_mcastcipher == IEEE80211_CIPHER_WEP && rsn->rsn_mcastkeylen >= 13) ADDSELECTOR(frm, wep104_suite); else ADDSELECTOR(frm, cipher_suite[rsn->rsn_mcastcipher]); /* unicast cipher list */ selcnt = frm; ADDSHORT(frm, 0); /* selector count */ if (rsn->rsn_ucastcipherset & (1<rsn_ucastcipherset & (1<rsn_keymgmtset & WPA_ASE_8021X_UNSPEC) { selcnt[0]++; ADDSELECTOR(frm, key_mgt_unspec); } if (rsn->rsn_keymgmtset & WPA_ASE_8021X_PSK) { selcnt[0]++; ADDSELECTOR(frm, key_mgt_psk); } /* optional capabilities */ ADDSHORT(frm, rsn->rsn_caps); /* XXX PMKID */ /* calculate element length */ ie[1] = frm - ie - 2; KASSERT(ie[1]+2 <= sizeof(struct ieee80211_ie_wpa), ("RSN IE too big, %u > %zu", ie[1]+2, sizeof(struct ieee80211_ie_wpa))); return frm; #undef ADDSELECTOR #undef ADDSHORT #undef RSN_OUI_BYTES } /* * Add a WPA/RSN element to a frame. */ static u_int8_t * ieee80211_add_wpa(u_int8_t *frm, struct ieee80211com *ic) { KASSERT(ic->ic_flags & IEEE80211_F_WPA, ("no WPA/RSN!")); if (ic->ic_flags & IEEE80211_F_WPA2) frm = ieee80211_setup_rsn_ie(ic, frm); if (ic->ic_flags & IEEE80211_F_WPA1) frm = ieee80211_setup_wpa_ie(ic, frm); return frm; } #define WME_OUI_BYTES 0x00, 0x50, 0xf2 /* * Add a WME information element to a frame. */ static u_int8_t * ieee80211_add_wme_info(u_int8_t *frm, struct ieee80211_wme_state *wme) { static const struct ieee80211_wme_info info = { .wme_id = IEEE80211_ELEMID_VENDOR, .wme_len = sizeof(struct ieee80211_wme_info) - 2, .wme_oui = { WME_OUI_BYTES }, .wme_type = WME_OUI_TYPE, .wme_subtype = WME_INFO_OUI_SUBTYPE, .wme_version = WME_VERSION, .wme_info = 0, }; memcpy(frm, &info, sizeof(info)); return frm + sizeof(info); } /* * Add a WME parameters element to a frame. */ static u_int8_t * ieee80211_add_wme_param(u_int8_t *frm, struct ieee80211_wme_state *wme) { #define SM(_v, _f) (((_v) << _f##_S) & _f) #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) /* NB: this works 'cuz a param has an info at the front */ static const struct ieee80211_wme_info param = { .wme_id = IEEE80211_ELEMID_VENDOR, .wme_len = sizeof(struct ieee80211_wme_param) - 2, .wme_oui = { WME_OUI_BYTES }, .wme_type = WME_OUI_TYPE, .wme_subtype = WME_PARAM_OUI_SUBTYPE, .wme_version = WME_VERSION, }; int i; memcpy(frm, ¶m, sizeof(param)); frm += __offsetof(struct ieee80211_wme_info, wme_info); *frm++ = wme->wme_bssChanParams.cap_info; /* AC info */ *frm++ = 0; /* reserved field */ for (i = 0; i < WME_NUM_AC; i++) { const struct wmeParams *ac = &wme->wme_bssChanParams.cap_wmeParams[i]; *frm++ = SM(i, WME_PARAM_ACI) | SM(ac->wmep_acm, WME_PARAM_ACM) | SM(ac->wmep_aifsn, WME_PARAM_AIFSN) ; *frm++ = SM(ac->wmep_logcwmax, WME_PARAM_LOGCWMAX) | SM(ac->wmep_logcwmin, WME_PARAM_LOGCWMIN) ; ADDSHORT(frm, ac->wmep_txopLimit); } return frm; #undef SM #undef ADDSHORT } #undef WME_OUI_BYTES /* * Send a probe request frame with the specified ssid * and any optional information element data. */ int ieee80211_send_probereq(struct ieee80211_node *ni, const u_int8_t sa[IEEE80211_ADDR_LEN], const u_int8_t da[IEEE80211_ADDR_LEN], const u_int8_t bssid[IEEE80211_ADDR_LEN], const u_int8_t *ssid, size_t ssidlen, const void *optie, size_t optielen) { struct ieee80211com *ic = ni->ni_ic; enum ieee80211_phymode mode; struct ieee80211_frame *wh; struct mbuf *m; u_int8_t *frm; /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); /* * prreq frame format * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates * [tlv] user-specified ie's */ m = ieee80211_getmgtframe(&frm, 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + (optie != NULL ? optielen : 0) ); if (m == NULL) { ic->ic_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } frm = ieee80211_add_ssid(frm, ssid, ssidlen); mode = ieee80211_chan2mode(ic, ic->ic_curchan); frm = ieee80211_add_rates(frm, &ic->ic_sup_rates[mode]); frm = ieee80211_add_xrates(frm, &ic->ic_sup_rates[mode]); if (optie != NULL) { memcpy(frm, optie, optielen); frm += optielen; } m->m_pkthdr.len = m->m_len = frm - mtod(m, u_int8_t *); M_PREPEND(m, sizeof(struct ieee80211_frame), M_DONTWAIT); if (m == NULL) return ENOMEM; KASSERT(m->m_pkthdr.rcvif == NULL, ("rcvif not null")); m->m_pkthdr.rcvif = (void *)ni; wh = mtod(m, struct ieee80211_frame *); ieee80211_send_setup(ic, ni, wh, IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ, sa, da, bssid); /* XXX power management? */ IEEE80211_NODE_STAT(ni, tx_probereq); IEEE80211_NODE_STAT(ni, tx_mgmt); IEEE80211_DPRINTF(ic, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, "[%s] send probe req on channel %u\n", ether_sprintf(wh->i_addr1), ieee80211_chan2ieee(ic, ic->ic_curchan)); IF_ENQUEUE(&ic->ic_mgtq, m); if_start(ic->ic_ifp); return 0; } /* * Calculate capability information for mgt frames. */ static u_int16_t getcapinfo(struct ieee80211com *ic, struct ieee80211_channel *chan) { u_int16_t capinfo; KASSERT(ic->ic_opmode != IEEE80211_M_STA, ("station mode")); if (ic->ic_opmode == IEEE80211_M_HOSTAP) capinfo = IEEE80211_CAPINFO_ESS; else if (ic->ic_opmode == IEEE80211_M_IBSS) capinfo = IEEE80211_CAPINFO_IBSS; else capinfo = 0; if (ic->ic_flags & IEEE80211_F_PRIVACY) capinfo |= IEEE80211_CAPINFO_PRIVACY; if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) && IEEE80211_IS_CHAN_2GHZ(chan)) capinfo |= IEEE80211_CAPINFO_SHORT_PREAMBLE; if (ic->ic_flags & IEEE80211_F_SHSLOT) capinfo |= IEEE80211_CAPINFO_SHORT_SLOTTIME; return capinfo; } /* * Send a management frame. The node is for the destination (or ic_bss * when in station mode). Nodes other than ic_bss have their reference * count bumped to reflect our use for an indeterminant time. */ int ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni, int type, int arg) { #define senderr(_x, _v) do { ic->ic_stats._v++; ret = _x; goto bad; } while (0) struct mbuf *m; u_int8_t *frm; u_int16_t capinfo; int has_challenge, is_shared_key, ret, timer, status; KASSERT(ni != NULL, ("null node")); /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); timer = 0; switch (type) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: /* * probe response frame format * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [tlv] parameter set (FH/DS) * [tlv] parameter set (IBSS) * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] WPA * [tlv] WME (optional) */ m = ieee80211_getmgtframe(&frm, 8 + sizeof(u_int16_t) + sizeof(u_int16_t) + 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + 7 /* max(7,3) */ + 6 + 3 + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) /* XXX !WPA1+WPA2 fits w/o a cluster */ + (ic->ic_flags & IEEE80211_F_WPA ? 2*sizeof(struct ieee80211_ie_wpa) : 0) + sizeof(struct ieee80211_wme_param) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); memset(frm, 0, 8); /* timestamp should be filled later */ frm += 8; *(u_int16_t *)frm = htole16(ic->ic_bss->ni_intval); frm += 2; capinfo = getcapinfo(ic, ic->ic_curchan); *(u_int16_t *)frm = htole16(capinfo); frm += 2; frm = ieee80211_add_ssid(frm, ic->ic_bss->ni_essid, ic->ic_bss->ni_esslen); frm = ieee80211_add_rates(frm, &ni->ni_rates); if (ic->ic_phytype == IEEE80211_T_FH) { *frm++ = IEEE80211_ELEMID_FHPARMS; *frm++ = 5; *frm++ = ni->ni_fhdwell & 0x00ff; *frm++ = (ni->ni_fhdwell >> 8) & 0x00ff; *frm++ = IEEE80211_FH_CHANSET( ieee80211_chan2ieee(ic, ic->ic_curchan)); *frm++ = IEEE80211_FH_CHANPAT( ieee80211_chan2ieee(ic, ic->ic_curchan)); *frm++ = ni->ni_fhindex; } else { *frm++ = IEEE80211_ELEMID_DSPARMS; *frm++ = 1; *frm++ = ieee80211_chan2ieee(ic, ic->ic_curchan); } if (ic->ic_opmode == IEEE80211_M_IBSS) { *frm++ = IEEE80211_ELEMID_IBSSPARMS; *frm++ = 2; *frm++ = 0; *frm++ = 0; /* TODO: ATIM window */ } if (ic->ic_flags & IEEE80211_F_WPA) frm = ieee80211_add_wpa(frm, ic); if (ic->ic_curmode == IEEE80211_MODE_11G) frm = ieee80211_add_erp(frm, ic); frm = ieee80211_add_xrates(frm, &ni->ni_rates); if (ic->ic_flags & IEEE80211_F_WME) frm = ieee80211_add_wme_param(frm, &ic->ic_wme); m->m_pkthdr.len = m->m_len = frm - mtod(m, u_int8_t *); break; case IEEE80211_FC0_SUBTYPE_AUTH: status = arg >> 16; arg &= 0xffff; has_challenge = ((arg == IEEE80211_AUTH_SHARED_CHALLENGE || arg == IEEE80211_AUTH_SHARED_RESPONSE) && ni->ni_challenge != NULL); /* * Deduce whether we're doing open authentication or * shared key authentication. We do the latter if * we're in the middle of a shared key authentication * handshake or if we're initiating an authentication * request and configured to use shared key. */ is_shared_key = has_challenge || arg >= IEEE80211_AUTH_SHARED_RESPONSE || (arg == IEEE80211_AUTH_SHARED_REQUEST && ic->ic_bss->ni_authmode == IEEE80211_AUTH_SHARED); m = ieee80211_getmgtframe(&frm, 3 * sizeof(u_int16_t) + (has_challenge && status == IEEE80211_STATUS_SUCCESS ? sizeof(u_int16_t)+IEEE80211_CHALLENGE_LEN : 0) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); ((u_int16_t *)frm)[0] = (is_shared_key) ? htole16(IEEE80211_AUTH_ALG_SHARED) : htole16(IEEE80211_AUTH_ALG_OPEN); ((u_int16_t *)frm)[1] = htole16(arg); /* sequence number */ ((u_int16_t *)frm)[2] = htole16(status);/* status */ if (has_challenge && status == IEEE80211_STATUS_SUCCESS) { ((u_int16_t *)frm)[3] = htole16((IEEE80211_CHALLENGE_LEN << 8) | IEEE80211_ELEMID_CHALLENGE); memcpy(&((u_int16_t *)frm)[4], ni->ni_challenge, IEEE80211_CHALLENGE_LEN); m->m_pkthdr.len = m->m_len = 4 * sizeof(u_int16_t) + IEEE80211_CHALLENGE_LEN; if (arg == IEEE80211_AUTH_SHARED_RESPONSE) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH, "[%s] request encrypt frame (%s)\n", ether_sprintf(ni->ni_macaddr), __func__); m->m_flags |= M_LINK0; /* WEP-encrypt, please */ } } else m->m_pkthdr.len = m->m_len = 3 * sizeof(u_int16_t); /* XXX not right for shared key */ if (status == IEEE80211_STATUS_SUCCESS) IEEE80211_NODE_STAT(ni, tx_auth); else IEEE80211_NODE_STAT(ni, tx_auth_fail); if (ic->ic_opmode == IEEE80211_M_STA) timer = IEEE80211_TRANS_WAIT; break; case IEEE80211_FC0_SUBTYPE_DEAUTH: IEEE80211_DPRINTF(ic, IEEE80211_MSG_AUTH, "[%s] send station deauthenticate (reason %d)\n", ether_sprintf(ni->ni_macaddr), arg); m = ieee80211_getmgtframe(&frm, sizeof(u_int16_t)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); *(u_int16_t *)frm = htole16(arg); /* reason */ m->m_pkthdr.len = m->m_len = sizeof(u_int16_t); IEEE80211_NODE_STAT(ni, tx_deauth); IEEE80211_NODE_STAT_SET(ni, tx_deauth_code, arg); ieee80211_node_unauthorize(ni); /* port closed */ break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: /* * asreq frame format * [2] capability information * [2] listen interval * [6*] current AP address (reassoc only) * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates * [tlv] WME * [tlv] user-specified ie's */ m = ieee80211_getmgtframe(&frm, sizeof(u_int16_t) + sizeof(u_int16_t) + IEEE80211_ADDR_LEN + 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_wme_info) + (ic->ic_opt_ie != NULL ? ic->ic_opt_ie_len : 0) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); KASSERT(ic->ic_opmode == IEEE80211_M_STA, ("wrong mode %u", ic->ic_opmode)); capinfo = IEEE80211_CAPINFO_ESS; if (ic->ic_flags & IEEE80211_F_PRIVACY) capinfo |= IEEE80211_CAPINFO_PRIVACY; /* * NB: Some 11a AP's reject the request when * short premable is set. */ if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) && IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) capinfo |= IEEE80211_CAPINFO_SHORT_PREAMBLE; if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) && (ic->ic_caps & IEEE80211_C_SHSLOT)) capinfo |= IEEE80211_CAPINFO_SHORT_SLOTTIME; *(u_int16_t *)frm = htole16(capinfo); frm += 2; *(u_int16_t *)frm = htole16(ic->ic_lintval); frm += 2; if (type == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) { IEEE80211_ADDR_COPY(frm, ic->ic_bss->ni_bssid); frm += IEEE80211_ADDR_LEN; } frm = ieee80211_add_ssid(frm, ni->ni_essid, ni->ni_esslen); frm = ieee80211_add_rates(frm, &ni->ni_rates); frm = ieee80211_add_xrates(frm, &ni->ni_rates); if ((ic->ic_flags & IEEE80211_F_WME) && ni->ni_wme_ie != NULL) frm = ieee80211_add_wme_info(frm, &ic->ic_wme); if (ic->ic_opt_ie != NULL) { memcpy(frm, ic->ic_opt_ie, ic->ic_opt_ie_len); frm += ic->ic_opt_ie_len; } m->m_pkthdr.len = m->m_len = frm - mtod(m, u_int8_t *); timer = IEEE80211_TRANS_WAIT; break; case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: /* * asreq frame format * [2] capability information * [2] status * [2] association ID * [tlv] supported rates * [tlv] extended supported rates * [tlv] WME (if enabled and STA enabled) */ m = ieee80211_getmgtframe(&frm, sizeof(u_int16_t) + sizeof(u_int16_t) + sizeof(u_int16_t) + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_wme_param) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); capinfo = getcapinfo(ic, ic->ic_curchan); *(u_int16_t *)frm = htole16(capinfo); frm += 2; *(u_int16_t *)frm = htole16(arg); /* status */ frm += 2; if (arg == IEEE80211_STATUS_SUCCESS) { *(u_int16_t *)frm = htole16(ni->ni_associd); IEEE80211_NODE_STAT(ni, tx_assoc); } else IEEE80211_NODE_STAT(ni, tx_assoc_fail); frm += 2; frm = ieee80211_add_rates(frm, &ni->ni_rates); frm = ieee80211_add_xrates(frm, &ni->ni_rates); if ((ic->ic_flags & IEEE80211_F_WME) && ni->ni_wme_ie != NULL) frm = ieee80211_add_wme_param(frm, &ic->ic_wme); m->m_pkthdr.len = m->m_len = frm - mtod(m, u_int8_t *); break; case IEEE80211_FC0_SUBTYPE_DISASSOC: IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, "[%s] send station disassociate (reason %d)\n", ether_sprintf(ni->ni_macaddr), arg); m = ieee80211_getmgtframe(&frm, sizeof(u_int16_t)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); *(u_int16_t *)frm = htole16(arg); /* reason */ m->m_pkthdr.len = m->m_len = sizeof(u_int16_t); IEEE80211_NODE_STAT(ni, tx_disassoc); IEEE80211_NODE_STAT_SET(ni, tx_disassoc_code, arg); break; default: IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, "[%s] invalid mgmt frame type %u\n", ether_sprintf(ni->ni_macaddr), type); senderr(EINVAL, is_tx_unknownmgt); /* NOTREACHED */ } ret = ieee80211_mgmt_output(ic, ni, m, type, timer); if (ret != 0) { bad: ieee80211_free_node(ni); } return ret; #undef senderr } /* * Allocate a beacon frame and fillin the appropriate bits. */ struct mbuf * ieee80211_beacon_alloc(struct ieee80211com *ic, struct ieee80211_node *ni, struct ieee80211_beacon_offsets *bo) { struct ifnet *ifp = ic->ic_ifp; struct ieee80211_frame *wh; struct mbuf *m; int pktlen; u_int8_t *frm, *efrm; u_int16_t capinfo; struct ieee80211_rateset *rs; /* * beacon frame format * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [3] parameter set (DS) * [tlv] parameter set (IBSS/TIM) * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] WME parameters * [tlv] WPA/RSN parameters * XXX Vendor-specific OIDs (e.g. Atheros) * NB: we allocate the max space required for the TIM bitmap. */ rs = &ni->ni_rates; pktlen = 8 /* time stamp */ + sizeof(u_int16_t) /* beacon interval */ + sizeof(u_int16_t) /* capabilities */ + 2 + ni->ni_esslen /* ssid */ + 2 + IEEE80211_RATE_SIZE /* supported rates */ + 2 + 1 /* DS parameters */ + 2 + 4 + ic->ic_tim_len /* DTIM/IBSSPARMS */ + 2 + 1 /* ERP */ + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + (ic->ic_caps & IEEE80211_C_WME ? /* WME */ sizeof(struct ieee80211_wme_param) : 0) + (ic->ic_caps & IEEE80211_C_WPA ? /* WPA 1+2 */ 2*sizeof(struct ieee80211_ie_wpa) : 0) ; m = ieee80211_getmgtframe(&frm, pktlen); if (m == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, "%s: cannot get buf; size %u\n", __func__, pktlen); ic->ic_stats.is_tx_nobuf++; return NULL; } memset(frm, 0, 8); /* XXX timestamp is set by hardware/driver */ frm += 8; *(u_int16_t *)frm = htole16(ni->ni_intval); frm += 2; capinfo = getcapinfo(ic, ni->ni_chan); bo->bo_caps = (u_int16_t *)frm; *(u_int16_t *)frm = htole16(capinfo); frm += 2; *frm++ = IEEE80211_ELEMID_SSID; if ((ic->ic_flags & IEEE80211_F_HIDESSID) == 0) { *frm++ = ni->ni_esslen; memcpy(frm, ni->ni_essid, ni->ni_esslen); frm += ni->ni_esslen; } else *frm++ = 0; frm = ieee80211_add_rates(frm, rs); if (ic->ic_curmode != IEEE80211_MODE_FH) { *frm++ = IEEE80211_ELEMID_DSPARMS; *frm++ = 1; *frm++ = ieee80211_chan2ieee(ic, ni->ni_chan); } bo->bo_tim = frm; if (ic->ic_opmode == IEEE80211_M_IBSS) { *frm++ = IEEE80211_ELEMID_IBSSPARMS; *frm++ = 2; *frm++ = 0; *frm++ = 0; /* TODO: ATIM window */ bo->bo_tim_len = 0; } else if (ic->ic_opmode == IEEE80211_M_HOSTAP) { struct ieee80211_tim_ie *tie = (struct ieee80211_tim_ie *) frm; tie->tim_ie = IEEE80211_ELEMID_TIM; tie->tim_len = 4; /* length */ tie->tim_count = 0; /* DTIM count */ tie->tim_period = ic->ic_dtim_period; /* DTIM period */ tie->tim_bitctl = 0; /* bitmap control */ tie->tim_bitmap[0] = 0; /* Partial Virtual Bitmap */ frm += sizeof(struct ieee80211_tim_ie); bo->bo_tim_len = 1; } bo->bo_trailer = frm; if (ic->ic_flags & IEEE80211_F_WME) { bo->bo_wme = frm; frm = ieee80211_add_wme_param(frm, &ic->ic_wme); ic->ic_flags &= ~IEEE80211_F_WMEUPDATE; } if (ic->ic_flags & IEEE80211_F_WPA) frm = ieee80211_add_wpa(frm, ic); if (ic->ic_curmode == IEEE80211_MODE_11G) { bo->bo_erp = frm; frm = ieee80211_add_erp(frm, ic); } efrm = ieee80211_add_xrates(frm, rs); bo->bo_trailer_len = efrm - bo->bo_trailer; m->m_pkthdr.len = m->m_len = efrm - mtod(m, u_int8_t *); M_PREPEND(m, sizeof(struct ieee80211_frame), M_DONTWAIT); KASSERT(m != NULL, ("no space for 802.11 header?")); wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_BEACON; wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; *(u_int16_t *)wh->i_dur = 0; IEEE80211_ADDR_COPY(wh->i_addr1, ifp->if_broadcastaddr); IEEE80211_ADDR_COPY(wh->i_addr2, ic->ic_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, ni->ni_bssid); *(u_int16_t *)wh->i_seq = 0; return m; } /* * Update the dynamic parts of a beacon frame based on the current state. */ int ieee80211_beacon_update(struct ieee80211com *ic, struct ieee80211_node *ni, struct ieee80211_beacon_offsets *bo, struct mbuf *m, int mcast) { int len_changed = 0; u_int16_t capinfo; IEEE80211_BEACON_LOCK(ic); /* XXX faster to recalculate entirely or just changes? */ capinfo = getcapinfo(ic, ni->ni_chan); *bo->bo_caps = htole16(capinfo); if (ic->ic_flags & IEEE80211_F_WME) { struct ieee80211_wme_state *wme = &ic->ic_wme; /* * Check for agressive mode change. When there is * significant high priority traffic in the BSS * throttle back BE traffic by using conservative * parameters. Otherwise BE uses agressive params * to optimize performance of legacy/non-QoS traffic. */ if (wme->wme_flags & WME_F_AGGRMODE) { if (wme->wme_hipri_traffic > wme->wme_hipri_switch_thresh) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_WME, "%s: traffic %u, disable aggressive mode\n", __func__, wme->wme_hipri_traffic); wme->wme_flags &= ~WME_F_AGGRMODE; ieee80211_wme_updateparams_locked(ic); wme->wme_hipri_traffic = wme->wme_hipri_switch_hysteresis; } else wme->wme_hipri_traffic = 0; } else { if (wme->wme_hipri_traffic <= wme->wme_hipri_switch_thresh) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_WME, "%s: traffic %u, enable aggressive mode\n", __func__, wme->wme_hipri_traffic); wme->wme_flags |= WME_F_AGGRMODE; ieee80211_wme_updateparams_locked(ic); wme->wme_hipri_traffic = 0; } else wme->wme_hipri_traffic = wme->wme_hipri_switch_hysteresis; } if (ic->ic_flags & IEEE80211_F_WMEUPDATE) { (void) ieee80211_add_wme_param(bo->bo_wme, wme); ic->ic_flags &= ~IEEE80211_F_WMEUPDATE; } } if (ic->ic_opmode == IEEE80211_M_HOSTAP) { /* NB: no IBSS support*/ struct ieee80211_tim_ie *tie = (struct ieee80211_tim_ie *) bo->bo_tim; if (ic->ic_flags & IEEE80211_F_TIMUPDATE) { u_int timlen, timoff, i; /* * ATIM/DTIM needs updating. If it fits in the * current space allocated then just copy in the * new bits. Otherwise we need to move any trailing * data to make room. Note that we know there is * contiguous space because ieee80211_beacon_allocate * insures there is space in the mbuf to write a * maximal-size virtual bitmap (based on ic_max_aid). */ /* * Calculate the bitmap size and offset, copy any * trailer out of the way, and then copy in the * new bitmap and update the information element. * Note that the tim bitmap must contain at least * one byte and any offset must be even. */ if (ic->ic_ps_pending != 0) { timoff = 128; /* impossibly large */ for (i = 0; i < ic->ic_tim_len; i++) if (ic->ic_tim_bitmap[i]) { timoff = i &~ 1; break; } KASSERT(timoff != 128, ("tim bitmap empty!")); for (i = ic->ic_tim_len-1; i >= timoff; i--) if (ic->ic_tim_bitmap[i]) break; timlen = 1 + (i - timoff); } else { timoff = 0; timlen = 1; } if (timlen != bo->bo_tim_len) { /* copy up/down trailer */ int adjust = tie->tim_bitmap+timlen - bo->bo_trailer; ovbcopy(bo->bo_trailer, bo->bo_trailer+adjust, bo->bo_trailer_len); bo->bo_trailer += adjust; bo->bo_wme += adjust; bo->bo_erp += adjust; bo->bo_tim_len = timlen; /* update information element */ tie->tim_len = 3 + timlen; tie->tim_bitctl = timoff; len_changed = 1; } memcpy(tie->tim_bitmap, ic->ic_tim_bitmap + timoff, bo->bo_tim_len); ic->ic_flags &= ~IEEE80211_F_TIMUPDATE; IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, "%s: TIM updated, pending %u, off %u, len %u\n", __func__, ic->ic_ps_pending, timoff, timlen); } /* count down DTIM period */ if (tie->tim_count == 0) tie->tim_count = tie->tim_period - 1; else tie->tim_count--; /* update state for buffered multicast frames on DTIM */ if (mcast && tie->tim_count == 0) tie->tim_bitctl |= 1; else tie->tim_bitctl &= ~1; if (ic->ic_flags_ext & IEEE80211_FEXT_ERPUPDATE) { /* * ERP element needs updating. */ (void) ieee80211_add_erp(bo->bo_erp, ic); ic->ic_flags_ext &= ~IEEE80211_FEXT_ERPUPDATE; } } IEEE80211_BEACON_UNLOCK(ic); return len_changed; } /* * Save an outbound packet for a node in power-save sleep state. * The new packet is placed on the node's saved queue, and the TIM * is changed, if necessary. */ void ieee80211_pwrsave(struct ieee80211com *ic, struct ieee80211_node *ni, struct mbuf *m) { int qlen, age; IEEE80211_NODE_SAVEQ_LOCK(ni); if (_IF_QFULL(&ni->ni_savedq)) { _IF_DROP(&ni->ni_savedq); IEEE80211_NODE_SAVEQ_UNLOCK(ni); IEEE80211_DPRINTF(ic, IEEE80211_MSG_ANY, "[%s] pwr save q overflow, drops %d (size %d)\n", ether_sprintf(ni->ni_macaddr), ni->ni_savedq.ifq_drops, IEEE80211_PS_MAX_QUEUE); #ifdef IEEE80211_DEBUG if (ieee80211_msg_dumppkts(ic)) ieee80211_dump_pkt(mtod(m, caddr_t), m->m_len, -1, -1); #endif m_freem(m); return; } /* * Tag the frame with it's expiry time and insert * it in the queue. The aging interval is 4 times * the listen interval specified by the station. * Frames that sit around too long are reclaimed * using this information. */ /* XXX handle overflow? */ age = ((ni->ni_intval * ic->ic_bintval) << 2) / 1024; /* TU -> secs */ _IEEE80211_NODE_SAVEQ_ENQUEUE(ni, m, qlen, age); IEEE80211_NODE_SAVEQ_UNLOCK(ni); IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, "[%s] save frame with age %d, %u now queued\n", ether_sprintf(ni->ni_macaddr), age, qlen); if (qlen == 1) ic->ic_set_tim(ni, 1); } Index: head/sys/netgraph/ng_vlan.c =================================================================== --- head/sys/netgraph/ng_vlan.c (revision 162374) +++ head/sys/netgraph/ng_vlan.c (revision 162375) @@ -1,469 +1,469 @@ /*- * Copyright (c) 2003 IPNET Internet Communication Company * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * Author: Ruslan Ermilov * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static ng_constructor_t ng_vlan_constructor; static ng_rcvmsg_t ng_vlan_rcvmsg; static ng_shutdown_t ng_vlan_shutdown; static ng_newhook_t ng_vlan_newhook; static ng_rcvdata_t ng_vlan_rcvdata; static ng_disconnect_t ng_vlan_disconnect; /* Parse type for struct ng_vlan_filter. */ static const struct ng_parse_struct_field ng_vlan_filter_fields[] = NG_VLAN_FILTER_FIELDS; static const struct ng_parse_type ng_vlan_filter_type = { &ng_parse_struct_type, &ng_vlan_filter_fields }; static int ng_vlan_getTableLength(const struct ng_parse_type *type, const u_char *start, const u_char *buf) { const struct ng_vlan_table *const table = (const struct ng_vlan_table *)(buf - sizeof(u_int32_t)); return table->n; } /* Parse type for struct ng_vlan_table. */ static const struct ng_parse_array_info ng_vlan_table_array_info = { &ng_vlan_filter_type, ng_vlan_getTableLength }; static const struct ng_parse_type ng_vlan_table_array_type = { &ng_parse_array_type, &ng_vlan_table_array_info }; static const struct ng_parse_struct_field ng_vlan_table_fields[] = NG_VLAN_TABLE_FIELDS; static const struct ng_parse_type ng_vlan_table_type = { &ng_parse_struct_type, &ng_vlan_table_fields }; /* List of commands and how to convert arguments to/from ASCII. */ static const struct ng_cmdlist ng_vlan_cmdlist[] = { { NGM_VLAN_COOKIE, NGM_VLAN_ADD_FILTER, "addfilter", &ng_vlan_filter_type, NULL }, { NGM_VLAN_COOKIE, NGM_VLAN_DEL_FILTER, "delfilter", &ng_parse_hookbuf_type, NULL }, { NGM_VLAN_COOKIE, NGM_VLAN_GET_TABLE, "gettable", NULL, &ng_vlan_table_type }, { 0 } }; static struct ng_type ng_vlan_typestruct = { .version = NG_ABI_VERSION, .name = NG_VLAN_NODE_TYPE, .constructor = ng_vlan_constructor, .rcvmsg = ng_vlan_rcvmsg, .shutdown = ng_vlan_shutdown, .newhook = ng_vlan_newhook, .rcvdata = ng_vlan_rcvdata, .disconnect = ng_vlan_disconnect, .cmdlist = ng_vlan_cmdlist, }; NETGRAPH_INIT(vlan, &ng_vlan_typestruct); struct filter { LIST_ENTRY(filter) next; u_int16_t vlan; hook_p hook; }; #define HASHSIZE 16 #define HASH(id) ((((id) >> 8) ^ ((id) >> 4) ^ (id)) & 0x0f) LIST_HEAD(filterhead, filter); typedef struct { hook_p downstream_hook; hook_p nomatch_hook; struct filterhead hashtable[HASHSIZE]; u_int32_t nent; } *priv_p; static struct filter * ng_vlan_findentry(priv_p priv, u_int16_t vlan) { struct filterhead *chain = &priv->hashtable[HASH(vlan)]; struct filter *f; LIST_FOREACH(f, chain, next) if (f->vlan == vlan) return (f); return (NULL); } static int ng_vlan_constructor(node_p node) { priv_p priv; int i; MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); if (priv == NULL) return (ENOMEM); for (i = 0; i < HASHSIZE; i++) LIST_INIT(&priv->hashtable[i]); NG_NODE_SET_PRIVATE(node, priv); return (0); } static int ng_vlan_newhook(node_p node, hook_p hook, const char *name) { const priv_p priv = NG_NODE_PRIVATE(node); if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0) priv->downstream_hook = hook; else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0) priv->nomatch_hook = hook; else { /* * Any other hook name is valid and can * later be associated with a filter rule. */ } NG_HOOK_SET_PRIVATE(hook, NULL); return (0); } static int ng_vlan_rcvmsg(node_p node, item_p item, hook_p lasthook) { const priv_p priv = NG_NODE_PRIVATE(node); int error = 0; struct ng_mesg *msg, *resp = NULL; struct ng_vlan_filter *vf; struct filter *f; hook_p hook; struct ng_vlan_table *t; int i; NGI_GET_MSG(item, msg); /* Deal with message according to cookie and command. */ switch (msg->header.typecookie) { case NGM_VLAN_COOKIE: switch (msg->header.cmd) { case NGM_VLAN_ADD_FILTER: /* Check that message is long enough. */ if (msg->header.arglen != sizeof(*vf)) { error = EINVAL; break; } vf = (struct ng_vlan_filter *)msg->data; /* Sanity check the VLAN ID value. */ if (vf->vlan & ~EVL_VLID_MASK) { error = EINVAL; break; } /* Check that a referenced hook exists. */ hook = ng_findhook(node, vf->hook); if (hook == NULL) { error = ENOENT; break; } /* And is not one of the special hooks. */ if (hook == priv->downstream_hook || hook == priv->nomatch_hook) { error = EINVAL; break; } /* And is not already in service. */ if (NG_HOOK_PRIVATE(hook) != NULL) { error = EEXIST; break; } /* Check we don't already trap this VLAN. */ if (ng_vlan_findentry(priv, vf->vlan)) { error = EEXIST; break; } /* Create filter. */ MALLOC(f, struct filter *, sizeof(*f), M_NETGRAPH, M_NOWAIT | M_ZERO); if (f == NULL) { error = ENOMEM; break; } /* Link filter and hook together. */ f->hook = hook; f->vlan = vf->vlan; NG_HOOK_SET_PRIVATE(hook, f); /* Register filter in a hash table. */ LIST_INSERT_HEAD( &priv->hashtable[HASH(f->vlan)], f, next); priv->nent++; break; case NGM_VLAN_DEL_FILTER: /* Check that message is long enough. */ if (msg->header.arglen != NG_HOOKSIZ) { error = EINVAL; break; } /* Check that hook exists and is active. */ hook = ng_findhook(node, (char *)msg->data); if (hook == NULL || (f = NG_HOOK_PRIVATE(hook)) == NULL) { error = ENOENT; break; } /* Purge a rule that refers to this hook. */ NG_HOOK_SET_PRIVATE(hook, NULL); LIST_REMOVE(f, next); priv->nent--; FREE(f, M_NETGRAPH); break; case NGM_VLAN_GET_TABLE: NG_MKRESPONSE(resp, msg, sizeof(*t) + priv->nent * sizeof(*t->filter), M_NOWAIT); if (resp == NULL) { error = ENOMEM; break; } t = (struct ng_vlan_table *)resp->data; t->n = priv->nent; vf = &t->filter[0]; for (i = 0; i < HASHSIZE; i++) { LIST_FOREACH(f, &priv->hashtable[i], next) { vf->vlan = f->vlan; strncpy(vf->hook, NG_HOOK_NAME(f->hook), NG_HOOKSIZ); vf++; } } break; default: /* Unknown command. */ error = EINVAL; break; } break; case NGM_FLOW_COOKIE: { struct ng_mesg *copy; struct filterhead *chain; struct filter *f; /* * Flow control messages should come only * from downstream. */ if (lasthook == NULL) break; if (lasthook != priv->downstream_hook) break; /* Broadcast the event to all uplinks. */ for (i = 0, chain = priv->hashtable; i < HASHSIZE; i++, chain++) LIST_FOREACH(f, chain, next) { NG_COPYMESSAGE(copy, msg, M_NOWAIT); if (copy == NULL) continue; NG_SEND_MSG_HOOK(error, node, copy, f->hook, 0); } break; } default: /* Unknown type cookie. */ error = EINVAL; break; } NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } static int ng_vlan_rcvdata(hook_p hook, item_p item) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct ether_header *eh; struct ether_vlan_header *evl; int error; u_int16_t vlan; struct mbuf *m; - struct m_tag *mtag; struct filter *f; /* Make sure we have an entire header. */ NGI_GET_M(item, m); if (m->m_len < sizeof(*eh) && (m = m_pullup(m, sizeof(*eh))) == NULL) { NG_FREE_ITEM(item); return (EINVAL); } eh = mtod(m, struct ether_header *); if (hook == priv->downstream_hook) { /* * If from downstream, select between a match hook * or the nomatch hook. */ - mtag = m_tag_locate(m, MTAG_VLAN, MTAG_VLAN_TAG, NULL); - if (mtag != NULL || eh->ether_type == htons(ETHERTYPE_VLAN)) { - if (mtag != NULL) { + if (m->m_flags & M_VLANTAG || + eh->ether_type == htons(ETHERTYPE_VLAN)) { + if (m->m_flags & M_VLANTAG) { /* * Packet is tagged, m contains a normal * Ethernet frame; tag is stored out-of-band. */ - vlan = EVL_VLANOFTAG(VLAN_TAG_VALUE(mtag)); + vlan = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag); (void)&evl; /* XXX silence GCC */ } else { if (m->m_len < sizeof(*evl) && (m = m_pullup(m, sizeof(*evl))) == NULL) { NG_FREE_ITEM(item); return (EINVAL); } evl = mtod(m, struct ether_vlan_header *); vlan = EVL_VLANOFTAG(ntohs(evl->evl_tag)); } if ((f = ng_vlan_findentry(priv, vlan)) != NULL) { - if (mtag != NULL) - m_tag_delete(m, mtag); - else { + if (m->m_flags & M_VLANTAG) { + m->m_pkthdr.ether_vtag = 0; + m->m_flags &= ~M_VLANTAG; + } else { evl->evl_encap_proto = evl->evl_proto; bcopy(mtod(m, caddr_t), mtod(m, caddr_t) + ETHER_VLAN_ENCAP_LEN, ETHER_HDR_LEN); m_adj(m, ETHER_VLAN_ENCAP_LEN); } } } else f = NULL; if (f != NULL) NG_FWD_NEW_DATA(error, item, f->hook, m); else NG_FWD_NEW_DATA(error, item, priv->nomatch_hook, m); } else { /* * It is heading towards the downstream. * If from nomatch, pass it unmodified. * Otherwise, do the VLAN encapsulation. */ if (hook != priv->nomatch_hook) { if ((f = NG_HOOK_PRIVATE(hook)) == NULL) { NG_FREE_ITEM(item); NG_FREE_M(m); return (EOPNOTSUPP); } M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, M_DONTWAIT); /* M_PREPEND takes care of m_len and m_pkthdr.len. */ if (m == NULL || (m->m_len < sizeof(*evl) && (m = m_pullup(m, sizeof(*evl))) == NULL)) { NG_FREE_ITEM(item); return (ENOMEM); } /* * Transform the Ethernet header into an Ethernet header * with 802.1Q encapsulation. */ bcopy(mtod(m, char *) + ETHER_VLAN_ENCAP_LEN, mtod(m, char *), ETHER_HDR_LEN); evl = mtod(m, struct ether_vlan_header *); evl->evl_proto = evl->evl_encap_proto; evl->evl_encap_proto = htons(ETHERTYPE_VLAN); evl->evl_tag = htons(f->vlan); } NG_FWD_NEW_DATA(error, item, priv->downstream_hook, m); } return (error); } static int ng_vlan_shutdown(node_p node) { const priv_p priv = NG_NODE_PRIVATE(node); NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node); FREE(priv, M_NETGRAPH); return (0); } static int ng_vlan_disconnect(hook_p hook) { const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct filter *f; if (hook == priv->downstream_hook) priv->downstream_hook = NULL; else if (hook == priv->nomatch_hook) priv->nomatch_hook = NULL; else { /* Purge a rule that refers to this hook. */ if ((f = NG_HOOK_PRIVATE(hook)) != NULL) { LIST_REMOVE(f, next); priv->nent--; FREE(f, M_NETGRAPH); } } NG_HOOK_SET_PRIVATE(hook, NULL); if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) ng_rmnode_self(NG_HOOK_NODE(hook)); return (0); } Index: head/sys/sys/mbuf.h =================================================================== --- head/sys/sys/mbuf.h (revision 162374) +++ head/sys/sys/mbuf.h (revision 162375) @@ -1,858 +1,855 @@ /*- * Copyright (c) 1982, 1986, 1988, 1993 * The Regents of the University of California. 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. * * @(#)mbuf.h 8.5 (Berkeley) 2/19/95 * $FreeBSD$ */ #ifndef _SYS_MBUF_H_ #define _SYS_MBUF_H_ /* XXX: These includes suck. Sorry! */ #include #ifdef _KERNEL #include #include #ifdef WITNESS #include #endif #endif /* * Mbufs are of a single size, MSIZE (sys/param.h), which * includes overhead. An mbuf may add a single "mbuf cluster" of size * MCLBYTES (also in sys/param.h), which has no additional overhead * and is used instead of the internal data area; this is done when * at least MINCLSIZE of data must be stored. Additionally, it is possible * to allocate a separate buffer externally and attach it to the mbuf in * a way similar to that of mbuf clusters. */ #define MLEN (MSIZE - sizeof(struct m_hdr)) /* normal data len */ #define MHLEN (MLEN - sizeof(struct pkthdr)) /* data len w/pkthdr */ #define MINCLSIZE (MHLEN + 1) /* smallest amount to put in cluster */ #define M_MAXCOMPRESS (MHLEN / 2) /* max amount to copy for compression */ #ifdef _KERNEL /*- * Macros for type conversion: * mtod(m, t) -- Convert mbuf pointer to data pointer of correct type. * dtom(x) -- Convert data pointer within mbuf to mbuf pointer (XXX). */ #define mtod(m, t) ((t)((m)->m_data)) #define dtom(x) ((struct mbuf *)((intptr_t)(x) & ~(MSIZE-1))) /* * Argument structure passed to UMA routines during mbuf and packet * allocations. */ struct mb_args { int flags; /* Flags for mbuf being allocated */ short type; /* Type of mbuf being allocated */ }; #endif /* _KERNEL */ /* * Header present at the beginning of every mbuf. */ struct m_hdr { struct mbuf *mh_next; /* next buffer in chain */ struct mbuf *mh_nextpkt; /* next chain in queue/record */ caddr_t mh_data; /* location of data */ int mh_len; /* amount of data in this mbuf */ int mh_flags; /* flags; see below */ short mh_type; /* type of data in this mbuf */ }; /* * Packet tag structure (see below for details). */ struct m_tag { SLIST_ENTRY(m_tag) m_tag_link; /* List of packet tags */ u_int16_t m_tag_id; /* Tag ID */ u_int16_t m_tag_len; /* Length of data */ u_int32_t m_tag_cookie; /* ABI/Module ID */ void (*m_tag_free)(struct m_tag *); }; /* * Record/packet header in first mbuf of chain; valid only if M_PKTHDR is set. */ struct pkthdr { struct ifnet *rcvif; /* rcv interface */ int len; /* total packet length */ /* variables for ip and tcp reassembly */ void *header; /* pointer to packet header */ /* variables for hardware checksum */ int csum_flags; /* flags regarding checksum */ int csum_data; /* data field used by csum routines */ u_int16_t tso_segsz; /* TSO segment size */ - u_int16_t ether_vlan; /* Ethernet 802.1p+q vlan tag */ + u_int16_t ether_vtag; /* Ethernet 802.1p+q vlan tag */ SLIST_HEAD(packet_tags, m_tag) tags; /* list of packet tags */ }; /* * Description of external storage mapped into mbuf; valid only if M_EXT is set. */ struct m_ext { caddr_t ext_buf; /* start of buffer */ void (*ext_free) /* free routine if not the usual */ (void *, void *); void *ext_args; /* optional argument pointer */ u_int ext_size; /* size of buffer, for ext_free */ volatile u_int *ref_cnt; /* pointer to ref count info */ int ext_type; /* type of external storage */ }; /* * The core of the mbuf object along with some shortcut defines for * practical purposes. */ struct mbuf { struct m_hdr m_hdr; union { struct { struct pkthdr MH_pkthdr; /* M_PKTHDR set */ union { struct m_ext MH_ext; /* M_EXT set */ char MH_databuf[MHLEN]; } MH_dat; } MH; char M_databuf[MLEN]; /* !M_PKTHDR, !M_EXT */ } M_dat; }; #define m_next m_hdr.mh_next #define m_len m_hdr.mh_len #define m_data m_hdr.mh_data #define m_type m_hdr.mh_type #define m_flags m_hdr.mh_flags #define m_nextpkt m_hdr.mh_nextpkt #define m_act m_nextpkt #define m_pkthdr M_dat.MH.MH_pkthdr #define m_ext M_dat.MH.MH_dat.MH_ext #define m_pktdat M_dat.MH.MH_dat.MH_databuf #define m_dat M_dat.M_databuf /* * mbuf flags. */ #define M_EXT 0x0001 /* has associated external storage */ #define M_PKTHDR 0x0002 /* start of record */ #define M_EOR 0x0004 /* end of record */ #define M_RDONLY 0x0008 /* associated data is marked read-only */ #define M_PROTO1 0x0010 /* protocol-specific */ #define M_PROTO2 0x0020 /* protocol-specific */ #define M_PROTO3 0x0040 /* protocol-specific */ #define M_PROTO4 0x0080 /* protocol-specific */ #define M_PROTO5 0x0100 /* protocol-specific */ #define M_SKIP_FIREWALL 0x4000 /* skip firewall processing */ #define M_FREELIST 0x8000 /* mbuf is on the free list */ /* * mbuf pkthdr flags (also stored in m_flags). */ #define M_BCAST 0x0200 /* send/received as link-level broadcast */ #define M_MCAST 0x0400 /* send/received as link-level multicast */ #define M_FRAG 0x0800 /* packet is a fragment of a larger packet */ #define M_FIRSTFRAG 0x1000 /* packet is first fragment */ #define M_LASTFRAG 0x2000 /* packet is last fragment */ #define M_VLANTAG 0x10000 /* packet has VLAN tag attached */ /* * External buffer types: identify ext_buf type. */ #define EXT_CLUSTER 1 /* mbuf cluster */ #define EXT_SFBUF 2 /* sendfile(2)'s sf_bufs */ #define EXT_JUMBOP 3 /* jumbo cluster 4096 bytes */ #define EXT_JUMBO9 4 /* jumbo cluster 9216 bytes */ #define EXT_JUMBO16 5 /* jumbo cluster 16184 bytes */ #define EXT_PACKET 6 /* mbuf+cluster from packet zone */ #define EXT_NET_DRV 100 /* custom ext_buf provided by net driver(s) */ #define EXT_MOD_TYPE 200 /* custom module's ext_buf type */ #define EXT_DISPOSABLE 300 /* can throw this buffer away w/page flipping */ #define EXT_EXTREF 400 /* has externally maintained ref_cnt ptr */ /* * Flags copied when copying m_pkthdr. */ #define M_COPYFLAGS (M_PKTHDR|M_EOR|M_RDONLY|M_PROTO1|M_PROTO1|M_PROTO2|\ M_PROTO3|M_PROTO4|M_PROTO5|M_SKIP_FIREWALL|\ M_BCAST|M_MCAST|M_FRAG|M_FIRSTFRAG|M_LASTFRAG|\ M_VLANTAG) /* * Flags to purge when crossing layers. */ #define M_PROTOFLAGS (M_PROTO1|M_PROTO2|M_PROTO3|M_PROTO4|M_PROTO5) /* * Flags indicating hw checksum support and sw checksum requirements. * This field can be directly tested against if_data.ifi_hwassist. */ #define CSUM_IP 0x0001 /* will csum IP */ #define CSUM_TCP 0x0002 /* will csum TCP */ #define CSUM_UDP 0x0004 /* will csum UDP */ #define CSUM_IP_FRAGS 0x0008 /* will csum IP fragments */ #define CSUM_FRAGMENT 0x0010 /* will do IP fragmentation */ #define CSUM_TSO 0x0020 /* will do TSO */ #define CSUM_IP_CHECKED 0x0100 /* did csum IP */ #define CSUM_IP_VALID 0x0200 /* ... the csum is valid */ #define CSUM_DATA_VALID 0x0400 /* csum_data field is valid */ #define CSUM_PSEUDO_HDR 0x0800 /* csum_data has pseudo hdr */ #define CSUM_DELAY_DATA (CSUM_TCP | CSUM_UDP) #define CSUM_DELAY_IP (CSUM_IP) /* XXX add ipv6 here too? */ /* * mbuf types. */ #define MT_NOTMBUF 0 /* USED INTERNALLY ONLY! Object is not mbuf */ #define MT_DATA 1 /* dynamic (data) allocation */ #define MT_HEADER MT_DATA /* packet header, use M_PKTHDR instead */ #define MT_SONAME 8 /* socket name */ #define MT_CONTROL 14 /* extra-data protocol message */ #define MT_OOBDATA 15 /* expedited data */ #define MT_NTYPES 16 /* number of mbuf types for mbtypes[] */ #define MT_NOINIT 255 /* Not a type but a flag to allocate a non-initialized mbuf */ /* * General mbuf allocator statistics structure. * * Many of these statistics are no longer used; we instead track many * allocator statistics through UMA's built in statistics mechanism. */ struct mbstat { u_long m_mbufs; /* XXX */ u_long m_mclusts; /* XXX */ u_long m_drain; /* times drained protocols for space */ u_long m_mcfail; /* XXX: times m_copym failed */ u_long m_mpfail; /* XXX: times m_pullup failed */ u_long m_msize; /* length of an mbuf */ u_long m_mclbytes; /* length of an mbuf cluster */ u_long m_minclsize; /* min length of data to allocate a cluster */ u_long m_mlen; /* length of data in an mbuf */ u_long m_mhlen; /* length of data in a header mbuf */ /* Number of mbtypes (gives # elems in mbtypes[] array: */ short m_numtypes; /* XXX: Sendfile stats should eventually move to their own struct */ u_long sf_iocnt; /* times sendfile had to do disk I/O */ u_long sf_allocfail; /* times sfbuf allocation failed */ u_long sf_allocwait; /* times sfbuf allocation had to wait */ }; /* * Flags specifying how an allocation should be made. * * The flag to use is as follows: * - M_DONTWAIT or M_NOWAIT from an interrupt handler to not block allocation. * - M_WAIT or M_WAITOK or M_TRYWAIT from wherever it is safe to block. * * M_DONTWAIT/M_NOWAIT means that we will not block the thread explicitly * and if we cannot allocate immediately we may return NULL, * whereas M_WAIT/M_WAITOK/M_TRYWAIT means that if we cannot allocate * resources we will block until they are available, and thus never * return NULL. * * XXX Eventually just phase this out to use M_WAITOK/M_NOWAIT. */ #define MBTOM(how) (how) #define M_DONTWAIT M_NOWAIT #define M_TRYWAIT M_WAITOK #define M_WAIT M_WAITOK /* * String names of mbuf-related UMA(9) and malloc(9) types. Exposed to * !_KERNEL so that monitoring tools can look up the zones with * libmemstat(3). */ #define MBUF_MEM_NAME "mbuf" #define MBUF_CLUSTER_MEM_NAME "mbuf_cluster" #define MBUF_PACKET_MEM_NAME "mbuf_packet" #define MBUF_JUMBOP_MEM_NAME "mbuf_jumbo_pagesize" #define MBUF_JUMBO9_MEM_NAME "mbuf_jumbo_9k" #define MBUF_JUMBO16_MEM_NAME "mbuf_jumbo_16k" #define MBUF_TAG_MEM_NAME "mbuf_tag" #define MBUF_EXTREFCNT_MEM_NAME "mbuf_ext_refcnt" #ifdef _KERNEL #ifdef WITNESS #define MBUF_CHECKSLEEP(how) do { \ if (how == M_WAITOK) \ WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, \ "Sleeping in \"%s\"", __func__); \ } while (0) #else #define MBUF_CHECKSLEEP(how) #endif /* * Network buffer allocation API * * The rest of it is defined in kern/kern_mbuf.c */ extern uma_zone_t zone_mbuf; extern uma_zone_t zone_clust; extern uma_zone_t zone_pack; extern uma_zone_t zone_jumbop; extern uma_zone_t zone_jumbo9; extern uma_zone_t zone_jumbo16; extern uma_zone_t zone_ext_refcnt; -extern uma_zone_t zone_mtag_vlan; static __inline struct mbuf *m_get(int how, short type); static __inline struct mbuf *m_gethdr(int how, short type); static __inline struct mbuf *m_getcl(int how, short type, int flags); static __inline struct mbuf *m_getjcl(int how, short type, int flags, int size); static __inline struct mbuf *m_getclr(int how, short type); /* XXX */ static __inline struct mbuf *m_free(struct mbuf *m); static __inline void m_clget(struct mbuf *m, int how); static __inline void *m_cljget(struct mbuf *m, int how, int size); static __inline void m_chtype(struct mbuf *m, short new_type); void mb_free_ext(struct mbuf *); static __inline struct mbuf * m_get(int how, short type) { struct mb_args args; args.flags = 0; args.type = type; return (struct mbuf *)(uma_zalloc_arg(zone_mbuf, &args, how)); } /* XXX This should be depracated, very little use */ static __inline struct mbuf * m_getclr(int how, short type) { struct mbuf *m; struct mb_args args; args.flags = 0; args.type = type; m = uma_zalloc_arg(zone_mbuf, &args, how); if (m != NULL) bzero(m->m_data, MLEN); return m; } static __inline struct mbuf * m_gethdr(int how, short type) { struct mb_args args; args.flags = M_PKTHDR; args.type = type; return (struct mbuf *)(uma_zalloc_arg(zone_mbuf, &args, how)); } static __inline struct mbuf * m_getcl(int how, short type, int flags) { struct mb_args args; args.flags = flags; args.type = type; return (struct mbuf *)(uma_zalloc_arg(zone_pack, &args, how)); } /* * m_getjcl() returns an mbuf with a cluster of the specified size attached. * For size it takes MCLBYTES, MJUMPAGESIZE, MJUM9BYTES, MJUM16BYTES. */ static __inline /* XXX: This is rather large, should be real function maybe. */ struct mbuf * m_getjcl(int how, short type, int flags, int size) { struct mb_args args; struct mbuf *m, *n; uma_zone_t zone; args.flags = flags; args.type = type; m = uma_zalloc_arg(zone_mbuf, &args, how); if (m == NULL) return NULL; switch (size) { case MCLBYTES: zone = zone_clust; break; #if MJUMPAGESIZE != MCLBYTES case MJUMPAGESIZE: zone = zone_jumbop; break; #endif case MJUM9BYTES: zone = zone_jumbo9; break; case MJUM16BYTES: zone = zone_jumbo16; break; default: panic("%s: m_getjcl: invalid cluster type", __func__); } n = uma_zalloc_arg(zone, m, how); if (n == NULL) { uma_zfree(zone_mbuf, m); return NULL; } return m; } static __inline struct mbuf * m_free(struct mbuf *m) { struct mbuf *n = m->m_next; if (m->m_flags & M_EXT) mb_free_ext(m); else uma_zfree(zone_mbuf, m); return n; } static __inline void m_clget(struct mbuf *m, int how) { if (m->m_flags & M_EXT) printf("%s: %p mbuf already has cluster\n", __func__, m); m->m_ext.ext_buf = (char *)NULL; uma_zalloc_arg(zone_clust, m, how); } /* * m_cljget() is different from m_clget() as it can allocate clusters * without attaching them to an mbuf. In that case the return value * is the pointer to the cluster of the requested size. If an mbuf was * specified, it gets the cluster attached to it and the return value * can be safely ignored. * For size it takes MCLBYTES, MJUMPAGESIZE, MJUM9BYTES, MJUM16BYTES. */ static __inline void * m_cljget(struct mbuf *m, int how, int size) { uma_zone_t zone; if (m && m->m_flags & M_EXT) printf("%s: %p mbuf already has cluster\n", __func__, m); if (m != NULL) m->m_ext.ext_buf = NULL; switch (size) { case MCLBYTES: zone = zone_clust; break; #if MJUMPAGESIZE != MCLBYTES case MJUMPAGESIZE: zone = zone_jumbop; break; #endif case MJUM9BYTES: zone = zone_jumbo9; break; case MJUM16BYTES: zone = zone_jumbo16; break; default: panic("%s: m_getjcl: invalid cluster type", __func__); } return (uma_zalloc_arg(zone, m, how)); } static __inline void m_chtype(struct mbuf *m, short new_type) { m->m_type = new_type; } /* * mbuf, cluster, and external object allocation macros * (for compatibility purposes). */ /* NB: M_COPY_PKTHDR is deprecated. Use M_MOVE_PKTHDR or m_dup_pktdr. */ #define M_MOVE_PKTHDR(to, from) m_move_pkthdr((to), (from)) #define MGET(m, how, type) ((m) = m_get((how), (type))) #define MGETHDR(m, how, type) ((m) = m_gethdr((how), (type))) #define MCLGET(m, how) m_clget((m), (how)) #define MEXTADD(m, buf, size, free, args, flags, type) \ m_extadd((m), (caddr_t)(buf), (size), (free), (args), (flags), (type)) /* * Evaluate TRUE if it's safe to write to the mbuf m's data region (this * can be both the local data payload, or an external buffer area, * depending on whether M_EXT is set). */ #define M_WRITABLE(m) (!((m)->m_flags & M_RDONLY) && \ (!(((m)->m_flags & M_EXT)) || \ (*((m)->m_ext.ref_cnt) == 1)) ) \ /* Check if the supplied mbuf has a packet header, or else panic. */ #define M_ASSERTPKTHDR(m) \ KASSERT(m != NULL && m->m_flags & M_PKTHDR, \ ("%s: no mbuf packet header!", __func__)) /* Ensure that the supplied mbuf is a valid, non-free mbuf. */ /* XXX: Broken at the moment. Need some UMA magic to make it work again. */ #define M_ASSERTVALID(m) \ KASSERT((((struct mbuf *)m)->m_flags & 0) == 0, \ ("%s: attempted use of a free mbuf!", __func__)) /* * Set the m_data pointer of a newly-allocated mbuf (m_get/MGET) to place * an object of the specified size at the end of the mbuf, longword aligned. */ #define M_ALIGN(m, len) do { \ KASSERT(!((m)->m_flags & (M_PKTHDR|M_EXT)), \ ("%s: M_ALIGN not normal mbuf", __func__)); \ KASSERT((m)->m_data == (m)->m_dat, \ ("%s: M_ALIGN not a virgin mbuf", __func__)); \ (m)->m_data += (MLEN - (len)) & ~(sizeof(long) - 1); \ } while (0) /* * As above, for mbufs allocated with m_gethdr/MGETHDR * or initialized by M_COPY_PKTHDR. */ #define MH_ALIGN(m, len) do { \ KASSERT((m)->m_flags & M_PKTHDR && !((m)->m_flags & M_EXT), \ ("%s: MH_ALIGN not PKTHDR mbuf", __func__)); \ KASSERT((m)->m_data == (m)->m_pktdat, \ ("%s: MH_ALIGN not a virgin mbuf", __func__)); \ (m)->m_data += (MHLEN - (len)) & ~(sizeof(long) - 1); \ } while (0) /* * Compute the amount of space available * before the current start of data in an mbuf. * * The M_WRITABLE() is a temporary, conservative safety measure: the burden * of checking writability of the mbuf data area rests solely with the caller. */ #define M_LEADINGSPACE(m) \ ((m)->m_flags & M_EXT ? \ (M_WRITABLE(m) ? (m)->m_data - (m)->m_ext.ext_buf : 0): \ (m)->m_flags & M_PKTHDR ? (m)->m_data - (m)->m_pktdat : \ (m)->m_data - (m)->m_dat) /* * Compute the amount of space available * after the end of data in an mbuf. * * The M_WRITABLE() is a temporary, conservative safety measure: the burden * of checking writability of the mbuf data area rests solely with the caller. */ #define M_TRAILINGSPACE(m) \ ((m)->m_flags & M_EXT ? \ (M_WRITABLE(m) ? (m)->m_ext.ext_buf + (m)->m_ext.ext_size \ - ((m)->m_data + (m)->m_len) : 0) : \ &(m)->m_dat[MLEN] - ((m)->m_data + (m)->m_len)) /* * Arrange to prepend space of size plen to mbuf m. * If a new mbuf must be allocated, how specifies whether to wait. * If the allocation fails, the original mbuf chain is freed and m is * set to NULL. */ #define M_PREPEND(m, plen, how) do { \ struct mbuf **_mmp = &(m); \ struct mbuf *_mm = *_mmp; \ int _mplen = (plen); \ int __mhow = (how); \ \ MBUF_CHECKSLEEP(how); \ if (M_LEADINGSPACE(_mm) >= _mplen) { \ _mm->m_data -= _mplen; \ _mm->m_len += _mplen; \ } else \ _mm = m_prepend(_mm, _mplen, __mhow); \ if (_mm != NULL && _mm->m_flags & M_PKTHDR) \ _mm->m_pkthdr.len += _mplen; \ *_mmp = _mm; \ } while (0) /* * Change mbuf to new type. * This is a relatively expensive operation and should be avoided. */ #define MCHTYPE(m, t) m_chtype((m), (t)) /* Length to m_copy to copy all. */ #define M_COPYALL 1000000000 /* Compatibility with 4.3. */ #define m_copy(m, o, l) m_copym((m), (o), (l), M_DONTWAIT) extern int max_datalen; /* MHLEN - max_hdr */ extern int max_hdr; /* Largest link + protocol header */ extern int max_linkhdr; /* Largest link-level header */ extern int max_protohdr; /* Largest protocol header */ extern struct mbstat mbstat; /* General mbuf stats/infos */ extern int nmbclusters; /* Maximum number of clusters */ struct uio; void m_adj(struct mbuf *, int); void m_align(struct mbuf *, int); int m_apply(struct mbuf *, int, int, int (*)(void *, void *, u_int), void *); int m_append(struct mbuf *, int, c_caddr_t); void m_cat(struct mbuf *, struct mbuf *); void m_extadd(struct mbuf *, caddr_t, u_int, void (*)(void *, void *), void *, int, int); void m_copyback(struct mbuf *, int, int, c_caddr_t); void m_copydata(const struct mbuf *, int, int, caddr_t); struct mbuf *m_copym(struct mbuf *, int, int, int); struct mbuf *m_copymdata(struct mbuf *, struct mbuf *, int, int, int, int); struct mbuf *m_copypacket(struct mbuf *, int); void m_copy_pkthdr(struct mbuf *, struct mbuf *); struct mbuf *m_copyup(struct mbuf *n, int len, int dstoff); struct mbuf *m_defrag(struct mbuf *, int); void m_demote(struct mbuf *, int); struct mbuf *m_devget(char *, int, int, struct ifnet *, void (*)(char *, caddr_t, u_int)); struct mbuf *m_dup(struct mbuf *, int); int m_dup_pkthdr(struct mbuf *, struct mbuf *, int); u_int m_fixhdr(struct mbuf *); struct mbuf *m_fragment(struct mbuf *, int, int); void m_freem(struct mbuf *); struct mbuf *m_getm(struct mbuf *, int, int, short); struct mbuf *m_getptr(struct mbuf *, int, int *); u_int m_length(struct mbuf *, struct mbuf **); void m_move_pkthdr(struct mbuf *, struct mbuf *); struct mbuf *m_prepend(struct mbuf *, int, int); void m_print(const struct mbuf *, int); struct mbuf *m_pulldown(struct mbuf *, int, int, int *); struct mbuf *m_pullup(struct mbuf *, int); int m_sanity(struct mbuf *, int); struct mbuf *m_split(struct mbuf *, int, int); struct mbuf *m_uiotombuf(struct uio *, int, int, int); struct mbuf *m_unshare(struct mbuf *, int how); /*- * Network packets may have annotations attached by affixing a list * of "packet tags" to the pkthdr structure. Packet tags are * dynamically allocated semi-opaque data structures that have * a fixed header (struct m_tag) that specifies the size of the * memory block and a pair that identifies it. * The cookie is a 32-bit unique unsigned value used to identify * a module or ABI. By convention this value is chose as the * date+time that the module is created, expressed as the number of * seconds since the epoch (e.g., using date -u +'%s'). The type value * is an ABI/module-specific value that identifies a particular annotation * and is private to the module. For compatibility with systems * like OpenBSD that define packet tags w/o an ABI/module cookie, * the value PACKET_ABI_COMPAT is used to implement m_tag_get and * m_tag_find compatibility shim functions and several tag types are * defined below. Users that do not require compatibility should use * a private cookie value so that packet tag-related definitions * can be maintained privately. * * Note that the packet tag returned by m_tag_alloc has the default * memory alignment implemented by malloc. To reference private data * one can use a construct like: * * struct m_tag *mtag = m_tag_alloc(...); * struct foo *p = (struct foo *)(mtag+1); * * if the alignment of struct m_tag is sufficient for referencing members * of struct foo. Otherwise it is necessary to embed struct m_tag within * the private data structure to insure proper alignment; e.g., * * struct foo { * struct m_tag tag; * ... * }; * struct foo *p = (struct foo *) m_tag_alloc(...); * struct m_tag *mtag = &p->tag; */ /* * Persistent tags stay with an mbuf until the mbuf is reclaimed. * Otherwise tags are expected to ``vanish'' when they pass through * a network interface. For most interfaces this happens normally * as the tags are reclaimed when the mbuf is free'd. However in * some special cases reclaiming must be done manually. An example * is packets that pass through the loopback interface. Also, one * must be careful to do this when ``turning around'' packets (e.g., * icmp_reflect). * * To mark a tag persistent bit-or this flag in when defining the * tag id. The tag will then be treated as described above. */ #define MTAG_PERSISTENT 0x800 #define PACKET_TAG_NONE 0 /* Nadda */ /* Packet tags for use with PACKET_ABI_COMPAT. */ #define PACKET_TAG_IPSEC_IN_DONE 1 /* IPsec applied, in */ #define PACKET_TAG_IPSEC_OUT_DONE 2 /* IPsec applied, out */ #define PACKET_TAG_IPSEC_IN_CRYPTO_DONE 3 /* NIC IPsec crypto done */ #define PACKET_TAG_IPSEC_OUT_CRYPTO_NEEDED 4 /* NIC IPsec crypto req'ed */ #define PACKET_TAG_IPSEC_IN_COULD_DO_CRYPTO 5 /* NIC notifies IPsec */ #define PACKET_TAG_IPSEC_PENDING_TDB 6 /* Reminder to do IPsec */ #define PACKET_TAG_BRIDGE 7 /* Bridge processing done */ #define PACKET_TAG_GIF 8 /* GIF processing done */ #define PACKET_TAG_GRE 9 /* GRE processing done */ #define PACKET_TAG_IN_PACKET_CHECKSUM 10 /* NIC checksumming done */ #define PACKET_TAG_ENCAP 11 /* Encap. processing */ #define PACKET_TAG_IPSEC_SOCKET 12 /* IPSEC socket ref */ #define PACKET_TAG_IPSEC_HISTORY 13 /* IPSEC history */ #define PACKET_TAG_IPV6_INPUT 14 /* IPV6 input processing */ #define PACKET_TAG_DUMMYNET 15 /* dummynet info */ #define PACKET_TAG_DIVERT 17 /* divert info */ #define PACKET_TAG_IPFORWARD 18 /* ipforward info */ #define PACKET_TAG_MACLABEL (19 | MTAG_PERSISTENT) /* MAC label */ #define PACKET_TAG_PF_ROUTED 21 /* PF routed, avoid loops */ #define PACKET_TAG_PF_FRAGCACHE 22 /* PF fragment cached */ #define PACKET_TAG_PF_QID 23 /* PF ALTQ queue id */ #define PACKET_TAG_PF_TAG 24 /* PF tagged */ #define PACKET_TAG_RTSOCKFAM 25 /* rtsock sa family */ #define PACKET_TAG_PF_TRANSLATE_LOCALHOST 26 /* PF translate localhost */ #define PACKET_TAG_IPOPTIONS 27 /* Saved IP options */ #define PACKET_TAG_CARP 28 /* CARP info */ /* Specific cookies and tags. */ -#define MTAG_VLAN 1035328035 -#define MTAG_VLAN_TAG 0 /* tag of VLAN interface */ /* Packet tag routines. */ struct m_tag *m_tag_alloc(u_int32_t, int, int, int); void m_tag_delete(struct mbuf *, struct m_tag *); void m_tag_delete_chain(struct mbuf *, struct m_tag *); void m_tag_free_default(struct m_tag *); struct m_tag *m_tag_locate(struct mbuf *, u_int32_t, int, struct m_tag *); struct m_tag *m_tag_copy(struct m_tag *, int); int m_tag_copy_chain(struct mbuf *, struct mbuf *, int); void m_tag_delete_nonpersistent(struct mbuf *); /* * Initialize the list of tags associated with an mbuf. */ static __inline void m_tag_init(struct mbuf *m) { SLIST_INIT(&m->m_pkthdr.tags); } /* * Set up the contents of a tag. Note that this does not * fill in the free method; the caller is expected to do that. * * XXX probably should be called m_tag_init, but that was * already taken. */ static __inline void m_tag_setup(struct m_tag *t, u_int32_t cookie, int type, int len) { t->m_tag_id = type; t->m_tag_len = len; t->m_tag_cookie = cookie; } /* * Reclaim resources associated with a tag. */ static __inline void m_tag_free(struct m_tag *t) { (*t->m_tag_free)(t); } /* * Return the first tag associated with an mbuf. */ static __inline struct m_tag * m_tag_first(struct mbuf *m) { return (SLIST_FIRST(&m->m_pkthdr.tags)); } /* * Return the next tag in the list of tags associated with an mbuf. */ static __inline struct m_tag * m_tag_next(struct mbuf *m, struct m_tag *t) { return (SLIST_NEXT(t, m_tag_link)); } /* * Prepend a tag to the list of tags associated with an mbuf. */ static __inline void m_tag_prepend(struct mbuf *m, struct m_tag *t) { SLIST_INSERT_HEAD(&m->m_pkthdr.tags, t, m_tag_link); } /* * Unlink a tag from the list of tags associated with an mbuf. */ static __inline void m_tag_unlink(struct mbuf *m, struct m_tag *t) { SLIST_REMOVE(&m->m_pkthdr.tags, t, m_tag, m_tag_link); } /* These are for OpenBSD compatibility. */ #define MTAG_ABI_COMPAT 0 /* compatibility ABI */ static __inline struct m_tag * m_tag_get(int type, int length, int wait) { return (m_tag_alloc(MTAG_ABI_COMPAT, type, length, wait)); } static __inline struct m_tag * m_tag_find(struct mbuf *m, int type, struct m_tag *start) { return (SLIST_EMPTY(&m->m_pkthdr.tags) ? (struct m_tag *)NULL : m_tag_locate(m, MTAG_ABI_COMPAT, type, start)); } #endif /* _KERNEL */ #endif /* !_SYS_MBUF_H_ */