/* * RTEMS driver for TULIP based Ethernet Controller * * $Header$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef malloc #undef malloc #endif #ifdef free #undef free #endif #define PCI_VENDOR_ID_DEC 0x1011 #define PCI_DEVICE_ID_DEC_TULIP_FAST 0x0009 #define IO_MASK 0x3 #define MEM_MASK 0xF /* command and status registers, 32-bit access, only if IO-ACCESS */ #define ioCSR0 0x00 /* bus mode register */ #define ioCSR1 0x08 /* transmit poll demand */ #define ioCSR2 0x10 /* receive poll demand */ #define ioCSR3 0x18 /* receive list base address */ #define ioCSR4 0x20 /* transmit list base address */ #define ioCSR5 0x28 /* status register */ #define ioCSR6 0x30 /* operation mode register */ #define ioCSR7 0x38 /* interrupt mask register */ #define ioCSR8 0x40 /* missed frame counter */ #define ioCSR9 0x48 /* Ethernet ROM register */ #define ioCSR10 0x50 /* reserved */ #define ioCSR11 0x58 /* full-duplex register */ #define ioCSR12 0x60 /* SIA status register */ #define ioCSR13 0x68 #define ioCSR14 0x70 #define ioCSR15 0x78 /* SIA general register */ /* command and status registers, 32-bit access, only if MEMORY-ACCESS */ #define memCSR0 0x00 /* bus mode register */ #define memCSR1 0x02 /* transmit poll demand */ #define memCSR2 0x04 /* receive poll demand */ #define memCSR3 0x06 /* receive list base address */ #define memCSR4 0x08 /* transmit list base address */ #define memCSR5 0x0A /* status register */ #define memCSR6 0x0C /* operation mode register */ #define memCSR7 0x0E /* interrupt mask register */ #define memCSR8 0x10 /* missed frame counter */ #define memCSR9 0x12 /* Ethernet ROM register */ #define memCSR10 0x14 /* reserved */ #define memCSR11 0x16 /* full-duplex register */ #define memCSR12 0x18 /* SIA status register */ #define memCSR13 0x1A #define memCSR14 0x1C #define memCSR15 0x1E /* SIA general register */ #define DEC_REGISTER_SIZE 0x100 /* to reserve virtual memory */ #define RESET_CHIP 0x00000001 #define CSR0_MODE 0x01a08000 /* 01a08000 */ #define ROM_ADDRESS 0x00004800 #define CSR6_INIT 0x020c0000 /* 020c0000 */ #define CSR6_TX 0x00002000 #define CSR6_TXRX 0x00002002 #define IT_SETUP 0x00010040 /* 0001ebef */ #define CLEAR_IT 0xFFFFFFFF #define NO_IT 0x00000000 #define NRXBUFS 7 /* number of receive buffers */ #define NTXBUFS 1 /* number of transmit buffers */ /* message descriptor entry */ struct MD { volatile unsigned long status; volatile unsigned long counts; unsigned long buf1, buf2; }; /* * Number of WDs supported by this driver */ #define NDECDRIVER 1 /* * Receive buffer size -- Allow for a full ethernet packet including CRC */ #define RBUF_SIZE 1520 #define ET_MINLEN 60 /* minimum message length */ /* * RTEMS event used by interrupt handler to signal driver tasks. * This must not be any of the events used by the network task synchronization. */ #define INTERRUPT_EVENT RTEMS_EVENT_1 /* * RTEMS event used to start transmit daemon. * This must not be the same as INTERRUPT_EVENT. */ #define START_TRANSMIT_EVENT RTEMS_EVENT_2 #if (MCLBYTES < RBUF_SIZE) # error "Driver must have MCLBYTES > RBUF_SIZE" #endif /* * Per-device data */ struct dec21140_softc { struct arpcom arpcom; rtems_irq_connect_data irqInfo; struct MD *MDbase; char *bufferBase; int acceptBroadcast; int rxBdCount; int txBdCount; rtems_id rxDaemonTid; rtems_id txDaemonTid; unsigned int port; unsigned int *base; unsigned long bpar; /* * Statistics */ unsigned long rxInterrupts; unsigned long rxNotFirst; unsigned long rxNotLast; unsigned long rxGiant; unsigned long rxNonOctet; unsigned long rxRunt; unsigned long rxBadCRC; unsigned long rxOverrun; unsigned long rxCollision; unsigned long txInterrupts; unsigned long txDeferred; unsigned long txHeartbeat; unsigned long txLateCollision; unsigned long txRetryLimit; unsigned long txUnderrun; unsigned long txLostCarrier; unsigned long txRawWait; }; static struct dec21140_softc dec21140_softc[NDECDRIVER]; /* * DEC21140 interrupt handler */ static rtems_isr dec21140Enet_interrupt_handler (rtems_vector_number v) { unsigned int *tbase; unsigned long status; unsigned int sc; tbase = dec21140_softc[0].base ; /* * Read status */ *(tbase+memCSR7) = NO_IT; status = *(tbase+memCSR5); *(tbase+memCSR5) = CLEAR_IT; /* * Frame received? */ if (status & 0x00000040){ dec21140_softc[0].rxInterrupts++; sc = rtems_event_send (dec21140_softc[0].rxDaemonTid, INTERRUPT_EVENT); } } static void nopOn(const rtems_irq_connect_data* notUsed) { /* * code should be moved from dec21140Enet_initialize_hardware * to this location */ } static int dec21140IsOn(const rtems_irq_connect_data* irq) { return BSP_irq_enabled_at_i8259s (irq->name); } /* * Read and write the MII registers using software-generated serial * MDIO protocol. */ #define MDIO_SHIFT_CLK 0x10000 #define MDIO_DATA_WRITE0 0x00000 #define MDIO_DATA_WRITE1 0x20000 #define MDIO_ENB 0x00000 #define MDIO_ENB_IN 0x40000 #define MDIO_DATA_READ 0x80000 static int mdio_read(unsigned int *ioaddr, int phy_id, int location) { int i, i3; int read_cmd = (0xf6 << 10) | (phy_id << 5) | location; unsigned short retval = 0; /* Establish sync by sending at least 32 logic ones. */ for (i = 32; i >= 0; i--) { *ioaddr = MDIO_ENB | MDIO_DATA_WRITE1; for(i3=0; i3<1000; i3++); *ioaddr = MDIO_ENB | MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK; for(i3=0; i3<1000; i3++); } /* Shift the read command bits out. */ for (i = 17; i >= 0; i--) { int dataval = (read_cmd & (1 << i)) ? MDIO_DATA_WRITE1 : 0; *ioaddr = dataval; for(i3=0; i3<1000; i3++); *ioaddr = dataval | MDIO_SHIFT_CLK; for(i3=0; i3<1000; i3++); *ioaddr = dataval; for(i3=0; i3<1000; i3++); } *ioaddr = MDIO_ENB_IN | MDIO_SHIFT_CLK; for(i3=0; i3<1000; i3++); *ioaddr = MDIO_ENB_IN; for (i = 16; i > 0; i--) { *ioaddr = MDIO_ENB_IN | MDIO_SHIFT_CLK; for(i3=0; i3<1000; i3++); retval = (retval << 1) | ((*ioaddr & MDIO_DATA_READ) ? 1 : 0); *ioaddr = MDIO_ENB_IN; for(i3=0; i3<1000; i3++); } /* Clear out extra bits. */ for (i = 16; i > 0; i--) { *ioaddr = MDIO_ENB_IN | MDIO_SHIFT_CLK; for(i3=0; i3<1000; i3++); *ioaddr = MDIO_ENB_IN; for(i3=0; i3<1000; i3++); } return retval; } static int mdio_write(unsigned int *ioaddr, int phy_id, int location, int value) { int i, i3; int cmd = (0x5002 << 16) | (phy_id << 23) | (location << 18) | value; /* Establish sync by sending at least 32 logic ones. */ for (i = 32; i >= 0; i--) { *ioaddr = MDIO_ENB | MDIO_DATA_WRITE1; for(i3=0; i3<1000; i3++); *ioaddr = MDIO_ENB | MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK; for(i3=0; i3<1000; i3++); } /* Shift the read command bits out. */ for (i = 31; i >= 0; i--) { int dataval = (cmd & (1 << i)) ? MDIO_DATA_WRITE1 : 0; *ioaddr = dataval; for(i3=0; i3<1000; i3++); *ioaddr = dataval | MDIO_SHIFT_CLK; for(i3=0; i3<1000; i3++); } /* Clear out extra bits. */ for (i = 2; i > 0; i--) { *ioaddr = MDIO_ENB_IN; for(i3=0; i3<1000; i3++); *ioaddr = MDIO_ENB_IN | MDIO_SHIFT_CLK; for(i3=0; i3<1000; i3++); } return 0; } /* * This routine reads a word (16 bits) from the serial EEPROM. */ /* EEPROM_Ctrl bits. */ #define EE_SHIFT_CLK 0x02 /* EEPROM shift clock. */ #define EE_CS 0x01 /* EEPROM chip select. */ #define EE_DATA_WRITE 0x04 /* EEPROM chip data in. */ #define EE_WRITE_0 0x01 #define EE_WRITE_1 0x05 #define EE_DATA_READ 0x08 /* EEPROM chip data out. */ #define EE_ENB (0x4800 | EE_CS) /* The EEPROM commands include the alway-set leading bit. */ #define EE_WRITE_CMD (5 << 6) #define EE_READ_CMD (6 << 6) #define EE_ERASE_CMD (7 << 6) static int eeget16(unsigned int *ioaddr, int location) { int i, i3; unsigned short retval = 0; int read_cmd = location | EE_READ_CMD; *ioaddr = EE_ENB & ~EE_CS; *ioaddr = EE_ENB; /* Shift the read command bits out. */ for (i = 10; i >= 0; i--) { short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0; *ioaddr = EE_ENB | dataval; for (i3=0; i3<1000; i3++) ; *ioaddr = EE_ENB | dataval | EE_SHIFT_CLK; for (i3=0; i3<1000; i3++) ; *ioaddr = EE_ENB | dataval; /* Finish EEPROM a clock tick. */ for (i3=0; i3<1000; i3++) ; } *ioaddr = EE_ENB; for (i = 16; i > 0; i--) { *ioaddr = EE_ENB | EE_SHIFT_CLK; for (i3=0; i3<1000; i3++) ; retval = (retval << 1) | ((*ioaddr & EE_DATA_READ) ? 1 : 0); *ioaddr = EE_ENB; for (i3=0; i3<1000; i3++) ; } /* Terminate the EEPROM access. */ *ioaddr = EE_ENB & ~EE_CS; return retval; } /* * Initialize the ethernet hardware */ static void dec21140Enet_initialize_hardware (struct dec21140_softc *sc) { rtems_status_code st; unsigned int *tbase; union {char c[64]; unsigned short s[32];} rombuf; int i, i2, i3; char *cp, direction, *setup_frm, *eaddrs; unsigned long csr12_val, mii_reg0; unsigned char *buffer; struct MD *rmd; tbase = sc->base; /* * WARNING : First write in CSR6 * Then Reset the chip ( 1 in CSR0) */ *(tbase+memCSR6) = CSR6_INIT; *(tbase+memCSR0) = RESET_CHIP; for(i3=0; i3<1000; i3++); /* * Init CSR0 */ *(tbase+memCSR0) = CSR0_MODE; csr12_val = *(tbase+memCSR8); for (i=0; i<32; i++) rombuf.s[i] = eeget16(tbase+memCSR9, i); memcpy (sc->arpcom.ac_enaddr, rombuf.c+20, ETHER_ADDR_LEN); mii_reg0 = mdio_read(tbase+memCSR9, 0, 0); mdio_write(tbase+memCSR9, 0, 0, mii_reg0 | 0x1000); #ifdef DEC_DEBUG printk("DC21140 %x:%x:%x:%x:%x:%x IRQ %d IO %x M %x .........\n", sc->arpcom.ac_enaddr[0], sc->arpcom.ac_enaddr[1], sc->arpcom.ac_enaddr[2], sc->arpcom.ac_enaddr[3], sc->arpcom.ac_enaddr[4], sc->arpcom.ac_enaddr[5], sc->irqInfo.name, sc->port, sc->base); #endif /* * Init RX ring */ sc->rxBdCount = 0; cp = (char *)malloc((NRXBUFS+NTXBUFS)*(sizeof(struct MD)+ RBUF_SIZE) + PG_SIZE); sc->bufferBase = cp; cp += (PG_SIZE - (int)cp) & MASK_OFFSET; #ifdef PCI_BRIDGE_DOES_NOT_ENSURE_CACHE_COHERENCY_FOR_DMA if (_CPU_is_paging_enabled()) _CPU_change_memory_mapping_attribute (NULL, cp, (NRXBUFS+NTXBUFS)*(sizeof(struct MD)+ RBUF_SIZE), PTE_CACHE_DISABLE | PTE_WRITABLE); #endif rmd = (struct MD*)cp; sc->MDbase = rmd; buffer = cp + ((NRXBUFS+NTXBUFS)*sizeof(struct MD)); *(tbase+memCSR3) = (long)(sc->MDbase); for (i=0 ; ibuf2 = 0; rmd->buf1 = (unsigned long)(buffer + (i*RBUF_SIZE)); if (i == NRXBUFS-1) rmd->counts = 0xfec00000 | (RBUF_SIZE); else rmd->counts = 0xfcc00000 | (RBUF_SIZE); rmd->status = 0x80000000; rmd++; } /* * Init TX ring */ sc->txBdCount = 0; *(tbase+memCSR4) = (long)(rmd); rmd->buf2 = 0; rmd->buf1 = (unsigned long)(buffer + (NRXBUFS*RBUF_SIZE)); rmd->counts = 0x62000000; rmd->status = 0x0; /* * Set up interrupts */ *(tbase+memCSR5) = IT_SETUP; *(tbase+memCSR7) = IT_SETUP; sc->irqInfo.hdl = (rtems_irq_hdl)dec21140Enet_interrupt_handler; sc->irqInfo.on = nopOn; sc->irqInfo.off = nopOn; sc->irqInfo.isOn = dec21140IsOn; st = BSP_install_rtems_irq_handler (&sc->irqInfo); if (!st) rtems_panic ("Can't attach DEC21140 interrupt handler for irq %d\n", sc->irqInfo.name); /* * Start TX for setup frame */ *(tbase+memCSR6) = CSR6_INIT | CSR6_TX; /* * Build setup frame */ setup_frm = (char *)(rmd->buf1); eaddrs = (char *)(sc->arpcom.ac_enaddr); /* Fill the buffer with our physical address. */ for (i = 1; i < 16; i++) { *setup_frm++ = eaddrs[0]; *setup_frm++ = eaddrs[1]; setup_frm += 2; *setup_frm++ = eaddrs[2]; *setup_frm++ = eaddrs[3]; setup_frm += 2; *setup_frm++ = eaddrs[4]; *setup_frm++ = eaddrs[5]; setup_frm += 2; } /* Add the broadcast address when doing perfect filtering */ memset(setup_frm, 0xff, 12); rmd->counts = 0x0a000000 | 192 ; rmd->status = 0x80000000; *(tbase+memCSR1) = 1; while (rmd->status != 0x7fffffff); /* * Enable RX and TX */ *(tbase+memCSR6) = CSR6_INIT | CSR6_TXRX; /* * Set up PHY */ i = rombuf.c[27]; i+=2; direction = rombuf.c[i]; i +=4; *(tbase+memCSR12) = direction | 0x100; for (i2 = 0; i2 < rombuf.c[(i+2) + rombuf.c[i+1]]; i2++){ *(tbase + memCSR12) = rombuf.c[(i+3) + rombuf.c[i+1] + i2]; } for (i2 = 0; i2 < rombuf.c[i+1]; i2++){ *(tbase + memCSR12) = rombuf.c[(i+2) + i2]; } } static void dec21140_rxDaemon (void *arg) { unsigned int *tbase; struct ether_header *eh; struct dec21140_softc *dp = (struct dec21140_softc *)&dec21140_softc[0]; struct ifnet *ifp = &dp->arpcom.ac_if; struct mbuf *m; struct MD *rmd; unsigned int len; char *temp; rtems_event_set events; int nbMD; tbase = dec21140_softc[0].base ; for (;;){ rtems_bsdnet_event_receive (INTERRUPT_EVENT, RTEMS_WAIT|RTEMS_EVENT_ANY, RTEMS_NO_TIMEOUT, &events); rmd = dec21140_softc[0].MDbase; nbMD = 0; while (nbMD < NRXBUFS){ if ( (rmd->status & 0x80000000) == 0){ len = (rmd->status >> 16) & 0x7ff; MGETHDR (m, M_WAIT, MT_DATA); MCLGET (m, M_WAIT); m->m_pkthdr.rcvif = ifp; temp = m->m_data; m->m_len = m->m_pkthdr.len = len - sizeof(struct ether_header); memcpy(temp, (char *)rmd->buf1, len); rmd->status = 0x80000000; eh = mtod (m, struct ether_header *); m->m_data += sizeof(struct ether_header); ether_input (ifp, eh, m); } rmd++; nbMD++; } *(tbase+memCSR7) = IT_SETUP; } } static void sendpacket (struct ifnet *ifp, struct mbuf *m) { struct dec21140_softc *dp = ifp->if_softc; volatile struct MD *tmd; unsigned char *temp; struct mbuf *n; unsigned int len; unsigned int *tbase; tbase = dp->base; /* * Waiting for Transmitter ready */ tmd = dec21140_softc[0].MDbase + NRXBUFS; while ( (tmd->status & 0x80000000) != 0 ); len = 0; n = m; temp = (char *)(tmd->buf1); for (;;){ len += m->m_len; memcpy(temp, (char *)m->m_data, m->m_len); temp += m->m_len ; if ((m = m->m_next) == NULL) break; } if (len < ET_MINLEN) len = ET_MINLEN; tmd->counts = 0xe2000000 | len; tmd->status = 0x80000000; *(tbase+memCSR1) = 0x1; m_freem(n); } /* * Driver transmit daemon */ void dec21140_txDaemon (void *arg) { struct dec21140_softc *sc = (struct dec21140_softc *)arg; struct ifnet *ifp = &sc->arpcom.ac_if; struct mbuf *m; rtems_event_set events; for (;;) { /* * Wait for packet */ rtems_bsdnet_event_receive (START_TRANSMIT_EVENT, RTEMS_EVENT_ANY | RTEMS_WAIT, RTEMS_NO_TIMEOUT, &events); /* * Send packets till queue is empty */ for (;;) { /* * Get the next mbuf chain to transmit. */ IF_DEQUEUE(&ifp->if_snd, m); if (!m) break; sendpacket (ifp, m); } ifp->if_flags &= ~IFF_OACTIVE; } } static void dec21140_start (struct ifnet *ifp) { struct dec21140_softc *sc = ifp->if_softc; rtems_event_send (sc->txDaemonTid, START_TRANSMIT_EVENT); ifp->if_flags |= IFF_OACTIVE; } /* * Initialize and start the device */ static void dec21140_init (void *arg) { struct dec21140_softc *sc = arg; struct ifnet *ifp = &sc->arpcom.ac_if; if (sc->txDaemonTid == 0) { /* * Set up DEC21140 hardware */ dec21140Enet_initialize_hardware (sc); /* * Start driver tasks */ sc->rxDaemonTid = rtems_bsdnet_newproc ("DCrx", 4096, dec21140_rxDaemon, sc); sc->txDaemonTid = rtems_bsdnet_newproc ("DCtx", 4096, dec21140_txDaemon, sc); } /* * Tell the world that we're running. */ ifp->if_flags |= IFF_RUNNING; } /* * Stop the device */ static void dec21140_stop (struct dec21140_softc *sc) { unsigned int *tbase; struct ifnet *ifp = &sc->arpcom.ac_if; ifp->if_flags &= ~IFF_RUNNING; /* * Stop the transmitter */ tbase=dec21140_softc[0].base ; *(tbase+memCSR7) = NO_IT; *(tbase+memCSR6) = CSR6_INIT; free(sc->bufferBase); } /* * Show interface statistics */ static void dec21140_stats (struct dec21140_softc *sc) { printf (" Rx Interrupts:%-8lu", sc->rxInterrupts); printf (" Not First:%-8lu", sc->rxNotFirst); printf (" Not Last:%-8lu\n", sc->rxNotLast); printf (" Giant:%-8lu", sc->rxGiant); printf (" Runt:%-8lu", sc->rxRunt); printf (" Non-octet:%-8lu\n", sc->rxNonOctet); printf (" Bad CRC:%-8lu", sc->rxBadCRC); printf (" Overrun:%-8lu", sc->rxOverrun); printf (" Collision:%-8lu\n", sc->rxCollision); printf (" Tx Interrupts:%-8lu", sc->txInterrupts); printf (" Deferred:%-8lu", sc->txDeferred); printf (" Missed Hearbeat:%-8lu\n", sc->txHeartbeat); printf (" No Carrier:%-8lu", sc->txLostCarrier); printf ("Retransmit Limit:%-8lu", sc->txRetryLimit); printf (" Late Collision:%-8lu\n", sc->txLateCollision); printf (" Underrun:%-8lu", sc->txUnderrun); printf (" Raw output wait:%-8lu\n", sc->txRawWait); } /* * Driver ioctl handler */ static int dec21140_ioctl (struct ifnet *ifp, int command, caddr_t data) { struct dec21140_softc *sc = ifp->if_softc; int error = 0; switch (command) { case SIOCGIFADDR: case SIOCSIFADDR: ether_ioctl (ifp, command, data); break; case SIOCSIFFLAGS: switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) { case IFF_RUNNING: dec21140_stop (sc); break; case IFF_UP: dec21140_init (sc); break; case IFF_UP | IFF_RUNNING: dec21140_stop (sc); dec21140_init (sc); break; default: break; } break; case SIO_RTEMS_SHOW_STATS: dec21140_stats (sc); break; /* * FIXME: All sorts of multicast commands need to be added here! */ default: error = EINVAL; break; } return error; } /* * Attach an DEC21140 driver to the system */ int rtems_dec21140_driver_attach (struct rtems_bsdnet_ifconfig *config) { struct dec21140_softc *sc; struct ifnet *ifp; int mtu; int i; int signature; int value; char interrupt; int diag; /* * Initialise PCI module */ if (pcib_init() == PCIB_ERR_NOTPRESENT) rtems_panic("PCI BIOS not found !!"); /* * First, find a DEC board */ if ((diag = pcib_find_by_devid(PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TULIP_FAST, 0, &signature)) != PCIB_ERR_SUCCESS) rtems_panic("DEC PCI board not found !! (%d)\n", diag); else { printk("DEC PCI Device found\n"); } /* * Find a free driver */ for (i = 0 ; i < NDECDRIVER ; i++) { sc = &dec21140_softc[i]; ifp = &sc->arpcom.ac_if; if (ifp->if_softc == NULL) break; } if (i >= NDECDRIVER) { printk ("Too many DEC drivers.\n"); return 0; } /* * Process options */ pcib_conf_read32(signature, 16, &value); sc->port = value & ~IO_MASK; pcib_conf_read32(signature, 20, &value); if (_CPU_is_paging_enabled()) _CPU_map_phys_address(&(sc->base), (void *)(value & ~MEM_MASK), DEC_REGISTER_SIZE , PTE_CACHE_DISABLE | PTE_WRITABLE); else sc->base = (unsigned int *)(value & ~MEM_MASK); pcib_conf_read8(signature, 60, &interrupt); sc->irqInfo.name = (rtems_irq_symbolic_name)interrupt; if (config->hardware_address) { memcpy (sc->arpcom.ac_enaddr, config->hardware_address, ETHER_ADDR_LEN); } else { memset (sc->arpcom.ac_enaddr, 0x08,ETHER_ADDR_LEN); } if (config->mtu) mtu = config->mtu; else mtu = ETHERMTU; sc->acceptBroadcast = !config->ignore_broadcast; /* * Set up network interface values */ ifp->if_softc = sc; ifp->if_unit = i + 1; ifp->if_name = "dc"; ifp->if_mtu = mtu; ifp->if_init = dec21140_init; ifp->if_ioctl = dec21140_ioctl; ifp->if_start = dec21140_start; ifp->if_output = ether_output; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX; if (ifp->if_snd.ifq_maxlen == 0) ifp->if_snd.ifq_maxlen = ifqmaxlen; /* * Attach the interface */ if_attach (ifp); ether_ifattach (ifp); return 1; };