| /* | |
| net-3-driver for the SKNET MCA-based cards | |
| This is an extension to the Linux operating system, and is covered by the | |
| same GNU General Public License that covers that work. | |
| Copyright 1999 by Alfred Arnold (alfred@ccac.rwth-aachen.de, | |
| alfred.arnold@lancom.de) | |
| This driver is based both on the 3C523 driver and the SK_G16 driver. | |
| paper sources: | |
| 'PC Hardware: Aufbau, Funktionsweise, Programmierung' by | |
| Hans-Peter Messmer for the basic Microchannel stuff | |
| 'Linux Geraetetreiber' by Allesandro Rubini, Kalle Dalheimer | |
| for help on Ethernet driver programming | |
| 'Ethernet/IEEE 802.3 Family 1992 World Network Data Book/Handbook' by AMD | |
| for documentation on the AM7990 LANCE | |
| 'SKNET Personal Technisches Manual', Version 1.2 by Schneider&Koch | |
| for documentation on the Junior board | |
| 'SK-NET MC2+ Technical Manual", Version 1.1 by Schneider&Koch for | |
| documentation on the MC2 bord | |
| A big thank you to the S&K support for providing me so quickly with | |
| documentation! | |
| Also see http://www.syskonnect.com/ | |
| Missing things: | |
| -> set debug level via ioctl instead of compile-time switches | |
| -> I didn't follow the development of the 2.1.x kernels, so my | |
| assumptions about which things changed with which kernel version | |
| are probably nonsense | |
| History: | |
| May 16th, 1999 | |
| startup | |
| May 22st, 1999 | |
| added private structure, methods | |
| begun building data structures in RAM | |
| May 23nd, 1999 | |
| can receive frames, send frames | |
| May 24th, 1999 | |
| modularized initialization of LANCE | |
| loadable as module | |
| still Tx problem :-( | |
| May 26th, 1999 | |
| MC2 works | |
| support for multiple devices | |
| display media type for MC2+ | |
| May 28th, 1999 | |
| fixed problem in GetLANCE leaving interrupts turned off | |
| increase TX queue to 4 packets to improve send performance | |
| May 29th, 1999 | |
| a few corrections in statistics, caught rcvr overruns | |
| reinitialization of LANCE/board in critical situations | |
| MCA info implemented | |
| implemented LANCE multicast filter | |
| Jun 6th, 1999 | |
| additions for Linux 2.2 | |
| Dec 25th, 1999 | |
| unfortunately there seem to be newer MC2+ boards that react | |
| on IRQ 3/5/9/10 instead of 3/5/10/11, so we have to autoprobe | |
| in questionable cases... | |
| Dec 28th, 1999 | |
| integrated patches from David Weinehall & Bill Wendling for 2.3 | |
| kernels (isa_...functions). Things are defined in a way that | |
| it still works with 2.0.x 8-) | |
| Dec 30th, 1999 | |
| added handling of the remaining interrupt conditions. That | |
| should cure the spurious hangs. | |
| Jan 30th, 2000 | |
| newer kernels automatically probe more than one board, so the | |
| 'startslot' as a variable is also needed here | |
| June 1st, 2000 | |
| added changes for recent 2.3 kernels | |
| *************************************************************************/ | |
| #include <linux/kernel.h> | |
| #include <linux/string.h> | |
| #include <linux/errno.h> | |
| #include <linux/ioport.h> | |
| #include <linux/slab.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/delay.h> | |
| #include <linux/time.h> | |
| #include <linux/mca-legacy.h> | |
| #include <linux/init.h> | |
| #include <linux/module.h> | |
| #include <linux/netdevice.h> | |
| #include <linux/etherdevice.h> | |
| #include <linux/skbuff.h> | |
| #include <linux/bitops.h> | |
| #include <asm/processor.h> | |
| #include <asm/io.h> | |
| #define _SK_MCA_DRIVER_ | |
| #include "sk_mca.h" | |
| /* ------------------------------------------------------------------------ | |
| * global static data - not more since we can handle multiple boards and | |
| * have to pack all state info into the device struct! | |
| * ------------------------------------------------------------------------ */ | |
| static char *MediaNames[Media_Count] = | |
| { "10Base2", "10BaseT", "10Base5", "Unknown" }; | |
| static unsigned char poly[] = | |
| { 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, | |
| 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 | |
| }; | |
| /* ------------------------------------------------------------------------ | |
| * private subfunctions | |
| * ------------------------------------------------------------------------ */ | |
| /* dump parts of shared memory - only needed during debugging */ | |
| #ifdef DEBUG | |
| static void dumpmem(struct net_device *dev, u32 start, u32 len) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| int z; | |
| for (z = 0; z < len; z++) { | |
| if ((z & 15) == 0) | |
| printk("%04x:", z); | |
| printk(" %02x", readb(priv->base + start + z)); | |
| if ((z & 15) == 15) | |
| printk("\n"); | |
| } | |
| } | |
| /* print exact time - ditto */ | |
| static void PrTime(void) | |
| { | |
| struct timeval tv; | |
| do_gettimeofday(&tv); | |
| printk("%9d:%06d: ", tv.tv_sec, tv.tv_usec); | |
| } | |
| #endif | |
| /* deduce resources out of POS registers */ | |
| static void __init getaddrs(int slot, int junior, int *base, int *irq, | |
| skmca_medium * medium) | |
| { | |
| u_char pos0, pos1, pos2; | |
| if (junior) { | |
| pos0 = mca_read_stored_pos(slot, 2); | |
| *base = ((pos0 & 0x0e) << 13) + 0xc0000; | |
| *irq = ((pos0 & 0x10) >> 4) + 10; | |
| *medium = Media_Unknown; | |
| } else { | |
| /* reset POS 104 Bits 0+1 so the shared memory region goes to the | |
| configured area between 640K and 1M. Afterwards, enable the MC2. | |
| I really don't know what rode SK to do this... */ | |
| mca_write_pos(slot, 4, | |
| mca_read_stored_pos(slot, 4) & 0xfc); | |
| mca_write_pos(slot, 2, | |
| mca_read_stored_pos(slot, 2) | 0x01); | |
| pos1 = mca_read_stored_pos(slot, 3); | |
| pos2 = mca_read_stored_pos(slot, 4); | |
| *base = ((pos1 & 0x07) << 14) + 0xc0000; | |
| switch (pos2 & 0x0c) { | |
| case 0: | |
| *irq = 3; | |
| break; | |
| case 4: | |
| *irq = 5; | |
| break; | |
| case 8: | |
| *irq = -10; | |
| break; | |
| case 12: | |
| *irq = -11; | |
| break; | |
| } | |
| *medium = (pos2 >> 6) & 3; | |
| } | |
| } | |
| /* check for both cards: | |
| When the MC2 is turned off, it was configured for more than 15MB RAM, | |
| is disabled and won't get detected using the standard probe. We | |
| therefore have to scan the slots manually :-( */ | |
| static int __init dofind(int *junior, int firstslot) | |
| { | |
| int slot; | |
| unsigned int id; | |
| for (slot = firstslot; slot < MCA_MAX_SLOT_NR; slot++) { | |
| id = mca_read_stored_pos(slot, 0) | |
| + (((unsigned int) mca_read_stored_pos(slot, 1)) << 8); | |
| *junior = 0; | |
| if (id == SKNET_MCA_ID) | |
| return slot; | |
| *junior = 1; | |
| if (id == SKNET_JUNIOR_MCA_ID) | |
| return slot; | |
| } | |
| return MCA_NOTFOUND; | |
| } | |
| /* reset the whole board */ | |
| static void ResetBoard(struct net_device *dev) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| writeb(CTRL_RESET_ON, priv->ctrladdr); | |
| udelay(10); | |
| writeb(CTRL_RESET_OFF, priv->ctrladdr); | |
| } | |
| /* wait for LANCE interface to become not busy */ | |
| static int WaitLANCE(struct net_device *dev) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| int t = 0; | |
| while ((readb(priv->ctrladdr) & STAT_IO_BUSY) == | |
| STAT_IO_BUSY) { | |
| udelay(1); | |
| if (++t > 1000) { | |
| printk("%s: LANCE access timeout", dev->name); | |
| return 0; | |
| } | |
| } | |
| return 1; | |
| } | |
| /* set LANCE register - must be atomic */ | |
| static void SetLANCE(struct net_device *dev, u16 addr, u16 value) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| unsigned long flags; | |
| /* disable interrupts */ | |
| spin_lock_irqsave(&priv->lock, flags); | |
| /* wait until no transfer is pending */ | |
| WaitLANCE(dev); | |
| /* transfer register address to RAP */ | |
| writeb(CTRL_RESET_OFF | CTRL_RW_WRITE | CTRL_ADR_RAP, priv->ctrladdr); | |
| writew(addr, priv->ioregaddr); | |
| writeb(IOCMD_GO, priv->cmdaddr); | |
| udelay(1); | |
| WaitLANCE(dev); | |
| /* transfer data to register */ | |
| writeb(CTRL_RESET_OFF | CTRL_RW_WRITE | CTRL_ADR_DATA, priv->ctrladdr); | |
| writew(value, priv->ioregaddr); | |
| writeb(IOCMD_GO, priv->cmdaddr); | |
| udelay(1); | |
| WaitLANCE(dev); | |
| /* reenable interrupts */ | |
| spin_unlock_irqrestore(&priv->lock, flags); | |
| } | |
| /* get LANCE register */ | |
| static u16 GetLANCE(struct net_device *dev, u16 addr) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| unsigned long flags; | |
| unsigned int res; | |
| /* disable interrupts */ | |
| spin_lock_irqsave(&priv->lock, flags); | |
| /* wait until no transfer is pending */ | |
| WaitLANCE(dev); | |
| /* transfer register address to RAP */ | |
| writeb(CTRL_RESET_OFF | CTRL_RW_WRITE | CTRL_ADR_RAP, priv->ctrladdr); | |
| writew(addr, priv->ioregaddr); | |
| writeb(IOCMD_GO, priv->cmdaddr); | |
| udelay(1); | |
| WaitLANCE(dev); | |
| /* transfer data from register */ | |
| writeb(CTRL_RESET_OFF | CTRL_RW_READ | CTRL_ADR_DATA, priv->ctrladdr); | |
| writeb(IOCMD_GO, priv->cmdaddr); | |
| udelay(1); | |
| WaitLANCE(dev); | |
| res = readw(priv->ioregaddr); | |
| /* reenable interrupts */ | |
| spin_unlock_irqrestore(&priv->lock, flags); | |
| return res; | |
| } | |
| /* build up descriptors in shared RAM */ | |
| static void InitDscrs(struct net_device *dev) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| u32 bufaddr; | |
| /* Set up Tx descriptors. The board has only 16K RAM so bits 16..23 | |
| are always 0. */ | |
| bufaddr = RAM_DATABASE; | |
| { | |
| LANCE_TxDescr descr; | |
| int z; | |
| for (z = 0; z < TXCOUNT; z++) { | |
| descr.LowAddr = bufaddr; | |
| descr.Flags = 0; | |
| descr.Len = 0xf000; | |
| descr.Status = 0; | |
| memcpy_toio(priv->base + RAM_TXBASE + | |
| (z * sizeof(LANCE_TxDescr)), &descr, | |
| sizeof(LANCE_TxDescr)); | |
| memset_io(priv->base + bufaddr, 0, RAM_BUFSIZE); | |
| bufaddr += RAM_BUFSIZE; | |
| } | |
| } | |
| /* do the same for the Rx descriptors */ | |
| { | |
| LANCE_RxDescr descr; | |
| int z; | |
| for (z = 0; z < RXCOUNT; z++) { | |
| descr.LowAddr = bufaddr; | |
| descr.Flags = RXDSCR_FLAGS_OWN; | |
| descr.MaxLen = -RAM_BUFSIZE; | |
| descr.Len = 0; | |
| memcpy_toio(priv->base + RAM_RXBASE + | |
| (z * sizeof(LANCE_RxDescr)), &descr, | |
| sizeof(LANCE_RxDescr)); | |
| memset_io(priv->base + bufaddr, 0, RAM_BUFSIZE); | |
| bufaddr += RAM_BUFSIZE; | |
| } | |
| } | |
| } | |
| /* calculate the hash bit position for a given multicast address | |
| taken more or less directly from the AMD datasheet... */ | |
| static void UpdateCRC(unsigned char *CRC, int bit) | |
| { | |
| int j; | |
| /* shift CRC one bit */ | |
| memmove(CRC + 1, CRC, 32 * sizeof(unsigned char)); | |
| CRC[0] = 0; | |
| /* if bit XOR controlbit = 1, set CRC = CRC XOR polynomial */ | |
| if (bit ^ CRC[32]) | |
| for (j = 0; j < 32; j++) | |
| CRC[j] ^= poly[j]; | |
| } | |
| static unsigned int GetHash(char *address) | |
| { | |
| unsigned char CRC[33]; | |
| int i, byte, hashcode; | |
| /* a multicast address has bit 0 in the first byte set */ | |
| if ((address[0] & 1) == 0) | |
| return -1; | |
| /* initialize CRC */ | |
| memset(CRC, 1, sizeof(CRC)); | |
| /* loop through address bits */ | |
| for (byte = 0; byte < 6; byte++) | |
| for (i = 0; i < 8; i++) | |
| UpdateCRC(CRC, (address[byte] >> i) & 1); | |
| /* hashcode is the 6 least significant bits of the CRC */ | |
| hashcode = 0; | |
| for (i = 0; i < 6; i++) | |
| hashcode = (hashcode << 1) + CRC[i]; | |
| return hashcode; | |
| } | |
| /* feed ready-built initialization block into LANCE */ | |
| static void InitLANCE(struct net_device *dev) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| /* build up descriptors. */ | |
| InitDscrs(dev); | |
| /* next RX descriptor to be read is the first one. Since the LANCE | |
| will start from the beginning after initialization, we have to | |
| reset out pointers too. */ | |
| priv->nextrx = 0; | |
| /* no TX descriptors active */ | |
| priv->nexttxput = priv->nexttxdone = priv->txbusy = 0; | |
| /* set up the LANCE bus control register - constant for SKnet boards */ | |
| SetLANCE(dev, LANCE_CSR3, | |
| CSR3_BSWAP_OFF | CSR3_ALE_LOW | CSR3_BCON_HOLD); | |
| /* write address of initialization block into LANCE */ | |
| SetLANCE(dev, LANCE_CSR1, RAM_INITBASE & 0xffff); | |
| SetLANCE(dev, LANCE_CSR2, (RAM_INITBASE >> 16) & 0xff); | |
| /* we don't get ready until the LANCE has read the init block */ | |
| netif_stop_queue(dev); | |
| /* let LANCE read the initialization block. LANCE is ready | |
| when we receive the corresponding interrupt. */ | |
| SetLANCE(dev, LANCE_CSR0, CSR0_INEA | CSR0_INIT); | |
| } | |
| /* stop the LANCE so we can reinitialize it */ | |
| static void StopLANCE(struct net_device *dev) | |
| { | |
| /* can't take frames any more */ | |
| netif_stop_queue(dev); | |
| /* disable interrupts, stop it */ | |
| SetLANCE(dev, LANCE_CSR0, CSR0_STOP); | |
| } | |
| /* initialize card and LANCE for proper operation */ | |
| static void InitBoard(struct net_device *dev) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| LANCE_InitBlock block; | |
| /* Lay out the shared RAM - first we create the init block for the LANCE. | |
| We do not overwrite it later because we need it again when we switch | |
| promiscous mode on/off. */ | |
| block.Mode = 0; | |
| if (dev->flags & IFF_PROMISC) | |
| block.Mode |= LANCE_INIT_PROM; | |
| memcpy(block.PAdr, dev->dev_addr, 6); | |
| memset(block.LAdrF, 0, sizeof(block.LAdrF)); | |
| block.RdrP = (RAM_RXBASE & 0xffffff) | (LRXCOUNT << 29); | |
| block.TdrP = (RAM_TXBASE & 0xffffff) | (LTXCOUNT << 29); | |
| memcpy_toio(priv->base + RAM_INITBASE, &block, sizeof(block)); | |
| /* initialize LANCE. Implicitly sets up other structures in RAM. */ | |
| InitLANCE(dev); | |
| } | |
| /* deinitialize card and LANCE */ | |
| static void DeinitBoard(struct net_device *dev) | |
| { | |
| /* stop LANCE */ | |
| StopLANCE(dev); | |
| /* reset board */ | |
| ResetBoard(dev); | |
| } | |
| /* probe for device's irq */ | |
| static int __init ProbeIRQ(struct net_device *dev) | |
| { | |
| unsigned long imaskval, njiffies, irq; | |
| u16 csr0val; | |
| /* enable all interrupts */ | |
| imaskval = probe_irq_on(); | |
| /* initialize the board. Wait for interrupt 'Initialization done'. */ | |
| ResetBoard(dev); | |
| InitBoard(dev); | |
| njiffies = jiffies + HZ; | |
| do { | |
| csr0val = GetLANCE(dev, LANCE_CSR0); | |
| } | |
| while (((csr0val & CSR0_IDON) == 0) && (jiffies != njiffies)); | |
| /* turn of interrupts again */ | |
| irq = probe_irq_off(imaskval); | |
| /* if we found something, ack the interrupt */ | |
| if (irq) | |
| SetLANCE(dev, LANCE_CSR0, csr0val | CSR0_IDON); | |
| /* back to idle state */ | |
| DeinitBoard(dev); | |
| return irq; | |
| } | |
| /* ------------------------------------------------------------------------ | |
| * interrupt handler(s) | |
| * ------------------------------------------------------------------------ */ | |
| /* LANCE has read initialization block -> start it */ | |
| static u16 irqstart_handler(struct net_device *dev, u16 oldcsr0) | |
| { | |
| /* now we're ready to transmit */ | |
| netif_wake_queue(dev); | |
| /* reset IDON bit, start LANCE */ | |
| SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_IDON | CSR0_STRT); | |
| return GetLANCE(dev, LANCE_CSR0); | |
| } | |
| /* did we lose blocks due to a FIFO overrun ? */ | |
| static u16 irqmiss_handler(struct net_device *dev, u16 oldcsr0) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| /* update statistics */ | |
| priv->stat.rx_fifo_errors++; | |
| /* reset MISS bit */ | |
| SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_MISS); | |
| return GetLANCE(dev, LANCE_CSR0); | |
| } | |
| /* receive interrupt */ | |
| static u16 irqrx_handler(struct net_device *dev, u16 oldcsr0) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| LANCE_RxDescr descr; | |
| unsigned int descraddr; | |
| /* run through queue until we reach a descriptor we do not own */ | |
| descraddr = RAM_RXBASE + (priv->nextrx * sizeof(LANCE_RxDescr)); | |
| while (1) { | |
| /* read descriptor */ | |
| memcpy_fromio(&descr, priv->base + descraddr, | |
| sizeof(LANCE_RxDescr)); | |
| /* if we reach a descriptor we do not own, we're done */ | |
| if ((descr.Flags & RXDSCR_FLAGS_OWN) != 0) | |
| break; | |
| #ifdef DEBUG | |
| PrTime(); | |
| printk("Receive packet on descr %d len %d\n", priv->nextrx, | |
| descr.Len); | |
| #endif | |
| /* erroneous packet ? */ | |
| if ((descr.Flags & RXDSCR_FLAGS_ERR) != 0) { | |
| priv->stat.rx_errors++; | |
| if ((descr.Flags & RXDSCR_FLAGS_CRC) != 0) | |
| priv->stat.rx_crc_errors++; | |
| else if ((descr.Flags & RXDSCR_FLAGS_CRC) != 0) | |
| priv->stat.rx_frame_errors++; | |
| else if ((descr.Flags & RXDSCR_FLAGS_OFLO) != 0) | |
| priv->stat.rx_fifo_errors++; | |
| } | |
| /* good packet ? */ | |
| else { | |
| struct sk_buff *skb; | |
| skb = dev_alloc_skb(descr.Len + 2); | |
| if (skb == NULL) | |
| priv->stat.rx_dropped++; | |
| else { | |
| memcpy_fromio(skb_put(skb, descr.Len), | |
| priv->base + | |
| descr.LowAddr, descr.Len); | |
| skb->dev = dev; | |
| skb->protocol = eth_type_trans(skb, dev); | |
| skb->ip_summed = CHECKSUM_NONE; | |
| priv->stat.rx_packets++; | |
| priv->stat.rx_bytes += descr.Len; | |
| netif_rx(skb); | |
| dev->last_rx = jiffies; | |
| } | |
| } | |
| /* give descriptor back to LANCE */ | |
| descr.Len = 0; | |
| descr.Flags |= RXDSCR_FLAGS_OWN; | |
| /* update descriptor in shared RAM */ | |
| memcpy_toio(priv->base + descraddr, &descr, | |
| sizeof(LANCE_RxDescr)); | |
| /* go to next descriptor */ | |
| priv->nextrx++; | |
| descraddr += sizeof(LANCE_RxDescr); | |
| if (priv->nextrx >= RXCOUNT) { | |
| priv->nextrx = 0; | |
| descraddr = RAM_RXBASE; | |
| } | |
| } | |
| /* reset RINT bit */ | |
| SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_RINT); | |
| return GetLANCE(dev, LANCE_CSR0); | |
| } | |
| /* transmit interrupt */ | |
| static u16 irqtx_handler(struct net_device *dev, u16 oldcsr0) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| LANCE_TxDescr descr; | |
| unsigned int descraddr; | |
| /* check descriptors at most until no busy one is left */ | |
| descraddr = | |
| RAM_TXBASE + (priv->nexttxdone * sizeof(LANCE_TxDescr)); | |
| while (priv->txbusy > 0) { | |
| /* read descriptor */ | |
| memcpy_fromio(&descr, priv->base + descraddr, | |
| sizeof(LANCE_TxDescr)); | |
| /* if the LANCE still owns this one, we've worked out all sent packets */ | |
| if ((descr.Flags & TXDSCR_FLAGS_OWN) != 0) | |
| break; | |
| #ifdef DEBUG | |
| PrTime(); | |
| printk("Send packet done on descr %d\n", priv->nexttxdone); | |
| #endif | |
| /* update statistics */ | |
| if ((descr.Flags & TXDSCR_FLAGS_ERR) == 0) { | |
| priv->stat.tx_packets++; | |
| priv->stat.tx_bytes++; | |
| } else { | |
| priv->stat.tx_errors++; | |
| if ((descr.Status & TXDSCR_STATUS_UFLO) != 0) { | |
| priv->stat.tx_fifo_errors++; | |
| InitLANCE(dev); | |
| } | |
| else | |
| if ((descr.Status & TXDSCR_STATUS_LCOL) != | |
| 0) priv->stat.tx_window_errors++; | |
| else if ((descr.Status & TXDSCR_STATUS_LCAR) != 0) | |
| priv->stat.tx_carrier_errors++; | |
| else if ((descr.Status & TXDSCR_STATUS_RTRY) != 0) | |
| priv->stat.tx_aborted_errors++; | |
| } | |
| /* go to next descriptor */ | |
| priv->nexttxdone++; | |
| descraddr += sizeof(LANCE_TxDescr); | |
| if (priv->nexttxdone >= TXCOUNT) { | |
| priv->nexttxdone = 0; | |
| descraddr = RAM_TXBASE; | |
| } | |
| priv->txbusy--; | |
| } | |
| /* reset TX interrupt bit */ | |
| SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_TINT); | |
| oldcsr0 = GetLANCE(dev, LANCE_CSR0); | |
| /* at least one descriptor is freed. Therefore we can accept | |
| a new one */ | |
| /* inform upper layers we're in business again */ | |
| netif_wake_queue(dev); | |
| return oldcsr0; | |
| } | |
| /* general interrupt entry */ | |
| static irqreturn_t irq_handler(int irq, void *device) | |
| { | |
| struct net_device *dev = (struct net_device *) device; | |
| u16 csr0val; | |
| /* read CSR0 to get interrupt cause */ | |
| csr0val = GetLANCE(dev, LANCE_CSR0); | |
| /* in case we're not meant... */ | |
| if ((csr0val & CSR0_INTR) == 0) | |
| return IRQ_NONE; | |
| #if 0 | |
| set_bit(LINK_STATE_RXSEM, &dev->state); | |
| #endif | |
| /* loop through the interrupt bits until everything is clear */ | |
| do { | |
| if ((csr0val & CSR0_IDON) != 0) | |
| csr0val = irqstart_handler(dev, csr0val); | |
| if ((csr0val & CSR0_RINT) != 0) | |
| csr0val = irqrx_handler(dev, csr0val); | |
| if ((csr0val & CSR0_MISS) != 0) | |
| csr0val = irqmiss_handler(dev, csr0val); | |
| if ((csr0val & CSR0_TINT) != 0) | |
| csr0val = irqtx_handler(dev, csr0val); | |
| if ((csr0val & CSR0_MERR) != 0) { | |
| SetLANCE(dev, LANCE_CSR0, csr0val | CSR0_MERR); | |
| csr0val = GetLANCE(dev, LANCE_CSR0); | |
| } | |
| if ((csr0val & CSR0_BABL) != 0) { | |
| SetLANCE(dev, LANCE_CSR0, csr0val | CSR0_BABL); | |
| csr0val = GetLANCE(dev, LANCE_CSR0); | |
| } | |
| } | |
| while ((csr0val & CSR0_INTR) != 0); | |
| #if 0 | |
| clear_bit(LINK_STATE_RXSEM, &dev->state); | |
| #endif | |
| return IRQ_HANDLED; | |
| } | |
| /* ------------------------------------------------------------------------ | |
| * driver methods | |
| * ------------------------------------------------------------------------ */ | |
| /* MCA info */ | |
| static int skmca_getinfo(char *buf, int slot, void *d) | |
| { | |
| int len = 0, i; | |
| struct net_device *dev = (struct net_device *) d; | |
| skmca_priv *priv; | |
| /* can't say anything about an uninitialized device... */ | |
| if (dev == NULL) | |
| return len; | |
| priv = netdev_priv(dev); | |
| /* print info */ | |
| len += sprintf(buf + len, "IRQ: %d\n", priv->realirq); | |
| len += sprintf(buf + len, "Memory: %#lx-%#lx\n", dev->mem_start, | |
| dev->mem_end - 1); | |
| len += | |
| sprintf(buf + len, "Transceiver: %s\n", | |
| MediaNames[priv->medium]); | |
| len += sprintf(buf + len, "Device: %s\n", dev->name); | |
| len += sprintf(buf + len, "MAC address:"); | |
| for (i = 0; i < 6; i++) | |
| len += sprintf(buf + len, " %02x", dev->dev_addr[i]); | |
| buf[len++] = '\n'; | |
| buf[len] = 0; | |
| return len; | |
| } | |
| /* open driver. Means also initialization and start of LANCE */ | |
| static int skmca_open(struct net_device *dev) | |
| { | |
| int result; | |
| skmca_priv *priv = netdev_priv(dev); | |
| /* register resources - only necessary for IRQ */ | |
| result = | |
| request_irq(priv->realirq, irq_handler, | |
| IRQF_SHARED | IRQF_SAMPLE_RANDOM, "sk_mca", dev); | |
| if (result != 0) { | |
| printk("%s: failed to register irq %d\n", dev->name, | |
| dev->irq); | |
| return result; | |
| } | |
| dev->irq = priv->realirq; | |
| /* set up the card and LANCE */ | |
| InitBoard(dev); | |
| /* set up flags */ | |
| netif_start_queue(dev); | |
| return 0; | |
| } | |
| /* close driver. Shut down board and free allocated resources */ | |
| static int skmca_close(struct net_device *dev) | |
| { | |
| /* turn off board */ | |
| DeinitBoard(dev); | |
| /* release resources */ | |
| if (dev->irq != 0) | |
| free_irq(dev->irq, dev); | |
| dev->irq = 0; | |
| return 0; | |
| } | |
| /* transmit a block. */ | |
| static int skmca_tx(struct sk_buff *skb, struct net_device *dev) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| LANCE_TxDescr descr; | |
| unsigned int address; | |
| int tmplen, retval = 0; | |
| unsigned long flags; | |
| /* if we get called with a NULL descriptor, the Ethernet layer thinks | |
| our card is stuck an we should reset it. We'll do this completely: */ | |
| if (skb == NULL) { | |
| DeinitBoard(dev); | |
| InitBoard(dev); | |
| return 0; /* don't try to free the block here ;-) */ | |
| } | |
| /* is there space in the Tx queue ? If no, the upper layer gave us a | |
| packet in spite of us not being ready and is really in trouble. | |
| We'll do the dropping for him: */ | |
| if (priv->txbusy >= TXCOUNT) { | |
| priv->stat.tx_dropped++; | |
| retval = -EIO; | |
| goto tx_done; | |
| } | |
| /* get TX descriptor */ | |
| address = RAM_TXBASE + (priv->nexttxput * sizeof(LANCE_TxDescr)); | |
| memcpy_fromio(&descr, priv->base + address, sizeof(LANCE_TxDescr)); | |
| /* enter packet length as 2s complement - assure minimum length */ | |
| tmplen = skb->len; | |
| if (tmplen < 60) | |
| tmplen = 60; | |
| descr.Len = 65536 - tmplen; | |
| /* copy filler into RAM - in case we're filling up... | |
| we're filling a bit more than necessary, but that doesn't harm | |
| since the buffer is far larger... */ | |
| if (tmplen > skb->len) { | |
| char *fill = "NetBSD is a nice OS too! "; | |
| unsigned int destoffs = 0, l = strlen(fill); | |
| while (destoffs < tmplen) { | |
| memcpy_toio(priv->base + descr.LowAddr + | |
| destoffs, fill, l); | |
| destoffs += l; | |
| } | |
| } | |
| /* do the real data copying */ | |
| memcpy_toio(priv->base + descr.LowAddr, skb->data, skb->len); | |
| /* hand descriptor over to LANCE - this is the first and last chunk */ | |
| descr.Flags = | |
| TXDSCR_FLAGS_OWN | TXDSCR_FLAGS_STP | TXDSCR_FLAGS_ENP; | |
| #ifdef DEBUG | |
| PrTime(); | |
| printk("Send packet on descr %d len %d\n", priv->nexttxput, | |
| skb->len); | |
| #endif | |
| /* one more descriptor busy */ | |
| spin_lock_irqsave(&priv->lock, flags); | |
| priv->nexttxput++; | |
| if (priv->nexttxput >= TXCOUNT) | |
| priv->nexttxput = 0; | |
| priv->txbusy++; | |
| /* are we saturated ? */ | |
| if (priv->txbusy >= TXCOUNT) | |
| netif_stop_queue(dev); | |
| /* write descriptor back to RAM */ | |
| memcpy_toio(priv->base + address, &descr, sizeof(LANCE_TxDescr)); | |
| /* if no descriptors were active, give the LANCE a hint to read it | |
| immediately */ | |
| if (priv->txbusy == 0) | |
| SetLANCE(dev, LANCE_CSR0, CSR0_INEA | CSR0_TDMD); | |
| spin_unlock_irqrestore(&priv->lock, flags); | |
| tx_done: | |
| dev_kfree_skb(skb); | |
| return retval; | |
| } | |
| /* return pointer to Ethernet statistics */ | |
| static struct net_device_stats *skmca_stats(struct net_device *dev) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| return &(priv->stat); | |
| } | |
| /* switch receiver mode. We use the LANCE's multicast filter to prefilter | |
| multicast addresses. */ | |
| static void skmca_set_multicast_list(struct net_device *dev) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| LANCE_InitBlock block; | |
| /* first stop the LANCE... */ | |
| StopLANCE(dev); | |
| /* ...then modify the initialization block... */ | |
| memcpy_fromio(&block, priv->base + RAM_INITBASE, sizeof(block)); | |
| if (dev->flags & IFF_PROMISC) | |
| block.Mode |= LANCE_INIT_PROM; | |
| else | |
| block.Mode &= ~LANCE_INIT_PROM; | |
| if (dev->flags & IFF_ALLMULTI) { /* get all multicasts */ | |
| memset(block.LAdrF, 0xff, sizeof(block.LAdrF)); | |
| } else { /* get selected/no multicasts */ | |
| struct dev_mc_list *mptr; | |
| int code; | |
| memset(block.LAdrF, 0, sizeof(block.LAdrF)); | |
| for (mptr = dev->mc_list; mptr != NULL; mptr = mptr->next) { | |
| code = GetHash(mptr->dmi_addr); | |
| block.LAdrF[(code >> 3) & 7] |= 1 << (code & 7); | |
| } | |
| } | |
| memcpy_toio(priv->base + RAM_INITBASE, &block, sizeof(block)); | |
| /* ...then reinit LANCE with the correct flags */ | |
| InitLANCE(dev); | |
| } | |
| /* ------------------------------------------------------------------------ | |
| * hardware check | |
| * ------------------------------------------------------------------------ */ | |
| static int startslot; /* counts through slots when probing multiple devices */ | |
| static void cleanup_card(struct net_device *dev) | |
| { | |
| skmca_priv *priv = netdev_priv(dev); | |
| DeinitBoard(dev); | |
| if (dev->irq != 0) | |
| free_irq(dev->irq, dev); | |
| iounmap(priv->base); | |
| mca_mark_as_unused(priv->slot); | |
| mca_set_adapter_procfn(priv->slot, NULL, NULL); | |
| } | |
| struct net_device * __init skmca_probe(int unit) | |
| { | |
| struct net_device *dev; | |
| int force_detect = 0; | |
| int junior, slot, i; | |
| int base = 0, irq = 0; | |
| skmca_priv *priv; | |
| skmca_medium medium; | |
| int err; | |
| /* can't work without an MCA bus ;-) */ | |
| if (MCA_bus == 0) | |
| return ERR_PTR(-ENODEV); | |
| dev = alloc_etherdev(sizeof(skmca_priv)); | |
| if (!dev) | |
| return ERR_PTR(-ENOMEM); | |
| if (unit >= 0) { | |
| sprintf(dev->name, "eth%d", unit); | |
| netdev_boot_setup_check(dev); | |
| } | |
| SET_MODULE_OWNER(dev); | |
| /* start address of 1 --> forced detection */ | |
| if (dev->mem_start == 1) | |
| force_detect = 1; | |
| /* search through slots */ | |
| base = dev->mem_start; | |
| irq = dev->base_addr; | |
| for (slot = startslot; (slot = dofind(&junior, slot)) != -1; slot++) { | |
| /* deduce card addresses */ | |
| getaddrs(slot, junior, &base, &irq, &medium); | |
| /* slot already in use ? */ | |
| if (mca_is_adapter_used(slot)) | |
| continue; | |
| /* were we looking for something different ? */ | |
| if (dev->irq && dev->irq != irq) | |
| continue; | |
| if (dev->mem_start && dev->mem_start != base) | |
| continue; | |
| /* found something that matches */ | |
| break; | |
| } | |
| /* nothing found ? */ | |
| if (slot == -1) { | |
| free_netdev(dev); | |
| return (base || irq) ? ERR_PTR(-ENXIO) : ERR_PTR(-ENODEV); | |
| } | |
| /* make procfs entries */ | |
| if (junior) | |
| mca_set_adapter_name(slot, | |
| "SKNET junior MC2 Ethernet Adapter"); | |
| else | |
| mca_set_adapter_name(slot, "SKNET MC2+ Ethernet Adapter"); | |
| mca_set_adapter_procfn(slot, (MCA_ProcFn) skmca_getinfo, dev); | |
| mca_mark_as_used(slot); | |
| /* announce success */ | |
| printk("%s: SKNet %s adapter found in slot %d\n", dev->name, | |
| junior ? "Junior MC2" : "MC2+", slot + 1); | |
| priv = netdev_priv(dev); | |
| priv->base = ioremap(base, 0x4000); | |
| if (!priv->base) { | |
| mca_set_adapter_procfn(slot, NULL, NULL); | |
| mca_mark_as_unused(slot); | |
| free_netdev(dev); | |
| return ERR_PTR(-ENOMEM); | |
| } | |
| priv->slot = slot; | |
| priv->macbase = priv->base + 0x3fc0; | |
| priv->ioregaddr = priv->base + 0x3ff0; | |
| priv->ctrladdr = priv->base + 0x3ff2; | |
| priv->cmdaddr = priv->base + 0x3ff3; | |
| priv->medium = medium; | |
| memset(&priv->stat, 0, sizeof(struct net_device_stats)); | |
| spin_lock_init(&priv->lock); | |
| /* set base + irq for this device (irq not allocated so far) */ | |
| dev->irq = 0; | |
| dev->mem_start = base; | |
| dev->mem_end = base + 0x4000; | |
| /* autoprobe ? */ | |
| if (irq < 0) { | |
| int nirq; | |
| printk | |
| ("%s: ambigous POS bit combination, must probe for IRQ...\n", | |
| dev->name); | |
| nirq = ProbeIRQ(dev); | |
| if (nirq <= 0) | |
| printk("%s: IRQ probe failed, assuming IRQ %d", | |
| dev->name, priv->realirq = -irq); | |
| else | |
| priv->realirq = nirq; | |
| } else | |
| priv->realirq = irq; | |
| /* set methods */ | |
| dev->open = skmca_open; | |
| dev->stop = skmca_close; | |
| dev->hard_start_xmit = skmca_tx; | |
| dev->do_ioctl = NULL; | |
| dev->get_stats = skmca_stats; | |
| dev->set_multicast_list = skmca_set_multicast_list; | |
| dev->flags |= IFF_MULTICAST; | |
| /* copy out MAC address */ | |
| for (i = 0; i < 6; i++) | |
| dev->dev_addr[i] = readb(priv->macbase + (i << 1)); | |
| /* print config */ | |
| printk("%s: IRQ %d, memory %#lx-%#lx, " | |
| "MAC address %02x:%02x:%02x:%02x:%02x:%02x.\n", | |
| dev->name, priv->realirq, dev->mem_start, dev->mem_end - 1, | |
| dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], | |
| dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); | |
| printk("%s: %s medium\n", dev->name, MediaNames[priv->medium]); | |
| /* reset board */ | |
| ResetBoard(dev); | |
| startslot = slot + 1; | |
| err = register_netdev(dev); | |
| if (err) { | |
| cleanup_card(dev); | |
| free_netdev(dev); | |
| dev = ERR_PTR(err); | |
| } | |
| return dev; | |
| } | |
| /* ------------------------------------------------------------------------ | |
| * modularization support | |
| * ------------------------------------------------------------------------ */ | |
| #ifdef MODULE | |
| MODULE_LICENSE("GPL"); | |
| #define DEVMAX 5 | |
| static struct net_device *moddevs[DEVMAX]; | |
| int init_module(void) | |
| { | |
| int z; | |
| startslot = 0; | |
| for (z = 0; z < DEVMAX; z++) { | |
| struct net_device *dev = skmca_probe(-1); | |
| if (IS_ERR(dev)) | |
| break; | |
| moddevs[z] = dev; | |
| } | |
| if (!z) | |
| return -EIO; | |
| return 0; | |
| } | |
| void cleanup_module(void) | |
| { | |
| int z; | |
| for (z = 0; z < DEVMAX; z++) { | |
| struct net_device *dev = moddevs[z]; | |
| if (dev) { | |
| unregister_netdev(dev); | |
| cleanup_card(dev); | |
| free_netdev(dev); | |
| } | |
| } | |
| } | |
| #endif /* MODULE */ |