X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/blobdiff_plain/7cdc3d6e9b952ac5d4d25da9258133ca3b6267a9..78195a764d5e70464a6d4f49bc08332a2a8bb4d0:/sys/dev/netif/xe/if_xe.c diff --git a/sys/dev/netif/xe/if_xe.c b/sys/dev/netif/xe/if_xe.c index 53a38ed9fa..ae3c50e41f 100644 --- a/sys/dev/netif/xe/if_xe.c +++ b/sys/dev/netif/xe/if_xe.c @@ -1,5 +1,5 @@ /*- - * Copyright (c) 1998, 1999 Scott Mitchell + * Copyright (c) 1998, 1999, 2003 Scott Mitchell * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,17 +24,8 @@ * SUCH DAMAGE. * * $Id: if_xe.c,v 1.20 1999/06/13 19:17:40 scott Exp $ - * $FreeBSD: src/sys/dev/xe/if_xe.c,v 1.13.2.6 2003/02/05 22:03:57 mbr Exp $ - * $DragonFly: src/sys/dev/netif/xe/if_xe.c,v 1.26 2005/07/13 17:46:05 joerg Exp $ - */ - -/* - * XXX TODO XXX - * - * I've pushed this fairly far, but there are some things that need to be - * done here. I'm documenting them here in case I get destracted. -- imp - * - * xe_cem56fix -- need to figure out how to map the extra stuff. + * $FreeBSD: src/sys/dev/xe/if_xe.c,v 1.39 2003/10/14 22:51:35 rsm Exp $ + * $DragonFly: src/sys/dev/netif/xe/if_xe.c,v 1.30 2005/11/28 17:13:44 dillon Exp $ */ /* @@ -98,14 +89,8 @@ * the CEM56/REM56 support code; and the FreeBSD UK Users' Group for hosting * the web pages. * - * Contact points: - * - * Driver web page: http://ukug.uk.freebsd.org/~scott/xe_drv/ - * - * Mailing list: http://www.lovett.com/lists/freebsd-xircom/ - * or send "subscribe freebsd-xircom" to - * * Author email: + * Driver web page: http://ukug.uk.freebsd.org/~scott/xe_drv/ */ @@ -117,8 +102,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -161,6 +148,10 @@ struct xe_mii_frame { #define XE_AUTONEG_100TX 3 /* Trying to force 100baseTX link */ #define XE_AUTONEG_FAIL 4 /* Autonegotiation failed */ +/* + * Multicast hashing CRC constants + */ +#define XE_CRC_POLY 0x04c11db6 /* * Prototypes start here @@ -173,18 +164,15 @@ static void xe_watchdog (struct ifnet *ifp); static int xe_media_change (struct ifnet *ifp); static void xe_media_status (struct ifnet *ifp, struct ifmediareq *mrp); static timeout_t xe_setmedia; -static void xe_hard_reset (struct xe_softc *scp); -static void xe_soft_reset (struct xe_softc *scp); +static timeout_t xe_setmedia_serialized; +static void xe_reset (struct xe_softc *scp); static void xe_stop (struct xe_softc *scp); static void xe_enable_intr (struct xe_softc *scp); static void xe_disable_intr (struct xe_softc *scp); -static void xe_setmulti (struct xe_softc *scp); -static void xe_setaddrs (struct xe_softc *scp); +static void xe_set_multicast (struct xe_softc *scp); +static void xe_set_addr (struct xe_softc *scp, u_int8_t* addr, unsigned idx); +static void xe_set_hash (struct xe_softc *scp, u_int8_t* addr); static int xe_pio_write_packet (struct xe_softc *scp, struct mbuf *mbp); -#if 0 -static u_int32_t xe_compute_crc (u_int8_t *data, int len); -static int xe_compute_hashbit (u_int32_t crc); -#endif /* * MII functions @@ -197,20 +185,42 @@ static int xe_mii_writereg (struct xe_softc *scp, struct xe_mii_frame *fra static u_int16_t xe_phy_readreg (struct xe_softc *scp, u_int16_t reg); static void xe_phy_writereg (struct xe_softc *scp, u_int16_t reg, u_int16_t data); -/* - * Debug functions -- uncomment for VERY verbose dignostic information. - * Set to 1 for less verbose information - */ -/* #define XE_DEBUG 2 */ -#ifdef XE_DEBUG -#define XE_REG_DUMP(scp) xe_reg_dump((scp)) -#define XE_MII_DUMP(scp) xe_mii_dump((scp)) +/* Debugging functions */ static void xe_reg_dump (struct xe_softc *scp); static void xe_mii_dump (struct xe_softc *scp); -#else + +#define XE_DEBUG + +#ifdef XE_DEBUG + +/* sysctl vars */ +SYSCTL_NODE(_hw, OID_AUTO, xe, CTLFLAG_RD, 0, "xe parameters"); + +/* + * Debug logging levels - set with hw.xe.debug sysctl + * 0 = None + * 1 = More hardware details, probe/attach progress + * 2 = Most function calls, ioctls and media selection progress + * 3 = Everything - interrupts, packets in/out and multicast address setup + */ +int xe_debug = 1; +SYSCTL_INT(_hw_xe, OID_AUTO, debug, CTLFLAG_RW, &xe_debug, 0, "xe debug level"); + +#define DPRINTF(level, arg) if (xe_debug >= (level)) printf arg +#define IFPRINTF(level, arg) if (xe_debug >= (level)) if_printf arg +#define DEVPRINTF(level, arg) if (xe_debug >= (level)) device_printf arg +#define XE_MII_DUMP(scp) if (xe_debug >= 3) xe_mii_dump(scp) +#define XE_REG_DUMP(scp) if (xe_debug >= 3) xe_reg_dump(scp) + +#else /* !XE_DEBUG */ + +#define DPRINTF(level, arg) +#define IFPRINTF(level, arg) +#define DEVPRINTF(level, arg) #define XE_REG_DUMP(scp) #define XE_MII_DUMP(scp) -#endif + +#endif /* XE_DEBUG */ /* * The device entry is being removed, probably because someone ejected the @@ -245,18 +255,12 @@ xe_attach (device_t dev) struct xe_softc *scp = device_get_softc(dev); int err; -#ifdef XE_DEBUG - device_printf(dev, "attach\n"); -#endif + DEVPRINTF(2, (dev, "attach\n")); /* Fill in some private data */ scp->ifp = &scp->arpcom.ac_if; scp->ifm = &scp->ifmedia; - scp->autoneg_status = 0; - - /* Hopefully safe to read this here */ - XE_SELECT_PAGE(4); - scp->version = XE_INB(XE_BOV); + scp->autoneg_status = XE_AUTONEG_NONE; /* Initialise the ifnet structure */ scp->ifp->if_softc = scp; @@ -269,6 +273,7 @@ xe_attach (device_t dev) scp->ifp->if_ioctl = xe_ioctl; scp->ifp->if_watchdog = xe_watchdog; scp->ifp->if_init = xe_init; + scp->ifp->if_baudrate = 100000000; ifq_set_maxlen(&scp->ifp->if_snd, IFQ_MAXLEN); ifq_set_ready(&scp->ifp->if_snd); @@ -276,49 +281,60 @@ xe_attach (device_t dev) ifmedia_init(scp->ifm, 0, xe_media_change, xe_media_status); callout_init(&scp->xe_timer); - /* - * Fill in supported media types. Some cards _do_ support full duplex - * operation, but this driver doesn't, yet. Therefore we leave those modes - * out of the list. We support some form of autoselection in all cases. - */ + /* Add supported media types */ if (scp->mohawk) { ifmedia_add(scp->ifm, IFM_ETHER|IFM_100_TX, 0, NULL); - ifmedia_add(scp->ifm, IFM_ETHER|IFM_10_T, 0, NULL); + ifmedia_add(scp->ifm, IFM_ETHER|IFM_10_T|IFM_FDX, 0, NULL); + ifmedia_add(scp->ifm, IFM_ETHER|IFM_10_T|IFM_HDX, 0, NULL); } - else { - ifmedia_add(scp->ifm, IFM_ETHER|IFM_10_T, 0, NULL); + ifmedia_add(scp->ifm, IFM_ETHER|IFM_10_T, 0, NULL); + if (scp->ce2) ifmedia_add(scp->ifm, IFM_ETHER|IFM_10_2, 0, NULL); - } ifmedia_add(scp->ifm, IFM_ETHER|IFM_AUTO, 0, NULL); /* Default is to autoselect best supported media type */ ifmedia_set(scp->ifm, IFM_ETHER|IFM_AUTO); + /* Get the hardware into a known state */ + xe_reset(scp); + + /* Get hardware version numbers */ + XE_SELECT_PAGE(4); + scp->version = XE_INB(XE_BOV); + if (scp->mohawk) + scp->srev = (XE_INB(XE_BOV) & 0x70) >> 4; + else + scp->srev = (XE_INB(XE_BOV) & 0x30) >> 4; + /* Print some useful information */ - device_printf(dev, "%s %s, bonding version %#x%s%s\n", + device_printf(dev, "%s %s, version 0x%02x/0x%02x%s%s\n", scp->vendor, scp->card_type, scp->version, + scp->srev, scp->mohawk ? ", 100Mbps capable" : "", scp->modem ? ", with modem" : ""); + if (scp->mohawk) { XE_SELECT_PAGE(0x10); - device_printf(dev, "DingoID = %#x, RevisionID = %#x, VendorID = %#x\n", - XE_INW(XE_DINGOID), - XE_INW(XE_RevID), - XE_INW(XE_VendorID)); + DEVPRINTF(1, (dev, "DingoID=0x%04x, RevisionID=0x%04x, VendorID=0x%04x\n", + XE_INW(XE_DINGOID), + XE_INW(XE_RevID), + XE_INW(XE_VendorID))); } if (scp->ce2) { XE_SELECT_PAGE(0x45); - device_printf(dev, "CE2 version = %#x\n", XE_INB(XE_REV)); + DEVPRINTF(1, (dev, "CE2 version = 0x%#02x\n", XE_INB(XE_REV))); } /* Attach the interface */ - ether_ifattach(scp->ifp, scp->arpcom.ac_enaddr); + ether_ifattach(scp->ifp, scp->arpcom.ac_enaddr, NULL); - err = bus_setup_intr(dev, scp->irq_res, INTR_TYPE_NET, xe_intr, scp, - &scp->intrhand, NULL); + err = bus_setup_intr(dev, scp->irq_res, INTR_NETSAFE, + xe_intr, scp, &scp->intrhand, + scp->arpcom.ac_if.if_serializer); if (err) { + device_printf(dev, "Setup intr failed\n"); ether_ifdetach(&scp->arpcom.ac_if); xe_deactivate(dev); return err; @@ -330,97 +346,140 @@ xe_attach (device_t dev) /* - * Initialize device. Completes the reset procedure on the card and starts - * output. If there's an autonegotiation in progress we DON'T do anything; - * the media selection code will call us again when it's done. + * Complete hardware intitialisation and enable output. Exits without doing + * anything if there's no address assigned to the card, or if media selection + * is in progress (the latter implies we've already run this function). */ static void xe_init(void *xscp) { struct xe_softc *scp = xscp; + u_int i; -#ifdef XE_DEBUG - if_printf(scp->ifp, "init\n"); -#endif + if (scp->autoneg_status != XE_AUTONEG_NONE) return; + + IFPRINTF(2, (scp->ifp, "init\n")); + + crit_enter(); /* Reset transmitter flags */ scp->tx_queued = 0; scp->tx_tpr = 0; - scp->tx_collisions = 0; + scp->tx_timeouts = 0; + scp->tx_thres = 64; + scp->tx_min = ETHER_MIN_LEN - ETHER_CRC_LEN; scp->ifp->if_timer = 0; - crit_enter(); + /* Soft reset the card */ + XE_SELECT_PAGE(0); + XE_OUTB(XE_CR, XE_CR_SOFT_RESET); + DELAY(40000); + XE_OUTB(XE_CR, 0); + DELAY(40000); + + if (scp->mohawk) { + /* + * set GP1 and GP2 as outputs (bits 2 & 3) + * set GP1 low to power on the ML6692 (bit 0) + * set GP2 high to power on the 10Mhz chip (bit 1) + */ + XE_SELECT_PAGE(4); + XE_OUTB(XE_GPR0, XE_GPR0_GP2_SELECT|XE_GPR0_GP1_SELECT|XE_GPR0_GP2_OUT); + } + + /* Shut off interrupts */ + xe_disable_intr(scp); + + /* Wait for everything to wake up */ + DELAY(500000); + /* Check for PHY */ + if (scp->mohawk) + scp->phy_ok = xe_mii_init(scp); + + /* Disable 'source insertion' (not sure what that means) */ XE_SELECT_PAGE(0x42); - XE_OUTB(XE_SWC0, 0x20); /* Disable source insertion (WTF is that?) */ + XE_OUTB(XE_SWC0, XE_SWC0_NO_SRC_INSERT); - /* - * Set the 'local memory dividing line' -- splits the 32K card memory into - * 8K for transmit buffers and 24K for receive. This is done automatically - * on newer revision cards. - */ + /* Set 8K/24K Tx/Rx buffer split */ if (scp->srev != 1) { XE_SELECT_PAGE(2); XE_OUTW(XE_RBS, 0x2000); } + /* Enable early transmit mode on Mohawk/Dingo */ + if (scp->mohawk) { + XE_SELECT_PAGE(0x03); + XE_OUTW(XE_TPT, scp->tx_thres); + XE_SELECT_PAGE(0x01); + XE_OUTB(XE_ECR, XE_INB(XE_ECR) | XE_ECR_EARLY_TX); + } + + /* Put MAC address in first 'individual address' register */ + XE_SELECT_PAGE(0x50); + for (i = 0; i < 6; i++) + XE_OUTB(0x08 + i, scp->arpcom.ac_enaddr[scp->mohawk ? 5 - i : i]); + /* Set up multicast addresses */ - xe_setmulti(scp); + xe_set_multicast(scp); - /* Fix the data offset register -- reset leaves it off-by-one */ + /* Fix the receive data offset -- reset can leave it off-by-one */ XE_SELECT_PAGE(0); XE_OUTW(XE_DO, 0x2000); - /* - * Set MAC interrupt masks and clear status regs. The bit names are direct - * from the Linux code; I have no idea what most of them do. - */ - XE_SELECT_PAGE(0x40); /* Bit 7..0 */ - XE_OUTB(XE_RX0Msk, 0xff); /* ROK, RAB, rsv, RO, CRC, AE, PTL, MP */ - XE_OUTB(XE_TX0Msk, 0xff); /* TOK, TAB, SQE, LL, TU, JAB, EXC, CRS */ - XE_OUTB(XE_TX0Msk+1, 0xb0); /* rsv, rsv, PTD, EXT, rsv, rsv, rsv, rsv */ - XE_OUTB(XE_RST0, 0x00); /* ROK, RAB, REN, RO, CRC, AE, PTL, MP */ - XE_OUTB(XE_TXST0, 0x00); /* TOK, TAB, SQE, LL, TU, JAB, EXC, CRS */ - XE_OUTB(XE_TXST1, 0x00); /* TEN, rsv, PTD, EXT, retry_counter:4 */ - - /* - * Check for an in-progress autonegotiation. If one is active, just set - * IFF_RUNNING and return. The media selection code will call us again when - * it's done. - */ - if (scp->autoneg_status) { - scp->ifp->if_flags |= IFF_RUNNING; - } - else { - /* Enable receiver, put MAC online */ - XE_SELECT_PAGE(0x40); - XE_OUTB(XE_CMD0, XE_CMD0_RX_ENABLE|XE_CMD0_ONLINE); + /* Set interrupt masks */ + XE_SELECT_PAGE(1); + XE_OUTB(XE_IMR0, XE_IMR0_TX_PACKET | XE_IMR0_MAC_INTR | XE_IMR0_RX_PACKET); + + /* Set MAC interrupt masks */ + XE_SELECT_PAGE(0x40); + XE_OUTB(XE_RX0Msk, + ~(XE_RX0M_RX_OVERRUN | XE_RX0M_CRC_ERROR + | XE_RX0M_ALIGN_ERROR | XE_RX0M_LONG_PACKET)); + XE_OUTB(XE_TX0Msk, + ~(XE_TX0M_SQE_FAIL | XE_TX0M_LATE_COLLISION | XE_TX0M_TX_UNDERRUN + | XE_TX0M_16_COLLISIONS | XE_TX0M_NO_CARRIER)); + + /* Clear MAC status registers */ + XE_SELECT_PAGE(0x40); + XE_OUTB(XE_RST0, 0x00); + XE_OUTB(XE_TXST0, 0x00); + + /* Enable receiver and put MAC online */ + XE_SELECT_PAGE(0x40); + XE_OUTB(XE_CMD0, XE_CMD0_RX_ENABLE|XE_CMD0_ONLINE); + + /* Set up IMR, enable interrupts */ + xe_enable_intr(scp); - /* Set up IMR, enable interrupts */ - xe_enable_intr(scp); + /* Start media selection */ + xe_setmedia_serialized(scp); - /* Attempt to start output */ - scp->ifp->if_flags |= IFF_RUNNING; - scp->ifp->if_flags &= ~IFF_OACTIVE; - xe_start(scp->ifp); - } + /* Enable output */ + scp->ifp->if_flags |= IFF_RUNNING; + scp->ifp->if_flags &= ~IFF_OACTIVE; crit_exit(); } /* - * Start output on interface. We make two assumptions here: - * 1) that we are in a critical section _before_ this code - * is called *and* is returned to the appropriate priority after - * return - * 2) that the IFF_OACTIVE flag is checked before this code is called - * (i.e. that the output part of the interface is idle) + * Start output on interface. Should be called at splimp() priority. Check + * that the output is idle (ie, IFF_OACTIVE is not set) before calling this + * function. If media selection is in progress we set IFF_OACTIVE ourselves + * and return immediately. */ static void xe_start(struct ifnet *ifp) { struct xe_softc *scp = ifp->if_softc; struct mbuf *mbp; + if (scp->autoneg_status != XE_AUTONEG_NONE) { + ifp->if_flags |= IFF_OACTIVE; + return; + } + + IFPRINTF(3, (ifp, "start\n")); + /* * Loop while there are packets to be sent, and space to send them. */ @@ -444,7 +503,7 @@ xe_start(struct ifnet *ifp) { return; } - mbp = ifq_dequeue(&ifp->if_snd); + ifq_dequeue(&ifp->if_snd, mbp); BPF_MTAP(ifp, mbp); ifp->if_timer = 5; /* In case we don't hear from the card again */ @@ -470,15 +529,15 @@ xe_ioctl (struct ifnet *ifp, u_long command, caddr_t data, struct ucred *cr) { switch (command) { - case SIOCSIFFLAGS: + case SIOCSIFFLAGS: + IFPRINTF(2, (ifp, "ioctl: SIOCSIFFLAGS: 0x%04x\n", ifp->if_flags)); /* * If the interface is marked up and stopped, then start it. If it is * marked down and running, then stop it. */ if (ifp->if_flags & IFF_UP) { if (!(ifp->if_flags & IFF_RUNNING)) { - xe_hard_reset(scp); - xe_setmedia(scp); + xe_reset(scp); xe_init(scp); } } @@ -486,27 +545,30 @@ xe_ioctl (struct ifnet *ifp, u_long command, caddr_t data, struct ucred *cr) { if (ifp->if_flags & IFF_RUNNING) xe_stop(scp); } + /* FALL THROUGH (handle changes to PROMISC/ALLMULTI flags) */ - case SIOCADDMULTI: - case SIOCDELMULTI: + case SIOCADDMULTI: + case SIOCDELMULTI: + IFPRINTF(2, (ifp, "ioctl: SIOC{ADD,DEL}MULTI\n")); /* - * Multicast list has (maybe) changed; set the hardware filter - * accordingly. This also serves to deal with promiscuous mode if we have - * a BPF listener active. + * Multicast list has (maybe) changed; set the hardware filters + * accordingly. */ - xe_setmulti(scp); + xe_set_multicast(scp); error = 0; break; - case SIOCSIFMEDIA: - case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + case SIOCGIFMEDIA: + IFPRINTF(3, (ifp, "ioctl: bounce to ifmedia_ioctl\n")); /* * Someone wants to get/set media options. */ error = ifmedia_ioctl(ifp, (struct ifreq *)data, &scp->ifmedia, command); break; - default: + default: + IFPRINTF(3, (ifp, "ioctl: bounce to ether_ioctl\n")); error = ether_ioctl(ifp, command, data); break; } @@ -540,248 +602,259 @@ xe_intr(void *xscp) { struct xe_softc *scp = (struct xe_softc *) xscp; struct ifnet *ifp; - int result; - u_int16_t rx_bytes, rxs, txs; - u_int8_t psr, isr, esr, rsr; + u_int8_t psr, isr, esr, rsr, rst0, txst0, txst1, coll; ifp = &scp->arpcom.ac_if; - rx_bytes = 0; /* Bytes received on this interrupt */ - result = 0; /* Set true if the interrupt is for us */ - if (scp->mohawk) { - XE_OUTB(XE_CR, 0); /* Disable interrupts */ - } + /* Disable interrupts */ + if (scp->mohawk) + XE_OUTB(XE_CR, 0); - psr = XE_INB(XE_PR); /* Stash the current register page */ + /* Cache current register page */ + psr = XE_INB(XE_PR); - /* - * Read ISR to see what caused this interrupt. Note that this clears the - * ISR on CE2 type cards. - */ - if ((isr = XE_INB(XE_ISR)) && isr != 0xff) { + /* Read ISR to see what caused this interrupt */ + while ((isr = XE_INB(XE_ISR)) != 0) { + /* 0xff might mean the card is no longer around */ + if (isr == 0xff) { + IFPRINTF(3, (ifp, "intr: interrupt received for missing card?\n")); + break; + } - result = 1; /* This device did generate an int */ - esr = XE_INB(XE_ESR); /* Read the other status registers */ + /* Read other status registers */ XE_SELECT_PAGE(0x40); - rxs = XE_INB(XE_RST0); - XE_OUTB(XE_RST0, ~rxs & 0xff); - txs = XE_INB(XE_TXST0); - txs |= XE_INB(XE_TXST1) << 8; + rst0 = XE_INB(XE_RST0); + XE_OUTB(XE_RST0, 0); + txst0 = XE_INB(XE_TXST0); + txst1 = XE_INB(XE_TXST1); + coll = txst1 & XE_TXST1_RETRY_COUNT; XE_OUTB(XE_TXST0, 0); XE_OUTB(XE_TXST1, 0); XE_SELECT_PAGE(0); -#if XE_DEBUG > 2 - if_printf(ifp, "ISR=%#2.2x ESR=%#2.2x RST=%#2.2x TXST=%#4.4x\n", isr, esr, rxs, txs); -#endif + IFPRINTF(3, (ifp, + "intr: ISR=0x%02x, RST=0x%02x, TXT=0x%02x%02x, COLL=0x%01x\n", + isr, rst0, txst1, txst0, coll)); - /* - * Handle transmit interrupts - */ + /* Handle transmitted packet(s) */ if (isr & XE_ISR_TX_PACKET) { - u_int8_t new_tpr, sent; - - if ((new_tpr = XE_INB(XE_TPR)) < scp->tx_tpr) /* Update packet count */ - sent = (0xff - scp->tx_tpr) + new_tpr; /* TPR rolled over */ - else - sent = new_tpr - scp->tx_tpr; + u_int8_t tpr, sent; - if (sent > 0) { /* Packets sent since last interrupt */ - scp->tx_tpr = new_tpr; + /* Update packet count, accounting for rollover */ + tpr = XE_INB(XE_TPR); + sent = -scp->tx_tpr + tpr; + + /* Update statistics if we actually sent anything */ + if (sent > 0) { + scp->tx_tpr = tpr; scp->tx_queued -= sent; ifp->if_opackets += sent; - ifp->if_collisions += scp->tx_collisions; + ifp->if_collisions += coll; /* - * Collision stats are a PITA. If multiples frames have been sent, we - * distribute any outstanding collision count equally amongst them. - * However, if we're missing interrupts we're quite likely to also - * miss some collisions; thus the total count will be off anyway. - * Likewise, if we miss a frame dropped due to excessive collisions - * any outstanding collisions count will be held against the next - * frame to be successfully sent. Hopefully it averages out in the - * end! - * XXX - This will screw up if tx_collisions/sent > 14. FIX IT! + * According to the Xircom manual, Dingo will sometimes manage to + * transmit a packet with triggering an interrupt. If this happens, + * we have sent > 1 and the collision count only reflects collisions + * on the last packet sent (the one that triggered the interrupt). + * Collision stats might therefore be a bit low, but there doesn't + * seem to be anything we can do about that. */ - switch (scp->tx_collisions) { - case 0: + switch (coll) { + case 0: break; - case 1: + case 1: scp->mibdata.dot3StatsSingleCollisionFrames++; scp->mibdata.dot3StatsCollFrequencies[0]++; break; - default: - if (sent == 1) { - scp->mibdata.dot3StatsMultipleCollisionFrames++; - scp->mibdata.dot3StatsCollFrequencies[scp->tx_collisions-1]++; - } - else { /* Distribute across multiple frames */ - scp->mibdata.dot3StatsMultipleCollisionFrames += sent; - scp->mibdata. - dot3StatsCollFrequencies[scp->tx_collisions/sent] += sent - scp->tx_collisions%sent; - scp->mibdata. - dot3StatsCollFrequencies[scp->tx_collisions/sent + 1] += scp->tx_collisions%sent; - } + default: + scp->mibdata.dot3StatsMultipleCollisionFrames++; + scp->mibdata.dot3StatsCollFrequencies[coll-1]++; } - scp->tx_collisions = 0; } ifp->if_timer = 0; ifp->if_flags &= ~IFF_OACTIVE; } - if (txs & 0x0002) { /* Excessive collisions (packet dropped) */ - ifp->if_collisions += 16; - ifp->if_oerrors++; - scp->tx_collisions = 0; - scp->mibdata.dot3StatsExcessiveCollisions++; - scp->mibdata.dot3StatsMultipleCollisionFrames++; - scp->mibdata.dot3StatsCollFrequencies[15]++; - XE_OUTB(XE_CR, XE_CR_RESTART_TX); - } - if (txs & 0x0040) /* Transmit aborted -- probably collisions */ - scp->tx_collisions++; + /* Handle most MAC interrupts */ + if (isr & XE_ISR_MAC_INTR) { +#if 0 + /* Carrier sense lost -- only in 10Mbit HDX mode */ + if (txst0 & XE_TXST0_NO_CARRIER || !(txst1 & XE_TXST1_LINK_STATUS)) { + /* XXX - Need to update media status here */ + device_printf(scp->dev, "no carrier\n"); + ifp->if_oerrors++; + scp->mibdata.dot3StatsCarrierSenseErrors++; + } +#endif + /* Excessive collisions -- try sending again */ + if (txst0 & XE_TXST0_16_COLLISIONS) { + ifp->if_collisions += 16; + ifp->if_oerrors++; + scp->mibdata.dot3StatsExcessiveCollisions++; + scp->mibdata.dot3StatsMultipleCollisionFrames++; + scp->mibdata.dot3StatsCollFrequencies[15]++; + XE_OUTB(XE_CR, XE_CR_RESTART_TX); + } + /* Transmit underrun -- increase early transmit threshold */ + if (txst0 & XE_TXST0_TX_UNDERRUN && scp->mohawk) { + IFPRINTF(1, (ifp, "transmit underrun")); + if (scp->tx_thres < ETHER_MAX_LEN) { + if ((scp->tx_thres += 64) > ETHER_MAX_LEN) + scp->tx_thres = ETHER_MAX_LEN; + DPRINTF(1, (": increasing transmit threshold to %u", scp->tx_thres)); + XE_SELECT_PAGE(0x3); + XE_OUTW(XE_TPT, scp->tx_thres); + XE_SELECT_PAGE(0x0); + } + DPRINTF(1, ("\n")); + ifp->if_oerrors++; + scp->mibdata.dot3StatsInternalMacTransmitErrors++; + } + /* Late collision -- just complain about it */ + if (txst0 & XE_TXST0_LATE_COLLISION) { + if_printf(ifp, "late collision\n"); + ifp->if_oerrors++; + scp->mibdata.dot3StatsLateCollisions++; + } + /* SQE test failure -- just complain about it */ + if (txst0 & XE_TXST0_SQE_FAIL) { + if_printf(ifp, "SQE test failure\n"); + ifp->if_oerrors++; + scp->mibdata.dot3StatsSQETestErrors++; + } + /* Packet too long -- what happens to these */ + if (rst0 & XE_RST0_LONG_PACKET) { + if_printf(ifp, "received giant packet\n"); + ifp->if_ierrors++; + scp->mibdata.dot3StatsFrameTooLongs++; + } + /* CRC error -- packet dropped */ + if (rst0 & XE_RST0_CRC_ERROR) { + if_printf(ifp, "CRC error\n"); + ifp->if_ierrors++; + scp->mibdata.dot3StatsFCSErrors++; + } + } - /* - * Handle receive interrupts - */ + /* Handle received packet(s) */ while ((esr = XE_INB(XE_ESR)) & XE_ESR_FULL_PACKET_RX) { + rsr = XE_INB(XE_RSR); + IFPRINTF(3, (ifp, "intr: ESR=0x%02x, RSR=0x%02x\n", esr, rsr)); - if ((rsr = XE_INB(XE_RSR)) & XE_RSR_RX_OK) { + /* Make sure packet is a good one */ + if (rsr & XE_RSR_RX_OK) { struct ether_header *ehp; struct mbuf *mbp; u_int16_t len; - len = XE_INW(XE_RBC); + len = XE_INW(XE_RBC) - ETHER_CRC_LEN; + + IFPRINTF(3, (ifp, "intr: receive length = %d\n", len)); - if (len == 0) + if (len == 0) { + ifp->if_iqdrops++; continue; + } -#if 0 /* - * Limit the amount of time we spend in this loop, dropping packets if - * necessary. The Linux code does this with considerably more - * finesse, adjusting the threshold dynamically. + * Allocate mbuf to hold received packet. If the mbuf header isn't + * big enough, we attach an mbuf cluster to hold the packet. Note the + * +=2 to align the packet data on a 32-bit boundary, and the +3 to + * allow for the possibility of reading one more byte than the actual + * packet length (we always read 16-bit words). + * XXX - Surely there's a better way to do this alignment? */ - if ((rx_bytes += len) > 22000) { + MGETHDR(mbp, MB_DONTWAIT, MT_DATA); + if (mbp == NULL) { ifp->if_iqdrops++; - scp->mibData.dot3StatsMissedFrames++; - XE_OUTW(XE_DO, 0x8000); continue; } -#endif - if (len & 0x01) - len++; - - MGETHDR(mbp, MB_DONTWAIT, MT_DATA); /* Allocate a header mbuf */ - if (mbp != NULL) { - mbp->m_pkthdr.rcvif = ifp; - mbp->m_pkthdr.len = mbp->m_len = len; - - /* - * If the mbuf header isn't big enough for the packet, attach an - * mbuf cluster to hold it. The +2 is to allow for the nasty little - * alignment hack below. - */ - if (len + 2 > MHLEN) { - MCLGET(mbp, MB_DONTWAIT); - if ((mbp->m_flags & M_EXT) == 0) { - m_freem(mbp); - mbp = NULL; - } + if (len + 3 > MHLEN) { + MCLGET(mbp, MB_DONTWAIT); + if ((mbp->m_flags & M_EXT) == 0) { + m_freem(mbp); + ifp->if_iqdrops++; + continue; } } - if (mbp != NULL) { - /* - * The Ethernet header is 14 bytes long; thus the actual packet data - * won't be 32-bit aligned when it's dumped into the mbuf. We - * offset everything by 2 bytes to fix this. Apparently the - * alignment is important for NFS, damn its eyes. - */ - mbp->m_data += 2; - ehp = mtod(mbp, struct ether_header *); + mbp->m_data += 2; + ehp = mtod(mbp, struct ether_header *); - /* - * Now get the packet, including the Ethernet header and trailer (?) - * We use programmed I/O, because we don't know how to do shared - * memory with these cards. So yes, it's real slow, and heavy on - * the interrupts (CPU on my P150 maxed out at ~950KBps incoming). - */ - if (scp->srev == 0) { /* Workaround a bug in old cards */ - u_short rhs; - - XE_SELECT_PAGE(5); - rhs = XE_INW(XE_RHSA); - XE_SELECT_PAGE(0); - - rhs += 3; /* Skip control info */ - - if (rhs >= 0x8000) - rhs = 0; - - if (rhs + len > 0x8000) { - int i; - - /* - * XXX - This i-- seems very wrong, but it's what the Linux guys - * XXX - do. Need someone with an old CE2 to test this for me. - * XXX - 99/3/28: Changed the first i-- to an i++, maybe that'll - * XXX - fix it? It seems as though the previous version would - * XXX - have caused an infinite loop (what, another one?). - */ - for (i = 0; i < len; i++, rhs++) { - ((char *)ehp)[i] = XE_INB(XE_EDP); - if (rhs == 0x8000) { - rhs = 0; - i--; - } + /* + * Now get the packet in PIO mode, including the Ethernet header but + * omitting the trailing CRC. + */ + + /* + * Work around a bug in CE2 cards. There seems to be a problem with + * duplicated and extraneous bytes in the receive buffer, but without + * any real documentation for the CE2 it's hard to tell for sure. + * XXX - Needs testing on CE2 hardware + */ + if (scp->srev == 0) { + u_short rhs; + + XE_SELECT_PAGE(5); + rhs = XE_INW(XE_RHSA); + XE_SELECT_PAGE(0); + + rhs += 3; /* Skip control info */ + + if (rhs >= 0x8000) + rhs = 0; + + if (rhs + len > 0x8000) { + int i; + + for (i = 0; i < len; i++, rhs++) { + ((char *)ehp)[i] = XE_INB(XE_EDP); + if (rhs == 0x8000) { + rhs = 0; + i--; } } - else - bus_space_read_multi_2(scp->bst, scp->bsh, XE_EDP, - (u_int16_t *) ehp, len >> 1); } else bus_space_read_multi_2(scp->bst, scp->bsh, XE_EDP, - (u_int16_t *) ehp, len >> 1); - - /* Deliver packet to upper layers */ - if (mbp != NULL) { - mbp->m_pkthdr.len = mbp->m_len = len; - (*ifp->if_input)(ifp, mbp); /* Send the packet on its way */ - ifp->if_ipackets++; /* Success! */ - } - XE_OUTW(XE_DO, 0x8000); /* skip_rx_packet command */ + (u_int16_t *) ehp, (len + 1) >> 1); } + else + bus_space_read_multi_2(scp->bst, scp->bsh, XE_EDP, + (u_int16_t *) ehp, (len + 1) >> 1); + + /* Deliver packet to upper layers */ + mbp->m_pkthdr.rcvif = ifp; + mbp->m_pkthdr.len = mbp->m_len = len; + ifp->if_input(ifp, mbp); + ifp->if_ipackets++; } - else if (rsr & XE_RSR_LONG_PACKET) { /* Packet length >1518 bytes */ - scp->mibdata.dot3StatsFrameTooLongs++; - ifp->if_ierrors++; - } - else if (rsr & XE_RSR_CRC_ERROR) { /* Bad checksum on packet */ - scp->mibdata.dot3StatsFCSErrors++; - ifp->if_ierrors++; - } - else if (rsr & XE_RSR_ALIGN_ERROR) { /* Packet alignment error */ + else if (rsr & XE_RSR_ALIGN_ERROR) { + /* Packet alignment error -- drop packet */ + if_printf(ifp, "alignment error\n"); scp->mibdata.dot3StatsAlignmentErrors++; ifp->if_ierrors++; } + + /* Skip to next packet, if there is one */ + XE_OUTW(XE_DO, 0x8000); } - if (rxs & 0x10) { /* Receiver overrun */ - scp->mibdata.dot3StatsInternalMacReceiveErrors++; + + /* Clear receiver overruns now we have some free buffer space */ + if (rst0 & XE_RST0_RX_OVERRUN) { + IFPRINTF(1, (ifp, "receive overrun\n")); ifp->if_ierrors++; + scp->mibdata.dot3StatsInternalMacReceiveErrors++; XE_OUTB(XE_CR, XE_CR_CLEAR_OVERRUN); } } - XE_SELECT_PAGE(psr); /* Restore saved page */ - XE_OUTB(XE_CR, XE_CR_ENABLE_INTR); /* Re-enable interrupts */ + /* Restore saved page */ + XE_SELECT_PAGE(psr); - /* Could force an int here, instead of dropping packets? */ - /* XE_OUTB(XE_CR, XE_CR_ENABLE_INTR|XE_CE_FORCE_INTR); */ - - return; + /* Re-enable interrupts */ + XE_OUTB(XE_CR, XE_CR_ENABLE_INTR); } @@ -799,8 +872,7 @@ xe_watchdog(struct ifnet *ifp) { scp->tx_timeouts++; ifp->if_oerrors += scp->tx_queued; xe_stop(scp); - xe_hard_reset(scp); - xe_setmedia(scp); + xe_reset(scp); xe_init(scp); } @@ -812,9 +884,7 @@ static int xe_media_change(struct ifnet *ifp) { struct xe_softc *scp = ifp->if_softc; -#ifdef XE_DEBUG - if_printf(ifp, "media_change\n"); -#endif + IFPRINTF(2, (ifp, "media_change\n")); if (IFM_TYPE(scp->ifm->ifm_media) != IFM_ETHER) return(EINVAL); @@ -826,7 +896,7 @@ xe_media_change(struct ifnet *ifp) { IFM_SUBTYPE(scp->ifm->ifm_media) == IFM_100_TX) && !scp->phy_ok) return (EINVAL); - xe_setmedia(scp); + xe_setmedia_serialized(scp); return 0; } @@ -838,26 +908,35 @@ xe_media_change(struct ifnet *ifp) { static void xe_media_status(struct ifnet *ifp, struct ifmediareq *mrp) { -#ifdef XE_DEBUG - if_printf(ifp, "media_status\n"); -#endif + IFPRINTF(3, (ifp, "media_status\n")); + /* XXX - This is clearly wrong. Will fix once I have CE2 working */ + mrp->ifm_status = IFM_AVALID | IFM_ACTIVE; mrp->ifm_active = ((struct xe_softc *)ifp->if_softc)->media; - - return; } +static +void +xe_setmedia(void *xscp) +{ + struct xe_softc *scp = xscp; + + lwkt_serialize_enter(scp->arpcom.ac_if.if_serializer); + xe_setmedia_serialized(xscp); + lwkt_serialize_exit(scp->arpcom.ac_if.if_serializer); +} /* * Select active media. */ -static void xe_setmedia(void *xscp) { +static +void +xe_setmedia_serialized(void *xscp) +{ struct xe_softc *scp = xscp; u_int16_t bmcr, bmsr, anar, lpar; -#ifdef XE_DEBUG - if_printf(scp->ifp, "setmedia\n"); -#endif + IFPRINTF(2, (scp->ifp, "setmedia\n")); /* Cancel any pending timeout */ callout_stop(&scp->xe_timer); @@ -901,21 +980,19 @@ static void xe_setmedia(void *xscp) { */ switch (scp->autoneg_status) { - case XE_AUTONEG_NONE: -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Waiting for idle transmitter\n"); -#endif + case XE_AUTONEG_NONE: + IFPRINTF(2, (scp->ifp, "Waiting for idle transmitter\n")); scp->arpcom.ac_if.if_flags |= IFF_OACTIVE; scp->autoneg_status = XE_AUTONEG_WAITING; - callout_reset(&scp->xe_timer, hz * 2, xe_setmedia, scp); - return; + /* FALL THROUGH */ - case XE_AUTONEG_WAITING: - xe_soft_reset(scp); + case XE_AUTONEG_WAITING: + if (scp->tx_queued != 0) { + callout_reset(&scp->xe_timer, hz / 2, xe_setmedia, scp); + return; + } if (scp->phy_ok) { -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Starting autonegotiation\n"); -#endif + IFPRINTF(2, (scp->ifp, "Starting autonegotiation\n")); bmcr = xe_phy_readreg(scp, PHY_BMCR); bmcr &= ~(PHY_BMCR_AUTONEGENBL); xe_phy_writereg(scp, PHY_BMCR, bmcr); @@ -938,9 +1015,7 @@ static void xe_setmedia(void *xscp) { bmsr = xe_phy_readreg(scp, PHY_BMSR); lpar = xe_phy_readreg(scp, PHY_LPAR); if (bmsr & (PHY_BMSR_AUTONEGCOMP|PHY_BMSR_LINKSTAT)) { -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Autonegotiation complete!\n"); -#endif + IFPRINTF(2, (scp->ifp, "Autonegotiation complete!\n")); /* * XXX - Shouldn't have to do this, but (on my hub at least) the * XXX - transmitter won't work after a successful autoneg. So we see @@ -977,11 +1052,8 @@ static void xe_setmedia(void *xscp) { } } else { -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Autonegotiation failed; trying 100baseTX\n"); -#endif + IFPRINTF(2, (scp->ifp, "Autonegotiation failed; trying 100baseTX\n")); XE_MII_DUMP(scp); - xe_soft_reset(scp); if (scp->phy_ok) { xe_phy_writereg(scp, PHY_BMCR, PHY_BMCR_SPEEDSEL); scp->autoneg_status = XE_AUTONEG_100TX; @@ -998,9 +1070,7 @@ static void xe_setmedia(void *xscp) { (void)xe_phy_readreg(scp, PHY_BMSR); bmsr = xe_phy_readreg(scp, PHY_BMSR); if (bmsr & PHY_BMSR_LINKSTAT) { -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Got 100baseTX link!\n"); -#endif + IFPRINTF(2, (scp->ifp, "Got 100baseTX link!\n")); XE_MII_DUMP(scp); XE_SELECT_PAGE(2); XE_OUTB(XE_MSR, XE_INB(XE_MSR) | 0x08); @@ -1008,9 +1078,7 @@ static void xe_setmedia(void *xscp) { scp->autoneg_status = XE_AUTONEG_NONE; } else { -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Autonegotiation failed; disabling PHY\n"); -#endif + IFPRINTF(2, (scp->ifp, "Autonegotiation failed; disabling PHY\n")); XE_MII_DUMP(scp); xe_phy_writereg(scp, PHY_BMCR, 0x0000); XE_SELECT_PAGE(2); @@ -1028,9 +1096,7 @@ static void xe_setmedia(void *xscp) { * already by the big switch above. */ if (scp->autoneg_status == XE_AUTONEG_FAIL) { -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Selecting 10baseX\n"); -#endif + IFPRINTF(2, (scp->ifp, "Selecting 10baseX\n")); if (scp->mohawk) { XE_SELECT_PAGE(0x42); XE_OUTB(XE_SWC1, 0x80); @@ -1056,11 +1122,8 @@ static void xe_setmedia(void *xscp) { * no PHY, we fall back to 10baseT operation). */ case IFM_100_TX: /* Force 100baseTX */ - xe_soft_reset(scp); if (scp->phy_ok) { -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Selecting 100baseTX\n"); -#endif + IFPRINTF(2, (scp->ifp, "Selecting 100baseTX\n")); XE_SELECT_PAGE(0x42); XE_OUTB(XE_SWC1, 0); xe_phy_writereg(scp, PHY_BMCR, PHY_BMCR_SPEEDSEL); @@ -1072,10 +1135,7 @@ static void xe_setmedia(void *xscp) { /* FALLTHROUGH */ case IFM_10_T: /* Force 10baseT */ - xe_soft_reset(scp); -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Selecting 10baseT\n"); -#endif + IFPRINTF(2, (scp->ifp, "Selecting 10baseT\n")); if (scp->phy_ok) { xe_phy_writereg(scp, PHY_BMCR, 0x0000); XE_SELECT_PAGE(2); @@ -1087,10 +1147,7 @@ static void xe_setmedia(void *xscp) { break; case IFM_10_2: - xe_soft_reset(scp); -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Selecting 10base2\n"); -#endif + IFPRINTF(2, (scp->ifp, "Selecting 10base2\n")); XE_SELECT_PAGE(0x42); XE_OUTB(XE_SWC1, 0xc0); scp->media |= IFM_10_2; @@ -1102,9 +1159,7 @@ static void xe_setmedia(void *xscp) { * Finally, the LEDs are set to match whatever media was chosen and the * transmitter is unblocked. */ -#if XE_DEBUG > 1 - if_printf(scp->ifp, "Setting LEDs\n"); -#endif + IFPRINTF(2, (scp->ifp, "Setting LEDs\n")); XE_SELECT_PAGE(2); switch (IFM_SUBTYPE(scp->media)) { case IFM_100_TX: @@ -1120,8 +1175,9 @@ static void xe_setmedia(void *xscp) { } /* Restart output? */ + xe_enable_intr(scp); scp->ifp->if_flags &= ~IFF_OACTIVE; - xe_init(scp); + xe_start(scp->ifp); } @@ -1129,96 +1185,23 @@ static void xe_setmedia(void *xscp) { * Hard reset (power cycle) the card. */ static void -xe_hard_reset(struct xe_softc *scp) { - -#ifdef XE_DEBUG - if_printf(scp->ifp, "hard_reset\n"); -#endif +xe_reset(struct xe_softc *scp) { + IFPRINTF(2, (scp->ifp, "hard_reset\n")); crit_enter(); - /* - * Power cycle the card. - */ + /* Power down */ XE_SELECT_PAGE(4); - XE_OUTB(XE_GPR1, 0); /* Power off */ + XE_OUTB(XE_GPR1, 0); DELAY(40000); + /* Power up again */ if (scp->mohawk) - XE_OUTB(XE_GPR1, 1); /* And back on again */ + XE_OUTB(XE_GPR1, XE_GPR1_POWER_DOWN); else - XE_OUTB(XE_GPR1, 5); /* Also set AIC bit, whatever that is */ - DELAY(40000); - XE_SELECT_PAGE(0); + XE_OUTB(XE_GPR1, XE_GPR1_POWER_DOWN|XE_GPR1_AIC); - crit_exit(); -} - - -/* - * Soft reset the card. Also makes sure that the ML6692 and 10Mbit controller - * are powered up, sets the silicon revision number in softc, disables - * interrupts and checks for the prescence of a 100Mbit PHY. This should - * leave us in a position where we can access the PHY and do media - * selection. The function imposes a 0.5s delay while the hardware powers up. - */ -static void -xe_soft_reset(struct xe_softc *scp) { - -#ifdef XE_DEBUG - if_printf(scp->ifp, "soft_reset\n"); -#endif - - crit_enter(); - - /* - * Reset the card, (again). - */ - XE_SELECT_PAGE(0); - XE_OUTB(XE_CR, XE_CR_SOFT_RESET); DELAY(40000); - XE_OUTB(XE_CR, 0); - DELAY(40000); - - if (scp->mohawk) { - /* - * set GP1 and GP2 as outputs (bits 2 & 3) - * set GP1 low to power on the ML6692 (bit 0) - * set GP2 high to power on the 10Mhz chip (bit 1) - */ - XE_SELECT_PAGE(4); - XE_OUTB(XE_GPR0, 0x0e); - } - - /* - * Wait for everything to wake up. - */ - DELAY(500000); - - /* - * Get silicon revision number. - */ - XE_SELECT_PAGE(4); - if (scp->mohawk) - scp->srev = (XE_INB(XE_BOV) & 0x70) >> 4; - else - scp->srev = (XE_INB(XE_BOV) & 0x30) >> 4; -#ifdef XE_DEBUG - if_printf(scp->ifp, "silicon revision = %d\n", scp->srev); -#endif - - /* - * Shut off interrupts. - */ - xe_disable_intr(scp); - - /* - * Check for PHY. - */ - if (scp->mohawk) { - scp->phy_ok = xe_mii_init(scp); - } - XE_SELECT_PAGE(0); crit_exit(); @@ -1232,10 +1215,7 @@ xe_soft_reset(struct xe_softc *scp) { */ static void xe_stop(struct xe_softc *scp) { - -#ifdef XE_DEBUG - if_printf(scp->ifp, "stop\n"); -#endif + IFPRINTF(2, (scp->ifp, "stop\n")); crit_enter(); @@ -1250,6 +1230,15 @@ xe_stop(struct xe_softc *scp) { XE_SELECT_PAGE(4); XE_OUTB(XE_GPR1, 0); XE_SELECT_PAGE(0); + if (scp->mohawk) { + /* + * set GP1 and GP2 as outputs (bits 2 & 3) + * set GP1 high to power on the ML6692 (bit 0) + * set GP2 low to power on the 10Mhz chip (bit 1) + */ + XE_SELECT_PAGE(4); + XE_OUTB(XE_GPR0, XE_GPR0_GP2_SELECT|XE_GPR0_GP1_SELECT|XE_GPR0_GP1_OUT); + } /* * ~IFF_RUNNING == interface down. @@ -1263,18 +1252,11 @@ xe_stop(struct xe_softc *scp) { /* - * Enable Ethernet interrupts from the card. + * Enable interrupts from the card. */ static void xe_enable_intr(struct xe_softc *scp) { -#ifdef XE_DEBUG - if_printf(scp->ifp, "enable_intr\n"); -#endif - - XE_SELECT_PAGE(1); - XE_OUTB(XE_IMR0, 0xff); /* Unmask everything */ - XE_OUTB(XE_IMR1, 0x01); /* Unmask TX underrun detection */ - DELAY(1); + IFPRINTF(2, (scp->ifp, "enable_intr\n")); XE_SELECT_PAGE(0); XE_OUTB(XE_CR, XE_CR_ENABLE_INTR); /* Enable interrupts */ @@ -1287,133 +1269,221 @@ xe_enable_intr(struct xe_softc *scp) { /* - * Disable all Ethernet interrupts from the card. + * Disable interrupts from the card. */ static void xe_disable_intr(struct xe_softc *scp) { -#ifdef XE_DEBUG - if_printf(scp->ifp, "disable_intr\n"); -#endif + IFPRINTF(2, (scp->ifp, "disable_intr\n")); XE_SELECT_PAGE(0); XE_OUTB(XE_CR, 0); /* Disable interrupts */ - if (scp->modem && !scp->dingo) { /* More magic (does this work?) */ + if (scp->modem && !scp->dingo) { /* More magic */ XE_OUTB(0x10, 0x10); /* Mask the master int enable bit */ } - - XE_SELECT_PAGE(1); - XE_OUTB(XE_IMR0, 0); /* Forbid all interrupts */ - XE_OUTB(XE_IMR1, 0); - XE_SELECT_PAGE(0); } /* - * Set up multicast filter and promiscuous mode + * Set up multicast filter and promiscuous modes. */ static void -xe_setmulti(struct xe_softc *scp) { +xe_set_multicast(struct xe_softc *scp) { struct ifnet *ifp; struct ifmultiaddr *ifma; - int count; + u_int count, i; ifp = &scp->arpcom.ac_if; - /* Get length of multicast list */ + IFPRINTF(2, (ifp, "set_multicast\n")); + + XE_SELECT_PAGE(0x42); + + /* Handle PROMISC flag */ + if (ifp->if_flags & IFF_PROMISC) { + XE_OUTB(XE_SWC1, XE_INB(XE_SWC1) | XE_SWC1_PROMISCUOUS); + return; + } + else + XE_OUTB(XE_SWC1, XE_INB(XE_SWC1) & ~XE_SWC1_PROMISCUOUS); + + /* Handle ALLMULTI flag */ + if (ifp->if_flags & IFF_ALLMULTI) { + XE_OUTB(XE_SWC1, XE_INB(XE_SWC1) | XE_SWC1_ALLMULTI); + return; + } + else + XE_OUTB(XE_SWC1, XE_INB(XE_SWC1) & ~XE_SWC1_ALLMULTI); + + /* Iterate over multicast address list */ count = 0; - LIST_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + LIST_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + count++; - if ((ifp->if_flags & IFF_PROMISC) || (ifp->if_flags & IFF_ALLMULTI) || (count > 9)) { - /* - * Go into promiscuous mode if either of the PROMISC or ALLMULTI flags are - * set, or if we have been asked to deal with more than 9 multicast - * addresses. To do this: set MPE and PME in SWC1 - */ - XE_SELECT_PAGE(0x42); - XE_OUTB(XE_SWC1, 0x06); + if (count < 10) + /* First 9 use Individual Addresses for exact matching */ + xe_set_addr(scp, LLADDR((struct sockaddr_dl *)ifma->ifma_addr), count); + else + if (scp->mohawk) + /* Use hash filter on Mohawk and Dingo */ + xe_set_hash(scp, LLADDR((struct sockaddr_dl *)ifma->ifma_addr)); + else + /* Nowhere else to put them on CE2 */ + break; } - else if ((ifp->if_flags & IFF_MULTICAST) && (count > 0)) { - /* - * Program the filters for up to 9 addresses - */ + + IFPRINTF(2, (ifp, "set_multicast: count = %u\n", count)); + + /* Now do some cleanup and enable multicast handling as needed */ + if (count == 0) { + /* Disable all multicast handling */ + XE_SELECT_PAGE(0x42); - XE_OUTB(XE_SWC1, 0x01); - XE_SELECT_PAGE(0x40); - XE_OUTB(XE_CMD0, XE_CMD0_OFFLINE); - /*xe_reg_dump(scp);*/ - xe_setaddrs(scp); - /*xe_reg_dump(scp);*/ - XE_SELECT_PAGE(0x40); - XE_OUTB(XE_CMD0, XE_CMD0_RX_ENABLE|XE_CMD0_ONLINE); + XE_OUTB(XE_SWC1, XE_INB(XE_SWC1) & ~(XE_SWC1_IA_ENABLE|XE_SWC1_ALLMULTI)); + if (scp->mohawk) { + XE_SELECT_PAGE(0x02); + XE_OUTB(XE_MSR, XE_INB(XE_MSR) & ~XE_MSR_HASH_TABLE); + } } - else { - /* - * No multicast operation (default) - */ + else if (count < 10) { + /* Full in any unused Individual Addresses with our MAC address */ + for (i = count + 1; i < 10; i++) + xe_set_addr(scp, (u_int8_t *)(&scp->arpcom.ac_enaddr), i); + /* Enable Individual Address matching only */ XE_SELECT_PAGE(0x42); - XE_OUTB(XE_SWC1, 0); + XE_OUTB(XE_SWC1, (XE_INB(XE_SWC1) & ~XE_SWC1_ALLMULTI) | XE_SWC1_IA_ENABLE); + if (scp->mohawk) { + XE_SELECT_PAGE(0x02); + XE_OUTB(XE_MSR, XE_INB(XE_MSR) & ~XE_MSR_HASH_TABLE); + } + } + else { + if (scp->mohawk) { + /* Check whether hash table is full */ + XE_SELECT_PAGE(0x58); + for (i = 0x08; i < 0x10; i++) + if (XE_INB(i) != 0xff) + break; + if (i == 0x10) { + /* Hash table full - enable promiscuous multicast matching */ + XE_SELECT_PAGE(0x42); + XE_OUTB(XE_SWC1, (XE_INB(XE_SWC1) & ~XE_SWC1_IA_ENABLE) | XE_SWC1_ALLMULTI); + XE_SELECT_PAGE(0x02); + XE_OUTB(XE_MSR, XE_INB(XE_MSR) & ~XE_MSR_HASH_TABLE); + } + else { + /* Enable hash table and Individual Address matching */ + XE_SELECT_PAGE(0x42); + XE_OUTB(XE_SWC1, (XE_INB(XE_SWC1) & ~XE_SWC1_ALLMULTI) | XE_SWC1_IA_ENABLE); + XE_SELECT_PAGE(0x02); + XE_OUTB(XE_MSR, XE_INB(XE_MSR) | XE_MSR_HASH_TABLE); + } + } + else { + /* Enable promiscuous multicast matching */ + XE_SELECT_PAGE(0x42); + XE_OUTB(XE_SWC1, (XE_INB(XE_SWC1) & ~XE_SWC1_IA_ENABLE) | XE_SWC1_ALLMULTI); + } } XE_SELECT_PAGE(0); } /* - * Set up all on-chip addresses (for multicast). AFAICS, there are 10 - * of these things; the first is our MAC address, the other 9 are mcast - * addresses, padded with the MAC address if there aren't enough. - * XXX - This doesn't work right, but I'm not sure why yet. We seem to be - * XXX - doing much the same as the Linux code, which is weird enough that - * XXX - it's probably right (despite my earlier comments to the contrary). + * Copy the Ethernet multicast address in addr to the on-chip registers for + * Individual Address idx. Assumes that addr is really a multicast address + * and that idx > 0 (slot 0 is always used for the card MAC address). */ static void -xe_setaddrs(struct xe_softc *scp) { - struct ifmultiaddr *ifma; - u_int8_t *addr; - u_int8_t page, slot, byte, i; - - ifma = LIST_FIRST(&scp->arpcom.ac_if.if_multiaddrs); +xe_set_addr(struct xe_softc *scp, u_int8_t* addr, unsigned idx) { + uint8_t page, reg; + u_int i; - XE_SELECT_PAGE(page = 0x50); + /* + * Individual Addresses are stored in registers 8-F of pages 0x50-0x57. IA1 + * therefore starts at register 0xE on page 0x50. The expressions below + * compute the starting page and register for any IA index > 0. + */ + --idx; + page = 0x50 + idx%4 + idx/4*3; + reg = 0x0e - 2 * (idx%4); - for (slot = 0, byte = 8; slot < 10; slot++) { + IFPRINTF(3, (scp->ifp, "set_addr: idx = %u, page = 0x%02x, reg = 0x%02x\n", + idx+1, page, reg)); - if (slot == 0) - addr = (u_int8_t *)(&scp->arpcom.ac_enaddr); - else { - while (ifma != NULL && ifma->ifma_addr->sa_family != AF_LINK) - ifma = LIST_NEXT(ifma, ifma_link); - if (ifma != NULL) - addr = LLADDR((struct sockaddr_dl *)ifma->ifma_addr); - else - addr = (u_int8_t *)(&scp->arpcom.ac_enaddr); + /* + * Copy the IA bytes. Note that the byte order is reversed for Mohawk and + * Dingo wrt. CE2 hardware. + */ + XE_SELECT_PAGE(page); + for (i = 0; i < 6; i++) { +#ifdef XE_DEBUG + if (i > 0) { + DPRINTF(3, (":%02x", addr[i])); + } else { + IFPRINTF(3, (scp->ifp, "set_addr: %02x", addr[0])); } - - for (i = 0; i < 6; i++, byte++) { -#if XE_DEBUG > 2 - if (i) - printf(":%x", addr[i]); - else - if_printf(scp->ifp, "individual addresses %d: %x", slot, addr[0]); #endif + XE_OUTB(reg, addr[scp->mohawk ? 5 - i : i]); + if (++reg == 0x10) { + reg = 0x08; + XE_SELECT_PAGE(++page); + } + } + DPRINTF(3, ("\n")); +} - if (byte > 15) { - page++; - byte = 8; - XE_SELECT_PAGE(page); - } - if (scp->mohawk) - XE_OUTB(byte, addr[5 - i]); +/* + * Set the appropriate bit in the multicast hash table for the supplied + * Ethernet multicast address addr. Assumes that addr is really a multicast + * address. + */ +static void +xe_set_hash(struct xe_softc* scp, u_int8_t* addr) { + u_int32_t crc = 0xffffffff; + u_int8_t bit, byte, crc31, idx; + u_int i, j; + + /* Compute CRC of the address -- standard Ethernet CRC function */ + for (i = 0; i < 6; i++) { + byte = addr[i]; + for (j = 1; j <= 8; j++) { + if (crc & 0x80000000) + crc31 = 0x01; else - XE_OUTB(byte, addr[i]); + crc31 = 0; + bit = crc31 ^ (byte & 0x01); + crc <<= 1; + byte >>= 1; + if (bit) + crc = (crc ^ XE_CRC_POLY)|1; } -#if XE_DEBUG > 2 - printf("\n"); -#endif } - XE_SELECT_PAGE(0); + IFPRINTF(3, (scp->ifp, "set_hash: CRC = 0x%08x\n", crc)); + + /* Hash table index = 6 msbs of CRC, reversed */ + for (i = 0, idx = 0; i < 6; i++) { + idx >>= 1; + if (crc & 0x80000000) { + idx |= 0x20; + } + crc <<= 1; + } + + /* Top 3 bits of idx give register - 8, bottom 3 give bit within register */ + byte = idx >> 3 | 0x08; + bit = 0x01 << (idx & 0x07); + + IFPRINTF(3, (scp->ifp, + "set_hash: idx = 0x%02x, byte = 0x%02x, bit = 0x%02x\n", + idx, byte, bit)); + + XE_SELECT_PAGE(0x58); + XE_OUTB(byte, XE_INB(byte) | bit); } @@ -1422,28 +1492,32 @@ xe_setaddrs(struct xe_softc *scp) { */ static int xe_pio_write_packet(struct xe_softc *scp, struct mbuf *mbp) { - struct mbuf *mbp2; - u_int16_t len, pad, free, ok; + u_int len, pad; + u_char wantbyte; u_int8_t *data; - u_int8_t savebyte[2], wantbyte; + u_int8_t savebyte[2]; /* Get total packet length */ - for (len = 0, mbp2 = mbp; mbp2 != NULL; len += mbp2->m_len, mbp2 = mbp2->m_next); + if (mbp->m_flags & M_PKTHDR) + len = mbp->m_pkthdr.len; + else { + struct mbuf* mbp2 = mbp; + for (len = 0; mbp2 != NULL; len += mbp2->m_len, mbp2 = mbp2->m_next); + } + + IFPRINTF(3, (scp->ifp, "pio_write_packet: len = %u\n", len)); /* Packets < minimum length may need to be padded out */ pad = 0; - if (len < ETHER_MIN_LEN - ETHER_CRC_LEN) { - pad = (ETHER_MIN_LEN - ETHER_CRC_LEN - len + 1) >> 1; - len = ETHER_MIN_LEN - ETHER_CRC_LEN; + if (len < scp->tx_min) { + pad = scp->tx_min - len; + len = scp->tx_min; } /* Check transmit buffer space */ XE_SELECT_PAGE(0); - XE_OUTW(XE_TRS, len+2); - free = XE_INW(XE_TSO); - ok = free & 0x8000; - free &= 0x7fff; - if (free <= len + 2) + XE_OUTW(XE_TRS, len+2); /* Only effective on rev. 1 CE2 cards */ + if ((XE_INW(XE_TSO) & 0x7fff) <= len + 2) return 1; /* Send packet length to card */ @@ -1477,82 +1551,34 @@ xe_pio_write_packet(struct xe_softc *scp, struct mbuf *mbp) { } mbp = mbp->m_next; } - if (wantbyte) /* Last byte for odd-length packets */ - XE_OUTW(XE_EDP, *(u_short *)savebyte); /* - * For CE3 cards, just tell 'em to send -- apparently the card will pad out - * short packets with random cruft. Otherwise, write nonsense words to fill - * out the packet. I guess it is then sent automatically (?) + * Send last byte of odd-length packets + */ + if (wantbyte) + XE_OUTB(XE_EDP, savebyte[0]); + + /* + * Can just tell CE3 cards to send; short packets will be padded out with + * random cruft automatically. For CE2, manually pad the packet with + * garbage; it will be sent when the required number or bytes have been + * delivered to the card. */ if (scp->mohawk) - XE_OUTB(XE_CR, XE_CR_TX_PACKET|XE_CR_ENABLE_INTR); - else + XE_OUTB(XE_CR, XE_CR_TX_PACKET | XE_CR_RESTART_TX | XE_CR_ENABLE_INTR); + else if (pad > 0) { + if (pad & 0x01) + XE_OUTB(XE_EDP, 0xaa); + pad >>= 1; while (pad > 0) { XE_OUTW(XE_EDP, 0xdead); pad--; } - - return 0; -} - -#if 0 -/* - * Compute the 32-bit Ethernet CRC for the given buffer. - */ -static u_int32_t -xe_compute_crc(u_int8_t *data, int len) { - u_int32_t crc = 0xffffffff; - u_int32_t poly = 0x04c11db6; - u_int8_t current, crc31, bit; - int i, k; - - for (i = 0; i < len; i++) { - current = data[i]; - for (k = 1; k <= 8; k++) { - if (crc & 0x80000000) { - crc31 = 0x01; - } - else { - crc31 = 0; - } - bit = crc31 ^ (current & 0x01); - crc <<= 1; - current >>= 1; - if (bit) { - crc = (crc ^ poly)|1; - } - } } - return crc; -} - - -/* - * Convert a CRC into an index into the multicast hash table. What we do is - * take the most-significant 6 bits of the CRC, reverse them, and use that as - * the bit number in the hash table. Bits 5:3 of the result give the byte - * within the table (0-7); bits 2:0 give the bit number within that byte (also - * 0-7), ie. the number of shifts needed to get it into the lsb position. - */ -static int -xe_compute_hashbit(u_int32_t crc) { - u_int8_t hashbit = 0; - int i; - for (i = 0; i < 6; i++) { - hashbit >>= 1; - if (crc & 0x80000000) { - hashbit &= 0x80; - } - crc <<= 1; - } - return (hashbit >> 2); + return 0; } -#endif - - /************************************************************** * * @@ -1599,15 +1625,11 @@ xe_mii_init(struct xe_softc *scp) { status = xe_phy_readreg(scp, PHY_BMSR); if ((status & 0xff00) != 0x7800) { -#if XE_DEBUG > 1 - if_printf(scp->ifp, "no PHY found, %0x\n", status); -#endif + IFPRINTF(2, (scp->ifp, "no PHY found, %0x\n", status)); return 0; } else { -#if XE_DEBUG > 1 - if_printf(scp->ifp, "PHY OK!\n"); -#endif + IFPRINTF(2, (scp->ifp, "PHY OK!\n")); /* Reset the PHY */ xe_phy_writereg(scp, PHY_BMCR, PHY_BMCR_RESET); @@ -1818,7 +1840,6 @@ xe_phy_writereg(struct xe_softc *scp, u_int16_t reg, u_int16_t data) { } -#ifdef XE_DEBUG /* * A bit of debugging code. */ @@ -1877,25 +1898,24 @@ xe_reg_dump(struct xe_softc *scp) { crit_exit(); } -#endif int xe_activate(device_t dev) { struct xe_softc *sc = device_get_softc(dev); - int start; + int start, i; + + DEVPRINTF(2, (dev, "activate\n")); - if (!sc->dingo) { + if (!sc->modem) { sc->port_rid = 0; /* 0 is managed by pccard */ sc->port_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->port_rid, 0, ~0, 16, RF_ACTIVE); - } else { + } else if (sc->dingo) { /* * Find a 16 byte aligned ioport for the card. */ -#if XE_DEBUG > 0 - device_printf(dev, "Finding an aligned port for RealPort\n"); -#endif /* XE_DEBUG */ + DEVPRINTF(1, (dev, "Finding an aligned port for RealPort\n")); sc->port_rid = 1; /* 0 is managed by pccard */ start = 0x100; do { @@ -1910,16 +1930,52 @@ xe_activate(device_t dev) sc->port_res); start = (rman_get_start(sc->port_res) + 15) & ~0xf; } while (1); -#if XE_DEBUG > 2 - device_printf(dev, "port 0x%0lx, size 0x%0lx\n", + DEVPRINTF(1, (dev, "RealPort port 0x%0lx, size 0x%0lx\n", + bus_get_resource_start(dev, SYS_RES_IOPORT, sc->port_rid), + bus_get_resource_count(dev, SYS_RES_IOPORT, sc->port_rid))); + } + else if (sc->ce2) { + /* + * Find contiguous I/O port for the Ethernet function on CEM2 and + * CEM3 cards. We allocate window 0 wherever pccard has decided + * it should be, then find an available window adjacent to it for + * the second function. Not sure that both windows are actually + * needed. + */ + DEVPRINTF(1, (dev, "Finding I/O port for CEM2/CEM3\n")); + sc->ce2_port_rid = 0; /* 0 is managed by pccard */ + sc->ce2_port_res = bus_alloc_resource(dev, SYS_RES_IOPORT, + &sc->ce2_port_rid, 0, ~0, + 8, RF_ACTIVE); + if (!sc->ce2_port_res) { + device_printf(dev, "Cannot allocate I/O port for modem\n"); + return ENOMEM; + } + + sc->port_rid = 1; + start = bus_get_resource_start(dev, SYS_RES_IOPORT, + sc->ce2_port_rid); + for (i = 0; i < 2; i++) { + start += (i == 0 ? 8 : -24); + sc->port_res = bus_alloc_resource(dev, SYS_RES_IOPORT, + &sc->port_rid, start, + start + 18, 18, RF_ACTIVE); + if (sc->port_res == 0) + continue; /* Failed, try again if possible */ + if (bus_get_resource_start(dev, SYS_RES_IOPORT, + sc->port_rid) == start) + break; /* Success! */ + + bus_release_resource(dev, SYS_RES_IOPORT, sc->port_rid, + sc->port_res); + sc->port_res = 0; + } + DEVPRINTF(1, (dev, "CEM2/CEM3 port 0x%0lx, size 0x%0lx\n", bus_get_resource_start(dev, SYS_RES_IOPORT, sc->port_rid), - bus_get_resource_count(dev, SYS_RES_IOPORT, sc->port_rid)); -#endif /* XE_DEBUG */ + bus_get_resource_count(dev, SYS_RES_IOPORT, sc->port_rid))); } if (!sc->port_res) { -#if XE_DEBUG > 0 device_printf(dev, "Cannot allocate ioport\n"); -#endif return ENOMEM; } @@ -1927,9 +1983,7 @@ xe_activate(device_t dev) sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid, RF_ACTIVE); if (!sc->irq_res) { -#if XE_DEBUG > 0 device_printf(dev, "Cannot allocate irq\n"); -#endif xe_deactivate(dev); return ENOMEM; } @@ -1944,10 +1998,17 @@ xe_deactivate(device_t dev) { struct xe_softc *sc = device_get_softc(dev); + DEVPRINTF(2, (dev, "deactivate\n")); + xe_disable_intr(sc); + if (sc->port_res) bus_release_resource(dev, SYS_RES_IOPORT, sc->port_rid, sc->port_res); sc->port_res = 0; + if (sc->ce2_port_res) + bus_release_resource(dev, SYS_RES_IOPORT, sc->ce2_port_rid, + sc->ce2_port_res); + sc->ce2_port_res = 0; if (sc->irq_res) bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res);