Date: Wed, 14 Apr 2010 20:45:33 +0000 (UTC) From: Pyun YongHyeon <yongari@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r206625 - in head/sys: amd64/conf boot/forth conf dev/sge i386/conf modules modules/sge Message-ID: <201004142045.o3EKjXs4082300@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: yongari Date: Wed Apr 14 20:45:33 2010 New Revision: 206625 URL: http://svn.freebsd.org/changeset/base/206625 Log: Add driver for Silicon Integrated Systems SiS190/191 Fast/Gigabit Ethernet. This driver was written by Alexander Pohoyda and greatly enhanced by Nikolay Denev. I don't have these hardwares but this driver was tested by Nikolay Denev and xclin. Because SiS didn't release data sheet for this controller, programming information came from Linux driver and OpenSolaris. Unlike other open source driver for SiS190/191, sge(4) takes full advantage of TX/RX checksum offloading and does not require additional copy operation in RX handler. The controller seems to have advanced offloading features like VLAN hardware tag insertion/stripping, TCP segmentation offload(TSO) as well as jumbo frame support but these features are not available yet. Special thanks to xclin <xclin<> cs dot nctu dot edu dot tw> who sent fix for receiving VLAN oversized frames. Added: head/sys/dev/sge/ head/sys/dev/sge/if_sge.c (contents, props changed) head/sys/dev/sge/if_sgereg.h (contents, props changed) head/sys/modules/sge/ head/sys/modules/sge/Makefile (contents, props changed) Modified: head/sys/amd64/conf/GENERIC head/sys/boot/forth/loader.conf head/sys/conf/NOTES head/sys/conf/files head/sys/i386/conf/GENERIC head/sys/modules/Makefile Modified: head/sys/amd64/conf/GENERIC ============================================================================== --- head/sys/amd64/conf/GENERIC Wed Apr 14 20:31:06 2010 (r206624) +++ head/sys/amd64/conf/GENERIC Wed Apr 14 20:45:33 2010 (r206625) @@ -224,6 +224,7 @@ device pcn # AMD Am79C97x PCI 10/100 ( device re # RealTek 8139C+/8169/8169S/8110S device rl # RealTek 8129/8139 device sf # Adaptec AIC-6915 (``Starfire'') +device sge # Silicon Integrated Systems SiS190/191 device sis # Silicon Integrated Systems SiS 900/SiS 7016 device sk # SysKonnect SK-984x & SK-982x gigabit Ethernet device ste # Sundance ST201 (D-Link DFE-550TX) Modified: head/sys/boot/forth/loader.conf ============================================================================== --- head/sys/boot/forth/loader.conf Wed Apr 14 20:31:06 2010 (r206624) +++ head/sys/boot/forth/loader.conf Wed Apr 14 20:45:33 2010 (r206625) @@ -271,6 +271,7 @@ if_rl_load="NO" # RealTek 8129/8139 if_rue_load="NO" # RealTek RTL8150 USB to Fast Ethernet if_sbni_load="NO" # Granch SBNI12 leased line adapters if_sf_load="NO" # Adaptec Duralink PCI (AIC-6915 "starfire") +if_sge_load="NO" # Silicon Integrated Systems SiS190/191 if_sis_load="NO" # Silicon Integrated Systems SiS 900/7016 if_sk_load="NO" # SysKonnect SK-984x series PCI Gigabit Ethernet if_sn_load="NO" # SMC 91Cxx Modified: head/sys/conf/NOTES ============================================================================== --- head/sys/conf/NOTES Wed Apr 14 20:31:06 2010 (r206624) +++ head/sys/conf/NOTES Wed Apr 14 20:45:33 2010 (r206625) @@ -1952,6 +1952,7 @@ device xmphy # XaQti XMAC II # This includes dual and quad port cards, as well as one 100baseFX card. # Most of these are 64-bit PCI devices, except for one single port # card which is 32-bit. +# sge: Silicon Integrated Systems SiS190/191 Fast/Gigabit Ethernet adapter # sis: Support for NICs based on the Silicon Integrated Systems SiS 900, # SiS 7016 and NS DP83815 PCI fast ethernet controller chips. # sk: Support for the SysKonnect SK-984x series PCI gigabit ethernet NICs. @@ -2046,6 +2047,7 @@ device re # RealTek 8139C+/8169/8169S/ device rl # RealTek 8129/8139 device pcn # AMD Am79C97x PCI 10/100 NICs device sf # Adaptec AIC-6915 (``Starfire'') +device sge # Silicon Integrated Systems SiS190/191 device sis # Silicon Integrated Systems SiS 900/SiS 7016 device sk # SysKonnect SK-984x & SK-982x gigabit Ethernet device ste # Sundance ST201 (D-Link DFE-550TX) Modified: head/sys/conf/files ============================================================================== --- head/sys/conf/files Wed Apr 14 20:31:06 2010 (r206624) +++ head/sys/conf/files Wed Apr 14 20:45:33 2010 (r206625) @@ -1490,6 +1490,7 @@ dev/scd/scd.c optional scd isa dev/scd/scd_isa.c optional scd isa dev/sdhci/sdhci.c optional sdhci pci dev/sf/if_sf.c optional sf pci +dev/sge/if_sge.c optional sge pci dev/si/si.c optional si dev/si/si2_z280.c optional si dev/si/si3_t225.c optional si Added: head/sys/dev/sge/if_sge.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/dev/sge/if_sge.c Wed Apr 14 20:45:33 2010 (r206625) @@ -0,0 +1,1745 @@ +/*- + * Copyright (c) 2008-2010 Nikolay Denev <ndenev@gmail.com> + * Copyright (c) 2007-2008 Alexander Pohoyda <alexander.pohoyda@gmx.net> + * Copyright (c) 1997, 1998, 1999 + * Bill Paul <wpaul@ctr.columbia.edu>. 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 AUTHORS OR + * THE VOICES IN THEIR HEADS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * SiS 190/191 PCI Ethernet NIC driver. + * + * Adapted to SiS 190 NIC by Alexander Pohoyda based on the original + * SiS 900 driver by Bill Paul, using SiS 190/191 Solaris driver by + * Masayuki Murayama and SiS 190/191 GNU/Linux driver by K.M. Liu + * <kmliu@sis.com>. Thanks to Pyun YongHyeon <pyunyh@gmail.com> for + * review and very useful comments. + * + * Adapted to SiS 191 NIC by Nikolay Denev with further ideas from the + * Linux and Solaris drivers. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/endian.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/rman.h> +#include <sys/socket.h> +#include <sys/sockio.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> +#include <net/if_vlan_var.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <dev/mii/mii.h> +#include <dev/mii/miivar.h> + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> + +#include "if_sgereg.h" + +MODULE_DEPEND(sge, pci, 1, 1, 1); +MODULE_DEPEND(sge, ether, 1, 1, 1); +MODULE_DEPEND(sge, miibus, 1, 1, 1); + +/* "device miibus0" required. See GENERIC if you get errors here. */ +#include "miibus_if.h" + +/* + * Various supported device vendors/types and their names. + */ +static struct sge_type sge_devs[] = { + { SIS_VENDORID, SIS_DEVICEID_190, "SiS190 Fast Ethernet" }, + { SIS_VENDORID, SIS_DEVICEID_191, "SiS191 Fast/Gigabit Ethernet" }, + { 0, 0, NULL } +}; + +static int sge_probe(device_t); +static int sge_attach(device_t); +static int sge_detach(device_t); +static int sge_shutdown(device_t); +static int sge_suspend(device_t); +static int sge_resume(device_t); + +static int sge_miibus_readreg(device_t, int, int); +static int sge_miibus_writereg(device_t, int, int, int); +static void sge_miibus_statchg(device_t); + +static int sge_newbuf(struct sge_softc *, int); +static int sge_encap(struct sge_softc *, struct mbuf **); +#ifndef __NO_STRICT_ALIGNMENT +static __inline void + sge_fixup_rx(struct mbuf *); +#endif +static __inline void + sge_discard_rxbuf(struct sge_softc *, int); +static void sge_rxeof(struct sge_softc *); +static void sge_txeof(struct sge_softc *); +static void sge_intr(void *); +static void sge_tick(void *); +static void sge_start(struct ifnet *); +static void sge_start_locked(struct ifnet *); +static int sge_ioctl(struct ifnet *, u_long, caddr_t); +static void sge_init(void *); +static void sge_init_locked(struct sge_softc *); +static void sge_stop(struct sge_softc *); +static void sge_watchdog(struct sge_softc *); +static int sge_ifmedia_upd(struct ifnet *); +static void sge_ifmedia_sts(struct ifnet *, struct ifmediareq *); + +static int sge_get_mac_addr_apc(struct sge_softc *, uint8_t *); +static int sge_get_mac_addr_eeprom(struct sge_softc *, uint8_t *); +static uint16_t sge_read_eeprom(struct sge_softc *, int); + +static void sge_rxfilter(struct sge_softc *); +static void sge_reset(struct sge_softc *); +static int sge_list_rx_init(struct sge_softc *); +static int sge_list_rx_free(struct sge_softc *); +static int sge_list_tx_init(struct sge_softc *); +static int sge_list_tx_free(struct sge_softc *); + +static int sge_dma_alloc(struct sge_softc *); +static void sge_dma_free(struct sge_softc *); +static void sge_dma_map_addr(void *, bus_dma_segment_t *, int, int); + +static device_method_t sge_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, sge_probe), + DEVMETHOD(device_attach, sge_attach), + DEVMETHOD(device_detach, sge_detach), + DEVMETHOD(device_suspend, sge_suspend), + DEVMETHOD(device_resume, sge_resume), + DEVMETHOD(device_shutdown, sge_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, sge_miibus_readreg), + DEVMETHOD(miibus_writereg, sge_miibus_writereg), + DEVMETHOD(miibus_statchg, sge_miibus_statchg), + + KOBJMETHOD_END +}; + +static driver_t sge_driver = { + "sge", sge_methods, sizeof(struct sge_softc) +}; + +static devclass_t sge_devclass; + +DRIVER_MODULE(sge, pci, sge_driver, sge_devclass, 0, 0); +DRIVER_MODULE(miibus, sge, miibus_driver, miibus_devclass, 0, 0); + +/* + * Register space access macros. + */ +#define CSR_WRITE_4(sc, reg, val) bus_write_4(sc->sge_res, reg, val) +#define CSR_WRITE_2(sc, reg, val) bus_write_2(sc->sge_res, reg, val) +#define CSR_WRITE_1(cs, reg, val) bus_write_1(sc->sge_res, reg, val) + +#define CSR_READ_4(sc, reg) bus_read_4(sc->sge_res, reg) +#define CSR_READ_2(sc, reg) bus_read_2(sc->sge_res, reg) +#define CSR_READ_1(sc, reg) bus_read_1(sc->sge_res, reg) + +/* Define to show Tx/Rx error status. */ +#undef SGE_SHOW_ERRORS + +#define SGE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) + +static void +sge_dma_map_addr(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + bus_addr_t *p; + + if (error != 0) + return; + KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg)); + p = arg; + *p = segs->ds_addr; +} + +/* + * Read a sequence of words from the EEPROM. + */ +static uint16_t +sge_read_eeprom(struct sge_softc *sc, int offset) +{ + uint32_t val; + int i; + + KASSERT(offset <= EI_OFFSET, ("EEPROM offset too big")); + CSR_WRITE_4(sc, ROMInterface, + EI_REQ | EI_OP_RD | (offset << EI_OFFSET_SHIFT)); + DELAY(500); + for (i = 0; i < SGE_TIMEOUT; i++) { + val = CSR_READ_4(sc, ROMInterface); + if ((val & EI_REQ) == 0) + break; + DELAY(100); + } + if (i == SGE_TIMEOUT) { + device_printf(sc->sge_dev, + "EEPROM read timeout : 0x%08x\n", val); + return (0xffff); + } + + return ((val & EI_DATA) >> EI_DATA_SHIFT); +} + +static int +sge_get_mac_addr_eeprom(struct sge_softc *sc, uint8_t *dest) +{ + uint16_t val; + int i; + + val = sge_read_eeprom(sc, EEPROMSignature); + if (val == 0xffff || val == 0) { + device_printf(sc->sge_dev, + "invalid EEPROM signature : 0x%04x\n", val); + return (EINVAL); + } + + for (i = 0; i < ETHER_ADDR_LEN; i += 2) { + val = sge_read_eeprom(sc, EEPROMMACAddr + i / 2); + dest[i + 0] = (uint8_t)val; + dest[i + 1] = (uint8_t)(val >> 8); + } + + if ((sge_read_eeprom(sc, EEPROMInfo) & 0x80) != 0) + sc->sge_flags |= SGE_FLAG_RGMII; + return (0); +} + +/* + * For SiS96x, APC CMOS RAM is used to store ethernet address. + * APC CMOS RAM is accessed through ISA bridge. + */ +static int +sge_get_mac_addr_apc(struct sge_softc *sc, uint8_t *dest) +{ +#if defined(__amd64__) || defined(__i386__) + devclass_t pci; + device_t bus, dev = NULL; + device_t *kids; + struct apc_tbl { + uint16_t vid; + uint16_t did; + } *tp, apc_tbls[] = { + { SIS_VENDORID, 0x0965 }, + { SIS_VENDORID, 0x0966 }, + { SIS_VENDORID, 0x0968 } + }; + uint8_t reg; + int busnum, cnt, i, j, numkids; + + cnt = sizeof(apc_tbls) / sizeof(apc_tbls[0]); + pci = devclass_find("pci"); + for (busnum = 0; busnum < devclass_get_maxunit(pci); busnum++) { + bus = devclass_get_device(pci, busnum); + if (!bus) + continue; + if (device_get_children(bus, &kids, &numkids) != 0) + continue; + for (i = 0; i < numkids; i++) { + dev = kids[i]; + if (pci_get_class(dev) == PCIC_BRIDGE && + pci_get_subclass(dev) == PCIS_BRIDGE_ISA) { + tp = apc_tbls; + for (j = 0; j < cnt; j++) { + if (pci_get_vendor(dev) == tp->vid && + pci_get_device(dev) == tp->did) { + free(kids, M_TEMP); + goto apc_found; + } + tp++; + } + } + } + free(kids, M_TEMP); + } + device_printf(sc->sge_dev, "couldn't find PCI-ISA bridge\n"); + return (EINVAL); +apc_found: + /* Enable port 0x78 and 0x79 to access APC registers. */ + reg = pci_read_config(dev, 0x48, 1); + pci_write_config(dev, 0x48, reg & ~0x02, 1); + DELAY(50); + pci_read_config(dev, 0x48, 1); + /* Read stored ethernet address. */ + for (i = 0; i < ETHER_ADDR_LEN; i++) { + outb(0x78, 0x09 + i); + dest[i] = inb(0x79); + } + outb(0x78, 0x12); + if ((inb(0x79) & 0x80) != 0) + sc->sge_flags |= SGE_FLAG_RGMII; + /* Restore access to APC registers. */ + pci_write_config(dev, 0x48, reg, 1); + + return (0); +#else + return (EINVAL); +#endif +} + +static int +sge_miibus_readreg(device_t dev, int phy, int reg) +{ + struct sge_softc *sc; + uint32_t val; + int i; + + sc = device_get_softc(dev); + CSR_WRITE_4(sc, GMIIControl, (phy << GMI_PHY_SHIFT) | + (reg << GMI_REG_SHIFT) | GMI_OP_RD | GMI_REQ); + DELAY(10); + for (i = 0; i < SGE_TIMEOUT; i++) { + val = CSR_READ_4(sc, GMIIControl); + if ((val & GMI_REQ) == 0) + break; + DELAY(10); + } + if (i == SGE_TIMEOUT) { + device_printf(sc->sge_dev, "PHY read timeout : %d\n", reg); + return (0); + } + return ((val & GMI_DATA) >> GMI_DATA_SHIFT); +} + +static int +sge_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct sge_softc *sc; + uint32_t val; + int i; + + sc = device_get_softc(dev); + CSR_WRITE_4(sc, GMIIControl, (phy << GMI_PHY_SHIFT) | + (reg << GMI_REG_SHIFT) | (data << GMI_DATA_SHIFT) | + GMI_OP_WR | GMI_REQ); + DELAY(10); + for (i = 0; i < SGE_TIMEOUT; i++) { + val = CSR_READ_4(sc, GMIIControl); + if ((val & GMI_REQ) == 0) + break; + DELAY(10); + } + if (i == SGE_TIMEOUT) + device_printf(sc->sge_dev, "PHY write timeout : %d\n", reg); + return (0); +} + +static void +sge_miibus_statchg(device_t dev) +{ + struct sge_softc *sc; + struct mii_data *mii; + struct ifnet *ifp; + uint32_t ctl, speed; + + sc = device_get_softc(dev); + mii = device_get_softc(sc->sge_miibus); + ifp = sc->sge_ifp; + if (mii == NULL || ifp == NULL || + (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + speed = 0; + sc->sge_flags &= ~SGE_FLAG_LINK; + if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == + (IFM_ACTIVE | IFM_AVALID)) { + switch (IFM_SUBTYPE(mii->mii_media_active)) { + case IFM_10_T: + sc->sge_flags |= SGE_FLAG_LINK; + speed = SC_SPEED_10; + break; + case IFM_100_TX: + sc->sge_flags |= SGE_FLAG_LINK; + speed = SC_SPEED_100; + break; + case IFM_1000_T: + if ((sc->sge_flags & SGE_FLAG_FASTETHER) == 0) { + sc->sge_flags |= SGE_FLAG_LINK; + speed = SC_SPEED_1000; + } + break; + default: + break; + } + } + if ((sc->sge_flags & SGE_FLAG_LINK) == 0) + return; + /* Reprogram MAC to resolved speed/duplex/flow-control parameters. */ + ctl = CSR_READ_4(sc, StationControl); + ctl &= ~(0x0f000000 | SC_FDX | SC_SPEED_MASK); + if (speed == SC_SPEED_1000) { + ctl |= 0x07000000; + sc->sge_flags |= SGE_FLAG_SPEED_1000; + } else { + ctl |= 0x04000000; + sc->sge_flags &= ~SGE_FLAG_SPEED_1000; + } +#ifdef notyet + if ((sc->sge_flags & SGE_FLAG_GMII) != 0) + ctl |= 0x03000000; +#endif + ctl |= speed; + if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { + ctl |= SC_FDX; + sc->sge_flags |= SGE_FLAG_FDX; + } else + sc->sge_flags &= ~SGE_FLAG_FDX; + CSR_WRITE_4(sc, StationControl, ctl); + if ((sc->sge_flags & SGE_FLAG_RGMII) != 0) { + CSR_WRITE_4(sc, RGMIIDelay, 0x0441); + CSR_WRITE_4(sc, RGMIIDelay, 0x0440); + } +} + +static void +sge_rxfilter(struct sge_softc *sc) +{ + struct ifnet *ifp; + struct ifmultiaddr *ifma; + uint32_t crc, hashes[2]; + uint16_t rxfilt; + + SGE_LOCK_ASSERT(sc); + + ifp = sc->sge_ifp; + hashes[0] = hashes[1] = 0; + rxfilt = AcceptMyPhys; + if ((ifp->if_flags & IFF_BROADCAST) != 0) + rxfilt |= AcceptBroadcast; + if ((ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI)) != 0) { + if ((ifp->if_flags & IFF_PROMISC) != 0) + rxfilt |= AcceptAllPhys; + rxfilt |= AcceptMulticast; + hashes[0] = 0xFFFFFFFF; + hashes[1] = 0xFFFFFFFF; + goto done; + } + rxfilt |= AcceptMulticast; + /* Now program new ones. */ + if_maddr_rlock(ifp); + TAILQ_FOREACH(ifma, &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); + hashes[crc >> 31] |= 1 << ((crc >> 26) & 0x1f); + } + if_maddr_runlock(ifp); +done: + CSR_WRITE_2(sc, RxMacControl, rxfilt | 0x02); + CSR_WRITE_4(sc, RxHashTable, hashes[0]); + CSR_WRITE_4(sc, RxHashTable2, hashes[1]); +} + +static void +sge_reset(struct sge_softc *sc) +{ + + CSR_WRITE_4(sc, IntrMask, 0); + CSR_WRITE_4(sc, IntrStatus, 0xffffffff); + + /* Soft reset. */ + CSR_WRITE_4(sc, IntrControl, 0x8000); + CSR_READ_4(sc, IntrControl); + DELAY(100); + CSR_WRITE_4(sc, IntrControl, 0); + /* Stop MAC. */ + CSR_WRITE_4(sc, TX_CTL, 0x1a00); + CSR_WRITE_4(sc, RX_CTL, 0x1a00); + + CSR_WRITE_4(sc, IntrMask, 0); + CSR_WRITE_4(sc, IntrStatus, 0xffffffff); + + CSR_WRITE_4(sc, GMIIControl, 0); +} + +/* + * Probe for an SiS chip. Check the PCI vendor and device + * IDs against our list and return a device name if we find a match. + */ +static int +sge_probe(device_t dev) +{ + struct sge_type *t; + + t = sge_devs; + while (t->sge_name != NULL) { + if ((pci_get_vendor(dev) == t->sge_vid) && + (pci_get_device(dev) == t->sge_did)) { + device_set_desc(dev, t->sge_name); + return (BUS_PROBE_DEFAULT); + } + t++; + } + + return (ENXIO); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +sge_attach(device_t dev) +{ + struct sge_softc *sc; + struct ifnet *ifp; + uint8_t eaddr[ETHER_ADDR_LEN]; + int error = 0, rid; + + sc = device_get_softc(dev); + sc->sge_dev = dev; + + mtx_init(&sc->sge_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, + MTX_DEF); + callout_init_mtx(&sc->sge_stat_ch, &sc->sge_mtx, 0); + + /* + * Map control/status registers. + */ + pci_enable_busmaster(dev); + + /* Allocate resources. */ + sc->sge_res_id = PCIR_BAR(0); + sc->sge_res_type = SYS_RES_MEMORY; + sc->sge_res = bus_alloc_resource_any(dev, sc->sge_res_type, + &sc->sge_res_id, RF_ACTIVE); + if (sc->sge_res == NULL) { + device_printf(dev, "couldn't allocate resource\n"); + error = ENXIO; + goto fail; + } + + rid = 0; + sc->sge_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sge_irq == NULL) { + device_printf(dev, "couldn't allocate IRQ resources\n"); + error = ENXIO; + goto fail; + } + sc->sge_rev = pci_get_revid(dev); + if (pci_get_device(dev) == SIS_DEVICEID_190) + sc->sge_flags |= SGE_FLAG_FASTETHER; + /* Reset the adapter. */ + sge_reset(sc); + + /* Get MAC address from the EEPROM. */ + if ((pci_read_config(dev, 0x73, 1) & 0x01) != 0) + sge_get_mac_addr_apc(sc, eaddr); + else + sge_get_mac_addr_eeprom(sc, eaddr); + + if ((error = sge_dma_alloc(sc)) != 0) + goto fail; + + ifp = sc->sge_ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + device_printf(dev, "cannot allocate ifnet structure.\n"); + error = ENOSPC; + 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 = sge_ioctl; + ifp->if_start = sge_start; + ifp->if_init = sge_init; + ifp->if_snd.ifq_drv_maxlen = SGE_TX_RING_CNT - 1; + IFQ_SET_MAXLEN(&ifp->if_snd, ifp->if_snd.ifq_drv_maxlen); + IFQ_SET_READY(&ifp->if_snd); + ifp->if_capabilities = IFCAP_TXCSUM | IFCAP_RXCSUM; + ifp->if_hwassist = SGE_CSUM_FEATURES; + ifp->if_capenable = ifp->if_capabilities; + /* + * Do MII setup. + */ + if (mii_phy_probe(dev, &sc->sge_miibus, sge_ifmedia_upd, + sge_ifmedia_sts)) { + device_printf(dev, "no PHY found!\n"); + error = ENXIO; + goto fail; + } + + /* + * Call MI attach routine. + */ + ether_ifattach(ifp, eaddr); + + /* VLAN setup. */ + ifp->if_capabilities |= IFCAP_VLAN_MTU; + ifp->if_capenable = ifp->if_capabilities; + /* Tell the upper layer(s) we support long frames. */ + ifp->if_data.ifi_hdrlen = sizeof(struct ether_vlan_header); + + /* Hook interrupt last to avoid having to lock softc */ + error = bus_setup_intr(dev, sc->sge_irq, INTR_TYPE_NET | INTR_MPSAFE, + NULL, sge_intr, sc, &sc->sge_intrhand); + if (error) { + device_printf(dev, "couldn't set up irq\n"); + ether_ifdetach(ifp); + goto fail; + } + +fail: + if (error) + sge_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 +sge_detach(device_t dev) +{ + struct sge_softc *sc; + struct ifnet *ifp; + + sc = device_get_softc(dev); + ifp = sc->sge_ifp; + /* These should only be active if attach succeeded. */ + if (device_is_attached(dev)) { + ether_ifdetach(ifp); + SGE_LOCK(sc); + sge_stop(sc); + SGE_UNLOCK(sc); + callout_drain(&sc->sge_stat_ch); + } + if (sc->sge_miibus) + device_delete_child(dev, sc->sge_miibus); + bus_generic_detach(dev); + + if (sc->sge_intrhand) + bus_teardown_intr(dev, sc->sge_irq, sc->sge_intrhand); + if (sc->sge_irq) + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sge_irq); + if (sc->sge_res) + bus_release_resource(dev, sc->sge_res_type, sc->sge_res_id, + sc->sge_res); + if (ifp) + if_free(ifp); + sge_dma_free(sc); + mtx_destroy(&sc->sge_mtx); + + return (0); +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +sge_shutdown(device_t dev) +{ + struct sge_softc *sc; + + sc = device_get_softc(dev); + SGE_LOCK(sc); + sge_stop(sc); + SGE_UNLOCK(sc); + return (0); +} + +static int +sge_suspend(device_t dev) +{ + struct sge_softc *sc; + struct ifnet *ifp; + + sc = device_get_softc(dev); + SGE_LOCK(sc); + ifp = sc->sge_ifp; + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) + sge_stop(sc); + SGE_UNLOCK(sc); + return (0); +} + +static int +sge_resume(device_t dev) +{ + struct sge_softc *sc; + struct ifnet *ifp; + + sc = device_get_softc(dev); + SGE_LOCK(sc); + ifp = sc->sge_ifp; + if ((ifp->if_flags & IFF_UP) != 0) + sge_init_locked(sc); + SGE_UNLOCK(sc); + return (0); +} + +static int +sge_dma_alloc(struct sge_softc *sc) +{ + struct sge_chain_data *cd; + struct sge_list_data *ld; + int error, i; + + cd = &sc->sge_cdata; + ld = &sc->sge_ldata; + error = bus_dma_tag_create(bus_get_dma_tag(sc->sge_dev), + 1, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ + 1, /* nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + 0, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockarg */ + &cd->sge_tag); + if (error != 0) { + device_printf(sc->sge_dev, + "could not create parent DMA tag.\n"); + goto fail; + } + + /* RX descriptor ring */ + error = bus_dma_tag_create(cd->sge_tag, + SGE_DESC_ALIGN, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + SGE_RX_RING_SZ, 1, /* maxsize,nsegments */ + SGE_RX_RING_SZ, /* maxsegsize */ + 0, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockarg */ + &cd->sge_rx_tag); + if (error != 0) { + device_printf(sc->sge_dev, + "could not create Rx ring DMA tag.\n"); + goto fail; + } + /* Allocate DMA'able memory and load DMA map for RX ring. */ + error = bus_dmamem_alloc(cd->sge_rx_tag, (void **)&ld->sge_rx_ring, + BUS_DMA_NOWAIT | BUS_DMA_ZERO | BUS_DMA_COHERENT, + &cd->sge_rx_dmamap); + if (error != 0) { + device_printf(sc->sge_dev, + "could not allocate DMA'able memory for Rx ring.\n"); + goto fail; + } + error = bus_dmamap_load(cd->sge_rx_tag, cd->sge_rx_dmamap, + ld->sge_rx_ring, SGE_RX_RING_SZ, sge_dma_map_addr, + &ld->sge_rx_paddr, BUS_DMA_NOWAIT); + if (error != 0) { + device_printf(sc->sge_dev, + "could not load DMA'able memory for Rx ring.\n"); + } + + /* TX descriptor ring */ + error = bus_dma_tag_create(cd->sge_tag, + SGE_DESC_ALIGN, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + SGE_TX_RING_SZ, 1, /* maxsize,nsegments */ + SGE_TX_RING_SZ, /* maxsegsize */ + 0, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockarg */ + &cd->sge_tx_tag); + if (error != 0) { + device_printf(sc->sge_dev, + "could not create Rx ring DMA tag.\n"); + goto fail; + } + /* Allocate DMA'able memory and load DMA map for TX ring. */ + error = bus_dmamem_alloc(cd->sge_tx_tag, (void **)&ld->sge_tx_ring, + BUS_DMA_NOWAIT | BUS_DMA_ZERO | BUS_DMA_COHERENT, + &cd->sge_tx_dmamap); + if (error != 0) { + device_printf(sc->sge_dev, + "could not allocate DMA'able memory for Tx ring.\n"); + goto fail; + } + error = bus_dmamap_load(cd->sge_tx_tag, cd->sge_tx_dmamap, + ld->sge_tx_ring, SGE_TX_RING_SZ, sge_dma_map_addr, + &ld->sge_tx_paddr, BUS_DMA_NOWAIT); + if (error != 0) { + device_printf(sc->sge_dev, + "could not load DMA'able memory for Rx ring.\n"); + goto fail; + } + + /* Create DMA tag for Tx buffers. */ + error = bus_dma_tag_create(cd->sge_tag, 1, 0, BUS_SPACE_MAXADDR, + BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES * SGE_MAXTXSEGS, + SGE_MAXTXSEGS, MCLBYTES, 0, NULL, NULL, &cd->sge_txmbuf_tag); + if (error != 0) { + device_printf(sc->sge_dev, + "could not create Tx mbuf DMA tag.\n"); + goto fail; + } + + /* Create DMA tag for Rx buffers. */ + error = bus_dma_tag_create(cd->sge_tag, SGE_RX_BUF_ALIGN, 0, + BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1, + MCLBYTES, 0, NULL, NULL, &cd->sge_rxmbuf_tag); + if (error != 0) { + device_printf(sc->sge_dev, + "could not create Rx mbuf DMA tag.\n"); + goto fail; + } + + /* Create DMA maps for Tx buffers. */ + for (i = 0; i < SGE_TX_RING_CNT; i++) { + error = bus_dmamap_create(cd->sge_txmbuf_tag, 0, + &cd->sge_tx_map[i]); + if (error != 0) { + device_printf(sc->sge_dev, + "could not create Tx DMA map.\n"); + goto fail; + } + } + /* Create spare DMA map for Rx buffer. */ + error = bus_dmamap_create(cd->sge_rxmbuf_tag, 0, &cd->sge_rx_spare_map); + if (error != 0) { + device_printf(sc->sge_dev, + "could not create spare Rx DMA map.\n"); + goto fail; + } + /* Create DMA maps for Rx buffers. */ + for (i = 0; i < SGE_RX_RING_CNT; i++) { + error = bus_dmamap_create(cd->sge_rxmbuf_tag, 0, + &cd->sge_rx_map[i]); + if (error) { + device_printf(sc->sge_dev, + "could not create Rx DMA map.\n"); + goto fail; + } + } +fail: + return (error); +} + +static void +sge_dma_free(struct sge_softc *sc) +{ + struct sge_chain_data *cd; + struct sge_list_data *ld; + int i; + + cd = &sc->sge_cdata; + ld = &sc->sge_ldata; + /* Rx ring. */ + if (cd->sge_rx_tag != NULL) { + if (cd->sge_rx_dmamap != NULL) + bus_dmamap_unload(cd->sge_rx_tag, cd->sge_rx_dmamap); + if (cd->sge_rx_dmamap != NULL && ld->sge_rx_ring != NULL) + bus_dmamem_free(cd->sge_rx_tag, ld->sge_rx_ring, + cd->sge_rx_dmamap); + ld->sge_rx_ring = NULL; + cd->sge_rx_dmamap = NULL; + bus_dma_tag_destroy(cd->sge_rx_tag); + cd->sge_rx_tag = NULL; + } + /* Tx ring. */ + if (cd->sge_tx_tag != NULL) { + if (cd->sge_tx_dmamap != NULL) + bus_dmamap_unload(cd->sge_tx_tag, cd->sge_tx_dmamap); + if (cd->sge_tx_dmamap != NULL && ld->sge_tx_ring != NULL) + bus_dmamem_free(cd->sge_tx_tag, ld->sge_tx_ring, + cd->sge_tx_dmamap); + ld->sge_tx_ring = NULL; + cd->sge_tx_dmamap = NULL; + bus_dma_tag_destroy(cd->sge_tx_tag); + cd->sge_tx_tag = NULL; + } + /* Rx buffers. */ + if (cd->sge_rxmbuf_tag != NULL) { + for (i = 0; i < SGE_RX_RING_CNT; i++) { + if (cd->sge_rx_map[i] != NULL) { + bus_dmamap_destroy(cd->sge_rxmbuf_tag, + cd->sge_rx_map[i]); + cd->sge_rx_map[i] = NULL; + } + } + if (cd->sge_rx_spare_map != NULL) { + bus_dmamap_destroy(cd->sge_rxmbuf_tag, + cd->sge_rx_spare_map); + cd->sge_rx_spare_map = NULL; + } + bus_dma_tag_destroy(cd->sge_rxmbuf_tag); + cd->sge_rxmbuf_tag = NULL; + } + /* Tx buffers. */ + if (cd->sge_txmbuf_tag != NULL) { + for (i = 0; i < SGE_TX_RING_CNT; i++) { + if (cd->sge_tx_map[i] != NULL) { + bus_dmamap_destroy(cd->sge_txmbuf_tag, + cd->sge_tx_map[i]); + cd->sge_tx_map[i] = NULL; + } + } + bus_dma_tag_destroy(cd->sge_txmbuf_tag); + cd->sge_txmbuf_tag = NULL; + } + if (cd->sge_tag != NULL) + bus_dma_tag_destroy(cd->sge_tag); + cd->sge_tag = NULL; +} + +/* + * Initialize the TX descriptors. + */ +static int +sge_list_tx_init(struct sge_softc *sc) +{ *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201004142045.o3EKjXs4082300>